blob: c765d9f816a4b4564f8235a0d4b9261f3c861400 [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
barfab@chromium.orgb6d29932012-04-11 09:46:43 +02005import dbus, logging, os, re, shutil
6
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
13class ChromiumOSError(error.InstallError):
14 """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
Frank Farzand5e36312012-01-13 14:34:03 -080039def get_tpm_status():
40 """Get the TPM status.
41
42 Returns:
43 A TPM status dictionary, for example:
44 { 'Enabled': True,
45 'Owned': True,
46 'Being Owned': False,
47 'Ready': True,
48 'Password': ''
49 }
50 """
51 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_status')
52 status = {}
53 for field in ['Enabled', 'Owned', 'Being Owned', 'Ready']:
54 match = re.search('TPM %s: (true|false)' % field, out)
55 if not match:
56 raise ChromiumOSError('Invalid TPM status: "%s".' % out)
57 status[field] = match.group(1) == 'true'
58 match = re.search('TPM Password: (\w*)', out)
59 status['Password'] = ''
60 if match:
61 status['Password'] = match.group(1)
62 return status
63
64
65def take_tpm_ownership():
66 """Take TPM owernship.
67
68 Blocks until TPM is owned.
69 """
70 __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_take_ownership')
71 __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_wait_ownership')
72
73
Darren Krahn0e73e7f2012-09-05 15:35:15 -070074def verify_ek():
75 """Verify the TPM endorsement key.
76
77 Returns true if EK is valid.
78 """
79 cmd = CRYPTOHOME_CMD + ' --action=tpm_verify_ek'
80 return (utils.system(cmd, ignore_status=True) == 0)
81
82
Sean Oe5d8fd02010-09-30 10:44:44 +020083def remove_vault(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +020084 """Remove the given user's vault from the shadow directory."""
Sean Oe5d8fd02010-09-30 10:44:44 +020085 logging.debug('user is %s', user)
86 user_hash = get_user_hash(user)
barfab@chromium.org5c374632012-04-05 16:50:56 +020087 logging.debug('Removing vault for user %s with hash %s' % (user, user_hash))
Sean Oe5d8fd02010-09-30 10:44:44 +020088 cmd = CRYPTOHOME_CMD + ' --action=remove --force --user=%s' % user
89 __run_cmd(cmd)
barfab@chromium.org5c374632012-04-05 16:50:56 +020090 # Ensure that the vault does not exist.
91 if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
92 raise ChromiumOSError('Cryptohome could not remove the user''s vault.')
Sean Oe5d8fd02010-09-30 10:44:44 +020093
94
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +020095def remove_all_vaults():
96 """Remove any existing vaults from the shadow directory.
97
98 This function must be run with root privileges.
99 """
barfab@chromium.org5c374632012-04-05 16:50:56 +0200100 for item in os.listdir(constants.SHADOW_ROOT):
101 abs_item = os.path.join(constants.SHADOW_ROOT, item)
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200102 if os.path.isdir(os.path.join(abs_item, 'vault')):
103 logging.debug('Removing vault for user with hash %s' % item)
104 shutil.rmtree(abs_item)
105
106
Sean Oe5d8fd02010-09-30 10:44:44 +0200107def mount_vault(user, password, create=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200108 """Mount the given user's vault."""
Sean Oe5d8fd02010-09-30 10:44:44 +0200109 cmd = (CRYPTOHOME_CMD + ' --action=mount --user=%s --password=%s' %
110 (user, password))
111 if create:
112 cmd += ' --create'
113 __run_cmd(cmd)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200114 # Ensure that the vault exists in the shadow directory.
Sean Oe5d8fd02010-09-30 10:44:44 +0200115 user_hash = get_user_hash(user)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200116 if not os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
Sean Oe5d8fd02010-09-30 10:44:44 +0200117 raise ChromiumOSError('Cryptohome vault not found after mount.')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200118 # Ensure that the vault is mounted.
119 if not is_vault_mounted(
120 user=user,
121 device_regex=constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER,
122 allow_fail=True):
123 raise ChromiumOSError('Cryptohome created a vault but did not mount.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200124
125
126def test_auth(user, password):
127 cmd = (CRYPTOHOME_CMD + ' --action=test_auth --user=%s --password=%s' %
128 (user, password))
Elly Jones630f3a92012-04-17 16:40:25 -0400129 cmd += ' --async'
Sean Oe5d8fd02010-09-30 10:44:44 +0200130 return 'Authentication succeeded' in __run_cmd(cmd)
131
132
Elly Jones686c2f42011-10-24 16:45:07 -0400133def unmount_vault(user=None):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200134 """Unmount the given user's vault.
135
136 Once unmounting for a specific user is supported, the user parameter will
137 name the target user. See crosbug.com/20778.
Elly Jones686c2f42011-10-24 16:45:07 -0400138 """
Sean Oe5d8fd02010-09-30 10:44:44 +0200139 cmd = (CRYPTOHOME_CMD + ' --action=unmount')
140 __run_cmd(cmd)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200141 # Ensure that the vault is not mounted.
142 if is_vault_mounted(allow_fail=True):
Sean Oe5d8fd02010-09-30 10:44:44 +0200143 raise ChromiumOSError('Cryptohome did not unmount the user.')
144
145
barfab@chromium.org5c374632012-04-05 16:50:56 +0200146def __get_mount_info(mount_point, allow_fail=False):
147 """Get information about the active mount at a given mount point."""
Will Drewry81ad6162010-04-01 10:26:07 -0500148 mount_line = utils.system_output(
barfab@chromium.org5c374632012-04-05 16:50:56 +0200149 'grep %s /proc/$(pgrep cryptohomed)/mounts' % mount_point,
150 ignore_status=allow_fail)
Sourav Poddar574bd622010-05-26 14:22:26 +0530151 return mount_line.split()
152
153
barfab@chromium.org5c374632012-04-05 16:50:56 +0200154def __get_user_mount_info(user=None, allow_fail=False):
155 """Get information about the active mounts for a given user.
156
157 Returns the active mounts at the user's user and system mount points. If no
158 user is given, the active mount at the shared mount point is returned
159 (regular users have a bind-mount at this mount point for backwards
160 compatibility; the guest user has a mount at this mount point only).
161 """
162 if user:
163 return [__get_mount_info(mount_point=user_path(user),
164 allow_fail=allow_fail),
165 __get_mount_info(mount_point=system_path(user),
166 allow_fail=allow_fail)]
Jim Hebertf08f88d2011-04-22 10:33:49 -0700167 else:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200168 return [__get_mount_info(mount_point=constants.CRYPTOHOME_MOUNT_PT,
169 allow_fail=allow_fail)]
Jim Hebertf08f88d2011-04-22 10:33:49 -0700170
171
barfab@chromium.org5c374632012-04-05 16:50:56 +0200172def is_vault_mounted(
173 user=None,
174 device_regex=constants.CRYPTOHOME_DEV_REGEX_ANY,
175 fs_regex=constants.CRYPTOHOME_FS_REGEX_ANY,
176 allow_fail=False):
177 """Check whether a vault is mounted for the given user.
178
179 If no user is given, the shared mount point is checked, determining whether
180 a vault is mounted for any user.
181 """
182 user_mount_info = __get_user_mount_info(user=user, allow_fail=allow_fail)
183 for mount_info in user_mount_info:
184 if (len(mount_info) < 3 or
185 not re.match(device_regex, mount_info[0]) or
186 not re.match(fs_regex, mount_info[2])):
187 return False
188 return True
Sourav Poddar574bd622010-05-26 14:22:26 +0530189
190
barfab@chromium.org5c374632012-04-05 16:50:56 +0200191def is_guest_vault_mounted(allow_fail=False):
192 """Check whether a vault backed by tmpfs is mounted for the guest user."""
193 return is_vault_mounted(
194 user=None,
195 device_regex=constants.CRYPTOHOME_DEV_REGEX_GUEST,
196 fs_regex=constants.CRYPTOHOME_FS_REGEX_TMPFS,
197 allow_fail=allow_fail)
198
199
200def get_mounted_vault_devices(user=None, allow_fail=False):
201 """Get the device(s) backing the vault mounted for the given user.
202
203 Returns the devices mounted at the user's user and system mount points. If
204 no user is given, the device mounted at the shared mount point is returned.
205 """
206 return [mount_info[0]
207 for mount_info
208 in __get_user_mount_info(user=user, allow_fail=allow_fail)
209 if len(mount_info)]
Nirnimesh66814492011-06-27 18:00:33 -0700210
211
212def canonicalize(credential):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200213 """Perform basic canonicalization of |email_address|.
Nirnimesh66814492011-06-27 18:00:33 -0700214
barfab@chromium.org5c374632012-04-05 16:50:56 +0200215 Perform basic canonicalization of |email_address|, taking into account that
216 gmail does not consider '.' or caps inside a username to matter. It also
217 ignores everything after a '+'. For example,
218 c.masone+abc@gmail.com == cMaSone@gmail.com, per
Nirnimesh66814492011-06-27 18:00:33 -0700219 http://mail.google.com/support/bin/answer.py?hl=en&ctx=mail&answer=10313
220 """
221 if not credential:
222 return None
223
224 parts = credential.split('@')
225 if len(parts) != 2:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200226 raise error.TestError('Malformed email: ' + credential)
Nirnimesh66814492011-06-27 18:00:33 -0700227
228 (name, domain) = parts
229 name = name.partition('+')[0]
barfab@chromium.org5c374632012-04-05 16:50:56 +0200230 if (domain == constants.SPECIAL_CASE_DOMAIN):
Nirnimesh66814492011-06-27 18:00:33 -0700231 name = name.replace('.', '')
232 return '@'.join([name, domain]).lower()
Elly Jones686c2f42011-10-24 16:45:07 -0400233
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200234
Elly Jones2f0ebba2011-10-27 13:43:20 -0400235class CryptohomeProxy:
236 def __init__(self):
237 BUSNAME = 'org.chromium.Cryptohome'
238 PATH = '/org/chromium/Cryptohome'
239 INTERFACE = 'org.chromium.CryptohomeInterface'
240 bus = dbus.SystemBus()
241 obj = bus.get_object(BUSNAME, PATH)
242 self.iface = dbus.Interface(obj, INTERFACE)
243
244 def mount(self, user, password, create=False):
245 """Mounts a cryptohome.
246
247 Returns True if the mount succeeds or False otherwise.
248 TODO(ellyjones): Migrate mount_vault() to use a multi-user-safe
249 heuristic, then remove this method. See <crosbug.com/20778>.
250 """
251 return self.iface.Mount(user, password, create, False, [])[1]
252
253 def unmount(self, user):
254 """Unmounts a cryptohome.
255
256 Returns True if the unmount suceeds or false otherwise.
257 TODO(ellyjones): Once there's a per-user unmount method, use it. See
258 <crosbug.com/20778>.
259 """
Elly Jones5c3c2b02011-12-21 14:26:28 -0500260 return self.iface.Unmount()
Elly Jones2f0ebba2011-10-27 13:43:20 -0400261
262 def is_mounted(self, user):
263 """Tests whether a user's cryptohome is mounted."""
264 return (utils.is_mountpoint(user_path(user))
265 and utils.is_mountpoint(system_path(user)))
266
267 def require_mounted(self, user):
268 """Raises a test failure if a user's cryptohome is not mounted."""
269 utils.require_mountpoint(user_path(user))
270 utils.require_mountpoint(system_path(user))
Elly Jones4458f442012-04-16 15:42:56 -0400271
272 def migrate(self, user, oldkey, newkey):
273 """Migrates the specified user's cryptohome from one key to another."""
274 return self.iface.MigrateKey(user, oldkey, newkey)
275
276 def remove(self, user):
277 return self.iface.Remove(user)