test: Allow tpm2 tests to run in parallel
[platform/kernel/u-boot.git] / test / py / tests / test_tpm2.py
1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2018, Bootlin
3 # Author: Miquel Raynal <miquel.raynal@bootlin.com>
4
5 import os.path
6 import pytest
7 import u_boot_utils
8 import re
9 import time
10
11 """
12 Test the TPMv2.x related commands. You must have a working hardware setup in
13 order to do these tests.
14
15 Notes:
16 * These tests will prove the password mechanism. The TPM chip must be cleared of
17 any password.
18 * Commands like pcr_setauthpolicy and pcr_resetauthpolicy are not implemented
19 here because they would fail the tests in most cases (TPMs do not implement them
20 and return an error).
21
22
23 Note:
24 This test doesn't rely on boardenv_* configuration value but can change test
25 behavior.
26
27 * Setup env__tpm_device_test_skip to True if tests with TPM devices should be
28 skipped.
29
30 """
31
32 updates = 0
33
34 def 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
55 def 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')
61 def 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')
71 def 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
80 def 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')
102 def 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')
125 def 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')
142 def 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')
168 def 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('cmd_tpm_v2')
190 def test_tpm2_get_capability(u_boot_console):
191     """Execute a TPM_GetCapability command.
192
193     Display one capability. In our test case, let's display the default DAM
194     lockout counter that should be 0 since the CLEAR:
195     - TPM_CAP_TPM_PROPERTIES = 0x6
196     - TPM_PT_LOCKOUT_COUNTER (1st parameter) = PTR_VAR + 14
197
198     There is no expected default values because it would depend on the chip
199     used. We can still save them in order to check they have changed later.
200     """
201     if is_sandbox(u_boot_console):
202         tpm2_sandbox_init(u_boot_console)
203
204     force_init(u_boot_console)
205     ram = u_boot_utils.find_ram_base(u_boot_console)
206
207     read_cap = u_boot_console.run_command('tpm2 get_capability 0x6 0x20e 0x200 1') #0x%x 1' % ram)
208     output = u_boot_console.run_command('echo $?')
209     assert output.endswith('0')
210     assert 'Property 0x0000020e: 0x00000000' in read_cap
211
212 @pytest.mark.buildconfigspec('cmd_tpm_v2')
213 def test_tpm2_dam_parameters(u_boot_console):
214     """Execute a TPM2_DictionaryAttackParameters command.
215
216     Change Dictionary Attack Mitigation (DAM) parameters. Ask the TPM to change:
217     - Max number of failed authentication before lockout: 3
218     - Time before the failure counter is automatically decremented: 10 sec
219     - Time after a lockout failure before it can be attempted again: 0 sec
220
221     For an unknown reason, the DAM parameters must be changed before changing
222     the authentication, otherwise the lockout will be engaged after the first
223     failed authentication attempt.
224     """
225     if is_sandbox(u_boot_console):
226         tpm2_sandbox_init(u_boot_console)
227     force_init(u_boot_console)
228     ram = u_boot_utils.find_ram_base(u_boot_console)
229
230     # Set the DAM parameters to known values
231     u_boot_console.run_command('tpm2 dam_parameters 3 10 0')
232     output = u_boot_console.run_command('echo $?')
233     assert output.endswith('0')
234
235     # Check the values have been saved
236     read_cap = u_boot_console.run_command('tpm2 get_capability 0x6 0x20f 0x%x 3' % ram)
237     output = u_boot_console.run_command('echo $?')
238     assert output.endswith('0')
239     assert 'Property 0x0000020f: 0x00000003' in read_cap
240     assert 'Property 0x00000210: 0x0000000a' in read_cap
241     assert 'Property 0x00000211: 0x00000000' in read_cap
242
243 @pytest.mark.buildconfigspec('cmd_tpm_v2')
244 def test_tpm2_pcr_read(u_boot_console):
245     """Execute a TPM2_PCR_Read command.
246
247     Perform a PCR read of the 0th PCR. Must be zero.
248     """
249     if is_sandbox(u_boot_console):
250         tpm2_sandbox_init(u_boot_console)
251
252     force_init(u_boot_console)
253     ram = u_boot_utils.find_ram_base(u_boot_console)
254
255     read_pcr = u_boot_console.run_command('tpm2 pcr_read 0 0x%x' % ram)
256     output = u_boot_console.run_command('echo $?')
257     assert output.endswith('0')
258
259     # Save the number of PCR updates
260     str = re.findall(r'\d+ known updates', read_pcr)[0]
261     global updates
262     updates = int(re.findall(r'\d+', str)[0])
263
264     # Check the output value
265     assert 'PCR #0 content' in read_pcr
266     assert '00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00' in read_pcr
267
268 @pytest.mark.buildconfigspec('cmd_tpm_v2')
269 def test_tpm2_pcr_extend(u_boot_console):
270     """Execute a TPM2_PCR_Extend command.
271
272     Perform a PCR extension with a known hash in memory (zeroed since the board
273     must have been rebooted).
274
275     No authentication mechanism is used here, not protecting against packet
276     replay, yet.
277     """
278     if is_sandbox(u_boot_console):
279         tpm2_sandbox_init(u_boot_console)
280     force_init(u_boot_console)
281     ram = u_boot_utils.find_ram_base(u_boot_console)
282
283     u_boot_console.run_command('tpm2 pcr_extend 0 0x%x' % ram)
284     output = u_boot_console.run_command('echo $?')
285     assert output.endswith('0')
286
287     # Read the value back into a different place so we can still use 'ram' as
288     # our zero bytes
289     read_pcr = u_boot_console.run_command('tpm2 pcr_read 0 0x%x' % (ram + 0x20))
290     output = u_boot_console.run_command('echo $?')
291     assert output.endswith('0')
292     assert 'f5 a5 fd 42 d1 6a 20 30 27 98 ef 6e d3 09 97 9b' in read_pcr
293     assert '43 00 3d 23 20 d9 f0 e8 ea 98 31 a9 27 59 fb 4b' in read_pcr
294
295     str = re.findall(r'\d+ known updates', read_pcr)[0]
296     new_updates = int(re.findall(r'\d+', str)[0])
297     assert (updates + 1) == new_updates
298
299     u_boot_console.run_command('tpm2 pcr_extend 0 0x%x' % ram)
300     output = u_boot_console.run_command('echo $?')
301     assert output.endswith('0')
302
303     read_pcr = u_boot_console.run_command('tpm2 pcr_read 0 0x%x' % (ram + 0x20))
304     output = u_boot_console.run_command('echo $?')
305     assert output.endswith('0')
306     assert '7a 05 01 f5 95 7b df 9c b3 a8 ff 49 66 f0 22 65' in read_pcr
307     assert 'f9 68 65 8b 7a 9c 62 64 2c ba 11 65 e8 66 42 f5' in read_pcr
308
309     str = re.findall(r'\d+ known updates', read_pcr)[0]
310     new_updates = int(re.findall(r'\d+', str)[0])
311     assert (updates + 2) == new_updates
312
313 @pytest.mark.buildconfigspec('cmd_tpm_v2')
314 def test_tpm2_cleanup(u_boot_console):
315     """Ensure the TPM is cleared from password or test related configuration."""
316
317     force_init(u_boot_console, True)