blob: 4fdfa0388a5b1bc1f9bb76f9be15164493ae0b61 [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
Eric Caruso8dc40982018-03-20 17:05:19 -07005import dbus, gobject, logging, os, random, re, shutil, string, sys, 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
Eric Caruso8dc40982018-03-20 17:05:19 -070020DBUS_PROTOS_DEP = 'dbus_protos'
21
22
Chris Masone5d010aa2013-05-06 14:38:42 -070023class ChromiumOSError(error.TestError):
Sean Oe5d8fd02010-09-30 10:44:44 +020024 """Generic error for ChromiumOS-specific exceptions."""
25 pass
26
Sean Oe5d8fd02010-09-30 10:44:44 +020027def __run_cmd(cmd):
28 return utils.system_output(cmd + ' 2>&1', retain_output=True,
29 ignore_status=True).strip()
30
Sean Oe5d8fd02010-09-30 10:44:44 +020031def get_user_hash(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +020032 """Get the user hash for the given user."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040033 return utils.system_output(['cryptohome', '--action=obfuscate_user',
34 '--user=%s' % user])
Sean Oe5d8fd02010-09-30 10:44:44 +020035
36
barfab@chromium.org5c374632012-04-05 16:50:56 +020037def user_path(user):
38 """Get the user mount point for the given user."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040039 return utils.system_output(['cryptohome-path', 'user', user])
barfab@chromium.org5c374632012-04-05 16:50:56 +020040
41
42def system_path(user):
43 """Get the system mount point for the given user."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040044 return utils.system_output(['cryptohome-path', 'system', user])
barfab@chromium.org5c374632012-04-05 16:50:56 +020045
46
Dan Spaidbe9789c2017-05-19 15:18:42 +090047def temporary_mount_path(user):
48 """Get the vault mount path used during crypto-migration for the user.
49
50 @param user: user the temporary mount should be for
51 """
52 return TEMP_MOUNT_PATTERN % (get_user_hash(user))
53
54
55def vault_path(user):
56 """ Get the vault path for the given user.
57
58 @param user: The user who's vault path should be returned.
59 """
60 return VAULT_PATH_PATTERN % (get_user_hash(user))
61
62
Chris Masone5d010aa2013-05-06 14:38:42 -070063def ensure_clean_cryptohome_for(user, password=None):
64 """Ensure a fresh cryptohome exists for user.
65
66 @param user: user who needs a shiny new cryptohome.
67 @param password: if unset, a random password will be used.
68 """
69 if not password:
70 password = ''.join(random.sample(string.ascii_lowercase, 6))
Eric Caruso272193b2018-02-05 14:02:11 -080071 unmount_vault(user)
Chris Masone5d010aa2013-05-06 14:38:42 -070072 remove_vault(user)
73 mount_vault(user, password, create=True)
74
75
Frank Farzand5e36312012-01-13 14:34:03 -080076def get_tpm_status():
77 """Get the TPM status.
78
79 Returns:
80 A TPM status dictionary, for example:
81 { 'Enabled': True,
82 'Owned': True,
83 'Being Owned': False,
84 'Ready': True,
85 'Password': ''
86 }
87 """
88 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_status')
89 status = {}
90 for field in ['Enabled', 'Owned', 'Being Owned', 'Ready']:
91 match = re.search('TPM %s: (true|false)' % field, out)
92 if not match:
93 raise ChromiumOSError('Invalid TPM status: "%s".' % out)
94 status[field] = match.group(1) == 'true'
95 match = re.search('TPM Password: (\w*)', out)
96 status['Password'] = ''
97 if match:
98 status['Password'] = match.group(1)
99 return status
100
101
Kris Rambish82ee1c02014-12-10 17:02:39 -0800102def get_tpm_more_status():
103 """Get more of the TPM status.
104
105 Returns:
106 A TPM more status dictionary, for example:
107 { 'dictionary_attack_lockout_in_effect': False,
108 'attestation_prepared': False,
109 'boot_lockbox_finalized': False,
110 'enabled': True,
111 'owned': True,
Kris Rambishbe132592014-12-17 14:26:06 -0800112 'owner_password': ''
Kris Rambish82ee1c02014-12-10 17:02:39 -0800113 'dictionary_attack_counter': 0,
114 'dictionary_attack_lockout_seconds_remaining': 0,
115 'dictionary_attack_threshold': 10,
116 'attestation_enrolled': False,
117 'initialized': True,
118 'verified_boot_measured': False,
119 'install_lockbox_finalized': True
120 }
Kris Rambishbb5258c2014-12-16 16:51:17 -0800121 An empty dictionary is returned if the command is not supported.
Kris Rambish82ee1c02014-12-10 17:02:39 -0800122 """
Kris Rambish82ee1c02014-12-10 17:02:39 -0800123 status = {}
Kris Rambishbb5258c2014-12-16 16:51:17 -0800124 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_more_status | grep :')
125 if out.startswith(UNAVAILABLE_ACTION):
126 # --action=tpm_more_status only exists >= 41.
127 logging.info('Method not supported!')
128 return status
Kris Rambish82ee1c02014-12-10 17:02:39 -0800129 for line in out.splitlines():
130 items = line.strip().split(':')
131 if items[1].strip() == 'false':
132 value = False
133 elif items[1].strip() == 'true':
134 value = True
Kris Rambishbe132592014-12-17 14:26:06 -0800135 elif items[1].strip().isdigit():
Kris Rambish82ee1c02014-12-10 17:02:39 -0800136 value = int(items[1].strip())
Kris Rambishbe132592014-12-17 14:26:06 -0800137 else:
138 value = items[1].strip(' "')
Kris Rambish82ee1c02014-12-10 17:02:39 -0800139 status[items[0]] = value
140 return status
141
142
Mary Ruthven9a0ce562017-05-30 13:01:47 -0700143def get_fwmp(cleared_fwmp=False):
144 """Get the firmware management parameters.
145
146 Args:
147 cleared_fwmp: True if the space should not exist.
148
149 Returns:
150 The dictionary with the FWMP contents, for example:
151 { 'flags': 0xbb41,
152 'developer_key_hash':
153 "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\
154 000\000\000\000\000\000\000\000\000\000\000",
155 }
156 or a dictionary with the Error if the FWMP doesn't exist and
157 cleared_fwmp is True
158 { 'error': 'CRYPTOHOME_ERROR_FIRMWARE_MANAGEMENT_PARAMETERS_INVALID' }
159
160 Raises:
161 ChromiumOSError if any expected field is not found in the cryptohome
162 output. This would typically happen when FWMP state does not match
163 'clreared_fwmp'
164 """
165 out = __run_cmd(CRYPTOHOME_CMD +
166 ' --action=get_firmware_management_parameters')
167
168 if cleared_fwmp:
169 fields = ['error']
170 else:
171 fields = ['flags', 'developer_key_hash']
172
173 status = {}
174 for field in fields:
175 match = re.search('%s: (\S+)\n' % field, out)
176 if not match:
177 raise ChromiumOSError('Invalid FWMP field %s: "%s".' %
178 (field, out))
179 status[field] = match.group(1)
180 return status
181
182
183def set_fwmp(flags, developer_key_hash=None):
184 """Set the firmware management parameter contents.
185
186 Args:
187 developer_key_hash: a string with the developer key hash
188
189 Raises:
190 ChromiumOSError cryptohome cannot set the FWMP contents
191 """
192 cmd = (CRYPTOHOME_CMD +
193 ' --action=set_firmware_management_parameters '
194 '--flags=' + flags)
195 if developer_key_hash:
196 cmd += ' --developer_key_hash=' + developer_key_hash
197
198 out = __run_cmd(cmd)
199 if 'SetFirmwareManagementParameters success' not in out:
200 raise ChromiumOSError('failed to set FWMP: %s' % out)
201
202
Kris Rambish82ee1c02014-12-10 17:02:39 -0800203def is_tpm_lockout_in_effect():
204 """Returns true if the TPM lockout is in effect; false otherwise."""
205 status = get_tpm_more_status()
Christopher Wiley94fd6b32014-12-13 18:52:03 -0800206 return status.get('dictionary_attack_lockout_in_effect', None)
Kris Rambish82ee1c02014-12-10 17:02:39 -0800207
208
David Pursell2a2ef342014-10-17 10:34:56 -0700209def get_login_status():
210 """Query the login status
211
212 Returns:
213 A login status dictionary containing:
214 { 'owner_user_exists': True|False,
215 'boot_lockbox_finalized': True|False
216 }
217 """
218 out = __run_cmd(CRYPTOHOME_CMD + ' --action=get_login_status')
219 status = {}
220 for field in ['owner_user_exists', 'boot_lockbox_finalized']:
221 match = re.search('%s: (true|false)' % field, out)
222 if not match:
223 raise ChromiumOSError('Invalid login status: "%s".' % out)
224 status[field] = match.group(1) == 'true'
225 return status
226
227
Darren Krahn5f880f62012-10-02 15:17:59 -0700228def get_tpm_attestation_status():
229 """Get the TPM attestation status. Works similar to get_tpm_status().
230 """
231 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_attestation_status')
232 status = {}
233 for field in ['Prepared', 'Enrolled']:
234 match = re.search('Attestation %s: (true|false)' % field, out)
235 if not match:
236 raise ChromiumOSError('Invalid attestation status: "%s".' % out)
237 status[field] = match.group(1) == 'true'
238 return status
239
240
Eric Caruso6da07a02018-02-07 16:02:41 -0800241def take_tpm_ownership(wait_for_ownership=True):
Frank Farzand5e36312012-01-13 14:34:03 -0800242 """Take TPM owernship.
243
Eric Caruso6da07a02018-02-07 16:02:41 -0800244 Args:
245 wait_for_ownership: block until TPM is owned if true
Frank Farzand5e36312012-01-13 14:34:03 -0800246 """
247 __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_take_ownership')
Eric Caruso6da07a02018-02-07 16:02:41 -0800248 if wait_for_ownership:
Maksim Ivanov21c967a2018-03-22 21:24:41 +0100249 # Note that waiting for the 'Ready' flag is more correct than waiting
250 # for the 'Owned' flag, as the latter is set by cryptohomed before some
251 # of the ownership tasks are completed.
252 utils.poll_for_condition(
253 lambda: get_tpm_status()['Ready'],
254 timeout=300,
255 exception=error.TestError('Timeout waiting for TPM ownership'))
Frank Farzand5e36312012-01-13 14:34:03 -0800256
257
Darren Krahn0e73e7f2012-09-05 15:35:15 -0700258def verify_ek():
259 """Verify the TPM endorsement key.
260
261 Returns true if EK is valid.
262 """
263 cmd = CRYPTOHOME_CMD + ' --action=tpm_verify_ek'
264 return (utils.system(cmd, ignore_status=True) == 0)
265
266
Sean Oe5d8fd02010-09-30 10:44:44 +0200267def remove_vault(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200268 """Remove the given user's vault from the shadow directory."""
Sean Oe5d8fd02010-09-30 10:44:44 +0200269 logging.debug('user is %s', user)
270 user_hash = get_user_hash(user)
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700271 logging.debug('Removing vault for user %s with hash %s', user, user_hash)
Sean Oe5d8fd02010-09-30 10:44:44 +0200272 cmd = CRYPTOHOME_CMD + ' --action=remove --force --user=%s' % user
273 __run_cmd(cmd)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200274 # Ensure that the vault does not exist.
275 if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
Darren Krahne6c44b92014-03-31 12:11:08 -0700276 raise ChromiumOSError('Cryptohome could not remove the user\'s vault.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200277
278
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200279def remove_all_vaults():
280 """Remove any existing vaults from the shadow directory.
281
282 This function must be run with root privileges.
283 """
barfab@chromium.org5c374632012-04-05 16:50:56 +0200284 for item in os.listdir(constants.SHADOW_ROOT):
285 abs_item = os.path.join(constants.SHADOW_ROOT, item)
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200286 if os.path.isdir(os.path.join(abs_item, 'vault')):
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700287 logging.debug('Removing vault for user with hash %s', item)
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200288 shutil.rmtree(abs_item)
289
290
Allen Webb8b4eb622018-07-20 15:46:11 -0700291def mount_vault(user, password, create=False, key_label=None):
Eric Caruso22acd672018-02-01 17:55:28 -0800292 """Mount the given user's vault. Mounts should be created by calling this
293 function with create=True, and can be used afterwards with create=False.
294 Only try to mount existing vaults created with this function.
295
296 """
297 args = [CRYPTOHOME_CMD, '--action=mount_ex', '--user=%s' % user,
Chris Masone3543e512013-11-04 13:09:30 -0800298 '--password=%s' % password, '--async']
Sean Oe5d8fd02010-09-30 10:44:44 +0200299 if create:
Allen Webb8b4eb622018-07-20 15:46:11 -0700300 args += ['--create']
301 if key_label is None:
302 key_label = 'bar'
303 if key_label is not None:
304 args += ['--key_label=%s' % key_label]
Chris Masone3543e512013-11-04 13:09:30 -0800305 logging.info(__run_cmd(' '.join(args)))
barfab@chromium.org5c374632012-04-05 16:50:56 +0200306 # Ensure that the vault exists in the shadow directory.
Sean Oe5d8fd02010-09-30 10:44:44 +0200307 user_hash = get_user_hash(user)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200308 if not os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
Alexis Saveryccb16be2017-02-01 16:23:15 -0800309 retry = 0
310 mounted = False
311 while retry < MOUNT_RETRY_COUNT and not mounted:
312 time.sleep(1)
313 logging.info("Retry " + str(retry + 1))
314 __run_cmd(' '.join(args))
315 # TODO: Remove this additional call to get_user_hash(user) when
316 # crbug.com/690994 is fixed
317 user_hash = get_user_hash(user)
318 if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
319 mounted = True
320 retry += 1
321 if not mounted:
322 raise ChromiumOSError('Cryptohome vault not found after mount.')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200323 # Ensure that the vault is mounted.
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700324 if not is_permanent_vault_mounted(user=user, allow_fail=True):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200325 raise ChromiumOSError('Cryptohome created a vault but did not mount.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200326
327
Chris Masone5d010aa2013-05-06 14:38:42 -0700328def mount_guest():
Sergey Poromov533008f2017-10-13 14:07:43 +0200329 """Mount the guest vault."""
Greg Kerrd7cdc132018-06-08 11:55:40 -0700330 args = [CRYPTOHOME_CMD, '--action=mount_guest_ex']
Chris Masone3543e512013-11-04 13:09:30 -0800331 logging.info(__run_cmd(' '.join(args)))
Sergey Poromov533008f2017-10-13 14:07:43 +0200332 # Ensure that the guest vault is mounted.
Chris Masone5d010aa2013-05-06 14:38:42 -0700333 if not is_guest_vault_mounted(allow_fail=True):
Sergey Poromov533008f2017-10-13 14:07:43 +0200334 raise ChromiumOSError('Cryptohome did not mount guest vault.')
Chris Masone5d010aa2013-05-06 14:38:42 -0700335
336
Sean Oe5d8fd02010-09-30 10:44:44 +0200337def test_auth(user, password):
Eric Carusode07cf82018-02-12 15:34:02 -0800338 cmd = [CRYPTOHOME_CMD, '--action=check_key_ex', '--user=%s' % user,
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400339 '--password=%s' % password, '--async']
Eric Carusode07cf82018-02-12 15:34:02 -0800340 out = __run_cmd(' '.join(cmd))
341 logging.info(out)
342 return 'Key authenticated.' in out
Sean Oe5d8fd02010-09-30 10:44:44 +0200343
344
Allen Webb8b4eb622018-07-20 15:46:11 -0700345def add_le_key(user, password, new_password, new_key_label):
346 args = [CRYPTOHOME_CMD, '--action=add_key_ex', '--key_policy=le',
347 '--user=%s' % user, '--password=%s' % password,
348 '--new_key_label=%s' % new_key_label,
349 '--new_password=%s' % new_password]
350 logging.info(__run_cmd(' '.join(args)))
351
352
353def remove_key(user, password, remove_key_label):
354 args = [CRYPTOHOME_CMD, '--action=remove_key_ex', '--user=%s' % user,
355 '--password=%s' % password,
356 '--remove_key_label=%s' % remove_key_label]
357 logging.info(__run_cmd(' '.join(args)))
358
359
360def get_supported_key_policies():
361 args = [CRYPTOHOME_CMD, '--action=get_supported_key_policies']
362 out = __run_cmd(' '.join(args))
363 logging.info(out)
364 policies = {}
365 for line in out.splitlines():
366 match = re.search(' ([^:]+): (true|false)', line)
367 if match:
368 policies[match.group(1)] = match.group(2) == 'true'
369 return policies
370
371
372def unmount_vault(user=None):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200373 """Unmount the given user's vault.
374
375 Once unmounting for a specific user is supported, the user parameter will
376 name the target user. See crosbug.com/20778.
Elly Jones686c2f42011-10-24 16:45:07 -0400377 """
Chris Masone3543e512013-11-04 13:09:30 -0800378 __run_cmd(CRYPTOHOME_CMD + ' --action=unmount')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200379 # Ensure that the vault is not mounted.
Allen Webb8b4eb622018-07-20 15:46:11 -0700380 if user is not None and is_vault_mounted(user, allow_fail=True):
Sean Oe5d8fd02010-09-30 10:44:44 +0200381 raise ChromiumOSError('Cryptohome did not unmount the user.')
382
383
barfab@chromium.org5c374632012-04-05 16:50:56 +0200384def __get_mount_info(mount_point, allow_fail=False):
385 """Get information about the active mount at a given mount point."""
beeps569f8672013-08-07 10:18:51 -0700386 cryptohomed_path = '/proc/$(pgrep cryptohomed)/mounts'
Jorge Lucangeli Obesf7627af2020-03-19 15:11:01 -0400387 # 'cryptohome-namespace-mounter' is currently only used for Guest sessions.
388 mounter_exe = 'cryptohome-namespace-mounter'
389 mounter_pid = 'pgrep -o -f %s' % mounter_exe
390 mounter_path = '/proc/$(%s)/mounts' % mounter_pid
391
392 status = utils.system(mounter_pid, ignore_status=True)
393 # Only check for these mounts if the mounter executable is running.
394 if status == 0:
395 try:
396 logging.debug('Active %s mounts:\n' % mounter_exe +
397 utils.system_output('cat %s' % mounter_path))
398 ns_mount_line = utils.system_output(
399 'grep %s %s' % (mount_point, mounter_path),
400 ignore_status=allow_fail)
401 except Exception as e:
402 logging.error(e)
403 raise ChromiumOSError('Could not get info about cryptohome vault '
404 'through %s. See logs for complete '
405 'mount-point.'
406 % os.path.dirname(str(mount_point)))
407 return ns_mount_line.split()
408
beeps569f8672013-08-07 10:18:51 -0700409 try:
Jorge Lucangeli Obesf7627af2020-03-19 15:11:01 -0400410 logging.debug('Active cryptohome mounts:\n' +
Daniel Erat2ec32792017-01-31 18:26:59 -0700411 utils.system_output('cat %s' % cryptohomed_path))
beeps569f8672013-08-07 10:18:51 -0700412 mount_line = utils.system_output(
413 'grep %s %s' % (mount_point, cryptohomed_path),
414 ignore_status=allow_fail)
415 except Exception as e:
416 logging.error(e)
417 raise ChromiumOSError('Could not get info about cryptohome vault '
418 'through %s. See logs for complete mount-point.'
419 % os.path.dirname(str(mount_point)))
Sourav Poddar574bd622010-05-26 14:22:26 +0530420 return mount_line.split()
421
422
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400423def __get_user_mount_info(user, allow_fail=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200424 """Get information about the active mounts for a given user.
425
426 Returns the active mounts at the user's user and system mount points. If no
427 user is given, the active mount at the shared mount point is returned
428 (regular users have a bind-mount at this mount point for backwards
429 compatibility; the guest user has a mount at this mount point only).
430 """
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400431 return [__get_mount_info(mount_point=user_path(user),
432 allow_fail=allow_fail),
433 __get_mount_info(mount_point=system_path(user),
434 allow_fail=allow_fail)]
Jim Hebertf08f88d2011-04-22 10:33:49 -0700435
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700436def is_vault_mounted(user, regexes=None, allow_fail=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200437 """Check whether a vault is mounted for the given user.
438
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700439 user: If no user is given, the shared mount point is checked, determining
440 whether a vault is mounted for any user.
441 regexes: dictionary of regexes to matches against the mount information.
442 The mount filesystem for the user's user and system mounts point must
443 match one of the keys.
444 The mount source point must match the selected device regex.
445
446 In addition, if mounted over ext4, we check the directory is encrypted.
barfab@chromium.org5c374632012-04-05 16:50:56 +0200447 """
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700448 if regexes is None:
449 regexes = {
450 constants.CRYPTOHOME_FS_REGEX_ANY :
451 constants.CRYPTOHOME_DEV_REGEX_ANY
452 }
barfab@chromium.org5c374632012-04-05 16:50:56 +0200453 user_mount_info = __get_user_mount_info(user=user, allow_fail=allow_fail)
454 for mount_info in user_mount_info:
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700455 # Look at each /proc/../mount lines that match mount point for a given
456 # user user/system mount (/home/user/.... /home/root/...)
457
458 # We should have at least 3 arguments (source, mount, type of mount)
459 if len(mount_info) < 3:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200460 return False
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700461
462 device_regex = None
463 for fs_regex in regexes.keys():
464 if re.match(fs_regex, mount_info[2]):
465 device_regex = regexes[fs_regex]
466 break
467
468 if not device_regex:
Jorge Lucangeli Obesf7627af2020-03-19 15:11:01 -0400469 # The third argument in not the expected mount point type.
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700470 return False
471
472 # Check if the mount source match the device regex: it can be loose,
473 # (anything) or stricter if we expect guest filesystem.
474 if not re.match(device_regex, mount_info[0]):
475 return False
476
Sergey Poromov533008f2017-10-13 14:07:43 +0200477 if (re.match(constants.CRYPTOHOME_FS_REGEX_EXT4, mount_info[2])
478 and not(re.match(constants.CRYPTOHOME_DEV_REGEX_LOOP_DEVICE,
479 mount_info[0]))):
480 # Ephemeral cryptohome uses ext4 mount from a loop device,
481 # otherwise it should be ext4 crypto. Check there is an encryption
482 # key for that directory.
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700483 find_key_cmd_list = ['e4crypt get_policy %s' % (mount_info[1]),
484 'cut -d \' \' -f 2']
485 key = __run_cmd(' | ' .join(find_key_cmd_list))
486 cmd_list = ['keyctl show @s',
487 'grep %s' % (key),
488 'wc -l']
489 out = __run_cmd(' | '.join(cmd_list))
490 if int(out) != 1:
491 return False
barfab@chromium.org5c374632012-04-05 16:50:56 +0200492 return True
Sourav Poddar574bd622010-05-26 14:22:26 +0530493
494
barfab@chromium.org5c374632012-04-05 16:50:56 +0200495def is_guest_vault_mounted(allow_fail=False):
Sergey Poromov533008f2017-10-13 14:07:43 +0200496 """Check whether a vault is mounted for the guest user.
Sergey Poromovd85dce52017-12-27 11:10:51 +0100497 It should be a mount of an ext4 partition on a loop device
498 or be backed by tmpfs.
Sergey Poromov533008f2017-10-13 14:07:43 +0200499 """
barfab@chromium.org5c374632012-04-05 16:50:56 +0200500 return is_vault_mounted(
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400501 user=GUEST_USER_NAME,
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700502 regexes={
Sergey Poromovd85dce52017-12-27 11:10:51 +0100503 # Remove tmpfs support when it becomes unnecessary as all guest
504 # modes will use ext4 on a loop device.
Sergey Poromov533008f2017-10-13 14:07:43 +0200505 constants.CRYPTOHOME_FS_REGEX_EXT4 :
506 constants.CRYPTOHOME_DEV_REGEX_LOOP_DEVICE,
Sergey Poromovd85dce52017-12-27 11:10:51 +0100507 constants.CRYPTOHOME_FS_REGEX_TMPFS :
508 constants.CRYPTOHOME_DEV_REGEX_GUEST,
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700509 },
barfab@chromium.org5c374632012-04-05 16:50:56 +0200510 allow_fail=allow_fail)
511
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700512def is_permanent_vault_mounted(user, allow_fail=False):
513 """Check if user is mounted over ecryptfs or ext4 crypto. """
514 return is_vault_mounted(
515 user=user,
516 regexes={
517 constants.CRYPTOHOME_FS_REGEX_ECRYPTFS :
518 constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER_SHADOW,
519 constants.CRYPTOHOME_FS_REGEX_EXT4 :
520 constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER_DEVICE,
521 },
522 allow_fail=allow_fail)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200523
Kazuhiro Inabaa3bf6452017-02-08 11:41:50 +0900524def get_mounted_vault_path(user, allow_fail=False):
525 """Get the path where the decrypted data for the user is located."""
526 return os.path.join(constants.SHADOW_ROOT, get_user_hash(user), 'mount')
Nirnimesh66814492011-06-27 18:00:33 -0700527
528
529def canonicalize(credential):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200530 """Perform basic canonicalization of |email_address|.
Nirnimesh66814492011-06-27 18:00:33 -0700531
barfab@chromium.org5c374632012-04-05 16:50:56 +0200532 Perform basic canonicalization of |email_address|, taking into account that
533 gmail does not consider '.' or caps inside a username to matter. It also
534 ignores everything after a '+'. For example,
535 c.masone+abc@gmail.com == cMaSone@gmail.com, per
Nirnimesh66814492011-06-27 18:00:33 -0700536 http://mail.google.com/support/bin/answer.py?hl=en&ctx=mail&answer=10313
537 """
538 if not credential:
539 return None
540
541 parts = credential.split('@')
542 if len(parts) != 2:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200543 raise error.TestError('Malformed email: ' + credential)
Nirnimesh66814492011-06-27 18:00:33 -0700544
545 (name, domain) = parts
546 name = name.partition('+')[0]
barfab@chromium.org5c374632012-04-05 16:50:56 +0200547 if (domain == constants.SPECIAL_CASE_DOMAIN):
Nirnimesh66814492011-06-27 18:00:33 -0700548 name = name.replace('.', '')
549 return '@'.join([name, domain]).lower()
Elly Jones686c2f42011-10-24 16:45:07 -0400550
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200551
Will Drewryd2fed972013-12-05 16:35:51 -0600552def crash_cryptohomed():
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600553 # Try to kill cryptohomed so we get something to work with.
554 pid = __run_cmd('pgrep cryptohomed')
555 try:
Will Drewry9e440792013-12-11 17:18:35 -0600556 pid = int(pid)
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600557 except ValueError, e: # empty or invalid string
Will Drewry9e440792013-12-11 17:18:35 -0600558 raise error.TestError('Cryptohomed was not running')
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600559 utils.system('kill -ABRT %d' % pid)
560 # CONT just in case cryptohomed had a spurious STOP.
561 utils.system('kill -CONT %d' % pid)
562 utils.poll_for_condition(
563 lambda: utils.system('ps -p %d' % pid,
564 ignore_status=True) != 0,
Will Drewry934d1532014-01-30 16:23:17 -0600565 timeout=180,
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600566 exception=error.TestError(
567 'Timeout waiting for cryptohomed to coredump'))
568
Will Drewryd2fed972013-12-05 16:35:51 -0600569
Dan Spaidbe9789c2017-05-19 15:18:42 +0900570def create_ecryptfs_homedir(user, password):
571 """Creates a new home directory as ecryptfs.
572
573 If a home directory for the user exists already, it will be removed.
574 The resulting home directory will be mounted.
575
576 @param user: Username to create the home directory for.
577 @param password: Password to use when creating the home directory.
578 """
579 unmount_vault(user)
580 remove_vault(user)
581 args = [
582 CRYPTOHOME_CMD,
583 '--action=mount_ex',
584 '--user=%s' % user,
585 '--password=%s' % password,
586 '--key_label=foo',
587 '--ecryptfs',
588 '--create']
589 logging.info(__run_cmd(' '.join(args)))
590 if not is_vault_mounted(user, regexes={
591 constants.CRYPTOHOME_FS_REGEX_ECRYPTFS :
592 constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER_SHADOW
593 }, allow_fail=True):
594 raise ChromiumOSError('Ecryptfs home could not be created')
595
596
597def do_dircrypto_migration(user, password, timeout=600):
598 """Start dircrypto migration for the user.
599
600 @param user: The user to migrate.
601 @param password: The password used to mount the users vault
602 @param timeout: How long in seconds to wait for the migration to finish
603 before failing.
604 """
605 unmount_vault(user)
606 args = [
607 CRYPTOHOME_CMD,
608 '--action=mount_ex',
609 '--to_migrate_from_ecryptfs',
610 '--user=%s' % user,
611 '--password=%s' % password]
612 logging.info(__run_cmd(' '.join(args)))
613 if not __get_mount_info(temporary_mount_path(user), allow_fail=True):
614 raise ChromiumOSError('Failed to mount home for migration')
615 args = [CRYPTOHOME_CMD, '--action=migrate_to_dircrypto', '--user=%s' % user]
616 logging.info(__run_cmd(' '.join(args)))
617 utils.poll_for_condition(
618 lambda: not __get_mount_info(
619 temporary_mount_path(user), allow_fail=True),
620 timeout=timeout,
621 exception=error.TestError(
622 'Timeout waiting for dircrypto migration to finish'))
623
624
Eric Caruso014d5ed2018-02-01 16:24:41 -0800625def change_password(user, password, new_password):
626 args = [
627 CRYPTOHOME_CMD,
Greg Kerr98aa75c2018-06-05 15:27:12 -0700628 '--action=migrate_key_ex',
Eric Caruso014d5ed2018-02-01 16:24:41 -0800629 '--user=%s' % user,
630 '--old_password=%s' % password,
631 '--password=%s' % new_password]
Eric Caruso43ec57e2018-02-05 15:54:49 -0800632 out = __run_cmd(' '.join(args))
633 logging.info(out)
634 if 'Key migration succeeded.' not in out:
635 raise ChromiumOSError('Key migration failed.')
Eric Caruso014d5ed2018-02-01 16:24:41 -0800636
637
Will Drewry9e440792013-12-11 17:18:35 -0600638class CryptohomeProxy(DBusClient):
639 """A DBus proxy client for testing the Cryptohome DBus server.
640 """
641 CRYPTOHOME_BUS_NAME = 'org.chromium.Cryptohome'
642 CRYPTOHOME_OBJECT_PATH = '/org/chromium/Cryptohome'
643 CRYPTOHOME_INTERFACE = 'org.chromium.CryptohomeInterface'
644 ASYNC_CALL_STATUS_SIGNAL = 'AsyncCallStatus'
645 ASYNC_CALL_STATUS_SIGNAL_ARGUMENTS = (
646 'async_id', 'return_status', 'return_code'
647 )
648 DBUS_PROPERTIES_INTERFACE = 'org.freedesktop.DBus.Properties'
649
Lutz Justenb9275782018-06-20 18:42:22 +0200650 # Default timeout in seconds for the D-Bus connection.
651 DEFAULT_DBUS_TIMEOUT = 30
Chris Masone19e305e2014-03-14 15:13:46 -0700652
Lutz Justenb9275782018-06-20 18:42:22 +0200653 def __init__(self, bus_loop=None, autodir=None, job=None,
654 timeout=DEFAULT_DBUS_TIMEOUT):
Eric Caruso8dc40982018-03-20 17:05:19 -0700655 if autodir and job:
656 # Install D-Bus protos necessary for some methods.
657 dep_dir = os.path.join(autodir, 'deps', DBUS_PROTOS_DEP)
658 job.install_pkg(DBUS_PROTOS_DEP, 'dep', dep_dir)
659 sys.path.append(dep_dir)
660
661 # Set up D-Bus main loop and interface.
Will Drewry9e440792013-12-11 17:18:35 -0600662 self.main_loop = gobject.MainLoop()
Will Drewry78db9dc2014-04-01 16:34:23 -0500663 if bus_loop is None:
Chris Masone64170f82014-03-14 15:47:05 -0700664 bus_loop = DBusGMainLoop(set_as_default=True)
Will Drewry9e440792013-12-11 17:18:35 -0600665 self.bus = dbus.SystemBus(mainloop=bus_loop)
666 super(CryptohomeProxy, self).__init__(self.main_loop, self.bus,
667 self.CRYPTOHOME_BUS_NAME,
Lutz Justen1c6be452018-05-29 13:37:00 +0200668 self.CRYPTOHOME_OBJECT_PATH,
669 timeout)
Will Drewry9e440792013-12-11 17:18:35 -0600670 self.iface = dbus.Interface(self.proxy_object,
671 self.CRYPTOHOME_INTERFACE)
672 self.properties = dbus.Interface(self.proxy_object,
673 self.DBUS_PROPERTIES_INTERFACE)
674 self.handle_signal(self.CRYPTOHOME_INTERFACE,
675 self.ASYNC_CALL_STATUS_SIGNAL,
676 self.ASYNC_CALL_STATUS_SIGNAL_ARGUMENTS)
Elly Jones2f0ebba2011-10-27 13:43:20 -0400677
Chris Masone19e305e2014-03-14 15:13:46 -0700678
Will Drewryd2fed972013-12-05 16:35:51 -0600679 # Wrap all proxied calls to catch cryptohomed failures.
680 def __call(self, method, *args):
681 try:
Chris Masonef59d9df2014-03-14 12:05:32 -0700682 return method(*args, timeout=180)
Will Drewryd2fed972013-12-05 16:35:51 -0600683 except dbus.exceptions.DBusException, e:
684 if e.get_dbus_name() == 'org.freedesktop.DBus.Error.NoReply':
685 logging.error('Cryptohome is not responding. Sending ABRT')
686 crash_cryptohomed()
687 raise ChromiumOSError('cryptohomed aborted. Check crashes!')
688 raise e
689
Chris Masone19e305e2014-03-14 15:13:46 -0700690
Will Drewry9e440792013-12-11 17:18:35 -0600691 def __wait_for_specific_signal(self, signal, data):
692 """Wait for the |signal| with matching |data|
693 Returns the resulting dict on success or {} on error.
694 """
Will Drewryc4de5ff2014-02-03 13:26:57 -0600695 # Do not bubble up the timeout here, just return {}.
696 result = {}
697 try:
698 result = self.wait_for_signal(signal)
699 except utils.TimeoutError:
700 return {}
Will Drewry9e440792013-12-11 17:18:35 -0600701 for k in data.keys():
702 if not result.has_key(k) or result[k] != data[k]:
703 return {}
704 return result
705
Chris Masone19e305e2014-03-14 15:13:46 -0700706
Will Drewry9e440792013-12-11 17:18:35 -0600707 # Perform a data-less async call.
708 # TODO(wad) Add __async_data_call.
709 def __async_call(self, method, *args):
Will Drewryfef135a2014-05-23 16:02:14 -0500710 # Clear out any superfluous async call signals.
711 self.clear_signal_content(self.ASYNC_CALL_STATUS_SIGNAL)
Will Drewry9e440792013-12-11 17:18:35 -0600712 out = self.__call(method, *args)
713 logging.debug('Issued call ' + str(method) +
714 ' with async_id ' + str(out))
715 result = {}
716 try:
Will Drewry934d1532014-01-30 16:23:17 -0600717 # __wait_for_specific_signal has a 10s timeout
Will Drewry9e440792013-12-11 17:18:35 -0600718 result = utils.poll_for_condition(
719 lambda: self.__wait_for_specific_signal(
720 self.ASYNC_CALL_STATUS_SIGNAL, {'async_id' : out}),
Will Drewry934d1532014-01-30 16:23:17 -0600721 timeout=180,
Will Drewry9e440792013-12-11 17:18:35 -0600722 desc='matching %s signal' % self.ASYNC_CALL_STATUS_SIGNAL)
723 except utils.TimeoutError, e:
724 logging.error('Cryptohome timed out. Sending ABRT.')
725 crash_cryptohomed()
726 raise ChromiumOSError('cryptohomed aborted. Check crashes!')
727 return result
728
Chris Masone19e305e2014-03-14 15:13:46 -0700729
Eric Caruso8dc40982018-03-20 17:05:19 -0700730 def mount(self, user, password, create=False, async=True, key_label='bar'):
Elly Jones2f0ebba2011-10-27 13:43:20 -0400731 """Mounts a cryptohome.
732
733 Returns True if the mount succeeds or False otherwise.
Elly Jones2f0ebba2011-10-27 13:43:20 -0400734 """
Eric Caruso8dc40982018-03-20 17:05:19 -0700735 import rpc_pb2
736
737 acc = rpc_pb2.AccountIdentifier()
738 acc.account_id = user
739
740 auth = rpc_pb2.AuthorizationRequest()
741 auth.key.secret = password
742 auth.key.data.label = key_label
743
744 mount_req = rpc_pb2.MountRequest()
745 if create:
746 mount_req.create.copy_authorization_key = True
747
748 out = self.__call(self.iface.MountEx, acc.SerializeToString(),
749 auth.SerializeToString(), mount_req.SerializeToString())
750 parsed_out = rpc_pb2.BaseReply()
751 parsed_out.ParseFromString(''.join(map(chr, out)))
752 return parsed_out.error == rpc_pb2.CRYPTOHOME_ERROR_NOT_SET
Elly Jones2f0ebba2011-10-27 13:43:20 -0400753
Chris Masone19e305e2014-03-14 15:13:46 -0700754
Elly Jones2f0ebba2011-10-27 13:43:20 -0400755 def unmount(self, user):
756 """Unmounts a cryptohome.
757
758 Returns True if the unmount suceeds or false otherwise.
Elly Jones2f0ebba2011-10-27 13:43:20 -0400759 """
Eric Caruso8f148792019-02-26 14:35:31 -0800760 import rpc_pb2
761
762 req = rpc_pb2.UnmountRequest()
763
764 out = self.__call(self.iface.UnmountEx, req.SerializeToString())
765 parsed_out = rpc_pb2.BaseReply()
766 parsed_out.ParseFromString(''.join(map(chr, out)))
767 return parsed_out.error == rpc_pb2.CRYPTOHOME_ERROR_NOT_SET
Elly Jones2f0ebba2011-10-27 13:43:20 -0400768
Chris Masone19e305e2014-03-14 15:13:46 -0700769
Elly Jones2f0ebba2011-10-27 13:43:20 -0400770 def is_mounted(self, user):
771 """Tests whether a user's cryptohome is mounted."""
772 return (utils.is_mountpoint(user_path(user))
773 and utils.is_mountpoint(system_path(user)))
774
Chris Masone19e305e2014-03-14 15:13:46 -0700775
Elly Jones2f0ebba2011-10-27 13:43:20 -0400776 def require_mounted(self, user):
777 """Raises a test failure if a user's cryptohome is not mounted."""
778 utils.require_mountpoint(user_path(user))
779 utils.require_mountpoint(system_path(user))
Elly Jones4458f442012-04-16 15:42:56 -0400780
Chris Masone19e305e2014-03-14 15:13:46 -0700781
Will Drewry9e440792013-12-11 17:18:35 -0600782 def remove(self, user, async=True):
Leo Laicdabea52019-04-29 15:56:14 +0800783 """Removes a users cryptohome.
Greg Kerrec7ba582018-09-13 16:19:26 -0700784
785 Returns True if the operation succeeds or False otherwise.
786 """
787 import rpc_pb2
788
789 acc = rpc_pb2.AccountIdentifier()
790 acc.account_id = user
791
792 out = self.__call(self.iface.RemoveEx, acc.SerializeToString())
793 parsed_out = rpc_pb2.BaseReply()
794 parsed_out.ParseFromString(''.join(map(chr, out)))
795 return parsed_out.error == rpc_pb2.CRYPTOHOME_ERROR_NOT_SET
Chris Masone19e305e2014-03-14 15:13:46 -0700796
797
798 def ensure_clean_cryptohome_for(self, user, password=None):
799 """Ensure a fresh cryptohome exists for user.
800
801 @param user: user who needs a shiny new cryptohome.
802 @param password: if unset, a random password will be used.
803 """
804 if not password:
805 password = ''.join(random.sample(string.ascii_lowercase, 6))
806 self.remove(user)
807 self.mount(user, password, create=True)
Roman Sorokina45273e2017-12-20 12:03:27 +0100808
809 def lock_install_attributes(self, attrs):
810 """Set and lock install attributes for the device.
811
812 @param attrs: dict of install attributes.
813 """
Roman Sorokin0a228d12018-01-23 12:36:45 +0100814 take_tpm_ownership()
Roman Sorokin58312b22018-10-17 13:34:39 +0200815 self.wait_for_install_attributes_ready()
Roman Sorokina45273e2017-12-20 12:03:27 +0100816 for key, value in attrs.items():
817 if not self.__call(self.iface.InstallAttributesSet, key,
818 dbus.ByteArray(value + '\0')):
819 return False
820 return self.__call(self.iface.InstallAttributesFinalize)
Roman Sorokin58312b22018-10-17 13:34:39 +0200821
822 def wait_for_install_attributes_ready(self):
823 """Wait until install attributes are ready.
824 """
825 utils.poll_for_condition(
826 lambda: self.__call(self.iface.InstallAttributesIsReady),
827 timeout=300,
828 exception=error.TestError(
829 'Timeout waiting for install attributes are ready'))