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