blob: e0cfc9c284509833c94d865305a37d83bf523237 [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
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)