1# Copyright 2017 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 __future__ import print_function
6
7import logging
8
9from autotest_lib.client.common_lib import error
10from autotest_lib.server import autotest
11from autotest_lib.server.cros.faft.cr50_test import Cr50Test
12
13
14class firmware_FWMPDisableCCD(Cr50Test):
15    """A test that uses cryptohome to set the FWMP flags and verifies that
16    cr50 disables/enables console unlock."""
17    version = 1
18
19    FWMP_DEV_DISABLE_CCD_UNLOCK = (1 << 6)
20    GSCTOOL_ERR = 'Error: rv 7, response 7'
21
22    def initialize(self, host, cmdline_args, full_args):
23        """Initialize servo check if cr50 exists"""
24        super(firmware_FWMPDisableCCD, self).initialize(host, cmdline_args,
25                full_args)
26        self.fast_ccd_open(enable_testlab=True)
27
28
29    def try_set_ccd_level(self, level, fwmp_disabled_ccd):
30        """Try setting the ccd level with a password.
31
32        The normal Cr50 ccd path requires entering dev mode. Entering dev mode
33        may be disabled by the FWMP flags. Entering dev mode also erases the
34        FWMP. The test uses a password to get around the dev mode requirement.
35        Send CCD commands from the commandline with the password. Check the
36        output to make sure if it fails, it failed because of the FWMP.
37
38        @param level: the desired ccd level: 'open' or 'unlock'.
39        @param fwmp_disabled_ccd: True if 'ccd set $LEVEL' should fail
40        """
41        # Make sure the console is locked
42        self.cr50.set_ccd_level('lock')
43        try:
44            self.cr50.set_ccd_level(level, self.CCD_PASSWORD)
45            if fwmp_disabled_ccd:
46                raise error.TestFail('FWMP failed to prevent %r' % level)
47        except error.TestFail as e:
48            logging.info(e)
49            if fwmp_disabled_ccd:
50                if ("FWMP disabled 'ccd open'" in str(e) or
51                    'Console unlock not allowed' in str(e)):
52                    logging.info('FWMP successfully disabled ccd %s', level)
53                    return
54                else:
55                    raise error.TestFail('FWMP disabled %s in unexpected '
56                                         'manner %r' % (level, str(e)))
57            raise
58
59
60    def open_cr50_and_setup_ccd(self):
61        """Configure cr50 ccd for the test.
62
63        Open Cr50. Reset ccd, so the capabilities are reset and the password is
64        cleared. Set OpenNoTPMWipe to Always, so the FWMP won't be cleared
65        during open.
66        """
67        # Clear the password and relock the console
68        self.cr50.send_command('ccd testlab open')
69        self.cr50.ccd_reset()
70        # Set this so when we run the open test, it won't clear the FWMP
71        self.cr50.set_cap('OpenNoTPMWipe', 'Always')
72
73
74    def cr50_check_lock_control(self, flags):
75        """Verify cr50 lock enable/disable works as intended based on flags.
76
77        If flags & self.FWMP_DEV_DISABLE_CCD_UNLOCK is true, lock disable should
78        fail.
79
80        This will only run during a test with access to the cr50  console
81
82        @param flags: A string with the FWMP settings.
83        """
84        fwmp_disabled_ccd = not not (self.FWMP_DEV_DISABLE_CCD_UNLOCK &
85                               int(flags, 16))
86
87        start_state = self.cr50.get_ccd_info('TPM')
88        if ('fwmp_lock' in start_state) != fwmp_disabled_ccd:
89            raise error.TestFail('Unexpected fwmp state with flags %s' % flags)
90
91        logging.info('Flags are set to %s ccd is%s permitted', flags,
92                     ' not' if fwmp_disabled_ccd else '')
93        if not self.faft_config.has_powerbutton:
94            logging.info('Can not test ccd without power button')
95            return
96
97        self.open_cr50_and_setup_ccd()
98        # Try setting password after FWMP has been created. Setting password is
99        # always allowed. Open and unlock should still be blocked. Opening cr50
100        # requires the device is in dev mode unless there's a password set. FWMP
101        # flags may disable dev mode. Set a password so we can get around this.
102        self.set_ccd_password(self.CCD_PASSWORD)
103
104        # run ccd commands with the password. ccd open and unlock should fail
105        # when the FWMP has disabled ccd.
106        self.try_set_ccd_level('open', fwmp_disabled_ccd)
107        if self.cr50.unlock_is_supported():
108            self.try_set_ccd_level('unlock', fwmp_disabled_ccd)
109
110        # Clear the password.
111        self.open_cr50_and_setup_ccd()
112        self.cr50.send_command('ccd lock')
113
114
115    def check_fwmp(self, flags, clear_fwmp, check_lock=True):
116        """Set the flags and verify ccd lock/unlock
117
118        Args:
119            flags: A string to used set the FWMP flags. If None, skip running
120                   firmware_SetFWMP.
121            clear_fwmp: True if the flags should be reset.
122            check_lock: Check ccd open
123        """
124        if clear_fwmp:
125            self.clear_fwmp()
126
127        logging.info('setting flags to %s', flags)
128        if flags:
129            autotest.Autotest(self.host).run_test('firmware_SetFWMP',
130                    flags=flags, fwmp_cleared=clear_fwmp,
131                    check_client_result=True)
132
133        # Verify ccd lock/unlock with the current flags works as intended.
134        if check_lock:
135            self.cr50_check_lock_control(flags if flags else '0')
136
137
138    def run_once(self):
139        """Verify FWMP disable with different flag values"""
140        # Skip checking ccd open, so the DUT doesn't reboot
141        self.check_fwmp('0xaa00', True, check_lock=False)
142        # Verify that the flags can be changed on the same boot
143        self.check_fwmp('0xbb00', False)
144
145        # Verify setting FWMP_DEV_DISABLE_CCD_UNLOCK disables ccd
146        self.check_fwmp(hex(self.FWMP_DEV_DISABLE_CCD_UNLOCK), True)
147
148        # 0x41 is the flag setting when dev boot is disabled. Make sure that
149        # nothing unexpected happens.
150        self.check_fwmp('0x41', True)
151
152        # Clear the TPM owner and verify lock can still be enabled/disabled when
153        # the FWMP has not been created
154        self.check_fwmp(None, True)
155