blob: dce175bc2abd69bd74990223f100700a2510eb9a [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
Sean Oe5d8fd02010-09-30 10:44:44 +020074def remove_vault(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +020075 """Remove the given user's vault from the shadow directory."""
Sean Oe5d8fd02010-09-30 10:44:44 +020076 logging.debug('user is %s', user)
77 user_hash = get_user_hash(user)
barfab@chromium.org5c374632012-04-05 16:50:56 +020078 logging.debug('Removing vault for user %s with hash %s' % (user, user_hash))
Sean Oe5d8fd02010-09-30 10:44:44 +020079 cmd = CRYPTOHOME_CMD + ' --action=remove --force --user=%s' % user
80 __run_cmd(cmd)
barfab@chromium.org5c374632012-04-05 16:50:56 +020081 # Ensure that the vault does not exist.
82 if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
83 raise ChromiumOSError('Cryptohome could not remove the user''s vault.')
Sean Oe5d8fd02010-09-30 10:44:44 +020084
85
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +020086def remove_all_vaults():
87 """Remove any existing vaults from the shadow directory.
88
89 This function must be run with root privileges.
90 """
barfab@chromium.org5c374632012-04-05 16:50:56 +020091 for item in os.listdir(constants.SHADOW_ROOT):
92 abs_item = os.path.join(constants.SHADOW_ROOT, item)
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +020093 if os.path.isdir(os.path.join(abs_item, 'vault')):
94 logging.debug('Removing vault for user with hash %s' % item)
95 shutil.rmtree(abs_item)
96
97
Sean Oe5d8fd02010-09-30 10:44:44 +020098def mount_vault(user, password, create=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +020099 """Mount the given user's vault."""
Sean Oe5d8fd02010-09-30 10:44:44 +0200100 cmd = (CRYPTOHOME_CMD + ' --action=mount --user=%s --password=%s' %
101 (user, password))
102 if create:
103 cmd += ' --create'
104 __run_cmd(cmd)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200105 # Ensure that the vault exists in the shadow directory.
Sean Oe5d8fd02010-09-30 10:44:44 +0200106 user_hash = get_user_hash(user)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200107 if not os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
Sean Oe5d8fd02010-09-30 10:44:44 +0200108 raise ChromiumOSError('Cryptohome vault not found after mount.')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200109 # Ensure that the vault is mounted.
110 if not is_vault_mounted(
111 user=user,
112 device_regex=constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER,
113 allow_fail=True):
114 raise ChromiumOSError('Cryptohome created a vault but did not mount.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200115
116
117def test_auth(user, password):
118 cmd = (CRYPTOHOME_CMD + ' --action=test_auth --user=%s --password=%s' %
119 (user, password))
Elly Jones630f3a92012-04-17 16:40:25 -0400120 cmd += ' --async'
Sean Oe5d8fd02010-09-30 10:44:44 +0200121 return 'Authentication succeeded' in __run_cmd(cmd)
122
123
Elly Jones686c2f42011-10-24 16:45:07 -0400124def unmount_vault(user=None):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200125 """Unmount the given user's vault.
126
127 Once unmounting for a specific user is supported, the user parameter will
128 name the target user. See crosbug.com/20778.
Elly Jones686c2f42011-10-24 16:45:07 -0400129 """
Sean Oe5d8fd02010-09-30 10:44:44 +0200130 cmd = (CRYPTOHOME_CMD + ' --action=unmount')
131 __run_cmd(cmd)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200132 # Ensure that the vault is not mounted.
133 if is_vault_mounted(allow_fail=True):
Sean Oe5d8fd02010-09-30 10:44:44 +0200134 raise ChromiumOSError('Cryptohome did not unmount the user.')
135
136
barfab@chromium.org5c374632012-04-05 16:50:56 +0200137def __get_mount_info(mount_point, allow_fail=False):
138 """Get information about the active mount at a given mount point."""
Will Drewry81ad6162010-04-01 10:26:07 -0500139 mount_line = utils.system_output(
barfab@chromium.org5c374632012-04-05 16:50:56 +0200140 'grep %s /proc/$(pgrep cryptohomed)/mounts' % mount_point,
141 ignore_status=allow_fail)
Sourav Poddar574bd622010-05-26 14:22:26 +0530142 return mount_line.split()
143
144
barfab@chromium.org5c374632012-04-05 16:50:56 +0200145def __get_user_mount_info(user=None, allow_fail=False):
146 """Get information about the active mounts for a given user.
147
148 Returns the active mounts at the user's user and system mount points. If no
149 user is given, the active mount at the shared mount point is returned
150 (regular users have a bind-mount at this mount point for backwards
151 compatibility; the guest user has a mount at this mount point only).
152 """
153 if user:
154 return [__get_mount_info(mount_point=user_path(user),
155 allow_fail=allow_fail),
156 __get_mount_info(mount_point=system_path(user),
157 allow_fail=allow_fail)]
Jim Hebertf08f88d2011-04-22 10:33:49 -0700158 else:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200159 return [__get_mount_info(mount_point=constants.CRYPTOHOME_MOUNT_PT,
160 allow_fail=allow_fail)]
Jim Hebertf08f88d2011-04-22 10:33:49 -0700161
162
barfab@chromium.org5c374632012-04-05 16:50:56 +0200163def is_vault_mounted(
164 user=None,
165 device_regex=constants.CRYPTOHOME_DEV_REGEX_ANY,
166 fs_regex=constants.CRYPTOHOME_FS_REGEX_ANY,
167 allow_fail=False):
168 """Check whether a vault is mounted for the given user.
169
170 If no user is given, the shared mount point is checked, determining whether
171 a vault is mounted for any user.
172 """
173 user_mount_info = __get_user_mount_info(user=user, allow_fail=allow_fail)
174 for mount_info in user_mount_info:
175 if (len(mount_info) < 3 or
176 not re.match(device_regex, mount_info[0]) or
177 not re.match(fs_regex, mount_info[2])):
178 return False
179 return True
Sourav Poddar574bd622010-05-26 14:22:26 +0530180
181
barfab@chromium.org5c374632012-04-05 16:50:56 +0200182def is_guest_vault_mounted(allow_fail=False):
183 """Check whether a vault backed by tmpfs is mounted for the guest user."""
184 return is_vault_mounted(
185 user=None,
186 device_regex=constants.CRYPTOHOME_DEV_REGEX_GUEST,
187 fs_regex=constants.CRYPTOHOME_FS_REGEX_TMPFS,
188 allow_fail=allow_fail)
189
190
191def get_mounted_vault_devices(user=None, allow_fail=False):
192 """Get the device(s) backing the vault mounted for the given user.
193
194 Returns the devices mounted at the user's user and system mount points. If
195 no user is given, the device mounted at the shared mount point is returned.
196 """
197 return [mount_info[0]
198 for mount_info
199 in __get_user_mount_info(user=user, allow_fail=allow_fail)
200 if len(mount_info)]
Nirnimesh66814492011-06-27 18:00:33 -0700201
202
203def canonicalize(credential):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200204 """Perform basic canonicalization of |email_address|.
Nirnimesh66814492011-06-27 18:00:33 -0700205
barfab@chromium.org5c374632012-04-05 16:50:56 +0200206 Perform basic canonicalization of |email_address|, taking into account that
207 gmail does not consider '.' or caps inside a username to matter. It also
208 ignores everything after a '+'. For example,
209 c.masone+abc@gmail.com == cMaSone@gmail.com, per
Nirnimesh66814492011-06-27 18:00:33 -0700210 http://mail.google.com/support/bin/answer.py?hl=en&ctx=mail&answer=10313
211 """
212 if not credential:
213 return None
214
215 parts = credential.split('@')
216 if len(parts) != 2:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200217 raise error.TestError('Malformed email: ' + credential)
Nirnimesh66814492011-06-27 18:00:33 -0700218
219 (name, domain) = parts
220 name = name.partition('+')[0]
barfab@chromium.org5c374632012-04-05 16:50:56 +0200221 if (domain == constants.SPECIAL_CASE_DOMAIN):
Nirnimesh66814492011-06-27 18:00:33 -0700222 name = name.replace('.', '')
223 return '@'.join([name, domain]).lower()
Elly Jones686c2f42011-10-24 16:45:07 -0400224
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200225
Elly Jones2f0ebba2011-10-27 13:43:20 -0400226class CryptohomeProxy:
227 def __init__(self):
228 BUSNAME = 'org.chromium.Cryptohome'
229 PATH = '/org/chromium/Cryptohome'
230 INTERFACE = 'org.chromium.CryptohomeInterface'
231 bus = dbus.SystemBus()
232 obj = bus.get_object(BUSNAME, PATH)
233 self.iface = dbus.Interface(obj, INTERFACE)
234
235 def mount(self, user, password, create=False):
236 """Mounts a cryptohome.
237
238 Returns True if the mount succeeds or False otherwise.
239 TODO(ellyjones): Migrate mount_vault() to use a multi-user-safe
240 heuristic, then remove this method. See <crosbug.com/20778>.
241 """
242 return self.iface.Mount(user, password, create, False, [])[1]
243
244 def unmount(self, user):
245 """Unmounts a cryptohome.
246
247 Returns True if the unmount suceeds or false otherwise.
248 TODO(ellyjones): Once there's a per-user unmount method, use it. See
249 <crosbug.com/20778>.
250 """
Elly Jones5c3c2b02011-12-21 14:26:28 -0500251 return self.iface.Unmount()
Elly Jones2f0ebba2011-10-27 13:43:20 -0400252
253 def is_mounted(self, user):
254 """Tests whether a user's cryptohome is mounted."""
255 return (utils.is_mountpoint(user_path(user))
256 and utils.is_mountpoint(system_path(user)))
257
258 def require_mounted(self, user):
259 """Raises a test failure if a user's cryptohome is not mounted."""
260 utils.require_mountpoint(user_path(user))
261 utils.require_mountpoint(system_path(user))
Elly Jones4458f442012-04-16 15:42:56 -0400262
263 def migrate(self, user, oldkey, newkey):
264 """Migrates the specified user's cryptohome from one key to another."""
265 return self.iface.MigrateKey(user, oldkey, newkey)
266
267 def remove(self, user):
268 return self.iface.Remove(user)