blob: 289372dc820676e9b66f1ad2e4bd719f2c255c14 [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
Chris Masone5d010aa2013-05-06 14:38:42 -07005import dbus, logging, os, random, re, shutil, string
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
18
19def __run_cmd(cmd):
20 return utils.system_output(cmd + ' 2>&1', retain_output=True,
21 ignore_status=True).strip()
22
23
24def get_user_hash(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +020025 """Get the user hash for the given user."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040026 return utils.system_output(['cryptohome', '--action=obfuscate_user',
27 '--user=%s' % user])
Sean Oe5d8fd02010-09-30 10:44:44 +020028
29
barfab@chromium.org5c374632012-04-05 16:50:56 +020030def user_path(user):
31 """Get the user mount point for the given user."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040032 return utils.system_output(['cryptohome-path', 'user', user])
barfab@chromium.org5c374632012-04-05 16:50:56 +020033
34
35def system_path(user):
36 """Get the system mount point for the given user."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -040037 return utils.system_output(['cryptohome-path', 'system', user])
barfab@chromium.org5c374632012-04-05 16:50:56 +020038
39
Chris Masone5d010aa2013-05-06 14:38:42 -070040def ensure_clean_cryptohome_for(user, password=None):
41 """Ensure a fresh cryptohome exists for user.
42
43 @param user: user who needs a shiny new cryptohome.
44 @param password: if unset, a random password will be used.
45 """
46 if not password:
47 password = ''.join(random.sample(string.ascii_lowercase, 6))
48 remove_vault(user)
49 mount_vault(user, password, create=True)
50
51
Frank Farzand5e36312012-01-13 14:34:03 -080052def get_tpm_status():
53 """Get the TPM status.
54
55 Returns:
56 A TPM status dictionary, for example:
57 { 'Enabled': True,
58 'Owned': True,
59 'Being Owned': False,
60 'Ready': True,
61 'Password': ''
62 }
63 """
64 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_status')
65 status = {}
66 for field in ['Enabled', 'Owned', 'Being Owned', 'Ready']:
67 match = re.search('TPM %s: (true|false)' % field, out)
68 if not match:
69 raise ChromiumOSError('Invalid TPM status: "%s".' % out)
70 status[field] = match.group(1) == 'true'
71 match = re.search('TPM Password: (\w*)', out)
72 status['Password'] = ''
73 if match:
74 status['Password'] = match.group(1)
75 return status
76
77
Darren Krahn5f880f62012-10-02 15:17:59 -070078def get_tpm_attestation_status():
79 """Get the TPM attestation status. Works similar to get_tpm_status().
80 """
81 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_attestation_status')
82 status = {}
83 for field in ['Prepared', 'Enrolled']:
84 match = re.search('Attestation %s: (true|false)' % field, out)
85 if not match:
86 raise ChromiumOSError('Invalid attestation status: "%s".' % out)
87 status[field] = match.group(1) == 'true'
88 return status
89
90
Frank Farzand5e36312012-01-13 14:34:03 -080091def take_tpm_ownership():
92 """Take TPM owernship.
93
94 Blocks until TPM is owned.
95 """
96 __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_take_ownership')
97 __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_wait_ownership')
98
99
Darren Krahn0e73e7f2012-09-05 15:35:15 -0700100def verify_ek():
101 """Verify the TPM endorsement key.
102
103 Returns true if EK is valid.
104 """
105 cmd = CRYPTOHOME_CMD + ' --action=tpm_verify_ek'
106 return (utils.system(cmd, ignore_status=True) == 0)
107
108
Sean Oe5d8fd02010-09-30 10:44:44 +0200109def remove_vault(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200110 """Remove the given user's vault from the shadow directory."""
Sean Oe5d8fd02010-09-30 10:44:44 +0200111 logging.debug('user is %s', user)
112 user_hash = get_user_hash(user)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200113 logging.debug('Removing vault for user %s with hash %s' % (user, user_hash))
Sean Oe5d8fd02010-09-30 10:44:44 +0200114 cmd = CRYPTOHOME_CMD + ' --action=remove --force --user=%s' % user
115 __run_cmd(cmd)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200116 # Ensure that the vault does not exist.
117 if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
118 raise ChromiumOSError('Cryptohome could not remove the user''s vault.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200119
120
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200121def remove_all_vaults():
122 """Remove any existing vaults from the shadow directory.
123
124 This function must be run with root privileges.
125 """
barfab@chromium.org5c374632012-04-05 16:50:56 +0200126 for item in os.listdir(constants.SHADOW_ROOT):
127 abs_item = os.path.join(constants.SHADOW_ROOT, item)
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200128 if os.path.isdir(os.path.join(abs_item, 'vault')):
129 logging.debug('Removing vault for user with hash %s' % item)
130 shutil.rmtree(abs_item)
131
132
Sean Oe5d8fd02010-09-30 10:44:44 +0200133def mount_vault(user, password, create=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200134 """Mount the given user's vault."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400135 args = [CRYPTOHOME_CMD, '--action=mount', '--user=%s' % user,
136 '--password=%s' % password]
Sean Oe5d8fd02010-09-30 10:44:44 +0200137 if create:
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400138 args.append('--create')
139 print utils.system_output(args)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200140 # Ensure that the vault exists in the shadow directory.
Sean Oe5d8fd02010-09-30 10:44:44 +0200141 user_hash = get_user_hash(user)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200142 if not os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
Sean Oe5d8fd02010-09-30 10:44:44 +0200143 raise ChromiumOSError('Cryptohome vault not found after mount.')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200144 # Ensure that the vault is mounted.
145 if not is_vault_mounted(
146 user=user,
147 device_regex=constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER,
148 allow_fail=True):
149 raise ChromiumOSError('Cryptohome created a vault but did not mount.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200150
151
Chris Masone5d010aa2013-05-06 14:38:42 -0700152def mount_guest():
153 """Mount the given user's vault."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400154 print utils.system_output([CRYPTOHOME_CMD, '--action=mount_guest'])
Chris Masone5d010aa2013-05-06 14:38:42 -0700155 # Ensure that the guest tmpfs is mounted.
156 if not is_guest_vault_mounted(allow_fail=True):
157 raise ChromiumOSError('Cryptohome did not mount tmpfs.')
158
159
Sean Oe5d8fd02010-09-30 10:44:44 +0200160def test_auth(user, password):
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400161 cmd = [CRYPTOHOME_CMD, '--action=test_auth', '--user=%s' % user,
162 '--password=%s' % password, '--async']
163 return 'Authentication succeeded' in utils.system_output(cmd)
Sean Oe5d8fd02010-09-30 10:44:44 +0200164
165
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400166def unmount_vault(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200167 """Unmount the given user's vault.
168
169 Once unmounting for a specific user is supported, the user parameter will
170 name the target user. See crosbug.com/20778.
Elly Jones686c2f42011-10-24 16:45:07 -0400171 """
Sean Oe5d8fd02010-09-30 10:44:44 +0200172 cmd = (CRYPTOHOME_CMD + ' --action=unmount')
173 __run_cmd(cmd)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200174 # Ensure that the vault is not mounted.
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400175 if is_vault_mounted(user, allow_fail=True):
Sean Oe5d8fd02010-09-30 10:44:44 +0200176 raise ChromiumOSError('Cryptohome did not unmount the user.')
177
178
barfab@chromium.org5c374632012-04-05 16:50:56 +0200179def __get_mount_info(mount_point, allow_fail=False):
180 """Get information about the active mount at a given mount point."""
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400181 print utils.system_output('cat /proc/$(pgrep cryptohomed)/mounts')
Will Drewry81ad6162010-04-01 10:26:07 -0500182 mount_line = utils.system_output(
barfab@chromium.org5c374632012-04-05 16:50:56 +0200183 'grep %s /proc/$(pgrep cryptohomed)/mounts' % mount_point,
184 ignore_status=allow_fail)
Sourav Poddar574bd622010-05-26 14:22:26 +0530185 return mount_line.split()
186
187
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400188def __get_user_mount_info(user, allow_fail=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200189 """Get information about the active mounts for a given user.
190
191 Returns the active mounts at the user's user and system mount points. If no
192 user is given, the active mount at the shared mount point is returned
193 (regular users have a bind-mount at this mount point for backwards
194 compatibility; the guest user has a mount at this mount point only).
195 """
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400196 return [__get_mount_info(mount_point=user_path(user),
197 allow_fail=allow_fail),
198 __get_mount_info(mount_point=system_path(user),
199 allow_fail=allow_fail)]
Jim Hebertf08f88d2011-04-22 10:33:49 -0700200
barfab@chromium.org5c374632012-04-05 16:50:56 +0200201def is_vault_mounted(
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400202 user,
barfab@chromium.org5c374632012-04-05 16:50:56 +0200203 device_regex=constants.CRYPTOHOME_DEV_REGEX_ANY,
204 fs_regex=constants.CRYPTOHOME_FS_REGEX_ANY,
205 allow_fail=False):
206 """Check whether a vault is mounted for the given user.
207
208 If no user is given, the shared mount point is checked, determining whether
209 a vault is mounted for any user.
210 """
211 user_mount_info = __get_user_mount_info(user=user, allow_fail=allow_fail)
212 for mount_info in user_mount_info:
213 if (len(mount_info) < 3 or
214 not re.match(device_regex, mount_info[0]) or
215 not re.match(fs_regex, mount_info[2])):
216 return False
217 return True
Sourav Poddar574bd622010-05-26 14:22:26 +0530218
219
barfab@chromium.org5c374632012-04-05 16:50:56 +0200220def is_guest_vault_mounted(allow_fail=False):
221 """Check whether a vault backed by tmpfs is mounted for the guest user."""
222 return is_vault_mounted(
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400223 user=GUEST_USER_NAME,
barfab@chromium.org5c374632012-04-05 16:50:56 +0200224 device_regex=constants.CRYPTOHOME_DEV_REGEX_GUEST,
225 fs_regex=constants.CRYPTOHOME_FS_REGEX_TMPFS,
226 allow_fail=allow_fail)
227
228
Elly Fong-Jones6cb26ad2013-05-21 12:09:23 -0400229def get_mounted_vault_devices(user, allow_fail=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200230 """Get the device(s) backing the vault mounted for the given user.
231
232 Returns the devices mounted at the user's user and system mount points. If
233 no user is given, the device mounted at the shared mount point is returned.
234 """
235 return [mount_info[0]
236 for mount_info
237 in __get_user_mount_info(user=user, allow_fail=allow_fail)
238 if len(mount_info)]
Nirnimesh66814492011-06-27 18:00:33 -0700239
240
241def canonicalize(credential):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200242 """Perform basic canonicalization of |email_address|.
Nirnimesh66814492011-06-27 18:00:33 -0700243
barfab@chromium.org5c374632012-04-05 16:50:56 +0200244 Perform basic canonicalization of |email_address|, taking into account that
245 gmail does not consider '.' or caps inside a username to matter. It also
246 ignores everything after a '+'. For example,
247 c.masone+abc@gmail.com == cMaSone@gmail.com, per
Nirnimesh66814492011-06-27 18:00:33 -0700248 http://mail.google.com/support/bin/answer.py?hl=en&ctx=mail&answer=10313
249 """
250 if not credential:
251 return None
252
253 parts = credential.split('@')
254 if len(parts) != 2:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200255 raise error.TestError('Malformed email: ' + credential)
Nirnimesh66814492011-06-27 18:00:33 -0700256
257 (name, domain) = parts
258 name = name.partition('+')[0]
barfab@chromium.org5c374632012-04-05 16:50:56 +0200259 if (domain == constants.SPECIAL_CASE_DOMAIN):
Nirnimesh66814492011-06-27 18:00:33 -0700260 name = name.replace('.', '')
261 return '@'.join([name, domain]).lower()
Elly Jones686c2f42011-10-24 16:45:07 -0400262
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200263
Elly Jones2f0ebba2011-10-27 13:43:20 -0400264class CryptohomeProxy:
265 def __init__(self):
266 BUSNAME = 'org.chromium.Cryptohome'
267 PATH = '/org/chromium/Cryptohome'
268 INTERFACE = 'org.chromium.CryptohomeInterface'
269 bus = dbus.SystemBus()
270 obj = bus.get_object(BUSNAME, PATH)
271 self.iface = dbus.Interface(obj, INTERFACE)
272
273 def mount(self, user, password, create=False):
274 """Mounts a cryptohome.
275
276 Returns True if the mount succeeds or False otherwise.
277 TODO(ellyjones): Migrate mount_vault() to use a multi-user-safe
278 heuristic, then remove this method. See <crosbug.com/20778>.
279 """
280 return self.iface.Mount(user, password, create, False, [])[1]
281
282 def unmount(self, user):
283 """Unmounts a cryptohome.
284
285 Returns True if the unmount suceeds or false otherwise.
286 TODO(ellyjones): Once there's a per-user unmount method, use it. See
287 <crosbug.com/20778>.
288 """
Elly Jones5c3c2b02011-12-21 14:26:28 -0500289 return self.iface.Unmount()
Elly Jones2f0ebba2011-10-27 13:43:20 -0400290
291 def is_mounted(self, user):
292 """Tests whether a user's cryptohome is mounted."""
293 return (utils.is_mountpoint(user_path(user))
294 and utils.is_mountpoint(system_path(user)))
295
296 def require_mounted(self, user):
297 """Raises a test failure if a user's cryptohome is not mounted."""
298 utils.require_mountpoint(user_path(user))
299 utils.require_mountpoint(system_path(user))
Elly Jones4458f442012-04-16 15:42:56 -0400300
301 def migrate(self, user, oldkey, newkey):
302 """Migrates the specified user's cryptohome from one key to another."""
303 return self.iface.MigrateKey(user, oldkey, newkey)
304
305 def remove(self, user):
306 return self.iface.Remove(user)