blob: 4a67d95f2891611b12f46f2162359a48df71b4ac [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))
120 return 'Authentication succeeded' in __run_cmd(cmd)
121
122
Elly Jones686c2f42011-10-24 16:45:07 -0400123def unmount_vault(user=None):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200124 """Unmount the given user's vault.
125
126 Once unmounting for a specific user is supported, the user parameter will
127 name the target user. See crosbug.com/20778.
Elly Jones686c2f42011-10-24 16:45:07 -0400128 """
Sean Oe5d8fd02010-09-30 10:44:44 +0200129 cmd = (CRYPTOHOME_CMD + ' --action=unmount')
130 __run_cmd(cmd)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200131 # Ensure that the vault is not mounted.
132 if is_vault_mounted(allow_fail=True):
Sean Oe5d8fd02010-09-30 10:44:44 +0200133 raise ChromiumOSError('Cryptohome did not unmount the user.')
134
135
barfab@chromium.org5c374632012-04-05 16:50:56 +0200136def __get_mount_info(mount_point, allow_fail=False):
137 """Get information about the active mount at a given mount point."""
Will Drewry81ad6162010-04-01 10:26:07 -0500138 mount_line = utils.system_output(
barfab@chromium.org5c374632012-04-05 16:50:56 +0200139 'grep %s /proc/$(pgrep cryptohomed)/mounts' % mount_point,
140 ignore_status=allow_fail)
Sourav Poddar574bd622010-05-26 14:22:26 +0530141 return mount_line.split()
142
143
barfab@chromium.org5c374632012-04-05 16:50:56 +0200144def __get_user_mount_info(user=None, allow_fail=False):
145 """Get information about the active mounts for a given user.
146
147 Returns the active mounts at the user's user and system mount points. If no
148 user is given, the active mount at the shared mount point is returned
149 (regular users have a bind-mount at this mount point for backwards
150 compatibility; the guest user has a mount at this mount point only).
151 """
152 if user:
153 return [__get_mount_info(mount_point=user_path(user),
154 allow_fail=allow_fail),
155 __get_mount_info(mount_point=system_path(user),
156 allow_fail=allow_fail)]
Jim Hebertf08f88d2011-04-22 10:33:49 -0700157 else:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200158 return [__get_mount_info(mount_point=constants.CRYPTOHOME_MOUNT_PT,
159 allow_fail=allow_fail)]
Jim Hebertf08f88d2011-04-22 10:33:49 -0700160
161
barfab@chromium.org5c374632012-04-05 16:50:56 +0200162def is_vault_mounted(
163 user=None,
164 device_regex=constants.CRYPTOHOME_DEV_REGEX_ANY,
165 fs_regex=constants.CRYPTOHOME_FS_REGEX_ANY,
166 allow_fail=False):
167 """Check whether a vault is mounted for the given user.
168
169 If no user is given, the shared mount point is checked, determining whether
170 a vault is mounted for any user.
171 """
172 user_mount_info = __get_user_mount_info(user=user, allow_fail=allow_fail)
173 for mount_info in user_mount_info:
174 if (len(mount_info) < 3 or
175 not re.match(device_regex, mount_info[0]) or
176 not re.match(fs_regex, mount_info[2])):
177 return False
178 return True
Sourav Poddar574bd622010-05-26 14:22:26 +0530179
180
barfab@chromium.org5c374632012-04-05 16:50:56 +0200181def is_guest_vault_mounted(allow_fail=False):
182 """Check whether a vault backed by tmpfs is mounted for the guest user."""
183 return is_vault_mounted(
184 user=None,
185 device_regex=constants.CRYPTOHOME_DEV_REGEX_GUEST,
186 fs_regex=constants.CRYPTOHOME_FS_REGEX_TMPFS,
187 allow_fail=allow_fail)
188
189
190def get_mounted_vault_devices(user=None, allow_fail=False):
191 """Get the device(s) backing the vault mounted for the given user.
192
193 Returns the devices mounted at the user's user and system mount points. If
194 no user is given, the device mounted at the shared mount point is returned.
195 """
196 return [mount_info[0]
197 for mount_info
198 in __get_user_mount_info(user=user, allow_fail=allow_fail)
199 if len(mount_info)]
Nirnimesh66814492011-06-27 18:00:33 -0700200
201
202def canonicalize(credential):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200203 """Perform basic canonicalization of |email_address|.
Nirnimesh66814492011-06-27 18:00:33 -0700204
barfab@chromium.org5c374632012-04-05 16:50:56 +0200205 Perform basic canonicalization of |email_address|, taking into account that
206 gmail does not consider '.' or caps inside a username to matter. It also
207 ignores everything after a '+'. For example,
208 c.masone+abc@gmail.com == cMaSone@gmail.com, per
Nirnimesh66814492011-06-27 18:00:33 -0700209 http://mail.google.com/support/bin/answer.py?hl=en&ctx=mail&answer=10313
210 """
211 if not credential:
212 return None
213
214 parts = credential.split('@')
215 if len(parts) != 2:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200216 raise error.TestError('Malformed email: ' + credential)
Nirnimesh66814492011-06-27 18:00:33 -0700217
218 (name, domain) = parts
219 name = name.partition('+')[0]
barfab@chromium.org5c374632012-04-05 16:50:56 +0200220 if (domain == constants.SPECIAL_CASE_DOMAIN):
Nirnimesh66814492011-06-27 18:00:33 -0700221 name = name.replace('.', '')
222 return '@'.join([name, domain]).lower()
Elly Jones686c2f42011-10-24 16:45:07 -0400223
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200224
Elly Jones2f0ebba2011-10-27 13:43:20 -0400225class CryptohomeProxy:
226 def __init__(self):
227 BUSNAME = 'org.chromium.Cryptohome'
228 PATH = '/org/chromium/Cryptohome'
229 INTERFACE = 'org.chromium.CryptohomeInterface'
230 bus = dbus.SystemBus()
231 obj = bus.get_object(BUSNAME, PATH)
232 self.iface = dbus.Interface(obj, INTERFACE)
233
234 def mount(self, user, password, create=False):
235 """Mounts a cryptohome.
236
237 Returns True if the mount succeeds or False otherwise.
238 TODO(ellyjones): Migrate mount_vault() to use a multi-user-safe
239 heuristic, then remove this method. See <crosbug.com/20778>.
240 """
241 return self.iface.Mount(user, password, create, False, [])[1]
242
243 def unmount(self, user):
244 """Unmounts a cryptohome.
245
246 Returns True if the unmount suceeds or false otherwise.
247 TODO(ellyjones): Once there's a per-user unmount method, use it. See
248 <crosbug.com/20778>.
249 """
Elly Jones5c3c2b02011-12-21 14:26:28 -0500250 return self.iface.Unmount()
Elly Jones2f0ebba2011-10-27 13:43:20 -0400251
252 def is_mounted(self, user):
253 """Tests whether a user's cryptohome is mounted."""
254 return (utils.is_mountpoint(user_path(user))
255 and utils.is_mountpoint(system_path(user)))
256
257 def require_mounted(self, user):
258 """Raises a test failure if a user's cryptohome is not mounted."""
259 utils.require_mountpoint(user_path(user))
260 utils.require_mountpoint(system_path(user))
Elly Jones4458f442012-04-16 15:42:56 -0400261
262 def migrate(self, user, oldkey, newkey):
263 """Migrates the specified user's cryptohome from one key to another."""
264 return self.iface.MigrateKey(user, oldkey, newkey)
265
266 def remove(self, user):
267 return self.iface.Remove(user)