blob: cf2756b31553620af9f8447b4bac4da4947f5f70 [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))
Eric Caruso272193b2018-02-05 14:02:11 -080068 unmount_vault(user)
Chris Masone5d010aa2013-05-06 14:38:42 -070069 remove_vault(user)
70 mount_vault(user, password, create=True)
71
72
Frank Farzand5e36312012-01-13 14:34:03 -080073def get_tpm_status():
74 """Get the TPM status.
75
76 Returns:
77 A TPM status dictionary, for example:
78 { 'Enabled': True,
79 'Owned': True,
80 'Being Owned': False,
81 'Ready': True,
82 'Password': ''
83 }
84 """
85 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_status')
86 status = {}
87 for field in ['Enabled', 'Owned', 'Being Owned', 'Ready']:
88 match = re.search('TPM %s: (true|false)' % field, out)
89 if not match:
90 raise ChromiumOSError('Invalid TPM status: "%s".' % out)
91 status[field] = match.group(1) == 'true'
92 match = re.search('TPM Password: (\w*)', out)
93 status['Password'] = ''
94 if match:
95 status['Password'] = match.group(1)
96 return status
97
98
Kris Rambish82ee1c02014-12-10 17:02:39 -080099def get_tpm_more_status():
100 """Get more of the TPM status.
101
102 Returns:
103 A TPM more status dictionary, for example:
104 { 'dictionary_attack_lockout_in_effect': False,
105 'attestation_prepared': False,
106 'boot_lockbox_finalized': False,
107 'enabled': True,
108 'owned': True,
Kris Rambishbe132592014-12-17 14:26:06 -0800109 'owner_password': ''
Kris Rambish82ee1c02014-12-10 17:02:39 -0800110 'dictionary_attack_counter': 0,
111 'dictionary_attack_lockout_seconds_remaining': 0,
112 'dictionary_attack_threshold': 10,
113 'attestation_enrolled': False,
114 'initialized': True,
115 'verified_boot_measured': False,
116 'install_lockbox_finalized': True
117 }
Kris Rambishbb5258c2014-12-16 16:51:17 -0800118 An empty dictionary is returned if the command is not supported.
Kris Rambish82ee1c02014-12-10 17:02:39 -0800119 """
Kris Rambish82ee1c02014-12-10 17:02:39 -0800120 status = {}
Kris Rambishbb5258c2014-12-16 16:51:17 -0800121 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_more_status | grep :')
122 if out.startswith(UNAVAILABLE_ACTION):
123 # --action=tpm_more_status only exists >= 41.
124 logging.info('Method not supported!')
125 return status
Kris Rambish82ee1c02014-12-10 17:02:39 -0800126 for line in out.splitlines():
127 items = line.strip().split(':')
128 if items[1].strip() == 'false':
129 value = False
130 elif items[1].strip() == 'true':
131 value = True
Kris Rambishbe132592014-12-17 14:26:06 -0800132 elif items[1].strip().isdigit():
Kris Rambish82ee1c02014-12-10 17:02:39 -0800133 value = int(items[1].strip())
Kris Rambishbe132592014-12-17 14:26:06 -0800134 else:
135 value = items[1].strip(' "')
Kris Rambish82ee1c02014-12-10 17:02:39 -0800136 status[items[0]] = value
137 return status
138
139
Mary Ruthven9a0ce562017-05-30 13:01:47 -0700140def get_fwmp(cleared_fwmp=False):
141 """Get the firmware management parameters.
142
143 Args:
144 cleared_fwmp: True if the space should not exist.
145
146 Returns:
147 The dictionary with the FWMP contents, for example:
148 { 'flags': 0xbb41,
149 'developer_key_hash':
150 "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\
151 000\000\000\000\000\000\000\000\000\000\000",
152 }
153 or a dictionary with the Error if the FWMP doesn't exist and
154 cleared_fwmp is True
155 { 'error': 'CRYPTOHOME_ERROR_FIRMWARE_MANAGEMENT_PARAMETERS_INVALID' }
156
157 Raises:
158 ChromiumOSError if any expected field is not found in the cryptohome
159 output. This would typically happen when FWMP state does not match
160 'clreared_fwmp'
161 """
162 out = __run_cmd(CRYPTOHOME_CMD +
163 ' --action=get_firmware_management_parameters')
164
165 if cleared_fwmp:
166 fields = ['error']
167 else:
168 fields = ['flags', 'developer_key_hash']
169
170 status = {}
171 for field in fields:
172 match = re.search('%s: (\S+)\n' % field, out)
173 if not match:
174 raise ChromiumOSError('Invalid FWMP field %s: "%s".' %
175 (field, out))
176 status[field] = match.group(1)
177 return status
178
179
180def set_fwmp(flags, developer_key_hash=None):
181 """Set the firmware management parameter contents.
182
183 Args:
184 developer_key_hash: a string with the developer key hash
185
186 Raises:
187 ChromiumOSError cryptohome cannot set the FWMP contents
188 """
189 cmd = (CRYPTOHOME_CMD +
190 ' --action=set_firmware_management_parameters '
191 '--flags=' + flags)
192 if developer_key_hash:
193 cmd += ' --developer_key_hash=' + developer_key_hash
194
195 out = __run_cmd(cmd)
196 if 'SetFirmwareManagementParameters success' not in out:
197 raise ChromiumOSError('failed to set FWMP: %s' % out)
198
199
Kris Rambish82ee1c02014-12-10 17:02:39 -0800200def is_tpm_lockout_in_effect():
201 """Returns true if the TPM lockout is in effect; false otherwise."""
202 status = get_tpm_more_status()
Christopher Wiley94fd6b32014-12-13 18:52:03 -0800203 return status.get('dictionary_attack_lockout_in_effect', None)
Kris Rambish82ee1c02014-12-10 17:02:39 -0800204
205
David Pursell2a2ef342014-10-17 10:34:56 -0700206def get_login_status():
207 """Query the login status
208
209 Returns:
210 A login status dictionary containing:
211 { 'owner_user_exists': True|False,
212 'boot_lockbox_finalized': True|False
213 }
214 """
215 out = __run_cmd(CRYPTOHOME_CMD + ' --action=get_login_status')
216 status = {}
217 for field in ['owner_user_exists', 'boot_lockbox_finalized']:
218 match = re.search('%s: (true|false)' % field, out)
219 if not match:
220 raise ChromiumOSError('Invalid login status: "%s".' % out)
221 status[field] = match.group(1) == 'true'
222 return status
223
224
Darren Krahn5f880f62012-10-02 15:17:59 -0700225def get_tpm_attestation_status():
226 """Get the TPM attestation status. Works similar to get_tpm_status().
227 """
228 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_attestation_status')
229 status = {}
230 for field in ['Prepared', 'Enrolled']:
231 match = re.search('Attestation %s: (true|false)' % field, out)
232 if not match:
233 raise ChromiumOSError('Invalid attestation status: "%s".' % out)
234 status[field] = match.group(1) == 'true'
235 return status
236
237
Eric Caruso6da07a02018-02-07 16:02:41 -0800238def take_tpm_ownership(wait_for_ownership=True):
Frank Farzand5e36312012-01-13 14:34:03 -0800239 """Take TPM owernship.
240
Eric Caruso6da07a02018-02-07 16:02:41 -0800241 Args:
242 wait_for_ownership: block until TPM is owned if true
Frank Farzand5e36312012-01-13 14:34:03 -0800243 """
244 __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_take_ownership')
Eric Caruso6da07a02018-02-07 16:02:41 -0800245 if wait_for_ownership:
Maksim Ivanov21c967a2018-03-22 21:24:41 +0100246 # Note that waiting for the 'Ready' flag is more correct than waiting
247 # for the 'Owned' flag, as the latter is set by cryptohomed before some
248 # of the ownership tasks are completed.
249 utils.poll_for_condition(
250 lambda: get_tpm_status()['Ready'],
251 timeout=300,
252 exception=error.TestError('Timeout waiting for TPM ownership'))
Frank Farzand5e36312012-01-13 14:34:03 -0800253
254
Darren Krahn0e73e7f2012-09-05 15:35:15 -0700255def verify_ek():
256 """Verify the TPM endorsement key.
257
258 Returns true if EK is valid.
259 """
260 cmd = CRYPTOHOME_CMD + ' --action=tpm_verify_ek'
261 return (utils.system(cmd, ignore_status=True) == 0)
262
263
Sean Oe5d8fd02010-09-30 10:44:44 +0200264def remove_vault(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200265 """Remove the given user's vault from the shadow directory."""
Sean Oe5d8fd02010-09-30 10:44:44 +0200266 logging.debug('user is %s', user)
267 user_hash = get_user_hash(user)
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700268 logging.debug('Removing vault for user %s with hash %s', user, user_hash)
Sean Oe5d8fd02010-09-30 10:44:44 +0200269 cmd = CRYPTOHOME_CMD + ' --action=remove --force --user=%s' % user
270 __run_cmd(cmd)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200271 # Ensure that the vault does not exist.
272 if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
Darren Krahne6c44b92014-03-31 12:11:08 -0700273 raise ChromiumOSError('Cryptohome could not remove the user\'s vault.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200274
275
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200276def remove_all_vaults():
277 """Remove any existing vaults from the shadow directory.
278
279 This function must be run with root privileges.
280 """
barfab@chromium.org5c374632012-04-05 16:50:56 +0200281 for item in os.listdir(constants.SHADOW_ROOT):
282 abs_item = os.path.join(constants.SHADOW_ROOT, item)
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200283 if os.path.isdir(os.path.join(abs_item, 'vault')):
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700284 logging.debug('Removing vault for user with hash %s', item)
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200285 shutil.rmtree(abs_item)
286
287
Eric Caruso22acd672018-02-01 17:55:28 -0800288def mount_vault(user, password, create=False, key_label='bar'):
289 """Mount the given user's vault. Mounts should be created by calling this
290 function with create=True, and can be used afterwards with create=False.
291 Only try to mount existing vaults created with this function.
292
293 """
294 args = [CRYPTOHOME_CMD, '--action=mount_ex', '--user=%s' % user,
Chris Masone3543e512013-11-04 13:09:30 -0800295 '--password=%s' % password, '--async']
Sean Oe5d8fd02010-09-30 10:44:44 +0200296 if create:
Eric Caruso22acd672018-02-01 17:55:28 -0800297 args += ['--key_label=%s' % key_label, '--create']
Chris Masone3543e512013-11-04 13:09:30 -0800298 logging.info(__run_cmd(' '.join(args)))
barfab@chromium.org5c374632012-04-05 16:50:56 +0200299 # Ensure that the vault exists in the shadow directory.
Sean Oe5d8fd02010-09-30 10:44:44 +0200300 user_hash = get_user_hash(user)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200301 if not os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
Alexis Saveryccb16be2017-02-01 16:23:15 -0800302 retry = 0
303 mounted = False
304 while retry < MOUNT_RETRY_COUNT and not mounted:
305 time.sleep(1)
306 logging.info("Retry " + str(retry + 1))
307 __run_cmd(' '.join(args))
308 # TODO: Remove this additional call to get_user_hash(user) when
309 # crbug.com/690994 is fixed
310 user_hash = get_user_hash(user)
311 if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
312 mounted = True
313 retry += 1
314 if not mounted:
315 raise ChromiumOSError('Cryptohome vault not found after mount.')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200316 # Ensure that the vault is mounted.
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700317 if not is_permanent_vault_mounted(user=user, allow_fail=True):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200318 raise ChromiumOSError('Cryptohome created a vault but did not mount.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200319
320
Chris Masone5d010aa2013-05-06 14:38:42 -0700321def mount_guest():
Sergey Poromov533008f2017-10-13 14:07:43 +0200322 """Mount the guest vault."""
Chris Masone3543e512013-11-04 13:09:30 -0800323 args = [CRYPTOHOME_CMD, '--action=mount_guest', '--async']
324 logging.info(__run_cmd(' '.join(args)))
Sergey Poromov533008f2017-10-13 14:07:43 +0200325 # Ensure that the guest vault is mounted.
Chris Masone5d010aa2013-05-06 14:38:42 -0700326 if not is_guest_vault_mounted(allow_fail=True):
Sergey Poromov533008f2017-10-13 14:07:43 +0200327 raise ChromiumOSError('Cryptohome did not mount guest vault.')
Chris Masone5d010aa2013-05-06 14:38:42 -0700328
329
Sean Oe5d8fd02010-09-30 10:44:44 +0200330def test_auth(user, password):
Eric Carusode07cf82018-02-12 15:34:02 -0800331 cmd = [CRYPTOHOME_CMD, '--action=check_key_ex', '--user=%s' % user,
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400332 '--password=%s' % password, '--async']
Eric Carusode07cf82018-02-12 15:34:02 -0800333 out = __run_cmd(' '.join(cmd))
334 logging.info(out)
335 return 'Key authenticated.' in out
Sean Oe5d8fd02010-09-30 10:44:44 +0200336
337
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400338def unmount_vault(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200339 """Unmount the given user's vault.
340
341 Once unmounting for a specific user is supported, the user parameter will
342 name the target user. See crosbug.com/20778.
Elly Jones686c2f42011-10-24 16:45:07 -0400343 """
Chris Masone3543e512013-11-04 13:09:30 -0800344 __run_cmd(CRYPTOHOME_CMD + ' --action=unmount')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200345 # Ensure that the vault is not mounted.
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400346 if is_vault_mounted(user, allow_fail=True):
Sean Oe5d8fd02010-09-30 10:44:44 +0200347 raise ChromiumOSError('Cryptohome did not unmount the user.')
348
349
barfab@chromium.org5c374632012-04-05 16:50:56 +0200350def __get_mount_info(mount_point, allow_fail=False):
351 """Get information about the active mount at a given mount point."""
beeps569f8672013-08-07 10:18:51 -0700352 cryptohomed_path = '/proc/$(pgrep cryptohomed)/mounts'
353 try:
Daniel Erat2ec32792017-01-31 18:26:59 -0700354 logging.debug("Active cryptohome mounts:\n" +
355 utils.system_output('cat %s' % cryptohomed_path))
beeps569f8672013-08-07 10:18:51 -0700356 mount_line = utils.system_output(
357 'grep %s %s' % (mount_point, cryptohomed_path),
358 ignore_status=allow_fail)
359 except Exception as e:
360 logging.error(e)
361 raise ChromiumOSError('Could not get info about cryptohome vault '
362 'through %s. See logs for complete mount-point.'
363 % os.path.dirname(str(mount_point)))
Sourav Poddar574bd622010-05-26 14:22:26 +0530364 return mount_line.split()
365
366
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400367def __get_user_mount_info(user, allow_fail=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200368 """Get information about the active mounts for a given user.
369
370 Returns the active mounts at the user's user and system mount points. If no
371 user is given, the active mount at the shared mount point is returned
372 (regular users have a bind-mount at this mount point for backwards
373 compatibility; the guest user has a mount at this mount point only).
374 """
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400375 return [__get_mount_info(mount_point=user_path(user),
376 allow_fail=allow_fail),
377 __get_mount_info(mount_point=system_path(user),
378 allow_fail=allow_fail)]
Jim Hebertf08f88d2011-04-22 10:33:49 -0700379
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700380def is_vault_mounted(user, regexes=None, allow_fail=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200381 """Check whether a vault is mounted for the given user.
382
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700383 user: If no user is given, the shared mount point is checked, determining
384 whether a vault is mounted for any user.
385 regexes: dictionary of regexes to matches against the mount information.
386 The mount filesystem for the user's user and system mounts point must
387 match one of the keys.
388 The mount source point must match the selected device regex.
389
390 In addition, if mounted over ext4, we check the directory is encrypted.
barfab@chromium.org5c374632012-04-05 16:50:56 +0200391 """
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700392 if regexes is None:
393 regexes = {
394 constants.CRYPTOHOME_FS_REGEX_ANY :
395 constants.CRYPTOHOME_DEV_REGEX_ANY
396 }
barfab@chromium.org5c374632012-04-05 16:50:56 +0200397 user_mount_info = __get_user_mount_info(user=user, allow_fail=allow_fail)
398 for mount_info in user_mount_info:
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700399 # Look at each /proc/../mount lines that match mount point for a given
400 # user user/system mount (/home/user/.... /home/root/...)
401
402 # We should have at least 3 arguments (source, mount, type of mount)
403 if len(mount_info) < 3:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200404 return False
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700405
406 device_regex = None
407 for fs_regex in regexes.keys():
408 if re.match(fs_regex, mount_info[2]):
409 device_regex = regexes[fs_regex]
410 break
411
412 if not device_regex:
413 # The thrid argument in not the expectd mount point type.
414 return False
415
416 # Check if the mount source match the device regex: it can be loose,
417 # (anything) or stricter if we expect guest filesystem.
418 if not re.match(device_regex, mount_info[0]):
419 return False
420
Sergey Poromov533008f2017-10-13 14:07:43 +0200421 if (re.match(constants.CRYPTOHOME_FS_REGEX_EXT4, mount_info[2])
422 and not(re.match(constants.CRYPTOHOME_DEV_REGEX_LOOP_DEVICE,
423 mount_info[0]))):
424 # Ephemeral cryptohome uses ext4 mount from a loop device,
425 # otherwise it should be ext4 crypto. Check there is an encryption
426 # key for that directory.
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700427 find_key_cmd_list = ['e4crypt get_policy %s' % (mount_info[1]),
428 'cut -d \' \' -f 2']
429 key = __run_cmd(' | ' .join(find_key_cmd_list))
430 cmd_list = ['keyctl show @s',
431 'grep %s' % (key),
432 'wc -l']
433 out = __run_cmd(' | '.join(cmd_list))
434 if int(out) != 1:
435 return False
barfab@chromium.org5c374632012-04-05 16:50:56 +0200436 return True
Sourav Poddar574bd622010-05-26 14:22:26 +0530437
438
barfab@chromium.org5c374632012-04-05 16:50:56 +0200439def is_guest_vault_mounted(allow_fail=False):
Sergey Poromov533008f2017-10-13 14:07:43 +0200440 """Check whether a vault is mounted for the guest user.
Sergey Poromovd85dce52017-12-27 11:10:51 +0100441 It should be a mount of an ext4 partition on a loop device
442 or be backed by tmpfs.
Sergey Poromov533008f2017-10-13 14:07:43 +0200443 """
barfab@chromium.org5c374632012-04-05 16:50:56 +0200444 return is_vault_mounted(
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400445 user=GUEST_USER_NAME,
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700446 regexes={
Sergey Poromovd85dce52017-12-27 11:10:51 +0100447 # Remove tmpfs support when it becomes unnecessary as all guest
448 # modes will use ext4 on a loop device.
Sergey Poromov533008f2017-10-13 14:07:43 +0200449 constants.CRYPTOHOME_FS_REGEX_EXT4 :
450 constants.CRYPTOHOME_DEV_REGEX_LOOP_DEVICE,
Sergey Poromovd85dce52017-12-27 11:10:51 +0100451 constants.CRYPTOHOME_FS_REGEX_TMPFS :
452 constants.CRYPTOHOME_DEV_REGEX_GUEST,
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700453 },
barfab@chromium.org5c374632012-04-05 16:50:56 +0200454 allow_fail=allow_fail)
455
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700456def is_permanent_vault_mounted(user, allow_fail=False):
457 """Check if user is mounted over ecryptfs or ext4 crypto. """
458 return is_vault_mounted(
459 user=user,
460 regexes={
461 constants.CRYPTOHOME_FS_REGEX_ECRYPTFS :
462 constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER_SHADOW,
463 constants.CRYPTOHOME_FS_REGEX_EXT4 :
464 constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER_DEVICE,
465 },
466 allow_fail=allow_fail)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200467
Kazuhiro Inabaa3bf6452017-02-08 11:41:50 +0900468def get_mounted_vault_path(user, allow_fail=False):
469 """Get the path where the decrypted data for the user is located."""
470 return os.path.join(constants.SHADOW_ROOT, get_user_hash(user), 'mount')
Nirnimesh66814492011-06-27 18:00:33 -0700471
472
473def canonicalize(credential):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200474 """Perform basic canonicalization of |email_address|.
Nirnimesh66814492011-06-27 18:00:33 -0700475
barfab@chromium.org5c374632012-04-05 16:50:56 +0200476 Perform basic canonicalization of |email_address|, taking into account that
477 gmail does not consider '.' or caps inside a username to matter. It also
478 ignores everything after a '+'. For example,
479 c.masone+abc@gmail.com == cMaSone@gmail.com, per
Nirnimesh66814492011-06-27 18:00:33 -0700480 http://mail.google.com/support/bin/answer.py?hl=en&ctx=mail&answer=10313
481 """
482 if not credential:
483 return None
484
485 parts = credential.split('@')
486 if len(parts) != 2:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200487 raise error.TestError('Malformed email: ' + credential)
Nirnimesh66814492011-06-27 18:00:33 -0700488
489 (name, domain) = parts
490 name = name.partition('+')[0]
barfab@chromium.org5c374632012-04-05 16:50:56 +0200491 if (domain == constants.SPECIAL_CASE_DOMAIN):
Nirnimesh66814492011-06-27 18:00:33 -0700492 name = name.replace('.', '')
493 return '@'.join([name, domain]).lower()
Elly Jones686c2f42011-10-24 16:45:07 -0400494
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200495
Will Drewryd2fed972013-12-05 16:35:51 -0600496def crash_cryptohomed():
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600497 # Try to kill cryptohomed so we get something to work with.
498 pid = __run_cmd('pgrep cryptohomed')
499 try:
Will Drewry9e440792013-12-11 17:18:35 -0600500 pid = int(pid)
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600501 except ValueError, e: # empty or invalid string
Will Drewry9e440792013-12-11 17:18:35 -0600502 raise error.TestError('Cryptohomed was not running')
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600503 utils.system('kill -ABRT %d' % pid)
504 # CONT just in case cryptohomed had a spurious STOP.
505 utils.system('kill -CONT %d' % pid)
506 utils.poll_for_condition(
507 lambda: utils.system('ps -p %d' % pid,
508 ignore_status=True) != 0,
Will Drewry934d1532014-01-30 16:23:17 -0600509 timeout=180,
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600510 exception=error.TestError(
511 'Timeout waiting for cryptohomed to coredump'))
512
Will Drewryd2fed972013-12-05 16:35:51 -0600513
Dan Spaidbe9789c2017-05-19 15:18:42 +0900514def create_ecryptfs_homedir(user, password):
515 """Creates a new home directory as ecryptfs.
516
517 If a home directory for the user exists already, it will be removed.
518 The resulting home directory will be mounted.
519
520 @param user: Username to create the home directory for.
521 @param password: Password to use when creating the home directory.
522 """
523 unmount_vault(user)
524 remove_vault(user)
525 args = [
526 CRYPTOHOME_CMD,
527 '--action=mount_ex',
528 '--user=%s' % user,
529 '--password=%s' % password,
530 '--key_label=foo',
531 '--ecryptfs',
532 '--create']
533 logging.info(__run_cmd(' '.join(args)))
534 if not is_vault_mounted(user, regexes={
535 constants.CRYPTOHOME_FS_REGEX_ECRYPTFS :
536 constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER_SHADOW
537 }, allow_fail=True):
538 raise ChromiumOSError('Ecryptfs home could not be created')
539
540
541def do_dircrypto_migration(user, password, timeout=600):
542 """Start dircrypto migration for the user.
543
544 @param user: The user to migrate.
545 @param password: The password used to mount the users vault
546 @param timeout: How long in seconds to wait for the migration to finish
547 before failing.
548 """
549 unmount_vault(user)
550 args = [
551 CRYPTOHOME_CMD,
552 '--action=mount_ex',
553 '--to_migrate_from_ecryptfs',
554 '--user=%s' % user,
555 '--password=%s' % password]
556 logging.info(__run_cmd(' '.join(args)))
557 if not __get_mount_info(temporary_mount_path(user), allow_fail=True):
558 raise ChromiumOSError('Failed to mount home for migration')
559 args = [CRYPTOHOME_CMD, '--action=migrate_to_dircrypto', '--user=%s' % user]
560 logging.info(__run_cmd(' '.join(args)))
561 utils.poll_for_condition(
562 lambda: not __get_mount_info(
563 temporary_mount_path(user), allow_fail=True),
564 timeout=timeout,
565 exception=error.TestError(
566 'Timeout waiting for dircrypto migration to finish'))
567
568
Eric Caruso014d5ed2018-02-01 16:24:41 -0800569def change_password(user, password, new_password):
570 args = [
571 CRYPTOHOME_CMD,
572 '--action=migrate_key',
573 '--async',
574 '--user=%s' % user,
575 '--old_password=%s' % password,
576 '--password=%s' % new_password]
Eric Caruso43ec57e2018-02-05 15:54:49 -0800577 out = __run_cmd(' '.join(args))
578 logging.info(out)
579 if 'Key migration succeeded.' not in out:
580 raise ChromiumOSError('Key migration failed.')
Eric Caruso014d5ed2018-02-01 16:24:41 -0800581
582
Will Drewry9e440792013-12-11 17:18:35 -0600583class CryptohomeProxy(DBusClient):
584 """A DBus proxy client for testing the Cryptohome DBus server.
585 """
586 CRYPTOHOME_BUS_NAME = 'org.chromium.Cryptohome'
587 CRYPTOHOME_OBJECT_PATH = '/org/chromium/Cryptohome'
588 CRYPTOHOME_INTERFACE = 'org.chromium.CryptohomeInterface'
589 ASYNC_CALL_STATUS_SIGNAL = 'AsyncCallStatus'
590 ASYNC_CALL_STATUS_SIGNAL_ARGUMENTS = (
591 'async_id', 'return_status', 'return_code'
592 )
593 DBUS_PROPERTIES_INTERFACE = 'org.freedesktop.DBus.Properties'
594
Chris Masone19e305e2014-03-14 15:13:46 -0700595
Chris Masone64170f82014-03-14 15:47:05 -0700596 def __init__(self, bus_loop=None):
Will Drewry9e440792013-12-11 17:18:35 -0600597 self.main_loop = gobject.MainLoop()
Will Drewry78db9dc2014-04-01 16:34:23 -0500598 if bus_loop is None:
Chris Masone64170f82014-03-14 15:47:05 -0700599 bus_loop = DBusGMainLoop(set_as_default=True)
Will Drewry9e440792013-12-11 17:18:35 -0600600 self.bus = dbus.SystemBus(mainloop=bus_loop)
601 super(CryptohomeProxy, self).__init__(self.main_loop, self.bus,
602 self.CRYPTOHOME_BUS_NAME,
603 self.CRYPTOHOME_OBJECT_PATH)
604 self.iface = dbus.Interface(self.proxy_object,
605 self.CRYPTOHOME_INTERFACE)
606 self.properties = dbus.Interface(self.proxy_object,
607 self.DBUS_PROPERTIES_INTERFACE)
608 self.handle_signal(self.CRYPTOHOME_INTERFACE,
609 self.ASYNC_CALL_STATUS_SIGNAL,
610 self.ASYNC_CALL_STATUS_SIGNAL_ARGUMENTS)
Elly Jones2f0ebba2011-10-27 13:43:20 -0400611
Chris Masone19e305e2014-03-14 15:13:46 -0700612
Will Drewryd2fed972013-12-05 16:35:51 -0600613 # Wrap all proxied calls to catch cryptohomed failures.
614 def __call(self, method, *args):
615 try:
Chris Masonef59d9df2014-03-14 12:05:32 -0700616 return method(*args, timeout=180)
Will Drewryd2fed972013-12-05 16:35:51 -0600617 except dbus.exceptions.DBusException, e:
618 if e.get_dbus_name() == 'org.freedesktop.DBus.Error.NoReply':
619 logging.error('Cryptohome is not responding. Sending ABRT')
620 crash_cryptohomed()
621 raise ChromiumOSError('cryptohomed aborted. Check crashes!')
622 raise e
623
Chris Masone19e305e2014-03-14 15:13:46 -0700624
Will Drewry9e440792013-12-11 17:18:35 -0600625 def __wait_for_specific_signal(self, signal, data):
626 """Wait for the |signal| with matching |data|
627 Returns the resulting dict on success or {} on error.
628 """
Will Drewryc4de5ff2014-02-03 13:26:57 -0600629 # Do not bubble up the timeout here, just return {}.
630 result = {}
631 try:
632 result = self.wait_for_signal(signal)
633 except utils.TimeoutError:
634 return {}
Will Drewry9e440792013-12-11 17:18:35 -0600635 for k in data.keys():
636 if not result.has_key(k) or result[k] != data[k]:
637 return {}
638 return result
639
Chris Masone19e305e2014-03-14 15:13:46 -0700640
Will Drewry9e440792013-12-11 17:18:35 -0600641 # Perform a data-less async call.
642 # TODO(wad) Add __async_data_call.
643 def __async_call(self, method, *args):
Will Drewryfef135a2014-05-23 16:02:14 -0500644 # Clear out any superfluous async call signals.
645 self.clear_signal_content(self.ASYNC_CALL_STATUS_SIGNAL)
Will Drewry9e440792013-12-11 17:18:35 -0600646 out = self.__call(method, *args)
647 logging.debug('Issued call ' + str(method) +
648 ' with async_id ' + str(out))
649 result = {}
650 try:
Will Drewry934d1532014-01-30 16:23:17 -0600651 # __wait_for_specific_signal has a 10s timeout
Will Drewry9e440792013-12-11 17:18:35 -0600652 result = utils.poll_for_condition(
653 lambda: self.__wait_for_specific_signal(
654 self.ASYNC_CALL_STATUS_SIGNAL, {'async_id' : out}),
Will Drewry934d1532014-01-30 16:23:17 -0600655 timeout=180,
Will Drewry9e440792013-12-11 17:18:35 -0600656 desc='matching %s signal' % self.ASYNC_CALL_STATUS_SIGNAL)
657 except utils.TimeoutError, e:
658 logging.error('Cryptohome timed out. Sending ABRT.')
659 crash_cryptohomed()
660 raise ChromiumOSError('cryptohomed aborted. Check crashes!')
661 return result
662
Chris Masone19e305e2014-03-14 15:13:46 -0700663
Will Drewry9e440792013-12-11 17:18:35 -0600664 def mount(self, user, password, create=False, async=True):
Elly Jones2f0ebba2011-10-27 13:43:20 -0400665 """Mounts a cryptohome.
666
667 Returns True if the mount succeeds or False otherwise.
668 TODO(ellyjones): Migrate mount_vault() to use a multi-user-safe
669 heuristic, then remove this method. See <crosbug.com/20778>.
670 """
Will Drewry9e440792013-12-11 17:18:35 -0600671 if async:
672 return self.__async_call(self.iface.AsyncMount, user, password,
673 create, False, [])['return_status']
Will Drewryd2fed972013-12-05 16:35:51 -0600674 out = self.__call(self.iface.Mount, user, password, create, False, [])
Will Drewry9e440792013-12-11 17:18:35 -0600675 # Sync returns (return code, return status)
676 return out[1] if len(out) > 1 else False
Elly Jones2f0ebba2011-10-27 13:43:20 -0400677
Chris Masone19e305e2014-03-14 15:13:46 -0700678
Elly Jones2f0ebba2011-10-27 13:43:20 -0400679 def unmount(self, user):
680 """Unmounts a cryptohome.
681
682 Returns True if the unmount suceeds or false otherwise.
683 TODO(ellyjones): Once there's a per-user unmount method, use it. See
684 <crosbug.com/20778>.
685 """
Will Drewryd2fed972013-12-05 16:35:51 -0600686 return self.__call(self.iface.Unmount)
Elly Jones2f0ebba2011-10-27 13:43:20 -0400687
Chris Masone19e305e2014-03-14 15:13:46 -0700688
Elly Jones2f0ebba2011-10-27 13:43:20 -0400689 def is_mounted(self, user):
690 """Tests whether a user's cryptohome is mounted."""
691 return (utils.is_mountpoint(user_path(user))
692 and utils.is_mountpoint(system_path(user)))
693
Chris Masone19e305e2014-03-14 15:13:46 -0700694
Elly Jones2f0ebba2011-10-27 13:43:20 -0400695 def require_mounted(self, user):
696 """Raises a test failure if a user's cryptohome is not mounted."""
697 utils.require_mountpoint(user_path(user))
698 utils.require_mountpoint(system_path(user))
Elly Jones4458f442012-04-16 15:42:56 -0400699
Chris Masone19e305e2014-03-14 15:13:46 -0700700
Will Drewry9e440792013-12-11 17:18:35 -0600701 def migrate(self, user, oldkey, newkey, async=True):
Elly Jones4458f442012-04-16 15:42:56 -0400702 """Migrates the specified user's cryptohome from one key to another."""
Will Drewry9e440792013-12-11 17:18:35 -0600703 if async:
704 return self.__async_call(self.iface.AsyncMigrateKey,
705 user, oldkey, newkey)['return_status']
Will Drewryd2fed972013-12-05 16:35:51 -0600706 return self.__call(self.iface.MigrateKey, user, oldkey, newkey)
Elly Jones4458f442012-04-16 15:42:56 -0400707
Chris Masone19e305e2014-03-14 15:13:46 -0700708
Will Drewry9e440792013-12-11 17:18:35 -0600709 def remove(self, user, async=True):
710 if async:
711 return self.__async_call(self.iface.AsyncRemove,
712 user)['return_status']
Will Drewryd2fed972013-12-05 16:35:51 -0600713 return self.__call(self.iface.Remove, user)
Chris Masone19e305e2014-03-14 15:13:46 -0700714
715
716 def ensure_clean_cryptohome_for(self, user, password=None):
717 """Ensure a fresh cryptohome exists for user.
718
719 @param user: user who needs a shiny new cryptohome.
720 @param password: if unset, a random password will be used.
721 """
722 if not password:
723 password = ''.join(random.sample(string.ascii_lowercase, 6))
724 self.remove(user)
725 self.mount(user, password, create=True)
Roman Sorokina45273e2017-12-20 12:03:27 +0100726
727 def lock_install_attributes(self, attrs):
728 """Set and lock install attributes for the device.
729
730 @param attrs: dict of install attributes.
731 """
Roman Sorokin0a228d12018-01-23 12:36:45 +0100732 take_tpm_ownership()
Roman Sorokina45273e2017-12-20 12:03:27 +0100733 for key, value in attrs.items():
734 if not self.__call(self.iface.InstallAttributesSet, key,
735 dbus.ByteArray(value + '\0')):
736 return False
737 return self.__call(self.iface.InstallAttributesFinalize)