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