blob: 599d311a3e579190a19eb0c1b3701ed31f3b8252 [file] [log] [blame]
Frank Farzand5e36312012-01-13 14:34:03 -08001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Chris Masone5e06f182010-03-23 08:29:52 -07002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Alexis Saveryccb16be2017-02-01 16:23:15 -08005import dbus, gobject, logging, os, random, re, shutil, string, time
Will Drewry9e440792013-12-11 17:18:35 -06006from dbus.mainloop.glib import DBusGMainLoop
barfab@chromium.orgb6d29932012-04-11 09:46:43 +02007
Hsinyu Chaoe0b08e62015-08-11 10:50:37 +00008import common, constants
barfab@chromium.org5c374632012-04-05 16:50:56 +02009from autotest_lib.client.bin import utils
Chris Masone5e06f182010-03-23 08:29:52 -070010from autotest_lib.client.common_lib import error
Will Drewry9e440792013-12-11 17:18:35 -060011from autotest_lib.client.cros.cros_disks import DBusClient
Eric Lic4d8f4a2010-12-10 09:49:23 -080012
Sean Oe5d8fd02010-09-30 10:44:44 +020013CRYPTOHOME_CMD = '/usr/sbin/cryptohome'
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040014GUEST_USER_NAME = '$guest'
Kris Rambishbb5258c2014-12-16 16:51:17 -080015UNAVAILABLE_ACTION = 'Unknown action or no action given.'
Alexis Saveryccb16be2017-02-01 16:23:15 -080016MOUNT_RETRY_COUNT = 20
Dan Spaidbe9789c2017-05-19 15:18:42 +090017TEMP_MOUNT_PATTERN = '/home/.shadow/%s/temporary_mount'
18VAULT_PATH_PATTERN = '/home/.shadow/%s/vault'
Sean Oe5d8fd02010-09-30 10:44:44 +020019
Chris Masone5d010aa2013-05-06 14:38:42 -070020class ChromiumOSError(error.TestError):
Sean Oe5d8fd02010-09-30 10:44:44 +020021 """Generic error for ChromiumOS-specific exceptions."""
22 pass
23
Sean Oe5d8fd02010-09-30 10:44:44 +020024def __run_cmd(cmd):
25 return utils.system_output(cmd + ' 2>&1', retain_output=True,
26 ignore_status=True).strip()
27
Sean Oe5d8fd02010-09-30 10:44:44 +020028def get_user_hash(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +020029 """Get the user hash for the given user."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040030 return utils.system_output(['cryptohome', '--action=obfuscate_user',
31 '--user=%s' % user])
Sean Oe5d8fd02010-09-30 10:44:44 +020032
33
barfab@chromium.org5c374632012-04-05 16:50:56 +020034def user_path(user):
35 """Get the user mount point for the given user."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040036 return utils.system_output(['cryptohome-path', 'user', user])
barfab@chromium.org5c374632012-04-05 16:50:56 +020037
38
39def system_path(user):
40 """Get the system mount point for the given user."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040041 return utils.system_output(['cryptohome-path', 'system', user])
barfab@chromium.org5c374632012-04-05 16:50:56 +020042
43
Dan Spaidbe9789c2017-05-19 15:18:42 +090044def temporary_mount_path(user):
45 """Get the vault mount path used during crypto-migration for the user.
46
47 @param user: user the temporary mount should be for
48 """
49 return TEMP_MOUNT_PATTERN % (get_user_hash(user))
50
51
52def vault_path(user):
53 """ Get the vault path for the given user.
54
55 @param user: The user who's vault path should be returned.
56 """
57 return VAULT_PATH_PATTERN % (get_user_hash(user))
58
59
Chris Masone5d010aa2013-05-06 14:38:42 -070060def ensure_clean_cryptohome_for(user, password=None):
61 """Ensure a fresh cryptohome exists for user.
62
63 @param user: user who needs a shiny new cryptohome.
64 @param password: if unset, a random password will be used.
65 """
66 if not password:
67 password = ''.join(random.sample(string.ascii_lowercase, 6))
68 remove_vault(user)
69 mount_vault(user, password, create=True)
70
71
Frank Farzand5e36312012-01-13 14:34:03 -080072def get_tpm_status():
73 """Get the TPM status.
74
75 Returns:
76 A TPM status dictionary, for example:
77 { 'Enabled': True,
78 'Owned': True,
79 'Being Owned': False,
80 'Ready': True,
81 'Password': ''
82 }
83 """
84 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_status')
85 status = {}
86 for field in ['Enabled', 'Owned', 'Being Owned', 'Ready']:
87 match = re.search('TPM %s: (true|false)' % field, out)
88 if not match:
89 raise ChromiumOSError('Invalid TPM status: "%s".' % out)
90 status[field] = match.group(1) == 'true'
91 match = re.search('TPM Password: (\w*)', out)
92 status['Password'] = ''
93 if match:
94 status['Password'] = match.group(1)
95 return status
96
97
Kris Rambish82ee1c02014-12-10 17:02:39 -080098def get_tpm_more_status():
99 """Get more of the TPM status.
100
101 Returns:
102 A TPM more status dictionary, for example:
103 { 'dictionary_attack_lockout_in_effect': False,
104 'attestation_prepared': False,
105 'boot_lockbox_finalized': False,
106 'enabled': True,
107 'owned': True,
Kris Rambishbe132592014-12-17 14:26:06 -0800108 'owner_password': ''
Kris Rambish82ee1c02014-12-10 17:02:39 -0800109 'dictionary_attack_counter': 0,
110 'dictionary_attack_lockout_seconds_remaining': 0,
111 'dictionary_attack_threshold': 10,
112 'attestation_enrolled': False,
113 'initialized': True,
114 'verified_boot_measured': False,
115 'install_lockbox_finalized': True
116 }
Kris Rambishbb5258c2014-12-16 16:51:17 -0800117 An empty dictionary is returned if the command is not supported.
Kris Rambish82ee1c02014-12-10 17:02:39 -0800118 """
Kris Rambish82ee1c02014-12-10 17:02:39 -0800119 status = {}
Kris Rambishbb5258c2014-12-16 16:51:17 -0800120 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_more_status | grep :')
121 if out.startswith(UNAVAILABLE_ACTION):
122 # --action=tpm_more_status only exists >= 41.
123 logging.info('Method not supported!')
124 return status
Kris Rambish82ee1c02014-12-10 17:02:39 -0800125 for line in out.splitlines():
126 items = line.strip().split(':')
127 if items[1].strip() == 'false':
128 value = False
129 elif items[1].strip() == 'true':
130 value = True
Kris Rambishbe132592014-12-17 14:26:06 -0800131 elif items[1].strip().isdigit():
Kris Rambish82ee1c02014-12-10 17:02:39 -0800132 value = int(items[1].strip())
Kris Rambishbe132592014-12-17 14:26:06 -0800133 else:
134 value = items[1].strip(' "')
Kris Rambish82ee1c02014-12-10 17:02:39 -0800135 status[items[0]] = value
136 return status
137
138
Mary Ruthven9a0ce562017-05-30 13:01:47 -0700139def get_fwmp(cleared_fwmp=False):
140 """Get the firmware management parameters.
141
142 Args:
143 cleared_fwmp: True if the space should not exist.
144
145 Returns:
146 The dictionary with the FWMP contents, for example:
147 { 'flags': 0xbb41,
148 'developer_key_hash':
149 "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\
150 000\000\000\000\000\000\000\000\000\000\000",
151 }
152 or a dictionary with the Error if the FWMP doesn't exist and
153 cleared_fwmp is True
154 { 'error': 'CRYPTOHOME_ERROR_FIRMWARE_MANAGEMENT_PARAMETERS_INVALID' }
155
156 Raises:
157 ChromiumOSError if any expected field is not found in the cryptohome
158 output. This would typically happen when FWMP state does not match
159 'clreared_fwmp'
160 """
161 out = __run_cmd(CRYPTOHOME_CMD +
162 ' --action=get_firmware_management_parameters')
163
164 if cleared_fwmp:
165 fields = ['error']
166 else:
167 fields = ['flags', 'developer_key_hash']
168
169 status = {}
170 for field in fields:
171 match = re.search('%s: (\S+)\n' % field, out)
172 if not match:
173 raise ChromiumOSError('Invalid FWMP field %s: "%s".' %
174 (field, out))
175 status[field] = match.group(1)
176 return status
177
178
179def set_fwmp(flags, developer_key_hash=None):
180 """Set the firmware management parameter contents.
181
182 Args:
183 developer_key_hash: a string with the developer key hash
184
185 Raises:
186 ChromiumOSError cryptohome cannot set the FWMP contents
187 """
188 cmd = (CRYPTOHOME_CMD +
189 ' --action=set_firmware_management_parameters '
190 '--flags=' + flags)
191 if developer_key_hash:
192 cmd += ' --developer_key_hash=' + developer_key_hash
193
194 out = __run_cmd(cmd)
195 if 'SetFirmwareManagementParameters success' not in out:
196 raise ChromiumOSError('failed to set FWMP: %s' % out)
197
198
Kris Rambish82ee1c02014-12-10 17:02:39 -0800199def is_tpm_lockout_in_effect():
200 """Returns true if the TPM lockout is in effect; false otherwise."""
201 status = get_tpm_more_status()
Christopher Wiley94fd6b32014-12-13 18:52:03 -0800202 return status.get('dictionary_attack_lockout_in_effect', None)
Kris Rambish82ee1c02014-12-10 17:02:39 -0800203
204
David Pursell2a2ef342014-10-17 10:34:56 -0700205def get_login_status():
206 """Query the login status
207
208 Returns:
209 A login status dictionary containing:
210 { 'owner_user_exists': True|False,
211 'boot_lockbox_finalized': True|False
212 }
213 """
214 out = __run_cmd(CRYPTOHOME_CMD + ' --action=get_login_status')
215 status = {}
216 for field in ['owner_user_exists', 'boot_lockbox_finalized']:
217 match = re.search('%s: (true|false)' % field, out)
218 if not match:
219 raise ChromiumOSError('Invalid login status: "%s".' % out)
220 status[field] = match.group(1) == 'true'
221 return status
222
223
Darren Krahn5f880f62012-10-02 15:17:59 -0700224def get_tpm_attestation_status():
225 """Get the TPM attestation status. Works similar to get_tpm_status().
226 """
227 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_attestation_status')
228 status = {}
229 for field in ['Prepared', 'Enrolled']:
230 match = re.search('Attestation %s: (true|false)' % field, out)
231 if not match:
232 raise ChromiumOSError('Invalid attestation status: "%s".' % out)
233 status[field] = match.group(1) == 'true'
234 return status
235
236
Frank Farzand5e36312012-01-13 14:34:03 -0800237def take_tpm_ownership():
238 """Take TPM owernship.
239
240 Blocks until TPM is owned.
241 """
242 __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_take_ownership')
243 __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_wait_ownership')
244
245
Darren Krahn0e73e7f2012-09-05 15:35:15 -0700246def verify_ek():
247 """Verify the TPM endorsement key.
248
249 Returns true if EK is valid.
250 """
251 cmd = CRYPTOHOME_CMD + ' --action=tpm_verify_ek'
252 return (utils.system(cmd, ignore_status=True) == 0)
253
254
Sean Oe5d8fd02010-09-30 10:44:44 +0200255def remove_vault(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200256 """Remove the given user's vault from the shadow directory."""
Sean Oe5d8fd02010-09-30 10:44:44 +0200257 logging.debug('user is %s', user)
258 user_hash = get_user_hash(user)
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700259 logging.debug('Removing vault for user %s with hash %s', user, user_hash)
Sean Oe5d8fd02010-09-30 10:44:44 +0200260 cmd = CRYPTOHOME_CMD + ' --action=remove --force --user=%s' % user
261 __run_cmd(cmd)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200262 # Ensure that the vault does not exist.
263 if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
Darren Krahne6c44b92014-03-31 12:11:08 -0700264 raise ChromiumOSError('Cryptohome could not remove the user\'s vault.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200265
266
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200267def remove_all_vaults():
268 """Remove any existing vaults from the shadow directory.
269
270 This function must be run with root privileges.
271 """
barfab@chromium.org5c374632012-04-05 16:50:56 +0200272 for item in os.listdir(constants.SHADOW_ROOT):
273 abs_item = os.path.join(constants.SHADOW_ROOT, item)
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200274 if os.path.isdir(os.path.join(abs_item, 'vault')):
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700275 logging.debug('Removing vault for user with hash %s', item)
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200276 shutil.rmtree(abs_item)
277
278
Sean Oe5d8fd02010-09-30 10:44:44 +0200279def mount_vault(user, password, create=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200280 """Mount the given user's vault."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400281 args = [CRYPTOHOME_CMD, '--action=mount', '--user=%s' % user,
Chris Masone3543e512013-11-04 13:09:30 -0800282 '--password=%s' % password, '--async']
Sean Oe5d8fd02010-09-30 10:44:44 +0200283 if create:
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400284 args.append('--create')
Chris Masone3543e512013-11-04 13:09:30 -0800285 logging.info(__run_cmd(' '.join(args)))
barfab@chromium.org5c374632012-04-05 16:50:56 +0200286 # Ensure that the vault exists in the shadow directory.
Sean Oe5d8fd02010-09-30 10:44:44 +0200287 user_hash = get_user_hash(user)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200288 if not os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
Alexis Saveryccb16be2017-02-01 16:23:15 -0800289 retry = 0
290 mounted = False
291 while retry < MOUNT_RETRY_COUNT and not mounted:
292 time.sleep(1)
293 logging.info("Retry " + str(retry + 1))
294 __run_cmd(' '.join(args))
295 # TODO: Remove this additional call to get_user_hash(user) when
296 # crbug.com/690994 is fixed
297 user_hash = get_user_hash(user)
298 if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
299 mounted = True
300 retry += 1
301 if not mounted:
302 raise ChromiumOSError('Cryptohome vault not found after mount.')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200303 # Ensure that the vault is mounted.
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700304 if not is_permanent_vault_mounted(user=user, allow_fail=True):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200305 raise ChromiumOSError('Cryptohome created a vault but did not mount.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200306
307
Chris Masone5d010aa2013-05-06 14:38:42 -0700308def mount_guest():
Sergey Poromov533008f2017-10-13 14:07:43 +0200309 """Mount the guest vault."""
Chris Masone3543e512013-11-04 13:09:30 -0800310 args = [CRYPTOHOME_CMD, '--action=mount_guest', '--async']
311 logging.info(__run_cmd(' '.join(args)))
Sergey Poromov533008f2017-10-13 14:07:43 +0200312 # Ensure that the guest vault is mounted.
Chris Masone5d010aa2013-05-06 14:38:42 -0700313 if not is_guest_vault_mounted(allow_fail=True):
Sergey Poromov533008f2017-10-13 14:07:43 +0200314 raise ChromiumOSError('Cryptohome did not mount guest vault.')
Chris Masone5d010aa2013-05-06 14:38:42 -0700315
316
Sean Oe5d8fd02010-09-30 10:44:44 +0200317def test_auth(user, password):
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400318 cmd = [CRYPTOHOME_CMD, '--action=test_auth', '--user=%s' % user,
319 '--password=%s' % password, '--async']
320 return 'Authentication succeeded' in utils.system_output(cmd)
Sean Oe5d8fd02010-09-30 10:44:44 +0200321
322
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400323def unmount_vault(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200324 """Unmount the given user's vault.
325
326 Once unmounting for a specific user is supported, the user parameter will
327 name the target user. See crosbug.com/20778.
Elly Jones686c2f42011-10-24 16:45:07 -0400328 """
Chris Masone3543e512013-11-04 13:09:30 -0800329 __run_cmd(CRYPTOHOME_CMD + ' --action=unmount')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200330 # Ensure that the vault is not mounted.
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400331 if is_vault_mounted(user, allow_fail=True):
Sean Oe5d8fd02010-09-30 10:44:44 +0200332 raise ChromiumOSError('Cryptohome did not unmount the user.')
333
334
barfab@chromium.org5c374632012-04-05 16:50:56 +0200335def __get_mount_info(mount_point, allow_fail=False):
336 """Get information about the active mount at a given mount point."""
beeps569f8672013-08-07 10:18:51 -0700337 cryptohomed_path = '/proc/$(pgrep cryptohomed)/mounts'
338 try:
Daniel Erat2ec32792017-01-31 18:26:59 -0700339 logging.debug("Active cryptohome mounts:\n" +
340 utils.system_output('cat %s' % cryptohomed_path))
beeps569f8672013-08-07 10:18:51 -0700341 mount_line = utils.system_output(
342 'grep %s %s' % (mount_point, cryptohomed_path),
343 ignore_status=allow_fail)
344 except Exception as e:
345 logging.error(e)
346 raise ChromiumOSError('Could not get info about cryptohome vault '
347 'through %s. See logs for complete mount-point.'
348 % os.path.dirname(str(mount_point)))
Sourav Poddar574bd622010-05-26 14:22:26 +0530349 return mount_line.split()
350
351
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400352def __get_user_mount_info(user, allow_fail=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200353 """Get information about the active mounts for a given user.
354
355 Returns the active mounts at the user's user and system mount points. If no
356 user is given, the active mount at the shared mount point is returned
357 (regular users have a bind-mount at this mount point for backwards
358 compatibility; the guest user has a mount at this mount point only).
359 """
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400360 return [__get_mount_info(mount_point=user_path(user),
361 allow_fail=allow_fail),
362 __get_mount_info(mount_point=system_path(user),
363 allow_fail=allow_fail)]
Jim Hebertf08f88d2011-04-22 10:33:49 -0700364
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700365def is_vault_mounted(user, regexes=None, allow_fail=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200366 """Check whether a vault is mounted for the given user.
367
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700368 user: If no user is given, the shared mount point is checked, determining
369 whether a vault is mounted for any user.
370 regexes: dictionary of regexes to matches against the mount information.
371 The mount filesystem for the user's user and system mounts point must
372 match one of the keys.
373 The mount source point must match the selected device regex.
374
375 In addition, if mounted over ext4, we check the directory is encrypted.
barfab@chromium.org5c374632012-04-05 16:50:56 +0200376 """
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700377 if regexes is None:
378 regexes = {
379 constants.CRYPTOHOME_FS_REGEX_ANY :
380 constants.CRYPTOHOME_DEV_REGEX_ANY
381 }
barfab@chromium.org5c374632012-04-05 16:50:56 +0200382 user_mount_info = __get_user_mount_info(user=user, allow_fail=allow_fail)
383 for mount_info in user_mount_info:
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700384 # Look at each /proc/../mount lines that match mount point for a given
385 # user user/system mount (/home/user/.... /home/root/...)
386
387 # We should have at least 3 arguments (source, mount, type of mount)
388 if len(mount_info) < 3:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200389 return False
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700390
391 device_regex = None
392 for fs_regex in regexes.keys():
393 if re.match(fs_regex, mount_info[2]):
394 device_regex = regexes[fs_regex]
395 break
396
397 if not device_regex:
398 # The thrid argument in not the expectd mount point type.
399 return False
400
401 # Check if the mount source match the device regex: it can be loose,
402 # (anything) or stricter if we expect guest filesystem.
403 if not re.match(device_regex, mount_info[0]):
404 return False
405
Sergey Poromov533008f2017-10-13 14:07:43 +0200406 if (re.match(constants.CRYPTOHOME_FS_REGEX_EXT4, mount_info[2])
407 and not(re.match(constants.CRYPTOHOME_DEV_REGEX_LOOP_DEVICE,
408 mount_info[0]))):
409 # Ephemeral cryptohome uses ext4 mount from a loop device,
410 # otherwise it should be ext4 crypto. Check there is an encryption
411 # key for that directory.
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700412 find_key_cmd_list = ['e4crypt get_policy %s' % (mount_info[1]),
413 'cut -d \' \' -f 2']
414 key = __run_cmd(' | ' .join(find_key_cmd_list))
415 cmd_list = ['keyctl show @s',
416 'grep %s' % (key),
417 'wc -l']
418 out = __run_cmd(' | '.join(cmd_list))
419 if int(out) != 1:
420 return False
barfab@chromium.org5c374632012-04-05 16:50:56 +0200421 return True
Sourav Poddar574bd622010-05-26 14:22:26 +0530422
423
barfab@chromium.org5c374632012-04-05 16:50:56 +0200424def is_guest_vault_mounted(allow_fail=False):
Sergey Poromov533008f2017-10-13 14:07:43 +0200425 """Check whether a vault is mounted for the guest user.
Sergey Poromovd85dce52017-12-27 11:10:51 +0100426 It should be a mount of an ext4 partition on a loop device
427 or be backed by tmpfs.
Sergey Poromov533008f2017-10-13 14:07:43 +0200428 """
barfab@chromium.org5c374632012-04-05 16:50:56 +0200429 return is_vault_mounted(
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400430 user=GUEST_USER_NAME,
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700431 regexes={
Sergey Poromovd85dce52017-12-27 11:10:51 +0100432 # Remove tmpfs support when it becomes unnecessary as all guest
433 # modes will use ext4 on a loop device.
Sergey Poromov533008f2017-10-13 14:07:43 +0200434 constants.CRYPTOHOME_FS_REGEX_EXT4 :
435 constants.CRYPTOHOME_DEV_REGEX_LOOP_DEVICE,
Sergey Poromovd85dce52017-12-27 11:10:51 +0100436 constants.CRYPTOHOME_FS_REGEX_TMPFS :
437 constants.CRYPTOHOME_DEV_REGEX_GUEST,
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700438 },
barfab@chromium.org5c374632012-04-05 16:50:56 +0200439 allow_fail=allow_fail)
440
Gwendal Grignou6bad6722016-06-09 16:36:04 -0700441def is_permanent_vault_mounted(user, allow_fail=False):
442 """Check if user is mounted over ecryptfs or ext4 crypto. """
443 return is_vault_mounted(
444 user=user,
445 regexes={
446 constants.CRYPTOHOME_FS_REGEX_ECRYPTFS :
447 constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER_SHADOW,
448 constants.CRYPTOHOME_FS_REGEX_EXT4 :
449 constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER_DEVICE,
450 },
451 allow_fail=allow_fail)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200452
Kazuhiro Inabaa3bf6452017-02-08 11:41:50 +0900453def get_mounted_vault_path(user, allow_fail=False):
454 """Get the path where the decrypted data for the user is located."""
455 return os.path.join(constants.SHADOW_ROOT, get_user_hash(user), 'mount')
Nirnimesh66814492011-06-27 18:00:33 -0700456
457
458def canonicalize(credential):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200459 """Perform basic canonicalization of |email_address|.
Nirnimesh66814492011-06-27 18:00:33 -0700460
barfab@chromium.org5c374632012-04-05 16:50:56 +0200461 Perform basic canonicalization of |email_address|, taking into account that
462 gmail does not consider '.' or caps inside a username to matter. It also
463 ignores everything after a '+'. For example,
464 c.masone+abc@gmail.com == cMaSone@gmail.com, per
Nirnimesh66814492011-06-27 18:00:33 -0700465 http://mail.google.com/support/bin/answer.py?hl=en&ctx=mail&answer=10313
466 """
467 if not credential:
468 return None
469
470 parts = credential.split('@')
471 if len(parts) != 2:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200472 raise error.TestError('Malformed email: ' + credential)
Nirnimesh66814492011-06-27 18:00:33 -0700473
474 (name, domain) = parts
475 name = name.partition('+')[0]
barfab@chromium.org5c374632012-04-05 16:50:56 +0200476 if (domain == constants.SPECIAL_CASE_DOMAIN):
Nirnimesh66814492011-06-27 18:00:33 -0700477 name = name.replace('.', '')
478 return '@'.join([name, domain]).lower()
Elly Jones686c2f42011-10-24 16:45:07 -0400479
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200480
Will Drewryd2fed972013-12-05 16:35:51 -0600481def crash_cryptohomed():
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600482 # Try to kill cryptohomed so we get something to work with.
483 pid = __run_cmd('pgrep cryptohomed')
484 try:
Will Drewry9e440792013-12-11 17:18:35 -0600485 pid = int(pid)
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600486 except ValueError, e: # empty or invalid string
Will Drewry9e440792013-12-11 17:18:35 -0600487 raise error.TestError('Cryptohomed was not running')
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600488 utils.system('kill -ABRT %d' % pid)
489 # CONT just in case cryptohomed had a spurious STOP.
490 utils.system('kill -CONT %d' % pid)
491 utils.poll_for_condition(
492 lambda: utils.system('ps -p %d' % pid,
493 ignore_status=True) != 0,
Will Drewry934d1532014-01-30 16:23:17 -0600494 timeout=180,
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600495 exception=error.TestError(
496 'Timeout waiting for cryptohomed to coredump'))
497
Will Drewryd2fed972013-12-05 16:35:51 -0600498
Dan Spaidbe9789c2017-05-19 15:18:42 +0900499def create_ecryptfs_homedir(user, password):
500 """Creates a new home directory as ecryptfs.
501
502 If a home directory for the user exists already, it will be removed.
503 The resulting home directory will be mounted.
504
505 @param user: Username to create the home directory for.
506 @param password: Password to use when creating the home directory.
507 """
508 unmount_vault(user)
509 remove_vault(user)
510 args = [
511 CRYPTOHOME_CMD,
512 '--action=mount_ex',
513 '--user=%s' % user,
514 '--password=%s' % password,
515 '--key_label=foo',
516 '--ecryptfs',
517 '--create']
518 logging.info(__run_cmd(' '.join(args)))
519 if not is_vault_mounted(user, regexes={
520 constants.CRYPTOHOME_FS_REGEX_ECRYPTFS :
521 constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER_SHADOW
522 }, allow_fail=True):
523 raise ChromiumOSError('Ecryptfs home could not be created')
524
525
526def do_dircrypto_migration(user, password, timeout=600):
527 """Start dircrypto migration for the user.
528
529 @param user: The user to migrate.
530 @param password: The password used to mount the users vault
531 @param timeout: How long in seconds to wait for the migration to finish
532 before failing.
533 """
534 unmount_vault(user)
535 args = [
536 CRYPTOHOME_CMD,
537 '--action=mount_ex',
538 '--to_migrate_from_ecryptfs',
539 '--user=%s' % user,
540 '--password=%s' % password]
541 logging.info(__run_cmd(' '.join(args)))
542 if not __get_mount_info(temporary_mount_path(user), allow_fail=True):
543 raise ChromiumOSError('Failed to mount home for migration')
544 args = [CRYPTOHOME_CMD, '--action=migrate_to_dircrypto', '--user=%s' % user]
545 logging.info(__run_cmd(' '.join(args)))
546 utils.poll_for_condition(
547 lambda: not __get_mount_info(
548 temporary_mount_path(user), allow_fail=True),
549 timeout=timeout,
550 exception=error.TestError(
551 'Timeout waiting for dircrypto migration to finish'))
552
553
Will Drewry9e440792013-12-11 17:18:35 -0600554class CryptohomeProxy(DBusClient):
555 """A DBus proxy client for testing the Cryptohome DBus server.
556 """
557 CRYPTOHOME_BUS_NAME = 'org.chromium.Cryptohome'
558 CRYPTOHOME_OBJECT_PATH = '/org/chromium/Cryptohome'
559 CRYPTOHOME_INTERFACE = 'org.chromium.CryptohomeInterface'
560 ASYNC_CALL_STATUS_SIGNAL = 'AsyncCallStatus'
561 ASYNC_CALL_STATUS_SIGNAL_ARGUMENTS = (
562 'async_id', 'return_status', 'return_code'
563 )
564 DBUS_PROPERTIES_INTERFACE = 'org.freedesktop.DBus.Properties'
565
Chris Masone19e305e2014-03-14 15:13:46 -0700566
Chris Masone64170f82014-03-14 15:47:05 -0700567 def __init__(self, bus_loop=None):
Will Drewry9e440792013-12-11 17:18:35 -0600568 self.main_loop = gobject.MainLoop()
Will Drewry78db9dc2014-04-01 16:34:23 -0500569 if bus_loop is None:
Chris Masone64170f82014-03-14 15:47:05 -0700570 bus_loop = DBusGMainLoop(set_as_default=True)
Will Drewry9e440792013-12-11 17:18:35 -0600571 self.bus = dbus.SystemBus(mainloop=bus_loop)
572 super(CryptohomeProxy, self).__init__(self.main_loop, self.bus,
573 self.CRYPTOHOME_BUS_NAME,
574 self.CRYPTOHOME_OBJECT_PATH)
575 self.iface = dbus.Interface(self.proxy_object,
576 self.CRYPTOHOME_INTERFACE)
577 self.properties = dbus.Interface(self.proxy_object,
578 self.DBUS_PROPERTIES_INTERFACE)
579 self.handle_signal(self.CRYPTOHOME_INTERFACE,
580 self.ASYNC_CALL_STATUS_SIGNAL,
581 self.ASYNC_CALL_STATUS_SIGNAL_ARGUMENTS)
Elly Jones2f0ebba2011-10-27 13:43:20 -0400582
Chris Masone19e305e2014-03-14 15:13:46 -0700583
Will Drewryd2fed972013-12-05 16:35:51 -0600584 # Wrap all proxied calls to catch cryptohomed failures.
585 def __call(self, method, *args):
586 try:
Chris Masonef59d9df2014-03-14 12:05:32 -0700587 return method(*args, timeout=180)
Will Drewryd2fed972013-12-05 16:35:51 -0600588 except dbus.exceptions.DBusException, e:
589 if e.get_dbus_name() == 'org.freedesktop.DBus.Error.NoReply':
590 logging.error('Cryptohome is not responding. Sending ABRT')
591 crash_cryptohomed()
592 raise ChromiumOSError('cryptohomed aborted. Check crashes!')
593 raise e
594
Chris Masone19e305e2014-03-14 15:13:46 -0700595
Will Drewry9e440792013-12-11 17:18:35 -0600596 def __wait_for_specific_signal(self, signal, data):
597 """Wait for the |signal| with matching |data|
598 Returns the resulting dict on success or {} on error.
599 """
Will Drewryc4de5ff2014-02-03 13:26:57 -0600600 # Do not bubble up the timeout here, just return {}.
601 result = {}
602 try:
603 result = self.wait_for_signal(signal)
604 except utils.TimeoutError:
605 return {}
Will Drewry9e440792013-12-11 17:18:35 -0600606 for k in data.keys():
607 if not result.has_key(k) or result[k] != data[k]:
608 return {}
609 return result
610
Chris Masone19e305e2014-03-14 15:13:46 -0700611
Will Drewry9e440792013-12-11 17:18:35 -0600612 # Perform a data-less async call.
613 # TODO(wad) Add __async_data_call.
614 def __async_call(self, method, *args):
Will Drewryfef135a2014-05-23 16:02:14 -0500615 # Clear out any superfluous async call signals.
616 self.clear_signal_content(self.ASYNC_CALL_STATUS_SIGNAL)
Will Drewry9e440792013-12-11 17:18:35 -0600617 out = self.__call(method, *args)
618 logging.debug('Issued call ' + str(method) +
619 ' with async_id ' + str(out))
620 result = {}
621 try:
Will Drewry934d1532014-01-30 16:23:17 -0600622 # __wait_for_specific_signal has a 10s timeout
Will Drewry9e440792013-12-11 17:18:35 -0600623 result = utils.poll_for_condition(
624 lambda: self.__wait_for_specific_signal(
625 self.ASYNC_CALL_STATUS_SIGNAL, {'async_id' : out}),
Will Drewry934d1532014-01-30 16:23:17 -0600626 timeout=180,
Will Drewry9e440792013-12-11 17:18:35 -0600627 desc='matching %s signal' % self.ASYNC_CALL_STATUS_SIGNAL)
628 except utils.TimeoutError, e:
629 logging.error('Cryptohome timed out. Sending ABRT.')
630 crash_cryptohomed()
631 raise ChromiumOSError('cryptohomed aborted. Check crashes!')
632 return result
633
Chris Masone19e305e2014-03-14 15:13:46 -0700634
Will Drewry9e440792013-12-11 17:18:35 -0600635 def mount(self, user, password, create=False, async=True):
Elly Jones2f0ebba2011-10-27 13:43:20 -0400636 """Mounts a cryptohome.
637
638 Returns True if the mount succeeds or False otherwise.
639 TODO(ellyjones): Migrate mount_vault() to use a multi-user-safe
640 heuristic, then remove this method. See <crosbug.com/20778>.
641 """
Will Drewry9e440792013-12-11 17:18:35 -0600642 if async:
643 return self.__async_call(self.iface.AsyncMount, user, password,
644 create, False, [])['return_status']
Will Drewryd2fed972013-12-05 16:35:51 -0600645 out = self.__call(self.iface.Mount, user, password, create, False, [])
Will Drewry9e440792013-12-11 17:18:35 -0600646 # Sync returns (return code, return status)
647 return out[1] if len(out) > 1 else False
Elly Jones2f0ebba2011-10-27 13:43:20 -0400648
Chris Masone19e305e2014-03-14 15:13:46 -0700649
Elly Jones2f0ebba2011-10-27 13:43:20 -0400650 def unmount(self, user):
651 """Unmounts a cryptohome.
652
653 Returns True if the unmount suceeds or false otherwise.
654 TODO(ellyjones): Once there's a per-user unmount method, use it. See
655 <crosbug.com/20778>.
656 """
Will Drewryd2fed972013-12-05 16:35:51 -0600657 return self.__call(self.iface.Unmount)
Elly Jones2f0ebba2011-10-27 13:43:20 -0400658
Chris Masone19e305e2014-03-14 15:13:46 -0700659
Elly Jones2f0ebba2011-10-27 13:43:20 -0400660 def is_mounted(self, user):
661 """Tests whether a user's cryptohome is mounted."""
662 return (utils.is_mountpoint(user_path(user))
663 and utils.is_mountpoint(system_path(user)))
664
Chris Masone19e305e2014-03-14 15:13:46 -0700665
Elly Jones2f0ebba2011-10-27 13:43:20 -0400666 def require_mounted(self, user):
667 """Raises a test failure if a user's cryptohome is not mounted."""
668 utils.require_mountpoint(user_path(user))
669 utils.require_mountpoint(system_path(user))
Elly Jones4458f442012-04-16 15:42:56 -0400670
Chris Masone19e305e2014-03-14 15:13:46 -0700671
Will Drewry9e440792013-12-11 17:18:35 -0600672 def migrate(self, user, oldkey, newkey, async=True):
Elly Jones4458f442012-04-16 15:42:56 -0400673 """Migrates the specified user's cryptohome from one key to another."""
Will Drewry9e440792013-12-11 17:18:35 -0600674 if async:
675 return self.__async_call(self.iface.AsyncMigrateKey,
676 user, oldkey, newkey)['return_status']
Will Drewryd2fed972013-12-05 16:35:51 -0600677 return self.__call(self.iface.MigrateKey, user, oldkey, newkey)
Elly Jones4458f442012-04-16 15:42:56 -0400678
Chris Masone19e305e2014-03-14 15:13:46 -0700679
Will Drewry9e440792013-12-11 17:18:35 -0600680 def remove(self, user, async=True):
681 if async:
682 return self.__async_call(self.iface.AsyncRemove,
683 user)['return_status']
Will Drewryd2fed972013-12-05 16:35:51 -0600684 return self.__call(self.iface.Remove, user)
Chris Masone19e305e2014-03-14 15:13:46 -0700685
686
687 def ensure_clean_cryptohome_for(self, user, password=None):
688 """Ensure a fresh cryptohome exists for user.
689
690 @param user: user who needs a shiny new cryptohome.
691 @param password: if unset, a random password will be used.
692 """
693 if not password:
694 password = ''.join(random.sample(string.ascii_lowercase, 6))
695 self.remove(user)
696 self.mount(user, password, create=True)
Roman Sorokina45273e2017-12-20 12:03:27 +0100697
698 def lock_install_attributes(self, attrs):
699 """Set and lock install attributes for the device.
700
701 @param attrs: dict of install attributes.
702 """
Roman Sorokin0a228d12018-01-23 12:36:45 +0100703 take_tpm_ownership()
Roman Sorokina45273e2017-12-20 12:03:27 +0100704 for key, value in attrs.items():
705 if not self.__call(self.iface.InstallAttributesSet, key,
706 dbus.ByteArray(value + '\0')):
707 return False
708 return self.__call(self.iface.InstallAttributesFinalize)