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 u_boot_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 30""" 31 32updates = 0 33 34def force_init(u_boot_console, force=False): 35 """When a test fails, U-Boot is reset. Because TPM stack must be initialized 36 after each reboot, we must ensure these lines are always executed before 37 trying any command or they will fail with no reason. Executing 'tpm init' 38 twice will spawn an error used to detect that the TPM was not reset and no 39 initialization code should be run. 40 """ 41 skip_test = u_boot_console.config.env.get('env__tpm_device_test_skip', False) 42 if skip_test: 43 pytest.skip('skip TPM device test') 44 output = u_boot_console.run_command('tpm2 init') 45 if force or not 'Error' in output: 46 u_boot_console.run_command('echo --- start of init ---') 47 u_boot_console.run_command('tpm2 startup TPM2_SU_CLEAR') 48 u_boot_console.run_command('tpm2 self_test full') 49 u_boot_console.run_command('tpm2 clear TPM2_RH_LOCKOUT') 50 output = u_boot_console.run_command('echo $?') 51 if not output.endswith('0'): 52 u_boot_console.run_command('tpm2 clear TPM2_RH_PLATFORM') 53 u_boot_console.run_command('echo --- end of init ---') 54 55def is_sandbox(cons): 56 # Array slice removes leading/trailing quotes. 57 sys_arch = cons.config.buildconfig.get('config_sys_arch', '"sandbox"')[1:-1] 58 return sys_arch == 'sandbox' 59 60@pytest.mark.buildconfigspec('cmd_tpm_v2') 61def test_tpm2_init(u_boot_console): 62 """Init the software stack to use TPMv2 commands.""" 63 skip_test = u_boot_console.config.env.get('env__tpm_device_test_skip', False) 64 if skip_test: 65 pytest.skip('skip TPM device test') 66 u_boot_console.run_command('tpm2 init') 67 output = u_boot_console.run_command('echo $?') 68 assert output.endswith('0') 69 70@pytest.mark.buildconfigspec('cmd_tpm_v2') 71def test_tpm2_startup(u_boot_console): 72 """Execute a TPM2_Startup command. 73 74 Initiate the TPM internal state machine. 75 """ 76 u_boot_console.run_command('tpm2 startup TPM2_SU_CLEAR') 77 output = u_boot_console.run_command('echo $?') 78 assert output.endswith('0') 79 80def tpm2_sandbox_init(u_boot_console): 81 """Put sandbox back into a known state so we can run a test 82 83 This allows all tests to run in parallel, since no test depends on another. 84 """ 85 u_boot_console.restart_uboot() 86 u_boot_console.run_command('tpm2 init') 87 output = u_boot_console.run_command('echo $?') 88 assert output.endswith('0') 89 90 skip_test = u_boot_console.config.env.get('env__tpm_device_test_skip', False) 91 if skip_test: 92 pytest.skip('skip TPM device test') 93 u_boot_console.run_command('tpm2 startup TPM2_SU_CLEAR') 94 output = u_boot_console.run_command('echo $?') 95 assert output.endswith('0') 96 97 u_boot_console.run_command('tpm2 self_test full') 98 output = u_boot_console.run_command('echo $?') 99 assert output.endswith('0') 100 101@pytest.mark.buildconfigspec('cmd_tpm_v2') 102def test_tpm2_sandbox_self_test_full(u_boot_console): 103 """Execute a TPM2_SelfTest (full) command. 104 105 Ask the TPM to perform all self tests to also enable full capabilities. 106 """ 107 if is_sandbox(u_boot_console): 108 u_boot_console.restart_uboot() 109 u_boot_console.run_command('tpm2 init') 110 output = u_boot_console.run_command('echo $?') 111 assert output.endswith('0') 112 113 u_boot_console.run_command('tpm2 startup TPM2_SU_CLEAR') 114 output = u_boot_console.run_command('echo $?') 115 assert output.endswith('0') 116 117 skip_test = u_boot_console.config.env.get('env__tpm_device_test_skip', False) 118 if skip_test: 119 pytest.skip('skip TPM device test') 120 u_boot_console.run_command('tpm2 self_test full') 121 output = u_boot_console.run_command('echo $?') 122 assert output.endswith('0') 123 124@pytest.mark.buildconfigspec('cmd_tpm_v2') 125def test_tpm2_continue_self_test(u_boot_console): 126 """Execute a TPM2_SelfTest (continued) command. 127 128 Ask the TPM to finish its self tests (alternative to the full test) in order 129 to enter a fully operational state. 130 """ 131 132 skip_test = u_boot_console.config.env.get('env__tpm_device_test_skip', False) 133 if skip_test: 134 pytest.skip('skip TPM device test') 135 if is_sandbox(u_boot_console): 136 tpm2_sandbox_init(u_boot_console) 137 u_boot_console.run_command('tpm2 self_test continue') 138 output = u_boot_console.run_command('echo $?') 139 assert output.endswith('0') 140 141@pytest.mark.buildconfigspec('cmd_tpm_v2') 142def test_tpm2_clear(u_boot_console): 143 """Execute a TPM2_Clear command. 144 145 Ask the TPM to reset entirely its internal state (including internal 146 configuration, passwords, counters and DAM parameters). This is half of the 147 TAKE_OWNERSHIP command from TPMv1. 148 149 Use the LOCKOUT hierarchy for this. The LOCKOUT/PLATFORM hierarchies must 150 not have a password set, otherwise this test will fail. ENDORSEMENT and 151 PLATFORM hierarchies are also available. 152 """ 153 if is_sandbox(u_boot_console): 154 tpm2_sandbox_init(u_boot_console) 155 156 skip_test = u_boot_console.config.env.get('env__tpm_device_test_skip', False) 157 if skip_test: 158 pytest.skip('skip TPM device test') 159 u_boot_console.run_command('tpm2 clear TPM2_RH_LOCKOUT') 160 output = u_boot_console.run_command('echo $?') 161 assert output.endswith('0') 162 163 u_boot_console.run_command('tpm2 clear TPM2_RH_PLATFORM') 164 output = u_boot_console.run_command('echo $?') 165 assert output.endswith('0') 166 167@pytest.mark.buildconfigspec('cmd_tpm_v2') 168def test_tpm2_change_auth(u_boot_console): 169 """Execute a TPM2_HierarchyChangeAuth command. 170 171 Ask the TPM to change the owner, ie. set a new password: 'unicorn' 172 173 Use the LOCKOUT hierarchy for this. ENDORSEMENT and PLATFORM hierarchies are 174 also available. 175 """ 176 if is_sandbox(u_boot_console): 177 tpm2_sandbox_init(u_boot_console) 178 force_init(u_boot_console) 179 180 u_boot_console.run_command('tpm2 change_auth TPM2_RH_LOCKOUT unicorn') 181 output = u_boot_console.run_command('echo $?') 182 assert output.endswith('0') 183 184 u_boot_console.run_command('tpm2 clear TPM2_RH_LOCKOUT unicorn') 185 output = u_boot_console.run_command('echo $?') 186 u_boot_console.run_command('tpm2 clear TPM2_RH_PLATFORM') 187 assert output.endswith('0') 188 189@pytest.mark.buildconfigspec('sandbox') 190@pytest.mark.buildconfigspec('cmd_tpm_v2') 191def test_tpm2_get_capability(u_boot_console): 192 """Execute a TPM_GetCapability command. 193 194 Display one capability. In our test case, let's display the default DAM 195 lockout counter that should be 0 since the CLEAR: 196 - TPM_CAP_TPM_PROPERTIES = 0x6 197 - TPM_PT_LOCKOUT_COUNTER (1st parameter) = PTR_VAR + 14 198 199 There is no expected default values because it would depend on the chip 200 used. We can still save them in order to check they have changed later. 201 """ 202 if is_sandbox(u_boot_console): 203 tpm2_sandbox_init(u_boot_console) 204 205 force_init(u_boot_console) 206 ram = u_boot_utils.find_ram_base(u_boot_console) 207 208 read_cap = u_boot_console.run_command('tpm2 get_capability 0x6 0x20e 0x200 1') #0x%x 1' % ram) 209 output = u_boot_console.run_command('echo $?') 210 assert output.endswith('0') 211 assert 'Property 0x0000020e: 0x00000000' in read_cap 212 213@pytest.mark.buildconfigspec('cmd_tpm_v2') 214def test_tpm2_dam_parameters(u_boot_console): 215 """Execute a TPM2_DictionaryAttackParameters command. 216 217 Change Dictionary Attack Mitigation (DAM) parameters. Ask the TPM to change: 218 - Max number of failed authentication before lockout: 3 219 - Time before the failure counter is automatically decremented: 10 sec 220 - Time after a lockout failure before it can be attempted again: 0 sec 221 222 For an unknown reason, the DAM parameters must be changed before changing 223 the authentication, otherwise the lockout will be engaged after the first 224 failed authentication attempt. 225 """ 226 if is_sandbox(u_boot_console): 227 tpm2_sandbox_init(u_boot_console) 228 force_init(u_boot_console) 229 ram = u_boot_utils.find_ram_base(u_boot_console) 230 231 # Set the DAM parameters to known values 232 u_boot_console.run_command('tpm2 dam_parameters 3 10 0') 233 output = u_boot_console.run_command('echo $?') 234 assert output.endswith('0') 235 236 # Check the values have been saved 237 read_cap = u_boot_console.run_command('tpm2 get_capability 0x6 0x20f 0x%x 3' % ram) 238 output = u_boot_console.run_command('echo $?') 239 assert output.endswith('0') 240 assert 'Property 0x0000020f: 0x00000003' in read_cap 241 assert 'Property 0x00000210: 0x0000000a' in read_cap 242 assert 'Property 0x00000211: 0x00000000' in read_cap 243 244@pytest.mark.buildconfigspec('cmd_tpm_v2') 245def test_tpm2_pcr_read(u_boot_console): 246 """Execute a TPM2_PCR_Read command. 247 248 Perform a PCR read of the 0th PCR. Must be zero. 249 """ 250 if is_sandbox(u_boot_console): 251 tpm2_sandbox_init(u_boot_console) 252 253 force_init(u_boot_console) 254 ram = u_boot_utils.find_ram_base(u_boot_console) 255 256 read_pcr = u_boot_console.run_command('tpm2 pcr_read 0 0x%x' % ram) 257 output = u_boot_console.run_command('echo $?') 258 assert output.endswith('0') 259 260 # Save the number of PCR updates 261 str = re.findall(r'\d+ known updates', read_pcr)[0] 262 global updates 263 updates = int(re.findall(r'\d+', str)[0]) 264 265 # Check the output value 266 assert 'PCR #0 content' in read_pcr 267 assert '00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00' in read_pcr 268 269@pytest.mark.buildconfigspec('cmd_tpm_v2') 270def test_tpm2_pcr_extend(u_boot_console): 271 """Execute a TPM2_PCR_Extend command. 272 273 Perform a PCR extension with a known hash in memory (zeroed since the board 274 must have been rebooted). 275 276 No authentication mechanism is used here, not protecting against packet 277 replay, yet. 278 """ 279 if is_sandbox(u_boot_console): 280 tpm2_sandbox_init(u_boot_console) 281 force_init(u_boot_console) 282 ram = u_boot_utils.find_ram_base(u_boot_console) 283 284 u_boot_console.run_command('tpm2 pcr_extend 0 0x%x' % ram) 285 output = u_boot_console.run_command('echo $?') 286 assert output.endswith('0') 287 288 # Read the value back into a different place so we can still use 'ram' as 289 # our zero bytes 290 read_pcr = u_boot_console.run_command('tpm2 pcr_read 0 0x%x' % (ram + 0x20)) 291 output = u_boot_console.run_command('echo $?') 292 assert output.endswith('0') 293 assert 'f5 a5 fd 42 d1 6a 20 30 27 98 ef 6e d3 09 97 9b' in read_pcr 294 assert '43 00 3d 23 20 d9 f0 e8 ea 98 31 a9 27 59 fb 4b' in read_pcr 295 296 str = re.findall(r'\d+ known updates', read_pcr)[0] 297 new_updates = int(re.findall(r'\d+', str)[0]) 298 assert (updates + 1) == new_updates 299 300 u_boot_console.run_command('tpm2 pcr_extend 0 0x%x' % ram) 301 output = u_boot_console.run_command('echo $?') 302 assert output.endswith('0') 303 304 read_pcr = u_boot_console.run_command('tpm2 pcr_read 0 0x%x' % (ram + 0x20)) 305 output = u_boot_console.run_command('echo $?') 306 assert output.endswith('0') 307 assert '7a 05 01 f5 95 7b df 9c b3 a8 ff 49 66 f0 22 65' in read_pcr 308 assert 'f9 68 65 8b 7a 9c 62 64 2c ba 11 65 e8 66 42 f5' in read_pcr 309 310 str = re.findall(r'\d+ known updates', read_pcr)[0] 311 new_updates = int(re.findall(r'\d+', str)[0]) 312 assert (updates + 2) == new_updates 313 314@pytest.mark.buildconfigspec('cmd_tpm_v2') 315def test_tpm2_cleanup(u_boot_console): 316 """Ensure the TPM is cleared from password or test related configuration.""" 317 318 force_init(u_boot_console, True) 319