blob: 4c4840b5098b5648078bd6492ac2c591ef7432d8 [file] [log] [blame]
Frank Farzand5e36312012-01-13 14:34:03 -08001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Chris Masone5e06f182010-03-23 08:29:52 -07002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Will Drewry9e440792013-12-11 17:18:35 -06005import dbus, gobject, logging, os, random, re, shutil, string
6from dbus.mainloop.glib import DBusGMainLoop
barfab@chromium.orgb6d29932012-04-11 09:46:43 +02007
8import common, constants
barfab@chromium.org5c374632012-04-05 16:50:56 +02009from autotest_lib.client.bin import utils
Chris Masone5e06f182010-03-23 08:29:52 -070010from autotest_lib.client.common_lib import error
Will Drewry9e440792013-12-11 17:18:35 -060011from autotest_lib.client.cros.cros_disks import DBusClient
Eric Lic4d8f4a2010-12-10 09:49:23 -080012
Sean Oe5d8fd02010-09-30 10:44:44 +020013CRYPTOHOME_CMD = '/usr/sbin/cryptohome'
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040014GUEST_USER_NAME = '$guest'
Kris Rambishbb5258c2014-12-16 16:51:17 -080015UNAVAILABLE_ACTION = 'Unknown action or no action given.'
Sean Oe5d8fd02010-09-30 10:44:44 +020016
Chris Masone5d010aa2013-05-06 14:38:42 -070017class ChromiumOSError(error.TestError):
Sean Oe5d8fd02010-09-30 10:44:44 +020018 """Generic error for ChromiumOS-specific exceptions."""
19 pass
20
Sean Oe5d8fd02010-09-30 10:44:44 +020021def __run_cmd(cmd):
22 return utils.system_output(cmd + ' 2>&1', retain_output=True,
23 ignore_status=True).strip()
24
Sean Oe5d8fd02010-09-30 10:44:44 +020025def get_user_hash(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +020026 """Get the user hash for the given user."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040027 return utils.system_output(['cryptohome', '--action=obfuscate_user',
28 '--user=%s' % user])
Sean Oe5d8fd02010-09-30 10:44:44 +020029
30
barfab@chromium.org5c374632012-04-05 16:50:56 +020031def user_path(user):
32 """Get the user mount point for the given user."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040033 return utils.system_output(['cryptohome-path', 'user', user])
barfab@chromium.org5c374632012-04-05 16:50:56 +020034
35
36def system_path(user):
37 """Get the system mount point for the given user."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040038 return utils.system_output(['cryptohome-path', 'system', user])
barfab@chromium.org5c374632012-04-05 16:50:56 +020039
40
Chris Masone5d010aa2013-05-06 14:38:42 -070041def ensure_clean_cryptohome_for(user, password=None):
42 """Ensure a fresh cryptohome exists for user.
43
44 @param user: user who needs a shiny new cryptohome.
45 @param password: if unset, a random password will be used.
46 """
47 if not password:
48 password = ''.join(random.sample(string.ascii_lowercase, 6))
49 remove_vault(user)
50 mount_vault(user, password, create=True)
51
52
Frank Farzand5e36312012-01-13 14:34:03 -080053def get_tpm_status():
54 """Get the TPM status.
55
56 Returns:
57 A TPM status dictionary, for example:
58 { 'Enabled': True,
59 'Owned': True,
60 'Being Owned': False,
61 'Ready': True,
62 'Password': ''
63 }
64 """
65 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_status')
66 status = {}
67 for field in ['Enabled', 'Owned', 'Being Owned', 'Ready']:
68 match = re.search('TPM %s: (true|false)' % field, out)
69 if not match:
70 raise ChromiumOSError('Invalid TPM status: "%s".' % out)
71 status[field] = match.group(1) == 'true'
72 match = re.search('TPM Password: (\w*)', out)
73 status['Password'] = ''
74 if match:
75 status['Password'] = match.group(1)
76 return status
77
78
Kris Rambish82ee1c02014-12-10 17:02:39 -080079def get_tpm_more_status():
80 """Get more of the TPM status.
81
82 Returns:
83 A TPM more status dictionary, for example:
84 { 'dictionary_attack_lockout_in_effect': False,
85 'attestation_prepared': False,
86 'boot_lockbox_finalized': False,
87 'enabled': True,
88 'owned': True,
89 'dictionary_attack_counter': 0,
90 'dictionary_attack_lockout_seconds_remaining': 0,
91 'dictionary_attack_threshold': 10,
92 'attestation_enrolled': False,
93 'initialized': True,
94 'verified_boot_measured': False,
95 'install_lockbox_finalized': True
96 }
Kris Rambishbb5258c2014-12-16 16:51:17 -080097 An empty dictionary is returned if the command is not supported.
Kris Rambish82ee1c02014-12-10 17:02:39 -080098 """
Kris Rambish82ee1c02014-12-10 17:02:39 -080099 status = {}
Kris Rambishbb5258c2014-12-16 16:51:17 -0800100 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_more_status | grep :')
101 if out.startswith(UNAVAILABLE_ACTION):
102 # --action=tpm_more_status only exists >= 41.
103 logging.info('Method not supported!')
104 return status
Kris Rambish82ee1c02014-12-10 17:02:39 -0800105 for line in out.splitlines():
106 items = line.strip().split(':')
107 if items[1].strip() == 'false':
108 value = False
109 elif items[1].strip() == 'true':
110 value = True
111 else:
112 value = int(items[1].strip())
113 status[items[0]] = value
114 return status
115
116
117def is_tpm_lockout_in_effect():
118 """Returns true if the TPM lockout is in effect; false otherwise."""
119 status = get_tpm_more_status()
Christopher Wiley94fd6b32014-12-13 18:52:03 -0800120 return status.get('dictionary_attack_lockout_in_effect', None)
Kris Rambish82ee1c02014-12-10 17:02:39 -0800121
122
David Pursell2a2ef342014-10-17 10:34:56 -0700123def get_login_status():
124 """Query the login status
125
126 Returns:
127 A login status dictionary containing:
128 { 'owner_user_exists': True|False,
129 'boot_lockbox_finalized': True|False
130 }
131 """
132 out = __run_cmd(CRYPTOHOME_CMD + ' --action=get_login_status')
133 status = {}
134 for field in ['owner_user_exists', 'boot_lockbox_finalized']:
135 match = re.search('%s: (true|false)' % field, out)
136 if not match:
137 raise ChromiumOSError('Invalid login status: "%s".' % out)
138 status[field] = match.group(1) == 'true'
139 return status
140
141
Darren Krahn5f880f62012-10-02 15:17:59 -0700142def get_tpm_attestation_status():
143 """Get the TPM attestation status. Works similar to get_tpm_status().
144 """
145 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_attestation_status')
146 status = {}
147 for field in ['Prepared', 'Enrolled']:
148 match = re.search('Attestation %s: (true|false)' % field, out)
149 if not match:
150 raise ChromiumOSError('Invalid attestation status: "%s".' % out)
151 status[field] = match.group(1) == 'true'
152 return status
153
154
Frank Farzand5e36312012-01-13 14:34:03 -0800155def take_tpm_ownership():
156 """Take TPM owernship.
157
158 Blocks until TPM is owned.
159 """
160 __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_take_ownership')
161 __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_wait_ownership')
162
163
Darren Krahn0e73e7f2012-09-05 15:35:15 -0700164def verify_ek():
165 """Verify the TPM endorsement key.
166
167 Returns true if EK is valid.
168 """
169 cmd = CRYPTOHOME_CMD + ' --action=tpm_verify_ek'
170 return (utils.system(cmd, ignore_status=True) == 0)
171
172
Sean Oe5d8fd02010-09-30 10:44:44 +0200173def remove_vault(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200174 """Remove the given user's vault from the shadow directory."""
Sean Oe5d8fd02010-09-30 10:44:44 +0200175 logging.debug('user is %s', user)
176 user_hash = get_user_hash(user)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200177 logging.debug('Removing vault for user %s with hash %s' % (user, user_hash))
Sean Oe5d8fd02010-09-30 10:44:44 +0200178 cmd = CRYPTOHOME_CMD + ' --action=remove --force --user=%s' % user
179 __run_cmd(cmd)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200180 # Ensure that the vault does not exist.
181 if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
Darren Krahne6c44b92014-03-31 12:11:08 -0700182 raise ChromiumOSError('Cryptohome could not remove the user\'s vault.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200183
184
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200185def remove_all_vaults():
186 """Remove any existing vaults from the shadow directory.
187
188 This function must be run with root privileges.
189 """
barfab@chromium.org5c374632012-04-05 16:50:56 +0200190 for item in os.listdir(constants.SHADOW_ROOT):
191 abs_item = os.path.join(constants.SHADOW_ROOT, item)
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200192 if os.path.isdir(os.path.join(abs_item, 'vault')):
193 logging.debug('Removing vault for user with hash %s' % item)
194 shutil.rmtree(abs_item)
195
196
Sean Oe5d8fd02010-09-30 10:44:44 +0200197def mount_vault(user, password, create=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200198 """Mount the given user's vault."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400199 args = [CRYPTOHOME_CMD, '--action=mount', '--user=%s' % user,
Chris Masone3543e512013-11-04 13:09:30 -0800200 '--password=%s' % password, '--async']
Sean Oe5d8fd02010-09-30 10:44:44 +0200201 if create:
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400202 args.append('--create')
Chris Masone3543e512013-11-04 13:09:30 -0800203 logging.info(__run_cmd(' '.join(args)))
barfab@chromium.org5c374632012-04-05 16:50:56 +0200204 # Ensure that the vault exists in the shadow directory.
Sean Oe5d8fd02010-09-30 10:44:44 +0200205 user_hash = get_user_hash(user)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200206 if not os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
Sean Oe5d8fd02010-09-30 10:44:44 +0200207 raise ChromiumOSError('Cryptohome vault not found after mount.')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200208 # Ensure that the vault is mounted.
209 if not is_vault_mounted(
210 user=user,
211 device_regex=constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER,
212 allow_fail=True):
213 raise ChromiumOSError('Cryptohome created a vault but did not mount.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200214
215
Chris Masone5d010aa2013-05-06 14:38:42 -0700216def mount_guest():
217 """Mount the given user's vault."""
Chris Masone3543e512013-11-04 13:09:30 -0800218 args = [CRYPTOHOME_CMD, '--action=mount_guest', '--async']
219 logging.info(__run_cmd(' '.join(args)))
Chris Masone5d010aa2013-05-06 14:38:42 -0700220 # Ensure that the guest tmpfs is mounted.
221 if not is_guest_vault_mounted(allow_fail=True):
222 raise ChromiumOSError('Cryptohome did not mount tmpfs.')
223
224
Sean Oe5d8fd02010-09-30 10:44:44 +0200225def test_auth(user, password):
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400226 cmd = [CRYPTOHOME_CMD, '--action=test_auth', '--user=%s' % user,
227 '--password=%s' % password, '--async']
228 return 'Authentication succeeded' in utils.system_output(cmd)
Sean Oe5d8fd02010-09-30 10:44:44 +0200229
230
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400231def unmount_vault(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200232 """Unmount the given user's vault.
233
234 Once unmounting for a specific user is supported, the user parameter will
235 name the target user. See crosbug.com/20778.
Elly Jones686c2f42011-10-24 16:45:07 -0400236 """
Chris Masone3543e512013-11-04 13:09:30 -0800237 __run_cmd(CRYPTOHOME_CMD + ' --action=unmount')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200238 # Ensure that the vault is not mounted.
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400239 if is_vault_mounted(user, allow_fail=True):
Sean Oe5d8fd02010-09-30 10:44:44 +0200240 raise ChromiumOSError('Cryptohome did not unmount the user.')
241
242
barfab@chromium.org5c374632012-04-05 16:50:56 +0200243def __get_mount_info(mount_point, allow_fail=False):
244 """Get information about the active mount at a given mount point."""
beeps569f8672013-08-07 10:18:51 -0700245 cryptohomed_path = '/proc/$(pgrep cryptohomed)/mounts'
246 try:
247 logging.info(utils.system_output('cat %s' % cryptohomed_path))
248 mount_line = utils.system_output(
249 'grep %s %s' % (mount_point, cryptohomed_path),
250 ignore_status=allow_fail)
251 except Exception as e:
252 logging.error(e)
253 raise ChromiumOSError('Could not get info about cryptohome vault '
254 'through %s. See logs for complete mount-point.'
255 % os.path.dirname(str(mount_point)))
Sourav Poddar574bd622010-05-26 14:22:26 +0530256 return mount_line.split()
257
258
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400259def __get_user_mount_info(user, allow_fail=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200260 """Get information about the active mounts for a given user.
261
262 Returns the active mounts at the user's user and system mount points. If no
263 user is given, the active mount at the shared mount point is returned
264 (regular users have a bind-mount at this mount point for backwards
265 compatibility; the guest user has a mount at this mount point only).
266 """
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400267 return [__get_mount_info(mount_point=user_path(user),
268 allow_fail=allow_fail),
269 __get_mount_info(mount_point=system_path(user),
270 allow_fail=allow_fail)]
Jim Hebertf08f88d2011-04-22 10:33:49 -0700271
barfab@chromium.org5c374632012-04-05 16:50:56 +0200272def is_vault_mounted(
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400273 user,
barfab@chromium.org5c374632012-04-05 16:50:56 +0200274 device_regex=constants.CRYPTOHOME_DEV_REGEX_ANY,
275 fs_regex=constants.CRYPTOHOME_FS_REGEX_ANY,
276 allow_fail=False):
277 """Check whether a vault is mounted for the given user.
278
279 If no user is given, the shared mount point is checked, determining whether
280 a vault is mounted for any user.
281 """
282 user_mount_info = __get_user_mount_info(user=user, allow_fail=allow_fail)
283 for mount_info in user_mount_info:
284 if (len(mount_info) < 3 or
285 not re.match(device_regex, mount_info[0]) or
286 not re.match(fs_regex, mount_info[2])):
287 return False
288 return True
Sourav Poddar574bd622010-05-26 14:22:26 +0530289
290
barfab@chromium.org5c374632012-04-05 16:50:56 +0200291def is_guest_vault_mounted(allow_fail=False):
292 """Check whether a vault backed by tmpfs is mounted for the guest user."""
293 return is_vault_mounted(
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400294 user=GUEST_USER_NAME,
barfab@chromium.org5c374632012-04-05 16:50:56 +0200295 device_regex=constants.CRYPTOHOME_DEV_REGEX_GUEST,
296 fs_regex=constants.CRYPTOHOME_FS_REGEX_TMPFS,
297 allow_fail=allow_fail)
298
299
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400300def get_mounted_vault_devices(user, allow_fail=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200301 """Get the device(s) backing the vault mounted for the given user.
302
303 Returns the devices mounted at the user's user and system mount points. If
304 no user is given, the device mounted at the shared mount point is returned.
305 """
306 return [mount_info[0]
307 for mount_info
308 in __get_user_mount_info(user=user, allow_fail=allow_fail)
309 if len(mount_info)]
Nirnimesh66814492011-06-27 18:00:33 -0700310
311
312def canonicalize(credential):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200313 """Perform basic canonicalization of |email_address|.
Nirnimesh66814492011-06-27 18:00:33 -0700314
barfab@chromium.org5c374632012-04-05 16:50:56 +0200315 Perform basic canonicalization of |email_address|, taking into account that
316 gmail does not consider '.' or caps inside a username to matter. It also
317 ignores everything after a '+'. For example,
318 c.masone+abc@gmail.com == cMaSone@gmail.com, per
Nirnimesh66814492011-06-27 18:00:33 -0700319 http://mail.google.com/support/bin/answer.py?hl=en&ctx=mail&answer=10313
320 """
321 if not credential:
322 return None
323
324 parts = credential.split('@')
325 if len(parts) != 2:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200326 raise error.TestError('Malformed email: ' + credential)
Nirnimesh66814492011-06-27 18:00:33 -0700327
328 (name, domain) = parts
329 name = name.partition('+')[0]
barfab@chromium.org5c374632012-04-05 16:50:56 +0200330 if (domain == constants.SPECIAL_CASE_DOMAIN):
Nirnimesh66814492011-06-27 18:00:33 -0700331 name = name.replace('.', '')
332 return '@'.join([name, domain]).lower()
Elly Jones686c2f42011-10-24 16:45:07 -0400333
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200334
Will Drewryd2fed972013-12-05 16:35:51 -0600335def crash_cryptohomed():
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600336 # Try to kill cryptohomed so we get something to work with.
337 pid = __run_cmd('pgrep cryptohomed')
338 try:
Will Drewry9e440792013-12-11 17:18:35 -0600339 pid = int(pid)
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600340 except ValueError, e: # empty or invalid string
Will Drewry9e440792013-12-11 17:18:35 -0600341 raise error.TestError('Cryptohomed was not running')
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600342 utils.system('kill -ABRT %d' % pid)
343 # CONT just in case cryptohomed had a spurious STOP.
344 utils.system('kill -CONT %d' % pid)
345 utils.poll_for_condition(
346 lambda: utils.system('ps -p %d' % pid,
347 ignore_status=True) != 0,
Will Drewry934d1532014-01-30 16:23:17 -0600348 timeout=180,
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600349 exception=error.TestError(
350 'Timeout waiting for cryptohomed to coredump'))
351
Will Drewryd2fed972013-12-05 16:35:51 -0600352
Will Drewry9e440792013-12-11 17:18:35 -0600353class CryptohomeProxy(DBusClient):
354 """A DBus proxy client for testing the Cryptohome DBus server.
355 """
356 CRYPTOHOME_BUS_NAME = 'org.chromium.Cryptohome'
357 CRYPTOHOME_OBJECT_PATH = '/org/chromium/Cryptohome'
358 CRYPTOHOME_INTERFACE = 'org.chromium.CryptohomeInterface'
359 ASYNC_CALL_STATUS_SIGNAL = 'AsyncCallStatus'
360 ASYNC_CALL_STATUS_SIGNAL_ARGUMENTS = (
361 'async_id', 'return_status', 'return_code'
362 )
363 DBUS_PROPERTIES_INTERFACE = 'org.freedesktop.DBus.Properties'
364
Chris Masone19e305e2014-03-14 15:13:46 -0700365
Chris Masone64170f82014-03-14 15:47:05 -0700366 def __init__(self, bus_loop=None):
Will Drewry9e440792013-12-11 17:18:35 -0600367 self.main_loop = gobject.MainLoop()
Will Drewry78db9dc2014-04-01 16:34:23 -0500368 if bus_loop is None:
Chris Masone64170f82014-03-14 15:47:05 -0700369 bus_loop = DBusGMainLoop(set_as_default=True)
Will Drewry9e440792013-12-11 17:18:35 -0600370 self.bus = dbus.SystemBus(mainloop=bus_loop)
371 super(CryptohomeProxy, self).__init__(self.main_loop, self.bus,
372 self.CRYPTOHOME_BUS_NAME,
373 self.CRYPTOHOME_OBJECT_PATH)
374 self.iface = dbus.Interface(self.proxy_object,
375 self.CRYPTOHOME_INTERFACE)
376 self.properties = dbus.Interface(self.proxy_object,
377 self.DBUS_PROPERTIES_INTERFACE)
378 self.handle_signal(self.CRYPTOHOME_INTERFACE,
379 self.ASYNC_CALL_STATUS_SIGNAL,
380 self.ASYNC_CALL_STATUS_SIGNAL_ARGUMENTS)
Elly Jones2f0ebba2011-10-27 13:43:20 -0400381
Chris Masone19e305e2014-03-14 15:13:46 -0700382
Will Drewryd2fed972013-12-05 16:35:51 -0600383 # Wrap all proxied calls to catch cryptohomed failures.
384 def __call(self, method, *args):
385 try:
Chris Masonef59d9df2014-03-14 12:05:32 -0700386 return method(*args, timeout=180)
Will Drewryd2fed972013-12-05 16:35:51 -0600387 except dbus.exceptions.DBusException, e:
388 if e.get_dbus_name() == 'org.freedesktop.DBus.Error.NoReply':
389 logging.error('Cryptohome is not responding. Sending ABRT')
390 crash_cryptohomed()
391 raise ChromiumOSError('cryptohomed aborted. Check crashes!')
392 raise e
393
Chris Masone19e305e2014-03-14 15:13:46 -0700394
Will Drewry9e440792013-12-11 17:18:35 -0600395 def __wait_for_specific_signal(self, signal, data):
396 """Wait for the |signal| with matching |data|
397 Returns the resulting dict on success or {} on error.
398 """
Will Drewryc4de5ff2014-02-03 13:26:57 -0600399 # Do not bubble up the timeout here, just return {}.
400 result = {}
401 try:
402 result = self.wait_for_signal(signal)
403 except utils.TimeoutError:
404 return {}
Will Drewry9e440792013-12-11 17:18:35 -0600405 for k in data.keys():
406 if not result.has_key(k) or result[k] != data[k]:
407 return {}
408 return result
409
Chris Masone19e305e2014-03-14 15:13:46 -0700410
Will Drewry9e440792013-12-11 17:18:35 -0600411 # Perform a data-less async call.
412 # TODO(wad) Add __async_data_call.
413 def __async_call(self, method, *args):
Will Drewryfef135a2014-05-23 16:02:14 -0500414 # Clear out any superfluous async call signals.
415 self.clear_signal_content(self.ASYNC_CALL_STATUS_SIGNAL)
Will Drewry9e440792013-12-11 17:18:35 -0600416 out = self.__call(method, *args)
417 logging.debug('Issued call ' + str(method) +
418 ' with async_id ' + str(out))
419 result = {}
420 try:
Will Drewry934d1532014-01-30 16:23:17 -0600421 # __wait_for_specific_signal has a 10s timeout
Will Drewry9e440792013-12-11 17:18:35 -0600422 result = utils.poll_for_condition(
423 lambda: self.__wait_for_specific_signal(
424 self.ASYNC_CALL_STATUS_SIGNAL, {'async_id' : out}),
Will Drewry934d1532014-01-30 16:23:17 -0600425 timeout=180,
Will Drewry9e440792013-12-11 17:18:35 -0600426 desc='matching %s signal' % self.ASYNC_CALL_STATUS_SIGNAL)
427 except utils.TimeoutError, e:
428 logging.error('Cryptohome timed out. Sending ABRT.')
429 crash_cryptohomed()
430 raise ChromiumOSError('cryptohomed aborted. Check crashes!')
431 return result
432
Chris Masone19e305e2014-03-14 15:13:46 -0700433
Will Drewry9e440792013-12-11 17:18:35 -0600434 def mount(self, user, password, create=False, async=True):
Elly Jones2f0ebba2011-10-27 13:43:20 -0400435 """Mounts a cryptohome.
436
437 Returns True if the mount succeeds or False otherwise.
438 TODO(ellyjones): Migrate mount_vault() to use a multi-user-safe
439 heuristic, then remove this method. See <crosbug.com/20778>.
440 """
Will Drewry9e440792013-12-11 17:18:35 -0600441 if async:
442 return self.__async_call(self.iface.AsyncMount, user, password,
443 create, False, [])['return_status']
Will Drewryd2fed972013-12-05 16:35:51 -0600444 out = self.__call(self.iface.Mount, user, password, create, False, [])
Will Drewry9e440792013-12-11 17:18:35 -0600445 # Sync returns (return code, return status)
446 return out[1] if len(out) > 1 else False
Elly Jones2f0ebba2011-10-27 13:43:20 -0400447
Chris Masone19e305e2014-03-14 15:13:46 -0700448
Elly Jones2f0ebba2011-10-27 13:43:20 -0400449 def unmount(self, user):
450 """Unmounts a cryptohome.
451
452 Returns True if the unmount suceeds or false otherwise.
453 TODO(ellyjones): Once there's a per-user unmount method, use it. See
454 <crosbug.com/20778>.
455 """
Will Drewryd2fed972013-12-05 16:35:51 -0600456 return self.__call(self.iface.Unmount)
Elly Jones2f0ebba2011-10-27 13:43:20 -0400457
Chris Masone19e305e2014-03-14 15:13:46 -0700458
Elly Jones2f0ebba2011-10-27 13:43:20 -0400459 def is_mounted(self, user):
460 """Tests whether a user's cryptohome is mounted."""
461 return (utils.is_mountpoint(user_path(user))
462 and utils.is_mountpoint(system_path(user)))
463
Chris Masone19e305e2014-03-14 15:13:46 -0700464
Elly Jones2f0ebba2011-10-27 13:43:20 -0400465 def require_mounted(self, user):
466 """Raises a test failure if a user's cryptohome is not mounted."""
467 utils.require_mountpoint(user_path(user))
468 utils.require_mountpoint(system_path(user))
Elly Jones4458f442012-04-16 15:42:56 -0400469
Chris Masone19e305e2014-03-14 15:13:46 -0700470
Will Drewry9e440792013-12-11 17:18:35 -0600471 def migrate(self, user, oldkey, newkey, async=True):
Elly Jones4458f442012-04-16 15:42:56 -0400472 """Migrates the specified user's cryptohome from one key to another."""
Will Drewry9e440792013-12-11 17:18:35 -0600473 if async:
474 return self.__async_call(self.iface.AsyncMigrateKey,
475 user, oldkey, newkey)['return_status']
Will Drewryd2fed972013-12-05 16:35:51 -0600476 return self.__call(self.iface.MigrateKey, user, oldkey, newkey)
Elly Jones4458f442012-04-16 15:42:56 -0400477
Chris Masone19e305e2014-03-14 15:13:46 -0700478
Will Drewry9e440792013-12-11 17:18:35 -0600479 def remove(self, user, async=True):
480 if async:
481 return self.__async_call(self.iface.AsyncRemove,
482 user)['return_status']
Will Drewryd2fed972013-12-05 16:35:51 -0600483 return self.__call(self.iface.Remove, user)
Chris Masone19e305e2014-03-14 15:13:46 -0700484
485
486 def ensure_clean_cryptohome_for(self, user, password=None):
487 """Ensure a fresh cryptohome exists for user.
488
489 @param user: user who needs a shiny new cryptohome.
490 @param password: if unset, a random password will be used.
491 """
492 if not password:
493 password = ''.join(random.sample(string.ascii_lowercase, 6))
494 self.remove(user)
495 self.mount(user, password, create=True)