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