1# SPDX-License-Identifier: GPL-2.0+ 2# Copyright (c) 2018, Bootlin 3# Author: Miquel Raynal <miquel.raynal@bootlin.com> 4 5import os.path 6import pytest 7import utils 8import re 9import time 10 11""" 12Test the TPMv2.x related commands. You must have a working hardware setup in 13order to do these tests. 14 15Notes: 16* These tests will prove the password mechanism. The TPM chip must be cleared of 17any password. 18* Commands like pcr_setauthpolicy and pcr_resetauthpolicy are not implemented 19here because they would fail the tests in most cases (TPMs do not implement them 20and return an error). 21 22 23Note: 24This test doesn't rely on boardenv_* configuration value but can change test 25behavior. 26 27* Setup env__tpm_device_test_skip to True if tests with TPM devices should be 28skipped. 29 30Parallel tests 31-------------- 32 33These tests can be run in parallel on sandbox. In that case any action taken 34by one test may be independent of another. For sandbox, care should be taken to 35ensure that tests are independent. 36 37Unfortunately, tests cannot be made independent on real hardware, since there is 38no way to reset the TPM other than restarting the board. Perhaps that would be 39the best approach? 40""" 41 42updates = 0 43 44def force_init(ubman, force=False): 45 """When a test fails, U-Boot is reset. Because TPM stack must be initialized 46 after each reboot, we must ensure these lines are always executed before 47 trying any command or they will fail with no reason. Executing 'tpm init' 48 twice will spawn an error used to detect that the TPM was not reset and no 49 initialization code should be run. 50 """ 51 skip_test = ubman.config.env.get('env__tpm_device_test_skip', False) 52 if skip_test: 53 pytest.skip('skip TPM device test') 54 output = ubman.run_command('tpm2 autostart') 55 if force or not 'Error' in output: 56 ubman.run_command('echo --- start of init ---') 57 ubman.run_command('tpm2 clear TPM2_RH_LOCKOUT') 58 output = ubman.run_command('echo $?') 59 if not output.endswith('0'): 60 ubman.run_command('tpm2 clear TPM2_RH_PLATFORM') 61 ubman.run_command('echo --- end of init ---') 62 63@pytest.mark.buildconfigspec('cmd_tpm_v2') 64def test_tpm2_autostart(ubman): 65 """Init the software stack to use TPMv2 commands.""" 66 skip_test = ubman.config.env.get('env__tpm_device_test_skip', False) 67 if skip_test: 68 pytest.skip('skip TPM device test') 69 ubman.run_command('tpm2 autostart') 70 output = ubman.run_command('echo $?') 71 assert output.endswith('0') 72 73@pytest.mark.buildconfigspec('cmd_tpm_v2') 74def test_tpm2_continue_self_test(ubman): 75 """Execute a TPM2_SelfTest (continued) command. 76 77 Ask the TPM to finish its self tests (alternative to the full test) in order 78 to enter a fully operational state. 79 """ 80 81 skip_test = ubman.config.env.get('env__tpm_device_test_skip', False) 82 if skip_test: 83 pytest.skip('skip TPM device test') 84 ubman.run_command('tpm2 self_test continue') 85 output = ubman.run_command('echo $?') 86 assert output.endswith('0') 87 88@pytest.mark.buildconfigspec('cmd_tpm_v2') 89def test_tpm2_clear(ubman): 90 """Execute a TPM2_Clear command. 91 92 Ask the TPM to reset entirely its internal state (including internal 93 configuration, passwords, counters and DAM parameters). This is half of the 94 TAKE_OWNERSHIP command from TPMv1. 95 96 Use the LOCKOUT hierarchy for this. The LOCKOUT/PLATFORM hierarchies must 97 not have a password set, otherwise this test will fail. ENDORSEMENT and 98 PLATFORM hierarchies are also available. 99 """ 100 skip_test = ubman.config.env.get('env__tpm_device_test_skip', False) 101 if skip_test: 102 pytest.skip('skip TPM device test') 103 ubman.run_command('tpm2 clear TPM2_RH_LOCKOUT') 104 output = ubman.run_command('echo $?') 105 assert output.endswith('0') 106 107 ubman.run_command('tpm2 clear TPM2_RH_PLATFORM') 108 output = ubman.run_command('echo $?') 109 assert output.endswith('0') 110 111@pytest.mark.buildconfigspec('cmd_tpm_v2') 112def test_tpm2_change_auth(ubman): 113 """Execute a TPM2_HierarchyChangeAuth command. 114 115 Ask the TPM to change the owner, ie. set a new password: 'unicorn' 116 117 Use the LOCKOUT hierarchy for this. ENDORSEMENT and PLATFORM hierarchies are 118 also available. 119 """ 120 force_init(ubman) 121 122 ubman.run_command('tpm2 change_auth TPM2_RH_LOCKOUT unicorn') 123 output = ubman.run_command('echo $?') 124 assert output.endswith('0') 125 126 ubman.run_command('tpm2 clear TPM2_RH_LOCKOUT unicorn') 127 output = ubman.run_command('echo $?') 128 ubman.run_command('tpm2 clear TPM2_RH_PLATFORM') 129 assert output.endswith('0') 130 131@pytest.mark.buildconfigspec('sandbox') 132@pytest.mark.buildconfigspec('cmd_tpm_v2') 133def test_tpm2_get_capability(ubman): 134 """Execute a TPM_GetCapability command. 135 136 Display one capability. In our test case, let's display the default DAM 137 lockout counter that should be 0 since the CLEAR: 138 - TPM_CAP_TPM_PROPERTIES = 0x6 139 - TPM_PT_LOCKOUT_COUNTER (1st parameter) = PTR_VAR + 14 140 141 There is no expected default values because it would depend on the chip 142 used. We can still save them in order to check they have changed later. 143 """ 144 force_init(ubman) 145 ram = utils.find_ram_base(ubman) 146 147 read_cap = ubman.run_command('tpm2 get_capability 0x6 0x20e 0x200 1') #0x%x 1' % ram) 148 output = ubman.run_command('echo $?') 149 assert output.endswith('0') 150 assert 'Property 0x0000020e: 0x00000000' in read_cap 151 152@pytest.mark.buildconfigspec('cmd_tpm_v2') 153def test_tpm2_dam_parameters(ubman): 154 """Execute a TPM2_DictionaryAttackParameters command. 155 156 Change Dictionary Attack Mitigation (DAM) parameters. Ask the TPM to change: 157 - Max number of failed authentication before lockout: 3 158 - Time before the failure counter is automatically decremented: 10 sec 159 - Time after a lockout failure before it can be attempted again: 0 sec 160 161 For an unknown reason, the DAM parameters must be changed before changing 162 the authentication, otherwise the lockout will be engaged after the first 163 failed authentication attempt. 164 """ 165 force_init(ubman) 166 ram = utils.find_ram_base(ubman) 167 168 # Set the DAM parameters to known values 169 ubman.run_command('tpm2 dam_parameters 3 10 0') 170 output = ubman.run_command('echo $?') 171 assert output.endswith('0') 172 173 # Check the values have been saved 174 read_cap = ubman.run_command('tpm2 get_capability 0x6 0x20f 0x%x 3' % ram) 175 output = ubman.run_command('echo $?') 176 assert output.endswith('0') 177 assert 'Property 0x0000020f: 0x00000003' in read_cap 178 assert 'Property 0x00000210: 0x0000000a' in read_cap 179 assert 'Property 0x00000211: 0x00000000' in read_cap 180 181@pytest.mark.buildconfigspec('cmd_tpm_v2') 182@pytest.mark.notbuildconfigspec('target_chromebook_coral') 183def test_tpm2_pcr_read(ubman): 184 """Execute a TPM2_PCR_Read command. 185 186 Perform a PCR read of the 10th PCR. Must be zero. 187 """ 188 force_init(ubman) 189 ram = utils.find_ram_base(ubman) 190 191 read_pcr = ubman.run_command('tpm2 pcr_read 10 0x%x' % ram) 192 output = ubman.run_command('echo $?') 193 assert output.endswith('0') 194 195 # Save the number of PCR updates 196 str = re.findall(r'\d+ known updates', read_pcr)[0] 197 global updates 198 updates = int(re.findall(r'\d+', str)[0]) 199 200 # Check the output value 201 assert 'PCR #10 sha256 32 byte content' in read_pcr 202 assert '00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00' in read_pcr 203 204@pytest.mark.buildconfigspec('cmd_tpm_v2') 205@pytest.mark.notbuildconfigspec('target_chromebook_coral') 206def test_tpm2_pcr_extend(ubman): 207 """Execute a TPM2_PCR_Extend command. 208 209 Perform a PCR extension with a known hash in memory (zeroed since the board 210 must have been rebooted). 211 212 No authentication mechanism is used here, not protecting against packet 213 replay, yet. 214 """ 215 force_init(ubman) 216 ram = utils.find_ram_base(ubman) 217 218 read_pcr = ubman.run_command('tpm2 pcr_read 10 0x%x' % (ram + 0x20)) 219 output = ubman.run_command('echo $?') 220 assert output.endswith('0') 221 str = re.findall(r'\d+ known updates', read_pcr)[0] 222 updates = int(re.findall(r'\d+', str)[0]) 223 224 ubman.run_command('tpm2 pcr_extend 10 0x%x' % ram) 225 output = ubman.run_command('echo $?') 226 assert output.endswith('0') 227 228 # Read the value back into a different place so we can still use 'ram' as 229 # our zero bytes 230 read_pcr = ubman.run_command('tpm2 pcr_read 10 0x%x' % (ram + 0x20)) 231 output = ubman.run_command('echo $?') 232 assert output.endswith('0') 233 assert 'f5 a5 fd 42 d1 6a 20 30 27 98 ef 6e d3 09 97 9b' in read_pcr 234 assert '43 00 3d 23 20 d9 f0 e8 ea 98 31 a9 27 59 fb 4b' in read_pcr 235 236 str = re.findall(r'\d+ known updates', read_pcr)[0] 237 new_updates = int(re.findall(r'\d+', str)[0]) 238 assert (updates + 1) == new_updates 239 240 ubman.run_command('tpm2 pcr_extend 10 0x%x' % ram) 241 output = ubman.run_command('echo $?') 242 assert output.endswith('0') 243 244 read_pcr = ubman.run_command('tpm2 pcr_read 10 0x%x' % (ram + 0x20)) 245 output = ubman.run_command('echo $?') 246 assert output.endswith('0') 247 assert '7a 05 01 f5 95 7b df 9c b3 a8 ff 49 66 f0 22 65' in read_pcr 248 assert 'f9 68 65 8b 7a 9c 62 64 2c ba 11 65 e8 66 42 f5' in read_pcr 249 250 str = re.findall(r'\d+ known updates', read_pcr)[0] 251 new_updates = int(re.findall(r'\d+', str)[0]) 252 assert (updates + 2) == new_updates 253 254@pytest.mark.buildconfigspec('cmd_tpm_v2') 255def test_tpm2_cleanup(ubman): 256 """Ensure the TPM is cleared from password or test related configuration.""" 257 258 force_init(ubman, True) 259