blob: 6a6f0cf3cf1668448867916d44be94bdd7987cff [file] [log] [blame]
barfab@chromium.orgb6d29932012-04-11 09:46:43 +02001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Eric Li479233e2011-03-04 13:06:15 -08002# 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 logging, os, shutil, tempfile
barfab@chromium.orgb6d29932012-04-11 09:46:43 +02006
Eric Carusoa4ac7a82015-08-06 10:53:54 -07007import common, cryptohome
Eric Li479233e2011-03-04 13:06:15 -08008from autotest_lib.client.bin import utils
9from autotest_lib.client.common_lib import autotemp, error
Eric Carusoa4ac7a82015-08-06 10:53:54 -070010from autotest_lib.client.common_lib.cros import constants
Chris Masonea2b32852014-02-26 12:12:59 -080011from autotest_lib.client.cros import cros_ui
Eric Li479233e2011-03-04 13:06:15 -080012
13
Mike Frysinger4bcdd2b2014-04-17 22:12:32 -040014PK12UTIL = 'pk12util'
15CERTUTIL = 'certutil'
Chris Masone4dbb67c2013-04-30 17:43:17 -070016OPENSSLP12 = 'openssl pkcs12'
17OPENSSLX509 = 'openssl x509'
18OPENSSLRSA = 'openssl rsa'
19OPENSSLREQ = 'openssl req'
20OPENSSLCRYPTO = 'openssl sha1'
21
Chris Masoned976e0e2013-05-06 13:10:07 -070022TESTUSER = 'ownership_test@chromium.org'
23TESTPASS = 'testme'
24
Chris Masone4dbb67c2013-04-30 17:43:17 -070025
Chris Masone105706e2011-04-29 14:37:11 -070026class OwnershipError(error.TestError):
27 """Generic error for ownership-related failures."""
28 pass
29
30
Eric Li479233e2011-03-04 13:06:15 -080031class scoped_tempfile(object):
32 """A wrapper that provides scoped semantics for temporary files.
33
34 Providing a file path causes the scoped_tempfile to take ownership of the
35 file at the provided path. The file at the path will be deleted when this
36 object goes out of scope. If no path is provided, then a temporary file
37 object will be created for the lifetime of the scoped_tempfile
38
39 autotemp.tempfile objects don't seem to play nicely with being
40 used in system commands, so they can't be used for my purposes.
41 """
42
Chris Masonea2b32852014-02-26 12:12:59 -080043 tempdir = autotemp.tempdir(unique_id='ownership')
Eric Li479233e2011-03-04 13:06:15 -080044
45 def __init__(self, name=None):
46 self.name = name
47 if not self.name:
48 self.fo = tempfile.TemporaryFile()
49
50
51 def __del__(self):
52 if self.name:
53 if os.path.exists(self.name):
54 os.unlink(self.name)
55 else:
56 self.fo.close() # Will destroy the underlying tempfile
57
58
59def system_output_on_fail(cmd):
Chris Masone4dbb67c2013-04-30 17:43:17 -070060 """Run a |cmd|, capturing output and logging it only on error.
61
62 @param cmd: the command to run.
63 """
Eric Li479233e2011-03-04 13:06:15 -080064 output = None
65 try:
66 output = utils.system_output(cmd)
67 except:
68 logging.error(output)
69 raise
70
71
Chris Masoneeac4f4f2011-04-06 14:34:25 -070072def __unlink(filename):
Chris Masone4dbb67c2013-04-30 17:43:17 -070073 """unlink a file, but log OSError and IOError instead of raising.
74
75 This allows unlinking files that don't exist safely.
76
77 @param filename: the file to attempt to unlink.
78 """
Chris Masoneeac4f4f2011-04-06 14:34:25 -070079 try:
80 os.unlink(filename)
81 except (IOError, OSError) as error:
82 logging.info(error)
83
84
Chris Masonea2b32852014-02-26 12:12:59 -080085def restart_ui_to_clear_ownership_files():
86 """Remove on-disk state related to device ownership.
87
88 The UI must be stopped while we do this, or the session_manager will
89 write the policy and key files out again.
90 """
Chris Masone2b368ef2014-07-29 15:14:06 -070091 cros_ui.stop(allow_fail=not cros_ui.is_up())
Chris Masonea2b32852014-02-26 12:12:59 -080092 clear_ownership_files_no_restart()
93 cros_ui.start()
94
95
96def clear_ownership_files_no_restart():
97 """Remove on-disk state related to device ownership.
98
99 The UI must be stopped while we do this, or the session_manager will
100 write the policy and key files out again.
101 """
102 if cros_ui.is_up():
103 raise error.TestError("Tried to clear ownership with UI running.")
Chris Masoneeac4f4f2011-04-06 14:34:25 -0700104 __unlink(constants.OWNER_KEY_FILE)
Chris Masoneeac4f4f2011-04-06 14:34:25 -0700105 __unlink(constants.SIGNED_POLICY_FILE)
Chris Masone64c95bc2014-04-10 13:14:56 -0700106 __unlink(os.path.join(constants.USER_DATA_DIR, 'Local State'))
Chris Masoneeac4f4f2011-04-06 14:34:25 -0700107
108
Chris Masone5d010aa2013-05-06 14:38:42 -0700109def fake_ownership():
110 """Fake ownership by generating the necessary magic files."""
111 # Determine the module directory.
112 dirname = os.path.dirname(__file__)
113 mock_certfile = os.path.join(dirname, constants.MOCK_OWNER_CERT)
114 mock_signedpolicyfile = os.path.join(dirname,
115 constants.MOCK_OWNER_POLICY)
116 utils.open_write_close(constants.OWNER_KEY_FILE,
117 cert_extract_pubkey_der(mock_certfile))
118 shutil.copy(mock_signedpolicyfile,
119 constants.SIGNED_POLICY_FILE)
Chris Masoned6ce5472011-04-14 16:38:34 -0700120
Chris Masone105706e2011-04-29 14:37:11 -0700121
122POLICY_TYPE = 'google/chromeos/device'
123
124
125def assert_has_policy_data(response_proto):
Chris Masone4dbb67c2013-04-30 17:43:17 -0700126 """Assert that given protobuf has a policy_data field.
127
128 @param response_proto: a PolicyFetchResponse protobuf.
129 @raises OwnershipError on failure.
130 """
Chris Masone105706e2011-04-29 14:37:11 -0700131 if not response_proto.HasField("policy_data"):
132 raise OwnershipError('Malformatted response.')
133
134
135def assert_has_device_settings(data_proto):
Chris Masone4dbb67c2013-04-30 17:43:17 -0700136 """Assert that given protobuf is a policy with device settings in it.
137
138 @param data_proto: a PolicyData protobuf.
139 @raises OwnershipError if this isn't CrOS policy, or has no settings inside.
140 """
Chris Masone105706e2011-04-29 14:37:11 -0700141 if (not data_proto.HasField("policy_type") or
142 data_proto.policy_type != POLICY_TYPE or
143 not data_proto.HasField("policy_value")):
144 raise OwnershipError('Malformatted response.')
145
146
147def assert_username(data_proto, username):
Chris Masone4dbb67c2013-04-30 17:43:17 -0700148 """Assert that given protobuf is a policy associated with the given user.
149
150 @param data_proto: a PolicyData protobuf.
151 @param username: the username to check for
152 @raises OwnershipError if data_proto isn't associated with username
153 """
Chris Masone105706e2011-04-29 14:37:11 -0700154 if data_proto.username != username:
155 raise OwnershipError('Incorrect username.')
156
157
158def assert_guest_setting(settings, guests):
Chris Masone4dbb67c2013-04-30 17:43:17 -0700159 """Assert that given protobuf has given guest-related settings.
160
161 @param settings: a ChromeDeviceSettingsProto protobuf.
162 @param guests: boolean indicating whether guests are allowed to sign in.
163 @raises OwnershipError if settings doesn't enforce the provided setting.
164 """
Chris Masone105706e2011-04-29 14:37:11 -0700165 if not settings.HasField("guest_mode_enabled"):
166 raise OwnershipError('No guest mode setting protobuf.')
167 if not settings.guest_mode_enabled.HasField("guest_mode_enabled"):
168 raise OwnershipError('No guest mode setting.')
169 if settings.guest_mode_enabled.guest_mode_enabled != guests:
170 raise OwnershipError('Incorrect guest mode setting.')
171
172
173def assert_show_users(settings, show_users):
Chris Masone4dbb67c2013-04-30 17:43:17 -0700174 """Assert that given protobuf has given user-avatar-showing settings.
175
176 @param settings: a ChromeDeviceSettingsProto protobuf.
177 @param show_users: boolean indicating whether avatars are shown on sign in.
178 @raises OwnershipError if settings doesn't enforce the provided setting.
179 """
Chris Masone105706e2011-04-29 14:37:11 -0700180 if not settings.HasField("show_user_names"):
181 raise OwnershipError('No show users setting protobuf.')
182 if not settings.show_user_names.HasField("show_user_names"):
183 raise OwnershipError('No show users setting.')
184 if settings.show_user_names.show_user_names != show_users:
185 raise OwnershipError('Incorrect show users setting.')
186
187
188def assert_roaming(settings, roaming):
Chris Masone4dbb67c2013-04-30 17:43:17 -0700189 """Assert that given protobuf has given roaming settings.
190
191 @param settings: a ChromeDeviceSettingsProto protobuf.
192 @param roaming: boolean indicating whether roaming is allowed.
193 @raises OwnershipError if settings doesn't enforce the provided setting.
194 """
Chris Masone105706e2011-04-29 14:37:11 -0700195 if not settings.HasField("data_roaming_enabled"):
196 raise OwnershipError('No roaming setting protobuf.')
197 if not settings.data_roaming_enabled.HasField("data_roaming_enabled"):
198 raise OwnershipError('No roaming setting.')
199 if settings.data_roaming_enabled.data_roaming_enabled != roaming:
200 raise OwnershipError('Incorrect roaming setting.')
201
202
203def assert_new_users(settings, new_users):
Chris Masone4dbb67c2013-04-30 17:43:17 -0700204 """Assert that given protobuf has given new user settings.
205
206 @param settings: a ChromeDeviceSettingsProto protobuf.
207 @param new_users: boolean indicating whether adding users is allowed.
208 @raises OwnershipError if settings doesn't enforce the provided setting.
209 """
Chris Masone105706e2011-04-29 14:37:11 -0700210 if not settings.HasField("allow_new_users"):
211 raise OwnershipError('No allow new users setting protobuf.')
212 if not settings.allow_new_users.HasField("allow_new_users"):
213 raise OwnershipError('No allow new users setting.')
214 if settings.allow_new_users.allow_new_users != new_users:
215 raise OwnershipError('Incorrect allow new users setting.')
216
217
218def assert_users_on_whitelist(settings, users):
Chris Masone4dbb67c2013-04-30 17:43:17 -0700219 """Assert that given protobuf has given users on the whitelist.
220
221 @param settings: a ChromeDeviceSettingsProto protobuf.
222 @param users: iterable containing usernames that should be on whitelist.
223 @raises OwnershipError if settings doesn't enforce the provided setting.
224 """
Chris Masone105706e2011-04-29 14:37:11 -0700225 if settings.HasField("user_whitelist"):
226 for user in users:
227 if user not in settings.user_whitelist.user_whitelist:
228 raise OwnershipError(user + ' not whitelisted.')
229 else:
230 raise OwnershipError('No user whitelist.')
231
232
233def assert_proxy_settings(settings, proxies):
Chris Masone4dbb67c2013-04-30 17:43:17 -0700234 """Assert that given protobuf has given proxy settings.
235
236 @param settings: a ChromeDeviceSettingsProto protobuf.
237 @param proxies: dict { 'proxy_mode': <mode string> }
238 @raises OwnershipError if settings doesn't enforce the provided setting.
239 """
Chris Masone105706e2011-04-29 14:37:11 -0700240 if not settings.HasField("device_proxy_settings"):
241 raise OwnershipError('No proxy settings protobuf.')
242 if not settings.device_proxy_settings.HasField("proxy_mode"):
243 raise OwnershipError('No proxy_mode setting.')
244 if settings.device_proxy_settings.proxy_mode != proxies['proxy_mode']:
245 raise OwnershipError('Incorrect proxies: %s' % proxies)
246
247
Chris Masone4dbb67c2013-04-30 17:43:17 -0700248def __user_nssdb(user):
249 """Returns the path to the NSSDB for the provided user.
250
251 @param user: the user whose NSSDB the caller wants.
252 @return: absolute path to user's NSSDB.
253 """
254 return os.path.join(cryptohome.user_path(user), '.pki', 'nssdb')
Eric Li479233e2011-03-04 13:06:15 -0800255
256
Chris Masone4dbb67c2013-04-30 17:43:17 -0700257def use_known_ownerkeys(user):
Chris Masone105706e2011-04-29 14:37:11 -0700258 """Sets the system up to use a well-known keypair for owner operations.
259
260 Assuming the appropriate cryptohome is already mounted, configures the
261 device to accept policies signed with the checked-in 'mock' owner key.
Chris Masone4dbb67c2013-04-30 17:43:17 -0700262
263 @param user: the user whose NSSDB should be populated with key material.
Chris Masone105706e2011-04-29 14:37:11 -0700264 """
265 dirname = os.path.dirname(__file__)
266 mock_keyfile = os.path.join(dirname, constants.MOCK_OWNER_KEY)
267 mock_certfile = os.path.join(dirname, constants.MOCK_OWNER_CERT)
Chris Masone4dbb67c2013-04-30 17:43:17 -0700268 push_to_nss(mock_keyfile, mock_certfile, __user_nssdb(user))
Chris Masone105706e2011-04-29 14:37:11 -0700269 utils.open_write_close(constants.OWNER_KEY_FILE,
270 cert_extract_pubkey_der(mock_certfile))
271
272
273def known_privkey():
274 """Returns the mock owner private key in PEM format.
Chris Masone4dbb67c2013-04-30 17:43:17 -0700275
276 @return: mock owner private key in PEM format.
Chris Masone105706e2011-04-29 14:37:11 -0700277 """
278 dirname = os.path.dirname(__file__)
279 return utils.read_file(os.path.join(dirname, constants.MOCK_OWNER_KEY))
280
281
282def known_pubkey():
283 """Returns the mock owner public key in DER format.
Chris Masone4dbb67c2013-04-30 17:43:17 -0700284
285 @return: mock owner public key in DER format.
Chris Masone105706e2011-04-29 14:37:11 -0700286 """
287 dirname = os.path.dirname(__file__)
288 return cert_extract_pubkey_der(os.path.join(dirname,
289 constants.MOCK_OWNER_CERT))
290
291
Eric Li479233e2011-03-04 13:06:15 -0800292def pairgen():
293 """Generate a self-signed cert and associated private key.
294
295 Generates a self-signed X509 certificate and the associated private key.
296 The key is 2048 bits. The generated material is stored in PEM format
297 and the paths to the two files are returned.
298
299 The caller is responsible for cleaning up these files.
Chris Masone4dbb67c2013-04-30 17:43:17 -0700300
301 @return: (/path/to/private_key, /path/to/self-signed_cert)
Eric Li479233e2011-03-04 13:06:15 -0800302 """
Chris Masonebbd576f2011-04-04 11:40:11 -0700303 keyfile = scoped_tempfile.tempdir.name + '/private.key'
304 certfile = scoped_tempfile.tempdir.name + '/cert.pem'
Eric Li479233e2011-03-04 13:06:15 -0800305 cmd = '%s -x509 -subj %s -newkey rsa:2048 -nodes -keyout %s -out %s' % (
306 OPENSSLREQ, '/CN=me', keyfile, certfile)
307 system_output_on_fail(cmd)
308 return (keyfile, certfile)
309
310
Chris Masoneeac4f4f2011-04-06 14:34:25 -0700311def pairgen_as_data():
312 """Generates keypair, returns keys as data.
313
314 Generates a fresh owner keypair and then passes back the
Chris Masone4dbb67c2013-04-30 17:43:17 -0700315 PEM-encoded private key and the DER-encoded public key.
316
317 @return: (PEM-encoded private key, DER-encoded public key)
Chris Masoneeac4f4f2011-04-06 14:34:25 -0700318 """
319 (keypath, certpath) = pairgen()
320 keyfile = scoped_tempfile(keypath)
321 certfile = scoped_tempfile(certpath)
322 return (utils.read_file(keyfile.name),
323 cert_extract_pubkey_der(certfile.name))
324
325
Eric Li479233e2011-03-04 13:06:15 -0800326def push_to_nss(keyfile, certfile, nssdb):
327 """Takes a pre-generated key pair and pushes them to an NSS DB.
328
329 Given paths to a private key and cert in PEM format, stores the pair
330 in the provided nssdb.
Chris Masone4dbb67c2013-04-30 17:43:17 -0700331
332 @param keyfile: path to PEM-formatted private key file.
333 @param certfile: path to PEM-formatted cert file for associated public key.
334 @param nssdb: path to NSSDB to be populated with the provided keys.
Eric Li479233e2011-03-04 13:06:15 -0800335 """
Chris Masoneeac4f4f2011-04-06 14:34:25 -0700336 for_push = scoped_tempfile(scoped_tempfile.tempdir.name + '/for_push.p12')
Eric Li479233e2011-03-04 13:06:15 -0800337 cmd = '%s -export -in %s -inkey %s -out %s ' % (
338 OPENSSLP12, certfile, keyfile, for_push.name)
339 cmd += '-passin pass: -passout pass:'
340 system_output_on_fail(cmd)
341 cmd = '%s -d "sql:%s" -i %s -W ""' % (PK12UTIL,
342 nssdb,
343 for_push.name)
344 system_output_on_fail(cmd)
345
346
Eric Li479233e2011-03-04 13:06:15 -0800347def cert_extract_pubkey_der(pem):
348 """Given a PEM-formatted cert, extracts the public key in DER format.
349
350 Pass in an X509 certificate in PEM format, and you'll get back the
351 DER-formatted public key as a string.
Chris Masone4dbb67c2013-04-30 17:43:17 -0700352
353 @param pem: path to a PEM-formatted cert file.
354 @return: DER-encoded public key from cert, as a string.
Eric Li479233e2011-03-04 13:06:15 -0800355 """
Chris Masoneeac4f4f2011-04-06 14:34:25 -0700356 outfile = scoped_tempfile(scoped_tempfile.tempdir.name + '/pubkey.der')
Eric Li479233e2011-03-04 13:06:15 -0800357 cmd = '%s -in %s -pubkey -noout ' % (OPENSSLX509, pem)
358 cmd += '| %s -outform DER -pubin -out %s' % (OPENSSLRSA,
359 outfile.name)
360 system_output_on_fail(cmd)
361 der = utils.read_file(outfile.name)
362 return der
363
364
Chris Masoneeac4f4f2011-04-06 14:34:25 -0700365def sign(pem_key, data):
366 """Signs |data| with key from |pem_key|, returns signature.
Eric Li479233e2011-03-04 13:06:15 -0800367
Chris Masoneeac4f4f2011-04-06 14:34:25 -0700368 Using the PEM-formatted private key in |pem_key|, generates an
Eric Li479233e2011-03-04 13:06:15 -0800369 RSA-with-SHA1 signature over |data| and returns the signature in
370 a string.
Chris Masone4dbb67c2013-04-30 17:43:17 -0700371
372 @param pem_key: PEM-formatted private key, as a string.
373 @param data: data to be signed.
374 @return: signature as a string.
Eric Li479233e2011-03-04 13:06:15 -0800375 """
376 sig = scoped_tempfile()
377 err = scoped_tempfile()
378 data_file = scoped_tempfile()
379 data_file.fo.write(data)
380 data_file.fo.seek(0)
381
Chris Masoneeac4f4f2011-04-06 14:34:25 -0700382 pem_key_file = scoped_tempfile(scoped_tempfile.tempdir.name + '/pkey.pem')
383 utils.open_write_close(pem_key_file.name, pem_key)
384
385 cmd = '%s -sign %s' % (OPENSSLCRYPTO, pem_key_file.name)
Eric Li479233e2011-03-04 13:06:15 -0800386 try:
387 utils.run(cmd,
388 stdin=data_file.fo,
389 stdout_tee=sig.fo,
390 stderr_tee=err.fo)
391 except:
392 err.fo.seek(0)
393 logging.error(err.fo.read())
394 raise
395
396 sig.fo.seek(0)
397 sig_data = sig.fo.read()
398 if not sig_data:
Chris Masone105706e2011-04-29 14:37:11 -0700399 raise error.OwnershipError('Empty signature!')
Eric Li479233e2011-03-04 13:06:15 -0800400 return sig_data
Joao da Silva55811572013-02-06 00:59:15 +0100401
Chris Masone4dbb67c2013-04-30 17:43:17 -0700402
Joao da Silva55811572013-02-06 00:59:15 +0100403def get_user_policy_key_filename(username):
Chris Masone4dbb67c2013-04-30 17:43:17 -0700404 """Returns the path to the user policy key for the given username.
405
406 @param username: the user whose policy key we want the path to.
407 @return: absolute path to user's policy key file.
408 """
409 return os.path.join(constants.USER_POLICY_DIR,
410 cryptohome.get_user_hash(username),
411 constants.USER_POLICY_KEY_FILENAME)