Merge lp:~apw/launchpad/signing-opal into lp:launchpad
- signing-opal
- Merge into devel
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 |
Related bugs: |
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 : | # |
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): |
Reworked the signing validation to be more comprehensive. Cleaned up some lint. Added an additional noted missing test for checksums in tarball mode.