blob: 746d4460e40c43ccb0e72afc17def74f5cd580e7 [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
Will Drewryd2fed972013-12-05 16:35:51 -06005import dbus, logging, os, random, re, shutil, string, time
barfab@chromium.orgb6d29932012-04-11 09:46:43 +02006
7import common, constants
barfab@chromium.org5c374632012-04-05 16:50:56 +02008from autotest_lib.client.bin import utils
Chris Masone5e06f182010-03-23 08:29:52 -07009from autotest_lib.client.common_lib import error
Eric Lic4d8f4a2010-12-10 09:49:23 -080010
Sean Oe5d8fd02010-09-30 10:44:44 +020011CRYPTOHOME_CMD = '/usr/sbin/cryptohome'
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040012GUEST_USER_NAME = '$guest'
Sean Oe5d8fd02010-09-30 10:44:44 +020013
Chris Masone5d010aa2013-05-06 14:38:42 -070014class ChromiumOSError(error.TestError):
Sean Oe5d8fd02010-09-30 10:44:44 +020015 """Generic error for ChromiumOS-specific exceptions."""
16 pass
17
Sean Oe5d8fd02010-09-30 10:44:44 +020018def __run_cmd(cmd):
19 return utils.system_output(cmd + ' 2>&1', retain_output=True,
20 ignore_status=True).strip()
21
Sean Oe5d8fd02010-09-30 10:44:44 +020022def get_user_hash(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +020023 """Get the user hash for the given user."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040024 return utils.system_output(['cryptohome', '--action=obfuscate_user',
25 '--user=%s' % user])
Sean Oe5d8fd02010-09-30 10:44:44 +020026
27
barfab@chromium.org5c374632012-04-05 16:50:56 +020028def user_path(user):
29 """Get the user mount point for the given user."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040030 return utils.system_output(['cryptohome-path', 'user', user])
barfab@chromium.org5c374632012-04-05 16:50:56 +020031
32
33def system_path(user):
34 """Get the system mount point for the given user."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040035 return utils.system_output(['cryptohome-path', 'system', user])
barfab@chromium.org5c374632012-04-05 16:50:56 +020036
37
Chris Masone5d010aa2013-05-06 14:38:42 -070038def ensure_clean_cryptohome_for(user, password=None):
39 """Ensure a fresh cryptohome exists for user.
40
41 @param user: user who needs a shiny new cryptohome.
42 @param password: if unset, a random password will be used.
43 """
44 if not password:
45 password = ''.join(random.sample(string.ascii_lowercase, 6))
46 remove_vault(user)
47 mount_vault(user, password, create=True)
48
49
Frank Farzand5e36312012-01-13 14:34:03 -080050def get_tpm_status():
51 """Get the TPM status.
52
53 Returns:
54 A TPM status dictionary, for example:
55 { 'Enabled': True,
56 'Owned': True,
57 'Being Owned': False,
58 'Ready': True,
59 'Password': ''
60 }
61 """
62 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_status')
63 status = {}
64 for field in ['Enabled', 'Owned', 'Being Owned', 'Ready']:
65 match = re.search('TPM %s: (true|false)' % field, out)
66 if not match:
67 raise ChromiumOSError('Invalid TPM status: "%s".' % out)
68 status[field] = match.group(1) == 'true'
69 match = re.search('TPM Password: (\w*)', out)
70 status['Password'] = ''
71 if match:
72 status['Password'] = match.group(1)
73 return status
74
75
Darren Krahn5f880f62012-10-02 15:17:59 -070076def get_tpm_attestation_status():
77 """Get the TPM attestation status. Works similar to get_tpm_status().
78 """
79 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_attestation_status')
80 status = {}
81 for field in ['Prepared', 'Enrolled']:
82 match = re.search('Attestation %s: (true|false)' % field, out)
83 if not match:
84 raise ChromiumOSError('Invalid attestation status: "%s".' % out)
85 status[field] = match.group(1) == 'true'
86 return status
87
88
Frank Farzand5e36312012-01-13 14:34:03 -080089def take_tpm_ownership():
90 """Take TPM owernship.
91
92 Blocks until TPM is owned.
93 """
94 __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_take_ownership')
95 __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_wait_ownership')
96
97
Darren Krahn0e73e7f2012-09-05 15:35:15 -070098def verify_ek():
99 """Verify the TPM endorsement key.
100
101 Returns true if EK is valid.
102 """
103 cmd = CRYPTOHOME_CMD + ' --action=tpm_verify_ek'
104 return (utils.system(cmd, ignore_status=True) == 0)
105
106
Sean Oe5d8fd02010-09-30 10:44:44 +0200107def remove_vault(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200108 """Remove the given user's vault from the shadow directory."""
Sean Oe5d8fd02010-09-30 10:44:44 +0200109 logging.debug('user is %s', user)
110 user_hash = get_user_hash(user)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200111 logging.debug('Removing vault for user %s with hash %s' % (user, user_hash))
Sean Oe5d8fd02010-09-30 10:44:44 +0200112 cmd = CRYPTOHOME_CMD + ' --action=remove --force --user=%s' % user
113 __run_cmd(cmd)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200114 # Ensure that the vault does not exist.
115 if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
116 raise ChromiumOSError('Cryptohome could not remove the user''s vault.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200117
118
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200119def remove_all_vaults():
120 """Remove any existing vaults from the shadow directory.
121
122 This function must be run with root privileges.
123 """
barfab@chromium.org5c374632012-04-05 16:50:56 +0200124 for item in os.listdir(constants.SHADOW_ROOT):
125 abs_item = os.path.join(constants.SHADOW_ROOT, item)
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200126 if os.path.isdir(os.path.join(abs_item, 'vault')):
127 logging.debug('Removing vault for user with hash %s' % item)
128 shutil.rmtree(abs_item)
129
130
Sean Oe5d8fd02010-09-30 10:44:44 +0200131def mount_vault(user, password, create=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200132 """Mount the given user's vault."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400133 args = [CRYPTOHOME_CMD, '--action=mount', '--user=%s' % user,
Chris Masone3543e512013-11-04 13:09:30 -0800134 '--password=%s' % password, '--async']
Sean Oe5d8fd02010-09-30 10:44:44 +0200135 if create:
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400136 args.append('--create')
Chris Masone3543e512013-11-04 13:09:30 -0800137 logging.info(__run_cmd(' '.join(args)))
barfab@chromium.org5c374632012-04-05 16:50:56 +0200138 # Ensure that the vault exists in the shadow directory.
Sean Oe5d8fd02010-09-30 10:44:44 +0200139 user_hash = get_user_hash(user)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200140 if not os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
Sean Oe5d8fd02010-09-30 10:44:44 +0200141 raise ChromiumOSError('Cryptohome vault not found after mount.')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200142 # Ensure that the vault is mounted.
143 if not is_vault_mounted(
144 user=user,
145 device_regex=constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER,
146 allow_fail=True):
147 raise ChromiumOSError('Cryptohome created a vault but did not mount.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200148
149
Chris Masone5d010aa2013-05-06 14:38:42 -0700150def mount_guest():
151 """Mount the given user's vault."""
Chris Masone3543e512013-11-04 13:09:30 -0800152 args = [CRYPTOHOME_CMD, '--action=mount_guest', '--async']
153 logging.info(__run_cmd(' '.join(args)))
Chris Masone5d010aa2013-05-06 14:38:42 -0700154 # Ensure that the guest tmpfs is mounted.
155 if not is_guest_vault_mounted(allow_fail=True):
156 raise ChromiumOSError('Cryptohome did not mount tmpfs.')
157
158
Sean Oe5d8fd02010-09-30 10:44:44 +0200159def test_auth(user, password):
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400160 cmd = [CRYPTOHOME_CMD, '--action=test_auth', '--user=%s' % user,
161 '--password=%s' % password, '--async']
162 return 'Authentication succeeded' in utils.system_output(cmd)
Sean Oe5d8fd02010-09-30 10:44:44 +0200163
164
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400165def unmount_vault(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200166 """Unmount the given user's vault.
167
168 Once unmounting for a specific user is supported, the user parameter will
169 name the target user. See crosbug.com/20778.
Elly Jones686c2f42011-10-24 16:45:07 -0400170 """
Chris Masone3543e512013-11-04 13:09:30 -0800171 __run_cmd(CRYPTOHOME_CMD + ' --action=unmount')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200172 # Ensure that the vault is not mounted.
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400173 if is_vault_mounted(user, allow_fail=True):
Sean Oe5d8fd02010-09-30 10:44:44 +0200174 raise ChromiumOSError('Cryptohome did not unmount the user.')
175
176
barfab@chromium.org5c374632012-04-05 16:50:56 +0200177def __get_mount_info(mount_point, allow_fail=False):
178 """Get information about the active mount at a given mount point."""
beeps569f8672013-08-07 10:18:51 -0700179 cryptohomed_path = '/proc/$(pgrep cryptohomed)/mounts'
180 try:
181 logging.info(utils.system_output('cat %s' % cryptohomed_path))
182 mount_line = utils.system_output(
183 'grep %s %s' % (mount_point, cryptohomed_path),
184 ignore_status=allow_fail)
185 except Exception as e:
186 logging.error(e)
187 raise ChromiumOSError('Could not get info about cryptohome vault '
188 'through %s. See logs for complete mount-point.'
189 % os.path.dirname(str(mount_point)))
Sourav Poddar574bd622010-05-26 14:22:26 +0530190 return mount_line.split()
191
192
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400193def __get_user_mount_info(user, allow_fail=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200194 """Get information about the active mounts for a given user.
195
196 Returns the active mounts at the user's user and system mount points. If no
197 user is given, the active mount at the shared mount point is returned
198 (regular users have a bind-mount at this mount point for backwards
199 compatibility; the guest user has a mount at this mount point only).
200 """
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400201 return [__get_mount_info(mount_point=user_path(user),
202 allow_fail=allow_fail),
203 __get_mount_info(mount_point=system_path(user),
204 allow_fail=allow_fail)]
Jim Hebertf08f88d2011-04-22 10:33:49 -0700205
barfab@chromium.org5c374632012-04-05 16:50:56 +0200206def is_vault_mounted(
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400207 user,
barfab@chromium.org5c374632012-04-05 16:50:56 +0200208 device_regex=constants.CRYPTOHOME_DEV_REGEX_ANY,
209 fs_regex=constants.CRYPTOHOME_FS_REGEX_ANY,
210 allow_fail=False):
211 """Check whether a vault is mounted for the given user.
212
213 If no user is given, the shared mount point is checked, determining whether
214 a vault is mounted for any user.
215 """
216 user_mount_info = __get_user_mount_info(user=user, allow_fail=allow_fail)
217 for mount_info in user_mount_info:
218 if (len(mount_info) < 3 or
219 not re.match(device_regex, mount_info[0]) or
220 not re.match(fs_regex, mount_info[2])):
221 return False
222 return True
Sourav Poddar574bd622010-05-26 14:22:26 +0530223
224
barfab@chromium.org5c374632012-04-05 16:50:56 +0200225def is_guest_vault_mounted(allow_fail=False):
226 """Check whether a vault backed by tmpfs is mounted for the guest user."""
227 return is_vault_mounted(
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400228 user=GUEST_USER_NAME,
barfab@chromium.org5c374632012-04-05 16:50:56 +0200229 device_regex=constants.CRYPTOHOME_DEV_REGEX_GUEST,
230 fs_regex=constants.CRYPTOHOME_FS_REGEX_TMPFS,
231 allow_fail=allow_fail)
232
233
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400234def get_mounted_vault_devices(user, allow_fail=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200235 """Get the device(s) backing the vault mounted for the given user.
236
237 Returns the devices mounted at the user's user and system mount points. If
238 no user is given, the device mounted at the shared mount point is returned.
239 """
240 return [mount_info[0]
241 for mount_info
242 in __get_user_mount_info(user=user, allow_fail=allow_fail)
243 if len(mount_info)]
Nirnimesh66814492011-06-27 18:00:33 -0700244
245
246def canonicalize(credential):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200247 """Perform basic canonicalization of |email_address|.
Nirnimesh66814492011-06-27 18:00:33 -0700248
barfab@chromium.org5c374632012-04-05 16:50:56 +0200249 Perform basic canonicalization of |email_address|, taking into account that
250 gmail does not consider '.' or caps inside a username to matter. It also
251 ignores everything after a '+'. For example,
252 c.masone+abc@gmail.com == cMaSone@gmail.com, per
Nirnimesh66814492011-06-27 18:00:33 -0700253 http://mail.google.com/support/bin/answer.py?hl=en&ctx=mail&answer=10313
254 """
255 if not credential:
256 return None
257
258 parts = credential.split('@')
259 if len(parts) != 2:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200260 raise error.TestError('Malformed email: ' + credential)
Nirnimesh66814492011-06-27 18:00:33 -0700261
262 (name, domain) = parts
263 name = name.partition('+')[0]
barfab@chromium.org5c374632012-04-05 16:50:56 +0200264 if (domain == constants.SPECIAL_CASE_DOMAIN):
Nirnimesh66814492011-06-27 18:00:33 -0700265 name = name.replace('.', '')
266 return '@'.join([name, domain]).lower()
Elly Jones686c2f42011-10-24 16:45:07 -0400267
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200268
Will Drewryd2fed972013-12-05 16:35:51 -0600269def crash_cryptohomed():
Will Drewrydc2b0dd2013-12-10 16:41:04 -0600270 # Try to kill cryptohomed so we get something to work with.
271 pid = __run_cmd('pgrep cryptohomed')
272 try:
273 pid = int(pid)
274 except ValueError, e: # empty or invalid string
275 raise error.TestError('Cryptohomed was not running')
276 utils.system('kill -ABRT %d' % pid)
277 # CONT just in case cryptohomed had a spurious STOP.
278 utils.system('kill -CONT %d' % pid)
279 utils.poll_for_condition(
280 lambda: utils.system('ps -p %d' % pid,
281 ignore_status=True) != 0,
282 timeout=60,
283 exception=error.TestError(
284 'Timeout waiting for cryptohomed to coredump'))
285
Will Drewryd2fed972013-12-05 16:35:51 -0600286
Elly Jones2f0ebba2011-10-27 13:43:20 -0400287class CryptohomeProxy:
288 def __init__(self):
289 BUSNAME = 'org.chromium.Cryptohome'
290 PATH = '/org/chromium/Cryptohome'
291 INTERFACE = 'org.chromium.CryptohomeInterface'
292 bus = dbus.SystemBus()
293 obj = bus.get_object(BUSNAME, PATH)
294 self.iface = dbus.Interface(obj, INTERFACE)
295
Will Drewryd2fed972013-12-05 16:35:51 -0600296 # Wrap all proxied calls to catch cryptohomed failures.
297 def __call(self, method, *args):
298 try:
299 return method(*args)
300 except dbus.exceptions.DBusException, e:
301 if e.get_dbus_name() == 'org.freedesktop.DBus.Error.NoReply':
302 logging.error('Cryptohome is not responding. Sending ABRT')
303 crash_cryptohomed()
304 raise ChromiumOSError('cryptohomed aborted. Check crashes!')
305 raise e
306
Elly Jones2f0ebba2011-10-27 13:43:20 -0400307 def mount(self, user, password, create=False):
308 """Mounts a cryptohome.
309
310 Returns True if the mount succeeds or False otherwise.
311 TODO(ellyjones): Migrate mount_vault() to use a multi-user-safe
312 heuristic, then remove this method. See <crosbug.com/20778>.
313 """
Will Drewryd2fed972013-12-05 16:35:51 -0600314 out = self.__call(self.iface.Mount, user, password, create, False, [])
315 if not out:
316 return out
317 return out[1]
Elly Jones2f0ebba2011-10-27 13:43:20 -0400318
319 def unmount(self, user):
320 """Unmounts a cryptohome.
321
322 Returns True if the unmount suceeds or false otherwise.
323 TODO(ellyjones): Once there's a per-user unmount method, use it. See
324 <crosbug.com/20778>.
325 """
Will Drewryd2fed972013-12-05 16:35:51 -0600326 return self.__call(self.iface.Unmount)
Elly Jones2f0ebba2011-10-27 13:43:20 -0400327
328 def is_mounted(self, user):
329 """Tests whether a user's cryptohome is mounted."""
330 return (utils.is_mountpoint(user_path(user))
331 and utils.is_mountpoint(system_path(user)))
332
333 def require_mounted(self, user):
334 """Raises a test failure if a user's cryptohome is not mounted."""
335 utils.require_mountpoint(user_path(user))
336 utils.require_mountpoint(system_path(user))
Elly Jones4458f442012-04-16 15:42:56 -0400337
338 def migrate(self, user, oldkey, newkey):
339 """Migrates the specified user's cryptohome from one key to another."""
Will Drewryd2fed972013-12-05 16:35:51 -0600340 return self.__call(self.iface.MigrateKey, user, oldkey, newkey)
Elly Jones4458f442012-04-16 15:42:56 -0400341
342 def remove(self, user):
Will Drewryd2fed972013-12-05 16:35:51 -0600343 return self.__call(self.iface.Remove, user)