blob: 1d01924a044cddcb16bb6aa26a6b0746b6fcdb64 [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
Darren Krahn5f880f62012-10-02 15:17:59 -070065def get_tpm_attestation_status():
66 """Get the TPM attestation status. Works similar to get_tpm_status().
67 """
68 out = __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_attestation_status')
69 status = {}
70 for field in ['Prepared', 'Enrolled']:
71 match = re.search('Attestation %s: (true|false)' % field, out)
72 if not match:
73 raise ChromiumOSError('Invalid attestation status: "%s".' % out)
74 status[field] = match.group(1) == 'true'
75 return status
76
77
Frank Farzand5e36312012-01-13 14:34:03 -080078def take_tpm_ownership():
79 """Take TPM owernship.
80
81 Blocks until TPM is owned.
82 """
83 __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_take_ownership')
84 __run_cmd(CRYPTOHOME_CMD + ' --action=tpm_wait_ownership')
85
86
Darren Krahn0e73e7f2012-09-05 15:35:15 -070087def verify_ek():
88 """Verify the TPM endorsement key.
89
90 Returns true if EK is valid.
91 """
92 cmd = CRYPTOHOME_CMD + ' --action=tpm_verify_ek'
93 return (utils.system(cmd, ignore_status=True) == 0)
94
95
Sean Oe5d8fd02010-09-30 10:44:44 +020096def remove_vault(user):
barfab@chromium.org5c374632012-04-05 16:50:56 +020097 """Remove the given user's vault from the shadow directory."""
Sean Oe5d8fd02010-09-30 10:44:44 +020098 logging.debug('user is %s', user)
99 user_hash = get_user_hash(user)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200100 logging.debug('Removing vault for user %s with hash %s' % (user, user_hash))
Sean Oe5d8fd02010-09-30 10:44:44 +0200101 cmd = CRYPTOHOME_CMD + ' --action=remove --force --user=%s' % user
102 __run_cmd(cmd)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200103 # Ensure that the vault does not exist.
104 if os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
105 raise ChromiumOSError('Cryptohome could not remove the user''s vault.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200106
107
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200108def remove_all_vaults():
109 """Remove any existing vaults from the shadow directory.
110
111 This function must be run with root privileges.
112 """
barfab@chromium.org5c374632012-04-05 16:50:56 +0200113 for item in os.listdir(constants.SHADOW_ROOT):
114 abs_item = os.path.join(constants.SHADOW_ROOT, item)
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200115 if os.path.isdir(os.path.join(abs_item, 'vault')):
116 logging.debug('Removing vault for user with hash %s' % item)
117 shutil.rmtree(abs_item)
118
119
Sean Oe5d8fd02010-09-30 10:44:44 +0200120def mount_vault(user, password, create=False):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200121 """Mount the given user's vault."""
Sean Oe5d8fd02010-09-30 10:44:44 +0200122 cmd = (CRYPTOHOME_CMD + ' --action=mount --user=%s --password=%s' %
123 (user, password))
124 if create:
125 cmd += ' --create'
126 __run_cmd(cmd)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200127 # Ensure that the vault exists in the shadow directory.
Sean Oe5d8fd02010-09-30 10:44:44 +0200128 user_hash = get_user_hash(user)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200129 if not os.path.exists(os.path.join(constants.SHADOW_ROOT, user_hash)):
Sean Oe5d8fd02010-09-30 10:44:44 +0200130 raise ChromiumOSError('Cryptohome vault not found after mount.')
barfab@chromium.org5c374632012-04-05 16:50:56 +0200131 # Ensure that the vault is mounted.
132 if not is_vault_mounted(
133 user=user,
134 device_regex=constants.CRYPTOHOME_DEV_REGEX_REGULAR_USER,
135 allow_fail=True):
136 raise ChromiumOSError('Cryptohome created a vault but did not mount.')
Sean Oe5d8fd02010-09-30 10:44:44 +0200137
138
139def test_auth(user, password):
140 cmd = (CRYPTOHOME_CMD + ' --action=test_auth --user=%s --password=%s' %
141 (user, password))
Elly Jones630f3a92012-04-17 16:40:25 -0400142 cmd += ' --async'
Sean Oe5d8fd02010-09-30 10:44:44 +0200143 return 'Authentication succeeded' in __run_cmd(cmd)
144
145
Elly Jones686c2f42011-10-24 16:45:07 -0400146def unmount_vault(user=None):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200147 """Unmount the given user's vault.
148
149 Once unmounting for a specific user is supported, the user parameter will
150 name the target user. See crosbug.com/20778.
Elly Jones686c2f42011-10-24 16:45:07 -0400151 """
Sean Oe5d8fd02010-09-30 10:44:44 +0200152 cmd = (CRYPTOHOME_CMD + ' --action=unmount')
153 __run_cmd(cmd)
barfab@chromium.org5c374632012-04-05 16:50:56 +0200154 # Ensure that the vault is not mounted.
155 if is_vault_mounted(allow_fail=True):
Sean Oe5d8fd02010-09-30 10:44:44 +0200156 raise ChromiumOSError('Cryptohome did not unmount the user.')
157
158
barfab@chromium.org5c374632012-04-05 16:50:56 +0200159def __get_mount_info(mount_point, allow_fail=False):
160 """Get information about the active mount at a given mount point."""
Will Drewry81ad6162010-04-01 10:26:07 -0500161 mount_line = utils.system_output(
barfab@chromium.org5c374632012-04-05 16:50:56 +0200162 'grep %s /proc/$(pgrep cryptohomed)/mounts' % mount_point,
163 ignore_status=allow_fail)
Sourav Poddar574bd622010-05-26 14:22:26 +0530164 return mount_line.split()
165
166
barfab@chromium.org5c374632012-04-05 16:50:56 +0200167def __get_user_mount_info(user=None, allow_fail=False):
168 """Get information about the active mounts for a given user.
169
170 Returns the active mounts at the user's user and system mount points. If no
171 user is given, the active mount at the shared mount point is returned
172 (regular users have a bind-mount at this mount point for backwards
173 compatibility; the guest user has a mount at this mount point only).
174 """
175 if user:
176 return [__get_mount_info(mount_point=user_path(user),
177 allow_fail=allow_fail),
178 __get_mount_info(mount_point=system_path(user),
179 allow_fail=allow_fail)]
Jim Hebertf08f88d2011-04-22 10:33:49 -0700180 else:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200181 return [__get_mount_info(mount_point=constants.CRYPTOHOME_MOUNT_PT,
182 allow_fail=allow_fail)]
Jim Hebertf08f88d2011-04-22 10:33:49 -0700183
184
barfab@chromium.org5c374632012-04-05 16:50:56 +0200185def is_vault_mounted(
186 user=None,
187 device_regex=constants.CRYPTOHOME_DEV_REGEX_ANY,
188 fs_regex=constants.CRYPTOHOME_FS_REGEX_ANY,
189 allow_fail=False):
190 """Check whether a vault is mounted for the given user.
191
192 If no user is given, the shared mount point is checked, determining whether
193 a vault is mounted for any user.
194 """
195 user_mount_info = __get_user_mount_info(user=user, allow_fail=allow_fail)
196 for mount_info in user_mount_info:
197 if (len(mount_info) < 3 or
198 not re.match(device_regex, mount_info[0]) or
199 not re.match(fs_regex, mount_info[2])):
200 return False
201 return True
Sourav Poddar574bd622010-05-26 14:22:26 +0530202
203
barfab@chromium.org5c374632012-04-05 16:50:56 +0200204def is_guest_vault_mounted(allow_fail=False):
205 """Check whether a vault backed by tmpfs is mounted for the guest user."""
206 return is_vault_mounted(
207 user=None,
208 device_regex=constants.CRYPTOHOME_DEV_REGEX_GUEST,
209 fs_regex=constants.CRYPTOHOME_FS_REGEX_TMPFS,
210 allow_fail=allow_fail)
211
212
213def get_mounted_vault_devices(user=None, allow_fail=False):
214 """Get the device(s) backing the vault mounted for the given user.
215
216 Returns the devices mounted at the user's user and system mount points. If
217 no user is given, the device mounted at the shared mount point is returned.
218 """
219 return [mount_info[0]
220 for mount_info
221 in __get_user_mount_info(user=user, allow_fail=allow_fail)
222 if len(mount_info)]
Nirnimesh66814492011-06-27 18:00:33 -0700223
224
225def canonicalize(credential):
barfab@chromium.org5c374632012-04-05 16:50:56 +0200226 """Perform basic canonicalization of |email_address|.
Nirnimesh66814492011-06-27 18:00:33 -0700227
barfab@chromium.org5c374632012-04-05 16:50:56 +0200228 Perform basic canonicalization of |email_address|, taking into account that
229 gmail does not consider '.' or caps inside a username to matter. It also
230 ignores everything after a '+'. For example,
231 c.masone+abc@gmail.com == cMaSone@gmail.com, per
Nirnimesh66814492011-06-27 18:00:33 -0700232 http://mail.google.com/support/bin/answer.py?hl=en&ctx=mail&answer=10313
233 """
234 if not credential:
235 return None
236
237 parts = credential.split('@')
238 if len(parts) != 2:
barfab@chromium.org5c374632012-04-05 16:50:56 +0200239 raise error.TestError('Malformed email: ' + credential)
Nirnimesh66814492011-06-27 18:00:33 -0700240
241 (name, domain) = parts
242 name = name.partition('+')[0]
barfab@chromium.org5c374632012-04-05 16:50:56 +0200243 if (domain == constants.SPECIAL_CASE_DOMAIN):
Nirnimesh66814492011-06-27 18:00:33 -0700244 name = name.replace('.', '')
245 return '@'.join([name, domain]).lower()
Elly Jones686c2f42011-10-24 16:45:07 -0400246
barfab@chromium.orgcf2151e2012-04-04 15:39:34 +0200247
Elly Jones2f0ebba2011-10-27 13:43:20 -0400248class CryptohomeProxy:
249 def __init__(self):
250 BUSNAME = 'org.chromium.Cryptohome'
251 PATH = '/org/chromium/Cryptohome'
252 INTERFACE = 'org.chromium.CryptohomeInterface'
253 bus = dbus.SystemBus()
254 obj = bus.get_object(BUSNAME, PATH)
255 self.iface = dbus.Interface(obj, INTERFACE)
256
257 def mount(self, user, password, create=False):
258 """Mounts a cryptohome.
259
260 Returns True if the mount succeeds or False otherwise.
261 TODO(ellyjones): Migrate mount_vault() to use a multi-user-safe
262 heuristic, then remove this method. See <crosbug.com/20778>.
263 """
264 return self.iface.Mount(user, password, create, False, [])[1]
265
266 def unmount(self, user):
267 """Unmounts a cryptohome.
268
269 Returns True if the unmount suceeds or false otherwise.
270 TODO(ellyjones): Once there's a per-user unmount method, use it. See
271 <crosbug.com/20778>.
272 """
Elly Jones5c3c2b02011-12-21 14:26:28 -0500273 return self.iface.Unmount()
Elly Jones2f0ebba2011-10-27 13:43:20 -0400274
275 def is_mounted(self, user):
276 """Tests whether a user's cryptohome is mounted."""
277 return (utils.is_mountpoint(user_path(user))
278 and utils.is_mountpoint(system_path(user)))
279
280 def require_mounted(self, user):
281 """Raises a test failure if a user's cryptohome is not mounted."""
282 utils.require_mountpoint(user_path(user))
283 utils.require_mountpoint(system_path(user))
Elly Jones4458f442012-04-16 15:42:56 -0400284
285 def migrate(self, user, oldkey, newkey):
286 """Migrates the specified user's cryptohome from one key to another."""
287 return self.iface.MigrateKey(user, oldkey, newkey)
288
289 def remove(self, user):
290 return self.iface.Remove(user)