Home | History | Annotate | Download | only in cros
      1 # Copyright (c) 2012 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 
      5 import dbus, gobject, logging, os, random, re, shutil, string, time
      6 from dbus.mainloop.glib import DBusGMainLoop
      7 
      8 import common, constants
      9 from autotest_lib.client.bin import utils
     10 from autotest_lib.client.common_lib import error
     11 from autotest_lib.client.cros.cros_disks import DBusClient
     12 
     13 CRYPTOHOME_CMD = '/usr/sbin/cryptohome'
     14 GUEST_USER_NAME = '$guest'
     15 UNAVAILABLE_ACTION = 'Unknown action or no action given.'
     16 MOUNT_RETRY_COUNT = 20
     17 TEMP_MOUNT_PATTERN = '/home/.shadow/%s/temporary_mount'
     18 VAULT_PATH_PATTERN = '/home/.shadow/%s/vault'
     19 
     20 class ChromiumOSError(error.TestError):
     21     """Generic error for ChromiumOS-specific exceptions."""
     22     pass
     23 
     24 def __run_cmd(cmd):
     25     return utils.system_output(cmd + ' 2>&1', retain_output=True,
     26                                ignore_status=True).strip()
     27 
     28 def get_user_hash(user):
     29     """Get the user hash for the given user."""
     30     return utils.system_output(['cryptohome', '--action=obfuscate_user',
     31                                 '--user=%s' % user])
     32 
     33 
     34 def user_path(user):
     35     """Get the user mount point for the given user."""
     36     return utils.system_output(['cryptohome-path', 'user', user])
     37 
     38 
     39 def system_path(user):
     40     """Get the system mount point for the given user."""
     41     return utils.system_output(['cryptohome-path', 'system', user])
     42 
     43 
     44 def temporary_mount_path(user):
     45     """Get the vault mount path used during crypto-migration for the user.
     46 
     47     @param user: user the temporary mount should be for
     48     """
     49     return TEMP_MOUNT_PATTERN % (get_user_hash(user))
     50 
     51 
     52 def vault_path(user):
     53     """ Get the vault path for the given user.
     54 
     55     @param user: The user who's vault path should be returned.
     56     """
     57     return VAULT_PATH_PATTERN % (get_user_hash(user))
     58 
     59 
     60 def ensure_clean_cryptohome_for(user, password=None):
     61     """Ensure a fresh cryptohome exists for user.
     62 
     63     @param user: user who needs a shiny new cryptohome.
     64     @param password: if unset, a random password will be used.
     65     """
     66     if not password:
     67         password = ''.join(random.sample(string.ascii_lowercase, 6))
     68     unmount_vault(user)
     69     remove_vault(user)
     70     mount_vault(user, password, create=True)
     71 
     72 
     73 def get_tpm_status():
     74     """Get the TPM status.
     75 
     76     Returns:
     77         A TPM status dictionary, for example:
     78         { 'Enabled': True,
     79           'Owned': True,
     80           'Being Owned': False,
     81           'Ready': True,
     82           'Password': ''
     83         }
     84     """
     85     out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_status')
     86     status = {}
     87     for field in ['Enabled', 'Owned', 'Being Owned', 'Ready']:
     88         match = re.search('TPM %s: (true|false)' % field, out)
     89         if not match:
     90             raise ChromiumOSError('Invalid TPM status: "%s".' % out)
     91         status[field] = match.group(1) == 'true'
     92     match = re.search('TPM Password: (\w*)', out)
     93     status['Password'] = ''
     94     if match:
     95         status['Password'] = match.group(1)
     96     return status
     97 
     98 
     99 def get_tpm_more_status():
    100     """Get more of the TPM status.
    101 
    102     Returns:
    103         A TPM more status dictionary, for example:
    104         { 'dictionary_attack_lockout_in_effect': False,
    105           'attestation_prepared': False,
    106           'boot_lockbox_finalized': False,
    107           'enabled': True,
    108           'owned': True,
    109           'owner_password': ''
    110           'dictionary_attack_counter': 0,
    111           'dictionary_attack_lockout_seconds_remaining': 0,
    112           'dictionary_attack_threshold': 10,
    113           'attestation_enrolled': False,
    114           'initialized': True,
    115           'verified_boot_measured': False,
    116           'install_lockbox_finalized': True
    117         }
    118         An empty dictionary is returned if the command is not supported.
    119     """
    120     status = {}
    121     out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_more_status | grep :')
    122     if out.startswith(UNAVAILABLE_ACTION):
    123         # --action=tpm_more_status only exists >= 41.
    124         logging.info('Method not supported!')
    125         return status
    126     for line in out.splitlines():
    127         items = line.strip().split(':')
    128         if items[1].strip() == 'false':
    129             value = False
    130         elif items[1].strip() == 'true':
    131             value = True
    132         elif items[1].strip().isdigit():
    133             value = int(items[1].strip())
    134         else:
    135             value = items[1].strip(' "')
    136         status[items[0]] = value
    137     return status
    138 
    139 
    140 def get_fwmp(cleared_fwmp=False):
    141     """Get the firmware management parameters.
    142 
    143     Args:
    144         cleared_fwmp: True if the space should not exist.
    145 
    146     Returns:
    147         The dictionary with the FWMP contents, for example:
    148         { 'flags': 0xbb41,
    149           'developer_key_hash':
    150             "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\
    151              000\000\000\000\000\000\000\000\000\000\000",
    152         }
    153         or a dictionary with the Error if the FWMP doesn't exist and
    154         cleared_fwmp is True
    155         { 'error': 'CRYPTOHOME_ERROR_FIRMWARE_MANAGEMENT_PARAMETERS_INVALID' }
    156 
    157     Raises:
    158          ChromiumOSError if any expected field is not found in the cryptohome
    159          output. This would typically happen when FWMP state does not match
    160          'clreared_fwmp'
    161     """
    162     out = __run_cmd(CRYPTOHOME_CMD +
    163                     ' --action=get_firmware_management_parameters')
    164 
    165     if cleared_fwmp:
    166         fields = ['error']
    167     else:
    168         fields = ['flags', 'developer_key_hash']
    169 
    170     status = {}
    171     for field in fields:
    172         match = re.search('%s: (\S+)\n' % field, out)
    173         if not match:
    174             raise ChromiumOSError('Invalid FWMP field %s: "%s".' %
    175                                   (field, out))
    176         status[field] = match.group(1)
    177     return status
    178 
    179 
    180 def set_fwmp(flags, developer_key_hash=None):
    181     """Set the firmware management parameter contents.
    182 
    183     Args:
    184         developer_key_hash: a string with the developer key hash
    185 
    186     Raises:
    187          ChromiumOSError cryptohome cannot set the FWMP contents
    188     """
    189     cmd = (CRYPTOHOME_CMD +
    190           ' --action=set_firmware_management_parameters '
    191           '--flags=' + flags)
    192     if developer_key_hash:
    193         cmd += ' --developer_key_hash=' + developer_key_hash
    194 
    195     out = __run_cmd(cmd)
    196     if 'SetFirmwareManagementParameters success' not in out:
    197         raise ChromiumOSError('failed to set FWMP: %s' % out)
    198 
    199 
    200 def is_tpm_lockout_in_effect():
    201     """Returns true if the TPM lockout is in effect; false otherwise."""
    202     status = get_tpm_more_status()
    203     return status.get('dictionary_attack_lockout_in_effect', None)
    204 
    205 
    206 def get_login_status():
    207     """Query the login status
    208 
    209     Returns:
    210         A login status dictionary containing:
    211         { 'owner_user_exists': True|False,
    212           'boot_lockbox_finalized': True|False
    213         }
    214     """
    215     out = __run_cmd(CRYPTOHOME_CMD + ' --action=get_login_status')
    216     status = {}
    217     for field in ['owner_user_exists', 'boot_lockbox_finalized']:
    218         match = re.search('%s: (true|false)' % field, out)
    219         if not match:
    220             raise ChromiumOSError('Invalid login status: "%s".' % out)
    221         status[field] = match.group(1) == 'true'
    222     return status
    223 
    224 
    225 def get_tpm_attestation_status():
    226     """Get the TPM attestation status.  Works similar to get_tpm_status().
    227     """
    228     out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_attestation_status')
    229     status = {}
    230     for field in ['Prepared', 'Enrolled']:
    231         match = re.search('Attestation %s: (true|false)' % field, out)
    232         if not match:
    233             raise ChromiumOSError('Invalid attestation status: "%s".' % out)
    234         status[field] = match.group(1) == 'true'
    235     return status
    236 
    237 
    238 def take_tpm_ownership(wait_for_ownership=True):
    239     """Take TPM owernship.
    240 
    241     Args:
    242         wait_for_ownership: block until TPM is owned if true
    243     """
    244     __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_take_ownership')
    245     if wait_for_ownership:
    246         while not get_tpm_status()['Owned']:
    247             time.sleep(0.1)
    248 
    249 
    250 def verify_ek():
    251     """Verify the TPM endorsement key.
    252 
    253     Returns true if EK is valid.
    254     """
    255     cmd = CRYPTOHOME_CMD + ' --action=tpm_verify_ek'
    256     return (utils.system(cmd, ignore_status=True) == 0)
    257 
    258 
    259 def remove_vault(user):
    260     """Remove the given user's vault from the shadow directory."""
    261     logging.debug('user is %s', user)
    262     user_hash = get_user_hash(user)
    263     logging.debug('Removing vault for user %s with hash %s', user, user_hash)
    264     cmd = CRYPTOHOME_CMD + ' --action=remove --force --user=%s' % user
    265     __run_cmd(cmd)
    266     # Ensure that the vault does not exist.
    267     if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
    268         raise ChromiumOSError('Cryptohome could not remove the user\'s vault.')
    269 
    270 
    271 def remove_all_vaults():
    272     """Remove any existing vaults from the shadow directory.
    273 
    274     This function must be run with root privileges.
    275     """
    276     for item in os.listdir(constants.SHADOW_ROOT):
    277         abs_item = os.path.join(constants.SHADOW_ROOT, item)
    278         if os.path.isdir(os.path.join(abs_item, 'vault')):
    279             logging.debug('Removing vault for user with hash %s', item)
    280             shutil.rmtree(abs_item)
    281 
    282 
    283 def mount_vault(user, password, create=False, key_label='bar'):
    284     """Mount the given user's vault. Mounts should be created by calling this
    285     function with create=True, and can be used afterwards with create=False.
    286     Only try to mount existing vaults created with this function.
    287 
    288     """
    289     args = [CRYPTOHOME_CMD, '--action=mount_ex', '--user=%s' % user,
    290             '--password=%s' % password, '--async']
    291     if create:
    292         args += ['--key_label=%s' % key_label, '--create']
    293     logging.info(__run_cmd(' '.join(args)))
    294     # Ensure that the vault exists in the shadow directory.
    295     user_hash = get_user_hash(user)
    296     if not os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
    297         retry = 0
    298         mounted = False
    299         while retry < MOUNT_RETRY_COUNT and not mounted:
    300             time.sleep(1)
    301             logging.info("Retry " + str(retry + 1))
    302             __run_cmd(' '.join(args))
    303             # TODO: Remove this additional call to get_user_hash(user) when
    304             # crbug.com/690994 is fixed
    305             user_hash = get_user_hash(user)
    306             if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
    307                 mounted = True
    308             retry += 1
    309         if not mounted:
    310             raise ChromiumOSError('Cryptohome vault not found after mount.')
    311     # Ensure that the vault is mounted.
    312     if not is_permanent_vault_mounted(user=user, allow_fail=True):
    313         raise ChromiumOSError('Cryptohome created a vault but did not mount.')
    314 
    315 
    316 def mount_guest():
    317     """Mount the guest vault."""
    318     args = [CRYPTOHOME_CMD, '--action=mount_guest', '--async']
    319     logging.info(__run_cmd(' '.join(args)))
    320     # Ensure that the guest vault is mounted.
    321     if not is_guest_vault_mounted(allow_fail=True):
    322         raise ChromiumOSError('Cryptohome did not mount guest vault.')
    323 
    324 
    325 def test_auth(user, password):
    326     cmd = [CRYPTOHOME_CMD, '--action=check_key_ex', '--user=%s' % user,
    327            '--password=%s' % password, '--async']
    328     out = __run_cmd(' '.join(cmd))
    329     logging.info(out)
    330     return 'Key authenticated.' in out
    331 
    332 
    333 def unmount_vault(user):
    334     """Unmount the given user's vault.
    335 
    336     Once unmounting for a specific user is supported, the user parameter will
    337     name the target user. See crosbug.com/20778.
    338     """
    339     __run_cmd(CRYPTOHOME_CMD + ' --action=unmount')
    340     # Ensure that the vault is not mounted.
    341     if is_vault_mounted(user, allow_fail=True):
    342         raise ChromiumOSError('Cryptohome did not unmount the user.')
    343 
    344 
    345 def __get_mount_info(mount_point, allow_fail=False):
    346     """Get information about the active mount at a given mount point."""
    347     cryptohomed_path = '/proc/$(pgrep cryptohomed)/mounts'
    348     try:
    349         logging.debug("Active cryptohome mounts:\n" +
    350                       utils.system_output('cat %s' % cryptohomed_path))
    351         mount_line = utils.system_output(
    352             'grep %s %s' % (mount_point, cryptohomed_path),
    353             ignore_status=allow_fail)
    354     except Exception as e:
    355         logging.error(e)
    356         raise ChromiumOSError('Could not get info about cryptohome vault '
    357                               'through %s. See logs for complete mount-point.'
    358                               % os.path.dirname(str(mount_point)))
    359     return mount_line.split()
    360 
    361 
    362 def __get_user_mount_info(user, allow_fail=False):
    363     """Get information about the active mounts for a given user.
    364 
    365     Returns the active mounts at the user's user and system mount points. If no
    366     user is given, the active mount at the shared mount point is returned
    367     (regular users have a bind-mount at this mount point for backwards
    368     compatibility; the guest user has a mount at this mount point only).
    369     """
    370     return [__get_mount_info(mount_point=user_path(user),
    371                              allow_fail=allow_fail),
    372             __get_mount_info(mount_point=system_path(user),
    373                              allow_fail=allow_fail)]
    374 
    375 def is_vault_mounted(user, regexes=None, allow_fail=False):
    376     """Check whether a vault is mounted for the given user.
    377 
    378     user: If no user is given, the shared mount point is checked, determining
    379       whether a vault is mounted for any user.
    380     regexes: dictionary of regexes to matches against the mount information.
    381       The mount filesystem for the user's user and system mounts point must
    382       match one of the keys.
    383       The mount source point must match the selected device regex.
    384 
    385     In addition, if mounted over ext4, we check the directory is encrypted.
    386     """
    387     if regexes is None:
    388         regexes = {
    389             constants.CRYPTOHOME_FS_REGEX_ANY :
    390                constants.CRYPTOHOME_DEV_REGEX_ANY
    391         }
    392     user_mount_info = __get_user_mount_info(user=user, allow_fail=allow_fail)
    393     for mount_info in user_mount_info:
    394         # Look at each /proc/../mount lines that match mount point for a given
    395         # user user/system mount (/home/user/.... /home/root/...)
    396 
    397         # We should have at least 3 arguments (source, mount, type of mount)
    398         if len(mount_info) < 3:
    399             return False
    400 
    401         device_regex = None
    402         for fs_regex in regexes.keys():
    403             if re.match(fs_regex, mount_info[2]):
    404                 device_regex = regexes[fs_regex]
    405                 break
    406 
    407         if not device_regex:
    408             # The thrid argument in not the expectd mount point type.
    409             return False
    410 
    411         # Check if the mount source match the device regex: it can be loose,
    412         # (anything) or stricter if we expect guest filesystem.
    413         if not re.match(device_regex, mount_info[0]):
    414             return False
    415 
    416         if (re.match(constants.CRYPTOHOME_FS_REGEX_EXT4, mount_info[2])
    417             and not(re.match(constants.CRYPTOHOME_DEV_REGEX_LOOP_DEVICE,
    418                              mount_info[0]))):
    419             # Ephemeral cryptohome uses ext4 mount from a loop device,
    420             # otherwise it should be ext4 crypto. Check there is an encryption
    421             # key for that directory.
    422             find_key_cmd_list = ['e4crypt  get_policy %s' % (mount_info[1]),
    423                                  'cut -d \' \' -f 2']
    424             key = __run_cmd(' | ' .join(find_key_cmd_list))
    425             cmd_list = ['keyctl show @s',
    426                         'grep %s' % (key),
    427                         'wc -l']
    428             out = __run_cmd(' | '.join(cmd_list))
    429             if int(out) != 1:
    430                 return False
    431     return True
    432 
    433 
    434 def is_guest_vault_mounted(allow_fail=False):
    435     """Check whether a vault is mounted for the guest user.
    436        It should be a mount of an ext4 partition on a loop device
    437        or be backed by tmpfs.
    438     """
    439     return is_vault_mounted(
    440         user=GUEST_USER_NAME,
    441         regexes={
    442             # Remove tmpfs support when it becomes unnecessary as all guest
    443             # modes will use ext4 on a loop device.
    444             constants.CRYPTOHOME_FS_REGEX_EXT4 :
    445                 constants.CRYPTOHOME_DEV_REGEX_LOOP_DEVICE,
    446             constants.CRYPTOHOME_FS_REGEX_TMPFS :
    447                 constants.CRYPTOHOME_DEV_REGEX_GUEST,
    448         },
    449         allow_fail=allow_fail)
    450 
    451 def is_permanent_vault_mounted(user, allow_fail=False):
    452     """Check if user is mounted over ecryptfs or ext4 crypto. """
    453     return is_vault_mounted(
    454         user=user,
    455         regexes={
    456             constants.CRYPTOHOME_FS_REGEX_ECRYPTFS :
    457                 constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER_SHADOW,
    458             constants.CRYPTOHOME_FS_REGEX_EXT4 :
    459                 constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER_DEVICE,
    460         },
    461         allow_fail=allow_fail)
    462 
    463 def get_mounted_vault_path(user, allow_fail=False):
    464     """Get the path where the decrypted data for the user is located."""
    465     return os.path.join(constants.SHADOW_ROOT, get_user_hash(user), 'mount')
    466 
    467 
    468 def canonicalize(credential):
    469     """Perform basic canonicalization of |email_address|.
    470 
    471     Perform basic canonicalization of |email_address|, taking into account that
    472     gmail does not consider '.' or caps inside a username to matter. It also
    473     ignores everything after a '+'. For example,
    474     c.masone+abc (at] gmail.com == cMaSone (at] gmail.com, per
    475     http://mail.google.com/support/bin/answer.py?hl=en&ctx=mail&answer=10313
    476     """
    477     if not credential:
    478       return None
    479 
    480     parts = credential.split('@')
    481     if len(parts) != 2:
    482         raise error.TestError('Malformed email: ' + credential)
    483 
    484     (name, domain) = parts
    485     name = name.partition('+')[0]
    486     if (domain == constants.SPECIAL_CASE_DOMAIN):
    487         name = name.replace('.', '')
    488     return '@'.join([name, domain]).lower()
    489 
    490 
    491 def crash_cryptohomed():
    492     # Try to kill cryptohomed so we get something to work with.
    493     pid = __run_cmd('pgrep cryptohomed')
    494     try:
    495         pid = int(pid)
    496     except ValueError, e:  # empty or invalid string
    497         raise error.TestError('Cryptohomed was not running')
    498     utils.system('kill -ABRT %d' % pid)
    499     # CONT just in case cryptohomed had a spurious STOP.
    500     utils.system('kill -CONT %d' % pid)
    501     utils.poll_for_condition(
    502         lambda: utils.system('ps -p %d' % pid,
    503                              ignore_status=True) != 0,
    504             timeout=180,
    505             exception=error.TestError(
    506                 'Timeout waiting for cryptohomed to coredump'))
    507 
    508 
    509 def create_ecryptfs_homedir(user, password):
    510     """Creates a new home directory as ecryptfs.
    511 
    512     If a home directory for the user exists already, it will be removed.
    513     The resulting home directory will be mounted.
    514 
    515     @param user: Username to create the home directory for.
    516     @param password: Password to use when creating the home directory.
    517     """
    518     unmount_vault(user)
    519     remove_vault(user)
    520     args = [
    521             CRYPTOHOME_CMD,
    522             '--action=mount_ex',
    523             '--user=%s' % user,
    524             '--password=%s' % password,
    525             '--key_label=foo',
    526             '--ecryptfs',
    527             '--create']
    528     logging.info(__run_cmd(' '.join(args)))
    529     if not is_vault_mounted(user, regexes={
    530         constants.CRYPTOHOME_FS_REGEX_ECRYPTFS :
    531             constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER_SHADOW
    532     }, allow_fail=True):
    533         raise ChromiumOSError('Ecryptfs home could not be created')
    534 
    535 
    536 def do_dircrypto_migration(user, password, timeout=600):
    537     """Start dircrypto migration for the user.
    538 
    539     @param user: The user to migrate.
    540     @param password: The password used to mount the users vault
    541     @param timeout: How long in seconds to wait for the migration to finish
    542     before failing.
    543     """
    544     unmount_vault(user)
    545     args = [
    546             CRYPTOHOME_CMD,
    547             '--action=mount_ex',
    548             '--to_migrate_from_ecryptfs',
    549             '--user=%s' % user,
    550             '--password=%s' % password]
    551     logging.info(__run_cmd(' '.join(args)))
    552     if not __get_mount_info(temporary_mount_path(user), allow_fail=True):
    553         raise ChromiumOSError('Failed to mount home for migration')
    554     args = [CRYPTOHOME_CMD, '--action=migrate_to_dircrypto', '--user=%s' % user]
    555     logging.info(__run_cmd(' '.join(args)))
    556     utils.poll_for_condition(
    557         lambda: not __get_mount_info(
    558                 temporary_mount_path(user), allow_fail=True),
    559         timeout=timeout,
    560         exception=error.TestError(
    561                 'Timeout waiting for dircrypto migration to finish'))
    562 
    563 
    564 def change_password(user, password, new_password):
    565     args = [
    566             CRYPTOHOME_CMD,
    567             '--action=migrate_key',
    568             '--async',
    569             '--user=%s' % user,
    570             '--old_password=%s' % password,
    571             '--password=%s' % new_password]
    572     out = __run_cmd(' '.join(args))
    573     logging.info(out)
    574     if 'Key migration succeeded.' not in out:
    575         raise ChromiumOSError('Key migration failed.')
    576 
    577 
    578 class CryptohomeProxy(DBusClient):
    579     """A DBus proxy client for testing the Cryptohome DBus server.
    580     """
    581     CRYPTOHOME_BUS_NAME = 'org.chromium.Cryptohome'
    582     CRYPTOHOME_OBJECT_PATH = '/org/chromium/Cryptohome'
    583     CRYPTOHOME_INTERFACE = 'org.chromium.CryptohomeInterface'
    584     ASYNC_CALL_STATUS_SIGNAL = 'AsyncCallStatus'
    585     ASYNC_CALL_STATUS_SIGNAL_ARGUMENTS = (
    586         'async_id', 'return_status', 'return_code'
    587     )
    588     DBUS_PROPERTIES_INTERFACE = 'org.freedesktop.DBus.Properties'
    589 
    590 
    591     def __init__(self, bus_loop=None):
    592         self.main_loop = gobject.MainLoop()
    593         if bus_loop is None:
    594             bus_loop = DBusGMainLoop(set_as_default=True)
    595         self.bus = dbus.SystemBus(mainloop=bus_loop)
    596         super(CryptohomeProxy, self).__init__(self.main_loop, self.bus,
    597                                               self.CRYPTOHOME_BUS_NAME,
    598                                               self.CRYPTOHOME_OBJECT_PATH)
    599         self.iface = dbus.Interface(self.proxy_object,
    600                                     self.CRYPTOHOME_INTERFACE)
    601         self.properties = dbus.Interface(self.proxy_object,
    602                                          self.DBUS_PROPERTIES_INTERFACE)
    603         self.handle_signal(self.CRYPTOHOME_INTERFACE,
    604                            self.ASYNC_CALL_STATUS_SIGNAL,
    605                            self.ASYNC_CALL_STATUS_SIGNAL_ARGUMENTS)
    606 
    607 
    608     # Wrap all proxied calls to catch cryptohomed failures.
    609     def __call(self, method, *args):
    610         try:
    611             return method(*args, timeout=180)
    612         except dbus.exceptions.DBusException, e:
    613             if e.get_dbus_name() == 'org.freedesktop.DBus.Error.NoReply':
    614                 logging.error('Cryptohome is not responding. Sending ABRT')
    615                 crash_cryptohomed()
    616                 raise ChromiumOSError('cryptohomed aborted. Check crashes!')
    617             raise e
    618 
    619 
    620     def __wait_for_specific_signal(self, signal, data):
    621       """Wait for the |signal| with matching |data|
    622          Returns the resulting dict on success or {} on error.
    623       """
    624       # Do not bubble up the timeout here, just return {}.
    625       result = {}
    626       try:
    627           result = self.wait_for_signal(signal)
    628       except utils.TimeoutError:
    629           return {}
    630       for k in data.keys():
    631           if not result.has_key(k) or result[k] != data[k]:
    632             return {}
    633       return result
    634 
    635 
    636     # Perform a data-less async call.
    637     # TODO(wad) Add __async_data_call.
    638     def __async_call(self, method, *args):
    639         # Clear out any superfluous async call signals.
    640         self.clear_signal_content(self.ASYNC_CALL_STATUS_SIGNAL)
    641         out = self.__call(method, *args)
    642         logging.debug('Issued call ' + str(method) +
    643                       ' with async_id ' + str(out))
    644         result = {}
    645         try:
    646             # __wait_for_specific_signal has a 10s timeout
    647             result = utils.poll_for_condition(
    648                 lambda: self.__wait_for_specific_signal(
    649                     self.ASYNC_CALL_STATUS_SIGNAL, {'async_id' : out}),
    650                 timeout=180,
    651                 desc='matching %s signal' % self.ASYNC_CALL_STATUS_SIGNAL)
    652         except utils.TimeoutError, e:
    653             logging.error('Cryptohome timed out. Sending ABRT.')
    654             crash_cryptohomed()
    655             raise ChromiumOSError('cryptohomed aborted. Check crashes!')
    656         return result
    657 
    658 
    659     def mount(self, user, password, create=False, async=True):
    660         """Mounts a cryptohome.
    661 
    662         Returns True if the mount succeeds or False otherwise.
    663         TODO(ellyjones): Migrate mount_vault() to use a multi-user-safe
    664         heuristic, then remove this method. See <crosbug.com/20778>.
    665         """
    666         if async:
    667             return self.__async_call(self.iface.AsyncMount, user, password,
    668                                      create, False, [])['return_status']
    669         out = self.__call(self.iface.Mount, user, password, create, False, [])
    670         # Sync returns (return code, return status)
    671         return out[1] if len(out) > 1 else False
    672 
    673 
    674     def unmount(self, user):
    675         """Unmounts a cryptohome.
    676 
    677         Returns True if the unmount suceeds or false otherwise.
    678         TODO(ellyjones): Once there's a per-user unmount method, use it. See
    679         <crosbug.com/20778>.
    680         """
    681         return self.__call(self.iface.Unmount)
    682 
    683 
    684     def is_mounted(self, user):
    685         """Tests whether a user's cryptohome is mounted."""
    686         return (utils.is_mountpoint(user_path(user))
    687                 and utils.is_mountpoint(system_path(user)))
    688 
    689 
    690     def require_mounted(self, user):
    691         """Raises a test failure if a user's cryptohome is not mounted."""
    692         utils.require_mountpoint(user_path(user))
    693         utils.require_mountpoint(system_path(user))
    694 
    695 
    696     def migrate(self, user, oldkey, newkey, async=True):
    697         """Migrates the specified user's cryptohome from one key to another."""
    698         if async:
    699             return self.__async_call(self.iface.AsyncMigrateKey,
    700                                      user, oldkey, newkey)['return_status']
    701         return self.__call(self.iface.MigrateKey, user, oldkey, newkey)
    702 
    703 
    704     def remove(self, user, async=True):
    705         if async:
    706             return self.__async_call(self.iface.AsyncRemove,
    707                                      user)['return_status']
    708         return self.__call(self.iface.Remove, user)
    709 
    710 
    711     def ensure_clean_cryptohome_for(self, user, password=None):
    712         """Ensure a fresh cryptohome exists for user.
    713 
    714         @param user: user who needs a shiny new cryptohome.
    715         @param password: if unset, a random password will be used.
    716         """
    717         if not password:
    718             password = ''.join(random.sample(string.ascii_lowercase, 6))
    719         self.remove(user)
    720         self.mount(user, password, create=True)
    721 
    722     def lock_install_attributes(self, attrs):
    723         """Set and lock install attributes for the device.
    724 
    725         @param attrs: dict of install attributes.
    726         """
    727         take_tpm_ownership()
    728         for key, value in attrs.items():
    729             if not self.__call(self.iface.InstallAttributesSet, key,
    730                                dbus.ByteArray(value + '\0')):
    731                 return False
    732         return self.__call(self.iface.InstallAttributesFinalize)
    733