| #!/usr/bin/python |
| # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| '''A module containing TPM handler class used by SAFT.''' |
| import os |
| import re |
| import shutil |
| |
| FW_NV_ADDRESS = 0x1007 |
| KERNEL_NV_ADDRESS = 0x1008 |
| COMMENT_PATTERN = '#saft' |
| |
| class TpmError(Exception): |
| pass |
| |
| class TpmNvRam(object): |
| '''An object representing TPM NvRam. |
| |
| Attributes: |
| addr: a number, NvRAm address in TPM. |
| size: a number, count of bites in this NvRam section. |
| os_if: an instance of the OS interface (chromeos_interface or a mock |
| object). |
| version_offset: - a number, offset into the NvRam contents where the the |
| versions are stored. The total version field size is 4 bytes, the |
| first two bytes are the body version, the second two bytes are the key |
| version. Numbers are stored in little endian format. |
| pattern: optional, a tuple of two elements, the first element is the |
| offset of the pattern expected to be present in the NvRam, and the |
| second element is an array of bytes the pattern must match. |
| contents: an array of bytes, the contents of the NvRam. |
| ''' |
| |
| def __init__(self, addr, size, version_offset, data_pattern=None): |
| self.addr = addr |
| self.size = size |
| self.os_if = None |
| self.version_offset = version_offset |
| self.pattern = data_pattern |
| self.contents = [] |
| |
| def init(self, os_if): |
| self.os_if = os_if |
| cmd = 'tpmc read 0x%x 0x%x' % (self.addr, self.size) |
| nvram_data = self.os_if.run_shell_command_get_output(cmd)[0].split() |
| self.contents = [int(x, 16) for x in nvram_data] |
| if self.pattern: |
| pattern_offset = self.pattern[0] |
| pattern_data = self.pattern[1] |
| contents_pattern = self.contents[pattern_offset:pattern_offset + |
| len(pattern_data)] |
| if contents_pattern != pattern_data: |
| raise TpmError('Nvram pattern does not match') |
| |
| def get_body_version(self): |
| return self.contents[ |
| self.version_offset + 1] * 256 + self.contents[self.version_offset] |
| |
| def get_key_version(self): |
| return self.contents[ |
| self.version_offset + 3] * 256 + self.contents[ |
| self.version_offset + 2] |
| |
| def set_body_version(self, new_version): |
| new_contents = list(self.contents) |
| new_contents[self.version_offset + 1] = (new_version >> 8) & 0xff |
| new_contents[self.version_offset] = new_version & 0xff |
| if new_contents != self.contents: |
| self.contents = new_contents |
| cmd = 'tpmc write 0x%x %s' % ( |
| self.addr, ' '.join('0x%2.2x' % x for x in new_contents)) |
| self.os_if.run_shell_command(cmd) |
| else: |
| self.os_if.log('no version change for 0x%x' % self.addr) |
| |
| class TpmHandler(object): |
| '''An object to control TPM device's NVRAM. |
| |
| Attributes: |
| cros_if: an instance of the OS interface (chromeos_interface or a mock |
| object). |
| nvrams: A dictionary where the keys are the nvram names, and the values |
| are instances of TpmNvRam objects, providing access to the |
| appropriate TPM NvRam sections. |
| ''' |
| |
| def __init__(self): |
| self.cros_if = None |
| self.nvrams = { |
| 'kernel': TpmNvRam(KERNEL_NV_ADDRESS, 13, 5, ( |
| 1, [0x4c, 0x57, 0x52, 0x47])), |
| 'bios': TpmNvRam(FW_NV_ADDRESS, 10, 2) |
| } |
| |
| def init(self, cros_if): |
| self.cros_if = cros_if |
| status = self.cros_if.run_shell_command_get_output( |
| 'initctl status tcsd')[0] |
| if status.startswith('tcsd start/running'): |
| self.cros_if.run_shell_command('stop tcsd') |
| |
| for nvram in self.nvrams.itervalues(): |
| nvram.init(self.cros_if) |
| |
| def get_fw_version(self): |
| return self.nvrams['bios'].get_body_version() |
| |
| def get_kernel_version(self): |
| return self.nvrams['kernel'].get_body_version() |
| |
| def set_fw_version(self, new_version): |
| return self.nvrams['bios'].set_body_version(new_version) |
| |
| def set_kernel_version(self, new_version): |
| return self.nvrams['kernel'].set_body_version(new_version) |
| |
| def _version_good(self, nvram, version_a, version_b): |
| return self.nvrams[nvram].get_body_version() == max( |
| version_a, version_b) |
| |
| def kernel_version_good(self, version_a, version_b): |
| return self._version_good('kernel', version_a, version_b) |
| |
| def fw_version_good(self, version_a, version_b): |
| return self._version_good('bios', version_a, version_b) |
| |
| def enable_write_access(self, config_file_name='/etc/init/tcsd.conf'): |
| '''Enable TPM write access on the next recovery mode restart. |
| |
| Comment out from the TPM upstart script the following lines locking |
| the TPM (and thus preventing write access): |
| |
| tpmc block || logger "tpmc block: status $?" |
| tpmc pplock || logger "tpmc pplock: status $?" |
| |
| Save the original file in the state directory for future recovery. |
| ''' |
| |
| pattern = re.compile('^\s+tpmc\s+(b|pp)lock\s+\|\|') |
| new_file_name = self.cros_if.state_dir_file( |
| os.path.basename(config_file_name)) |
| shutil.copyfile(config_file_name, new_file_name + '.bak') |
| config_file = open(config_file_name, 'r') |
| new_file = open(new_file_name, 'w') |
| for line in config_file.readlines(): |
| if pattern.search(line): |
| new_file.write('%s%s' % (COMMENT_PATTERN, line)) |
| else: |
| new_file.write(line) |
| new_file.close() |
| config_file.close() |
| shutil.move(new_file_name, config_file_name) |
| |
| def disable_write_access(self, config_file_name='/etc/init/tcsd.conf'): |
| '''Disable TPM write access. |
| |
| Restore previously edited startup file to lock the TPM on the next |
| restart. |
| ''' |
| backup_file_name = self.cros_if.state_dir_file( |
| os.path.basename(config_file_name) + '.bak') |
| shutil.move(backup_file_name, config_file_name) |