blob: 90eed84d74de21685cba7b084ae0d63452631c9d [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
barfab@chromium.orgb6d29932012-04-11 09:46:43 +02005import dbus, logging, os, tempfile
6
7import common, constants, cros_ui, 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 Li479233e2011-03-04 13:06:15 -080010
11
Chris Masone105706e2011-04-29 14:37:11 -070012class OwnershipError(error.TestError):
13 """Generic error for ownership-related failures."""
14 pass
15
16
Eric Li479233e2011-03-04 13:06:15 -080017class scoped_tempfile(object):
18 """A wrapper that provides scoped semantics for temporary files.
19
20 Providing a file path causes the scoped_tempfile to take ownership of the
21 file at the provided path. The file at the path will be deleted when this
22 object goes out of scope. If no path is provided, then a temporary file
23 object will be created for the lifetime of the scoped_tempfile
24
25 autotemp.tempfile objects don't seem to play nicely with being
26 used in system commands, so they can't be used for my purposes.
27 """
28
29 tempdir = autotemp.tempdir(unique_id=__module__)
30
31 def __init__(self, name=None):
32 self.name = name
33 if not self.name:
34 self.fo = tempfile.TemporaryFile()
35
36
37 def __del__(self):
38 if self.name:
39 if os.path.exists(self.name):
40 os.unlink(self.name)
41 else:
42 self.fo.close() # Will destroy the underlying tempfile
43
44
45def system_output_on_fail(cmd):
46 """Run a |cmd|, capturing output and logging it only on error."""
47 output = None
48 try:
49 output = utils.system_output(cmd)
50 except:
51 logging.error(output)
52 raise
53
54
Chris Masoneeac4f4f2011-04-06 14:34:25 -070055def __unlink(filename):
56 try:
57 os.unlink(filename)
58 except (IOError, OSError) as error:
59 logging.info(error)
60
61
62def clear_ownership():
63 __unlink(constants.OWNER_KEY_FILE)
Chris Masoneeac4f4f2011-04-06 14:34:25 -070064 __unlink(constants.SIGNED_POLICY_FILE)
65
66
Chris Masoned6ce5472011-04-14 16:38:34 -070067def connect_to_session_manager():
68 """Create and return a DBus connection to session_manager.
69
70 Connects to the session manager over the DBus system bus. Returns
71 appropriately configured DBus interface object.
72 """
73 bus = dbus.SystemBus()
74 proxy = bus.get_object('org.chromium.SessionManager',
75 '/org/chromium/SessionManager')
76 return dbus.Interface(proxy, 'org.chromium.SessionManagerInterface')
77
78
Chris Masone105706e2011-04-29 14:37:11 -070079def listen_to_session_manager_signal(callback, signal):
80 """Create and return a DBus connection to session_manager.
81
82 Connects to the session manager over the DBus system bus. Returns
83 appropriately configured DBus interface object.
84 """
85 bus = dbus.SystemBus()
86 bus.add_signal_receiver(
87 handler_function=callback,
88 signal_name=signal,
89 dbus_interface='org.chromium.Chromium',
90 bus_name=None,
91 path='/')
92
93POLICY_TYPE = 'google/chromeos/device'
94
95
96def assert_has_policy_data(response_proto):
97 if not response_proto.HasField("policy_data"):
98 raise OwnershipError('Malformatted response.')
99
100
101def assert_has_device_settings(data_proto):
102 if (not data_proto.HasField("policy_type") or
103 data_proto.policy_type != POLICY_TYPE or
104 not data_proto.HasField("policy_value")):
105 raise OwnershipError('Malformatted response.')
106
107
108def assert_username(data_proto, username):
109 if data_proto.username != username:
110 raise OwnershipError('Incorrect username.')
111
112
113def assert_guest_setting(settings, guests):
114 if not settings.HasField("guest_mode_enabled"):
115 raise OwnershipError('No guest mode setting protobuf.')
116 if not settings.guest_mode_enabled.HasField("guest_mode_enabled"):
117 raise OwnershipError('No guest mode setting.')
118 if settings.guest_mode_enabled.guest_mode_enabled != guests:
119 raise OwnershipError('Incorrect guest mode setting.')
120
121
122def assert_show_users(settings, show_users):
123 if not settings.HasField("show_user_names"):
124 raise OwnershipError('No show users setting protobuf.')
125 if not settings.show_user_names.HasField("show_user_names"):
126 raise OwnershipError('No show users setting.')
127 if settings.show_user_names.show_user_names != show_users:
128 raise OwnershipError('Incorrect show users setting.')
129
130
131def assert_roaming(settings, roaming):
132 if not settings.HasField("data_roaming_enabled"):
133 raise OwnershipError('No roaming setting protobuf.')
134 if not settings.data_roaming_enabled.HasField("data_roaming_enabled"):
135 raise OwnershipError('No roaming setting.')
136 if settings.data_roaming_enabled.data_roaming_enabled != roaming:
137 raise OwnershipError('Incorrect roaming setting.')
138
139
140def assert_new_users(settings, new_users):
141 if not settings.HasField("allow_new_users"):
142 raise OwnershipError('No allow new users setting protobuf.')
143 if not settings.allow_new_users.HasField("allow_new_users"):
144 raise OwnershipError('No allow new users setting.')
145 if settings.allow_new_users.allow_new_users != new_users:
146 raise OwnershipError('Incorrect allow new users setting.')
147
148
149def assert_users_on_whitelist(settings, users):
150 if settings.HasField("user_whitelist"):
151 for user in users:
152 if user not in settings.user_whitelist.user_whitelist:
153 raise OwnershipError(user + ' not whitelisted.')
154 else:
155 raise OwnershipError('No user whitelist.')
156
157
158def assert_proxy_settings(settings, proxies):
159 if not settings.HasField("device_proxy_settings"):
160 raise OwnershipError('No proxy settings protobuf.')
161 if not settings.device_proxy_settings.HasField("proxy_mode"):
162 raise OwnershipError('No proxy_mode setting.')
163 if settings.device_proxy_settings.proxy_mode != proxies['proxy_mode']:
164 raise OwnershipError('Incorrect proxies: %s' % proxies)
165
166
Eric Li479233e2011-03-04 13:06:15 -0800167NSSDB = constants.CRYPTOHOME_MOUNT_PT + '/.pki/nssdb'
168PK12UTIL = 'nsspk12util'
169OPENSSLP12 = 'openssl pkcs12'
170OPENSSLX509 = 'openssl x509'
171OPENSSLRSA = 'openssl rsa'
172OPENSSLREQ = 'openssl req'
173OPENSSLCRYPTO = 'openssl sha1'
174
175
Chris Masone105706e2011-04-29 14:37:11 -0700176def use_known_ownerkeys():
177 """Sets the system up to use a well-known keypair for owner operations.
178
179 Assuming the appropriate cryptohome is already mounted, configures the
180 device to accept policies signed with the checked-in 'mock' owner key.
181 """
182 dirname = os.path.dirname(__file__)
183 mock_keyfile = os.path.join(dirname, constants.MOCK_OWNER_KEY)
184 mock_certfile = os.path.join(dirname, constants.MOCK_OWNER_CERT)
185 push_to_nss(mock_keyfile, mock_certfile, NSSDB)
186 utils.open_write_close(constants.OWNER_KEY_FILE,
187 cert_extract_pubkey_der(mock_certfile))
188
189
190def known_privkey():
191 """Returns the mock owner private key in PEM format.
192 """
193 dirname = os.path.dirname(__file__)
194 return utils.read_file(os.path.join(dirname, constants.MOCK_OWNER_KEY))
195
196
197def known_pubkey():
198 """Returns the mock owner public key in DER format.
199 """
200 dirname = os.path.dirname(__file__)
201 return cert_extract_pubkey_der(os.path.join(dirname,
202 constants.MOCK_OWNER_CERT))
203
204
Eric Li479233e2011-03-04 13:06:15 -0800205def pairgen():
206 """Generate a self-signed cert and associated private key.
207
208 Generates a self-signed X509 certificate and the associated private key.
209 The key is 2048 bits. The generated material is stored in PEM format
210 and the paths to the two files are returned.
211
212 The caller is responsible for cleaning up these files.
213 """
Chris Masonebbd576f2011-04-04 11:40:11 -0700214 keyfile = scoped_tempfile.tempdir.name + '/private.key'
215 certfile = scoped_tempfile.tempdir.name + '/cert.pem'
Eric Li479233e2011-03-04 13:06:15 -0800216 cmd = '%s -x509 -subj %s -newkey rsa:2048 -nodes -keyout %s -out %s' % (
217 OPENSSLREQ, '/CN=me', keyfile, certfile)
218 system_output_on_fail(cmd)
219 return (keyfile, certfile)
220
221
Chris Masoneeac4f4f2011-04-06 14:34:25 -0700222def pairgen_as_data():
223 """Generates keypair, returns keys as data.
224
225 Generates a fresh owner keypair and then passes back the
226 PEM-formatted private key and the DER-encoded public key.
227 """
228 (keypath, certpath) = pairgen()
229 keyfile = scoped_tempfile(keypath)
230 certfile = scoped_tempfile(certpath)
231 return (utils.read_file(keyfile.name),
232 cert_extract_pubkey_der(certfile.name))
233
234
Eric Li479233e2011-03-04 13:06:15 -0800235def push_to_nss(keyfile, certfile, nssdb):
236 """Takes a pre-generated key pair and pushes them to an NSS DB.
237
238 Given paths to a private key and cert in PEM format, stores the pair
239 in the provided nssdb.
240 """
Chris Masoneeac4f4f2011-04-06 14:34:25 -0700241 for_push = scoped_tempfile(scoped_tempfile.tempdir.name + '/for_push.p12')
Eric Li479233e2011-03-04 13:06:15 -0800242 cmd = '%s -export -in %s -inkey %s -out %s ' % (
243 OPENSSLP12, certfile, keyfile, for_push.name)
244 cmd += '-passin pass: -passout pass:'
245 system_output_on_fail(cmd)
246 cmd = '%s -d "sql:%s" -i %s -W ""' % (PK12UTIL,
247 nssdb,
248 for_push.name)
249 system_output_on_fail(cmd)
250
251
252def generate_owner_creds():
253 """Generates a keypair, registered with NSS, and returns key and cert.
254
255 Generates a fresh self-signed cert and private key. Registers them
256 with NSS and then passes back paths to files containing the
257 PEM-formatted private key and certificate.
258 """
259 (keyfile, certfile) = pairgen()
260 push_to_nss(keyfile, certfile, NSSDB)
261 return (keyfile, certfile)
262
263
264
265def cert_extract_pubkey_der(pem):
266 """Given a PEM-formatted cert, extracts the public key in DER format.
267
268 Pass in an X509 certificate in PEM format, and you'll get back the
269 DER-formatted public key as a string.
270 """
Chris Masoneeac4f4f2011-04-06 14:34:25 -0700271 outfile = scoped_tempfile(scoped_tempfile.tempdir.name + '/pubkey.der')
Eric Li479233e2011-03-04 13:06:15 -0800272 cmd = '%s -in %s -pubkey -noout ' % (OPENSSLX509, pem)
273 cmd += '| %s -outform DER -pubin -out %s' % (OPENSSLRSA,
274 outfile.name)
275 system_output_on_fail(cmd)
276 der = utils.read_file(outfile.name)
277 return der
278
279
Chris Masoneeac4f4f2011-04-06 14:34:25 -0700280def generate_and_register_keypair(testuser, testpass):
281 """Generates keypair, registers with NSS, sets owner key, returns keypair.
Eric Li479233e2011-03-04 13:06:15 -0800282
283 Generates a fresh owner keypair. Registers keys with NSS,
284 puts the owner public key in the right place, ensures that the
285 session_manager picks it up, ensures the owner's home dir is
Chris Masoneeac4f4f2011-04-06 14:34:25 -0700286 mounted, and then passes back the PEM-formatted private key and the
287 DER-encoded public key.
Eric Li479233e2011-03-04 13:06:15 -0800288 """
Chris Masoneeac4f4f2011-04-06 14:34:25 -0700289 (keypath, certpath) = generate_owner_creds()
290 keyfile = scoped_tempfile(keypath)
291 certfile = scoped_tempfile(certpath)
292
293 pubkey = cert_extract_pubkey_der(certfile.name)
294 utils.open_write_close(constants.OWNER_KEY_FILE, pubkey)
295
David Jamesd51ac9c2011-09-10 00:45:24 -0700296 cros_ui.nuke()
Eric Li479233e2011-03-04 13:06:15 -0800297 cryptohome.mount_vault(testuser, testpass, create=False)
Chris Masoneeac4f4f2011-04-06 14:34:25 -0700298 return (utils.read_file(keyfile.name), pubkey)
Eric Li479233e2011-03-04 13:06:15 -0800299
300
Chris Masoneeac4f4f2011-04-06 14:34:25 -0700301def sign(pem_key, data):
302 """Signs |data| with key from |pem_key|, returns signature.
Eric Li479233e2011-03-04 13:06:15 -0800303
Chris Masoneeac4f4f2011-04-06 14:34:25 -0700304 Using the PEM-formatted private key in |pem_key|, generates an
Eric Li479233e2011-03-04 13:06:15 -0800305 RSA-with-SHA1 signature over |data| and returns the signature in
306 a string.
307 """
308 sig = scoped_tempfile()
309 err = scoped_tempfile()
310 data_file = scoped_tempfile()
311 data_file.fo.write(data)
312 data_file.fo.seek(0)
313
Chris Masoneeac4f4f2011-04-06 14:34:25 -0700314 pem_key_file = scoped_tempfile(scoped_tempfile.tempdir.name + '/pkey.pem')
315 utils.open_write_close(pem_key_file.name, pem_key)
316
317 cmd = '%s -sign %s' % (OPENSSLCRYPTO, pem_key_file.name)
Eric Li479233e2011-03-04 13:06:15 -0800318 try:
319 utils.run(cmd,
320 stdin=data_file.fo,
321 stdout_tee=sig.fo,
322 stderr_tee=err.fo)
323 except:
324 err.fo.seek(0)
325 logging.error(err.fo.read())
326 raise
327
328 sig.fo.seek(0)
329 sig_data = sig.fo.read()
330 if not sig_data:
Chris Masone105706e2011-04-29 14:37:11 -0700331 raise error.OwnershipError('Empty signature!')
Eric Li479233e2011-03-04 13:06:15 -0800332 return sig_data