1# Copyright 2018 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import logging, re
6from autotest_lib.client.bin import test, utils
7from autotest_lib.client.common_lib import error
8from autotest_lib.client.cros import service_stopper
9from autotest_lib.client.cros import cryptohome
10
11tpm_owner_password = ''
12tpm_pw_hex = ''
13
14def run_tpmc_cmd(subcommand):
15    """Make this test more readable by simplifying commonly used tpmc command.
16
17    @param subcommand: String of the tpmc subcommand (read, raw, ...)
18    @return String output (which may be empty).
19    """
20
21    # Run tpmc command, redirect stderr to stdout, and merge to one line.
22    cmd = 'tpmc %s 2>&1 | awk 1 ORS=""' % subcommand
23    return utils.system_output(cmd, ignore_status=True).strip()
24
25def check_tpmc(subcommand, expected):
26    """Runs tpmc command and checks the output against an expected regex.
27
28    @param subcommand: String of the tpmc subcommand (read, raw, ...)
29    @param expected: String re.
30    @raises error.TestError() for not matching expected.
31    """
32    error_msg = 'invalid response to tpmc %s' % subcommand
33    out = run_tpmc_cmd(subcommand)
34    if (not re.match(expected, out)):
35        raise error.TestError('%s: %s' % (error_msg, out))
36
37    return out
38
39def expect_tpmc_error(subcommand, expected_error):
40    check_tpmc(subcommand, '.*failed.*%s.*' % expected_error)
41
42class firmware_Cr50VirtualNVRam(test.test):
43    'Tests the virtual NVRAM functionality in Cr50 through TPM commands.'
44
45    version = 1
46
47    def __take_tpm_ownership(self):
48        global tpm_owner_password
49        global tpm_pw_hex
50        cryptohome.take_tpm_ownership(wait_for_ownership=True)
51
52        tpm_owner_password = cryptohome.get_tpm_status()['Password']
53        if not tpm_owner_password:
54            raise error.TestError('TPM owner password is empty after '
55                                  'taking ownership.')
56        for ch in tpm_owner_password:
57            tpm_pw_hex = tpm_pw_hex + format(ord(ch), 'x') + ' '
58
59    def __read_tests(self):
60        # Check reading board ID returns a valid value.
61        bid = check_tpmc('read 0x3fff00 0xc',
62                         '([0-9a-f]{1,2} ?){12}')
63
64        # Check that a subset of board ID can be read.
65        check_tpmc('read 0x3fff00 0x6',
66                   ' '.join(bid.split()[:6]))  # Matches previous read
67
68        # Check that size constraints are respected.
69        expect_tpmc_error('read 0x3fffff 0xd',
70                          '0x146')  # TPM_RC_NV_RANGE
71
72        # Check zero-length can be read.
73        check_tpmc('read 0x3fff00 0x0', '')
74
75        # Check arbitrary index can be read.
76        check_tpmc('read 0x3fff73 0x0', '')
77
78        # Check highest index can be read.
79        check_tpmc('read 0x3fffff 0x0', '')
80
81    def __get_write_cmd(self, index, offset, size):
82        assert (size + 35) < 256
83        assert offset < 256
84        cmd_size = format(35 + size, 'x') + ' '
85        size_hex = '00 ' + format(size, 'x') + ' '
86        offset_hex = '00 ' + format(offset, 'x') + ' '
87        data = ''
88        for _ in range(size):
89            data = data + 'ff '
90
91        return ('raw '
92                '80 02 '                    # TPM_ST_SESSIONS
93                '00 00 00 ' + cmd_size +    # commandSize
94                '00 00 01 37 ' +            # TPM_CC_NV_Write
95                 index + ' ' +              # authHandle
96                 index + ' ' +              # nvIndex
97                '00 00 00 09 '              # sessionSize
98                '40 00 00 09 '              # passwordAuth
99                '00 00 '                    # nonceSize
100                '00 '                       # sessionAttributes
101                '00 00 ' +                  # password length
102                 size_hex +                 # dataSize
103                 data +                     # data
104                 offset_hex)                # offset
105
106    def __write_tests(self):
107        # Check an implemented index cannot be written to:
108
109        # Zero-length write.
110        expect_tpmc_error(self.__get_write_cmd('01 3f ff 00', 0, 0),
111                          '0x148')  # TPM_RC_NV_LOCKED
112
113        # Single byte.
114        expect_tpmc_error(self.__get_write_cmd('01 3f ff 00', 0, 1),
115                          '0x148')  # TPM_RC_NV_LOCKED
116
117        # Single byte, offset.
118        expect_tpmc_error(self.__get_write_cmd('01 3f ff 00', 4, 1),
119                          '0x148')  # TPM_RC_NV_LOCKED
120
121        # Write full length of index.
122        expect_tpmc_error(self.__get_write_cmd('01 3f ff 00', 0, 12),
123                          '0x148')  # TPM_RC_NV_LOCKED
124
125        # Check an unimplemented index cannot be written to.
126        expect_tpmc_error(self.__get_write_cmd('01 3f ff ff', 0, 1),
127                          '0x148')  # TPM_RC_NV_LOCKED
128
129    def __get_define_cmd(self, index, size):
130        assert (len(tpm_owner_password) + 45) < 256
131        pw_sz = format(len(tpm_owner_password), 'x') + ' '
132        session_sz = format(len(tpm_owner_password) + 9, 'x') + ' '
133        cmd_sz = format(len(tpm_owner_password) + 45, 'x') + ' '
134
135        return ('raw '
136                '80 02 '
137                '00 00 00 ' + cmd_sz  +  # commandSize
138                '00 00 01 2a '              # commandCode
139                '40 00 00 01 '              # TPM_RH_OWNER
140                '00 00 00 ' + session_sz +  # sessionSize
141                '40 00 00 09 '              # passwordAuth
142                '00 00 '                    # nonceSize
143                '00 '                       # sessionAttributes
144                '00 ' + pw_sz +             # password length
145                tpm_pw_hex +                # password
146                '00 00 '                    # auth value
147
148                # TPM2B_NV_PUBLIC: publicInfo
149                  '00 0e ' +                 # size
150                  index + ' '                # nvIndex
151                  '00 0b '                   # TPM_ALG_SHA256
152                  '00 02 00 02 '             # attributes
153                  '00 00 '                   # authPolicy
154                  '00 ' + format(size, 'x')) # size
155
156    def __get_undefine_cmd(self, index):
157        assert (len(tpm_owner_password) + 31) < 256
158        pw_sz = format(len(tpm_owner_password), 'x') + ' '
159        session_sz = format(len(tpm_owner_password) + 9, 'x') + ' '
160        cmd_sz = format(len(tpm_owner_password) + 31, 'x') + ' '
161
162        return ('raw '
163                '80 02 '
164                '00 00 00 ' + cmd_sz + ' ' +  # commandSize
165                '00 00 01 22 '                # commandCode
166                '40 00 00 01 ' +              # TPM_RH_OWNER
167                index + ' ' +                 # nvIndex
168                '00 00 00 ' + session_sz +    # sessionSize
169                '40 00 00 09 '                # passwordAuth
170                '00 00 '                      # nonceSize
171                '00 '                         # sessionAttributes
172                '00 ' + pw_sz +               # password length
173                tpm_pw_hex)                   # password
174
175    def __definespace_sanity_check(self):
176        # A space outside the virtual range can be defined
177        check_tpmc(self.__get_define_cmd('01 4f aa df', 12),
178                   '(0x[0-9]{2} ){6}'
179                   '(0x00 ){4}'        # TPM_RC_SUCCESS
180                   '(0x[0-9]{2} ?){9}')
181
182        # A space outside the virtual range can be undefined.
183        check_tpmc(self.__get_undefine_cmd('01 4f aa df'),
184                   '(0x[0-9]{2} ){6}'
185                   '(0x00 ){4}'        # TPM_RC_SUCCESS
186                   '(0x[0-9]{2} ?){9}')
187
188    def __definespace_tests(self):
189        # Check an implemented space in the virtual range cannot be defined.
190        expect_tpmc_error(self.__get_define_cmd('01 3f ff 00', 12),
191                          '0x149')  # TPM_RC_NV_AUTHORIZATION
192
193        # Check an unimplemented space in the virtual range cannot be defined.
194        expect_tpmc_error(self.__get_define_cmd('01 3f ff df', 12),
195                          '0x149')  # TPM_RC_NV_AUTHORIZATION
196
197    def __undefinespace_tests(self):
198        # Check an implemented space in the virtual range cannot be defined.
199        expect_tpmc_error(self.__get_undefine_cmd('01 3f ff 00'),
200                          '0x149')  # TPM_RC_NV_AUTHORIZATION
201
202        # Check an unimplemented space in the virtual range cannot be defined.
203        expect_tpmc_error(self.__get_undefine_cmd('01 3f ff df'),
204                          '0x149')  # TPM_RC_NV_AUTHORIZATION
205
206    def __readpublic_test(self):
207        public = check_tpmc(('raw '
208                    '80 01 '         # TPM_ST_NO_SESSIONS
209                    '00 00 00 0e '   # commandSize
210                    '00 00 01 69 '   # TPM_CC_ReadPublic
211                    '01 3f ff 00'),  # nvIndex
212                   '(0x([0-9a-f]){2} *){62}')
213
214        # For attribute details see Table 204, Part 2 of TPM2.0 spec
215
216        attributes = hex(1 << 10 | # TPMA_NV_POLICY_DELETE
217                         1 << 11 | # TPMA_NV_WRITELOCKED
218                         1 << 13 | # TPMA_NV_WRITEDEFINE
219                         1 << 18 | # TPMA_NV_AUTHREAD
220                         1 << 29)  # TPMA_NV_WRITTEN
221
222        attributes = '%s %s %s %s ' % (attributes[2:4],
223                                       attributes[4:6],
224                                       attributes[6:8],
225                                       attributes[8:10])
226
227        expected = ('80 01 '        # TPM_ST_NO_SESSIONS
228                    '00 00 00 3e '  # responseSize
229                    '00 00 00 00 '  # responseCode
230
231                    # TPM2B_PUBLIC: nvPublic
232                      '00 0e '        # size
233                      '01 3f ff 00 '  # nvIndex
234                      '00 0b ' +      # TPM_ALG_SHA256
235                      attributes +    # attributes
236                      '00 00 '        # authPolicy
237                      '00 0c '        # dataSize
238
239                   # TPM2B_NAME: name
240                     '00 22 '  # size
241                     '([0-9a-f] ?){34} ')
242
243        if (not re.match(expected, re.sub('0x', '', public))):
244            raise error.TestError('%s does not match expected (%s)'
245                                  % (public, expected))
246
247    def __readlock_test(self):
248        # Virtual NV indices explicitly cannot be read locked, and attempts
249        # to do so will return an authorization error.
250
251        expect_tpmc_error(('raw '
252                           '80 02 '             # TPM_ST_SESSIONS
253                           '00 00 00 1f '       # commandSize
254                           '00 00 01 4f '       # TPM_CC_NV_ReadLock
255                           '01 3f ff 00 '       # authHandle
256                           '01 3f ff 00 '       # nvIndex
257                           '00 00 00 09 '       # sessionSize
258                           '40 00 00 09 '       # password auth
259                           '00 00 00 00 00'),   # empty password
260                          '0x149')  # TPM_RC_NV_AUTHORIZATION
261
262    def __writelock_test(self):
263        # Virtual NV indices have no write policy defined, so cannot be write
264        # locked.
265
266        expect_tpmc_error(('raw '
267                           '80 02 '              # TPM_ST_SESSIONS
268                           '00 00 00 1f '        # commandSize
269                           '00 00 01 38 '        # TPM_CC_NV_WriteLock
270                           '01 3f ff 00 '        # authHandle
271                           '01 3f ff 00 '        # nvIndex
272                           '00 00 00 09 '        # sessionSize
273                           '40 00 00 09 '        # password auth
274                           '00 00 00 00 00'),    # empty password
275                          '0x12f');  # TPM_RC_AUTH_UNAVAILABLE
276
277    def initialize(self):
278        self.__take_tpm_ownership()
279        # Stop services that access to the TPM, to be able to use tpmc.
280        # Note: for TPM2 the order of re-starting services (they are started
281        # in the reversed listed order) is important: e.g. tpm_managerd must
282        # start after trunksd, and cryptohomed after attestationd.
283        self._services = service_stopper.ServiceStopper(['cryptohomed',
284                                                         'chapsd',
285                                                         'attestationd',
286                                                         'tpm_managerd',
287                                                         'tcsd',
288                                                         'trunksd'])
289        self._services.stop_services()
290
291    def run_once(self):
292        """Run a few TPM state checks."""
293        # If first virtual index is not defined, assumed we are not running
294        # on cr50, or running on an old build of cr50. Skip the test.
295        if re.match('.*failed.*0x18b.*',  # TPM_RC_HANDLE
296                    run_tpmc_cmd('read 0x3fff00 0x0')):
297            raise error.TestNAError("TPM does not support vNVRAM")
298
299        self.__readpublic_test()
300        self.__definespace_sanity_check()
301        self.__definespace_tests()
302        self.__undefinespace_tests()
303        self.__readlock_test()
304        self.__writelock_test()
305        self.__write_tests()
306        self.__read_tests()
307
308    def cleanup(self):
309        self._services.restore_services()
310