blob: 9e2775e9898c2929ba903219f528c9d71f9c2617 [file] [log] [blame]
# 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()