OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/python |
| 2 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. |
| 5 |
| 6 '''A module containing TPM handler class used by SAFT.''' |
| 7 import os |
| 8 import re |
| 9 import shutil |
| 10 |
| 11 FW_NV_ADDRESS = 0x1007 |
| 12 KERNEL_NV_ADDRESS = 0x1008 |
| 13 COMMENT_PATTERN = '#saft' |
| 14 |
| 15 class TpmError(Exception): |
| 16 pass |
| 17 |
| 18 class TpmNvRam(object): |
| 19 '''An object representing TPM NvRam. |
| 20 |
| 21 Attributes: |
| 22 addr: a number, NvRAm address in TPM. |
| 23 size: a number, count of bites in this NvRam section. |
| 24 os_if: an instance of the OS interface (chromeos_interface or a mock |
| 25 object). |
| 26 version_offset: - a number, offset into the NvRam contents where the the |
| 27 versions are stored. The total version field size is 4 bytes, the |
| 28 first two bytes are the body version, the second two bytes are the key |
| 29 version. Numbers are stored in little endian format. |
| 30 pattern: optional, a tuple of two elements, the first element is the |
| 31 offset of the pattern expected to be present in the NvRam, and the |
| 32 second element is an array of bytes the pattern must match. |
| 33 contents: an array of bytes, the contents of the NvRam. |
| 34 ''' |
| 35 |
| 36 def __init__(self, addr, size, version_offset, data_pattern=None): |
| 37 self.addr = addr |
| 38 self.size = size |
| 39 self.os_if = None |
| 40 self.version_offset = version_offset |
| 41 self.pattern = data_pattern |
| 42 self.contents = [] |
| 43 |
| 44 def init(self, os_if): |
| 45 self.os_if = os_if |
| 46 cmd = 'tpmc read 0x%x 0x%x' % (self.addr, self.size) |
| 47 nvram_data = self.os_if.run_shell_command_get_output(cmd)[0].split() |
| 48 self.contents = [int(x, 16) for x in nvram_data] |
| 49 if self.pattern: |
| 50 pattern_offset = self.pattern[0] |
| 51 pattern_data = self.pattern[1] |
| 52 contents_pattern = self.contents[pattern_offset:pattern_offset + |
| 53 len(pattern_data)] |
| 54 if contents_pattern != pattern_data: |
| 55 raise TpmError('Nvram pattern does not match') |
| 56 |
| 57 def get_body_version(self): |
| 58 return self.contents[ |
| 59 self.version_offset + 1] * 256 + self.contents[self.version_offset] |
| 60 |
| 61 def get_key_version(self): |
| 62 return self.contents[ |
| 63 self.version_offset + 3] * 256 + self.contents[ |
| 64 self.version_offset + 2] |
| 65 |
| 66 def set_body_version(self, new_version): |
| 67 new_contents = list(self.contents) |
| 68 new_contents[self.version_offset + 1] = (new_version >> 8) & 0xff |
| 69 new_contents[self.version_offset] = new_version & 0xff |
| 70 if new_contents != self.contents: |
| 71 self.contents = new_contents |
| 72 cmd = 'tpmc write 0x%x %s' % ( |
| 73 self.addr, ' '.join('0x%2.2x' % x for x in new_contents)) |
| 74 self.os_if.run_shell_command(cmd) |
| 75 else: |
| 76 self.os_if.log('no version change for 0x%x' % self.addr) |
| 77 |
| 78 class TpmHandler(object): |
| 79 '''An object to control TPM device's NVRAM. |
| 80 |
| 81 Attributes: |
| 82 cros_if: an instance of the OS interface (chromeos_interface or a mock |
| 83 object). |
| 84 nvrams: A dictionary where the keys are the nvram names, and the values |
| 85 are instances of TpmNvRam objects, providing access to the |
| 86 appropriate TPM NvRam sections. |
| 87 ''' |
| 88 |
| 89 def __init__(self): |
| 90 self.cros_if = None |
| 91 self.nvrams = { |
| 92 'kernel': TpmNvRam(KERNEL_NV_ADDRESS, 13, 5, ( |
| 93 1, [0x4c, 0x57, 0x52, 0x47])), |
| 94 'bios': TpmNvRam(FW_NV_ADDRESS, 10, 2) |
| 95 } |
| 96 |
| 97 def init(self, cros_if): |
| 98 self.cros_if = cros_if |
| 99 status = self.cros_if.run_shell_command_get_output( |
| 100 'initctl status tcsd')[0] |
| 101 if status.startswith('tcsd start/running'): |
| 102 self.cros_if.run_shell_command('stop tcsd') |
| 103 |
| 104 for nvram in self.nvrams.itervalues(): |
| 105 nvram.init(self.cros_if) |
| 106 |
| 107 def get_fw_version(self): |
| 108 return self.nvrams['bios'].get_body_version() |
| 109 |
| 110 def get_kernel_version(self): |
| 111 return self.nvrams['kernel'].get_body_version() |
| 112 |
| 113 def set_kernel_version(self, new_version): |
| 114 return self.nvrams['kernel'].set_body_version(new_version) |
| 115 |
| 116 def _version_good(self, nvram, version_a, version_b): |
| 117 return self.nvrams[nvram].get_body_version() == max( |
| 118 version_a, version_b) |
| 119 |
| 120 def kernel_version_good(self, version_a, version_b): |
| 121 return self._version_good('kernel', version_a, version_b) |
| 122 |
| 123 def fw_version_good(self, version_a, version_b): |
| 124 return self._version_good('bios', version_a, version_b) |
| 125 |
| 126 def enable_write_access(self, config_file_name='/etc/init/tcsd.conf'): |
| 127 '''Enable TPM write access on the next recovery mode restart. |
| 128 |
| 129 Comment out from the TPM upstart script the following lines locking |
| 130 the TPM (and thus preventing write access): |
| 131 |
| 132 tpmc block || logger "tpmc block: status $?" |
| 133 tpmc pplock || logger "tpmc pplock: status $?" |
| 134 |
| 135 Save the original file in the state directory for future recovery. |
| 136 ''' |
| 137 |
| 138 pattern = re.compile('^\s+tpmc\s+(b|pp)lock\s+\|\|') |
| 139 new_file_name = self.cros_if.state_dir_file( |
| 140 os.path.basename(config_file_name)) |
| 141 shutil.copyfile(config_file_name, new_file_name + '.bak') |
| 142 config_file = open(config_file_name, 'r') |
| 143 new_file = open(new_file_name, 'w') |
| 144 for line in config_file.readlines(): |
| 145 if pattern.search(line): |
| 146 new_file.write('%s%s' % (COMMENT_PATTERN, line)) |
| 147 else: |
| 148 new_file.write(line) |
| 149 new_file.close() |
| 150 config_file.close() |
| 151 shutil.move(new_file_name, config_file_name) |
| 152 |
| 153 def disable_write_access(self, config_file_name='/etc/init/tcsd.conf'): |
| 154 '''Disable TPM write access. |
| 155 |
| 156 Restore previously edited startup file to lock the TPM on the next |
| 157 restart. |
| 158 ''' |
| 159 backup_file_name = self.cros_if.state_dir_file( |
| 160 os.path.basename(config_file_name) + '.bak') |
| 161 shutil.move(backup_file_name, config_file_name) |
OLD | NEW |