blob: 3da0fb34c690e00f81d31c1e87c9180ed59f5acf [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:
246 __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_wait_ownership')
Frank Farzand5e36312012-01-13 14:34:03 -0800247
248
Darren Krahn0e73e7f2012-09-05 15:35:15 -0700249def verify_ek():
250 """Verify the TPM endorsement key.
251
252 Returns true if EK is valid.
253 """
254 cmd = CRYPTOHOME_CMD + ' --action=tpm_verify_ek'
255 return (utils.system(cmd, ignore_status=True) == 0)
256
257
Sean Oe5d8fd02010-09-30 10:44:44 +0200258def remove_vault(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200259 """Remove the given user's vault from the shadow directory."""
Sean Oe5d8fd02010-09-30 10:44:44 +0200260 logging.debug('user is %s', user)
261 user_hash = get_user_hash(user)
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700262 logging.debug('Removing vault for user %s with hash %s', user, user_hash)
Sean Oe5d8fd02010-09-30 10:44:44 +0200263 cmd = CRYPTOHOME_CMD + ' --action=remove --force --user=%s' % user
264 __run_cmd(cmd)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200265 # Ensure that the vault does not exist.
266 if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
Darren Krahne6c44b92014-03-31 12:11:08 -0700267 raise ChromiumOSError('Cryptohome could not remove the user\'s vault.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200268
269
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200270def remove_all_vaults():
271 """Remove any existing vaults from the shadow directory.
272
273 This function must be run with root privileges.
274 """
barfab@chromium.org5c374632012-04-05 16:50:56 +0200275 for item in os.listdir(constants.SHADOW_ROOT):
276 abs_item = os.path.join(constants.SHADOW_ROOT, item)
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200277 if os.path.isdir(os.path.join(abs_item, 'vault')):
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700278 logging.debug('Removing vault for user with hash %s', item)
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200279 shutil.rmtree(abs_item)
280
281
Eric Caruso22acd672018-02-01 17:55:28 -0800282def mount_vault(user, password, create=False, key_label='bar'):
283 """Mount the given user's vault. Mounts should be created by calling this
284 function with create=True, and can be used afterwards with create=False.
285 Only try to mount existing vaults created with this function.
286
287 """
288 args = [CRYPTOHOME_CMD, '--action=mount_ex', '--user=%s' % user,
Chris Masone3543e512013-11-04 13:09:30 -0800289 '--password=%s' % password, '--async']
Sean Oe5d8fd02010-09-30 10:44:44 +0200290 if create:
Eric Caruso22acd672018-02-01 17:55:28 -0800291 args += ['--key_label=%s' % key_label, '--create']
Chris Masone3543e512013-11-04 13:09:30 -0800292 logging.info(__run_cmd(' '.join(args)))
barfab@chromium.org5c374632012-04-05 16:50:56 +0200293 # Ensure that the vault exists in the shadow directory.
Sean Oe5d8fd02010-09-30 10:44:44 +0200294 user_hash = get_user_hash(user)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200295 if not os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
Alexis Saveryccb16be2017-02-01 16:23:15 -0800296 retry = 0
297 mounted = False
298 while retry < MOUNT_RETRY_COUNT and not mounted:
299 time.sleep(1)
300 logging.info("Retry " + str(retry + 1))
301 __run_cmd(' '.join(args))
302 # TODO: Remove this additional call to get_user_hash(user) when
303 # crbug.com/690994 is fixed
304 user_hash = get_user_hash(user)
305 if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
306 mounted = True
307 retry += 1
308 if not mounted:
309 raise ChromiumOSError('Cryptohome vault not found after mount.')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200310 # Ensure that the vault is mounted.
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700311 if not is_permanent_vault_mounted(user=user, allow_fail=True):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200312 raise ChromiumOSError('Cryptohome created a vault but did not mount.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200313
314
Chris Masone5d010aa2013-05-06 14:38:42 -0700315def mount_guest():
Sergey Poromov533008f2017-10-13 14:07:43 +0200316 """Mount the guest vault."""
Chris Masone3543e512013-11-04 13:09:30 -0800317 args = [CRYPTOHOME_CMD, '--action=mount_guest', '--async']
318 logging.info(__run_cmd(' '.join(args)))
Sergey Poromov533008f2017-10-13 14:07:43 +0200319 # Ensure that the guest vault is mounted.
Chris Masone5d010aa2013-05-06 14:38:42 -0700320 if not is_guest_vault_mounted(allow_fail=True):
Sergey Poromov533008f2017-10-13 14:07:43 +0200321 raise ChromiumOSError('Cryptohome did not mount guest vault.')
Chris Masone5d010aa2013-05-06 14:38:42 -0700322
323
Sean Oe5d8fd02010-09-30 10:44:44 +0200324def test_auth(user, password):
Eric Carusode07cf82018-02-12 15:34:02 -0800325 cmd = [CRYPTOHOME_CMD, '--action=check_key_ex', '--user=%s' % user,
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400326 '--password=%s' % password, '--async']
Eric Carusode07cf82018-02-12 15:34:02 -0800327 out = __run_cmd(' '.join(cmd))
328 logging.info(out)
329 return 'Key authenticated.' in out
Sean Oe5d8fd02010-09-30 10:44:44 +0200330
331
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400332def unmount_vault(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200333 """Unmount the given user's vault.
334
335 Once unmounting for a specific user is supported, the user parameter will
336 name the target user. See crosbug.com/20778.
Elly Jones686c2f42011-10-24 16:45:07 -0400337 """
Chris Masone3543e512013-11-04 13:09:30 -0800338 __run_cmd(CRYPTOHOME_CMD + ' --action=unmount')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200339 # Ensure that the vault is not mounted.
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400340 if is_vault_mounted(user, allow_fail=True):
Sean Oe5d8fd02010-09-30 10:44:44 +0200341 raise ChromiumOSError('Cryptohome did not unmount the user.')
342
343
barfab@chromium.org5c374632012-04-05 16:50:56 +0200344def __get_mount_info(mount_point, allow_fail=False):
345 """Get information about the active mount at a given mount point."""
beeps569f8672013-08-07 10:18:51 -0700346 cryptohomed_path = '/proc/$(pgrep cryptohomed)/mounts'
347 try:
Daniel Erat2ec32792017-01-31 18:26:59 -0700348 logging.debug("Active cryptohome mounts:\n" +
349 utils.system_output('cat %s' % cryptohomed_path))
beeps569f8672013-08-07 10:18:51 -0700350 mount_line = utils.system_output(
351 'grep %s %s' % (mount_point, cryptohomed_path),
352 ignore_status=allow_fail)
353 except Exception as e:
354 logging.error(e)
355 raise ChromiumOSError('Could not get info about cryptohome vault '
356 'through %s. See logs for complete mount-point.'
357 % os.path.dirname(str(mount_point)))
Sourav Poddar574bd622010-05-26 14:22:26 +0530358 return mount_line.split()
359
360
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400361def __get_user_mount_info(user, allow_fail=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200362 """Get information about the active mounts for a given user.
363
364 Returns the active mounts at the user's user and system mount points. If no
365 user is given, the active mount at the shared mount point is returned
366 (regular users have a bind-mount at this mount point for backwards
367 compatibility; the guest user has a mount at this mount point only).
368 """
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400369 return [__get_mount_info(mount_point=user_path(user),
370 allow_fail=allow_fail),
371 __get_mount_info(mount_point=system_path(user),
372 allow_fail=allow_fail)]
Jim Hebertf08f88d2011-04-22 10:33:49 -0700373
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700374def is_vault_mounted(user, regexes=None, allow_fail=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200375 """Check whether a vault is mounted for the given user.
376
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700377 user: If no user is given, the shared mount point is checked, determining
378 whether a vault is mounted for any user.
379 regexes: dictionary of regexes to matches against the mount information.
380 The mount filesystem for the user's user and system mounts point must
381 match one of the keys.
382 The mount source point must match the selected device regex.
383
384 In addition, if mounted over ext4, we check the directory is encrypted.
barfab@chromium.org5c374632012-04-05 16:50:56 +0200385 """
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700386 if regexes is None:
387 regexes = {
388 constants.CRYPTOHOME_FS_REGEX_ANY :
389 constants.CRYPTOHOME_DEV_REGEX_ANY
390 }
barfab@chromium.org5c374632012-04-05 16:50:56 +0200391 user_mount_info = __get_user_mount_info(user=user, allow_fail=allow_fail)
392 for mount_info in user_mount_info:
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700393 # Look at each /proc/../mount lines that match mount point for a given
394 # user user/system mount (/home/user/.... /home/root/...)
395
396 # We should have at least 3 arguments (source, mount, type of mount)
397 if len(mount_info) < 3:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200398 return False
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700399
400 device_regex = None
401 for fs_regex in regexes.keys():
402 if re.match(fs_regex, mount_info[2]):
403 device_regex = regexes[fs_regex]
404 break
405
406 if not device_regex:
407 # The thrid argument in not the expectd mount point type.
408 return False
409
410 # Check if the mount source match the device regex: it can be loose,
411 # (anything) or stricter if we expect guest filesystem.
412 if not re.match(device_regex, mount_info[0]):
413 return False
414
Sergey Poromov533008f2017-10-13 14:07:43 +0200415 if (re.match(constants.CRYPTOHOME_FS_REGEX_EXT4, mount_info[2])
416 and not(re.match(constants.CRYPTOHOME_DEV_REGEX_LOOP_DEVICE,
417 mount_info[0]))):
418 # Ephemeral cryptohome uses ext4 mount from a loop device,
419 # otherwise it should be ext4 crypto. Check there is an encryption
420 # key for that directory.
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700421 find_key_cmd_list = ['e4crypt get_policy %s' % (mount_info[1]),
422 'cut -d \' \' -f 2']
423 key = __run_cmd(' | ' .join(find_key_cmd_list))
424 cmd_list = ['keyctl show @s',
425 'grep %s' % (key),
426 'wc -l']
427 out = __run_cmd(' | '.join(cmd_list))
428 if int(out) != 1:
429 return False
barfab@chromium.org5c374632012-04-05 16:50:56 +0200430 return True
Sourav Poddar574bd622010-05-26 14:22:26 +0530431
432
barfab@chromium.org5c374632012-04-05 16:50:56 +0200433def is_guest_vault_mounted(allow_fail=False):
Sergey Poromov533008f2017-10-13 14:07:43 +0200434 """Check whether a vault is mounted for the guest user.
Sergey Poromovd85dce52017-12-27 11:10:51 +0100435 It should be a mount of an ext4 partition on a loop device
436 or be backed by tmpfs.
Sergey Poromov533008f2017-10-13 14:07:43 +0200437 """
barfab@chromium.org5c374632012-04-05 16:50:56 +0200438 return is_vault_mounted(
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400439 user=GUEST_USER_NAME,
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700440 regexes={
Sergey Poromovd85dce52017-12-27 11:10:51 +0100441 # Remove tmpfs support when it becomes unnecessary as all guest
442 # modes will use ext4 on a loop device.
Sergey Poromov533008f2017-10-13 14:07:43 +0200443 constants.CRYPTOHOME_FS_REGEX_EXT4 :
444 constants.CRYPTOHOME_DEV_REGEX_LOOP_DEVICE,
Sergey Poromovd85dce52017-12-27 11:10:51 +0100445 constants.CRYPTOHOME_FS_REGEX_TMPFS :
446 constants.CRYPTOHOME_DEV_REGEX_GUEST,
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700447 },
barfab@chromium.org5c374632012-04-05 16:50:56 +0200448 allow_fail=allow_fail)
449
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700450def is_permanent_vault_mounted(user, allow_fail=False):
451 """Check if user is mounted over ecryptfs or ext4 crypto. """
452 return is_vault_mounted(
453 user=user,
454 regexes={
455 constants.CRYPTOHOME_FS_REGEX_ECRYPTFS :
456 constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER_SHADOW,
457 constants.CRYPTOHOME_FS_REGEX_EXT4 :
458 constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER_DEVICE,
459 },
460 allow_fail=allow_fail)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200461
Kazuhiro Inabaa3bf6452017-02-08 11:41:50 +0900462def get_mounted_vault_path(user, allow_fail=False):
463 """Get the path where the decrypted data for the user is located."""
464 return os.path.join(constants.SHADOW_ROOT, get_user_hash(user), 'mount')
Nirnimesh66814492011-06-27 18:00:33 -0700465
466
467def canonicalize(credential):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200468 """Perform basic canonicalization of |email_address|.
Nirnimesh66814492011-06-27 18:00:33 -0700469
barfab@chromium.org5c374632012-04-05 16:50:56 +0200470 Perform basic canonicalization of |email_address|, taking into account that
471 gmail does not consider '.' or caps inside a username to matter. It also
472 ignores everything after a '+'. For example,
473 c.masone+abc@gmail.com == cMaSone@gmail.com, per
Nirnimesh66814492011-06-27 18:00:33 -0700474 http://mail.google.com/support/bin/answer.py?hl=en&ctx=mail&answer=10313
475 """
476 if not credential:
477 return None
478
479 parts = credential.split('@')
480 if len(parts) != 2:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200481 raise error.TestError('Malformed email: ' + credential)
Nirnimesh66814492011-06-27 18:00:33 -0700482
483 (name, domain) = parts
484 name = name.partition('+')[0]
barfab@chromium.org5c374632012-04-05 16:50:56 +0200485 if (domain == constants.SPECIAL_CASE_DOMAIN):
Nirnimesh66814492011-06-27 18:00:33 -0700486 name = name.replace('.', '')
487 return '@'.join([name, domain]).lower()
Elly Jones686c2f42011-10-24 16:45:07 -0400488
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200489
Will Drewryd2fed972013-12-05 16:35:51 -0600490def crash_cryptohomed():
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600491 # Try to kill cryptohomed so we get something to work with.
492 pid = __run_cmd('pgrep cryptohomed')
493 try:
Will Drewry9e440792013-12-11 17:18:35 -0600494 pid = int(pid)
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600495 except ValueError, e: # empty or invalid string
Will Drewry9e440792013-12-11 17:18:35 -0600496 raise error.TestError('Cryptohomed was not running')
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600497 utils.system('kill -ABRT %d' % pid)
498 # CONT just in case cryptohomed had a spurious STOP.
499 utils.system('kill -CONT %d' % pid)
500 utils.poll_for_condition(
501 lambda: utils.system('ps -p %d' % pid,
502 ignore_status=True) != 0,
Will Drewry934d1532014-01-30 16:23:17 -0600503 timeout=180,
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600504 exception=error.TestError(
505 'Timeout waiting for cryptohomed to coredump'))
506
Will Drewryd2fed972013-12-05 16:35:51 -0600507
Dan Spaidbe9789c2017-05-19 15:18:42 +0900508def create_ecryptfs_homedir(user, password):
509 """Creates a new home directory as ecryptfs.
510
511 If a home directory for the user exists already, it will be removed.
512 The resulting home directory will be mounted.
513
514 @param user: Username to create the home directory for.
515 @param password: Password to use when creating the home directory.
516 """
517 unmount_vault(user)
518 remove_vault(user)
519 args = [
520 CRYPTOHOME_CMD,
521 '--action=mount_ex',
522 '--user=%s' % user,
523 '--password=%s' % password,
524 '--key_label=foo',
525 '--ecryptfs',
526 '--create']
527 logging.info(__run_cmd(' '.join(args)))
528 if not is_vault_mounted(user, regexes={
529 constants.CRYPTOHOME_FS_REGEX_ECRYPTFS :
530 constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER_SHADOW
531 }, allow_fail=True):
532 raise ChromiumOSError('Ecryptfs home could not be created')
533
534
535def do_dircrypto_migration(user, password, timeout=600):
536 """Start dircrypto migration for the user.
537
538 @param user: The user to migrate.
539 @param password: The password used to mount the users vault
540 @param timeout: How long in seconds to wait for the migration to finish
541 before failing.
542 """
543 unmount_vault(user)
544 args = [
545 CRYPTOHOME_CMD,
546 '--action=mount_ex',
547 '--to_migrate_from_ecryptfs',
548 '--user=%s' % user,
549 '--password=%s' % password]
550 logging.info(__run_cmd(' '.join(args)))
551 if not __get_mount_info(temporary_mount_path(user), allow_fail=True):
552 raise ChromiumOSError('Failed to mount home for migration')
553 args = [CRYPTOHOME_CMD, '--action=migrate_to_dircrypto', '--user=%s' % user]
554 logging.info(__run_cmd(' '.join(args)))
555 utils.poll_for_condition(
556 lambda: not __get_mount_info(
557 temporary_mount_path(user), allow_fail=True),
558 timeout=timeout,
559 exception=error.TestError(
560 'Timeout waiting for dircrypto migration to finish'))
561
562
Eric Caruso014d5ed2018-02-01 16:24:41 -0800563def change_password(user, password, new_password):
564 args = [
565 CRYPTOHOME_CMD,
566 '--action=migrate_key',
567 '--async',
568 '--user=%s' % user,
569 '--old_password=%s' % password,
570 '--password=%s' % new_password]
Eric Caruso43ec57e2018-02-05 15:54:49 -0800571 out = __run_cmd(' '.join(args))
572 logging.info(out)
573 if 'Key migration succeeded.' not in out:
574 raise ChromiumOSError('Key migration failed.')
Eric Caruso014d5ed2018-02-01 16:24:41 -0800575
576
Will Drewry9e440792013-12-11 17:18:35 -0600577class CryptohomeProxy(DBusClient):
578 """A DBus proxy client for testing the Cryptohome DBus server.
579 """
580 CRYPTOHOME_BUS_NAME = 'org.chromium.Cryptohome'
581 CRYPTOHOME_OBJECT_PATH = '/org/chromium/Cryptohome'
582 CRYPTOHOME_INTERFACE = 'org.chromium.CryptohomeInterface'
583 ASYNC_CALL_STATUS_SIGNAL = 'AsyncCallStatus'
584 ASYNC_CALL_STATUS_SIGNAL_ARGUMENTS = (
585 'async_id', 'return_status', 'return_code'
586 )
587 DBUS_PROPERTIES_INTERFACE = 'org.freedesktop.DBus.Properties'
588
Chris Masone19e305e2014-03-14 15:13:46 -0700589
Chris Masone64170f82014-03-14 15:47:05 -0700590 def __init__(self, bus_loop=None):
Will Drewry9e440792013-12-11 17:18:35 -0600591 self.main_loop = gobject.MainLoop()
Will Drewry78db9dc2014-04-01 16:34:23 -0500592 if bus_loop is None:
Chris Masone64170f82014-03-14 15:47:05 -0700593 bus_loop = DBusGMainLoop(set_as_default=True)
Will Drewry9e440792013-12-11 17:18:35 -0600594 self.bus = dbus.SystemBus(mainloop=bus_loop)
595 super(CryptohomeProxy, self).__init__(self.main_loop, self.bus,
596 self.CRYPTOHOME_BUS_NAME,
597 self.CRYPTOHOME_OBJECT_PATH)
598 self.iface = dbus.Interface(self.proxy_object,
599 self.CRYPTOHOME_INTERFACE)
600 self.properties = dbus.Interface(self.proxy_object,
601 self.DBUS_PROPERTIES_INTERFACE)
602 self.handle_signal(self.CRYPTOHOME_INTERFACE,
603 self.ASYNC_CALL_STATUS_SIGNAL,
604 self.ASYNC_CALL_STATUS_SIGNAL_ARGUMENTS)
Elly Jones2f0ebba2011-10-27 13:43:20 -0400605
Chris Masone19e305e2014-03-14 15:13:46 -0700606
Will Drewryd2fed972013-12-05 16:35:51 -0600607 # Wrap all proxied calls to catch cryptohomed failures.
608 def __call(self, method, *args):
609 try:
Chris Masonef59d9df2014-03-14 12:05:32 -0700610 return method(*args, timeout=180)
Will Drewryd2fed972013-12-05 16:35:51 -0600611 except dbus.exceptions.DBusException, e:
612 if e.get_dbus_name() == 'org.freedesktop.DBus.Error.NoReply':
613 logging.error('Cryptohome is not responding. Sending ABRT')
614 crash_cryptohomed()
615 raise ChromiumOSError('cryptohomed aborted. Check crashes!')
616 raise e
617
Chris Masone19e305e2014-03-14 15:13:46 -0700618
Will Drewry9e440792013-12-11 17:18:35 -0600619 def __wait_for_specific_signal(self, signal, data):
620 """Wait for the |signal| with matching |data|
621 Returns the resulting dict on success or {} on error.
622 """
Will Drewryc4de5ff2014-02-03 13:26:57 -0600623 # Do not bubble up the timeout here, just return {}.
624 result = {}
625 try:
626 result = self.wait_for_signal(signal)
627 except utils.TimeoutError:
628 return {}
Will Drewry9e440792013-12-11 17:18:35 -0600629 for k in data.keys():
630 if not result.has_key(k) or result[k] != data[k]:
631 return {}
632 return result
633
Chris Masone19e305e2014-03-14 15:13:46 -0700634
Will Drewry9e440792013-12-11 17:18:35 -0600635 # Perform a data-less async call.
636 # TODO(wad) Add __async_data_call.
637 def __async_call(self, method, *args):
Will Drewryfef135a2014-05-23 16:02:14 -0500638 # Clear out any superfluous async call signals.
639 self.clear_signal_content(self.ASYNC_CALL_STATUS_SIGNAL)
Will Drewry9e440792013-12-11 17:18:35 -0600640 out = self.__call(method, *args)
641 logging.debug('Issued call ' + str(method) +
642 ' with async_id ' + str(out))
643 result = {}
644 try:
Will Drewry934d1532014-01-30 16:23:17 -0600645 # __wait_for_specific_signal has a 10s timeout
Will Drewry9e440792013-12-11 17:18:35 -0600646 result = utils.poll_for_condition(
647 lambda: self.__wait_for_specific_signal(
648 self.ASYNC_CALL_STATUS_SIGNAL, {'async_id' : out}),
Will Drewry934d1532014-01-30 16:23:17 -0600649 timeout=180,
Will Drewry9e440792013-12-11 17:18:35 -0600650 desc='matching %s signal' % self.ASYNC_CALL_STATUS_SIGNAL)
651 except utils.TimeoutError, e:
652 logging.error('Cryptohome timed out. Sending ABRT.')
653 crash_cryptohomed()
654 raise ChromiumOSError('cryptohomed aborted. Check crashes!')
655 return result
656
Chris Masone19e305e2014-03-14 15:13:46 -0700657
Will Drewry9e440792013-12-11 17:18:35 -0600658 def mount(self, user, password, create=False, async=True):
Elly Jones2f0ebba2011-10-27 13:43:20 -0400659 """Mounts a cryptohome.
660
661 Returns True if the mount succeeds or False otherwise.
662 TODO(ellyjones): Migrate mount_vault() to use a multi-user-safe
663 heuristic, then remove this method. See <crosbug.com/20778>.
664 """
Will Drewry9e440792013-12-11 17:18:35 -0600665 if async:
666 return self.__async_call(self.iface.AsyncMount, user, password,
667 create, False, [])['return_status']
Will Drewryd2fed972013-12-05 16:35:51 -0600668 out = self.__call(self.iface.Mount, user, password, create, False, [])
Will Drewry9e440792013-12-11 17:18:35 -0600669 # Sync returns (return code, return status)
670 return out[1] if len(out) > 1 else False
Elly Jones2f0ebba2011-10-27 13:43:20 -0400671
Chris Masone19e305e2014-03-14 15:13:46 -0700672
Elly Jones2f0ebba2011-10-27 13:43:20 -0400673 def unmount(self, user):
674 """Unmounts a cryptohome.
675
676 Returns True if the unmount suceeds or false otherwise.
677 TODO(ellyjones): Once there's a per-user unmount method, use it. See
678 <crosbug.com/20778>.
679 """
Will Drewryd2fed972013-12-05 16:35:51 -0600680 return self.__call(self.iface.Unmount)
Elly Jones2f0ebba2011-10-27 13:43:20 -0400681
Chris Masone19e305e2014-03-14 15:13:46 -0700682
Elly Jones2f0ebba2011-10-27 13:43:20 -0400683 def is_mounted(self, user):
684 """Tests whether a user's cryptohome is mounted."""
685 return (utils.is_mountpoint(user_path(user))
686 and utils.is_mountpoint(system_path(user)))
687
Chris Masone19e305e2014-03-14 15:13:46 -0700688
Elly Jones2f0ebba2011-10-27 13:43:20 -0400689 def require_mounted(self, user):
690 """Raises a test failure if a user's cryptohome is not mounted."""
691 utils.require_mountpoint(user_path(user))
692 utils.require_mountpoint(system_path(user))
Elly Jones4458f442012-04-16 15:42:56 -0400693
Chris Masone19e305e2014-03-14 15:13:46 -0700694
Will Drewry9e440792013-12-11 17:18:35 -0600695 def migrate(self, user, oldkey, newkey, async=True):
Elly Jones4458f442012-04-16 15:42:56 -0400696 """Migrates the specified user's cryptohome from one key to another."""
Will Drewry9e440792013-12-11 17:18:35 -0600697 if async:
698 return self.__async_call(self.iface.AsyncMigrateKey,
699 user, oldkey, newkey)['return_status']
Will Drewryd2fed972013-12-05 16:35:51 -0600700 return self.__call(self.iface.MigrateKey, user, oldkey, newkey)
Elly Jones4458f442012-04-16 15:42:56 -0400701
Chris Masone19e305e2014-03-14 15:13:46 -0700702
Will Drewry9e440792013-12-11 17:18:35 -0600703 def remove(self, user, async=True):
704 if async:
705 return self.__async_call(self.iface.AsyncRemove,
706 user)['return_status']
Will Drewryd2fed972013-12-05 16:35:51 -0600707 return self.__call(self.iface.Remove, user)
Chris Masone19e305e2014-03-14 15:13:46 -0700708
709
710 def ensure_clean_cryptohome_for(self, user, password=None):
711 """Ensure a fresh cryptohome exists for user.
712
713 @param user: user who needs a shiny new cryptohome.
714 @param password: if unset, a random password will be used.
715 """
716 if not password:
717 password = ''.join(random.sample(string.ascii_lowercase, 6))
718 self.remove(user)
719 self.mount(user, password, create=True)
Roman Sorokina45273e2017-12-20 12:03:27 +0100720
721 def lock_install_attributes(self, attrs):
722 """Set and lock install attributes for the device.
723
724 @param attrs: dict of install attributes.
725 """
Roman Sorokin0a228d12018-01-23 12:36:45 +0100726 take_tpm_ownership()
Roman Sorokina45273e2017-12-20 12:03:27 +0100727 for key, value in attrs.items():
728 if not self.__call(self.iface.InstallAttributesSet, key,
729 dbus.ByteArray(value + '\0')):
730 return False
731 return self.__call(self.iface.InstallAttributesFinalize)