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