blob: 904046d595e083f67c12caf34b81f3a3c788589d [file] [log] [blame]
Derek Beckett1091ed12020-10-19 10:47:16 -07001# Lint as: python2, python3
Frank Farzand5e36312012-01-13 14:34:03 -08002# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Chris Masone5e06f182010-03-23 08:29:52 -07003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Derek Beckett1091ed12020-10-19 10:47:16 -07006from __future__ import absolute_import
7from __future__ import division
8from __future__ import print_function
9
Eric Caruso8dc40982018-03-20 17:05:19 -070010import dbus, gobject, logging, os, random, re, shutil, string, sys, time
Will Drewry9e440792013-12-11 17:18:35 -060011from dbus.mainloop.glib import DBusGMainLoop
Derek Beckett1091ed12020-10-19 10:47:16 -070012from six.moves import map
barfab@chromium.orgb6d29932012-04-11 09:46:43 +020013
Derek Beckett1091ed12020-10-19 10:47:16 -070014import common
15
16from autotest_lib.client.cros import constants
barfab@chromium.org5c374632012-04-05 16:50:56 +020017from autotest_lib.client.bin import utils
Chris Masone5e06f182010-03-23 08:29:52 -070018from autotest_lib.client.common_lib import error
Will Drewry9e440792013-12-11 17:18:35 -060019from autotest_lib.client.cros.cros_disks import DBusClient
Eric Lic4d8f4a2010-12-10 09:49:23 -080020
Sean Oe5d8fd02010-09-30 10:44:44 +020021CRYPTOHOME_CMD = '/usr/sbin/cryptohome'
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040022GUEST_USER_NAME = '$guest'
Kris Rambishbb5258c2014-12-16 16:51:17 -080023UNAVAILABLE_ACTION = 'Unknown action or no action given.'
Alexis Saveryccb16be2017-02-01 16:23:15 -080024MOUNT_RETRY_COUNT = 20
Dan Spaidbe9789c2017-05-19 15:18:42 +090025TEMP_MOUNT_PATTERN = '/home/.shadow/%s/temporary_mount'
26VAULT_PATH_PATTERN = '/home/.shadow/%s/vault'
Sean Oe5d8fd02010-09-30 10:44:44 +020027
Eric Caruso8dc40982018-03-20 17:05:19 -070028DBUS_PROTOS_DEP = 'dbus_protos'
29
30
Brian Norriscd51d8d2020-12-17 00:53:41 +000031class ChromiumOSError(error.TestError):
32 """Generic error for ChromiumOS-specific exceptions."""
33 pass
34
35def __run_cmd(cmd):
36 return utils.system_output(cmd + ' 2>&1', retain_output=True,
37 ignore_status=True).strip()
38
Sean Oe5d8fd02010-09-30 10:44:44 +020039def get_user_hash(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +020040 """Get the user hash for the given user."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040041 return utils.system_output(['cryptohome', '--action=obfuscate_user',
42 '--user=%s' % user])
Sean Oe5d8fd02010-09-30 10:44:44 +020043
44
barfab@chromium.org5c374632012-04-05 16:50:56 +020045def user_path(user):
46 """Get the user mount point for the given user."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040047 return utils.system_output(['cryptohome-path', 'user', user])
barfab@chromium.org5c374632012-04-05 16:50:56 +020048
49
50def system_path(user):
51 """Get the system mount point for the given user."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040052 return utils.system_output(['cryptohome-path', 'system', user])
barfab@chromium.org5c374632012-04-05 16:50:56 +020053
54
Dan Spaidbe9789c2017-05-19 15:18:42 +090055def temporary_mount_path(user):
56 """Get the vault mount path used during crypto-migration for the user.
57
58 @param user: user the temporary mount should be for
59 """
60 return TEMP_MOUNT_PATTERN % (get_user_hash(user))
61
62
63def vault_path(user):
64 """ Get the vault path for the given user.
65
66 @param user: The user who's vault path should be returned.
67 """
68 return VAULT_PATH_PATTERN % (get_user_hash(user))
69
70
Chris Masone5d010aa2013-05-06 14:38:42 -070071def ensure_clean_cryptohome_for(user, password=None):
72 """Ensure a fresh cryptohome exists for user.
73
74 @param user: user who needs a shiny new cryptohome.
75 @param password: if unset, a random password will be used.
76 """
77 if not password:
78 password = ''.join(random.sample(string.ascii_lowercase, 6))
Eric Caruso272193b2018-02-05 14:02:11 -080079 unmount_vault(user)
Chris Masone5d010aa2013-05-06 14:38:42 -070080 remove_vault(user)
81 mount_vault(user, password, create=True)
82
83
Brian Norriscd51d8d2020-12-17 00:53:41 +000084def get_tpm_status():
85 """Get the TPM status.
86
87 Returns:
88 A TPM status dictionary, for example:
89 { 'Enabled': True,
90 'Owned': True,
91 'Being Owned': False,
92 'Ready': True,
93 'Password': ''
94 }
95 """
96 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_status')
97 status = {}
98 for field in ['Enabled', 'Owned', 'Being Owned', 'Ready']:
99 match = re.search('TPM %s: (true|false)' % field, out)
100 if not match:
101 raise ChromiumOSError('Invalid TPM status: "%s".' % out)
102 status[field] = match.group(1) == 'true'
103 match = re.search('TPM Password: (\w*)', out)
104 status['Password'] = ''
105 if match:
106 status['Password'] = match.group(1)
107 return status
108
109
110def get_tpm_more_status():
111 """Get more of the TPM status.
112
113 Returns:
114 A TPM more status dictionary, for example:
115 { 'dictionary_attack_lockout_in_effect': False,
116 'attestation_prepared': False,
117 'boot_lockbox_finalized': False,
118 'enabled': True,
119 'owned': True,
120 'owner_password': ''
121 'dictionary_attack_counter': 0,
122 'dictionary_attack_lockout_seconds_remaining': 0,
123 'dictionary_attack_threshold': 10,
124 'attestation_enrolled': False,
125 'initialized': True,
126 'verified_boot_measured': False,
127 'install_lockbox_finalized': True
128 }
129 An empty dictionary is returned if the command is not supported.
130 """
131 status = {}
132 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_more_status | grep :')
133 if out.startswith(UNAVAILABLE_ACTION):
134 # --action=tpm_more_status only exists >= 41.
135 logging.info('Method not supported!')
136 return status
137 for line in out.splitlines():
138 items = line.strip().split(':')
139 if items[1].strip() == 'false':
140 value = False
141 elif items[1].strip() == 'true':
142 value = True
143 elif items[1].strip().isdigit():
144 value = int(items[1].strip())
145 else:
146 value = items[1].strip(' "')
147 status[items[0]] = value
148 return status
149
150
Mary Ruthven9a0ce562017-05-30 13:01:47 -0700151def get_fwmp(cleared_fwmp=False):
152 """Get the firmware management parameters.
153
154 Args:
155 cleared_fwmp: True if the space should not exist.
156
157 Returns:
158 The dictionary with the FWMP contents, for example:
159 { 'flags': 0xbb41,
160 'developer_key_hash':
161 "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\
162 000\000\000\000\000\000\000\000\000\000\000",
163 }
164 or a dictionary with the Error if the FWMP doesn't exist and
165 cleared_fwmp is True
166 { 'error': 'CRYPTOHOME_ERROR_FIRMWARE_MANAGEMENT_PARAMETERS_INVALID' }
167
168 Raises:
169 ChromiumOSError if any expected field is not found in the cryptohome
170 output. This would typically happen when FWMP state does not match
171 'clreared_fwmp'
172 """
173 out = __run_cmd(CRYPTOHOME_CMD +
174 ' --action=get_firmware_management_parameters')
175
176 if cleared_fwmp:
177 fields = ['error']
178 else:
179 fields = ['flags', 'developer_key_hash']
180
181 status = {}
182 for field in fields:
183 match = re.search('%s: (\S+)\n' % field, out)
184 if not match:
185 raise ChromiumOSError('Invalid FWMP field %s: "%s".' %
186 (field, out))
187 status[field] = match.group(1)
188 return status
189
190
191def set_fwmp(flags, developer_key_hash=None):
192 """Set the firmware management parameter contents.
193
194 Args:
195 developer_key_hash: a string with the developer key hash
196
197 Raises:
198 ChromiumOSError cryptohome cannot set the FWMP contents
199 """
200 cmd = (CRYPTOHOME_CMD +
201 ' --action=set_firmware_management_parameters '
202 '--flags=' + flags)
203 if developer_key_hash:
204 cmd += ' --developer_key_hash=' + developer_key_hash
205
206 out = __run_cmd(cmd)
207 if 'SetFirmwareManagementParameters success' not in out:
208 raise ChromiumOSError('failed to set FWMP: %s' % out)
209
210
Kris Rambish82ee1c02014-12-10 17:02:39 -0800211def is_tpm_lockout_in_effect():
212 """Returns true if the TPM lockout is in effect; false otherwise."""
213 status = get_tpm_more_status()
Christopher Wiley94fd6b32014-12-13 18:52:03 -0800214 return status.get('dictionary_attack_lockout_in_effect', None)
Kris Rambish82ee1c02014-12-10 17:02:39 -0800215
216
David Pursell2a2ef342014-10-17 10:34:56 -0700217def get_login_status():
218 """Query the login status
219
220 Returns:
221 A login status dictionary containing:
222 { 'owner_user_exists': True|False,
223 'boot_lockbox_finalized': True|False
224 }
225 """
226 out = __run_cmd(CRYPTOHOME_CMD + ' --action=get_login_status')
227 status = {}
228 for field in ['owner_user_exists', 'boot_lockbox_finalized']:
229 match = re.search('%s: (true|false)' % field, out)
230 if not match:
231 raise ChromiumOSError('Invalid login status: "%s".' % out)
232 status[field] = match.group(1) == 'true'
233 return status
234
235
Darren Krahn5f880f62012-10-02 15:17:59 -0700236def get_tpm_attestation_status():
237 """Get the TPM attestation status. Works similar to get_tpm_status().
238 """
239 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_attestation_status')
240 status = {}
241 for field in ['Prepared', 'Enrolled']:
242 match = re.search('Attestation %s: (true|false)' % field, out)
243 if not match:
244 raise ChromiumOSError('Invalid attestation status: "%s".' % out)
245 status[field] = match.group(1) == 'true'
246 return status
247
248
Eric Caruso6da07a02018-02-07 16:02:41 -0800249def take_tpm_ownership(wait_for_ownership=True):
Frank Farzand5e36312012-01-13 14:34:03 -0800250 """Take TPM owernship.
251
Eric Caruso6da07a02018-02-07 16:02:41 -0800252 Args:
253 wait_for_ownership: block until TPM is owned if true
Frank Farzand5e36312012-01-13 14:34:03 -0800254 """
255 __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_take_ownership')
Eric Caruso6da07a02018-02-07 16:02:41 -0800256 if wait_for_ownership:
Maksim Ivanov21c967a2018-03-22 21:24:41 +0100257 # Note that waiting for the 'Ready' flag is more correct than waiting
258 # for the 'Owned' flag, as the latter is set by cryptohomed before some
259 # of the ownership tasks are completed.
260 utils.poll_for_condition(
261 lambda: get_tpm_status()['Ready'],
262 timeout=300,
263 exception=error.TestError('Timeout waiting for TPM ownership'))
Frank Farzand5e36312012-01-13 14:34:03 -0800264
265
Darren Krahn0e73e7f2012-09-05 15:35:15 -0700266def verify_ek():
267 """Verify the TPM endorsement key.
268
269 Returns true if EK is valid.
270 """
271 cmd = CRYPTOHOME_CMD + ' --action=tpm_verify_ek'
272 return (utils.system(cmd, ignore_status=True) == 0)
273
274
Sean Oe5d8fd02010-09-30 10:44:44 +0200275def remove_vault(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200276 """Remove the given user's vault from the shadow directory."""
Sean Oe5d8fd02010-09-30 10:44:44 +0200277 logging.debug('user is %s', user)
278 user_hash = get_user_hash(user)
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700279 logging.debug('Removing vault for user %s with hash %s', user, user_hash)
Sean Oe5d8fd02010-09-30 10:44:44 +0200280 cmd = CRYPTOHOME_CMD + ' --action=remove --force --user=%s' % user
281 __run_cmd(cmd)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200282 # Ensure that the vault does not exist.
283 if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
Darren Krahne6c44b92014-03-31 12:11:08 -0700284 raise ChromiumOSError('Cryptohome could not remove the user\'s vault.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200285
286
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200287def remove_all_vaults():
288 """Remove any existing vaults from the shadow directory.
289
290 This function must be run with root privileges.
291 """
barfab@chromium.org5c374632012-04-05 16:50:56 +0200292 for item in os.listdir(constants.SHADOW_ROOT):
293 abs_item = os.path.join(constants.SHADOW_ROOT, item)
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200294 if os.path.isdir(os.path.join(abs_item, 'vault')):
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700295 logging.debug('Removing vault for user with hash %s', item)
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200296 shutil.rmtree(abs_item)
297
298
Allen Webb8b4eb622018-07-20 15:46:11 -0700299def mount_vault(user, password, create=False, key_label=None):
Eric Caruso22acd672018-02-01 17:55:28 -0800300 """Mount the given user's vault. Mounts should be created by calling this
301 function with create=True, and can be used afterwards with create=False.
302 Only try to mount existing vaults created with this function.
303
304 """
305 args = [CRYPTOHOME_CMD, '--action=mount_ex', '--user=%s' % user,
Chris Masone3543e512013-11-04 13:09:30 -0800306 '--password=%s' % password, '--async']
Sean Oe5d8fd02010-09-30 10:44:44 +0200307 if create:
Allen Webb8b4eb622018-07-20 15:46:11 -0700308 args += ['--create']
309 if key_label is None:
310 key_label = 'bar'
311 if key_label is not None:
312 args += ['--key_label=%s' % key_label]
Chris Masone3543e512013-11-04 13:09:30 -0800313 logging.info(__run_cmd(' '.join(args)))
barfab@chromium.org5c374632012-04-05 16:50:56 +0200314 # Ensure that the vault exists in the shadow directory.
Sean Oe5d8fd02010-09-30 10:44:44 +0200315 user_hash = get_user_hash(user)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200316 if not os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
Alexis Saveryccb16be2017-02-01 16:23:15 -0800317 retry = 0
318 mounted = False
319 while retry < MOUNT_RETRY_COUNT and not mounted:
320 time.sleep(1)
321 logging.info("Retry " + str(retry + 1))
322 __run_cmd(' '.join(args))
323 # TODO: Remove this additional call to get_user_hash(user) when
324 # crbug.com/690994 is fixed
325 user_hash = get_user_hash(user)
326 if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
327 mounted = True
328 retry += 1
329 if not mounted:
330 raise ChromiumOSError('Cryptohome vault not found after mount.')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200331 # Ensure that the vault is mounted.
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700332 if not is_permanent_vault_mounted(user=user, allow_fail=True):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200333 raise ChromiumOSError('Cryptohome created a vault but did not mount.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200334
335
Chris Masone5d010aa2013-05-06 14:38:42 -0700336def mount_guest():
Sergey Poromov533008f2017-10-13 14:07:43 +0200337 """Mount the guest vault."""
Greg Kerrd7cdc132018-06-08 11:55:40 -0700338 args = [CRYPTOHOME_CMD, '--action=mount_guest_ex']
Chris Masone3543e512013-11-04 13:09:30 -0800339 logging.info(__run_cmd(' '.join(args)))
Sergey Poromov533008f2017-10-13 14:07:43 +0200340 # Ensure that the guest vault is mounted.
Chris Masone5d010aa2013-05-06 14:38:42 -0700341 if not is_guest_vault_mounted(allow_fail=True):
Sergey Poromov533008f2017-10-13 14:07:43 +0200342 raise ChromiumOSError('Cryptohome did not mount guest vault.')
Chris Masone5d010aa2013-05-06 14:38:42 -0700343
344
Sean Oe5d8fd02010-09-30 10:44:44 +0200345def test_auth(user, password):
Eric Carusode07cf82018-02-12 15:34:02 -0800346 cmd = [CRYPTOHOME_CMD, '--action=check_key_ex', '--user=%s' % user,
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400347 '--password=%s' % password, '--async']
Eric Carusode07cf82018-02-12 15:34:02 -0800348 out = __run_cmd(' '.join(cmd))
349 logging.info(out)
350 return 'Key authenticated.' in out
Sean Oe5d8fd02010-09-30 10:44:44 +0200351
352
Allen Webb8b4eb622018-07-20 15:46:11 -0700353def add_le_key(user, password, new_password, new_key_label):
354 args = [CRYPTOHOME_CMD, '--action=add_key_ex', '--key_policy=le',
355 '--user=%s' % user, '--password=%s' % password,
356 '--new_key_label=%s' % new_key_label,
357 '--new_password=%s' % new_password]
358 logging.info(__run_cmd(' '.join(args)))
359
360
361def remove_key(user, password, remove_key_label):
362 args = [CRYPTOHOME_CMD, '--action=remove_key_ex', '--user=%s' % user,
363 '--password=%s' % password,
364 '--remove_key_label=%s' % remove_key_label]
365 logging.info(__run_cmd(' '.join(args)))
366
367
368def get_supported_key_policies():
369 args = [CRYPTOHOME_CMD, '--action=get_supported_key_policies']
370 out = __run_cmd(' '.join(args))
371 logging.info(out)
372 policies = {}
373 for line in out.splitlines():
374 match = re.search(' ([^:]+): (true|false)', line)
375 if match:
376 policies[match.group(1)] = match.group(2) == 'true'
377 return policies
378
379
380def unmount_vault(user=None):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200381 """Unmount the given user's vault.
382
383 Once unmounting for a specific user is supported, the user parameter will
384 name the target user. See crosbug.com/20778.
Elly Jones686c2f42011-10-24 16:45:07 -0400385 """
Chris Masone3543e512013-11-04 13:09:30 -0800386 __run_cmd(CRYPTOHOME_CMD + ' --action=unmount')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200387 # Ensure that the vault is not mounted.
Allen Webb8b4eb622018-07-20 15:46:11 -0700388 if user is not None and is_vault_mounted(user, allow_fail=True):
Sean Oe5d8fd02010-09-30 10:44:44 +0200389 raise ChromiumOSError('Cryptohome did not unmount the user.')
390
391
barfab@chromium.org5c374632012-04-05 16:50:56 +0200392def __get_mount_info(mount_point, allow_fail=False):
393 """Get information about the active mount at a given mount point."""
beeps569f8672013-08-07 10:18:51 -0700394 cryptohomed_path = '/proc/$(pgrep cryptohomed)/mounts'
Jorge Lucangeli Obesf7627af2020-03-19 15:11:01 -0400395 # 'cryptohome-namespace-mounter' is currently only used for Guest sessions.
396 mounter_exe = 'cryptohome-namespace-mounter'
397 mounter_pid = 'pgrep -o -f %s' % mounter_exe
398 mounter_path = '/proc/$(%s)/mounts' % mounter_pid
399
400 status = utils.system(mounter_pid, ignore_status=True)
401 # Only check for these mounts if the mounter executable is running.
402 if status == 0:
403 try:
404 logging.debug('Active %s mounts:\n' % mounter_exe +
405 utils.system_output('cat %s' % mounter_path))
406 ns_mount_line = utils.system_output(
407 'grep %s %s' % (mount_point, mounter_path),
408 ignore_status=allow_fail)
409 except Exception as e:
410 logging.error(e)
411 raise ChromiumOSError('Could not get info about cryptohome vault '
412 'through %s. See logs for complete '
413 'mount-point.'
414 % os.path.dirname(str(mount_point)))
415 return ns_mount_line.split()
416
beeps569f8672013-08-07 10:18:51 -0700417 try:
Jorge Lucangeli Obesf7627af2020-03-19 15:11:01 -0400418 logging.debug('Active cryptohome mounts:\n' +
Daniel Erat2ec32792017-01-31 18:26:59 -0700419 utils.system_output('cat %s' % cryptohomed_path))
beeps569f8672013-08-07 10:18:51 -0700420 mount_line = utils.system_output(
421 'grep %s %s' % (mount_point, cryptohomed_path),
422 ignore_status=allow_fail)
423 except Exception as e:
424 logging.error(e)
425 raise ChromiumOSError('Could not get info about cryptohome vault '
426 'through %s. See logs for complete mount-point.'
427 % os.path.dirname(str(mount_point)))
Sourav Poddar574bd622010-05-26 14:22:26 +0530428 return mount_line.split()
429
430
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400431def __get_user_mount_info(user, allow_fail=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200432 """Get information about the active mounts for a given user.
433
434 Returns the active mounts at the user's user and system mount points. If no
435 user is given, the active mount at the shared mount point is returned
436 (regular users have a bind-mount at this mount point for backwards
437 compatibility; the guest user has a mount at this mount point only).
438 """
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400439 return [__get_mount_info(mount_point=user_path(user),
440 allow_fail=allow_fail),
441 __get_mount_info(mount_point=system_path(user),
442 allow_fail=allow_fail)]
Jim Hebertf08f88d2011-04-22 10:33:49 -0700443
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700444def is_vault_mounted(user, regexes=None, allow_fail=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200445 """Check whether a vault is mounted for the given user.
446
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700447 user: If no user is given, the shared mount point is checked, determining
448 whether a vault is mounted for any user.
449 regexes: dictionary of regexes to matches against the mount information.
450 The mount filesystem for the user's user and system mounts point must
451 match one of the keys.
452 The mount source point must match the selected device regex.
453
454 In addition, if mounted over ext4, we check the directory is encrypted.
barfab@chromium.org5c374632012-04-05 16:50:56 +0200455 """
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700456 if regexes is None:
457 regexes = {
458 constants.CRYPTOHOME_FS_REGEX_ANY :
459 constants.CRYPTOHOME_DEV_REGEX_ANY
460 }
barfab@chromium.org5c374632012-04-05 16:50:56 +0200461 user_mount_info = __get_user_mount_info(user=user, allow_fail=allow_fail)
462 for mount_info in user_mount_info:
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700463 # Look at each /proc/../mount lines that match mount point for a given
464 # user user/system mount (/home/user/.... /home/root/...)
465
466 # We should have at least 3 arguments (source, mount, type of mount)
467 if len(mount_info) < 3:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200468 return False
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700469
470 device_regex = None
471 for fs_regex in regexes.keys():
472 if re.match(fs_regex, mount_info[2]):
473 device_regex = regexes[fs_regex]
474 break
475
476 if not device_regex:
Jorge Lucangeli Obesf7627af2020-03-19 15:11:01 -0400477 # The third argument in not the expected mount point type.
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700478 return False
479
480 # Check if the mount source match the device regex: it can be loose,
481 # (anything) or stricter if we expect guest filesystem.
482 if not re.match(device_regex, mount_info[0]):
483 return False
484
barfab@chromium.org5c374632012-04-05 16:50:56 +0200485 return True
Sourav Poddar574bd622010-05-26 14:22:26 +0530486
487
barfab@chromium.org5c374632012-04-05 16:50:56 +0200488def is_guest_vault_mounted(allow_fail=False):
Sergey Poromov533008f2017-10-13 14:07:43 +0200489 """Check whether a vault is mounted for the guest user.
Sergey Poromovd85dce52017-12-27 11:10:51 +0100490 It should be a mount of an ext4 partition on a loop device
491 or be backed by tmpfs.
Sergey Poromov533008f2017-10-13 14:07:43 +0200492 """
barfab@chromium.org5c374632012-04-05 16:50:56 +0200493 return is_vault_mounted(
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400494 user=GUEST_USER_NAME,
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700495 regexes={
Sergey Poromovd85dce52017-12-27 11:10:51 +0100496 # Remove tmpfs support when it becomes unnecessary as all guest
497 # modes will use ext4 on a loop device.
Sergey Poromov533008f2017-10-13 14:07:43 +0200498 constants.CRYPTOHOME_FS_REGEX_EXT4 :
499 constants.CRYPTOHOME_DEV_REGEX_LOOP_DEVICE,
Sergey Poromovd85dce52017-12-27 11:10:51 +0100500 constants.CRYPTOHOME_FS_REGEX_TMPFS :
501 constants.CRYPTOHOME_DEV_REGEX_GUEST,
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700502 },
barfab@chromium.org5c374632012-04-05 16:50:56 +0200503 allow_fail=allow_fail)
504
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700505def is_permanent_vault_mounted(user, allow_fail=False):
506 """Check if user is mounted over ecryptfs or ext4 crypto. """
507 return is_vault_mounted(
508 user=user,
509 regexes={
510 constants.CRYPTOHOME_FS_REGEX_ECRYPTFS :
511 constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER_SHADOW,
512 constants.CRYPTOHOME_FS_REGEX_EXT4 :
513 constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER_DEVICE,
514 },
515 allow_fail=allow_fail)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200516
Kazuhiro Inabaa3bf6452017-02-08 11:41:50 +0900517def get_mounted_vault_path(user, allow_fail=False):
518 """Get the path where the decrypted data for the user is located."""
519 return os.path.join(constants.SHADOW_ROOT, get_user_hash(user), 'mount')
Nirnimesh66814492011-06-27 18:00:33 -0700520
521
522def canonicalize(credential):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200523 """Perform basic canonicalization of |email_address|.
Nirnimesh66814492011-06-27 18:00:33 -0700524
barfab@chromium.org5c374632012-04-05 16:50:56 +0200525 Perform basic canonicalization of |email_address|, taking into account that
526 gmail does not consider '.' or caps inside a username to matter. It also
527 ignores everything after a '+'. For example,
528 c.masone+abc@gmail.com == cMaSone@gmail.com, per
Nirnimesh66814492011-06-27 18:00:33 -0700529 http://mail.google.com/support/bin/answer.py?hl=en&ctx=mail&answer=10313
530 """
531 if not credential:
532 return None
533
534 parts = credential.split('@')
535 if len(parts) != 2:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200536 raise error.TestError('Malformed email: ' + credential)
Nirnimesh66814492011-06-27 18:00:33 -0700537
538 (name, domain) = parts
539 name = name.partition('+')[0]
barfab@chromium.org5c374632012-04-05 16:50:56 +0200540 if (domain == constants.SPECIAL_CASE_DOMAIN):
Nirnimesh66814492011-06-27 18:00:33 -0700541 name = name.replace('.', '')
542 return '@'.join([name, domain]).lower()
Elly Jones686c2f42011-10-24 16:45:07 -0400543
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200544
Will Drewryd2fed972013-12-05 16:35:51 -0600545def crash_cryptohomed():
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600546 # Try to kill cryptohomed so we get something to work with.
547 pid = __run_cmd('pgrep cryptohomed')
548 try:
Will Drewry9e440792013-12-11 17:18:35 -0600549 pid = int(pid)
Derek Beckett1091ed12020-10-19 10:47:16 -0700550 except ValueError as e: # empty or invalid string
Will Drewry9e440792013-12-11 17:18:35 -0600551 raise error.TestError('Cryptohomed was not running')
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600552 utils.system('kill -ABRT %d' % pid)
553 # CONT just in case cryptohomed had a spurious STOP.
554 utils.system('kill -CONT %d' % pid)
555 utils.poll_for_condition(
556 lambda: utils.system('ps -p %d' % pid,
557 ignore_status=True) != 0,
Will Drewry934d1532014-01-30 16:23:17 -0600558 timeout=180,
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600559 exception=error.TestError(
560 'Timeout waiting for cryptohomed to coredump'))
561
Will Drewryd2fed972013-12-05 16:35:51 -0600562
Dan Spaidbe9789c2017-05-19 15:18:42 +0900563def create_ecryptfs_homedir(user, password):
564 """Creates a new home directory as ecryptfs.
565
566 If a home directory for the user exists already, it will be removed.
567 The resulting home directory will be mounted.
568
569 @param user: Username to create the home directory for.
570 @param password: Password to use when creating the home directory.
571 """
572 unmount_vault(user)
573 remove_vault(user)
574 args = [
575 CRYPTOHOME_CMD,
576 '--action=mount_ex',
577 '--user=%s' % user,
578 '--password=%s' % password,
579 '--key_label=foo',
580 '--ecryptfs',
581 '--create']
582 logging.info(__run_cmd(' '.join(args)))
583 if not is_vault_mounted(user, regexes={
584 constants.CRYPTOHOME_FS_REGEX_ECRYPTFS :
585 constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER_SHADOW
586 }, allow_fail=True):
587 raise ChromiumOSError('Ecryptfs home could not be created')
588
589
590def do_dircrypto_migration(user, password, timeout=600):
591 """Start dircrypto migration for the user.
592
593 @param user: The user to migrate.
594 @param password: The password used to mount the users vault
595 @param timeout: How long in seconds to wait for the migration to finish
596 before failing.
597 """
598 unmount_vault(user)
599 args = [
600 CRYPTOHOME_CMD,
601 '--action=mount_ex',
602 '--to_migrate_from_ecryptfs',
603 '--user=%s' % user,
604 '--password=%s' % password]
605 logging.info(__run_cmd(' '.join(args)))
606 if not __get_mount_info(temporary_mount_path(user), allow_fail=True):
607 raise ChromiumOSError('Failed to mount home for migration')
608 args = [CRYPTOHOME_CMD, '--action=migrate_to_dircrypto', '--user=%s' % user]
609 logging.info(__run_cmd(' '.join(args)))
610 utils.poll_for_condition(
611 lambda: not __get_mount_info(
612 temporary_mount_path(user), allow_fail=True),
613 timeout=timeout,
614 exception=error.TestError(
615 'Timeout waiting for dircrypto migration to finish'))
616
617
Eric Caruso014d5ed2018-02-01 16:24:41 -0800618def change_password(user, password, new_password):
619 args = [
620 CRYPTOHOME_CMD,
Greg Kerr98aa75c2018-06-05 15:27:12 -0700621 '--action=migrate_key_ex',
Eric Caruso014d5ed2018-02-01 16:24:41 -0800622 '--user=%s' % user,
623 '--old_password=%s' % password,
624 '--password=%s' % new_password]
Eric Caruso43ec57e2018-02-05 15:54:49 -0800625 out = __run_cmd(' '.join(args))
626 logging.info(out)
627 if 'Key migration succeeded.' not in out:
628 raise ChromiumOSError('Key migration failed.')
Eric Caruso014d5ed2018-02-01 16:24:41 -0800629
630
Will Drewry9e440792013-12-11 17:18:35 -0600631class CryptohomeProxy(DBusClient):
632 """A DBus proxy client for testing the Cryptohome DBus server.
633 """
634 CRYPTOHOME_BUS_NAME = 'org.chromium.Cryptohome'
635 CRYPTOHOME_OBJECT_PATH = '/org/chromium/Cryptohome'
636 CRYPTOHOME_INTERFACE = 'org.chromium.CryptohomeInterface'
637 ASYNC_CALL_STATUS_SIGNAL = 'AsyncCallStatus'
638 ASYNC_CALL_STATUS_SIGNAL_ARGUMENTS = (
639 'async_id', 'return_status', 'return_code'
640 )
641 DBUS_PROPERTIES_INTERFACE = 'org.freedesktop.DBus.Properties'
642
Lutz Justenb9275782018-06-20 18:42:22 +0200643 # Default timeout in seconds for the D-Bus connection.
644 DEFAULT_DBUS_TIMEOUT = 30
Chris Masone19e305e2014-03-14 15:13:46 -0700645
Lutz Justenb9275782018-06-20 18:42:22 +0200646 def __init__(self, bus_loop=None, autodir=None, job=None,
647 timeout=DEFAULT_DBUS_TIMEOUT):
Eric Caruso8dc40982018-03-20 17:05:19 -0700648 if autodir and job:
649 # Install D-Bus protos necessary for some methods.
650 dep_dir = os.path.join(autodir, 'deps', DBUS_PROTOS_DEP)
651 job.install_pkg(DBUS_PROTOS_DEP, 'dep', dep_dir)
652 sys.path.append(dep_dir)
653
654 # Set up D-Bus main loop and interface.
Will Drewry9e440792013-12-11 17:18:35 -0600655 self.main_loop = gobject.MainLoop()
Will Drewry78db9dc2014-04-01 16:34:23 -0500656 if bus_loop is None:
Chris Masone64170f82014-03-14 15:47:05 -0700657 bus_loop = DBusGMainLoop(set_as_default=True)
Will Drewry9e440792013-12-11 17:18:35 -0600658 self.bus = dbus.SystemBus(mainloop=bus_loop)
659 super(CryptohomeProxy, self).__init__(self.main_loop, self.bus,
660 self.CRYPTOHOME_BUS_NAME,
Lutz Justen1c6be452018-05-29 13:37:00 +0200661 self.CRYPTOHOME_OBJECT_PATH,
662 timeout)
Will Drewry9e440792013-12-11 17:18:35 -0600663 self.iface = dbus.Interface(self.proxy_object,
664 self.CRYPTOHOME_INTERFACE)
665 self.properties = dbus.Interface(self.proxy_object,
666 self.DBUS_PROPERTIES_INTERFACE)
667 self.handle_signal(self.CRYPTOHOME_INTERFACE,
668 self.ASYNC_CALL_STATUS_SIGNAL,
669 self.ASYNC_CALL_STATUS_SIGNAL_ARGUMENTS)
Elly Jones2f0ebba2011-10-27 13:43:20 -0400670
Chris Masone19e305e2014-03-14 15:13:46 -0700671
Will Drewryd2fed972013-12-05 16:35:51 -0600672 # Wrap all proxied calls to catch cryptohomed failures.
673 def __call(self, method, *args):
674 try:
Chris Masonef59d9df2014-03-14 12:05:32 -0700675 return method(*args, timeout=180)
Derek Beckett1091ed12020-10-19 10:47:16 -0700676 except dbus.exceptions.DBusException as e:
Will Drewryd2fed972013-12-05 16:35:51 -0600677 if e.get_dbus_name() == 'org.freedesktop.DBus.Error.NoReply':
678 logging.error('Cryptohome is not responding. Sending ABRT')
679 crash_cryptohomed()
680 raise ChromiumOSError('cryptohomed aborted. Check crashes!')
681 raise e
682
Chris Masone19e305e2014-03-14 15:13:46 -0700683
Will Drewry9e440792013-12-11 17:18:35 -0600684 def __wait_for_specific_signal(self, signal, data):
685 """Wait for the |signal| with matching |data|
686 Returns the resulting dict on success or {} on error.
687 """
Will Drewryc4de5ff2014-02-03 13:26:57 -0600688 # Do not bubble up the timeout here, just return {}.
689 result = {}
690 try:
691 result = self.wait_for_signal(signal)
692 except utils.TimeoutError:
693 return {}
Will Drewry9e440792013-12-11 17:18:35 -0600694 for k in data.keys():
Derek Beckett1091ed12020-10-19 10:47:16 -0700695 if k not in result or result[k] != data[k]:
Will Drewry9e440792013-12-11 17:18:35 -0600696 return {}
697 return result
698
Chris Masone19e305e2014-03-14 15:13:46 -0700699
Will Drewry9e440792013-12-11 17:18:35 -0600700 # Perform a data-less async call.
701 # TODO(wad) Add __async_data_call.
702 def __async_call(self, method, *args):
Will Drewryfef135a2014-05-23 16:02:14 -0500703 # Clear out any superfluous async call signals.
704 self.clear_signal_content(self.ASYNC_CALL_STATUS_SIGNAL)
Will Drewry9e440792013-12-11 17:18:35 -0600705 out = self.__call(method, *args)
706 logging.debug('Issued call ' + str(method) +
707 ' with async_id ' + str(out))
708 result = {}
709 try:
Will Drewry934d1532014-01-30 16:23:17 -0600710 # __wait_for_specific_signal has a 10s timeout
Will Drewry9e440792013-12-11 17:18:35 -0600711 result = utils.poll_for_condition(
712 lambda: self.__wait_for_specific_signal(
713 self.ASYNC_CALL_STATUS_SIGNAL, {'async_id' : out}),
Will Drewry934d1532014-01-30 16:23:17 -0600714 timeout=180,
Will Drewry9e440792013-12-11 17:18:35 -0600715 desc='matching %s signal' % self.ASYNC_CALL_STATUS_SIGNAL)
Derek Beckett1091ed12020-10-19 10:47:16 -0700716 except utils.TimeoutError as e:
Will Drewry9e440792013-12-11 17:18:35 -0600717 logging.error('Cryptohome timed out. Sending ABRT.')
718 crash_cryptohomed()
719 raise ChromiumOSError('cryptohomed aborted. Check crashes!')
720 return result
721
Chris Masone19e305e2014-03-14 15:13:46 -0700722
Derek Beckett1091ed12020-10-19 10:47:16 -0700723 def mount(self, user, password, create=False, key_label='bar'):
Elly Jones2f0ebba2011-10-27 13:43:20 -0400724 """Mounts a cryptohome.
725
726 Returns True if the mount succeeds or False otherwise.
Elly Jones2f0ebba2011-10-27 13:43:20 -0400727 """
Eric Caruso8dc40982018-03-20 17:05:19 -0700728 import rpc_pb2
729
730 acc = rpc_pb2.AccountIdentifier()
731 acc.account_id = user
732
733 auth = rpc_pb2.AuthorizationRequest()
734 auth.key.secret = password
735 auth.key.data.label = key_label
736
737 mount_req = rpc_pb2.MountRequest()
738 if create:
739 mount_req.create.copy_authorization_key = True
740
741 out = self.__call(self.iface.MountEx, acc.SerializeToString(),
742 auth.SerializeToString(), mount_req.SerializeToString())
743 parsed_out = rpc_pb2.BaseReply()
744 parsed_out.ParseFromString(''.join(map(chr, out)))
745 return parsed_out.error == rpc_pb2.CRYPTOHOME_ERROR_NOT_SET
Elly Jones2f0ebba2011-10-27 13:43:20 -0400746
Chris Masone19e305e2014-03-14 15:13:46 -0700747
Elly Jones2f0ebba2011-10-27 13:43:20 -0400748 def unmount(self, user):
749 """Unmounts a cryptohome.
750
751 Returns True if the unmount suceeds or false otherwise.
Elly Jones2f0ebba2011-10-27 13:43:20 -0400752 """
Eric Caruso8f148792019-02-26 14:35:31 -0800753 import rpc_pb2
754
755 req = rpc_pb2.UnmountRequest()
756
757 out = self.__call(self.iface.UnmountEx, req.SerializeToString())
758 parsed_out = rpc_pb2.BaseReply()
759 parsed_out.ParseFromString(''.join(map(chr, out)))
760 return parsed_out.error == rpc_pb2.CRYPTOHOME_ERROR_NOT_SET
Elly Jones2f0ebba2011-10-27 13:43:20 -0400761
Chris Masone19e305e2014-03-14 15:13:46 -0700762
Elly Jones2f0ebba2011-10-27 13:43:20 -0400763 def is_mounted(self, user):
764 """Tests whether a user's cryptohome is mounted."""
765 return (utils.is_mountpoint(user_path(user))
766 and utils.is_mountpoint(system_path(user)))
767
Chris Masone19e305e2014-03-14 15:13:46 -0700768
Elly Jones2f0ebba2011-10-27 13:43:20 -0400769 def require_mounted(self, user):
770 """Raises a test failure if a user's cryptohome is not mounted."""
771 utils.require_mountpoint(user_path(user))
772 utils.require_mountpoint(system_path(user))
Elly Jones4458f442012-04-16 15:42:56 -0400773
Chris Masone19e305e2014-03-14 15:13:46 -0700774
Derek Beckett1091ed12020-10-19 10:47:16 -0700775 def remove(self, user):
Leo Laicdabea52019-04-29 15:56:14 +0800776 """Removes a users cryptohome.
Greg Kerrec7ba582018-09-13 16:19:26 -0700777
778 Returns True if the operation succeeds or False otherwise.
779 """
780 import rpc_pb2
781
782 acc = rpc_pb2.AccountIdentifier()
783 acc.account_id = user
784
785 out = self.__call(self.iface.RemoveEx, acc.SerializeToString())
786 parsed_out = rpc_pb2.BaseReply()
787 parsed_out.ParseFromString(''.join(map(chr, out)))
788 return parsed_out.error == rpc_pb2.CRYPTOHOME_ERROR_NOT_SET
Chris Masone19e305e2014-03-14 15:13:46 -0700789
790
791 def ensure_clean_cryptohome_for(self, user, password=None):
792 """Ensure a fresh cryptohome exists for user.
793
794 @param user: user who needs a shiny new cryptohome.
795 @param password: if unset, a random password will be used.
796 """
797 if not password:
798 password = ''.join(random.sample(string.ascii_lowercase, 6))
799 self.remove(user)
800 self.mount(user, password, create=True)
Roman Sorokina45273e2017-12-20 12:03:27 +0100801
802 def lock_install_attributes(self, attrs):
803 """Set and lock install attributes for the device.
804
805 @param attrs: dict of install attributes.
806 """
Roman Sorokin0a228d12018-01-23 12:36:45 +0100807 take_tpm_ownership()
Roman Sorokin58312b22018-10-17 13:34:39 +0200808 self.wait_for_install_attributes_ready()
Roman Sorokina45273e2017-12-20 12:03:27 +0100809 for key, value in attrs.items():
810 if not self.__call(self.iface.InstallAttributesSet, key,
811 dbus.ByteArray(value + '\0')):
812 return False
813 return self.__call(self.iface.InstallAttributesFinalize)
Roman Sorokin58312b22018-10-17 13:34:39 +0200814
815 def wait_for_install_attributes_ready(self):
816 """Wait until install attributes are ready.
817 """
818 utils.poll_for_condition(
819 lambda: self.__call(self.iface.InstallAttributesIsReady),
820 timeout=300,
821 exception=error.TestError(
822 'Timeout waiting for install attributes are ready'))