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
5import dbus, gobject, logging, os, random, re, shutil, string, time
6from dbus.mainloop.glib import DBusGMainLoop
7
8import common, constants
9from autotest_lib.client.bin import utils
10from autotest_lib.client.common_lib import error
11from autotest_lib.client.cros.cros_disks import DBusClient
12
13CRYPTOHOME_CMD = '/usr/sbin/cryptohome'
14GUEST_USER_NAME = '$guest'
15UNAVAILABLE_ACTION = 'Unknown action or no action given.'
16MOUNT_RETRY_COUNT = 20
17TEMP_MOUNT_PATTERN = '/home/.shadow/%s/temporary_mount'
18VAULT_PATH_PATTERN = '/home/.shadow/%s/vault'
19
20class ChromiumOSError(error.TestError):
21    """Generic error for ChromiumOS-specific exceptions."""
22    pass
23
24def __run_cmd(cmd):
25    return utils.system_output(cmd + ' 2>&1', retain_output=True,
26                               ignore_status=True).strip()
27
28def 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
34def user_path(user):
35    """Get the user mount point for the given user."""
36    return utils.system_output(['cryptohome-path', 'user', user])
37
38
39def system_path(user):
40    """Get the system mount point for the given user."""
41    return utils.system_output(['cryptohome-path', 'system', user])
42
43
44def 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
52def 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
60def 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
73def 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
99def 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
140def 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
180def 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
200def 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
206def 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
225def 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
238def 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
250def 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
259def 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
271def 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
283def 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
316def 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
325def 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
333def 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
345def __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
362def __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
375def 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
434def 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
451def 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
463def 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
468def 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@gmail.com == cMaSone@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
491def 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
509def 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
536def 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
564def 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
578class 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