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
5from hashlib import sha256
6import logging
7from pprint import pformat
8
9from autotest_lib.client.common_lib import error
10from autotest_lib.client.common_lib.cros import pinweaver_client
11from autotest_lib.server import test
12
13
14def compute_empty_tree_auxilary_hashes(bits_per_level=2, height=6):
15    """Returns a binary string representation of the auxilary digests of an
16    empty path in a Merkle tree with the specified parameters.
17    """
18    num_siblings = 2 ^ bits_per_level - 1
19    child = b'\0' * 32
20    result = b''
21    for _ in range(height):
22        part = child * num_siblings
23        child = sha256(part + child).digest()
24        result += part
25    return result
26
27class firmware_Cr50PinWeaverServer(test.test):
28    """Tests the PinWeaver functionality on Cr50 using pinweaver_client through
29    trunksd.
30    """
31
32    version = 1
33
34    RESULT_CODE_SUCCESS = 'EC_SUCCESS'
35    RESULT_CODE_AUTH_FAILED = 'PW_ERR_LOWENT_AUTH_FAILED'
36    RESULT_CODE_RATE_LIMITED = 'PW_ERR_RATE_LIMIT_REACHED'
37
38    def run_once(self, host):
39        """Runs the firmware_Cr50PinWeaverServer test.
40        This test is made up of the pinweaver_client self test, and a test that
41        checks that PinWeaver works as expected across device reboots.
42        """
43
44        # Run "pinweaver_client selftest".
45        try:
46            if not pinweaver_client.SelfTest(host):
47                raise error.TestFail('Failed SelfTest: %s' %
48                                     self.__class__.__name__)
49        except pinweaver_client.PinWeaverNotAvailableError:
50            logging.info('PinWeaver not supported!')
51            raise error.TestNAError('PinWeaver is not available')
52
53        # Check PinWeaver logic across reboots including the reboot counter.
54        # Insert an entry.
55        #
56        # Label 0 is guaranteed to be empty because the self test above resets
57        # the tree and removes the leaf it adds.
58        label = 0
59        hashes = compute_empty_tree_auxilary_hashes()
60        # TODO(mruthven): always use hashes.hex() after python3 migration.
61        h_aux = hashes.hex() if hasattr(hashes,
62                                        'hex') else hashes.encode('hex')
63        le_secret = sha256(b'1234').hexdigest()
64        he_secret = sha256(b'ag3#l4Z9').hexdigest()
65        reset_secret = sha256(b'[email protected]').hexdigest()
66        delay_schedule = '5 %d' % 0x00ffffffff
67        result = pinweaver_client.InsertLeaf(host, label, h_aux, le_secret,
68                                             he_secret, reset_secret,
69                                             delay_schedule)
70        logging.info('Insert: %s', pformat(result))
71        if (result['result_code']['name'] !=
72            firmware_Cr50PinWeaverServer.RESULT_CODE_SUCCESS):
73            raise error.TestFail('Failed InsertLeaf: %s' %
74                                 self.__class__.__name__)
75        cred_metadata = result['cred_metadata']
76
77        # Exhaust the allowed number of attempts.
78        for i in range(6):
79            result = pinweaver_client.TryAuth(host, h_aux, '0' * 64,
80                                              cred_metadata)
81            if result['cred_metadata']:
82                cred_metadata = result['cred_metadata']
83            logging.info('TryAuth: %s', pformat(result))
84            if ((i <= 4 and result['result_code']['name'] !=
85                 firmware_Cr50PinWeaverServer.RESULT_CODE_AUTH_FAILED) or
86                (i > 4 and result['result_code']['name'] !=
87                 firmware_Cr50PinWeaverServer.RESULT_CODE_RATE_LIMITED)):
88                raise error.TestFail('Failed TryAuth: %s' %
89                                     self.__class__.__name__)
90
91        if result['seconds_to_wait'] == 0:
92            raise error.TestFail('Failed TryAuth: %s' %
93                                 self.__class__.__name__)
94
95        # Reboot the device. This calls TPM_startup() which reloads the Merkle
96        # tree from NVRAM. Note that this doesn't reset the timer on Cr50, so
97        # restart_count doesn't increment.
98        host.reboot()
99
100        # Verify that the lockout is still enforced.
101        result = pinweaver_client.TryAuth(host, h_aux, le_secret, cred_metadata)
102        logging.info('TryAuth: %s', pformat(result))
103        if (result['result_code']['name'] !=
104            firmware_Cr50PinWeaverServer.RESULT_CODE_RATE_LIMITED):
105            raise error.TestFail('Failed TryAuth: %s' %
106                                 self.__class__.__name__)
107        if result['seconds_to_wait'] == 0:
108            raise error.TestFail('Failed TryAuth: %s' %
109                                 self.__class__.__name__)
110
111        # Perform a reset.
112        result = pinweaver_client.ResetAuth(host, h_aux, reset_secret,
113                                            cred_metadata)
114        if (result['result_code']['name'] !=
115            firmware_Cr50PinWeaverServer.RESULT_CODE_SUCCESS):
116            raise error.TestFail('Failed ResetAuth: %s' %
117                                 self.__class__.__name__)
118        cred_metadata = result['cred_metadata']
119        logging.info('ResetAuth: %s', pformat(result))
120
121        # Verify that using a PIN would work.
122        result = pinweaver_client.TryAuth(host, h_aux, le_secret, cred_metadata)
123        mac = result['mac']
124        logging.info('TryAuth: %s', pformat(result))
125        if (result['result_code']['name'] !=
126            firmware_Cr50PinWeaverServer.RESULT_CODE_SUCCESS):
127            raise error.TestFail('Failed TryAuth: %s' %
128                                 self.__class__.__name__)
129
130        # Remove the leaf.
131        result = pinweaver_client.RemoveLeaf(host, label, h_aux, mac)
132        logging.info('RemoveLeaf: %s', pformat(result))
133        if (result['result_code']['name'] !=
134            firmware_Cr50PinWeaverServer.RESULT_CODE_SUCCESS):
135            raise error.TestFail('Failed RemoveLeaf: %s' %
136                                 self.__class__.__name__)
137