blob: da5c0105de50e87adafc27466f854be3be63e162 [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'
12
Chris Masone5d010aa2013-05-06 14:38:42 -070013class ChromiumOSError(error.TestError):
Sean Oe5d8fd02010-09-30 10:44:44 +020014 """Generic error for ChromiumOS-specific exceptions."""
15 pass
16
17
18def __run_cmd(cmd):
19 return utils.system_output(cmd + ' 2>&1', retain_output=True,
20 ignore_status=True).strip()
21
22
23def get_user_hash(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +020024 """Get the user hash for the given user."""
Sean Oe5d8fd02010-09-30 10:44:44 +020025 hash_cmd = CRYPTOHOME_CMD + ' --action=obfuscate_user --user=%s' % user
26 return __run_cmd(hash_cmd)
27
28
barfab@chromium.org5c374632012-04-05 16:50:56 +020029def user_path(user):
30 """Get the user mount point for the given user."""
31 return utils.system_output('cryptohome-path user %s' % user)
32
33
34def system_path(user):
35 """Get the system mount point for the given user."""
36 return utils.system_output('cryptohome-path system %s' % user)
37
38
Chris Masone5d010aa2013-05-06 14:38:42 -070039def ensure_clean_cryptohome_for(user, password=None):
40 """Ensure a fresh cryptohome exists for user.
41
42 @param user: user who needs a shiny new cryptohome.
43 @param password: if unset, a random password will be used.
44 """
45 if not password:
46 password = ''.join(random.sample(string.ascii_lowercase, 6))
47 remove_vault(user)
48 mount_vault(user, password, create=True)
49
50
Frank Farzand5e36312012-01-13 14:34:03 -080051def get_tpm_status():
52 """Get the TPM status.
53
54 Returns:
55 A TPM status dictionary, for example:
56 { 'Enabled': True,
57 'Owned': True,
58 'Being Owned': False,
59 'Ready': True,
60 'Password': ''
61 }
62 """
63 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_status')
64 status = {}
65 for field in ['Enabled', 'Owned', 'Being Owned', 'Ready']:
66 match = re.search('TPM %s: (true|false)' % field, out)
67 if not match:
68 raise ChromiumOSError('Invalid TPM status: "%s".' % out)
69 status[field] = match.group(1) == 'true'
70 match = re.search('TPM Password: (\w*)', out)
71 status['Password'] = ''
72 if match:
73 status['Password'] = match.group(1)
74 return status
75
76
Darren Krahn5f880f62012-10-02 15:17:59 -070077def get_tpm_attestation_status():
78 """Get the TPM attestation status. Works similar to get_tpm_status().
79 """
80 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_attestation_status')
81 status = {}
82 for field in ['Prepared', 'Enrolled']:
83 match = re.search('Attestation %s: (true|false)' % field, out)
84 if not match:
85 raise ChromiumOSError('Invalid attestation status: "%s".' % out)
86 status[field] = match.group(1) == 'true'
87 return status
88
89
Frank Farzand5e36312012-01-13 14:34:03 -080090def take_tpm_ownership():
91 """Take TPM owernship.
92
93 Blocks until TPM is owned.
94 """
95 __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_take_ownership')
96 __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_wait_ownership')
97
98
Darren Krahn0e73e7f2012-09-05 15:35:15 -070099def verify_ek():
100 """Verify the TPM endorsement key.
101
102 Returns true if EK is valid.
103 """
104 cmd = CRYPTOHOME_CMD + ' --action=tpm_verify_ek'
105 return (utils.system(cmd, ignore_status=True) == 0)
106
107
Sean Oe5d8fd02010-09-30 10:44:44 +0200108def remove_vault(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200109 """Remove the given user's vault from the shadow directory."""
Sean Oe5d8fd02010-09-30 10:44:44 +0200110 logging.debug('user is %s', user)
111 user_hash = get_user_hash(user)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200112 logging.debug('Removing vault for user %s with hash %s' % (user, user_hash))
Sean Oe5d8fd02010-09-30 10:44:44 +0200113 cmd = CRYPTOHOME_CMD + ' --action=remove --force --user=%s' % user
114 __run_cmd(cmd)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200115 # Ensure that the vault does not exist.
116 if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
117 raise ChromiumOSError('Cryptohome could not remove the user''s vault.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200118
119
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200120def remove_all_vaults():
121 """Remove any existing vaults from the shadow directory.
122
123 This function must be run with root privileges.
124 """
barfab@chromium.org5c374632012-04-05 16:50:56 +0200125 for item in os.listdir(constants.SHADOW_ROOT):
126 abs_item = os.path.join(constants.SHADOW_ROOT, item)
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200127 if os.path.isdir(os.path.join(abs_item, 'vault')):
128 logging.debug('Removing vault for user with hash %s' % item)
129 shutil.rmtree(abs_item)
130
131
Sean Oe5d8fd02010-09-30 10:44:44 +0200132def mount_vault(user, password, create=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200133 """Mount the given user's vault."""
Sean Oe5d8fd02010-09-30 10:44:44 +0200134 cmd = (CRYPTOHOME_CMD + ' --action=mount --user=%s --password=%s' %
135 (user, password))
136 if create:
137 cmd += ' --create'
138 __run_cmd(cmd)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200139 # Ensure that the vault exists in the shadow directory.
Sean Oe5d8fd02010-09-30 10:44:44 +0200140 user_hash = get_user_hash(user)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200141 if not os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
Sean Oe5d8fd02010-09-30 10:44:44 +0200142 raise ChromiumOSError('Cryptohome vault not found after mount.')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200143 # Ensure that the vault is mounted.
144 if not is_vault_mounted(
145 user=user,
146 device_regex=constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER,
147 allow_fail=True):
148 raise ChromiumOSError('Cryptohome created a vault but did not mount.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200149
150
Chris Masone5d010aa2013-05-06 14:38:42 -0700151def mount_guest():
152 """Mount the given user's vault."""
153 cmd = CRYPTOHOME_CMD + ' --action=mount_guest'
154 __run_cmd(cmd)
155 # 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):
161 cmd = (CRYPTOHOME_CMD + ' --action=test_auth --user=%s --password=%s' %
162 (user, password))
Elly Jones630f3a92012-04-17 16:40:25 -0400163 cmd += ' --async'
Sean Oe5d8fd02010-09-30 10:44:44 +0200164 return 'Authentication succeeded' in __run_cmd(cmd)
165
166
Elly Jones686c2f42011-10-24 16:45:07 -0400167def unmount_vault(user=None):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200168 """Unmount the given user's vault.
169
170 Once unmounting for a specific user is supported, the user parameter will
171 name the target user. See crosbug.com/20778.
Elly Jones686c2f42011-10-24 16:45:07 -0400172 """
Sean Oe5d8fd02010-09-30 10:44:44 +0200173 cmd = (CRYPTOHOME_CMD + ' --action=unmount')
174 __run_cmd(cmd)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200175 # Ensure that the vault is not mounted.
176 if is_vault_mounted(allow_fail=True):
Sean Oe5d8fd02010-09-30 10:44:44 +0200177 raise ChromiumOSError('Cryptohome did not unmount the user.')
178
179
barfab@chromium.org5c374632012-04-05 16:50:56 +0200180def __get_mount_info(mount_point, allow_fail=False):
181 """Get information about the active mount at a given mount point."""
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
barfab@chromium.org5c374632012-04-05 16:50:56 +0200188def __get_user_mount_info(user=None, allow_fail=False):
189 """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 """
196 if user:
197 return [__get_mount_info(mount_point=user_path(user),
198 allow_fail=allow_fail),
199 __get_mount_info(mount_point=system_path(user),
200 allow_fail=allow_fail)]
Jim Hebertf08f88d2011-04-22 10:33:49 -0700201 else:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200202 return [__get_mount_info(mount_point=constants.CRYPTOHOME_MOUNT_PT,
203 allow_fail=allow_fail)]
Jim Hebertf08f88d2011-04-22 10:33:49 -0700204
205
barfab@chromium.org5c374632012-04-05 16:50:56 +0200206def is_vault_mounted(
207 user=None,
208 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(
228 user=None,
229 device_regex=constants.CRYPTOHOME_DEV_REGEX_GUEST,
230 fs_regex=constants.CRYPTOHOME_FS_REGEX_TMPFS,
231 allow_fail=allow_fail)
232
233
234def get_mounted_vault_devices(user=None, allow_fail=False):
235 """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
Elly Jones2f0ebba2011-10-27 13:43:20 -0400269class CryptohomeProxy:
270 def __init__(self):
271 BUSNAME = 'org.chromium.Cryptohome'
272 PATH = '/org/chromium/Cryptohome'
273 INTERFACE = 'org.chromium.CryptohomeInterface'
274 bus = dbus.SystemBus()
275 obj = bus.get_object(BUSNAME, PATH)
276 self.iface = dbus.Interface(obj, INTERFACE)
277
278 def mount(self, user, password, create=False):
279 """Mounts a cryptohome.
280
281 Returns True if the mount succeeds or False otherwise.
282 TODO(ellyjones): Migrate mount_vault() to use a multi-user-safe
283 heuristic, then remove this method. See <crosbug.com/20778>.
284 """
285 return self.iface.Mount(user, password, create, False, [])[1]
286
287 def unmount(self, user):
288 """Unmounts a cryptohome.
289
290 Returns True if the unmount suceeds or false otherwise.
291 TODO(ellyjones): Once there's a per-user unmount method, use it. See
292 <crosbug.com/20778>.
293 """
Elly Jones5c3c2b02011-12-21 14:26:28 -0500294 return self.iface.Unmount()
Elly Jones2f0ebba2011-10-27 13:43:20 -0400295
296 def is_mounted(self, user):
297 """Tests whether a user's cryptohome is mounted."""
298 return (utils.is_mountpoint(user_path(user))
299 and utils.is_mountpoint(system_path(user)))
300
301 def require_mounted(self, user):
302 """Raises a test failure if a user's cryptohome is not mounted."""
303 utils.require_mountpoint(user_path(user))
304 utils.require_mountpoint(system_path(user))
Elly Jones4458f442012-04-16 15:42:56 -0400305
306 def migrate(self, user, oldkey, newkey):
307 """Migrates the specified user's cryptohome from one key to another."""
308 return self.iface.MigrateKey(user, oldkey, newkey)
309
310 def remove(self, user):
311 return self.iface.Remove(user)