blob: dce1f721fb4ffe09e8016242c30cacf22e4c843c [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
Derek Beckett31554d22020-12-11 11:21:30 -080019from autotest_lib.client.cros.tpm import *
Will Drewry9e440792013-12-11 17:18:35 -060020from autotest_lib.client.cros.cros_disks import DBusClient
Eric Lic4d8f4a2010-12-10 09:49:23 -080021
Yi Chou2167d362020-12-14 17:41:10 +080022ATTESTATION_CMD = '/usr/bin/attestation_client'
Sean Oe5d8fd02010-09-30 10:44:44 +020023CRYPTOHOME_CMD = '/usr/sbin/cryptohome'
Yi Chou2167d362020-12-14 17:41:10 +080024TPM_MANAGER_CMD = '/usr/bin/tpm_manager_client'
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040025GUEST_USER_NAME = '$guest'
Kris Rambishbb5258c2014-12-16 16:51:17 -080026UNAVAILABLE_ACTION = 'Unknown action or no action given.'
Alexis Saveryccb16be2017-02-01 16:23:15 -080027MOUNT_RETRY_COUNT = 20
Dan Spaidbe9789c2017-05-19 15:18:42 +090028TEMP_MOUNT_PATTERN = '/home/.shadow/%s/temporary_mount'
29VAULT_PATH_PATTERN = '/home/.shadow/%s/vault'
Sean Oe5d8fd02010-09-30 10:44:44 +020030
Eric Caruso8dc40982018-03-20 17:05:19 -070031DBUS_PROTOS_DEP = 'dbus_protos'
32
33
Sean Oe5d8fd02010-09-30 10:44:44 +020034def get_user_hash(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +020035 """Get the user hash for the given user."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040036 return utils.system_output(['cryptohome', '--action=obfuscate_user',
37 '--user=%s' % user])
Sean Oe5d8fd02010-09-30 10:44:44 +020038
39
barfab@chromium.org5c374632012-04-05 16:50:56 +020040def user_path(user):
41 """Get the user mount point for the given user."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040042 return utils.system_output(['cryptohome-path', 'user', user])
barfab@chromium.org5c374632012-04-05 16:50:56 +020043
44
45def system_path(user):
46 """Get the system mount point for the given user."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040047 return utils.system_output(['cryptohome-path', 'system', user])
barfab@chromium.org5c374632012-04-05 16:50:56 +020048
49
Dan Spaidbe9789c2017-05-19 15:18:42 +090050def temporary_mount_path(user):
51 """Get the vault mount path used during crypto-migration for the user.
52
53 @param user: user the temporary mount should be for
54 """
55 return TEMP_MOUNT_PATTERN % (get_user_hash(user))
56
57
58def vault_path(user):
59 """ Get the vault path for the given user.
60
61 @param user: The user who's vault path should be returned.
62 """
63 return VAULT_PATH_PATTERN % (get_user_hash(user))
64
65
Chris Masone5d010aa2013-05-06 14:38:42 -070066def ensure_clean_cryptohome_for(user, password=None):
67 """Ensure a fresh cryptohome exists for user.
68
69 @param user: user who needs a shiny new cryptohome.
70 @param password: if unset, a random password will be used.
71 """
72 if not password:
73 password = ''.join(random.sample(string.ascii_lowercase, 6))
Eric Caruso272193b2018-02-05 14:02:11 -080074 unmount_vault(user)
Chris Masone5d010aa2013-05-06 14:38:42 -070075 remove_vault(user)
76 mount_vault(user, password, create=True)
77
78
Yi Chou2167d362020-12-14 17:41:10 +080079def get_tpm_password():
80 """Get the TPM password.
Brian Norriscd51d8d2020-12-17 00:53:41 +000081
82 Returns:
Yi Chou2167d362020-12-14 17:41:10 +080083 A TPM password
84 """
Derek Beckett31554d22020-12-11 11:21:30 -080085 out = run_cmd(TPM_MANAGER_CMD + ' status')
Yi Chou2167d362020-12-14 17:41:10 +080086 match = re.search('owner_password: (\w*)', out)
87 password = ''
88 if match:
Yi Chou4f8455b2021-01-05 16:18:50 +080089 hex_pass = match.group(1)
Yi Chou2167d362020-12-14 17:41:10 +080090 password = ''.join(
91 chr(int(hex_pass[i:i + 2], 16))
92 for i in range(0, len(hex_pass), 2))
93 return password
94
95
Mary Ruthven9a0ce562017-05-30 13:01:47 -070096def get_fwmp(cleared_fwmp=False):
97 """Get the firmware management parameters.
98
99 Args:
100 cleared_fwmp: True if the space should not exist.
101
102 Returns:
103 The dictionary with the FWMP contents, for example:
104 { 'flags': 0xbb41,
105 'developer_key_hash':
106 "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\
107 000\000\000\000\000\000\000\000\000\000\000",
108 }
109 or a dictionary with the Error if the FWMP doesn't exist and
110 cleared_fwmp is True
111 { 'error': 'CRYPTOHOME_ERROR_FIRMWARE_MANAGEMENT_PARAMETERS_INVALID' }
112
113 Raises:
114 ChromiumOSError if any expected field is not found in the cryptohome
115 output. This would typically happen when FWMP state does not match
116 'clreared_fwmp'
117 """
Derek Beckett31554d22020-12-11 11:21:30 -0800118 out = run_cmd(CRYPTOHOME_CMD +
Mary Ruthven9a0ce562017-05-30 13:01:47 -0700119 ' --action=get_firmware_management_parameters')
120
121 if cleared_fwmp:
122 fields = ['error']
123 else:
124 fields = ['flags', 'developer_key_hash']
125
126 status = {}
127 for field in fields:
128 match = re.search('%s: (\S+)\n' % field, out)
129 if not match:
130 raise ChromiumOSError('Invalid FWMP field %s: "%s".' %
131 (field, out))
132 status[field] = match.group(1)
133 return status
134
135
136def set_fwmp(flags, developer_key_hash=None):
137 """Set the firmware management parameter contents.
138
139 Args:
140 developer_key_hash: a string with the developer key hash
141
142 Raises:
143 ChromiumOSError cryptohome cannot set the FWMP contents
144 """
145 cmd = (CRYPTOHOME_CMD +
146 ' --action=set_firmware_management_parameters '
147 '--flags=' + flags)
148 if developer_key_hash:
149 cmd += ' --developer_key_hash=' + developer_key_hash
150
Derek Beckett31554d22020-12-11 11:21:30 -0800151 out = run_cmd(cmd)
Mary Ruthven9a0ce562017-05-30 13:01:47 -0700152 if 'SetFirmwareManagementParameters success' not in out:
153 raise ChromiumOSError('failed to set FWMP: %s' % out)
154
155
Kris Rambish82ee1c02014-12-10 17:02:39 -0800156def is_tpm_lockout_in_effect():
157 """Returns true if the TPM lockout is in effect; false otherwise."""
Yi Chou2167d362020-12-14 17:41:10 +0800158 status = get_tpm_da_info()
Christopher Wiley94fd6b32014-12-13 18:52:03 -0800159 return status.get('dictionary_attack_lockout_in_effect', None)
Kris Rambish82ee1c02014-12-10 17:02:39 -0800160
161
David Pursell2a2ef342014-10-17 10:34:56 -0700162def get_login_status():
163 """Query the login status
164
165 Returns:
166 A login status dictionary containing:
Yi Chou6e287442021-05-27 10:53:00 +0800167 { 'owner_user_exists': True|False }
David Pursell2a2ef342014-10-17 10:34:56 -0700168 """
Derek Beckett31554d22020-12-11 11:21:30 -0800169 out = run_cmd(CRYPTOHOME_CMD + ' --action=get_login_status')
David Pursell2a2ef342014-10-17 10:34:56 -0700170 status = {}
Yi Chou6e287442021-05-27 10:53:00 +0800171 for field in ['owner_user_exists']:
David Pursell2a2ef342014-10-17 10:34:56 -0700172 match = re.search('%s: (true|false)' % field, out)
173 if not match:
174 raise ChromiumOSError('Invalid login status: "%s".' % out)
175 status[field] = match.group(1) == 'true'
176 return status
177
178
Yi Chou2167d362020-12-14 17:41:10 +0800179def get_install_attribute_status():
180 """Query the install attribute status
181
182 Returns:
183 A status string, which could be:
184 "UNKNOWN"
185 "TPM_NOT_OWNED"
186 "FIRST_INSTALL"
187 "VALID"
188 "INVALID"
189 """
Derek Beckett31554d22020-12-11 11:21:30 -0800190 out = run_cmd(CRYPTOHOME_CMD + ' --action=install_attributes_get_status')
Yi Chou2167d362020-12-14 17:41:10 +0800191 return out.strip()
192
193
Darren Krahn5f880f62012-10-02 15:17:59 -0700194def get_tpm_attestation_status():
195 """Get the TPM attestation status. Works similar to get_tpm_status().
196 """
Derek Beckett31554d22020-12-11 11:21:30 -0800197 out = run_cmd(ATTESTATION_CMD + ' status')
Darren Krahn5f880f62012-10-02 15:17:59 -0700198 status = {}
Yi Chou2167d362020-12-14 17:41:10 +0800199 for field in ['prepared_for_enrollment', 'enrolled']:
200 match = re.search('%s: (true|false)' % field, out)
Darren Krahn5f880f62012-10-02 15:17:59 -0700201 if not match:
202 raise ChromiumOSError('Invalid attestation status: "%s".' % out)
203 status[field] = match.group(1) == 'true'
204 return status
205
206
Eric Caruso6da07a02018-02-07 16:02:41 -0800207def take_tpm_ownership(wait_for_ownership=True):
Frank Farzand5e36312012-01-13 14:34:03 -0800208 """Take TPM owernship.
209
Eric Caruso6da07a02018-02-07 16:02:41 -0800210 Args:
211 wait_for_ownership: block until TPM is owned if true
Frank Farzand5e36312012-01-13 14:34:03 -0800212 """
Derek Beckett31554d22020-12-11 11:21:30 -0800213 run_cmd(CRYPTOHOME_CMD + ' --action=tpm_take_ownership')
Eric Caruso6da07a02018-02-07 16:02:41 -0800214 if wait_for_ownership:
Maksim Ivanov21c967a2018-03-22 21:24:41 +0100215 # Note that waiting for the 'Ready' flag is more correct than waiting
216 # for the 'Owned' flag, as the latter is set by cryptohomed before some
217 # of the ownership tasks are completed.
218 utils.poll_for_condition(
219 lambda: get_tpm_status()['Ready'],
220 timeout=300,
221 exception=error.TestError('Timeout waiting for TPM ownership'))
Frank Farzand5e36312012-01-13 14:34:03 -0800222
223
Darren Krahn0e73e7f2012-09-05 15:35:15 -0700224def verify_ek():
225 """Verify the TPM endorsement key.
226
227 Returns true if EK is valid.
228 """
229 cmd = CRYPTOHOME_CMD + ' --action=tpm_verify_ek'
230 return (utils.system(cmd, ignore_status=True) == 0)
231
232
Sean Oe5d8fd02010-09-30 10:44:44 +0200233def remove_vault(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200234 """Remove the given user's vault from the shadow directory."""
Sean Oe5d8fd02010-09-30 10:44:44 +0200235 logging.debug('user is %s', user)
236 user_hash = get_user_hash(user)
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700237 logging.debug('Removing vault for user %s with hash %s', user, user_hash)
Sean Oe5d8fd02010-09-30 10:44:44 +0200238 cmd = CRYPTOHOME_CMD + ' --action=remove --force --user=%s' % user
Derek Beckett31554d22020-12-11 11:21:30 -0800239 run_cmd(cmd)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200240 # Ensure that the vault does not exist.
241 if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
Darren Krahne6c44b92014-03-31 12:11:08 -0700242 raise ChromiumOSError('Cryptohome could not remove the user\'s vault.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200243
244
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200245def remove_all_vaults():
246 """Remove any existing vaults from the shadow directory.
247
248 This function must be run with root privileges.
249 """
barfab@chromium.org5c374632012-04-05 16:50:56 +0200250 for item in os.listdir(constants.SHADOW_ROOT):
251 abs_item = os.path.join(constants.SHADOW_ROOT, item)
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200252 if os.path.isdir(os.path.join(abs_item, 'vault')):
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700253 logging.debug('Removing vault for user with hash %s', item)
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200254 shutil.rmtree(abs_item)
255
256
Allen Webb8b4eb622018-07-20 15:46:11 -0700257def mount_vault(user, password, create=False, key_label=None):
Eric Caruso22acd672018-02-01 17:55:28 -0800258 """Mount the given user's vault. Mounts should be created by calling this
259 function with create=True, and can be used afterwards with create=False.
260 Only try to mount existing vaults created with this function.
261
262 """
263 args = [CRYPTOHOME_CMD, '--action=mount_ex', '--user=%s' % user,
Chris Masone3543e512013-11-04 13:09:30 -0800264 '--password=%s' % password, '--async']
Sean Oe5d8fd02010-09-30 10:44:44 +0200265 if create:
Allen Webb8b4eb622018-07-20 15:46:11 -0700266 args += ['--create']
267 if key_label is None:
268 key_label = 'bar'
269 if key_label is not None:
270 args += ['--key_label=%s' % key_label]
Derek Beckett31554d22020-12-11 11:21:30 -0800271 logging.info(run_cmd(' '.join(args)))
barfab@chromium.org5c374632012-04-05 16:50:56 +0200272 # Ensure that the vault exists in the shadow directory.
Sean Oe5d8fd02010-09-30 10:44:44 +0200273 user_hash = get_user_hash(user)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200274 if not os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
Alexis Saveryccb16be2017-02-01 16:23:15 -0800275 retry = 0
276 mounted = False
277 while retry < MOUNT_RETRY_COUNT and not mounted:
278 time.sleep(1)
Yi Chou2167d362020-12-14 17:41:10 +0800279 logging.info("Retry %s", str(retry + 1))
Derek Beckett31554d22020-12-11 11:21:30 -0800280 run_cmd(' '.join(args))
Alexis Saveryccb16be2017-02-01 16:23:15 -0800281 # TODO: Remove this additional call to get_user_hash(user) when
282 # crbug.com/690994 is fixed
283 user_hash = get_user_hash(user)
284 if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
285 mounted = True
286 retry += 1
287 if not mounted:
288 raise ChromiumOSError('Cryptohome vault not found after mount.')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200289 # Ensure that the vault is mounted.
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700290 if not is_permanent_vault_mounted(user=user, allow_fail=True):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200291 raise ChromiumOSError('Cryptohome created a vault but did not mount.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200292
293
Chris Masone5d010aa2013-05-06 14:38:42 -0700294def mount_guest():
Sergey Poromov533008f2017-10-13 14:07:43 +0200295 """Mount the guest vault."""
Greg Kerrd7cdc132018-06-08 11:55:40 -0700296 args = [CRYPTOHOME_CMD, '--action=mount_guest_ex']
Derek Beckett31554d22020-12-11 11:21:30 -0800297 logging.info(run_cmd(' '.join(args)))
Sergey Poromov533008f2017-10-13 14:07:43 +0200298 # Ensure that the guest vault is mounted.
Chris Masone5d010aa2013-05-06 14:38:42 -0700299 if not is_guest_vault_mounted(allow_fail=True):
Sergey Poromov533008f2017-10-13 14:07:43 +0200300 raise ChromiumOSError('Cryptohome did not mount guest vault.')
Chris Masone5d010aa2013-05-06 14:38:42 -0700301
302
Sean Oe5d8fd02010-09-30 10:44:44 +0200303def test_auth(user, password):
Yi Chou2167d362020-12-14 17:41:10 +0800304 """Test key auth."""
Eric Carusode07cf82018-02-12 15:34:02 -0800305 cmd = [CRYPTOHOME_CMD, '--action=check_key_ex', '--user=%s' % user,
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400306 '--password=%s' % password, '--async']
Derek Beckett31554d22020-12-11 11:21:30 -0800307 out = run_cmd(' '.join(cmd))
Eric Carusode07cf82018-02-12 15:34:02 -0800308 logging.info(out)
309 return 'Key authenticated.' in out
Sean Oe5d8fd02010-09-30 10:44:44 +0200310
311
Allen Webb8b4eb622018-07-20 15:46:11 -0700312def add_le_key(user, password, new_password, new_key_label):
Yi Chou2167d362020-12-14 17:41:10 +0800313 """Add low entropy key."""
Allen Webb8b4eb622018-07-20 15:46:11 -0700314 args = [CRYPTOHOME_CMD, '--action=add_key_ex', '--key_policy=le',
315 '--user=%s' % user, '--password=%s' % password,
316 '--new_key_label=%s' % new_key_label,
317 '--new_password=%s' % new_password]
Derek Beckett31554d22020-12-11 11:21:30 -0800318 logging.info(run_cmd(' '.join(args)))
Allen Webb8b4eb622018-07-20 15:46:11 -0700319
320
321def remove_key(user, password, remove_key_label):
Yi Chou2167d362020-12-14 17:41:10 +0800322 """Remove a key."""
Allen Webb8b4eb622018-07-20 15:46:11 -0700323 args = [CRYPTOHOME_CMD, '--action=remove_key_ex', '--user=%s' % user,
324 '--password=%s' % password,
325 '--remove_key_label=%s' % remove_key_label]
Derek Beckett31554d22020-12-11 11:21:30 -0800326 logging.info(run_cmd(' '.join(args)))
Allen Webb8b4eb622018-07-20 15:46:11 -0700327
328
329def get_supported_key_policies():
Yi Chou2167d362020-12-14 17:41:10 +0800330 """Get supported key policies."""
Allen Webb8b4eb622018-07-20 15:46:11 -0700331 args = [CRYPTOHOME_CMD, '--action=get_supported_key_policies']
Derek Beckett31554d22020-12-11 11:21:30 -0800332 out = run_cmd(' '.join(args))
Allen Webb8b4eb622018-07-20 15:46:11 -0700333 logging.info(out)
334 policies = {}
335 for line in out.splitlines():
336 match = re.search(' ([^:]+): (true|false)', line)
337 if match:
338 policies[match.group(1)] = match.group(2) == 'true'
339 return policies
340
341
342def unmount_vault(user=None):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200343 """Unmount the given user's vault.
344
345 Once unmounting for a specific user is supported, the user parameter will
346 name the target user. See crosbug.com/20778.
Elly Jones686c2f42011-10-24 16:45:07 -0400347 """
Derek Beckett31554d22020-12-11 11:21:30 -0800348 run_cmd(CRYPTOHOME_CMD + ' --action=unmount')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200349 # Ensure that the vault is not mounted.
Allen Webb8b4eb622018-07-20 15:46:11 -0700350 if user is not None and is_vault_mounted(user, allow_fail=True):
Sean Oe5d8fd02010-09-30 10:44:44 +0200351 raise ChromiumOSError('Cryptohome did not unmount the user.')
352
353
barfab@chromium.org5c374632012-04-05 16:50:56 +0200354def __get_mount_info(mount_point, allow_fail=False):
355 """Get information about the active mount at a given mount point."""
beeps569f8672013-08-07 10:18:51 -0700356 cryptohomed_path = '/proc/$(pgrep cryptohomed)/mounts'
Jorge Lucangeli Obesf7627af2020-03-19 15:11:01 -0400357 # 'cryptohome-namespace-mounter' is currently only used for Guest sessions.
358 mounter_exe = 'cryptohome-namespace-mounter'
359 mounter_pid = 'pgrep -o -f %s' % mounter_exe
360 mounter_path = '/proc/$(%s)/mounts' % mounter_pid
361
362 status = utils.system(mounter_pid, ignore_status=True)
363 # Only check for these mounts if the mounter executable is running.
364 if status == 0:
365 try:
366 logging.debug('Active %s mounts:\n' % mounter_exe +
367 utils.system_output('cat %s' % mounter_path))
368 ns_mount_line = utils.system_output(
369 'grep %s %s' % (mount_point, mounter_path),
370 ignore_status=allow_fail)
371 except Exception as e:
372 logging.error(e)
373 raise ChromiumOSError('Could not get info about cryptohome vault '
374 'through %s. See logs for complete '
375 'mount-point.'
376 % os.path.dirname(str(mount_point)))
377 return ns_mount_line.split()
378
beeps569f8672013-08-07 10:18:51 -0700379 try:
Yi Chou2167d362020-12-14 17:41:10 +0800380 logging.debug('Active cryptohome mounts:\n%s',
Daniel Erat2ec32792017-01-31 18:26:59 -0700381 utils.system_output('cat %s' % cryptohomed_path))
beeps569f8672013-08-07 10:18:51 -0700382 mount_line = utils.system_output(
383 'grep %s %s' % (mount_point, cryptohomed_path),
384 ignore_status=allow_fail)
385 except Exception as e:
386 logging.error(e)
387 raise ChromiumOSError('Could not get info about cryptohome vault '
388 'through %s. See logs for complete mount-point.'
389 % os.path.dirname(str(mount_point)))
Sourav Poddar574bd622010-05-26 14:22:26 +0530390 return mount_line.split()
391
392
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400393def __get_user_mount_info(user, allow_fail=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200394 """Get information about the active mounts for a given user.
395
396 Returns the active mounts at the user's user and system mount points. If no
397 user is given, the active mount at the shared mount point is returned
398 (regular users have a bind-mount at this mount point for backwards
399 compatibility; the guest user has a mount at this mount point only).
400 """
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400401 return [__get_mount_info(mount_point=user_path(user),
402 allow_fail=allow_fail),
403 __get_mount_info(mount_point=system_path(user),
404 allow_fail=allow_fail)]
Jim Hebertf08f88d2011-04-22 10:33:49 -0700405
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700406def is_vault_mounted(user, regexes=None, allow_fail=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200407 """Check whether a vault is mounted for the given user.
408
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700409 user: If no user is given, the shared mount point is checked, determining
410 whether a vault is mounted for any user.
411 regexes: dictionary of regexes to matches against the mount information.
412 The mount filesystem for the user's user and system mounts point must
413 match one of the keys.
414 The mount source point must match the selected device regex.
415
416 In addition, if mounted over ext4, we check the directory is encrypted.
barfab@chromium.org5c374632012-04-05 16:50:56 +0200417 """
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700418 if regexes is None:
419 regexes = {
420 constants.CRYPTOHOME_FS_REGEX_ANY :
421 constants.CRYPTOHOME_DEV_REGEX_ANY
422 }
barfab@chromium.org5c374632012-04-05 16:50:56 +0200423 user_mount_info = __get_user_mount_info(user=user, allow_fail=allow_fail)
424 for mount_info in user_mount_info:
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700425 # Look at each /proc/../mount lines that match mount point for a given
426 # user user/system mount (/home/user/.... /home/root/...)
427
428 # We should have at least 3 arguments (source, mount, type of mount)
429 if len(mount_info) < 3:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200430 return False
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700431
432 device_regex = None
433 for fs_regex in regexes.keys():
434 if re.match(fs_regex, mount_info[2]):
435 device_regex = regexes[fs_regex]
436 break
437
438 if not device_regex:
Jorge Lucangeli Obesf7627af2020-03-19 15:11:01 -0400439 # The third argument in not the expected mount point type.
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700440 return False
441
442 # Check if the mount source match the device regex: it can be loose,
443 # (anything) or stricter if we expect guest filesystem.
444 if not re.match(device_regex, mount_info[0]):
445 return False
446
barfab@chromium.org5c374632012-04-05 16:50:56 +0200447 return True
Sourav Poddar574bd622010-05-26 14:22:26 +0530448
449
barfab@chromium.org5c374632012-04-05 16:50:56 +0200450def is_guest_vault_mounted(allow_fail=False):
Sergey Poromov533008f2017-10-13 14:07:43 +0200451 """Check whether a vault is mounted for the guest user.
Sergey Poromovd85dce52017-12-27 11:10:51 +0100452 It should be a mount of an ext4 partition on a loop device
453 or be backed by tmpfs.
Sergey Poromov533008f2017-10-13 14:07:43 +0200454 """
barfab@chromium.org5c374632012-04-05 16:50:56 +0200455 return is_vault_mounted(
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400456 user=GUEST_USER_NAME,
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700457 regexes={
Sergey Poromovd85dce52017-12-27 11:10:51 +0100458 # Remove tmpfs support when it becomes unnecessary as all guest
459 # modes will use ext4 on a loop device.
Sergey Poromov533008f2017-10-13 14:07:43 +0200460 constants.CRYPTOHOME_FS_REGEX_EXT4 :
461 constants.CRYPTOHOME_DEV_REGEX_LOOP_DEVICE,
Sergey Poromovd85dce52017-12-27 11:10:51 +0100462 constants.CRYPTOHOME_FS_REGEX_TMPFS :
463 constants.CRYPTOHOME_DEV_REGEX_GUEST,
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700464 },
barfab@chromium.org5c374632012-04-05 16:50:56 +0200465 allow_fail=allow_fail)
466
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700467def is_permanent_vault_mounted(user, allow_fail=False):
468 """Check if user is mounted over ecryptfs or ext4 crypto. """
469 return is_vault_mounted(
470 user=user,
471 regexes={
472 constants.CRYPTOHOME_FS_REGEX_ECRYPTFS :
473 constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER_SHADOW,
474 constants.CRYPTOHOME_FS_REGEX_EXT4 :
475 constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER_DEVICE,
476 },
477 allow_fail=allow_fail)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200478
Kazuhiro Inabaa3bf6452017-02-08 11:41:50 +0900479def get_mounted_vault_path(user, allow_fail=False):
480 """Get the path where the decrypted data for the user is located."""
481 return os.path.join(constants.SHADOW_ROOT, get_user_hash(user), 'mount')
Nirnimesh66814492011-06-27 18:00:33 -0700482
483
484def canonicalize(credential):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200485 """Perform basic canonicalization of |email_address|.
Nirnimesh66814492011-06-27 18:00:33 -0700486
barfab@chromium.org5c374632012-04-05 16:50:56 +0200487 Perform basic canonicalization of |email_address|, taking into account that
488 gmail does not consider '.' or caps inside a username to matter. It also
489 ignores everything after a '+'. For example,
490 c.masone+abc@gmail.com == cMaSone@gmail.com, per
Nirnimesh66814492011-06-27 18:00:33 -0700491 http://mail.google.com/support/bin/answer.py?hl=en&ctx=mail&answer=10313
492 """
493 if not credential:
Yi Chou2167d362020-12-14 17:41:10 +0800494 return None
Nirnimesh66814492011-06-27 18:00:33 -0700495
496 parts = credential.split('@')
497 if len(parts) != 2:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200498 raise error.TestError('Malformed email: ' + credential)
Nirnimesh66814492011-06-27 18:00:33 -0700499
500 (name, domain) = parts
501 name = name.partition('+')[0]
barfab@chromium.org5c374632012-04-05 16:50:56 +0200502 if (domain == constants.SPECIAL_CASE_DOMAIN):
Nirnimesh66814492011-06-27 18:00:33 -0700503 name = name.replace('.', '')
504 return '@'.join([name, domain]).lower()
Elly Jones686c2f42011-10-24 16:45:07 -0400505
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200506
Will Drewryd2fed972013-12-05 16:35:51 -0600507def crash_cryptohomed():
Yi Chou2167d362020-12-14 17:41:10 +0800508 """Let cryptohome crash."""
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600509 # Try to kill cryptohomed so we get something to work with.
Derek Beckett31554d22020-12-11 11:21:30 -0800510 pid = run_cmd('pgrep cryptohomed')
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600511 try:
Will Drewry9e440792013-12-11 17:18:35 -0600512 pid = int(pid)
Derek Beckett1091ed12020-10-19 10:47:16 -0700513 except ValueError as e: # empty or invalid string
Will Drewry9e440792013-12-11 17:18:35 -0600514 raise error.TestError('Cryptohomed was not running')
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600515 utils.system('kill -ABRT %d' % pid)
516 # CONT just in case cryptohomed had a spurious STOP.
517 utils.system('kill -CONT %d' % pid)
518 utils.poll_for_condition(
519 lambda: utils.system('ps -p %d' % pid,
520 ignore_status=True) != 0,
Will Drewry934d1532014-01-30 16:23:17 -0600521 timeout=180,
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600522 exception=error.TestError(
523 'Timeout waiting for cryptohomed to coredump'))
524
Will Drewryd2fed972013-12-05 16:35:51 -0600525
Dan Spaidbe9789c2017-05-19 15:18:42 +0900526def create_ecryptfs_homedir(user, password):
527 """Creates a new home directory as ecryptfs.
528
529 If a home directory for the user exists already, it will be removed.
530 The resulting home directory will be mounted.
531
532 @param user: Username to create the home directory for.
533 @param password: Password to use when creating the home directory.
534 """
535 unmount_vault(user)
536 remove_vault(user)
537 args = [
538 CRYPTOHOME_CMD,
539 '--action=mount_ex',
540 '--user=%s' % user,
541 '--password=%s' % password,
542 '--key_label=foo',
543 '--ecryptfs',
544 '--create']
Derek Beckett31554d22020-12-11 11:21:30 -0800545 logging.info(run_cmd(' '.join(args)))
Dan Spaidbe9789c2017-05-19 15:18:42 +0900546 if not is_vault_mounted(user, regexes={
547 constants.CRYPTOHOME_FS_REGEX_ECRYPTFS :
548 constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER_SHADOW
549 }, allow_fail=True):
550 raise ChromiumOSError('Ecryptfs home could not be created')
551
552
553def do_dircrypto_migration(user, password, timeout=600):
554 """Start dircrypto migration for the user.
555
556 @param user: The user to migrate.
557 @param password: The password used to mount the users vault
558 @param timeout: How long in seconds to wait for the migration to finish
559 before failing.
560 """
561 unmount_vault(user)
562 args = [
563 CRYPTOHOME_CMD,
564 '--action=mount_ex',
565 '--to_migrate_from_ecryptfs',
566 '--user=%s' % user,
567 '--password=%s' % password]
Derek Beckett31554d22020-12-11 11:21:30 -0800568 logging.info(run_cmd(' '.join(args)))
Dan Spaidbe9789c2017-05-19 15:18:42 +0900569 if not __get_mount_info(temporary_mount_path(user), allow_fail=True):
570 raise ChromiumOSError('Failed to mount home for migration')
571 args = [CRYPTOHOME_CMD, '--action=migrate_to_dircrypto', '--user=%s' % user]
Derek Beckett31554d22020-12-11 11:21:30 -0800572 logging.info(run_cmd(' '.join(args)))
Dan Spaidbe9789c2017-05-19 15:18:42 +0900573 utils.poll_for_condition(
574 lambda: not __get_mount_info(
575 temporary_mount_path(user), allow_fail=True),
576 timeout=timeout,
577 exception=error.TestError(
578 'Timeout waiting for dircrypto migration to finish'))
579
580
Eric Caruso014d5ed2018-02-01 16:24:41 -0800581def change_password(user, password, new_password):
Yi Chou2167d362020-12-14 17:41:10 +0800582 """Change user password."""
Eric Caruso014d5ed2018-02-01 16:24:41 -0800583 args = [
584 CRYPTOHOME_CMD,
Greg Kerr98aa75c2018-06-05 15:27:12 -0700585 '--action=migrate_key_ex',
Eric Caruso014d5ed2018-02-01 16:24:41 -0800586 '--user=%s' % user,
587 '--old_password=%s' % password,
588 '--password=%s' % new_password]
Derek Beckett31554d22020-12-11 11:21:30 -0800589 out = run_cmd(' '.join(args))
Eric Caruso43ec57e2018-02-05 15:54:49 -0800590 logging.info(out)
591 if 'Key migration succeeded.' not in out:
592 raise ChromiumOSError('Key migration failed.')
Eric Caruso014d5ed2018-02-01 16:24:41 -0800593
594
Will Drewry9e440792013-12-11 17:18:35 -0600595class CryptohomeProxy(DBusClient):
596 """A DBus proxy client for testing the Cryptohome DBus server.
597 """
598 CRYPTOHOME_BUS_NAME = 'org.chromium.Cryptohome'
599 CRYPTOHOME_OBJECT_PATH = '/org/chromium/Cryptohome'
600 CRYPTOHOME_INTERFACE = 'org.chromium.CryptohomeInterface'
601 ASYNC_CALL_STATUS_SIGNAL = 'AsyncCallStatus'
602 ASYNC_CALL_STATUS_SIGNAL_ARGUMENTS = (
603 'async_id', 'return_status', 'return_code'
604 )
605 DBUS_PROPERTIES_INTERFACE = 'org.freedesktop.DBus.Properties'
606
Lutz Justenb9275782018-06-20 18:42:22 +0200607 # Default timeout in seconds for the D-Bus connection.
608 DEFAULT_DBUS_TIMEOUT = 30
Chris Masone19e305e2014-03-14 15:13:46 -0700609
Lutz Justenb9275782018-06-20 18:42:22 +0200610 def __init__(self, bus_loop=None, autodir=None, job=None,
611 timeout=DEFAULT_DBUS_TIMEOUT):
Eric Caruso8dc40982018-03-20 17:05:19 -0700612 if autodir and job:
613 # Install D-Bus protos necessary for some methods.
614 dep_dir = os.path.join(autodir, 'deps', DBUS_PROTOS_DEP)
615 job.install_pkg(DBUS_PROTOS_DEP, 'dep', dep_dir)
616 sys.path.append(dep_dir)
617
618 # Set up D-Bus main loop and interface.
Will Drewry9e440792013-12-11 17:18:35 -0600619 self.main_loop = gobject.MainLoop()
Will Drewry78db9dc2014-04-01 16:34:23 -0500620 if bus_loop is None:
Chris Masone64170f82014-03-14 15:47:05 -0700621 bus_loop = DBusGMainLoop(set_as_default=True)
Will Drewry9e440792013-12-11 17:18:35 -0600622 self.bus = dbus.SystemBus(mainloop=bus_loop)
623 super(CryptohomeProxy, self).__init__(self.main_loop, self.bus,
624 self.CRYPTOHOME_BUS_NAME,
Lutz Justen1c6be452018-05-29 13:37:00 +0200625 self.CRYPTOHOME_OBJECT_PATH,
626 timeout)
Will Drewry9e440792013-12-11 17:18:35 -0600627 self.iface = dbus.Interface(self.proxy_object,
628 self.CRYPTOHOME_INTERFACE)
629 self.properties = dbus.Interface(self.proxy_object,
630 self.DBUS_PROPERTIES_INTERFACE)
631 self.handle_signal(self.CRYPTOHOME_INTERFACE,
632 self.ASYNC_CALL_STATUS_SIGNAL,
633 self.ASYNC_CALL_STATUS_SIGNAL_ARGUMENTS)
Elly Jones2f0ebba2011-10-27 13:43:20 -0400634
Chris Masone19e305e2014-03-14 15:13:46 -0700635
Will Drewryd2fed972013-12-05 16:35:51 -0600636 # Wrap all proxied calls to catch cryptohomed failures.
637 def __call(self, method, *args):
638 try:
Chris Masonef59d9df2014-03-14 12:05:32 -0700639 return method(*args, timeout=180)
Derek Beckett1091ed12020-10-19 10:47:16 -0700640 except dbus.exceptions.DBusException as e:
Will Drewryd2fed972013-12-05 16:35:51 -0600641 if e.get_dbus_name() == 'org.freedesktop.DBus.Error.NoReply':
642 logging.error('Cryptohome is not responding. Sending ABRT')
643 crash_cryptohomed()
644 raise ChromiumOSError('cryptohomed aborted. Check crashes!')
645 raise e
646
Chris Masone19e305e2014-03-14 15:13:46 -0700647
Will Drewry9e440792013-12-11 17:18:35 -0600648 def __wait_for_specific_signal(self, signal, data):
Yi Chou2167d362020-12-14 17:41:10 +0800649 """Wait for the |signal| with matching |data|
650 Returns the resulting dict on success or {} on error.
651 """
652 # Do not bubble up the timeout here, just return {}.
653 result = {}
654 try:
655 result = self.wait_for_signal(signal)
656 except utils.TimeoutError:
Will Drewry9e440792013-12-11 17:18:35 -0600657 return {}
Yi Chou2167d362020-12-14 17:41:10 +0800658 for k in data.keys():
659 if k not in result or result[k] != data[k]:
660 return {}
661 return result
Will Drewry9e440792013-12-11 17:18:35 -0600662
Chris Masone19e305e2014-03-14 15:13:46 -0700663
Will Drewry9e440792013-12-11 17:18:35 -0600664 # Perform a data-less async call.
665 # TODO(wad) Add __async_data_call.
666 def __async_call(self, method, *args):
Will Drewryfef135a2014-05-23 16:02:14 -0500667 # Clear out any superfluous async call signals.
668 self.clear_signal_content(self.ASYNC_CALL_STATUS_SIGNAL)
Will Drewry9e440792013-12-11 17:18:35 -0600669 out = self.__call(method, *args)
670 logging.debug('Issued call ' + str(method) +
671 ' with async_id ' + str(out))
672 result = {}
673 try:
Will Drewry934d1532014-01-30 16:23:17 -0600674 # __wait_for_specific_signal has a 10s timeout
Will Drewry9e440792013-12-11 17:18:35 -0600675 result = utils.poll_for_condition(
676 lambda: self.__wait_for_specific_signal(
677 self.ASYNC_CALL_STATUS_SIGNAL, {'async_id' : out}),
Will Drewry934d1532014-01-30 16:23:17 -0600678 timeout=180,
Will Drewry9e440792013-12-11 17:18:35 -0600679 desc='matching %s signal' % self.ASYNC_CALL_STATUS_SIGNAL)
Derek Beckett1091ed12020-10-19 10:47:16 -0700680 except utils.TimeoutError as e:
Will Drewry9e440792013-12-11 17:18:35 -0600681 logging.error('Cryptohome timed out. Sending ABRT.')
682 crash_cryptohomed()
683 raise ChromiumOSError('cryptohomed aborted. Check crashes!')
684 return result
685
Chris Masone19e305e2014-03-14 15:13:46 -0700686
Derek Beckett1091ed12020-10-19 10:47:16 -0700687 def mount(self, user, password, create=False, key_label='bar'):
Elly Jones2f0ebba2011-10-27 13:43:20 -0400688 """Mounts a cryptohome.
689
690 Returns True if the mount succeeds or False otherwise.
Elly Jones2f0ebba2011-10-27 13:43:20 -0400691 """
Eric Caruso8dc40982018-03-20 17:05:19 -0700692 import rpc_pb2
693
694 acc = rpc_pb2.AccountIdentifier()
695 acc.account_id = user
696
697 auth = rpc_pb2.AuthorizationRequest()
698 auth.key.secret = password
699 auth.key.data.label = key_label
700
701 mount_req = rpc_pb2.MountRequest()
702 if create:
703 mount_req.create.copy_authorization_key = True
704
705 out = self.__call(self.iface.MountEx, acc.SerializeToString(),
706 auth.SerializeToString(), mount_req.SerializeToString())
707 parsed_out = rpc_pb2.BaseReply()
708 parsed_out.ParseFromString(''.join(map(chr, out)))
709 return parsed_out.error == rpc_pb2.CRYPTOHOME_ERROR_NOT_SET
Elly Jones2f0ebba2011-10-27 13:43:20 -0400710
Chris Masone19e305e2014-03-14 15:13:46 -0700711
Elly Jones2f0ebba2011-10-27 13:43:20 -0400712 def unmount(self, user):
713 """Unmounts a cryptohome.
714
715 Returns True if the unmount suceeds or false otherwise.
Elly Jones2f0ebba2011-10-27 13:43:20 -0400716 """
Eric Caruso8f148792019-02-26 14:35:31 -0800717 import rpc_pb2
718
719 req = rpc_pb2.UnmountRequest()
720
721 out = self.__call(self.iface.UnmountEx, req.SerializeToString())
722 parsed_out = rpc_pb2.BaseReply()
723 parsed_out.ParseFromString(''.join(map(chr, out)))
724 return parsed_out.error == rpc_pb2.CRYPTOHOME_ERROR_NOT_SET
Elly Jones2f0ebba2011-10-27 13:43:20 -0400725
Chris Masone19e305e2014-03-14 15:13:46 -0700726
Elly Jones2f0ebba2011-10-27 13:43:20 -0400727 def is_mounted(self, user):
728 """Tests whether a user's cryptohome is mounted."""
729 return (utils.is_mountpoint(user_path(user))
730 and utils.is_mountpoint(system_path(user)))
731
Chris Masone19e305e2014-03-14 15:13:46 -0700732
Elly Jones2f0ebba2011-10-27 13:43:20 -0400733 def require_mounted(self, user):
734 """Raises a test failure if a user's cryptohome is not mounted."""
735 utils.require_mountpoint(user_path(user))
736 utils.require_mountpoint(system_path(user))
Elly Jones4458f442012-04-16 15:42:56 -0400737
Chris Masone19e305e2014-03-14 15:13:46 -0700738
Derek Beckett1091ed12020-10-19 10:47:16 -0700739 def remove(self, user):
Leo Laicdabea52019-04-29 15:56:14 +0800740 """Removes a users cryptohome.
Greg Kerrec7ba582018-09-13 16:19:26 -0700741
742 Returns True if the operation succeeds or False otherwise.
743 """
744 import rpc_pb2
745
746 acc = rpc_pb2.AccountIdentifier()
747 acc.account_id = user
748
749 out = self.__call(self.iface.RemoveEx, acc.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
Chris Masone19e305e2014-03-14 15:13:46 -0700753
754
755 def ensure_clean_cryptohome_for(self, user, password=None):
756 """Ensure a fresh cryptohome exists for user.
757
758 @param user: user who needs a shiny new cryptohome.
759 @param password: if unset, a random password will be used.
760 """
761 if not password:
762 password = ''.join(random.sample(string.ascii_lowercase, 6))
763 self.remove(user)
764 self.mount(user, password, create=True)
Roman Sorokina45273e2017-12-20 12:03:27 +0100765
766 def lock_install_attributes(self, attrs):
767 """Set and lock install attributes for the device.
768
769 @param attrs: dict of install attributes.
770 """
Roman Sorokin0a228d12018-01-23 12:36:45 +0100771 take_tpm_ownership()
Roman Sorokin58312b22018-10-17 13:34:39 +0200772 self.wait_for_install_attributes_ready()
Roman Sorokina45273e2017-12-20 12:03:27 +0100773 for key, value in attrs.items():
774 if not self.__call(self.iface.InstallAttributesSet, key,
775 dbus.ByteArray(value + '\0')):
776 return False
777 return self.__call(self.iface.InstallAttributesFinalize)
Roman Sorokin58312b22018-10-17 13:34:39 +0200778
779 def wait_for_install_attributes_ready(self):
780 """Wait until install attributes are ready.
781 """
782 utils.poll_for_condition(
783 lambda: self.__call(self.iface.InstallAttributesIsReady),
784 timeout=300,
785 exception=error.TestError(
786 'Timeout waiting for install attributes are ready'))