blob: 1133e010b65d9736a3de186d4f1c4b9be53e90eb [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
Hsinyu Chaoe0b08e62015-08-11 10:50:37 +00008import 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,
Kris Rambishbe132592014-12-17 14:26:06 -080089 'owner_password': ''
Kris Rambish82ee1c02014-12-10 17:02:39 -080090 'dictionary_attack_counter': 0,
91 'dictionary_attack_lockout_seconds_remaining': 0,
92 'dictionary_attack_threshold': 10,
93 'attestation_enrolled': False,
94 'initialized': True,
95 'verified_boot_measured': False,
96 'install_lockbox_finalized': True
97 }
Kris Rambishbb5258c2014-12-16 16:51:17 -080098 An empty dictionary is returned if the command is not supported.
Kris Rambish82ee1c02014-12-10 17:02:39 -080099 """
Kris Rambish82ee1c02014-12-10 17:02:39 -0800100 status = {}
Kris Rambishbb5258c2014-12-16 16:51:17 -0800101 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_more_status | grep :')
102 if out.startswith(UNAVAILABLE_ACTION):
103 # --action=tpm_more_status only exists >= 41.
104 logging.info('Method not supported!')
105 return status
Kris Rambish82ee1c02014-12-10 17:02:39 -0800106 for line in out.splitlines():
107 items = line.strip().split(':')
108 if items[1].strip() == 'false':
109 value = False
110 elif items[1].strip() == 'true':
111 value = True
Kris Rambishbe132592014-12-17 14:26:06 -0800112 elif items[1].strip().isdigit():
Kris Rambish82ee1c02014-12-10 17:02:39 -0800113 value = int(items[1].strip())
Kris Rambishbe132592014-12-17 14:26:06 -0800114 else:
115 value = items[1].strip(' "')
Kris Rambish82ee1c02014-12-10 17:02:39 -0800116 status[items[0]] = value
117 return status
118
119
120def is_tpm_lockout_in_effect():
121 """Returns true if the TPM lockout is in effect; false otherwise."""
122 status = get_tpm_more_status()
Christopher Wiley94fd6b32014-12-13 18:52:03 -0800123 return status.get('dictionary_attack_lockout_in_effect', None)
Kris Rambish82ee1c02014-12-10 17:02:39 -0800124
125
David Pursell2a2ef342014-10-17 10:34:56 -0700126def get_login_status():
127 """Query the login status
128
129 Returns:
130 A login status dictionary containing:
131 { 'owner_user_exists': True|False,
132 'boot_lockbox_finalized': True|False
133 }
134 """
135 out = __run_cmd(CRYPTOHOME_CMD + ' --action=get_login_status')
136 status = {}
137 for field in ['owner_user_exists', 'boot_lockbox_finalized']:
138 match = re.search('%s: (true|false)' % field, out)
139 if not match:
140 raise ChromiumOSError('Invalid login status: "%s".' % out)
141 status[field] = match.group(1) == 'true'
142 return status
143
144
Darren Krahn5f880f62012-10-02 15:17:59 -0700145def get_tpm_attestation_status():
146 """Get the TPM attestation status. Works similar to get_tpm_status().
147 """
148 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_attestation_status')
149 status = {}
150 for field in ['Prepared', 'Enrolled']:
151 match = re.search('Attestation %s: (true|false)' % field, out)
152 if not match:
153 raise ChromiumOSError('Invalid attestation status: "%s".' % out)
154 status[field] = match.group(1) == 'true'
155 return status
156
157
Frank Farzand5e36312012-01-13 14:34:03 -0800158def take_tpm_ownership():
159 """Take TPM owernship.
160
161 Blocks until TPM is owned.
162 """
163 __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_take_ownership')
164 __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_wait_ownership')
165
166
Darren Krahn0e73e7f2012-09-05 15:35:15 -0700167def verify_ek():
168 """Verify the TPM endorsement key.
169
170 Returns true if EK is valid.
171 """
172 cmd = CRYPTOHOME_CMD + ' --action=tpm_verify_ek'
173 return (utils.system(cmd, ignore_status=True) == 0)
174
175
Sean Oe5d8fd02010-09-30 10:44:44 +0200176def remove_vault(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200177 """Remove the given user's vault from the shadow directory."""
Sean Oe5d8fd02010-09-30 10:44:44 +0200178 logging.debug('user is %s', user)
179 user_hash = get_user_hash(user)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200180 logging.debug('Removing vault for user %s with hash %s' % (user, user_hash))
Sean Oe5d8fd02010-09-30 10:44:44 +0200181 cmd = CRYPTOHOME_CMD + ' --action=remove --force --user=%s' % user
182 __run_cmd(cmd)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200183 # Ensure that the vault does not exist.
184 if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
Darren Krahne6c44b92014-03-31 12:11:08 -0700185 raise ChromiumOSError('Cryptohome could not remove the user\'s vault.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200186
187
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200188def remove_all_vaults():
189 """Remove any existing vaults from the shadow directory.
190
191 This function must be run with root privileges.
192 """
barfab@chromium.org5c374632012-04-05 16:50:56 +0200193 for item in os.listdir(constants.SHADOW_ROOT):
194 abs_item = os.path.join(constants.SHADOW_ROOT, item)
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200195 if os.path.isdir(os.path.join(abs_item, 'vault')):
196 logging.debug('Removing vault for user with hash %s' % item)
197 shutil.rmtree(abs_item)
198
199
Sean Oe5d8fd02010-09-30 10:44:44 +0200200def mount_vault(user, password, create=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200201 """Mount the given user's vault."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400202 args = [CRYPTOHOME_CMD, '--action=mount', '--user=%s' % user,
Chris Masone3543e512013-11-04 13:09:30 -0800203 '--password=%s' % password, '--async']
Sean Oe5d8fd02010-09-30 10:44:44 +0200204 if create:
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400205 args.append('--create')
Chris Masone3543e512013-11-04 13:09:30 -0800206 logging.info(__run_cmd(' '.join(args)))
barfab@chromium.org5c374632012-04-05 16:50:56 +0200207 # Ensure that the vault exists in the shadow directory.
Sean Oe5d8fd02010-09-30 10:44:44 +0200208 user_hash = get_user_hash(user)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200209 if not os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
Sean Oe5d8fd02010-09-30 10:44:44 +0200210 raise ChromiumOSError('Cryptohome vault not found after mount.')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200211 # Ensure that the vault is mounted.
212 if not is_vault_mounted(
213 user=user,
214 device_regex=constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER,
215 allow_fail=True):
216 raise ChromiumOSError('Cryptohome created a vault but did not mount.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200217
218
Chris Masone5d010aa2013-05-06 14:38:42 -0700219def mount_guest():
220 """Mount the given user's vault."""
Chris Masone3543e512013-11-04 13:09:30 -0800221 args = [CRYPTOHOME_CMD, '--action=mount_guest', '--async']
222 logging.info(__run_cmd(' '.join(args)))
Chris Masone5d010aa2013-05-06 14:38:42 -0700223 # Ensure that the guest tmpfs is mounted.
224 if not is_guest_vault_mounted(allow_fail=True):
225 raise ChromiumOSError('Cryptohome did not mount tmpfs.')
226
227
Sean Oe5d8fd02010-09-30 10:44:44 +0200228def test_auth(user, password):
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400229 cmd = [CRYPTOHOME_CMD, '--action=test_auth', '--user=%s' % user,
230 '--password=%s' % password, '--async']
231 return 'Authentication succeeded' in utils.system_output(cmd)
Sean Oe5d8fd02010-09-30 10:44:44 +0200232
233
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400234def unmount_vault(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200235 """Unmount the given user's vault.
236
237 Once unmounting for a specific user is supported, the user parameter will
238 name the target user. See crosbug.com/20778.
Elly Jones686c2f42011-10-24 16:45:07 -0400239 """
Chris Masone3543e512013-11-04 13:09:30 -0800240 __run_cmd(CRYPTOHOME_CMD + ' --action=unmount')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200241 # Ensure that the vault is not mounted.
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400242 if is_vault_mounted(user, allow_fail=True):
Sean Oe5d8fd02010-09-30 10:44:44 +0200243 raise ChromiumOSError('Cryptohome did not unmount the user.')
244
245
barfab@chromium.org5c374632012-04-05 16:50:56 +0200246def __get_mount_info(mount_point, allow_fail=False):
247 """Get information about the active mount at a given mount point."""
beeps569f8672013-08-07 10:18:51 -0700248 cryptohomed_path = '/proc/$(pgrep cryptohomed)/mounts'
249 try:
Daniel Erat2ec32792017-01-31 18:26:59 -0700250 logging.debug("Active cryptohome mounts:\n" +
251 utils.system_output('cat %s' % cryptohomed_path))
beeps569f8672013-08-07 10:18:51 -0700252 mount_line = utils.system_output(
253 'grep %s %s' % (mount_point, cryptohomed_path),
254 ignore_status=allow_fail)
255 except Exception as e:
256 logging.error(e)
257 raise ChromiumOSError('Could not get info about cryptohome vault '
258 'through %s. See logs for complete mount-point.'
259 % os.path.dirname(str(mount_point)))
Sourav Poddar574bd622010-05-26 14:22:26 +0530260 return mount_line.split()
261
262
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400263def __get_user_mount_info(user, allow_fail=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200264 """Get information about the active mounts for a given user.
265
266 Returns the active mounts at the user's user and system mount points. If no
267 user is given, the active mount at the shared mount point is returned
268 (regular users have a bind-mount at this mount point for backwards
269 compatibility; the guest user has a mount at this mount point only).
270 """
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400271 return [__get_mount_info(mount_point=user_path(user),
272 allow_fail=allow_fail),
273 __get_mount_info(mount_point=system_path(user),
274 allow_fail=allow_fail)]
Jim Hebertf08f88d2011-04-22 10:33:49 -0700275
barfab@chromium.org5c374632012-04-05 16:50:56 +0200276def is_vault_mounted(
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400277 user,
barfab@chromium.org5c374632012-04-05 16:50:56 +0200278 device_regex=constants.CRYPTOHOME_DEV_REGEX_ANY,
279 fs_regex=constants.CRYPTOHOME_FS_REGEX_ANY,
280 allow_fail=False):
281 """Check whether a vault is mounted for the given user.
282
283 If no user is given, the shared mount point is checked, determining whether
284 a vault is mounted for any user.
285 """
286 user_mount_info = __get_user_mount_info(user=user, allow_fail=allow_fail)
287 for mount_info in user_mount_info:
288 if (len(mount_info) < 3 or
289 not re.match(device_regex, mount_info[0]) or
290 not re.match(fs_regex, mount_info[2])):
291 return False
292 return True
Sourav Poddar574bd622010-05-26 14:22:26 +0530293
294
barfab@chromium.org5c374632012-04-05 16:50:56 +0200295def is_guest_vault_mounted(allow_fail=False):
296 """Check whether a vault backed by tmpfs is mounted for the guest user."""
297 return is_vault_mounted(
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400298 user=GUEST_USER_NAME,
barfab@chromium.org5c374632012-04-05 16:50:56 +0200299 device_regex=constants.CRYPTOHOME_DEV_REGEX_GUEST,
300 fs_regex=constants.CRYPTOHOME_FS_REGEX_TMPFS,
301 allow_fail=allow_fail)
302
303
Kazuhiro Inabaa3bf6452017-02-08 11:41:50 +0900304def get_mounted_vault_path(user, allow_fail=False):
305 """Get the path where the decrypted data for the user is located."""
306 return os.path.join(constants.SHADOW_ROOT, get_user_hash(user), 'mount')
Nirnimesh66814492011-06-27 18:00:33 -0700307
308
309def canonicalize(credential):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200310 """Perform basic canonicalization of |email_address|.
Nirnimesh66814492011-06-27 18:00:33 -0700311
barfab@chromium.org5c374632012-04-05 16:50:56 +0200312 Perform basic canonicalization of |email_address|, taking into account that
313 gmail does not consider '.' or caps inside a username to matter. It also
314 ignores everything after a '+'. For example,
315 c.masone+abc@gmail.com == cMaSone@gmail.com, per
Nirnimesh66814492011-06-27 18:00:33 -0700316 http://mail.google.com/support/bin/answer.py?hl=en&ctx=mail&answer=10313
317 """
318 if not credential:
319 return None
320
321 parts = credential.split('@')
322 if len(parts) != 2:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200323 raise error.TestError('Malformed email: ' + credential)
Nirnimesh66814492011-06-27 18:00:33 -0700324
325 (name, domain) = parts
326 name = name.partition('+')[0]
barfab@chromium.org5c374632012-04-05 16:50:56 +0200327 if (domain == constants.SPECIAL_CASE_DOMAIN):
Nirnimesh66814492011-06-27 18:00:33 -0700328 name = name.replace('.', '')
329 return '@'.join([name, domain]).lower()
Elly Jones686c2f42011-10-24 16:45:07 -0400330
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200331
Will Drewryd2fed972013-12-05 16:35:51 -0600332def crash_cryptohomed():
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600333 # Try to kill cryptohomed so we get something to work with.
334 pid = __run_cmd('pgrep cryptohomed')
335 try:
Will Drewry9e440792013-12-11 17:18:35 -0600336 pid = int(pid)
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600337 except ValueError, e: # empty or invalid string
Will Drewry9e440792013-12-11 17:18:35 -0600338 raise error.TestError('Cryptohomed was not running')
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600339 utils.system('kill -ABRT %d' % pid)
340 # CONT just in case cryptohomed had a spurious STOP.
341 utils.system('kill -CONT %d' % pid)
342 utils.poll_for_condition(
343 lambda: utils.system('ps -p %d' % pid,
344 ignore_status=True) != 0,
Will Drewry934d1532014-01-30 16:23:17 -0600345 timeout=180,
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600346 exception=error.TestError(
347 'Timeout waiting for cryptohomed to coredump'))
348
Will Drewryd2fed972013-12-05 16:35:51 -0600349
Will Drewry9e440792013-12-11 17:18:35 -0600350class CryptohomeProxy(DBusClient):
351 """A DBus proxy client for testing the Cryptohome DBus server.
352 """
353 CRYPTOHOME_BUS_NAME = 'org.chromium.Cryptohome'
354 CRYPTOHOME_OBJECT_PATH = '/org/chromium/Cryptohome'
355 CRYPTOHOME_INTERFACE = 'org.chromium.CryptohomeInterface'
356 ASYNC_CALL_STATUS_SIGNAL = 'AsyncCallStatus'
357 ASYNC_CALL_STATUS_SIGNAL_ARGUMENTS = (
358 'async_id', 'return_status', 'return_code'
359 )
360 DBUS_PROPERTIES_INTERFACE = 'org.freedesktop.DBus.Properties'
361
Chris Masone19e305e2014-03-14 15:13:46 -0700362
Chris Masone64170f82014-03-14 15:47:05 -0700363 def __init__(self, bus_loop=None):
Will Drewry9e440792013-12-11 17:18:35 -0600364 self.main_loop = gobject.MainLoop()
Will Drewry78db9dc2014-04-01 16:34:23 -0500365 if bus_loop is None:
Chris Masone64170f82014-03-14 15:47:05 -0700366 bus_loop = DBusGMainLoop(set_as_default=True)
Will Drewry9e440792013-12-11 17:18:35 -0600367 self.bus = dbus.SystemBus(mainloop=bus_loop)
368 super(CryptohomeProxy, self).__init__(self.main_loop, self.bus,
369 self.CRYPTOHOME_BUS_NAME,
370 self.CRYPTOHOME_OBJECT_PATH)
371 self.iface = dbus.Interface(self.proxy_object,
372 self.CRYPTOHOME_INTERFACE)
373 self.properties = dbus.Interface(self.proxy_object,
374 self.DBUS_PROPERTIES_INTERFACE)
375 self.handle_signal(self.CRYPTOHOME_INTERFACE,
376 self.ASYNC_CALL_STATUS_SIGNAL,
377 self.ASYNC_CALL_STATUS_SIGNAL_ARGUMENTS)
Elly Jones2f0ebba2011-10-27 13:43:20 -0400378
Chris Masone19e305e2014-03-14 15:13:46 -0700379
Will Drewryd2fed972013-12-05 16:35:51 -0600380 # Wrap all proxied calls to catch cryptohomed failures.
381 def __call(self, method, *args):
382 try:
Chris Masonef59d9df2014-03-14 12:05:32 -0700383 return method(*args, timeout=180)
Will Drewryd2fed972013-12-05 16:35:51 -0600384 except dbus.exceptions.DBusException, e:
385 if e.get_dbus_name() == 'org.freedesktop.DBus.Error.NoReply':
386 logging.error('Cryptohome is not responding. Sending ABRT')
387 crash_cryptohomed()
388 raise ChromiumOSError('cryptohomed aborted. Check crashes!')
389 raise e
390
Chris Masone19e305e2014-03-14 15:13:46 -0700391
Will Drewry9e440792013-12-11 17:18:35 -0600392 def __wait_for_specific_signal(self, signal, data):
393 """Wait for the |signal| with matching |data|
394 Returns the resulting dict on success or {} on error.
395 """
Will Drewryc4de5ff2014-02-03 13:26:57 -0600396 # Do not bubble up the timeout here, just return {}.
397 result = {}
398 try:
399 result = self.wait_for_signal(signal)
400 except utils.TimeoutError:
401 return {}
Will Drewry9e440792013-12-11 17:18:35 -0600402 for k in data.keys():
403 if not result.has_key(k) or result[k] != data[k]:
404 return {}
405 return result
406
Chris Masone19e305e2014-03-14 15:13:46 -0700407
Will Drewry9e440792013-12-11 17:18:35 -0600408 # Perform a data-less async call.
409 # TODO(wad) Add __async_data_call.
410 def __async_call(self, method, *args):
Will Drewryfef135a2014-05-23 16:02:14 -0500411 # Clear out any superfluous async call signals.
412 self.clear_signal_content(self.ASYNC_CALL_STATUS_SIGNAL)
Will Drewry9e440792013-12-11 17:18:35 -0600413 out = self.__call(method, *args)
414 logging.debug('Issued call ' + str(method) +
415 ' with async_id ' + str(out))
416 result = {}
417 try:
Will Drewry934d1532014-01-30 16:23:17 -0600418 # __wait_for_specific_signal has a 10s timeout
Will Drewry9e440792013-12-11 17:18:35 -0600419 result = utils.poll_for_condition(
420 lambda: self.__wait_for_specific_signal(
421 self.ASYNC_CALL_STATUS_SIGNAL, {'async_id' : out}),
Will Drewry934d1532014-01-30 16:23:17 -0600422 timeout=180,
Will Drewry9e440792013-12-11 17:18:35 -0600423 desc='matching %s signal' % self.ASYNC_CALL_STATUS_SIGNAL)
424 except utils.TimeoutError, e:
425 logging.error('Cryptohome timed out. Sending ABRT.')
426 crash_cryptohomed()
427 raise ChromiumOSError('cryptohomed aborted. Check crashes!')
428 return result
429
Chris Masone19e305e2014-03-14 15:13:46 -0700430
Will Drewry9e440792013-12-11 17:18:35 -0600431 def mount(self, user, password, create=False, async=True):
Elly Jones2f0ebba2011-10-27 13:43:20 -0400432 """Mounts a cryptohome.
433
434 Returns True if the mount succeeds or False otherwise.
435 TODO(ellyjones): Migrate mount_vault() to use a multi-user-safe
436 heuristic, then remove this method. See <crosbug.com/20778>.
437 """
Will Drewry9e440792013-12-11 17:18:35 -0600438 if async:
439 return self.__async_call(self.iface.AsyncMount, user, password,
440 create, False, [])['return_status']
Will Drewryd2fed972013-12-05 16:35:51 -0600441 out = self.__call(self.iface.Mount, user, password, create, False, [])
Will Drewry9e440792013-12-11 17:18:35 -0600442 # Sync returns (return code, return status)
443 return out[1] if len(out) > 1 else False
Elly Jones2f0ebba2011-10-27 13:43:20 -0400444
Chris Masone19e305e2014-03-14 15:13:46 -0700445
Elly Jones2f0ebba2011-10-27 13:43:20 -0400446 def unmount(self, user):
447 """Unmounts a cryptohome.
448
449 Returns True if the unmount suceeds or false otherwise.
450 TODO(ellyjones): Once there's a per-user unmount method, use it. See
451 <crosbug.com/20778>.
452 """
Will Drewryd2fed972013-12-05 16:35:51 -0600453 return self.__call(self.iface.Unmount)
Elly Jones2f0ebba2011-10-27 13:43:20 -0400454
Chris Masone19e305e2014-03-14 15:13:46 -0700455
Elly Jones2f0ebba2011-10-27 13:43:20 -0400456 def is_mounted(self, user):
457 """Tests whether a user's cryptohome is mounted."""
458 return (utils.is_mountpoint(user_path(user))
459 and utils.is_mountpoint(system_path(user)))
460
Chris Masone19e305e2014-03-14 15:13:46 -0700461
Elly Jones2f0ebba2011-10-27 13:43:20 -0400462 def require_mounted(self, user):
463 """Raises a test failure if a user's cryptohome is not mounted."""
464 utils.require_mountpoint(user_path(user))
465 utils.require_mountpoint(system_path(user))
Elly Jones4458f442012-04-16 15:42:56 -0400466
Chris Masone19e305e2014-03-14 15:13:46 -0700467
Will Drewry9e440792013-12-11 17:18:35 -0600468 def migrate(self, user, oldkey, newkey, async=True):
Elly Jones4458f442012-04-16 15:42:56 -0400469 """Migrates the specified user's cryptohome from one key to another."""
Will Drewry9e440792013-12-11 17:18:35 -0600470 if async:
471 return self.__async_call(self.iface.AsyncMigrateKey,
472 user, oldkey, newkey)['return_status']
Will Drewryd2fed972013-12-05 16:35:51 -0600473 return self.__call(self.iface.MigrateKey, user, oldkey, newkey)
Elly Jones4458f442012-04-16 15:42:56 -0400474
Chris Masone19e305e2014-03-14 15:13:46 -0700475
Will Drewry9e440792013-12-11 17:18:35 -0600476 def remove(self, user, async=True):
477 if async:
478 return self.__async_call(self.iface.AsyncRemove,
479 user)['return_status']
Will Drewryd2fed972013-12-05 16:35:51 -0600480 return self.__call(self.iface.Remove, user)
Chris Masone19e305e2014-03-14 15:13:46 -0700481
482
483 def ensure_clean_cryptohome_for(self, user, password=None):
484 """Ensure a fresh cryptohome exists for user.
485
486 @param user: user who needs a shiny new cryptohome.
487 @param password: if unset, a random password will be used.
488 """
489 if not password:
490 password = ''.join(random.sample(string.ascii_lowercase, 6))
491 self.remove(user)
492 self.mount(user, password, create=True)