blob: 4e6cc8b156dfd4c6f0e064b6f9ca0cc2a9bbd6fe [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
Alexis Saveryccb16be2017-02-01 16:23:15 -08005import dbus, gobject, logging, os, random, re, shutil, string, time
Will Drewry9e440792013-12-11 17:18:35 -06006from 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.'
Alexis Saveryccb16be2017-02-01 16:23:15 -080016MOUNT_RETRY_COUNT = 20
Sean Oe5d8fd02010-09-30 10:44:44 +020017
Chris Masone5d010aa2013-05-06 14:38:42 -070018class ChromiumOSError(error.TestError):
Sean Oe5d8fd02010-09-30 10:44:44 +020019 """Generic error for ChromiumOS-specific exceptions."""
20 pass
21
Sean Oe5d8fd02010-09-30 10:44:44 +020022def __run_cmd(cmd):
23 return utils.system_output(cmd + ' 2>&1', retain_output=True,
24 ignore_status=True).strip()
25
Sean Oe5d8fd02010-09-30 10:44:44 +020026def get_user_hash(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +020027 """Get the user hash for the given user."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040028 return utils.system_output(['cryptohome', '--action=obfuscate_user',
29 '--user=%s' % user])
Sean Oe5d8fd02010-09-30 10:44:44 +020030
31
barfab@chromium.org5c374632012-04-05 16:50:56 +020032def user_path(user):
33 """Get the user mount point for the given user."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040034 return utils.system_output(['cryptohome-path', 'user', user])
barfab@chromium.org5c374632012-04-05 16:50:56 +020035
36
37def system_path(user):
38 """Get the system mount point for the given user."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040039 return utils.system_output(['cryptohome-path', 'system', user])
barfab@chromium.org5c374632012-04-05 16:50:56 +020040
41
Chris Masone5d010aa2013-05-06 14:38:42 -070042def ensure_clean_cryptohome_for(user, password=None):
43 """Ensure a fresh cryptohome exists for user.
44
45 @param user: user who needs a shiny new cryptohome.
46 @param password: if unset, a random password will be used.
47 """
48 if not password:
49 password = ''.join(random.sample(string.ascii_lowercase, 6))
50 remove_vault(user)
51 mount_vault(user, password, create=True)
52
53
Frank Farzand5e36312012-01-13 14:34:03 -080054def get_tpm_status():
55 """Get the TPM status.
56
57 Returns:
58 A TPM status dictionary, for example:
59 { 'Enabled': True,
60 'Owned': True,
61 'Being Owned': False,
62 'Ready': True,
63 'Password': ''
64 }
65 """
66 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_status')
67 status = {}
68 for field in ['Enabled', 'Owned', 'Being Owned', 'Ready']:
69 match = re.search('TPM %s: (true|false)' % field, out)
70 if not match:
71 raise ChromiumOSError('Invalid TPM status: "%s".' % out)
72 status[field] = match.group(1) == 'true'
73 match = re.search('TPM Password: (\w*)', out)
74 status['Password'] = ''
75 if match:
76 status['Password'] = match.group(1)
77 return status
78
79
Kris Rambish82ee1c02014-12-10 17:02:39 -080080def get_tpm_more_status():
81 """Get more of the TPM status.
82
83 Returns:
84 A TPM more status dictionary, for example:
85 { 'dictionary_attack_lockout_in_effect': False,
86 'attestation_prepared': False,
87 'boot_lockbox_finalized': False,
88 'enabled': True,
89 'owned': True,
Kris Rambishbe132592014-12-17 14:26:06 -080090 'owner_password': ''
Kris Rambish82ee1c02014-12-10 17:02:39 -080091 'dictionary_attack_counter': 0,
92 'dictionary_attack_lockout_seconds_remaining': 0,
93 'dictionary_attack_threshold': 10,
94 'attestation_enrolled': False,
95 'initialized': True,
96 'verified_boot_measured': False,
97 'install_lockbox_finalized': True
98 }
Kris Rambishbb5258c2014-12-16 16:51:17 -080099 An empty dictionary is returned if the command is not supported.
Kris Rambish82ee1c02014-12-10 17:02:39 -0800100 """
Kris Rambish82ee1c02014-12-10 17:02:39 -0800101 status = {}
Kris Rambishbb5258c2014-12-16 16:51:17 -0800102 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_more_status | grep :')
103 if out.startswith(UNAVAILABLE_ACTION):
104 # --action=tpm_more_status only exists >= 41.
105 logging.info('Method not supported!')
106 return status
Kris Rambish82ee1c02014-12-10 17:02:39 -0800107 for line in out.splitlines():
108 items = line.strip().split(':')
109 if items[1].strip() == 'false':
110 value = False
111 elif items[1].strip() == 'true':
112 value = True
Kris Rambishbe132592014-12-17 14:26:06 -0800113 elif items[1].strip().isdigit():
Kris Rambish82ee1c02014-12-10 17:02:39 -0800114 value = int(items[1].strip())
Kris Rambishbe132592014-12-17 14:26:06 -0800115 else:
116 value = items[1].strip(' "')
Kris Rambish82ee1c02014-12-10 17:02:39 -0800117 status[items[0]] = value
118 return status
119
120
121def is_tpm_lockout_in_effect():
122 """Returns true if the TPM lockout is in effect; false otherwise."""
123 status = get_tpm_more_status()
Christopher Wiley94fd6b32014-12-13 18:52:03 -0800124 return status.get('dictionary_attack_lockout_in_effect', None)
Kris Rambish82ee1c02014-12-10 17:02:39 -0800125
126
David Pursell2a2ef342014-10-17 10:34:56 -0700127def get_login_status():
128 """Query the login status
129
130 Returns:
131 A login status dictionary containing:
132 { 'owner_user_exists': True|False,
133 'boot_lockbox_finalized': True|False
134 }
135 """
136 out = __run_cmd(CRYPTOHOME_CMD + ' --action=get_login_status')
137 status = {}
138 for field in ['owner_user_exists', 'boot_lockbox_finalized']:
139 match = re.search('%s: (true|false)' % field, out)
140 if not match:
141 raise ChromiumOSError('Invalid login status: "%s".' % out)
142 status[field] = match.group(1) == 'true'
143 return status
144
145
Darren Krahn5f880f62012-10-02 15:17:59 -0700146def get_tpm_attestation_status():
147 """Get the TPM attestation status. Works similar to get_tpm_status().
148 """
149 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_attestation_status')
150 status = {}
151 for field in ['Prepared', 'Enrolled']:
152 match = re.search('Attestation %s: (true|false)' % field, out)
153 if not match:
154 raise ChromiumOSError('Invalid attestation status: "%s".' % out)
155 status[field] = match.group(1) == 'true'
156 return status
157
158
Frank Farzand5e36312012-01-13 14:34:03 -0800159def take_tpm_ownership():
160 """Take TPM owernship.
161
162 Blocks until TPM is owned.
163 """
164 __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_take_ownership')
165 __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_wait_ownership')
166
167
Darren Krahn0e73e7f2012-09-05 15:35:15 -0700168def verify_ek():
169 """Verify the TPM endorsement key.
170
171 Returns true if EK is valid.
172 """
173 cmd = CRYPTOHOME_CMD + ' --action=tpm_verify_ek'
174 return (utils.system(cmd, ignore_status=True) == 0)
175
176
Sean Oe5d8fd02010-09-30 10:44:44 +0200177def remove_vault(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200178 """Remove the given user's vault from the shadow directory."""
Sean Oe5d8fd02010-09-30 10:44:44 +0200179 logging.debug('user is %s', user)
180 user_hash = get_user_hash(user)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200181 logging.debug('Removing vault for user %s with hash %s' % (user, user_hash))
Sean Oe5d8fd02010-09-30 10:44:44 +0200182 cmd = CRYPTOHOME_CMD + ' --action=remove --force --user=%s' % user
183 __run_cmd(cmd)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200184 # Ensure that the vault does not exist.
185 if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
Darren Krahne6c44b92014-03-31 12:11:08 -0700186 raise ChromiumOSError('Cryptohome could not remove the user\'s vault.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200187
188
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200189def remove_all_vaults():
190 """Remove any existing vaults from the shadow directory.
191
192 This function must be run with root privileges.
193 """
barfab@chromium.org5c374632012-04-05 16:50:56 +0200194 for item in os.listdir(constants.SHADOW_ROOT):
195 abs_item = os.path.join(constants.SHADOW_ROOT, item)
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200196 if os.path.isdir(os.path.join(abs_item, 'vault')):
197 logging.debug('Removing vault for user with hash %s' % item)
198 shutil.rmtree(abs_item)
199
200
Sean Oe5d8fd02010-09-30 10:44:44 +0200201def mount_vault(user, password, create=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200202 """Mount the given user's vault."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400203 args = [CRYPTOHOME_CMD, '--action=mount', '--user=%s' % user,
Chris Masone3543e512013-11-04 13:09:30 -0800204 '--password=%s' % password, '--async']
Sean Oe5d8fd02010-09-30 10:44:44 +0200205 if create:
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400206 args.append('--create')
Chris Masone3543e512013-11-04 13:09:30 -0800207 logging.info(__run_cmd(' '.join(args)))
barfab@chromium.org5c374632012-04-05 16:50:56 +0200208 # Ensure that the vault exists in the shadow directory.
Sean Oe5d8fd02010-09-30 10:44:44 +0200209 user_hash = get_user_hash(user)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200210 if not os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
Alexis Saveryccb16be2017-02-01 16:23:15 -0800211 retry = 0
212 mounted = False
213 while retry < MOUNT_RETRY_COUNT and not mounted:
214 time.sleep(1)
215 logging.info("Retry " + str(retry + 1))
216 __run_cmd(' '.join(args))
217 # TODO: Remove this additional call to get_user_hash(user) when
218 # crbug.com/690994 is fixed
219 user_hash = get_user_hash(user)
220 if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
221 mounted = True
222 retry += 1
223 if not mounted:
224 raise ChromiumOSError('Cryptohome vault not found after mount.')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200225 # Ensure that the vault is mounted.
226 if not is_vault_mounted(
227 user=user,
228 device_regex=constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER,
229 allow_fail=True):
230 raise ChromiumOSError('Cryptohome created a vault but did not mount.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200231
232
Chris Masone5d010aa2013-05-06 14:38:42 -0700233def mount_guest():
234 """Mount the given user's vault."""
Chris Masone3543e512013-11-04 13:09:30 -0800235 args = [CRYPTOHOME_CMD, '--action=mount_guest', '--async']
236 logging.info(__run_cmd(' '.join(args)))
Chris Masone5d010aa2013-05-06 14:38:42 -0700237 # Ensure that the guest tmpfs is mounted.
238 if not is_guest_vault_mounted(allow_fail=True):
239 raise ChromiumOSError('Cryptohome did not mount tmpfs.')
240
241
Sean Oe5d8fd02010-09-30 10:44:44 +0200242def test_auth(user, password):
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400243 cmd = [CRYPTOHOME_CMD, '--action=test_auth', '--user=%s' % user,
244 '--password=%s' % password, '--async']
245 return 'Authentication succeeded' in utils.system_output(cmd)
Sean Oe5d8fd02010-09-30 10:44:44 +0200246
247
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400248def unmount_vault(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200249 """Unmount the given user's vault.
250
251 Once unmounting for a specific user is supported, the user parameter will
252 name the target user. See crosbug.com/20778.
Elly Jones686c2f42011-10-24 16:45:07 -0400253 """
Chris Masone3543e512013-11-04 13:09:30 -0800254 __run_cmd(CRYPTOHOME_CMD + ' --action=unmount')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200255 # Ensure that the vault is not mounted.
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400256 if is_vault_mounted(user, allow_fail=True):
Sean Oe5d8fd02010-09-30 10:44:44 +0200257 raise ChromiumOSError('Cryptohome did not unmount the user.')
258
259
barfab@chromium.org5c374632012-04-05 16:50:56 +0200260def __get_mount_info(mount_point, allow_fail=False):
261 """Get information about the active mount at a given mount point."""
beeps569f8672013-08-07 10:18:51 -0700262 cryptohomed_path = '/proc/$(pgrep cryptohomed)/mounts'
263 try:
Daniel Erat2ec32792017-01-31 18:26:59 -0700264 logging.debug("Active cryptohome mounts:\n" +
265 utils.system_output('cat %s' % cryptohomed_path))
beeps569f8672013-08-07 10:18:51 -0700266 mount_line = utils.system_output(
267 'grep %s %s' % (mount_point, cryptohomed_path),
268 ignore_status=allow_fail)
269 except Exception as e:
270 logging.error(e)
271 raise ChromiumOSError('Could not get info about cryptohome vault '
272 'through %s. See logs for complete mount-point.'
273 % os.path.dirname(str(mount_point)))
Sourav Poddar574bd622010-05-26 14:22:26 +0530274 return mount_line.split()
275
276
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400277def __get_user_mount_info(user, allow_fail=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200278 """Get information about the active mounts for a given user.
279
280 Returns the active mounts at the user's user and system mount points. If no
281 user is given, the active mount at the shared mount point is returned
282 (regular users have a bind-mount at this mount point for backwards
283 compatibility; the guest user has a mount at this mount point only).
284 """
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400285 return [__get_mount_info(mount_point=user_path(user),
286 allow_fail=allow_fail),
287 __get_mount_info(mount_point=system_path(user),
288 allow_fail=allow_fail)]
Jim Hebertf08f88d2011-04-22 10:33:49 -0700289
barfab@chromium.org5c374632012-04-05 16:50:56 +0200290def is_vault_mounted(
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400291 user,
barfab@chromium.org5c374632012-04-05 16:50:56 +0200292 device_regex=constants.CRYPTOHOME_DEV_REGEX_ANY,
293 fs_regex=constants.CRYPTOHOME_FS_REGEX_ANY,
294 allow_fail=False):
295 """Check whether a vault is mounted for the given user.
296
297 If no user is given, the shared mount point is checked, determining whether
298 a vault is mounted for any user.
299 """
300 user_mount_info = __get_user_mount_info(user=user, allow_fail=allow_fail)
301 for mount_info in user_mount_info:
302 if (len(mount_info) < 3 or
303 not re.match(device_regex, mount_info[0]) or
304 not re.match(fs_regex, mount_info[2])):
305 return False
306 return True
Sourav Poddar574bd622010-05-26 14:22:26 +0530307
308
barfab@chromium.org5c374632012-04-05 16:50:56 +0200309def is_guest_vault_mounted(allow_fail=False):
310 """Check whether a vault backed by tmpfs is mounted for the guest user."""
311 return is_vault_mounted(
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400312 user=GUEST_USER_NAME,
barfab@chromium.org5c374632012-04-05 16:50:56 +0200313 device_regex=constants.CRYPTOHOME_DEV_REGEX_GUEST,
314 fs_regex=constants.CRYPTOHOME_FS_REGEX_TMPFS,
315 allow_fail=allow_fail)
316
317
Kazuhiro Inabaa3bf6452017-02-08 11:41:50 +0900318def get_mounted_vault_path(user, allow_fail=False):
319 """Get the path where the decrypted data for the user is located."""
320 return os.path.join(constants.SHADOW_ROOT, get_user_hash(user), 'mount')
Nirnimesh66814492011-06-27 18:00:33 -0700321
322
323def canonicalize(credential):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200324 """Perform basic canonicalization of |email_address|.
Nirnimesh66814492011-06-27 18:00:33 -0700325
barfab@chromium.org5c374632012-04-05 16:50:56 +0200326 Perform basic canonicalization of |email_address|, taking into account that
327 gmail does not consider '.' or caps inside a username to matter. It also
328 ignores everything after a '+'. For example,
329 c.masone+abc@gmail.com == cMaSone@gmail.com, per
Nirnimesh66814492011-06-27 18:00:33 -0700330 http://mail.google.com/support/bin/answer.py?hl=en&ctx=mail&answer=10313
331 """
332 if not credential:
333 return None
334
335 parts = credential.split('@')
336 if len(parts) != 2:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200337 raise error.TestError('Malformed email: ' + credential)
Nirnimesh66814492011-06-27 18:00:33 -0700338
339 (name, domain) = parts
340 name = name.partition('+')[0]
barfab@chromium.org5c374632012-04-05 16:50:56 +0200341 if (domain == constants.SPECIAL_CASE_DOMAIN):
Nirnimesh66814492011-06-27 18:00:33 -0700342 name = name.replace('.', '')
343 return '@'.join([name, domain]).lower()
Elly Jones686c2f42011-10-24 16:45:07 -0400344
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200345
Will Drewryd2fed972013-12-05 16:35:51 -0600346def crash_cryptohomed():
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600347 # Try to kill cryptohomed so we get something to work with.
348 pid = __run_cmd('pgrep cryptohomed')
349 try:
Will Drewry9e440792013-12-11 17:18:35 -0600350 pid = int(pid)
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600351 except ValueError, e: # empty or invalid string
Will Drewry9e440792013-12-11 17:18:35 -0600352 raise error.TestError('Cryptohomed was not running')
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600353 utils.system('kill -ABRT %d' % pid)
354 # CONT just in case cryptohomed had a spurious STOP.
355 utils.system('kill -CONT %d' % pid)
356 utils.poll_for_condition(
357 lambda: utils.system('ps -p %d' % pid,
358 ignore_status=True) != 0,
Will Drewry934d1532014-01-30 16:23:17 -0600359 timeout=180,
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600360 exception=error.TestError(
361 'Timeout waiting for cryptohomed to coredump'))
362
Will Drewryd2fed972013-12-05 16:35:51 -0600363
Will Drewry9e440792013-12-11 17:18:35 -0600364class CryptohomeProxy(DBusClient):
365 """A DBus proxy client for testing the Cryptohome DBus server.
366 """
367 CRYPTOHOME_BUS_NAME = 'org.chromium.Cryptohome'
368 CRYPTOHOME_OBJECT_PATH = '/org/chromium/Cryptohome'
369 CRYPTOHOME_INTERFACE = 'org.chromium.CryptohomeInterface'
370 ASYNC_CALL_STATUS_SIGNAL = 'AsyncCallStatus'
371 ASYNC_CALL_STATUS_SIGNAL_ARGUMENTS = (
372 'async_id', 'return_status', 'return_code'
373 )
374 DBUS_PROPERTIES_INTERFACE = 'org.freedesktop.DBus.Properties'
375
Chris Masone19e305e2014-03-14 15:13:46 -0700376
Chris Masone64170f82014-03-14 15:47:05 -0700377 def __init__(self, bus_loop=None):
Will Drewry9e440792013-12-11 17:18:35 -0600378 self.main_loop = gobject.MainLoop()
Will Drewry78db9dc2014-04-01 16:34:23 -0500379 if bus_loop is None:
Chris Masone64170f82014-03-14 15:47:05 -0700380 bus_loop = DBusGMainLoop(set_as_default=True)
Will Drewry9e440792013-12-11 17:18:35 -0600381 self.bus = dbus.SystemBus(mainloop=bus_loop)
382 super(CryptohomeProxy, self).__init__(self.main_loop, self.bus,
383 self.CRYPTOHOME_BUS_NAME,
384 self.CRYPTOHOME_OBJECT_PATH)
385 self.iface = dbus.Interface(self.proxy_object,
386 self.CRYPTOHOME_INTERFACE)
387 self.properties = dbus.Interface(self.proxy_object,
388 self.DBUS_PROPERTIES_INTERFACE)
389 self.handle_signal(self.CRYPTOHOME_INTERFACE,
390 self.ASYNC_CALL_STATUS_SIGNAL,
391 self.ASYNC_CALL_STATUS_SIGNAL_ARGUMENTS)
Elly Jones2f0ebba2011-10-27 13:43:20 -0400392
Chris Masone19e305e2014-03-14 15:13:46 -0700393
Will Drewryd2fed972013-12-05 16:35:51 -0600394 # Wrap all proxied calls to catch cryptohomed failures.
395 def __call(self, method, *args):
396 try:
Chris Masonef59d9df2014-03-14 12:05:32 -0700397 return method(*args, timeout=180)
Will Drewryd2fed972013-12-05 16:35:51 -0600398 except dbus.exceptions.DBusException, e:
399 if e.get_dbus_name() == 'org.freedesktop.DBus.Error.NoReply':
400 logging.error('Cryptohome is not responding. Sending ABRT')
401 crash_cryptohomed()
402 raise ChromiumOSError('cryptohomed aborted. Check crashes!')
403 raise e
404
Chris Masone19e305e2014-03-14 15:13:46 -0700405
Will Drewry9e440792013-12-11 17:18:35 -0600406 def __wait_for_specific_signal(self, signal, data):
407 """Wait for the |signal| with matching |data|
408 Returns the resulting dict on success or {} on error.
409 """
Will Drewryc4de5ff2014-02-03 13:26:57 -0600410 # Do not bubble up the timeout here, just return {}.
411 result = {}
412 try:
413 result = self.wait_for_signal(signal)
414 except utils.TimeoutError:
415 return {}
Will Drewry9e440792013-12-11 17:18:35 -0600416 for k in data.keys():
417 if not result.has_key(k) or result[k] != data[k]:
418 return {}
419 return result
420
Chris Masone19e305e2014-03-14 15:13:46 -0700421
Will Drewry9e440792013-12-11 17:18:35 -0600422 # Perform a data-less async call.
423 # TODO(wad) Add __async_data_call.
424 def __async_call(self, method, *args):
Will Drewryfef135a2014-05-23 16:02:14 -0500425 # Clear out any superfluous async call signals.
426 self.clear_signal_content(self.ASYNC_CALL_STATUS_SIGNAL)
Will Drewry9e440792013-12-11 17:18:35 -0600427 out = self.__call(method, *args)
428 logging.debug('Issued call ' + str(method) +
429 ' with async_id ' + str(out))
430 result = {}
431 try:
Will Drewry934d1532014-01-30 16:23:17 -0600432 # __wait_for_specific_signal has a 10s timeout
Will Drewry9e440792013-12-11 17:18:35 -0600433 result = utils.poll_for_condition(
434 lambda: self.__wait_for_specific_signal(
435 self.ASYNC_CALL_STATUS_SIGNAL, {'async_id' : out}),
Will Drewry934d1532014-01-30 16:23:17 -0600436 timeout=180,
Will Drewry9e440792013-12-11 17:18:35 -0600437 desc='matching %s signal' % self.ASYNC_CALL_STATUS_SIGNAL)
438 except utils.TimeoutError, e:
439 logging.error('Cryptohome timed out. Sending ABRT.')
440 crash_cryptohomed()
441 raise ChromiumOSError('cryptohomed aborted. Check crashes!')
442 return result
443
Chris Masone19e305e2014-03-14 15:13:46 -0700444
Will Drewry9e440792013-12-11 17:18:35 -0600445 def mount(self, user, password, create=False, async=True):
Elly Jones2f0ebba2011-10-27 13:43:20 -0400446 """Mounts a cryptohome.
447
448 Returns True if the mount succeeds or False otherwise.
449 TODO(ellyjones): Migrate mount_vault() to use a multi-user-safe
450 heuristic, then remove this method. See <crosbug.com/20778>.
451 """
Will Drewry9e440792013-12-11 17:18:35 -0600452 if async:
453 return self.__async_call(self.iface.AsyncMount, user, password,
454 create, False, [])['return_status']
Will Drewryd2fed972013-12-05 16:35:51 -0600455 out = self.__call(self.iface.Mount, user, password, create, False, [])
Will Drewry9e440792013-12-11 17:18:35 -0600456 # Sync returns (return code, return status)
457 return out[1] if len(out) > 1 else False
Elly Jones2f0ebba2011-10-27 13:43:20 -0400458
Chris Masone19e305e2014-03-14 15:13:46 -0700459
Elly Jones2f0ebba2011-10-27 13:43:20 -0400460 def unmount(self, user):
461 """Unmounts a cryptohome.
462
463 Returns True if the unmount suceeds or false otherwise.
464 TODO(ellyjones): Once there's a per-user unmount method, use it. See
465 <crosbug.com/20778>.
466 """
Will Drewryd2fed972013-12-05 16:35:51 -0600467 return self.__call(self.iface.Unmount)
Elly Jones2f0ebba2011-10-27 13:43:20 -0400468
Chris Masone19e305e2014-03-14 15:13:46 -0700469
Elly Jones2f0ebba2011-10-27 13:43:20 -0400470 def is_mounted(self, user):
471 """Tests whether a user's cryptohome is mounted."""
472 return (utils.is_mountpoint(user_path(user))
473 and utils.is_mountpoint(system_path(user)))
474
Chris Masone19e305e2014-03-14 15:13:46 -0700475
Elly Jones2f0ebba2011-10-27 13:43:20 -0400476 def require_mounted(self, user):
477 """Raises a test failure if a user's cryptohome is not mounted."""
478 utils.require_mountpoint(user_path(user))
479 utils.require_mountpoint(system_path(user))
Elly Jones4458f442012-04-16 15:42:56 -0400480
Chris Masone19e305e2014-03-14 15:13:46 -0700481
Will Drewry9e440792013-12-11 17:18:35 -0600482 def migrate(self, user, oldkey, newkey, async=True):
Elly Jones4458f442012-04-16 15:42:56 -0400483 """Migrates the specified user's cryptohome from one key to another."""
Will Drewry9e440792013-12-11 17:18:35 -0600484 if async:
485 return self.__async_call(self.iface.AsyncMigrateKey,
486 user, oldkey, newkey)['return_status']
Will Drewryd2fed972013-12-05 16:35:51 -0600487 return self.__call(self.iface.MigrateKey, user, oldkey, newkey)
Elly Jones4458f442012-04-16 15:42:56 -0400488
Chris Masone19e305e2014-03-14 15:13:46 -0700489
Will Drewry9e440792013-12-11 17:18:35 -0600490 def remove(self, user, async=True):
491 if async:
492 return self.__async_call(self.iface.AsyncRemove,
493 user)['return_status']
Will Drewryd2fed972013-12-05 16:35:51 -0600494 return self.__call(self.iface.Remove, user)
Chris Masone19e305e2014-03-14 15:13:46 -0700495
496
497 def ensure_clean_cryptohome_for(self, user, password=None):
498 """Ensure a fresh cryptohome exists for user.
499
500 @param user: user who needs a shiny new cryptohome.
501 @param password: if unset, a random password will be used.
502 """
503 if not password:
504 password = ''.join(random.sample(string.ascii_lowercase, 6))
505 self.remove(user)
506 self.mount(user, password, create=True)