blob: 5eb98cfded64fa6bb23e20c3d336a98fdce9a687 [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:
167 { 'owner_user_exists': True|False,
168 'boot_lockbox_finalized': True|False
169 }
170 """
Derek Beckett31554d22020-12-11 11:21:30 -0800171 out = run_cmd(CRYPTOHOME_CMD + ' --action=get_login_status')
David Pursell2a2ef342014-10-17 10:34:56 -0700172 status = {}
173 for field in ['owner_user_exists', 'boot_lockbox_finalized']:
174 match = re.search('%s: (true|false)' % field, out)
175 if not match:
176 raise ChromiumOSError('Invalid login status: "%s".' % out)
177 status[field] = match.group(1) == 'true'
178 return status
179
180
Yi Chou2167d362020-12-14 17:41:10 +0800181def get_install_attribute_status():
182 """Query the install attribute status
183
184 Returns:
185 A status string, which could be:
186 "UNKNOWN"
187 "TPM_NOT_OWNED"
188 "FIRST_INSTALL"
189 "VALID"
190 "INVALID"
191 """
Derek Beckett31554d22020-12-11 11:21:30 -0800192 out = run_cmd(CRYPTOHOME_CMD + ' --action=install_attributes_get_status')
Yi Chou2167d362020-12-14 17:41:10 +0800193 return out.strip()
194
195
Darren Krahn5f880f62012-10-02 15:17:59 -0700196def get_tpm_attestation_status():
197 """Get the TPM attestation status. Works similar to get_tpm_status().
198 """
Derek Beckett31554d22020-12-11 11:21:30 -0800199 out = run_cmd(ATTESTATION_CMD + ' status')
Darren Krahn5f880f62012-10-02 15:17:59 -0700200 status = {}
Yi Chou2167d362020-12-14 17:41:10 +0800201 for field in ['prepared_for_enrollment', 'enrolled']:
202 match = re.search('%s: (true|false)' % field, out)
Darren Krahn5f880f62012-10-02 15:17:59 -0700203 if not match:
204 raise ChromiumOSError('Invalid attestation status: "%s".' % out)
205 status[field] = match.group(1) == 'true'
206 return status
207
208
Eric Caruso6da07a02018-02-07 16:02:41 -0800209def take_tpm_ownership(wait_for_ownership=True):
Frank Farzand5e36312012-01-13 14:34:03 -0800210 """Take TPM owernship.
211
Eric Caruso6da07a02018-02-07 16:02:41 -0800212 Args:
213 wait_for_ownership: block until TPM is owned if true
Frank Farzand5e36312012-01-13 14:34:03 -0800214 """
Derek Beckett31554d22020-12-11 11:21:30 -0800215 run_cmd(CRYPTOHOME_CMD + ' --action=tpm_take_ownership')
Eric Caruso6da07a02018-02-07 16:02:41 -0800216 if wait_for_ownership:
Maksim Ivanov21c967a2018-03-22 21:24:41 +0100217 # Note that waiting for the 'Ready' flag is more correct than waiting
218 # for the 'Owned' flag, as the latter is set by cryptohomed before some
219 # of the ownership tasks are completed.
220 utils.poll_for_condition(
221 lambda: get_tpm_status()['Ready'],
222 timeout=300,
223 exception=error.TestError('Timeout waiting for TPM ownership'))
Frank Farzand5e36312012-01-13 14:34:03 -0800224
225
Darren Krahn0e73e7f2012-09-05 15:35:15 -0700226def verify_ek():
227 """Verify the TPM endorsement key.
228
229 Returns true if EK is valid.
230 """
231 cmd = CRYPTOHOME_CMD + ' --action=tpm_verify_ek'
232 return (utils.system(cmd, ignore_status=True) == 0)
233
234
Sean Oe5d8fd02010-09-30 10:44:44 +0200235def remove_vault(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200236 """Remove the given user's vault from the shadow directory."""
Sean Oe5d8fd02010-09-30 10:44:44 +0200237 logging.debug('user is %s', user)
238 user_hash = get_user_hash(user)
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700239 logging.debug('Removing vault for user %s with hash %s', user, user_hash)
Sean Oe5d8fd02010-09-30 10:44:44 +0200240 cmd = CRYPTOHOME_CMD + ' --action=remove --force --user=%s' % user
Derek Beckett31554d22020-12-11 11:21:30 -0800241 run_cmd(cmd)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200242 # Ensure that the vault does not exist.
243 if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
Darren Krahne6c44b92014-03-31 12:11:08 -0700244 raise ChromiumOSError('Cryptohome could not remove the user\'s vault.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200245
246
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200247def remove_all_vaults():
248 """Remove any existing vaults from the shadow directory.
249
250 This function must be run with root privileges.
251 """
barfab@chromium.org5c374632012-04-05 16:50:56 +0200252 for item in os.listdir(constants.SHADOW_ROOT):
253 abs_item = os.path.join(constants.SHADOW_ROOT, item)
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200254 if os.path.isdir(os.path.join(abs_item, 'vault')):
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700255 logging.debug('Removing vault for user with hash %s', item)
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200256 shutil.rmtree(abs_item)
257
258
Allen Webb8b4eb622018-07-20 15:46:11 -0700259def mount_vault(user, password, create=False, key_label=None):
Eric Caruso22acd672018-02-01 17:55:28 -0800260 """Mount the given user's vault. Mounts should be created by calling this
261 function with create=True, and can be used afterwards with create=False.
262 Only try to mount existing vaults created with this function.
263
264 """
265 args = [CRYPTOHOME_CMD, '--action=mount_ex', '--user=%s' % user,
Chris Masone3543e512013-11-04 13:09:30 -0800266 '--password=%s' % password, '--async']
Sean Oe5d8fd02010-09-30 10:44:44 +0200267 if create:
Allen Webb8b4eb622018-07-20 15:46:11 -0700268 args += ['--create']
269 if key_label is None:
270 key_label = 'bar'
271 if key_label is not None:
272 args += ['--key_label=%s' % key_label]
Derek Beckett31554d22020-12-11 11:21:30 -0800273 logging.info(run_cmd(' '.join(args)))
barfab@chromium.org5c374632012-04-05 16:50:56 +0200274 # Ensure that the vault exists in the shadow directory.
Sean Oe5d8fd02010-09-30 10:44:44 +0200275 user_hash = get_user_hash(user)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200276 if not os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
Alexis Saveryccb16be2017-02-01 16:23:15 -0800277 retry = 0
278 mounted = False
279 while retry < MOUNT_RETRY_COUNT and not mounted:
280 time.sleep(1)
Yi Chou2167d362020-12-14 17:41:10 +0800281 logging.info("Retry %s", str(retry + 1))
Derek Beckett31554d22020-12-11 11:21:30 -0800282 run_cmd(' '.join(args))
Alexis Saveryccb16be2017-02-01 16:23:15 -0800283 # TODO: Remove this additional call to get_user_hash(user) when
284 # crbug.com/690994 is fixed
285 user_hash = get_user_hash(user)
286 if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
287 mounted = True
288 retry += 1
289 if not mounted:
290 raise ChromiumOSError('Cryptohome vault not found after mount.')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200291 # Ensure that the vault is mounted.
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700292 if not is_permanent_vault_mounted(user=user, allow_fail=True):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200293 raise ChromiumOSError('Cryptohome created a vault but did not mount.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200294
295
Chris Masone5d010aa2013-05-06 14:38:42 -0700296def mount_guest():
Sergey Poromov533008f2017-10-13 14:07:43 +0200297 """Mount the guest vault."""
Greg Kerrd7cdc132018-06-08 11:55:40 -0700298 args = [CRYPTOHOME_CMD, '--action=mount_guest_ex']
Derek Beckett31554d22020-12-11 11:21:30 -0800299 logging.info(run_cmd(' '.join(args)))
Sergey Poromov533008f2017-10-13 14:07:43 +0200300 # Ensure that the guest vault is mounted.
Chris Masone5d010aa2013-05-06 14:38:42 -0700301 if not is_guest_vault_mounted(allow_fail=True):
Sergey Poromov533008f2017-10-13 14:07:43 +0200302 raise ChromiumOSError('Cryptohome did not mount guest vault.')
Chris Masone5d010aa2013-05-06 14:38:42 -0700303
304
Sean Oe5d8fd02010-09-30 10:44:44 +0200305def test_auth(user, password):
Yi Chou2167d362020-12-14 17:41:10 +0800306 """Test key auth."""
Eric Carusode07cf82018-02-12 15:34:02 -0800307 cmd = [CRYPTOHOME_CMD, '--action=check_key_ex', '--user=%s' % user,
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400308 '--password=%s' % password, '--async']
Derek Beckett31554d22020-12-11 11:21:30 -0800309 out = run_cmd(' '.join(cmd))
Eric Carusode07cf82018-02-12 15:34:02 -0800310 logging.info(out)
311 return 'Key authenticated.' in out
Sean Oe5d8fd02010-09-30 10:44:44 +0200312
313
Allen Webb8b4eb622018-07-20 15:46:11 -0700314def add_le_key(user, password, new_password, new_key_label):
Yi Chou2167d362020-12-14 17:41:10 +0800315 """Add low entropy key."""
Allen Webb8b4eb622018-07-20 15:46:11 -0700316 args = [CRYPTOHOME_CMD, '--action=add_key_ex', '--key_policy=le',
317 '--user=%s' % user, '--password=%s' % password,
318 '--new_key_label=%s' % new_key_label,
319 '--new_password=%s' % new_password]
Derek Beckett31554d22020-12-11 11:21:30 -0800320 logging.info(run_cmd(' '.join(args)))
Allen Webb8b4eb622018-07-20 15:46:11 -0700321
322
323def remove_key(user, password, remove_key_label):
Yi Chou2167d362020-12-14 17:41:10 +0800324 """Remove a key."""
Allen Webb8b4eb622018-07-20 15:46:11 -0700325 args = [CRYPTOHOME_CMD, '--action=remove_key_ex', '--user=%s' % user,
326 '--password=%s' % password,
327 '--remove_key_label=%s' % remove_key_label]
Derek Beckett31554d22020-12-11 11:21:30 -0800328 logging.info(run_cmd(' '.join(args)))
Allen Webb8b4eb622018-07-20 15:46:11 -0700329
330
331def get_supported_key_policies():
Yi Chou2167d362020-12-14 17:41:10 +0800332 """Get supported key policies."""
Allen Webb8b4eb622018-07-20 15:46:11 -0700333 args = [CRYPTOHOME_CMD, '--action=get_supported_key_policies']
Derek Beckett31554d22020-12-11 11:21:30 -0800334 out = run_cmd(' '.join(args))
Allen Webb8b4eb622018-07-20 15:46:11 -0700335 logging.info(out)
336 policies = {}
337 for line in out.splitlines():
338 match = re.search(' ([^:]+): (true|false)', line)
339 if match:
340 policies[match.group(1)] = match.group(2) == 'true'
341 return policies
342
343
344def unmount_vault(user=None):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200345 """Unmount the given user's vault.
346
347 Once unmounting for a specific user is supported, the user parameter will
348 name the target user. See crosbug.com/20778.
Elly Jones686c2f42011-10-24 16:45:07 -0400349 """
Derek Beckett31554d22020-12-11 11:21:30 -0800350 run_cmd(CRYPTOHOME_CMD + ' --action=unmount')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200351 # Ensure that the vault is not mounted.
Allen Webb8b4eb622018-07-20 15:46:11 -0700352 if user is not None and is_vault_mounted(user, allow_fail=True):
Sean Oe5d8fd02010-09-30 10:44:44 +0200353 raise ChromiumOSError('Cryptohome did not unmount the user.')
354
355
barfab@chromium.org5c374632012-04-05 16:50:56 +0200356def __get_mount_info(mount_point, allow_fail=False):
357 """Get information about the active mount at a given mount point."""
beeps569f8672013-08-07 10:18:51 -0700358 cryptohomed_path = '/proc/$(pgrep cryptohomed)/mounts'
Jorge Lucangeli Obesf7627af2020-03-19 15:11:01 -0400359 # 'cryptohome-namespace-mounter' is currently only used for Guest sessions.
360 mounter_exe = 'cryptohome-namespace-mounter'
361 mounter_pid = 'pgrep -o -f %s' % mounter_exe
362 mounter_path = '/proc/$(%s)/mounts' % mounter_pid
363
364 status = utils.system(mounter_pid, ignore_status=True)
365 # Only check for these mounts if the mounter executable is running.
366 if status == 0:
367 try:
368 logging.debug('Active %s mounts:\n' % mounter_exe +
369 utils.system_output('cat %s' % mounter_path))
370 ns_mount_line = utils.system_output(
371 'grep %s %s' % (mount_point, mounter_path),
372 ignore_status=allow_fail)
373 except Exception as e:
374 logging.error(e)
375 raise ChromiumOSError('Could not get info about cryptohome vault '
376 'through %s. See logs for complete '
377 'mount-point.'
378 % os.path.dirname(str(mount_point)))
379 return ns_mount_line.split()
380
beeps569f8672013-08-07 10:18:51 -0700381 try:
Yi Chou2167d362020-12-14 17:41:10 +0800382 logging.debug('Active cryptohome mounts:\n%s',
Daniel Erat2ec32792017-01-31 18:26:59 -0700383 utils.system_output('cat %s' % cryptohomed_path))
beeps569f8672013-08-07 10:18:51 -0700384 mount_line = utils.system_output(
385 'grep %s %s' % (mount_point, cryptohomed_path),
386 ignore_status=allow_fail)
387 except Exception as e:
388 logging.error(e)
389 raise ChromiumOSError('Could not get info about cryptohome vault '
390 'through %s. See logs for complete mount-point.'
391 % os.path.dirname(str(mount_point)))
Sourav Poddar574bd622010-05-26 14:22:26 +0530392 return mount_line.split()
393
394
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400395def __get_user_mount_info(user, allow_fail=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200396 """Get information about the active mounts for a given user.
397
398 Returns the active mounts at the user's user and system mount points. If no
399 user is given, the active mount at the shared mount point is returned
400 (regular users have a bind-mount at this mount point for backwards
401 compatibility; the guest user has a mount at this mount point only).
402 """
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400403 return [__get_mount_info(mount_point=user_path(user),
404 allow_fail=allow_fail),
405 __get_mount_info(mount_point=system_path(user),
406 allow_fail=allow_fail)]
Jim Hebertf08f88d2011-04-22 10:33:49 -0700407
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700408def is_vault_mounted(user, regexes=None, allow_fail=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200409 """Check whether a vault is mounted for the given user.
410
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700411 user: If no user is given, the shared mount point is checked, determining
412 whether a vault is mounted for any user.
413 regexes: dictionary of regexes to matches against the mount information.
414 The mount filesystem for the user's user and system mounts point must
415 match one of the keys.
416 The mount source point must match the selected device regex.
417
418 In addition, if mounted over ext4, we check the directory is encrypted.
barfab@chromium.org5c374632012-04-05 16:50:56 +0200419 """
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700420 if regexes is None:
421 regexes = {
422 constants.CRYPTOHOME_FS_REGEX_ANY :
423 constants.CRYPTOHOME_DEV_REGEX_ANY
424 }
barfab@chromium.org5c374632012-04-05 16:50:56 +0200425 user_mount_info = __get_user_mount_info(user=user, allow_fail=allow_fail)
426 for mount_info in user_mount_info:
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700427 # Look at each /proc/../mount lines that match mount point for a given
428 # user user/system mount (/home/user/.... /home/root/...)
429
430 # We should have at least 3 arguments (source, mount, type of mount)
431 if len(mount_info) < 3:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200432 return False
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700433
434 device_regex = None
435 for fs_regex in regexes.keys():
436 if re.match(fs_regex, mount_info[2]):
437 device_regex = regexes[fs_regex]
438 break
439
440 if not device_regex:
Jorge Lucangeli Obesf7627af2020-03-19 15:11:01 -0400441 # The third argument in not the expected mount point type.
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700442 return False
443
444 # Check if the mount source match the device regex: it can be loose,
445 # (anything) or stricter if we expect guest filesystem.
446 if not re.match(device_regex, mount_info[0]):
447 return False
448
barfab@chromium.org5c374632012-04-05 16:50:56 +0200449 return True
Sourav Poddar574bd622010-05-26 14:22:26 +0530450
451
barfab@chromium.org5c374632012-04-05 16:50:56 +0200452def is_guest_vault_mounted(allow_fail=False):
Sergey Poromov533008f2017-10-13 14:07:43 +0200453 """Check whether a vault is mounted for the guest user.
Sergey Poromovd85dce52017-12-27 11:10:51 +0100454 It should be a mount of an ext4 partition on a loop device
455 or be backed by tmpfs.
Sergey Poromov533008f2017-10-13 14:07:43 +0200456 """
barfab@chromium.org5c374632012-04-05 16:50:56 +0200457 return is_vault_mounted(
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400458 user=GUEST_USER_NAME,
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700459 regexes={
Sergey Poromovd85dce52017-12-27 11:10:51 +0100460 # Remove tmpfs support when it becomes unnecessary as all guest
461 # modes will use ext4 on a loop device.
Sergey Poromov533008f2017-10-13 14:07:43 +0200462 constants.CRYPTOHOME_FS_REGEX_EXT4 :
463 constants.CRYPTOHOME_DEV_REGEX_LOOP_DEVICE,
Sergey Poromovd85dce52017-12-27 11:10:51 +0100464 constants.CRYPTOHOME_FS_REGEX_TMPFS :
465 constants.CRYPTOHOME_DEV_REGEX_GUEST,
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700466 },
barfab@chromium.org5c374632012-04-05 16:50:56 +0200467 allow_fail=allow_fail)
468
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700469def is_permanent_vault_mounted(user, allow_fail=False):
470 """Check if user is mounted over ecryptfs or ext4 crypto. """
471 return is_vault_mounted(
472 user=user,
473 regexes={
474 constants.CRYPTOHOME_FS_REGEX_ECRYPTFS :
475 constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER_SHADOW,
476 constants.CRYPTOHOME_FS_REGEX_EXT4 :
477 constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER_DEVICE,
478 },
479 allow_fail=allow_fail)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200480
Kazuhiro Inabaa3bf6452017-02-08 11:41:50 +0900481def get_mounted_vault_path(user, allow_fail=False):
482 """Get the path where the decrypted data for the user is located."""
483 return os.path.join(constants.SHADOW_ROOT, get_user_hash(user), 'mount')
Nirnimesh66814492011-06-27 18:00:33 -0700484
485
486def canonicalize(credential):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200487 """Perform basic canonicalization of |email_address|.
Nirnimesh66814492011-06-27 18:00:33 -0700488
barfab@chromium.org5c374632012-04-05 16:50:56 +0200489 Perform basic canonicalization of |email_address|, taking into account that
490 gmail does not consider '.' or caps inside a username to matter. It also
491 ignores everything after a '+'. For example,
492 c.masone+abc@gmail.com == cMaSone@gmail.com, per
Nirnimesh66814492011-06-27 18:00:33 -0700493 http://mail.google.com/support/bin/answer.py?hl=en&ctx=mail&answer=10313
494 """
495 if not credential:
Yi Chou2167d362020-12-14 17:41:10 +0800496 return None
Nirnimesh66814492011-06-27 18:00:33 -0700497
498 parts = credential.split('@')
499 if len(parts) != 2:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200500 raise error.TestError('Malformed email: ' + credential)
Nirnimesh66814492011-06-27 18:00:33 -0700501
502 (name, domain) = parts
503 name = name.partition('+')[0]
barfab@chromium.org5c374632012-04-05 16:50:56 +0200504 if (domain == constants.SPECIAL_CASE_DOMAIN):
Nirnimesh66814492011-06-27 18:00:33 -0700505 name = name.replace('.', '')
506 return '@'.join([name, domain]).lower()
Elly Jones686c2f42011-10-24 16:45:07 -0400507
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200508
Will Drewryd2fed972013-12-05 16:35:51 -0600509def crash_cryptohomed():
Yi Chou2167d362020-12-14 17:41:10 +0800510 """Let cryptohome crash."""
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600511 # Try to kill cryptohomed so we get something to work with.
Derek Beckett31554d22020-12-11 11:21:30 -0800512 pid = run_cmd('pgrep cryptohomed')
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600513 try:
Will Drewry9e440792013-12-11 17:18:35 -0600514 pid = int(pid)
Derek Beckett1091ed12020-10-19 10:47:16 -0700515 except ValueError as e: # empty or invalid string
Will Drewry9e440792013-12-11 17:18:35 -0600516 raise error.TestError('Cryptohomed was not running')
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600517 utils.system('kill -ABRT %d' % pid)
518 # CONT just in case cryptohomed had a spurious STOP.
519 utils.system('kill -CONT %d' % pid)
520 utils.poll_for_condition(
521 lambda: utils.system('ps -p %d' % pid,
522 ignore_status=True) != 0,
Will Drewry934d1532014-01-30 16:23:17 -0600523 timeout=180,
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600524 exception=error.TestError(
525 'Timeout waiting for cryptohomed to coredump'))
526
Will Drewryd2fed972013-12-05 16:35:51 -0600527
Dan Spaidbe9789c2017-05-19 15:18:42 +0900528def create_ecryptfs_homedir(user, password):
529 """Creates a new home directory as ecryptfs.
530
531 If a home directory for the user exists already, it will be removed.
532 The resulting home directory will be mounted.
533
534 @param user: Username to create the home directory for.
535 @param password: Password to use when creating the home directory.
536 """
537 unmount_vault(user)
538 remove_vault(user)
539 args = [
540 CRYPTOHOME_CMD,
541 '--action=mount_ex',
542 '--user=%s' % user,
543 '--password=%s' % password,
544 '--key_label=foo',
545 '--ecryptfs',
546 '--create']
Derek Beckett31554d22020-12-11 11:21:30 -0800547 logging.info(run_cmd(' '.join(args)))
Dan Spaidbe9789c2017-05-19 15:18:42 +0900548 if not is_vault_mounted(user, regexes={
549 constants.CRYPTOHOME_FS_REGEX_ECRYPTFS :
550 constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER_SHADOW
551 }, allow_fail=True):
552 raise ChromiumOSError('Ecryptfs home could not be created')
553
554
555def do_dircrypto_migration(user, password, timeout=600):
556 """Start dircrypto migration for the user.
557
558 @param user: The user to migrate.
559 @param password: The password used to mount the users vault
560 @param timeout: How long in seconds to wait for the migration to finish
561 before failing.
562 """
563 unmount_vault(user)
564 args = [
565 CRYPTOHOME_CMD,
566 '--action=mount_ex',
567 '--to_migrate_from_ecryptfs',
568 '--user=%s' % user,
569 '--password=%s' % password]
Derek Beckett31554d22020-12-11 11:21:30 -0800570 logging.info(run_cmd(' '.join(args)))
Dan Spaidbe9789c2017-05-19 15:18:42 +0900571 if not __get_mount_info(temporary_mount_path(user), allow_fail=True):
572 raise ChromiumOSError('Failed to mount home for migration')
573 args = [CRYPTOHOME_CMD, '--action=migrate_to_dircrypto', '--user=%s' % user]
Derek Beckett31554d22020-12-11 11:21:30 -0800574 logging.info(run_cmd(' '.join(args)))
Dan Spaidbe9789c2017-05-19 15:18:42 +0900575 utils.poll_for_condition(
576 lambda: not __get_mount_info(
577 temporary_mount_path(user), allow_fail=True),
578 timeout=timeout,
579 exception=error.TestError(
580 'Timeout waiting for dircrypto migration to finish'))
581
582
Eric Caruso014d5ed2018-02-01 16:24:41 -0800583def change_password(user, password, new_password):
Yi Chou2167d362020-12-14 17:41:10 +0800584 """Change user password."""
Eric Caruso014d5ed2018-02-01 16:24:41 -0800585 args = [
586 CRYPTOHOME_CMD,
Greg Kerr98aa75c2018-06-05 15:27:12 -0700587 '--action=migrate_key_ex',
Eric Caruso014d5ed2018-02-01 16:24:41 -0800588 '--user=%s' % user,
589 '--old_password=%s' % password,
590 '--password=%s' % new_password]
Derek Beckett31554d22020-12-11 11:21:30 -0800591 out = run_cmd(' '.join(args))
Eric Caruso43ec57e2018-02-05 15:54:49 -0800592 logging.info(out)
593 if 'Key migration succeeded.' not in out:
594 raise ChromiumOSError('Key migration failed.')
Eric Caruso014d5ed2018-02-01 16:24:41 -0800595
596
Will Drewry9e440792013-12-11 17:18:35 -0600597class CryptohomeProxy(DBusClient):
598 """A DBus proxy client for testing the Cryptohome DBus server.
599 """
600 CRYPTOHOME_BUS_NAME = 'org.chromium.Cryptohome'
601 CRYPTOHOME_OBJECT_PATH = '/org/chromium/Cryptohome'
602 CRYPTOHOME_INTERFACE = 'org.chromium.CryptohomeInterface'
603 ASYNC_CALL_STATUS_SIGNAL = 'AsyncCallStatus'
604 ASYNC_CALL_STATUS_SIGNAL_ARGUMENTS = (
605 'async_id', 'return_status', 'return_code'
606 )
607 DBUS_PROPERTIES_INTERFACE = 'org.freedesktop.DBus.Properties'
608
Lutz Justenb9275782018-06-20 18:42:22 +0200609 # Default timeout in seconds for the D-Bus connection.
610 DEFAULT_DBUS_TIMEOUT = 30
Chris Masone19e305e2014-03-14 15:13:46 -0700611
Lutz Justenb9275782018-06-20 18:42:22 +0200612 def __init__(self, bus_loop=None, autodir=None, job=None,
613 timeout=DEFAULT_DBUS_TIMEOUT):
Eric Caruso8dc40982018-03-20 17:05:19 -0700614 if autodir and job:
615 # Install D-Bus protos necessary for some methods.
616 dep_dir = os.path.join(autodir, 'deps', DBUS_PROTOS_DEP)
617 job.install_pkg(DBUS_PROTOS_DEP, 'dep', dep_dir)
618 sys.path.append(dep_dir)
619
620 # Set up D-Bus main loop and interface.
Will Drewry9e440792013-12-11 17:18:35 -0600621 self.main_loop = gobject.MainLoop()
Will Drewry78db9dc2014-04-01 16:34:23 -0500622 if bus_loop is None:
Chris Masone64170f82014-03-14 15:47:05 -0700623 bus_loop = DBusGMainLoop(set_as_default=True)
Will Drewry9e440792013-12-11 17:18:35 -0600624 self.bus = dbus.SystemBus(mainloop=bus_loop)
625 super(CryptohomeProxy, self).__init__(self.main_loop, self.bus,
626 self.CRYPTOHOME_BUS_NAME,
Lutz Justen1c6be452018-05-29 13:37:00 +0200627 self.CRYPTOHOME_OBJECT_PATH,
628 timeout)
Will Drewry9e440792013-12-11 17:18:35 -0600629 self.iface = dbus.Interface(self.proxy_object,
630 self.CRYPTOHOME_INTERFACE)
631 self.properties = dbus.Interface(self.proxy_object,
632 self.DBUS_PROPERTIES_INTERFACE)
633 self.handle_signal(self.CRYPTOHOME_INTERFACE,
634 self.ASYNC_CALL_STATUS_SIGNAL,
635 self.ASYNC_CALL_STATUS_SIGNAL_ARGUMENTS)
Elly Jones2f0ebba2011-10-27 13:43:20 -0400636
Chris Masone19e305e2014-03-14 15:13:46 -0700637
Will Drewryd2fed972013-12-05 16:35:51 -0600638 # Wrap all proxied calls to catch cryptohomed failures.
639 def __call(self, method, *args):
640 try:
Chris Masonef59d9df2014-03-14 12:05:32 -0700641 return method(*args, timeout=180)
Derek Beckett1091ed12020-10-19 10:47:16 -0700642 except dbus.exceptions.DBusException as e:
Will Drewryd2fed972013-12-05 16:35:51 -0600643 if e.get_dbus_name() == 'org.freedesktop.DBus.Error.NoReply':
644 logging.error('Cryptohome is not responding. Sending ABRT')
645 crash_cryptohomed()
646 raise ChromiumOSError('cryptohomed aborted. Check crashes!')
647 raise e
648
Chris Masone19e305e2014-03-14 15:13:46 -0700649
Will Drewry9e440792013-12-11 17:18:35 -0600650 def __wait_for_specific_signal(self, signal, data):
Yi Chou2167d362020-12-14 17:41:10 +0800651 """Wait for the |signal| with matching |data|
652 Returns the resulting dict on success or {} on error.
653 """
654 # Do not bubble up the timeout here, just return {}.
655 result = {}
656 try:
657 result = self.wait_for_signal(signal)
658 except utils.TimeoutError:
Will Drewry9e440792013-12-11 17:18:35 -0600659 return {}
Yi Chou2167d362020-12-14 17:41:10 +0800660 for k in data.keys():
661 if k not in result or result[k] != data[k]:
662 return {}
663 return result
Will Drewry9e440792013-12-11 17:18:35 -0600664
Chris Masone19e305e2014-03-14 15:13:46 -0700665
Will Drewry9e440792013-12-11 17:18:35 -0600666 # Perform a data-less async call.
667 # TODO(wad) Add __async_data_call.
668 def __async_call(self, method, *args):
Will Drewryfef135a2014-05-23 16:02:14 -0500669 # Clear out any superfluous async call signals.
670 self.clear_signal_content(self.ASYNC_CALL_STATUS_SIGNAL)
Will Drewry9e440792013-12-11 17:18:35 -0600671 out = self.__call(method, *args)
672 logging.debug('Issued call ' + str(method) +
673 ' with async_id ' + str(out))
674 result = {}
675 try:
Will Drewry934d1532014-01-30 16:23:17 -0600676 # __wait_for_specific_signal has a 10s timeout
Will Drewry9e440792013-12-11 17:18:35 -0600677 result = utils.poll_for_condition(
678 lambda: self.__wait_for_specific_signal(
679 self.ASYNC_CALL_STATUS_SIGNAL, {'async_id' : out}),
Will Drewry934d1532014-01-30 16:23:17 -0600680 timeout=180,
Will Drewry9e440792013-12-11 17:18:35 -0600681 desc='matching %s signal' % self.ASYNC_CALL_STATUS_SIGNAL)
Derek Beckett1091ed12020-10-19 10:47:16 -0700682 except utils.TimeoutError as e:
Will Drewry9e440792013-12-11 17:18:35 -0600683 logging.error('Cryptohome timed out. Sending ABRT.')
684 crash_cryptohomed()
685 raise ChromiumOSError('cryptohomed aborted. Check crashes!')
686 return result
687
Chris Masone19e305e2014-03-14 15:13:46 -0700688
Derek Beckett1091ed12020-10-19 10:47:16 -0700689 def mount(self, user, password, create=False, key_label='bar'):
Elly Jones2f0ebba2011-10-27 13:43:20 -0400690 """Mounts a cryptohome.
691
692 Returns True if the mount succeeds or False otherwise.
Elly Jones2f0ebba2011-10-27 13:43:20 -0400693 """
Eric Caruso8dc40982018-03-20 17:05:19 -0700694 import rpc_pb2
695
696 acc = rpc_pb2.AccountIdentifier()
697 acc.account_id = user
698
699 auth = rpc_pb2.AuthorizationRequest()
700 auth.key.secret = password
701 auth.key.data.label = key_label
702
703 mount_req = rpc_pb2.MountRequest()
704 if create:
705 mount_req.create.copy_authorization_key = True
706
707 out = self.__call(self.iface.MountEx, acc.SerializeToString(),
708 auth.SerializeToString(), mount_req.SerializeToString())
709 parsed_out = rpc_pb2.BaseReply()
710 parsed_out.ParseFromString(''.join(map(chr, out)))
711 return parsed_out.error == rpc_pb2.CRYPTOHOME_ERROR_NOT_SET
Elly Jones2f0ebba2011-10-27 13:43:20 -0400712
Chris Masone19e305e2014-03-14 15:13:46 -0700713
Elly Jones2f0ebba2011-10-27 13:43:20 -0400714 def unmount(self, user):
715 """Unmounts a cryptohome.
716
717 Returns True if the unmount suceeds or false otherwise.
Elly Jones2f0ebba2011-10-27 13:43:20 -0400718 """
Eric Caruso8f148792019-02-26 14:35:31 -0800719 import rpc_pb2
720
721 req = rpc_pb2.UnmountRequest()
722
723 out = self.__call(self.iface.UnmountEx, req.SerializeToString())
724 parsed_out = rpc_pb2.BaseReply()
725 parsed_out.ParseFromString(''.join(map(chr, out)))
726 return parsed_out.error == rpc_pb2.CRYPTOHOME_ERROR_NOT_SET
Elly Jones2f0ebba2011-10-27 13:43:20 -0400727
Chris Masone19e305e2014-03-14 15:13:46 -0700728
Elly Jones2f0ebba2011-10-27 13:43:20 -0400729 def is_mounted(self, user):
730 """Tests whether a user's cryptohome is mounted."""
731 return (utils.is_mountpoint(user_path(user))
732 and utils.is_mountpoint(system_path(user)))
733
Chris Masone19e305e2014-03-14 15:13:46 -0700734
Elly Jones2f0ebba2011-10-27 13:43:20 -0400735 def require_mounted(self, user):
736 """Raises a test failure if a user's cryptohome is not mounted."""
737 utils.require_mountpoint(user_path(user))
738 utils.require_mountpoint(system_path(user))
Elly Jones4458f442012-04-16 15:42:56 -0400739
Chris Masone19e305e2014-03-14 15:13:46 -0700740
Derek Beckett1091ed12020-10-19 10:47:16 -0700741 def remove(self, user):
Leo Laicdabea52019-04-29 15:56:14 +0800742 """Removes a users cryptohome.
Greg Kerrec7ba582018-09-13 16:19:26 -0700743
744 Returns True if the operation succeeds or False otherwise.
745 """
746 import rpc_pb2
747
748 acc = rpc_pb2.AccountIdentifier()
749 acc.account_id = user
750
751 out = self.__call(self.iface.RemoveEx, acc.SerializeToString())
752 parsed_out = rpc_pb2.BaseReply()
753 parsed_out.ParseFromString(''.join(map(chr, out)))
754 return parsed_out.error == rpc_pb2.CRYPTOHOME_ERROR_NOT_SET
Chris Masone19e305e2014-03-14 15:13:46 -0700755
756
757 def ensure_clean_cryptohome_for(self, user, password=None):
758 """Ensure a fresh cryptohome exists for user.
759
760 @param user: user who needs a shiny new cryptohome.
761 @param password: if unset, a random password will be used.
762 """
763 if not password:
764 password = ''.join(random.sample(string.ascii_lowercase, 6))
765 self.remove(user)
766 self.mount(user, password, create=True)
Roman Sorokina45273e2017-12-20 12:03:27 +0100767
768 def lock_install_attributes(self, attrs):
769 """Set and lock install attributes for the device.
770
771 @param attrs: dict of install attributes.
772 """
Roman Sorokin0a228d12018-01-23 12:36:45 +0100773 take_tpm_ownership()
Roman Sorokin58312b22018-10-17 13:34:39 +0200774 self.wait_for_install_attributes_ready()
Roman Sorokina45273e2017-12-20 12:03:27 +0100775 for key, value in attrs.items():
776 if not self.__call(self.iface.InstallAttributesSet, key,
777 dbus.ByteArray(value + '\0')):
778 return False
779 return self.__call(self.iface.InstallAttributesFinalize)
Roman Sorokin58312b22018-10-17 13:34:39 +0200780
781 def wait_for_install_attributes_ready(self):
782 """Wait until install attributes are ready.
783 """
784 utils.poll_for_condition(
785 lambda: self.__call(self.iface.InstallAttributesIsReady),
786 timeout=300,
787 exception=error.TestError(
788 'Timeout waiting for install attributes are ready'))