# Copyright 2017 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. from __future__ import print_function import logging import time import StringIO import subprocess from autotest_lib.client.common_lib import error, utils from autotest_lib.client.common_lib.cros import tpm_utils from autotest_lib.client.cros import constants from autotest_lib.server.cros.faft.firmware_test import FirmwareTest class firmware_IntegratedU2F(FirmwareTest): """Verify U2F using the on-board cr50 firmware works.""" version = 1 U2F_TEST_PATH = '/usr/local/bin/U2FTest' U2F_FORCE_PATH = '/var/lib/u2f/force/u2f.force' G2F_FORCE_PATH = '/var/lib/u2f/force/g2f.force' USER_KEYS_FORCE_PATH = '/var/lib/u2f/force/user_keys.force' VID = '18D1' PID = '502C' SHORT_WAIT = 1 def cleanup(self): """Remove *.force files""" self.host.run('rm -f /var/lib/u2f/force/*.force') # Restart u2fd so that flag change takes effect. self.host.run('restart u2fd') # Put the device back to a known state; also restarts the device. tpm_utils.ClearTPMOwnerRequest(self.host, wait_for_ready=True) super(firmware_IntegratedU2F, self).cleanup() def u2fd_is_running(self): """Returns True if u2fd is running on the host""" return 'running' in self.host.run('status u2fd').stdout def cryptohome_ready(self): """Return True if cryptohome is running.""" return 'running' in self.host.run('status cryptohomed').stdout def owner_key_exists(self): """Return True if constants.OWNER_KEY_FILE exists.""" logging.info('checking for owner key') return self.host.path_exists(constants.OWNER_KEY_FILE) def wait_for_policy(self): """Start u2fd on the host""" # Wait for cryptohome to show the TPM is ready before logging in. if not utils.wait_for_value(self.cryptohome_ready, True, timeout_sec=60): raise error.TestError('Cryptohome did not start') # Wait for the owner key to exist before trying to start u2fd. if not utils.wait_for_value(self.owner_key_exists, True, timeout_sec=120): raise error.TestError('Device did not create owner key') def attestation_init_complete(self): """Return True if prepare_for_enrollment has completed""" return 'prepared_for_enrollment: true' in self.host.run( 'attestation_client status').stdout def chaps_init_complete(self): """Return True if chaps token initialization has completed""" try: return 'available with 2 token' in self.host.run( 'chaps_client --ping').stderr except error.AutoservRunError: logging.info('Chaps no response') return False def wait_for_cr50(self): """Wait for cr50 to complete any OOBE initialization""" if not utils.wait_for_value( self.attestation_init_complete, True, timeout_sec=120): raise error.TestError('Attestation initialization did not complete') if not utils.wait_for_value( self.chaps_init_complete, True, timeout_sec=120): raise error.TestError('Chaps initialization did not complete') def set_u2fd_flags(self, u2f, g2f, user_keys): # Start by removing all flags. self.host.run('rm -f /var/lib/u2f/force/*.force') if u2f: self.host.run('touch %s' % self.U2F_FORCE_PATH) if g2f: self.host.run('touch %s' % self.G2F_FORCE_PATH) if user_keys: self.host.run('touch %s' % self.USER_KEYS_FORCE_PATH) # Restart u2fd so that flag change takes effect. self.host.run('restart u2fd') # Make sure it is still running if not self.u2fd_is_running(): raise error.TestFail('could not start u2fd') logging.info('u2fd is running') def find_u2f_device(self): """Find the U2F device Returns: 0 if the device hasn't been found. Non-zero if it has """ self.device = '' path = '/sys/bus/hid/devices/*:%s:%s.*/hidraw' % (self.VID, self.PID) try: self.device = self.host.run('ls ' + path).stdout.strip() except error.AutoservRunError as e: logging.info('Could not find device') return len(self.device) def update_u2f_device_path(self): """Get the integrated u2f device.""" start_time = time.time() utils.wait_for_value(self.find_u2f_device, max_threshold=1, timeout_sec=30) wait_time = int(time.time() - start_time) if wait_time: logging.info('Took %ss to find device', wait_time) self.dev_path = '/dev/' + self.device def check_u2ftest_and_press_power_button(self): """Check stdout and press the power button if prompted Returns: True if the process has terminated. """ time.sleep(self.SHORT_WAIT) self.output += self.get_u2ftest_output() logging.info(self.output) if 'Touch device and hit enter..' in self.output: # press the power button self.servo.power_short_press() logging.info('pressed power button') time.sleep(self.SHORT_WAIT) # send enter to the test process self.u2ftest_job.sp.stdin.write('\n') logging.info('hit enter') self.output = '' return self.u2ftest_job.sp.poll() is not None def get_u2ftest_output(self): """Read the new output""" self.u2ftest_job.process_output() self.stdout.seek(self.last_len) output = self.stdout.read().strip() self.last_len = self.stdout.len return output def run_u2ftest(self): """Run U2FTest with the U2F device""" self.last_len = 0 self.output = '' u2ftest_cmd = utils.sh_escape('%s %s' % (self.U2F_TEST_PATH, self.dev_path)) full_ssh_command = '%s "%s"' % (self.host.ssh_command(options='-tt'), u2ftest_cmd) self.stdout = StringIO.StringIO() # Start running U2FTest in the background. self.u2ftest_job = utils.BgJob(full_ssh_command, nickname='u2ftest', stdout_tee=self.stdout, stderr_tee=utils.TEE_TO_LOGS, stdin=subprocess.PIPE) if self.u2ftest_job == None: raise error.TestFail('could not start u2ftest') try: utils.wait_for_value(self.check_u2ftest_and_press_power_button, expected_value=True, timeout_sec=30) finally: self.close_u2ftest() def close_u2ftest(self): """Terminate the process and check the results.""" exit_status = utils.nuke_subprocess(self.u2ftest_job.sp) stdout = self.stdout.getvalue().strip() if stdout: logging.debug('stdout of U2FTest:\n%s', stdout) if exit_status: logging.error('stderr of U2FTest:\n%s', self.output) raise error.TestError('U2FTest: %s' % self.output) def run_once(self, host): """Run U2FTest""" self.host = host if not self.host.path_exists(self.U2F_TEST_PATH): raise error.TestNAError('Device does not have U2FTest support') # u2fd reads files from the user's home dir, so we need to log in. self.host.run('/usr/local/autotest/bin/autologin.py') # u2fd needs the policy file to exist. self.wait_for_policy() # Wait for OOBE initialiation to complete, as long-running operations # (eg RSA key generation) could cause U2F operations to timeout. self.wait_for_cr50() logging.info("testing u2fd --u2f") self.set_u2fd_flags(True, False, False) # Setting the flags restarts u2fd, which will re-create the u2f device. self.update_u2f_device_path() self.run_u2ftest(); logging.info("testing u2fd --g2f") self.set_u2fd_flags(False, True, False) self.update_u2f_device_path() self.run_u2ftest(); logging.info("testing u2fd --u2f --user_keys") self.set_u2fd_flags(True, False, True) self.update_u2f_device_path() self.run_u2ftest(); logging.info("testing u2fd --g2f --user_keys") self.set_u2fd_flags(False, True, True) self.update_u2f_device_path() self.run_u2ftest();