| # Copyright 2018 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. |
| |
| import logging, re |
| from autotest_lib.client.bin import test, utils |
| from autotest_lib.client.common_lib import error |
| from autotest_lib.client.cros import service_stopper |
| from autotest_lib.client.cros import cryptohome |
| |
| tpm_owner_password = '' |
| tpm_pw_hex = '' |
| |
| def run_tpmc_cmd(subcommand): |
| """Make this test more readable by simplifying commonly used tpmc command. |
| |
| @param subcommand: String of the tpmc subcommand (read, raw, ...) |
| @return String output (which may be empty). |
| """ |
| |
| # Run tpmc command, redirect stderr to stdout, and merge to one line. |
| cmd = 'tpmc %s 2>&1 | awk 1 ORS=""' % subcommand |
| return utils.system_output(cmd, ignore_status=True).strip() |
| |
| def check_tpmc(subcommand, expected): |
| """Runs tpmc command and checks the output against an expected regex. |
| |
| @param subcommand: String of the tpmc subcommand (read, raw, ...) |
| @param expected: String re. |
| @raises error.TestError() for not matching expected. |
| """ |
| error_msg = 'invalid response to tpmc %s' % subcommand |
| out = run_tpmc_cmd(subcommand) |
| if (not re.match(expected, out)): |
| raise error.TestError('%s: %s' % (error_msg, out)) |
| |
| return out |
| |
| def expect_tpmc_error(subcommand, expected_error): |
| check_tpmc(subcommand, '.*failed.*%s.*' % expected_error) |
| |
| class firmware_Cr50VirtualNVRam(test.test): |
| 'Tests the virtual NVRAM functionality in Cr50 through TPM commands.' |
| |
| version = 1 |
| |
| def __take_tpm_ownership(self): |
| global tpm_owner_password |
| global tpm_pw_hex |
| cryptohome.take_tpm_ownership(wait_for_ownership=True) |
| |
| tpm_owner_password = cryptohome.get_tpm_status()['Password'] |
| if not tpm_owner_password: |
| raise error.TestError('TPM owner password is empty after ' |
| 'taking ownership.') |
| for ch in tpm_owner_password: |
| tpm_pw_hex = tpm_pw_hex + format(ord(ch), 'x') + ' ' |
| |
| def __read_tests(self): |
| # Check reading board ID returns a valid value. |
| bid = check_tpmc('read 0x3fff00 0xc', |
| '([0-9a-f]{1,2} ?){12}') |
| |
| # Check that a subset of board ID can be read. |
| check_tpmc('read 0x3fff00 0x6', |
| ' '.join(bid.split()[:6])) # Matches previous read |
| |
| # Check that size constraints are respected. |
| expect_tpmc_error('read 0x3fffff 0xd', |
| '0x146') # TPM_RC_NV_RANGE |
| |
| # Check zero-length can be read. |
| check_tpmc('read 0x3fff00 0x0', '') |
| |
| # Check arbitrary index can be read. |
| check_tpmc('read 0x3fff73 0x0', '') |
| |
| # Check highest index can be read. |
| check_tpmc('read 0x3fffff 0x0', '') |
| |
| def __get_write_cmd(self, index, offset, size): |
| assert (size + 35) < 256 |
| assert offset < 256 |
| cmd_size = format(35 + size, 'x') + ' ' |
| size_hex = '00 ' + format(size, 'x') + ' ' |
| offset_hex = '00 ' + format(offset, 'x') + ' ' |
| data = '' |
| for _ in range(size): |
| data = data + 'ff ' |
| |
| return ('raw ' |
| '80 02 ' # TPM_ST_SESSIONS |
| '00 00 00 ' + cmd_size + # commandSize |
| '00 00 01 37 ' + # TPM_CC_NV_Write |
| index + ' ' + # authHandle |
| index + ' ' + # nvIndex |
| '00 00 00 09 ' # sessionSize |
| '40 00 00 09 ' # passwordAuth |
| '00 00 ' # nonceSize |
| '00 ' # sessionAttributes |
| '00 00 ' + # password length |
| size_hex + # dataSize |
| data + # data |
| offset_hex) # offset |
| |
| def __write_tests(self): |
| # Check an implemented index cannot be written to: |
| |
| # Zero-length write. |
| expect_tpmc_error(self.__get_write_cmd('01 3f ff 00', 0, 0), |
| '0x148') # TPM_RC_NV_LOCKED |
| |
| # Single byte. |
| expect_tpmc_error(self.__get_write_cmd('01 3f ff 00', 0, 1), |
| '0x148') # TPM_RC_NV_LOCKED |
| |
| # Single byte, offset. |
| expect_tpmc_error(self.__get_write_cmd('01 3f ff 00', 4, 1), |
| '0x148') # TPM_RC_NV_LOCKED |
| |
| # Write full length of index. |
| expect_tpmc_error(self.__get_write_cmd('01 3f ff 00', 0, 12), |
| '0x148') # TPM_RC_NV_LOCKED |
| |
| # Check an unimplemented index cannot be written to. |
| expect_tpmc_error(self.__get_write_cmd('01 3f ff ff', 0, 1), |
| '0x148') # TPM_RC_NV_LOCKED |
| |
| def __get_define_cmd(self, index, size): |
| assert (len(tpm_owner_password) + 45) < 256 |
| pw_sz = format(len(tpm_owner_password), 'x') + ' ' |
| session_sz = format(len(tpm_owner_password) + 9, 'x') + ' ' |
| cmd_sz = format(len(tpm_owner_password) + 45, 'x') + ' ' |
| |
| return ('raw ' |
| '80 02 ' |
| '00 00 00 ' + cmd_sz + # commandSize |
| '00 00 01 2a ' # commandCode |
| '40 00 00 01 ' # TPM_RH_OWNER |
| '00 00 00 ' + session_sz + # sessionSize |
| '40 00 00 09 ' # passwordAuth |
| '00 00 ' # nonceSize |
| '00 ' # sessionAttributes |
| '00 ' + pw_sz + # password length |
| tpm_pw_hex + # password |
| '00 00 ' # auth value |
| |
| # TPM2B_NV_PUBLIC: publicInfo |
| '00 0e ' + # size |
| index + ' ' # nvIndex |
| '00 0b ' # TPM_ALG_SHA256 |
| '00 02 00 02 ' # attributes |
| '00 00 ' # authPolicy |
| '00 ' + format(size, 'x')) # size |
| |
| def __get_undefine_cmd(self, index): |
| assert (len(tpm_owner_password) + 31) < 256 |
| pw_sz = format(len(tpm_owner_password), 'x') + ' ' |
| session_sz = format(len(tpm_owner_password) + 9, 'x') + ' ' |
| cmd_sz = format(len(tpm_owner_password) + 31, 'x') + ' ' |
| |
| return ('raw ' |
| '80 02 ' |
| '00 00 00 ' + cmd_sz + ' ' + # commandSize |
| '00 00 01 22 ' # commandCode |
| '40 00 00 01 ' + # TPM_RH_OWNER |
| index + ' ' + # nvIndex |
| '00 00 00 ' + session_sz + # sessionSize |
| '40 00 00 09 ' # passwordAuth |
| '00 00 ' # nonceSize |
| '00 ' # sessionAttributes |
| '00 ' + pw_sz + # password length |
| tpm_pw_hex) # password |
| |
| def __definespace_sanity_check(self): |
| # A space outside the virtual range can be defined |
| check_tpmc(self.__get_define_cmd('01 4f aa df', 12), |
| '(0x[0-9]{2} ){6}' |
| '(0x00 ){4}' # TPM_RC_SUCCESS |
| '(0x[0-9]{2} ?){9}') |
| |
| # A space outside the virtual range can be undefined. |
| check_tpmc(self.__get_undefine_cmd('01 4f aa df'), |
| '(0x[0-9]{2} ){6}' |
| '(0x00 ){4}' # TPM_RC_SUCCESS |
| '(0x[0-9]{2} ?){9}') |
| |
| def __definespace_tests(self): |
| # Check an implemented space in the virtual range cannot be defined. |
| expect_tpmc_error(self.__get_define_cmd('01 3f ff 00', 12), |
| '0x149') # TPM_RC_NV_AUTHORIZATION |
| |
| # Check an unimplemented space in the virtual range cannot be defined. |
| expect_tpmc_error(self.__get_define_cmd('01 3f ff df', 12), |
| '0x149') # TPM_RC_NV_AUTHORIZATION |
| |
| def __undefinespace_tests(self): |
| # Check an implemented space in the virtual range cannot be defined. |
| expect_tpmc_error(self.__get_undefine_cmd('01 3f ff 00'), |
| '0x149') # TPM_RC_NV_AUTHORIZATION |
| |
| # Check an unimplemented space in the virtual range cannot be defined. |
| expect_tpmc_error(self.__get_undefine_cmd('01 3f ff df'), |
| '0x149') # TPM_RC_NV_AUTHORIZATION |
| |
| def __readpublic_test(self): |
| public = check_tpmc(('raw ' |
| '80 01 ' # TPM_ST_NO_SESSIONS |
| '00 00 00 0e ' # commandSize |
| '00 00 01 69 ' # TPM_CC_ReadPublic |
| '01 3f ff 00'), # nvIndex |
| '(0x([0-9a-f]){2} *){62}') |
| |
| # For attribute details see Table 204, Part 2 of TPM2.0 spec |
| |
| attributes = hex(1 << 10 | # TPMA_NV_POLICY_DELETE |
| 1 << 11 | # TPMA_NV_WRITELOCKED |
| 1 << 13 | # TPMA_NV_WRITEDEFINE |
| 1 << 18 | # TPMA_NV_AUTHREAD |
| 1 << 29) # TPMA_NV_WRITTEN |
| |
| attributes = '%s %s %s %s ' % (attributes[2:4], |
| attributes[4:6], |
| attributes[6:8], |
| attributes[8:10]) |
| |
| expected = ('80 01 ' # TPM_ST_NO_SESSIONS |
| '00 00 00 3e ' # responseSize |
| '00 00 00 00 ' # responseCode |
| |
| # TPM2B_PUBLIC: nvPublic |
| '00 0e ' # size |
| '01 3f ff 00 ' # nvIndex |
| '00 0b ' + # TPM_ALG_SHA256 |
| attributes + # attributes |
| '00 00 ' # authPolicy |
| '00 0c ' # dataSize |
| |
| # TPM2B_NAME: name |
| '00 22 ' # size |
| '([0-9a-f] ?){34} ') |
| |
| if (not re.match(expected, re.sub('0x', '', public))): |
| raise error.TestError('%s does not match expected (%s)' |
| % (public, expected)) |
| |
| def __readlock_test(self): |
| # Virtual NV indices explicitly cannot be read locked, and attempts |
| # to do so will return an authorization error. |
| |
| expect_tpmc_error(('raw ' |
| '80 02 ' # TPM_ST_SESSIONS |
| '00 00 00 1f ' # commandSize |
| '00 00 01 4f ' # TPM_CC_NV_ReadLock |
| '01 3f ff 00 ' # authHandle |
| '01 3f ff 00 ' # nvIndex |
| '00 00 00 09 ' # sessionSize |
| '40 00 00 09 ' # password auth |
| '00 00 00 00 00'), # empty password |
| '0x149') # TPM_RC_NV_AUTHORIZATION |
| |
| def __writelock_test(self): |
| # Virtual NV indices have no write policy defined, so cannot be write |
| # locked. |
| |
| expect_tpmc_error(('raw ' |
| '80 02 ' # TPM_ST_SESSIONS |
| '00 00 00 1f ' # commandSize |
| '00 00 01 38 ' # TPM_CC_NV_WriteLock |
| '01 3f ff 00 ' # authHandle |
| '01 3f ff 00 ' # nvIndex |
| '00 00 00 09 ' # sessionSize |
| '40 00 00 09 ' # password auth |
| '00 00 00 00 00'), # empty password |
| '0x12f'); # TPM_RC_AUTH_UNAVAILABLE |
| |
| def initialize(self): |
| self.__take_tpm_ownership() |
| # Stop services that access to the TPM, to be able to use tpmc. |
| # Note: for TPM2 the order of re-starting services (they are started |
| # in the reversed listed order) is important: e.g. tpm_managerd must |
| # start after trunksd, and cryptohomed after attestationd. |
| self._services = service_stopper.ServiceStopper(['cryptohomed', |
| 'chapsd', |
| 'attestationd', |
| 'tpm_managerd', |
| 'tcsd', |
| 'trunksd']) |
| self._services.stop_services() |
| |
| def run_once(self): |
| """Run a few TPM state checks.""" |
| # If first virtual index is not defined, assumed we are not running |
| # on cr50, or running on an old build of cr50. Skip the test. |
| if re.match('.*failed.*0x18b.*', # TPM_RC_HANDLE |
| run_tpmc_cmd('read 0x3fff00 0x0')): |
| raise error.TestNAError("TPM does not support vNVRAM") |
| |
| self.__readpublic_test() |
| self.__definespace_sanity_check() |
| self.__definespace_tests() |
| self.__undefinespace_tests() |
| self.__readlock_test() |
| self.__writelock_test() |
| self.__write_tests() |
| self.__read_tests() |
| |
| def cleanup(self): |
| self._services.restore_services() |