1# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""A module containing TPM handler class used by SAFT."""
6
7FW_NV_ADDRESS = 0x1007
8KERNEL_NV_ADDRESS = 0x1008
9
10class TpmError(Exception):
11    pass
12
13class TpmNvRam(object):
14    """An object representing TPM NvRam.
15
16    Attributes:
17    addr: a number, NvRAm address in TPM.
18    size: a number, count of bites in this NvRam section.
19    os_if: an instance of the OS interface (os_interface or a mock object).
20    version_offset: - a number, offset into the NvRam contents where the the
21        versions are stored. The total version field size is 4 bytes, the
22        first two bytes are the body version, the second two bytes are the key
23        version. Numbers are stored in little endian format.
24    pattern: optional, a tuple of two elements, the first element is the
25       offset of the pattern expected to be present in the NvRam, and the
26       second element is an array of bytes the pattern must match.
27    contents: an array of bytes, the contents of the NvRam.
28    """
29
30    def __init__(self, addr, size, version_offset, data_pattern=None):
31        self.addr = addr
32        self.size = size
33        self.os_if = None
34        self.version_offset = version_offset
35        self.pattern = data_pattern
36        self.contents = []
37
38    def init(self, os_if):
39        self.os_if = os_if
40        cmd = 'tpmc read 0x%x 0x%x' % (self.addr, self.size)
41        nvram_data = self.os_if.run_shell_command_get_output(cmd)[0].split()
42        self.contents = [int(x, 16) for x in nvram_data]
43        if self.pattern:
44            pattern_offset = self.pattern[0]
45            pattern_data = self.pattern[1]
46            contents_pattern = self.contents[pattern_offset:pattern_offset +
47                                             len(pattern_data)]
48            if contents_pattern != pattern_data:
49                raise TpmError('Nvram pattern does not match')
50
51    def get_body_version(self):
52        return self.contents[
53            self.version_offset + 1] * 256 + self.contents[self.version_offset]
54
55    def get_key_version(self):
56        return self.contents[
57            self.version_offset + 3] * 256 + self.contents[
58            self.version_offset + 2]
59
60class TpmHandler(object):
61    """An object to control TPM device's NVRAM.
62
63    Attributes:
64      os_if: an instance of the OS interface (os_interface or a mock object).
65      nvrams: A dictionary where the keys are the nvram names, and the values
66          are instances of TpmNvRam objects, providing access to the
67          appropriate TPM NvRam sections.
68    """
69
70    def __init__(self):
71        self.os_if = None
72        self.nvrams = {
73            'kernel': TpmNvRam(KERNEL_NV_ADDRESS, 13, 5, (
74                    1, [0x4c, 0x57, 0x52, 0x47])),
75            'bios': TpmNvRam(FW_NV_ADDRESS, 10, 2)
76            }
77        self.trunksd_started = False
78        self.tcsd_started = False
79
80    def init(self, os_if):
81        self.os_if = os_if
82        self.stop_daemon()
83        for nvram in self.nvrams.itervalues():
84            nvram.init(self.os_if)
85        self.restart_daemon()
86
87    def get_fw_version(self):
88        return self.nvrams['bios'].get_body_version()
89
90    def get_fw_key_version(self):
91        return self.nvrams['bios'].get_key_version()
92
93    def get_kernel_version(self):
94        return self.nvrams['kernel'].get_body_version()
95
96    def get_kernel_key_version(self):
97        return self.nvrams['kernel'].get_key_version()
98
99    def stop_daemon(self):
100        """Stop TPM related daemon."""
101        if self.trunksd_started or self.tcsd_started:
102            raise TpmError('Called stop_daemon() before')
103
104        cmd = 'initctl status tcsd || initctl status trunksd'
105        status = self.os_if.run_shell_command_get_output(cmd) or ['']
106        # Expected status is like ['trunksd start/running, process 2375']
107        self.trunksd_started = status[0].startswith('trunksd start/running')
108        if self.trunksd_started:
109            self.os_if.run_shell_command('stop trunksd')
110        else:
111            self.tcsd_started = status[0].startswith('tcsd start/running')
112            if self.tcsd_started:
113                self.os_if.run_shell_command('stop tcsd')
114
115    def restart_daemon(self):
116        """Restart TPM related daemon which was stopped by stop_daemon()."""
117        if self.trunksd_started:
118            self.os_if.run_shell_command('start trunksd')
119            self.trunksd_started = False
120        elif self.tcsd_started:
121            self.os_if.run_shell_command('start tcsd')
122            self.tcsd_started = False
123