blob: 8a6b567fbec934d7d9b710118c663162e900a090 [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
Eric Carusoa4ac7a82015-08-06 10:53:54 -07008import common
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
Eric Carusoa4ac7a82015-08-06 10:53:54 -070011from autotest_lib.client.common_lib.cros import constants
Will Drewry9e440792013-12-11 17:18:35 -060012from autotest_lib.client.cros.cros_disks import DBusClient
Eric Lic4d8f4a2010-12-10 09:49:23 -080013
Sean Oe5d8fd02010-09-30 10:44:44 +020014CRYPTOHOME_CMD = '/usr/sbin/cryptohome'
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040015GUEST_USER_NAME = '$guest'
Kris Rambishbb5258c2014-12-16 16:51:17 -080016UNAVAILABLE_ACTION = 'Unknown action or no action given.'
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)):
Sean Oe5d8fd02010-09-30 10:44:44 +0200211 raise ChromiumOSError('Cryptohome vault not found after mount.')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200212 # Ensure that the vault is mounted.
213 if not is_vault_mounted(
214 user=user,
215 device_regex=constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER,
216 allow_fail=True):
217 raise ChromiumOSError('Cryptohome created a vault but did not mount.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200218
219
Chris Masone5d010aa2013-05-06 14:38:42 -0700220def mount_guest():
221 """Mount the given user's vault."""
Chris Masone3543e512013-11-04 13:09:30 -0800222 args = [CRYPTOHOME_CMD, '--action=mount_guest', '--async']
223 logging.info(__run_cmd(' '.join(args)))
Chris Masone5d010aa2013-05-06 14:38:42 -0700224 # Ensure that the guest tmpfs is mounted.
225 if not is_guest_vault_mounted(allow_fail=True):
226 raise ChromiumOSError('Cryptohome did not mount tmpfs.')
227
228
Sean Oe5d8fd02010-09-30 10:44:44 +0200229def test_auth(user, password):
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400230 cmd = [CRYPTOHOME_CMD, '--action=test_auth', '--user=%s' % user,
231 '--password=%s' % password, '--async']
232 return 'Authentication succeeded' in utils.system_output(cmd)
Sean Oe5d8fd02010-09-30 10:44:44 +0200233
234
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400235def unmount_vault(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200236 """Unmount the given user's vault.
237
238 Once unmounting for a specific user is supported, the user parameter will
239 name the target user. See crosbug.com/20778.
Elly Jones686c2f42011-10-24 16:45:07 -0400240 """
Chris Masone3543e512013-11-04 13:09:30 -0800241 __run_cmd(CRYPTOHOME_CMD + ' --action=unmount')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200242 # Ensure that the vault is not mounted.
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400243 if is_vault_mounted(user, allow_fail=True):
Sean Oe5d8fd02010-09-30 10:44:44 +0200244 raise ChromiumOSError('Cryptohome did not unmount the user.')
245
246
barfab@chromium.org5c374632012-04-05 16:50:56 +0200247def __get_mount_info(mount_point, allow_fail=False):
248 """Get information about the active mount at a given mount point."""
beeps569f8672013-08-07 10:18:51 -0700249 cryptohomed_path = '/proc/$(pgrep cryptohomed)/mounts'
250 try:
251 logging.info(utils.system_output('cat %s' % cryptohomed_path))
252 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
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400304def get_mounted_vault_devices(user, allow_fail=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200305 """Get the device(s) backing the vault mounted for the given user.
306
307 Returns the devices mounted at the user's user and system mount points. If
308 no user is given, the device mounted at the shared mount point is returned.
309 """
310 return [mount_info[0]
311 for mount_info
312 in __get_user_mount_info(user=user, allow_fail=allow_fail)
313 if len(mount_info)]
Nirnimesh66814492011-06-27 18:00:33 -0700314
315
316def canonicalize(credential):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200317 """Perform basic canonicalization of |email_address|.
Nirnimesh66814492011-06-27 18:00:33 -0700318
barfab@chromium.org5c374632012-04-05 16:50:56 +0200319 Perform basic canonicalization of |email_address|, taking into account that
320 gmail does not consider '.' or caps inside a username to matter. It also
321 ignores everything after a '+'. For example,
322 c.masone+abc@gmail.com == cMaSone@gmail.com, per
Nirnimesh66814492011-06-27 18:00:33 -0700323 http://mail.google.com/support/bin/answer.py?hl=en&ctx=mail&answer=10313
324 """
325 if not credential:
326 return None
327
328 parts = credential.split('@')
329 if len(parts) != 2:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200330 raise error.TestError('Malformed email: ' + credential)
Nirnimesh66814492011-06-27 18:00:33 -0700331
332 (name, domain) = parts
333 name = name.partition('+')[0]
barfab@chromium.org5c374632012-04-05 16:50:56 +0200334 if (domain == constants.SPECIAL_CASE_DOMAIN):
Nirnimesh66814492011-06-27 18:00:33 -0700335 name = name.replace('.', '')
336 return '@'.join([name, domain]).lower()
Elly Jones686c2f42011-10-24 16:45:07 -0400337
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200338
Will Drewryd2fed972013-12-05 16:35:51 -0600339def crash_cryptohomed():
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600340 # Try to kill cryptohomed so we get something to work with.
341 pid = __run_cmd('pgrep cryptohomed')
342 try:
Will Drewry9e440792013-12-11 17:18:35 -0600343 pid = int(pid)
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600344 except ValueError, e: # empty or invalid string
Will Drewry9e440792013-12-11 17:18:35 -0600345 raise error.TestError('Cryptohomed was not running')
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600346 utils.system('kill -ABRT %d' % pid)
347 # CONT just in case cryptohomed had a spurious STOP.
348 utils.system('kill -CONT %d' % pid)
349 utils.poll_for_condition(
350 lambda: utils.system('ps -p %d' % pid,
351 ignore_status=True) != 0,
Will Drewry934d1532014-01-30 16:23:17 -0600352 timeout=180,
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600353 exception=error.TestError(
354 'Timeout waiting for cryptohomed to coredump'))
355
Will Drewryd2fed972013-12-05 16:35:51 -0600356
Will Drewry9e440792013-12-11 17:18:35 -0600357class CryptohomeProxy(DBusClient):
358 """A DBus proxy client for testing the Cryptohome DBus server.
359 """
360 CRYPTOHOME_BUS_NAME = 'org.chromium.Cryptohome'
361 CRYPTOHOME_OBJECT_PATH = '/org/chromium/Cryptohome'
362 CRYPTOHOME_INTERFACE = 'org.chromium.CryptohomeInterface'
363 ASYNC_CALL_STATUS_SIGNAL = 'AsyncCallStatus'
364 ASYNC_CALL_STATUS_SIGNAL_ARGUMENTS = (
365 'async_id', 'return_status', 'return_code'
366 )
367 DBUS_PROPERTIES_INTERFACE = 'org.freedesktop.DBus.Properties'
368
Chris Masone19e305e2014-03-14 15:13:46 -0700369
Chris Masone64170f82014-03-14 15:47:05 -0700370 def __init__(self, bus_loop=None):
Will Drewry9e440792013-12-11 17:18:35 -0600371 self.main_loop = gobject.MainLoop()
Will Drewry78db9dc2014-04-01 16:34:23 -0500372 if bus_loop is None:
Chris Masone64170f82014-03-14 15:47:05 -0700373 bus_loop = DBusGMainLoop(set_as_default=True)
Will Drewry9e440792013-12-11 17:18:35 -0600374 self.bus = dbus.SystemBus(mainloop=bus_loop)
375 super(CryptohomeProxy, self).__init__(self.main_loop, self.bus,
376 self.CRYPTOHOME_BUS_NAME,
377 self.CRYPTOHOME_OBJECT_PATH)
378 self.iface = dbus.Interface(self.proxy_object,
379 self.CRYPTOHOME_INTERFACE)
380 self.properties = dbus.Interface(self.proxy_object,
381 self.DBUS_PROPERTIES_INTERFACE)
382 self.handle_signal(self.CRYPTOHOME_INTERFACE,
383 self.ASYNC_CALL_STATUS_SIGNAL,
384 self.ASYNC_CALL_STATUS_SIGNAL_ARGUMENTS)
Elly Jones2f0ebba2011-10-27 13:43:20 -0400385
Chris Masone19e305e2014-03-14 15:13:46 -0700386
Will Drewryd2fed972013-12-05 16:35:51 -0600387 # Wrap all proxied calls to catch cryptohomed failures.
388 def __call(self, method, *args):
389 try:
Chris Masonef59d9df2014-03-14 12:05:32 -0700390 return method(*args, timeout=180)
Will Drewryd2fed972013-12-05 16:35:51 -0600391 except dbus.exceptions.DBusException, e:
392 if e.get_dbus_name() == 'org.freedesktop.DBus.Error.NoReply':
393 logging.error('Cryptohome is not responding. Sending ABRT')
394 crash_cryptohomed()
395 raise ChromiumOSError('cryptohomed aborted. Check crashes!')
396 raise e
397
Chris Masone19e305e2014-03-14 15:13:46 -0700398
Will Drewry9e440792013-12-11 17:18:35 -0600399 def __wait_for_specific_signal(self, signal, data):
400 """Wait for the |signal| with matching |data|
401 Returns the resulting dict on success or {} on error.
402 """
Will Drewryc4de5ff2014-02-03 13:26:57 -0600403 # Do not bubble up the timeout here, just return {}.
404 result = {}
405 try:
406 result = self.wait_for_signal(signal)
407 except utils.TimeoutError:
408 return {}
Will Drewry9e440792013-12-11 17:18:35 -0600409 for k in data.keys():
410 if not result.has_key(k) or result[k] != data[k]:
411 return {}
412 return result
413
Chris Masone19e305e2014-03-14 15:13:46 -0700414
Will Drewry9e440792013-12-11 17:18:35 -0600415 # Perform a data-less async call.
416 # TODO(wad) Add __async_data_call.
417 def __async_call(self, method, *args):
Will Drewryfef135a2014-05-23 16:02:14 -0500418 # Clear out any superfluous async call signals.
419 self.clear_signal_content(self.ASYNC_CALL_STATUS_SIGNAL)
Will Drewry9e440792013-12-11 17:18:35 -0600420 out = self.__call(method, *args)
421 logging.debug('Issued call ' + str(method) +
422 ' with async_id ' + str(out))
423 result = {}
424 try:
Will Drewry934d1532014-01-30 16:23:17 -0600425 # __wait_for_specific_signal has a 10s timeout
Will Drewry9e440792013-12-11 17:18:35 -0600426 result = utils.poll_for_condition(
427 lambda: self.__wait_for_specific_signal(
428 self.ASYNC_CALL_STATUS_SIGNAL, {'async_id' : out}),
Will Drewry934d1532014-01-30 16:23:17 -0600429 timeout=180,
Will Drewry9e440792013-12-11 17:18:35 -0600430 desc='matching %s signal' % self.ASYNC_CALL_STATUS_SIGNAL)
431 except utils.TimeoutError, e:
432 logging.error('Cryptohome timed out. Sending ABRT.')
433 crash_cryptohomed()
434 raise ChromiumOSError('cryptohomed aborted. Check crashes!')
435 return result
436
Chris Masone19e305e2014-03-14 15:13:46 -0700437
Will Drewry9e440792013-12-11 17:18:35 -0600438 def mount(self, user, password, create=False, async=True):
Elly Jones2f0ebba2011-10-27 13:43:20 -0400439 """Mounts a cryptohome.
440
441 Returns True if the mount succeeds or False otherwise.
442 TODO(ellyjones): Migrate mount_vault() to use a multi-user-safe
443 heuristic, then remove this method. See <crosbug.com/20778>.
444 """
Will Drewry9e440792013-12-11 17:18:35 -0600445 if async:
446 return self.__async_call(self.iface.AsyncMount, user, password,
447 create, False, [])['return_status']
Will Drewryd2fed972013-12-05 16:35:51 -0600448 out = self.__call(self.iface.Mount, user, password, create, False, [])
Will Drewry9e440792013-12-11 17:18:35 -0600449 # Sync returns (return code, return status)
450 return out[1] if len(out) > 1 else False
Elly Jones2f0ebba2011-10-27 13:43:20 -0400451
Chris Masone19e305e2014-03-14 15:13:46 -0700452
Elly Jones2f0ebba2011-10-27 13:43:20 -0400453 def unmount(self, user):
454 """Unmounts a cryptohome.
455
456 Returns True if the unmount suceeds or false otherwise.
457 TODO(ellyjones): Once there's a per-user unmount method, use it. See
458 <crosbug.com/20778>.
459 """
Will Drewryd2fed972013-12-05 16:35:51 -0600460 return self.__call(self.iface.Unmount)
Elly Jones2f0ebba2011-10-27 13:43:20 -0400461
Chris Masone19e305e2014-03-14 15:13:46 -0700462
Elly Jones2f0ebba2011-10-27 13:43:20 -0400463 def is_mounted(self, user):
464 """Tests whether a user's cryptohome is mounted."""
465 return (utils.is_mountpoint(user_path(user))
466 and utils.is_mountpoint(system_path(user)))
467
Chris Masone19e305e2014-03-14 15:13:46 -0700468
Elly Jones2f0ebba2011-10-27 13:43:20 -0400469 def require_mounted(self, user):
470 """Raises a test failure if a user's cryptohome is not mounted."""
471 utils.require_mountpoint(user_path(user))
472 utils.require_mountpoint(system_path(user))
Elly Jones4458f442012-04-16 15:42:56 -0400473
Chris Masone19e305e2014-03-14 15:13:46 -0700474
Will Drewry9e440792013-12-11 17:18:35 -0600475 def migrate(self, user, oldkey, newkey, async=True):
Elly Jones4458f442012-04-16 15:42:56 -0400476 """Migrates the specified user's cryptohome from one key to another."""
Will Drewry9e440792013-12-11 17:18:35 -0600477 if async:
478 return self.__async_call(self.iface.AsyncMigrateKey,
479 user, oldkey, newkey)['return_status']
Will Drewryd2fed972013-12-05 16:35:51 -0600480 return self.__call(self.iface.MigrateKey, user, oldkey, newkey)
Elly Jones4458f442012-04-16 15:42:56 -0400481
Chris Masone19e305e2014-03-14 15:13:46 -0700482
Will Drewry9e440792013-12-11 17:18:35 -0600483 def remove(self, user, async=True):
484 if async:
485 return self.__async_call(self.iface.AsyncRemove,
486 user)['return_status']
Will Drewryd2fed972013-12-05 16:35:51 -0600487 return self.__call(self.iface.Remove, user)
Chris Masone19e305e2014-03-14 15:13:46 -0700488
489
490 def ensure_clean_cryptohome_for(self, user, password=None):
491 """Ensure a fresh cryptohome exists for user.
492
493 @param user: user who needs a shiny new cryptohome.
494 @param password: if unset, a random password will be used.
495 """
496 if not password:
497 password = ''.join(random.sample(string.ascii_lowercase, 6))
498 self.remove(user)
499 self.mount(user, password, create=True)