blob: 8cca77c13ee1b8e5e352e07dfc7a14b75830149f [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
Dan Spaidbe9789c2017-05-19 15:18:42 +090017TEMP_MOUNT_PATTERN = '/home/.shadow/%s/temporary_mount'
18VAULT_PATH_PATTERN = '/home/.shadow/%s/vault'
Sean Oe5d8fd02010-09-30 10:44:44 +020019
Chris Masone5d010aa2013-05-06 14:38:42 -070020class ChromiumOSError(error.TestError):
Sean Oe5d8fd02010-09-30 10:44:44 +020021 """Generic error for ChromiumOS-specific exceptions."""
22 pass
23
Sean Oe5d8fd02010-09-30 10:44:44 +020024def __run_cmd(cmd):
25 return utils.system_output(cmd + ' 2>&1', retain_output=True,
26 ignore_status=True).strip()
27
Sean Oe5d8fd02010-09-30 10:44:44 +020028def get_user_hash(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +020029 """Get the user hash for the given user."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040030 return utils.system_output(['cryptohome', '--action=obfuscate_user',
31 '--user=%s' % user])
Sean Oe5d8fd02010-09-30 10:44:44 +020032
33
barfab@chromium.org5c374632012-04-05 16:50:56 +020034def user_path(user):
35 """Get the user mount point for the given user."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040036 return utils.system_output(['cryptohome-path', 'user', user])
barfab@chromium.org5c374632012-04-05 16:50:56 +020037
38
39def system_path(user):
40 """Get the system mount point for the given user."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040041 return utils.system_output(['cryptohome-path', 'system', user])
barfab@chromium.org5c374632012-04-05 16:50:56 +020042
43
Dan Spaidbe9789c2017-05-19 15:18:42 +090044def temporary_mount_path(user):
45 """Get the vault mount path used during crypto-migration for the user.
46
47 @param user: user the temporary mount should be for
48 """
49 return TEMP_MOUNT_PATTERN % (get_user_hash(user))
50
51
52def vault_path(user):
53 """ Get the vault path for the given user.
54
55 @param user: The user who's vault path should be returned.
56 """
57 return VAULT_PATH_PATTERN % (get_user_hash(user))
58
59
Chris Masone5d010aa2013-05-06 14:38:42 -070060def ensure_clean_cryptohome_for(user, password=None):
61 """Ensure a fresh cryptohome exists for user.
62
63 @param user: user who needs a shiny new cryptohome.
64 @param password: if unset, a random password will be used.
65 """
66 if not password:
67 password = ''.join(random.sample(string.ascii_lowercase, 6))
68 remove_vault(user)
69 mount_vault(user, password, create=True)
70
71
Frank Farzand5e36312012-01-13 14:34:03 -080072def get_tpm_status():
73 """Get the TPM status.
74
75 Returns:
76 A TPM status dictionary, for example:
77 { 'Enabled': True,
78 'Owned': True,
79 'Being Owned': False,
80 'Ready': True,
81 'Password': ''
82 }
83 """
84 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_status')
85 status = {}
86 for field in ['Enabled', 'Owned', 'Being Owned', 'Ready']:
87 match = re.search('TPM %s: (true|false)' % field, out)
88 if not match:
89 raise ChromiumOSError('Invalid TPM status: "%s".' % out)
90 status[field] = match.group(1) == 'true'
91 match = re.search('TPM Password: (\w*)', out)
92 status['Password'] = ''
93 if match:
94 status['Password'] = match.group(1)
95 return status
96
97
Kris Rambish82ee1c02014-12-10 17:02:39 -080098def get_tpm_more_status():
99 """Get more of the TPM status.
100
101 Returns:
102 A TPM more status dictionary, for example:
103 { 'dictionary_attack_lockout_in_effect': False,
104 'attestation_prepared': False,
105 'boot_lockbox_finalized': False,
106 'enabled': True,
107 'owned': True,
Kris Rambishbe132592014-12-17 14:26:06 -0800108 'owner_password': ''
Kris Rambish82ee1c02014-12-10 17:02:39 -0800109 'dictionary_attack_counter': 0,
110 'dictionary_attack_lockout_seconds_remaining': 0,
111 'dictionary_attack_threshold': 10,
112 'attestation_enrolled': False,
113 'initialized': True,
114 'verified_boot_measured': False,
115 'install_lockbox_finalized': True
116 }
Kris Rambishbb5258c2014-12-16 16:51:17 -0800117 An empty dictionary is returned if the command is not supported.
Kris Rambish82ee1c02014-12-10 17:02:39 -0800118 """
Kris Rambish82ee1c02014-12-10 17:02:39 -0800119 status = {}
Kris Rambishbb5258c2014-12-16 16:51:17 -0800120 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_more_status | grep :')
121 if out.startswith(UNAVAILABLE_ACTION):
122 # --action=tpm_more_status only exists >= 41.
123 logging.info('Method not supported!')
124 return status
Kris Rambish82ee1c02014-12-10 17:02:39 -0800125 for line in out.splitlines():
126 items = line.strip().split(':')
127 if items[1].strip() == 'false':
128 value = False
129 elif items[1].strip() == 'true':
130 value = True
Kris Rambishbe132592014-12-17 14:26:06 -0800131 elif items[1].strip().isdigit():
Kris Rambish82ee1c02014-12-10 17:02:39 -0800132 value = int(items[1].strip())
Kris Rambishbe132592014-12-17 14:26:06 -0800133 else:
134 value = items[1].strip(' "')
Kris Rambish82ee1c02014-12-10 17:02:39 -0800135 status[items[0]] = value
136 return status
137
138
139def is_tpm_lockout_in_effect():
140 """Returns true if the TPM lockout is in effect; false otherwise."""
141 status = get_tpm_more_status()
Christopher Wiley94fd6b32014-12-13 18:52:03 -0800142 return status.get('dictionary_attack_lockout_in_effect', None)
Kris Rambish82ee1c02014-12-10 17:02:39 -0800143
144
David Pursell2a2ef342014-10-17 10:34:56 -0700145def get_login_status():
146 """Query the login status
147
148 Returns:
149 A login status dictionary containing:
150 { 'owner_user_exists': True|False,
151 'boot_lockbox_finalized': True|False
152 }
153 """
154 out = __run_cmd(CRYPTOHOME_CMD + ' --action=get_login_status')
155 status = {}
156 for field in ['owner_user_exists', 'boot_lockbox_finalized']:
157 match = re.search('%s: (true|false)' % field, out)
158 if not match:
159 raise ChromiumOSError('Invalid login status: "%s".' % out)
160 status[field] = match.group(1) == 'true'
161 return status
162
163
Darren Krahn5f880f62012-10-02 15:17:59 -0700164def get_tpm_attestation_status():
165 """Get the TPM attestation status. Works similar to get_tpm_status().
166 """
167 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_attestation_status')
168 status = {}
169 for field in ['Prepared', 'Enrolled']:
170 match = re.search('Attestation %s: (true|false)' % field, out)
171 if not match:
172 raise ChromiumOSError('Invalid attestation status: "%s".' % out)
173 status[field] = match.group(1) == 'true'
174 return status
175
176
Frank Farzand5e36312012-01-13 14:34:03 -0800177def take_tpm_ownership():
178 """Take TPM owernship.
179
180 Blocks until TPM is owned.
181 """
182 __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_take_ownership')
183 __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_wait_ownership')
184
185
Darren Krahn0e73e7f2012-09-05 15:35:15 -0700186def verify_ek():
187 """Verify the TPM endorsement key.
188
189 Returns true if EK is valid.
190 """
191 cmd = CRYPTOHOME_CMD + ' --action=tpm_verify_ek'
192 return (utils.system(cmd, ignore_status=True) == 0)
193
194
Sean Oe5d8fd02010-09-30 10:44:44 +0200195def remove_vault(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200196 """Remove the given user's vault from the shadow directory."""
Sean Oe5d8fd02010-09-30 10:44:44 +0200197 logging.debug('user is %s', user)
198 user_hash = get_user_hash(user)
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700199 logging.debug('Removing vault for user %s with hash %s', user, user_hash)
Sean Oe5d8fd02010-09-30 10:44:44 +0200200 cmd = CRYPTOHOME_CMD + ' --action=remove --force --user=%s' % user
201 __run_cmd(cmd)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200202 # Ensure that the vault does not exist.
203 if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
Darren Krahne6c44b92014-03-31 12:11:08 -0700204 raise ChromiumOSError('Cryptohome could not remove the user\'s vault.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200205
206
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200207def remove_all_vaults():
208 """Remove any existing vaults from the shadow directory.
209
210 This function must be run with root privileges.
211 """
barfab@chromium.org5c374632012-04-05 16:50:56 +0200212 for item in os.listdir(constants.SHADOW_ROOT):
213 abs_item = os.path.join(constants.SHADOW_ROOT, item)
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200214 if os.path.isdir(os.path.join(abs_item, 'vault')):
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700215 logging.debug('Removing vault for user with hash %s', item)
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200216 shutil.rmtree(abs_item)
217
218
Sean Oe5d8fd02010-09-30 10:44:44 +0200219def mount_vault(user, password, create=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200220 """Mount the given user's vault."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400221 args = [CRYPTOHOME_CMD, '--action=mount', '--user=%s' % user,
Chris Masone3543e512013-11-04 13:09:30 -0800222 '--password=%s' % password, '--async']
Sean Oe5d8fd02010-09-30 10:44:44 +0200223 if create:
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400224 args.append('--create')
Chris Masone3543e512013-11-04 13:09:30 -0800225 logging.info(__run_cmd(' '.join(args)))
barfab@chromium.org5c374632012-04-05 16:50:56 +0200226 # Ensure that the vault exists in the shadow directory.
Sean Oe5d8fd02010-09-30 10:44:44 +0200227 user_hash = get_user_hash(user)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200228 if not os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
Alexis Saveryccb16be2017-02-01 16:23:15 -0800229 retry = 0
230 mounted = False
231 while retry < MOUNT_RETRY_COUNT and not mounted:
232 time.sleep(1)
233 logging.info("Retry " + str(retry + 1))
234 __run_cmd(' '.join(args))
235 # TODO: Remove this additional call to get_user_hash(user) when
236 # crbug.com/690994 is fixed
237 user_hash = get_user_hash(user)
238 if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
239 mounted = True
240 retry += 1
241 if not mounted:
242 raise ChromiumOSError('Cryptohome vault not found after mount.')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200243 # Ensure that the vault is mounted.
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700244 if not is_permanent_vault_mounted(user=user, allow_fail=True):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200245 raise ChromiumOSError('Cryptohome created a vault but did not mount.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200246
247
Chris Masone5d010aa2013-05-06 14:38:42 -0700248def mount_guest():
249 """Mount the given user's vault."""
Chris Masone3543e512013-11-04 13:09:30 -0800250 args = [CRYPTOHOME_CMD, '--action=mount_guest', '--async']
251 logging.info(__run_cmd(' '.join(args)))
Chris Masone5d010aa2013-05-06 14:38:42 -0700252 # Ensure that the guest tmpfs is mounted.
253 if not is_guest_vault_mounted(allow_fail=True):
254 raise ChromiumOSError('Cryptohome did not mount tmpfs.')
255
256
Sean Oe5d8fd02010-09-30 10:44:44 +0200257def test_auth(user, password):
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400258 cmd = [CRYPTOHOME_CMD, '--action=test_auth', '--user=%s' % user,
259 '--password=%s' % password, '--async']
260 return 'Authentication succeeded' in utils.system_output(cmd)
Sean Oe5d8fd02010-09-30 10:44:44 +0200261
262
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400263def unmount_vault(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200264 """Unmount the given user's vault.
265
266 Once unmounting for a specific user is supported, the user parameter will
267 name the target user. See crosbug.com/20778.
Elly Jones686c2f42011-10-24 16:45:07 -0400268 """
Chris Masone3543e512013-11-04 13:09:30 -0800269 __run_cmd(CRYPTOHOME_CMD + ' --action=unmount')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200270 # Ensure that the vault is not mounted.
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400271 if is_vault_mounted(user, allow_fail=True):
Sean Oe5d8fd02010-09-30 10:44:44 +0200272 raise ChromiumOSError('Cryptohome did not unmount the user.')
273
274
barfab@chromium.org5c374632012-04-05 16:50:56 +0200275def __get_mount_info(mount_point, allow_fail=False):
276 """Get information about the active mount at a given mount point."""
beeps569f8672013-08-07 10:18:51 -0700277 cryptohomed_path = '/proc/$(pgrep cryptohomed)/mounts'
278 try:
Daniel Erat2ec32792017-01-31 18:26:59 -0700279 logging.debug("Active cryptohome mounts:\n" +
280 utils.system_output('cat %s' % cryptohomed_path))
beeps569f8672013-08-07 10:18:51 -0700281 mount_line = utils.system_output(
282 'grep %s %s' % (mount_point, cryptohomed_path),
283 ignore_status=allow_fail)
284 except Exception as e:
285 logging.error(e)
286 raise ChromiumOSError('Could not get info about cryptohome vault '
287 'through %s. See logs for complete mount-point.'
288 % os.path.dirname(str(mount_point)))
Sourav Poddar574bd622010-05-26 14:22:26 +0530289 return mount_line.split()
290
291
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400292def __get_user_mount_info(user, allow_fail=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200293 """Get information about the active mounts for a given user.
294
295 Returns the active mounts at the user's user and system mount points. If no
296 user is given, the active mount at the shared mount point is returned
297 (regular users have a bind-mount at this mount point for backwards
298 compatibility; the guest user has a mount at this mount point only).
299 """
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400300 return [__get_mount_info(mount_point=user_path(user),
301 allow_fail=allow_fail),
302 __get_mount_info(mount_point=system_path(user),
303 allow_fail=allow_fail)]
Jim Hebertf08f88d2011-04-22 10:33:49 -0700304
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700305def is_vault_mounted(user, regexes=None, allow_fail=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200306 """Check whether a vault is mounted for the given user.
307
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700308 user: If no user is given, the shared mount point is checked, determining
309 whether a vault is mounted for any user.
310 regexes: dictionary of regexes to matches against the mount information.
311 The mount filesystem for the user's user and system mounts point must
312 match one of the keys.
313 The mount source point must match the selected device regex.
314
315 In addition, if mounted over ext4, we check the directory is encrypted.
barfab@chromium.org5c374632012-04-05 16:50:56 +0200316 """
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700317 if regexes is None:
318 regexes = {
319 constants.CRYPTOHOME_FS_REGEX_ANY :
320 constants.CRYPTOHOME_DEV_REGEX_ANY
321 }
barfab@chromium.org5c374632012-04-05 16:50:56 +0200322 user_mount_info = __get_user_mount_info(user=user, allow_fail=allow_fail)
323 for mount_info in user_mount_info:
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700324 # Look at each /proc/../mount lines that match mount point for a given
325 # user user/system mount (/home/user/.... /home/root/...)
326
327 # We should have at least 3 arguments (source, mount, type of mount)
328 if len(mount_info) < 3:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200329 return False
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700330
331 device_regex = None
332 for fs_regex in regexes.keys():
333 if re.match(fs_regex, mount_info[2]):
334 device_regex = regexes[fs_regex]
335 break
336
337 if not device_regex:
338 # The thrid argument in not the expectd mount point type.
339 return False
340
341 # Check if the mount source match the device regex: it can be loose,
342 # (anything) or stricter if we expect guest filesystem.
343 if not re.match(device_regex, mount_info[0]):
344 return False
345
346 if re.match(constants.CRYPTOHOME_FS_REGEX_EXT4, mount_info[2]):
347 # We are using ext4 crypto. Check there is an encryption key for
348 # that directory.
349 find_key_cmd_list = ['e4crypt get_policy %s' % (mount_info[1]),
350 'cut -d \' \' -f 2']
351 key = __run_cmd(' | ' .join(find_key_cmd_list))
352 cmd_list = ['keyctl show @s',
353 'grep %s' % (key),
354 'wc -l']
355 out = __run_cmd(' | '.join(cmd_list))
356 if int(out) != 1:
357 return False
barfab@chromium.org5c374632012-04-05 16:50:56 +0200358 return True
Sourav Poddar574bd622010-05-26 14:22:26 +0530359
360
barfab@chromium.org5c374632012-04-05 16:50:56 +0200361def is_guest_vault_mounted(allow_fail=False):
362 """Check whether a vault backed by tmpfs is mounted for the guest user."""
363 return is_vault_mounted(
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400364 user=GUEST_USER_NAME,
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700365 regexes={
366 constants.CRYPTOHOME_FS_REGEX_TMPFS :
367 constants.CRYPTOHOME_DEV_REGEX_GUEST,
368 },
barfab@chromium.org5c374632012-04-05 16:50:56 +0200369 allow_fail=allow_fail)
370
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700371def is_permanent_vault_mounted(user, allow_fail=False):
372 """Check if user is mounted over ecryptfs or ext4 crypto. """
373 return is_vault_mounted(
374 user=user,
375 regexes={
376 constants.CRYPTOHOME_FS_REGEX_ECRYPTFS :
377 constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER_SHADOW,
378 constants.CRYPTOHOME_FS_REGEX_EXT4 :
379 constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER_DEVICE,
380 },
381 allow_fail=allow_fail)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200382
Kazuhiro Inabaa3bf6452017-02-08 11:41:50 +0900383def get_mounted_vault_path(user, allow_fail=False):
384 """Get the path where the decrypted data for the user is located."""
385 return os.path.join(constants.SHADOW_ROOT, get_user_hash(user), 'mount')
Nirnimesh66814492011-06-27 18:00:33 -0700386
387
388def canonicalize(credential):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200389 """Perform basic canonicalization of |email_address|.
Nirnimesh66814492011-06-27 18:00:33 -0700390
barfab@chromium.org5c374632012-04-05 16:50:56 +0200391 Perform basic canonicalization of |email_address|, taking into account that
392 gmail does not consider '.' or caps inside a username to matter. It also
393 ignores everything after a '+'. For example,
394 c.masone+abc@gmail.com == cMaSone@gmail.com, per
Nirnimesh66814492011-06-27 18:00:33 -0700395 http://mail.google.com/support/bin/answer.py?hl=en&ctx=mail&answer=10313
396 """
397 if not credential:
398 return None
399
400 parts = credential.split('@')
401 if len(parts) != 2:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200402 raise error.TestError('Malformed email: ' + credential)
Nirnimesh66814492011-06-27 18:00:33 -0700403
404 (name, domain) = parts
405 name = name.partition('+')[0]
barfab@chromium.org5c374632012-04-05 16:50:56 +0200406 if (domain == constants.SPECIAL_CASE_DOMAIN):
Nirnimesh66814492011-06-27 18:00:33 -0700407 name = name.replace('.', '')
408 return '@'.join([name, domain]).lower()
Elly Jones686c2f42011-10-24 16:45:07 -0400409
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200410
Will Drewryd2fed972013-12-05 16:35:51 -0600411def crash_cryptohomed():
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600412 # Try to kill cryptohomed so we get something to work with.
413 pid = __run_cmd('pgrep cryptohomed')
414 try:
Will Drewry9e440792013-12-11 17:18:35 -0600415 pid = int(pid)
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600416 except ValueError, e: # empty or invalid string
Will Drewry9e440792013-12-11 17:18:35 -0600417 raise error.TestError('Cryptohomed was not running')
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600418 utils.system('kill -ABRT %d' % pid)
419 # CONT just in case cryptohomed had a spurious STOP.
420 utils.system('kill -CONT %d' % pid)
421 utils.poll_for_condition(
422 lambda: utils.system('ps -p %d' % pid,
423 ignore_status=True) != 0,
Will Drewry934d1532014-01-30 16:23:17 -0600424 timeout=180,
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600425 exception=error.TestError(
426 'Timeout waiting for cryptohomed to coredump'))
427
Will Drewryd2fed972013-12-05 16:35:51 -0600428
Dan Spaidbe9789c2017-05-19 15:18:42 +0900429def create_ecryptfs_homedir(user, password):
430 """Creates a new home directory as ecryptfs.
431
432 If a home directory for the user exists already, it will be removed.
433 The resulting home directory will be mounted.
434
435 @param user: Username to create the home directory for.
436 @param password: Password to use when creating the home directory.
437 """
438 unmount_vault(user)
439 remove_vault(user)
440 args = [
441 CRYPTOHOME_CMD,
442 '--action=mount_ex',
443 '--user=%s' % user,
444 '--password=%s' % password,
445 '--key_label=foo',
446 '--ecryptfs',
447 '--create']
448 logging.info(__run_cmd(' '.join(args)))
449 if not is_vault_mounted(user, regexes={
450 constants.CRYPTOHOME_FS_REGEX_ECRYPTFS :
451 constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER_SHADOW
452 }, allow_fail=True):
453 raise ChromiumOSError('Ecryptfs home could not be created')
454
455
456def do_dircrypto_migration(user, password, timeout=600):
457 """Start dircrypto migration for the user.
458
459 @param user: The user to migrate.
460 @param password: The password used to mount the users vault
461 @param timeout: How long in seconds to wait for the migration to finish
462 before failing.
463 """
464 unmount_vault(user)
465 args = [
466 CRYPTOHOME_CMD,
467 '--action=mount_ex',
468 '--to_migrate_from_ecryptfs',
469 '--user=%s' % user,
470 '--password=%s' % password]
471 logging.info(__run_cmd(' '.join(args)))
472 if not __get_mount_info(temporary_mount_path(user), allow_fail=True):
473 raise ChromiumOSError('Failed to mount home for migration')
474 args = [CRYPTOHOME_CMD, '--action=migrate_to_dircrypto', '--user=%s' % user]
475 logging.info(__run_cmd(' '.join(args)))
476 utils.poll_for_condition(
477 lambda: not __get_mount_info(
478 temporary_mount_path(user), allow_fail=True),
479 timeout=timeout,
480 exception=error.TestError(
481 'Timeout waiting for dircrypto migration to finish'))
482
483
Will Drewry9e440792013-12-11 17:18:35 -0600484class CryptohomeProxy(DBusClient):
485 """A DBus proxy client for testing the Cryptohome DBus server.
486 """
487 CRYPTOHOME_BUS_NAME = 'org.chromium.Cryptohome'
488 CRYPTOHOME_OBJECT_PATH = '/org/chromium/Cryptohome'
489 CRYPTOHOME_INTERFACE = 'org.chromium.CryptohomeInterface'
490 ASYNC_CALL_STATUS_SIGNAL = 'AsyncCallStatus'
491 ASYNC_CALL_STATUS_SIGNAL_ARGUMENTS = (
492 'async_id', 'return_status', 'return_code'
493 )
494 DBUS_PROPERTIES_INTERFACE = 'org.freedesktop.DBus.Properties'
495
Chris Masone19e305e2014-03-14 15:13:46 -0700496
Chris Masone64170f82014-03-14 15:47:05 -0700497 def __init__(self, bus_loop=None):
Will Drewry9e440792013-12-11 17:18:35 -0600498 self.main_loop = gobject.MainLoop()
Will Drewry78db9dc2014-04-01 16:34:23 -0500499 if bus_loop is None:
Chris Masone64170f82014-03-14 15:47:05 -0700500 bus_loop = DBusGMainLoop(set_as_default=True)
Will Drewry9e440792013-12-11 17:18:35 -0600501 self.bus = dbus.SystemBus(mainloop=bus_loop)
502 super(CryptohomeProxy, self).__init__(self.main_loop, self.bus,
503 self.CRYPTOHOME_BUS_NAME,
504 self.CRYPTOHOME_OBJECT_PATH)
505 self.iface = dbus.Interface(self.proxy_object,
506 self.CRYPTOHOME_INTERFACE)
507 self.properties = dbus.Interface(self.proxy_object,
508 self.DBUS_PROPERTIES_INTERFACE)
509 self.handle_signal(self.CRYPTOHOME_INTERFACE,
510 self.ASYNC_CALL_STATUS_SIGNAL,
511 self.ASYNC_CALL_STATUS_SIGNAL_ARGUMENTS)
Elly Jones2f0ebba2011-10-27 13:43:20 -0400512
Chris Masone19e305e2014-03-14 15:13:46 -0700513
Will Drewryd2fed972013-12-05 16:35:51 -0600514 # Wrap all proxied calls to catch cryptohomed failures.
515 def __call(self, method, *args):
516 try:
Chris Masonef59d9df2014-03-14 12:05:32 -0700517 return method(*args, timeout=180)
Will Drewryd2fed972013-12-05 16:35:51 -0600518 except dbus.exceptions.DBusException, e:
519 if e.get_dbus_name() == 'org.freedesktop.DBus.Error.NoReply':
520 logging.error('Cryptohome is not responding. Sending ABRT')
521 crash_cryptohomed()
522 raise ChromiumOSError('cryptohomed aborted. Check crashes!')
523 raise e
524
Chris Masone19e305e2014-03-14 15:13:46 -0700525
Will Drewry9e440792013-12-11 17:18:35 -0600526 def __wait_for_specific_signal(self, signal, data):
527 """Wait for the |signal| with matching |data|
528 Returns the resulting dict on success or {} on error.
529 """
Will Drewryc4de5ff2014-02-03 13:26:57 -0600530 # Do not bubble up the timeout here, just return {}.
531 result = {}
532 try:
533 result = self.wait_for_signal(signal)
534 except utils.TimeoutError:
535 return {}
Will Drewry9e440792013-12-11 17:18:35 -0600536 for k in data.keys():
537 if not result.has_key(k) or result[k] != data[k]:
538 return {}
539 return result
540
Chris Masone19e305e2014-03-14 15:13:46 -0700541
Will Drewry9e440792013-12-11 17:18:35 -0600542 # Perform a data-less async call.
543 # TODO(wad) Add __async_data_call.
544 def __async_call(self, method, *args):
Will Drewryfef135a2014-05-23 16:02:14 -0500545 # Clear out any superfluous async call signals.
546 self.clear_signal_content(self.ASYNC_CALL_STATUS_SIGNAL)
Will Drewry9e440792013-12-11 17:18:35 -0600547 out = self.__call(method, *args)
548 logging.debug('Issued call ' + str(method) +
549 ' with async_id ' + str(out))
550 result = {}
551 try:
Will Drewry934d1532014-01-30 16:23:17 -0600552 # __wait_for_specific_signal has a 10s timeout
Will Drewry9e440792013-12-11 17:18:35 -0600553 result = utils.poll_for_condition(
554 lambda: self.__wait_for_specific_signal(
555 self.ASYNC_CALL_STATUS_SIGNAL, {'async_id' : out}),
Will Drewry934d1532014-01-30 16:23:17 -0600556 timeout=180,
Will Drewry9e440792013-12-11 17:18:35 -0600557 desc='matching %s signal' % self.ASYNC_CALL_STATUS_SIGNAL)
558 except utils.TimeoutError, e:
559 logging.error('Cryptohome timed out. Sending ABRT.')
560 crash_cryptohomed()
561 raise ChromiumOSError('cryptohomed aborted. Check crashes!')
562 return result
563
Chris Masone19e305e2014-03-14 15:13:46 -0700564
Will Drewry9e440792013-12-11 17:18:35 -0600565 def mount(self, user, password, create=False, async=True):
Elly Jones2f0ebba2011-10-27 13:43:20 -0400566 """Mounts a cryptohome.
567
568 Returns True if the mount succeeds or False otherwise.
569 TODO(ellyjones): Migrate mount_vault() to use a multi-user-safe
570 heuristic, then remove this method. See <crosbug.com/20778>.
571 """
Will Drewry9e440792013-12-11 17:18:35 -0600572 if async:
573 return self.__async_call(self.iface.AsyncMount, user, password,
574 create, False, [])['return_status']
Will Drewryd2fed972013-12-05 16:35:51 -0600575 out = self.__call(self.iface.Mount, user, password, create, False, [])
Will Drewry9e440792013-12-11 17:18:35 -0600576 # Sync returns (return code, return status)
577 return out[1] if len(out) > 1 else False
Elly Jones2f0ebba2011-10-27 13:43:20 -0400578
Chris Masone19e305e2014-03-14 15:13:46 -0700579
Elly Jones2f0ebba2011-10-27 13:43:20 -0400580 def unmount(self, user):
581 """Unmounts a cryptohome.
582
583 Returns True if the unmount suceeds or false otherwise.
584 TODO(ellyjones): Once there's a per-user unmount method, use it. See
585 <crosbug.com/20778>.
586 """
Will Drewryd2fed972013-12-05 16:35:51 -0600587 return self.__call(self.iface.Unmount)
Elly Jones2f0ebba2011-10-27 13:43:20 -0400588
Chris Masone19e305e2014-03-14 15:13:46 -0700589
Elly Jones2f0ebba2011-10-27 13:43:20 -0400590 def is_mounted(self, user):
591 """Tests whether a user's cryptohome is mounted."""
592 return (utils.is_mountpoint(user_path(user))
593 and utils.is_mountpoint(system_path(user)))
594
Chris Masone19e305e2014-03-14 15:13:46 -0700595
Elly Jones2f0ebba2011-10-27 13:43:20 -0400596 def require_mounted(self, user):
597 """Raises a test failure if a user's cryptohome is not mounted."""
598 utils.require_mountpoint(user_path(user))
599 utils.require_mountpoint(system_path(user))
Elly Jones4458f442012-04-16 15:42:56 -0400600
Chris Masone19e305e2014-03-14 15:13:46 -0700601
Will Drewry9e440792013-12-11 17:18:35 -0600602 def migrate(self, user, oldkey, newkey, async=True):
Elly Jones4458f442012-04-16 15:42:56 -0400603 """Migrates the specified user's cryptohome from one key to another."""
Will Drewry9e440792013-12-11 17:18:35 -0600604 if async:
605 return self.__async_call(self.iface.AsyncMigrateKey,
606 user, oldkey, newkey)['return_status']
Will Drewryd2fed972013-12-05 16:35:51 -0600607 return self.__call(self.iface.MigrateKey, user, oldkey, newkey)
Elly Jones4458f442012-04-16 15:42:56 -0400608
Chris Masone19e305e2014-03-14 15:13:46 -0700609
Will Drewry9e440792013-12-11 17:18:35 -0600610 def remove(self, user, async=True):
611 if async:
612 return self.__async_call(self.iface.AsyncRemove,
613 user)['return_status']
Will Drewryd2fed972013-12-05 16:35:51 -0600614 return self.__call(self.iface.Remove, user)
Chris Masone19e305e2014-03-14 15:13:46 -0700615
616
617 def ensure_clean_cryptohome_for(self, user, password=None):
618 """Ensure a fresh cryptohome exists for user.
619
620 @param user: user who needs a shiny new cryptohome.
621 @param password: if unset, a random password will be used.
622 """
623 if not password:
624 password = ''.join(random.sample(string.ascii_lowercase, 6))
625 self.remove(user)
626 self.mount(user, password, create=True)