Merge lp:~apw/launchpad/signing-opal into lp:launchpad

Proposed by Andy Whitcroft
Status: Merged
Merged at revision: 18445
Proposed branch: lp:~apw/launchpad/signing-opal
Merge into: lp:launchpad
Diff against target: 649 lines (+316/-75)
2 files modified
lib/lp/archivepublisher/signing.py (+39/-13)
lib/lp/archivepublisher/tests/test_signing.py (+277/-62)
To merge this branch: bzr merge lp:~apw/launchpad/signing-opal
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+328108@code.launchpad.net

Commit message

Add signing support for vmlinux for use on ppc64el Opal (and compatible) firmware.

Description of the change

Add signing support for vmlinux for use on ppc64el Opal (and compatible) firmware.

To post a comment you must log in.
Revision history for this message
Andy Whitcroft (apw) wrote :

Reworked the signing validation to be more comprehensive. Cleaned up some lint. Added an additional noted missing test for checksums in tarball mode.

Revision history for this message
Colin Watson (cjwatson) wrote :

This generally seems OK; just some nits in the tests.

review: Approve
Revision history for this message
Andy Whitcroft (apw) wrote :

Nits applied as indicated. Fixed the errant match in the latest test. Should be good to go.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/archivepublisher/signing.py'
2--- lib/lp/archivepublisher/signing.py 2016-08-10 09:19:16 +0000
3+++ lib/lp/archivepublisher/signing.py 2017-08-02 19:21:26 +0000
4@@ -94,12 +94,16 @@
5 self.uefi_cert = None
6 self.kmod_pem = None
7 self.kmod_x509 = None
8+ self.opal_pem = None
9+ self.opal_x509 = None
10 self.autokey = False
11 else:
12 self.uefi_key = os.path.join(pubconf.signingroot, "uefi.key")
13 self.uefi_cert = os.path.join(pubconf.signingroot, "uefi.crt")
14 self.kmod_pem = os.path.join(pubconf.signingroot, "kmod.pem")
15 self.kmod_x509 = os.path.join(pubconf.signingroot, "kmod.x509")
16+ self.opal_pem = os.path.join(pubconf.signingroot, "opal.pem")
17+ self.opal_x509 = os.path.join(pubconf.signingroot, "opal.x509")
18 self.autokey = pubconf.signingautokey
19
20 self.setComponents(tarfile_path)
21@@ -171,6 +175,8 @@
22 yield (os.path.join(dirpath, filename), self.signUefi)
23 elif filename.endswith(".ko"):
24 yield (os.path.join(dirpath, filename), self.signKmod)
25+ elif filename.endswith(".opal"):
26+ yield (os.path.join(dirpath, filename), self.signOpal)
27
28 def getKeys(self, which, generate, *keynames):
29 """Validate and return the uefi key and cert for encryption."""
30@@ -237,15 +243,15 @@
31 cmdl = ["sbsign", "--key", key, "--cert", cert, image]
32 return self.callLog("UEFI signing", cmdl)
33
34- def generateKmodKeys(self):
35- """Generate new Kernel Signing Keys for this archive."""
36- directory = os.path.dirname(self.kmod_pem)
37+ def generatePemX509Pair(self, key_type, pem_filename, x509_filename):
38+ """Generate new pem/x509 key pairs."""
39+ directory = os.path.dirname(pem_filename)
40 if not os.path.exists(directory):
41 os.makedirs(directory)
42
43 # Truncate name to 64 character maximum.
44 common_name = self.generateKeyCommonName(
45- self.archive.owner.name, self.archive.name, "kmod")
46+ self.archive.owner.name, self.archive.name, key_type)
47
48 old_mask = os.umask(0o077)
49 try:
50@@ -276,21 +282,26 @@
51 new_key_cmd = [
52 'openssl', 'req', '-new', '-nodes', '-utf8', '-sha512',
53 '-days', '3650', '-batch', '-x509', '-config', tf.name,
54- '-outform', 'PEM', '-out', self.kmod_pem,
55- '-keyout', self.kmod_pem
56+ '-outform', 'PEM', '-out', pem_filename,
57+ '-keyout', pem_filename
58 ]
59- if self.callLog("Kmod keygen key", new_key_cmd) == 0:
60+ if self.callLog(key_type + " keygen key", new_key_cmd) == 0:
61 new_x509_cmd = [
62- 'openssl', 'x509', '-in', self.kmod_pem,
63- '-outform', 'DER', '-out', self.kmod_x509
64+ 'openssl', 'x509', '-in', pem_filename,
65+ '-outform', 'DER', '-out', x509_filename
66 ]
67- if self.callLog("Kmod keygen cert", new_x509_cmd) != 0:
68- os.unlink(self.kmod_pem)
69+ if self.callLog(key_type + " keygen cert",
70+ new_x509_cmd) != 0:
71+ os.unlink(pem_filename)
72 finally:
73 os.umask(old_mask)
74
75- if os.path.exists(self.kmod_x509):
76- os.chmod(self.kmod_x509, 0o644)
77+ if os.path.exists(x509_filename):
78+ os.chmod(x509_filename, 0o644)
79+
80+ def generateKmodKeys(self):
81+ """Generate new Kernel Signing Keys for this archive."""
82+ self.generatePemX509Pair("Kmod", self.kmod_pem, self.kmod_x509)
83
84 def signKmod(self, image):
85 """Attempt to sign a kernel module."""
86@@ -303,6 +314,21 @@
87 cmdl = ["kmodsign", "-D", "sha512", pem, cert, image, image + ".sig"]
88 return self.callLog("Kmod signing", cmdl)
89
90+ def generateOpalKeys(self):
91+ """Generate new Opal Signing Keys for this archive."""
92+ self.generatePemX509Pair("Opal", self.opal_pem, self.opal_x509)
93+
94+ def signOpal(self, image):
95+ """Attempt to sign a kernel image for Opal."""
96+ remove_if_exists("%s.sig" % image)
97+ (pem, cert) = self.getKeys('Opal Kernel', self.generateOpalKeys,
98+ self.opal_pem, self.opal_x509)
99+ if not pem or not cert:
100+ return
101+ self.publishPublicKey(cert)
102+ cmdl = ["kmodsign", "-D", "sha512", pem, cert, image, image + ".sig"]
103+ return self.callLog("Opal signing", cmdl)
104+
105 def convertToTarball(self):
106 """Convert unpacked output to signing tarball."""
107 tarfilename = os.path.join(self.tmpdir, "signed.tar.gz")
108
109=== modified file 'lib/lp/archivepublisher/tests/test_signing.py'
110--- lib/lp/archivepublisher/tests/test_signing.py 2017-07-24 10:06:38 +0000
111+++ lib/lp/archivepublisher/tests/test_signing.py 2017-08-02 19:21:26 +0000
112@@ -11,6 +11,13 @@
113
114 from fixtures import MonkeyPatch
115 from testtools.deferredruntest import AsynchronousDeferredRunTest
116+from testtools.matchers import (
117+ Contains,
118+ Matcher,
119+ MatchesAll,
120+ Mismatch,
121+ Not,
122+ )
123 from twisted.internet import defer
124 from zope.component import getUtility
125
126@@ -37,6 +44,30 @@
127 from lp.testing.layers import ZopelessDatabaseLayer
128
129
130+class SignedMatches(Matcher):
131+ """Matches if a signing result directory is valid."""
132+
133+ def __init__(self, expected):
134+ self.expected = expected
135+
136+ def match(self, base):
137+ content = []
138+ for root, dirs, files in os.walk(base):
139+ content.extend(
140+ [os.path.relpath(os.path.join(root, f), base) for f in files])
141+
142+ left_over = sorted(set(content) - set(self.expected))
143+ missing = sorted(set(self.expected) - set(content))
144+ if left_over != [] or missing != []:
145+ mismatch = ''
146+ if left_over:
147+ mismatch += " unexpected files: " + str(left_over)
148+ if missing:
149+ mismatch += " missing files: " + str(missing)
150+ return Mismatch("SignedMatches:" + mismatch)
151+ return None
152+
153+
154 class FakeMethodCallLog(FakeMethod):
155 """Fake execution general commands."""
156 def __init__(self, upload=None, *args, **kwargs):
157@@ -44,10 +75,13 @@
158 self.upload = upload
159 self.callers = {
160 "UEFI signing": 0,
161+ "UEFI keygen": 0,
162 "Kmod signing": 0,
163- "UEFI keygen": 0,
164 "Kmod keygen key": 0,
165 "Kmod keygen cert": 0,
166+ "Opal signing": 0,
167+ "Opal keygen key": 0,
168+ "Opal keygen cert": 0,
169 }
170
171 def __call__(self, *args, **kwargs):
172@@ -61,6 +95,10 @@
173 if filename.endswith(".efi"):
174 write_file(filename + ".signed", "")
175
176+ elif description == "UEFI keygen":
177+ write_file(self.upload.uefi_key, "")
178+ write_file(self.upload.uefi_cert, "")
179+
180 elif description == "Kmod signing":
181 filename = cmdl[-1]
182 if filename.endswith(".ko.sig"):
183@@ -72,9 +110,16 @@
184 elif description == "Kmod keygen key":
185 write_file(self.upload.kmod_pem, "")
186
187- elif description == "UEFI keygen":
188- write_file(self.upload.uefi_key, "")
189- write_file(self.upload.uefi_cert, "")
190+ elif description == "Opal signing":
191+ filename = cmdl[-1]
192+ if filename.endswith(".opal.sig"):
193+ write_file(filename, "")
194+
195+ elif description == "Opal keygen cert":
196+ write_file(self.upload.opal_x509, "")
197+
198+ elif description == "Opal keygen key":
199+ write_file(self.upload.opal_pem, "")
200
201 else:
202 raise AssertionError("unknown command executed cmd=(%s)" %
203@@ -85,6 +130,9 @@
204 def caller_count(self, caller):
205 return self.callers.get(caller, 0)
206
207+ def caller_list(self):
208+ return [(caller, n) for (caller, n) in self.callers.items() if n != 0]
209+
210
211 class TestSigningHelpers(TestCaseWithFactory):
212
213@@ -147,6 +195,13 @@
214 write_file(self.kmod_pem, "")
215 write_file(self.kmod_x509, "")
216
217+ def setUpOpalKeys(self, create=True):
218+ self.opal_pem = os.path.join(self.signing_dir, "opal.pem")
219+ self.opal_x509 = os.path.join(self.signing_dir, "opal.x509")
220+ if create:
221+ write_file(self.opal_pem, "")
222+ write_file(self.opal_x509, "")
223+
224 def openArchive(self, loader_type, version, arch):
225 self.path = os.path.join(
226 self.temp_dir, "%s_%s_%s.tar.gz" % (loader_type, version, arch))
227@@ -182,6 +237,7 @@
228 upload = SigningUpload()
229 upload.signUefi = FakeMethod()
230 upload.signKmod = FakeMethod()
231+ upload.signOpal = FakeMethod()
232 # Under no circumstances is it safe to execute actual commands.
233 fake_call = FakeMethod(result=0)
234 self.useFixture(MonkeyPatch("subprocess.call", fake_call))
235@@ -201,12 +257,9 @@
236 self.openArchive("test", "1.0", "amd64")
237 self.tarfile.add_file("1.0/empty.efi", "")
238 self.tarfile.add_file("1.0/empty.ko", "")
239+ self.tarfile.add_file("1.0/empty.opal", "")
240 upload = self.process_emulate()
241- self.assertEqual(0, upload.callLog.caller_count('UEFI keygen'))
242- self.assertEqual(0, upload.callLog.caller_count('Kmod keygen key'))
243- self.assertEqual(0, upload.callLog.caller_count('Kmod keygen cert'))
244- self.assertEqual(0, upload.callLog.caller_count('UEFI signing'))
245- self.assertEqual(0, upload.callLog.caller_count('Kmod signing'))
246+ self.assertContentEqual([], upload.callLog.caller_list())
247
248 def test_archive_primary_no_keys(self):
249 # If the configured key/cert are missing, processing succeeds but
250@@ -214,12 +267,9 @@
251 self.openArchive("test", "1.0", "amd64")
252 self.tarfile.add_file("1.0/empty.efi", "")
253 self.tarfile.add_file("1.0/empty.ko", "")
254+ self.tarfile.add_file("1.0/empty.opal", "")
255 upload = self.process_emulate()
256- self.assertEqual(0, upload.callLog.caller_count('UEFI keygen'))
257- self.assertEqual(0, upload.callLog.caller_count('Kmod keygen key'))
258- self.assertEqual(0, upload.callLog.caller_count('Kmod keygen cert'))
259- self.assertEqual(0, upload.callLog.caller_count('UEFI signing'))
260- self.assertEqual(0, upload.callLog.caller_count('Kmod signing'))
261+ self.assertContentEqual([], upload.callLog.caller_list())
262
263 def test_archive_primary_keys(self):
264 # If the configured key/cert are missing, processing succeeds but
265@@ -229,12 +279,13 @@
266 self.openArchive("test", "1.0", "amd64")
267 self.tarfile.add_file("1.0/empty.efi", "")
268 self.tarfile.add_file("1.0/empty.ko", "")
269+ self.tarfile.add_file("1.0/empty.opal", "")
270 upload = self.process_emulate()
271- self.assertEqual(0, upload.callLog.caller_count('UEFI keygen'))
272- self.assertEqual(0, upload.callLog.caller_count('Kmod keygen key'))
273- self.assertEqual(0, upload.callLog.caller_count('Kmod keygen cert'))
274- self.assertEqual(1, upload.callLog.caller_count('UEFI signing'))
275- self.assertEqual(1, upload.callLog.caller_count('Kmod signing'))
276+ expected_callers = [
277+ ('UEFI signing', 1),
278+ ('Kmod signing', 1),
279+ ]
280+ self.assertContentEqual(expected_callers, upload.callLog.caller_list())
281
282 def test_PPA_creates_keys(self):
283 # If the configured key/cert are missing, processing succeeds but
284@@ -243,12 +294,19 @@
285 self.openArchive("test", "1.0", "amd64")
286 self.tarfile.add_file("1.0/empty.efi", "")
287 self.tarfile.add_file("1.0/empty.ko", "")
288+ self.tarfile.add_file("1.0/empty.opal", "")
289 upload = self.process_emulate()
290- self.assertEqual(1, upload.callLog.caller_count('UEFI keygen'))
291- self.assertEqual(1, upload.callLog.caller_count('Kmod keygen key'))
292- self.assertEqual(1, upload.callLog.caller_count('Kmod keygen cert'))
293- self.assertEqual(1, upload.callLog.caller_count('UEFI signing'))
294- self.assertEqual(1, upload.callLog.caller_count('Kmod signing'))
295+ expected_callers = [
296+ ('UEFI keygen', 1),
297+ ('Kmod keygen key', 1),
298+ ('Kmod keygen cert', 1),
299+ ('Opal keygen key', 1),
300+ ('Opal keygen cert', 1),
301+ ('UEFI signing', 1),
302+ ('Kmod signing', 1),
303+ ('Opal signing', 1),
304+ ]
305+ self.assertContentEqual(expected_callers, upload.callLog.caller_list())
306
307 def test_common_name_plain(self):
308 upload = SigningUpload()
309@@ -319,42 +377,45 @@
310 # Specifying no options should leave us with an open tree.
311 self.setUpUefiKeys()
312 self.setUpKmodKeys()
313+ self.setUpOpalKeys()
314 self.openArchive("test", "1.0", "amd64")
315 self.tarfile.add_file("1.0/empty.efi", "")
316 self.tarfile.add_file("1.0/empty.ko", "")
317+ self.tarfile.add_file("1.0/empty.opal", "")
318 self.process_emulate()
319- self.assertTrue(os.path.exists(os.path.join(
320- self.getSignedPath("test", "amd64"), "1.0", "empty.efi")))
321- self.assertTrue(os.path.exists(os.path.join(
322- self.getSignedPath("test", "amd64"), "1.0", "empty.efi.signed")))
323- self.assertTrue(os.path.exists(os.path.join(
324- self.getSignedPath("test", "amd64"), "1.0", "empty.ko")))
325- self.assertTrue(os.path.exists(os.path.join(
326- self.getSignedPath("test", "amd64"), "1.0", "empty.ko.sig")))
327+ self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([
328+ "1.0/SHA256SUMS",
329+ "1.0/empty.efi", "1.0/empty.efi.signed", "1.0/control/uefi.crt",
330+ "1.0/empty.ko", "1.0/empty.ko.sig", "1.0/control/kmod.x509",
331+ "1.0/empty.opal", "1.0/empty.opal.sig", "1.0/control/opal.x509",
332+ ]))
333
334 def test_options_tarball(self):
335 # Specifying the "tarball" option should create an tarball in
336 # the tmpdir.
337 self.setUpUefiKeys()
338 self.setUpKmodKeys()
339+ self.setUpOpalKeys()
340 self.openArchive("test", "1.0", "amd64")
341 self.tarfile.add_file("1.0/control/options", "tarball")
342 self.tarfile.add_file("1.0/empty.efi", "")
343 self.tarfile.add_file("1.0/empty.ko", "")
344+ self.tarfile.add_file("1.0/empty.opal", "")
345 self.process_emulate()
346- self.assertFalse(os.path.exists(os.path.join(
347- self.getSignedPath("test", "amd64"), "1.0", "empty.efi")))
348- self.assertFalse(os.path.exists(os.path.join(
349- self.getSignedPath("test", "amd64"), "1.0", "empty.ko")))
350+ self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([
351+ "1.0/SHA256SUMS",
352+ "1.0/signed.tar.gz",
353+ ]))
354 tarfilename = os.path.join(self.getSignedPath("test", "amd64"),
355 "1.0", "signed.tar.gz")
356- self.assertTrue(os.path.exists(tarfilename))
357 with tarfile.open(tarfilename) as tarball:
358 self.assertContentEqual([
359- '1.0', '1.0/control', '1.0/control/kmod.x509',
360- '1.0/control/uefi.crt', '1.0/empty.efi',
361- '1.0/empty.efi.signed', '1.0/empty.ko', '1.0/empty.ko.sig',
362- '1.0/control/options',
363+ '1.0', '1.0/control', '1.0/control/options',
364+ '1.0/empty.efi', '1.0/empty.efi.signed',
365+ '1.0/control/uefi.crt',
366+ '1.0/empty.ko', '1.0/empty.ko.sig', '1.0/control/kmod.x509',
367+ '1.0/empty.opal', '1.0/empty.opal.sig',
368+ '1.0/control/opal.x509',
369 ], tarball.getnames())
370
371 def test_options_signed_only(self):
372@@ -362,19 +423,19 @@
373 # the source files leaving signatures only.
374 self.setUpUefiKeys()
375 self.setUpKmodKeys()
376+ self.setUpOpalKeys()
377 self.openArchive("test", "1.0", "amd64")
378 self.tarfile.add_file("1.0/control/options", "signed-only")
379 self.tarfile.add_file("1.0/empty.efi", "")
380 self.tarfile.add_file("1.0/empty.ko", "")
381+ self.tarfile.add_file("1.0/empty.opal", "")
382 self.process_emulate()
383- self.assertFalse(os.path.exists(os.path.join(
384- self.getSignedPath("test", "amd64"), "1.0", "empty.efi")))
385- self.assertTrue(os.path.exists(os.path.join(
386- self.getSignedPath("test", "amd64"), "1.0", "empty.efi.signed")))
387- self.assertFalse(os.path.exists(os.path.join(
388- self.getSignedPath("test", "amd64"), "1.0", "empty.ko")))
389- self.assertTrue(os.path.exists(os.path.join(
390- self.getSignedPath("test", "amd64"), "1.0", "empty.ko.sig")))
391+ self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([
392+ "1.0/SHA256SUMS", "1.0/control/options",
393+ "1.0/empty.efi.signed", "1.0/control/uefi.crt",
394+ "1.0/empty.ko.sig", "1.0/control/kmod.x509",
395+ "1.0/empty.opal.sig", "1.0/control/opal.x509",
396+ ]))
397
398 def test_options_tarball_signed_only(self):
399 # Specifying the "tarball" option should create an tarball in
400@@ -382,20 +443,26 @@
401 # original files.
402 self.setUpUefiKeys()
403 self.setUpKmodKeys()
404+ self.setUpOpalKeys()
405 self.openArchive("test", "1.0", "amd64")
406 self.tarfile.add_file("1.0/control/options",
407 "tarball\nsigned-only")
408 self.tarfile.add_file("1.0/empty.efi", "")
409 self.tarfile.add_file("1.0/empty.ko", "")
410+ self.tarfile.add_file("1.0/empty.opal", "")
411 self.process_emulate()
412+ self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([
413+ "1.0/SHA256SUMS",
414+ "1.0/signed.tar.gz",
415+ ]))
416 tarfilename = os.path.join(self.getSignedPath("test", "amd64"),
417 "1.0", "signed.tar.gz")
418- self.assertTrue(os.path.exists(tarfilename))
419 with tarfile.open(tarfilename) as tarball:
420 self.assertContentEqual([
421- '1.0', '1.0/control', '1.0/control/kmod.x509',
422- '1.0/control/uefi.crt', '1.0/empty.efi.signed',
423- '1.0/empty.ko.sig', '1.0/control/options',
424+ '1.0', '1.0/control', '1.0/control/options',
425+ '1.0/empty.efi.signed', '1.0/control/uefi.crt',
426+ '1.0/empty.ko.sig', '1.0/control/kmod.x509',
427+ '1.0/empty.opal.sig', '1.0/control/opal.x509',
428 ], tarball.getnames())
429
430 def test_no_signed_files(self):
431@@ -512,7 +579,7 @@
432 upload.generateKmodKeys = FakeMethod()
433 upload.setTargetDirectory(
434 self.archive, "test_1.0_amd64.tar.gz", "distroseries")
435- upload.signUefi('t.ko')
436+ upload.signKmod('t.ko')
437 self.assertEqual(0, fake_call.call_count)
438 self.assertEqual(0, upload.generateKmodKeys.call_count)
439
440@@ -547,6 +614,72 @@
441 ]
442 self.assertEqual(expected_cmd, args)
443
444+ def test_correct_opal_signing_command_executed(self):
445+ # Check that calling signOpal() will generate the expected command
446+ # when appropriate keys are present.
447+ self.setUpOpalKeys()
448+ fake_call = FakeMethod(result=0)
449+ self.useFixture(MonkeyPatch("subprocess.call", fake_call))
450+ upload = SigningUpload()
451+ upload.generateOpalKeys = FakeMethod()
452+ upload.setTargetDirectory(
453+ self.archive, "test_1.0_amd64.tar.gz", "distroseries")
454+ upload.signOpal('t.opal')
455+ self.assertEqual(1, fake_call.call_count)
456+ # Assert command form.
457+ args = fake_call.calls[0][0][0]
458+ expected_cmd = [
459+ 'kmodsign', '-D', 'sha512', self.opal_pem, self.opal_x509,
460+ 't.opal', 't.opal.sig'
461+ ]
462+ self.assertEqual(expected_cmd, args)
463+ self.assertEqual(0, upload.generateOpalKeys.call_count)
464+
465+ def test_correct_opal_signing_command_executed_no_keys(self):
466+ # Check that calling signOpal() will generate no commands when
467+ # no keys are present.
468+ self.setUpOpalKeys(create=False)
469+ fake_call = FakeMethod(result=0)
470+ self.useFixture(MonkeyPatch("subprocess.call", fake_call))
471+ upload = SigningUpload()
472+ upload.generateOpalKeys = FakeMethod()
473+ upload.setTargetDirectory(
474+ self.archive, "test_1.0_amd64.tar.gz", "distroseries")
475+ upload.signOpal('t.opal')
476+ self.assertEqual(0, fake_call.call_count)
477+ self.assertEqual(0, upload.generateOpalKeys.call_count)
478+
479+ def test_correct_opal_keygen_command_executed(self):
480+ # Check that calling generateOpalKeys() will generate the
481+ # expected command.
482+ self.setUpPPA()
483+ self.setUpOpalKeys(create=False)
484+ fake_call = FakeMethod(result=0)
485+ self.useFixture(MonkeyPatch("subprocess.call", fake_call))
486+ upload = SigningUpload()
487+ upload.setTargetDirectory(
488+ self.archive, "test_1.0_amd64.tar.gz", "distroseries")
489+ upload.generateOpalKeys()
490+ self.assertEqual(2, fake_call.call_count)
491+ # Assert the actual command matches.
492+ args = fake_call.calls[0][0][0]
493+ # Sanitise the keygen tmp file.
494+ if args[11].endswith('.keygen'):
495+ args[11] = 'XXX.keygen'
496+ expected_cmd = [
497+ 'openssl', 'req', '-new', '-nodes', '-utf8', '-sha512',
498+ '-days', '3650', '-batch', '-x509',
499+ '-config', 'XXX.keygen', '-outform', 'PEM',
500+ '-out', self.opal_pem, '-keyout', self.opal_pem
501+ ]
502+ self.assertEqual(expected_cmd, args)
503+ args = fake_call.calls[1][0][0]
504+ expected_cmd = [
505+ 'openssl', 'x509', '-in', self.opal_pem, '-outform', 'DER',
506+ '-out', self.opal_x509
507+ ]
508+ self.assertEqual(expected_cmd, args)
509+
510 def test_signs_uefi_image(self):
511 # Each image in the tarball is signed.
512 self.setUpUefiKeys()
513@@ -563,6 +696,14 @@
514 upload = self.process()
515 self.assertEqual(1, upload.signKmod.call_count)
516
517+ def test_signs_opal_image(self):
518+ # Each image in the tarball is signed.
519+ self.setUpOpalKeys()
520+ self.openArchive("test", "1.0", "amd64")
521+ self.tarfile.add_file("1.0/empty.opal", "")
522+ upload = self.process()
523+ self.assertEqual(1, upload.signOpal.call_count)
524+
525 def test_signs_combo_image(self):
526 # Each image in the tarball is signed.
527 self.setUpKmodKeys()
528@@ -570,9 +711,13 @@
529 self.tarfile.add_file("1.0/empty.efi", "")
530 self.tarfile.add_file("1.0/empty.ko", "")
531 self.tarfile.add_file("1.0/empty2.ko", "")
532+ self.tarfile.add_file("1.0/empty.opal", "")
533+ self.tarfile.add_file("1.0/empty2.opal", "")
534+ self.tarfile.add_file("1.0/empty3.opal", "")
535 upload = self.process()
536 self.assertEqual(1, upload.signUefi.call_count)
537 self.assertEqual(2, upload.signKmod.call_count)
538+ self.assertEqual(3, upload.signOpal.call_count)
539
540 def test_installed(self):
541 # Files in the tarball are installed correctly.
542@@ -681,14 +826,53 @@
543 self.assertEqual(stat.S_IMODE(os.stat(self.kmod_pem).st_mode), 0o600)
544 self.assertEqual(stat.S_IMODE(os.stat(self.kmod_x509).st_mode), 0o644)
545
546+ def test_create_opal_keys_autokey_off(self):
547+ # Keys are not created.
548+ self.setUpOpalKeys(create=False)
549+ self.assertFalse(os.path.exists(self.opal_pem))
550+ self.assertFalse(os.path.exists(self.opal_x509))
551+ fake_call = FakeMethod(result=0)
552+ self.useFixture(MonkeyPatch("subprocess.call", fake_call))
553+ upload = SigningUpload()
554+ upload.callLog = FakeMethodCallLog(upload=upload)
555+ upload.setTargetDirectory(
556+ self.archive, "test_1.0_amd64.tar.gz", "distroseries")
557+ upload.signOpal(os.path.join(self.makeTemporaryDirectory(), 't.opal'))
558+ self.assertEqual(0, upload.callLog.caller_count('Opal keygen key'))
559+ self.assertEqual(0, upload.callLog.caller_count('Opal keygen cert'))
560+ self.assertFalse(os.path.exists(self.opal_pem))
561+ self.assertFalse(os.path.exists(self.opal_x509))
562+
563+ def test_create_opal_keys_autokey_on(self):
564+ # Keys are created on demand.
565+ self.setUpPPA()
566+ self.setUpOpalKeys(create=False)
567+ self.assertFalse(os.path.exists(self.opal_pem))
568+ self.assertFalse(os.path.exists(self.opal_x509))
569+ fake_call = FakeMethod(result=0)
570+ self.useFixture(MonkeyPatch("subprocess.call", fake_call))
571+ upload = SigningUpload()
572+ upload.callLog = FakeMethodCallLog(upload=upload)
573+ upload.setTargetDirectory(
574+ self.archive, "test_1.0_amd64.tar.gz", "distroseries")
575+ upload.signOpal(os.path.join(self.makeTemporaryDirectory(), 't.opal'))
576+ self.assertEqual(1, upload.callLog.caller_count('Opal keygen key'))
577+ self.assertEqual(1, upload.callLog.caller_count('Opal keygen cert'))
578+ self.assertTrue(os.path.exists(self.opal_pem))
579+ self.assertTrue(os.path.exists(self.opal_x509))
580+ self.assertEqual(stat.S_IMODE(os.stat(self.opal_pem).st_mode), 0o600)
581+ self.assertEqual(stat.S_IMODE(os.stat(self.opal_x509).st_mode), 0o644)
582+
583 def test_checksumming_tree(self):
584 # Specifying no options should leave us with an open tree,
585 # confirm it is checksummed.
586 self.setUpUefiKeys()
587 self.setUpKmodKeys()
588+ self.setUpOpalKeys()
589 self.openArchive("test", "1.0", "amd64")
590 self.tarfile.add_file("1.0/empty.efi", "")
591 self.tarfile.add_file("1.0/empty.ko", "")
592+ self.tarfile.add_file("1.0/empty.opal", "")
593 self.process_emulate()
594 sha256file = os.path.join(self.getSignedPath("test", "amd64"),
595 "1.0", "SHA256SUMS")
596@@ -702,14 +886,45 @@
597 yield self.setUpArchiveKey()
598 self.setUpUefiKeys()
599 self.setUpKmodKeys()
600- self.openArchive("test", "1.0", "amd64")
601- self.tarfile.add_file("1.0/empty.efi", "")
602- self.tarfile.add_file("1.0/empty.ko", "")
603- self.process_emulate()
604- sha256file = os.path.join(self.getSignedPath("test", "amd64"),
605- "1.0", "SHA256SUMS")
606- self.assertTrue(os.path.exists(sha256file))
607- self.assertTrue(os.path.exists(sha256file + '.gpg'))
608+ self.setUpOpalKeys()
609+ self.openArchive("test", "1.0", "amd64")
610+ self.tarfile.add_file("1.0/empty.efi", "")
611+ self.tarfile.add_file("1.0/empty.ko", "")
612+ self.tarfile.add_file("1.0/empty.opal", "")
613+ self.process_emulate()
614+ sha256file = os.path.join(self.getSignedPath("test", "amd64"),
615+ "1.0", "SHA256SUMS")
616+ self.assertTrue(os.path.exists(sha256file))
617+ self.assertTrue(os.path.exists(sha256file + '.gpg'))
618+
619+ @defer.inlineCallbacks
620+ def test_checksumming_tree_signed_options_tarball(self):
621+ # Specifying no options should leave us with an open tree,
622+ # confirm it is checksummed. Supply an archive signing key
623+ # which should trigger signing of the checksum file.
624+ yield self.setUpArchiveKey()
625+ self.setUpUefiKeys()
626+ self.setUpKmodKeys()
627+ self.setUpOpalKeys()
628+ self.openArchive("test", "1.0", "amd64")
629+ self.tarfile.add_file("1.0/control/options", "tarball")
630+ self.tarfile.add_file("1.0/empty.efi", "")
631+ self.tarfile.add_file("1.0/empty.ko", "")
632+ self.tarfile.add_file("1.0/empty.opal", "")
633+ self.process_emulate()
634+ sha256file = os.path.join(self.getSignedPath("test", "amd64"),
635+ "1.0", "SHA256SUMS")
636+ self.assertTrue(os.path.exists(sha256file))
637+ self.assertTrue(os.path.exists(sha256file + '.gpg'))
638+
639+ tarfilename = os.path.join(self.getSignedPath("test", "amd64"),
640+ "1.0", "signed.tar.gz")
641+ with tarfile.open(tarfilename) as tarball:
642+ self.assertThat(tarball.getnames(), MatchesAll(*[
643+ Not(Contains(name)) for name in [
644+ "1.0/SHA256SUMS", "1.0/SHA256SUMS.gpg",
645+ "1.0/signed.tar.gz",
646+ ]]))
647
648
649 class TestUefi(TestSigningHelpers):