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

Proposed by Andy Whitcroft
Status: Superseded
Proposed branch: lp:~apw/launchpad/signing-fit
Merge into: lp:launchpad
Diff against target: 876 lines (+442/-35)
2 files modified
lib/lp/archivepublisher/signing.py (+81/-26)
lib/lp/archivepublisher/tests/test_signing.py (+361/-9)
To merge this branch: bzr merge lp:~apw/launchpad/signing-fit
Reviewer Review Type Date Requested Status
Launchpad code reviewers Pending
Review via email: mp+368276@code.launchpad.net

This proposal has been superseded by a proposal from 2019-06-03.

Commit message

Add u-boot Flat Image Tree signing support. u-boot supports generation and verification of FIT images in a similar fashion to secure boot. These signatures are expressed as signature nodes in the DTB contained within the FIT image. Add support for performing FIT signing against *.fit files in the signing custom uploads.

To post a comment you must log in.

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 2018-08-03 16:10:41 +0000
3+++ lib/lp/archivepublisher/signing.py 2019-06-03 14:59:55 +0000
4@@ -95,6 +95,10 @@
5 self.kmod_x509 = None
6 self.opal_pem = None
7 self.opal_x509 = None
8+ self.sipl_pem = None
9+ self.sipl_x509 = None
10+ self.fit_key = None
11+ self.fit_cert = None
12 self.autokey = False
13 else:
14 self.uefi_key = os.path.join(pubconf.signingroot, "uefi.key")
15@@ -103,6 +107,13 @@
16 self.kmod_x509 = os.path.join(pubconf.signingroot, "kmod.x509")
17 self.opal_pem = os.path.join(pubconf.signingroot, "opal.pem")
18 self.opal_x509 = os.path.join(pubconf.signingroot, "opal.x509")
19+ self.sipl_pem = os.path.join(pubconf.signingroot, "sipl.pem")
20+ self.sipl_x509 = os.path.join(pubconf.signingroot, "sipl.x509")
21+ # Note: the signature tool allows a collection of keys and takes
22+ # a directory name with all valid keys. Avoid mixing the
23+ # other signing types' keys with the fit keys.
24+ self.fit_key = os.path.join(pubconf.signingroot, "fit", "fit.key")
25+ self.fit_cert = os.path.join(pubconf.signingroot, "fit", "fit.crt")
26 self.autokey = pubconf.signingautokey
27
28 self.setComponents(tarfile_path)
29@@ -176,6 +187,10 @@
30 yield (os.path.join(dirpath, filename), self.signKmod)
31 elif filename.endswith(".opal"):
32 yield (os.path.join(dirpath, filename), self.signOpal)
33+ elif filename.endswith(".sipl"):
34+ yield (os.path.join(dirpath, filename), self.signSipl)
35+ elif filename.endswith(".fit"):
36+ yield (os.path.join(dirpath, filename), self.signFit)
37
38 def getKeys(self, which, generate, *keynames):
39 """Validate and return the uefi key and cert for encryption."""
40@@ -207,29 +222,33 @@
41 common_name = "PPA %s %s" % (owner, archive)
42 return common_name[0:64 - len(suffix)] + suffix
43
44- def generateUefiKeys(self):
45- """Generate new UEFI Keys for this archive."""
46- directory = os.path.dirname(self.uefi_key)
47+ def generateKeyCrtPair(self, key_type, key_filename, cert_filename):
48+ """Generate new Key/Crt key pairs."""
49+ directory = os.path.dirname(key_filename)
50 if not os.path.exists(directory):
51 os.makedirs(directory)
52
53 common_name = self.generateKeyCommonName(
54- self.archive.owner.name, self.archive.name)
55+ self.archive.owner.name, self.archive.name, key_type)
56 subject = '/CN=' + common_name + '/'
57
58 old_mask = os.umask(0o077)
59 try:
60 new_key_cmd = [
61 'openssl', 'req', '-new', '-x509', '-newkey', 'rsa:2048',
62- '-subj', subject, '-keyout', self.uefi_key,
63- '-out', self.uefi_cert, '-days', '3650', '-nodes', '-sha256',
64+ '-subj', subject, '-keyout', key_filename,
65+ '-out', cert_filename, '-days', '3650', '-nodes', '-sha256',
66 ]
67- self.callLog("UEFI keygen", new_key_cmd)
68+ self.callLog(key_type + " keygen", new_key_cmd)
69 finally:
70 os.umask(old_mask)
71
72- if os.path.exists(self.uefi_cert):
73- os.chmod(self.uefi_cert, 0o644)
74+ if os.path.exists(cert_filename):
75+ os.chmod(cert_filename, 0o644)
76+
77+ def generateUefiKeys(self):
78+ """Generate new UEFI Keys for this archive."""
79+ self.generateKeyCrtPair("UEFI", self.uefi_key, self.uefi_cert)
80
81 def signUefi(self, image):
82 """Attempt to sign an image."""
83@@ -242,7 +261,7 @@
84 cmdl = ["sbsign", "--key", key, "--cert", cert, image]
85 return self.callLog("UEFI signing", cmdl)
86
87- openssl_config_opal = textwrap.dedent("""
88+ openssl_config_base = textwrap.dedent("""
89 [ req ]
90 default_bits = 4096
91 distinguished_name = req_distinguished_name
92@@ -260,37 +279,35 @@
93 authorityKeyIdentifier=keyid
94 """)
95
96- openssl_config_kmod = openssl_config_opal + textwrap.dedent("""
97+ openssl_config_opal = "# OPAL openssl config" + openssl_config_base
98+
99+ openssl_config_kmod = "# KMOD openssl config" + openssl_config_base + \
100+ textwrap.dedent("""
101 # codeSigning: specifies that this key is used to sign code.
102 # 1.3.6.1.4.1.2312.16.1.2: defines this key as used for
103 # module signing only. See https://lkml.org/lkml/2015/8/26/741.
104 extendedKeyUsage = codeSigning,1.3.6.1.4.1.2312.16.1.2
105 """)
106
107- def generateOpensslConfig(self, key_type, common_name):
108- if key_type == 'Kmod':
109- genkey_tmpl = self.openssl_config_kmod
110- elif key_type == 'Opal':
111- genkey_tmpl = self.openssl_config_opal
112- else:
113- raise ValueError("unknown key_type " + key_type)
114+ openssl_config_sipl = "# SIPL openssl config" + openssl_config_base
115+
116+ def generateOpensslConfig(self, key_type, genkey_tmpl):
117+ # Truncate name to 64 character maximum.
118+ common_name = self.generateKeyCommonName(
119+ self.archive.owner.name, self.archive.name, key_type)
120
121 return genkey_tmpl.format(common_name=common_name)
122
123- def generatePemX509Pair(self, key_type, pem_filename, x509_filename):
124+ def generatePemX509Pair(self, key_type, genkey_text, pem_filename,
125+ x509_filename):
126 """Generate new pem/x509 key pairs."""
127 directory = os.path.dirname(pem_filename)
128 if not os.path.exists(directory):
129 os.makedirs(directory)
130
131- # Truncate name to 64 character maximum.
132- common_name = self.generateKeyCommonName(
133- self.archive.owner.name, self.archive.name, key_type)
134-
135 old_mask = os.umask(0o077)
136 try:
137 with tempfile.NamedTemporaryFile(suffix='.keygen') as tf:
138- genkey_text = self.generateOpensslConfig(key_type, common_name)
139 print(genkey_text, file=tf)
140
141 # Close out the underlying file so we know it is complete.
142@@ -318,7 +335,8 @@
143
144 def generateKmodKeys(self):
145 """Generate new Kernel Signing Keys for this archive."""
146- self.generatePemX509Pair("Kmod", self.kmod_pem, self.kmod_x509)
147+ config = self.generateOpensslConfig("Kmod", self.openssl_config_kmod)
148+ self.generatePemX509Pair("Kmod", config, self.kmod_pem, self.kmod_x509)
149
150 def signKmod(self, image):
151 """Attempt to sign a kernel module."""
152@@ -333,7 +351,8 @@
153
154 def generateOpalKeys(self):
155 """Generate new Opal Signing Keys for this archive."""
156- self.generatePemX509Pair("Opal", self.opal_pem, self.opal_x509)
157+ config = self.generateOpensslConfig("Opal", self.openssl_config_opal)
158+ self.generatePemX509Pair("Opal", config, self.opal_pem, self.opal_x509)
159
160 def signOpal(self, image):
161 """Attempt to sign a kernel image for Opal."""
162@@ -346,6 +365,42 @@
163 cmdl = ["kmodsign", "-D", "sha512", pem, cert, image, image + ".sig"]
164 return self.callLog("Opal signing", cmdl)
165
166+ def generateSiplKeys(self):
167+ """Generate new Sipl Signing Keys for this archive."""
168+ config = self.generateOpensslConfig("Sipl", self.openssl_config_sipl)
169+ self.generatePemX509Pair("Sipl", config, self.sipl_pem, self.sipl_x509)
170+
171+ def signSipl(self, image):
172+ """Attempt to sign a kernel image for Sipl."""
173+ remove_if_exists("%s.sig" % image)
174+ (pem, cert) = self.getKeys('Sipl Kernel', self.generateSiplKeys,
175+ self.sipl_pem, self.sipl_x509)
176+ if not pem or not cert:
177+ return
178+ self.publishPublicKey(cert)
179+ cmdl = ["kmodsign", "-D", "sha512", pem, cert, image, image + ".sig"]
180+ return self.callLog("Sipl signing", cmdl)
181+
182+ def generateFitKeys(self):
183+ """Generate new Fit Keys for this archive."""
184+ self.generateKeyCrtPair("Fit", self.fit_key, self.fit_cert)
185+
186+ def signFit(self, image):
187+ """Attempt to sign an image."""
188+ image_signed = "%s.signed" % image
189+ remove_if_exists(image_signed)
190+ (key, cert) = self.getKeys('Fit', self.generateFitKeys,
191+ self.fit_key, self.fit_cert)
192+ if not key or not cert:
193+ return
194+ self.publishPublicKey(cert)
195+ # Make a copy of the image as mkimage signs in place and in
196+ # signed-only mode we will remove the original file.
197+ shutil.copy(image, image_signed)
198+ cmdl = ["mkimage", "-F", "-k", os.path.dirname(key), "-r",
199+ image_signed]
200+ return self.callLog("Fit signing", cmdl)
201+
202 def convertToTarball(self):
203 """Convert unpacked output to signing tarball."""
204 tarfilename = os.path.join(self.tmpdir, "signed.tar.gz")
205
206=== modified file 'lib/lp/archivepublisher/tests/test_signing.py'
207--- lib/lp/archivepublisher/tests/test_signing.py 2019-05-24 11:10:38 +0000
208+++ lib/lp/archivepublisher/tests/test_signing.py 2019-06-03 14:59:55 +0000
209@@ -85,12 +85,17 @@
210 self.callers = {
211 "UEFI signing": 0,
212 "UEFI keygen": 0,
213+ "Fit signing": 0,
214+ "Fit keygen": 0,
215 "Kmod signing": 0,
216 "Kmod keygen key": 0,
217 "Kmod keygen cert": 0,
218 "Opal signing": 0,
219 "Opal keygen key": 0,
220 "Opal keygen cert": 0,
221+ "Sipl signing": 0,
222+ "Sipl keygen key": 0,
223+ "Sipl keygen cert": 0,
224 }
225
226 def __call__(self, *args, **kwargs):
227@@ -108,6 +113,15 @@
228 write_file(self.upload.uefi_key, b"")
229 write_file(self.upload.uefi_cert, b"")
230
231+ elif description == "Fit signing":
232+ filename = cmdl[-1]
233+ if filename.endswith(".fit"):
234+ write_file(filename + ".signed", b"")
235+
236+ elif description == "Fit keygen":
237+ write_file(self.upload.fit_key, b"")
238+ write_file(self.upload.fit_cert, b"")
239+
240 elif description == "Kmod signing":
241 filename = cmdl[-1]
242 if filename.endswith(".ko.sig"):
243@@ -130,9 +144,20 @@
244 elif description == "Opal keygen key":
245 write_file(self.upload.opal_pem, b"")
246
247+ elif description == "Sipl signing":
248+ filename = cmdl[-1]
249+ if filename.endswith(".sipl.sig"):
250+ write_file(filename, b"")
251+
252+ elif description == "Sipl keygen cert":
253+ write_file(self.upload.sipl_x509, b"")
254+
255+ elif description == "Sipl keygen key":
256+ write_file(self.upload.sipl_pem, b"")
257+
258 else:
259- raise AssertionError("unknown command executed cmd=(%s)" %
260- " ".join(cmdl))
261+ raise AssertionError("unknown command executed description=(%s) "
262+ "cmd=(%s)" % (description, " ".join(cmdl)))
263
264 return 0
265
266@@ -177,7 +202,7 @@
267 purpose=ArchivePurpose.PPA)
268 self.signing_dir = os.path.join(
269 self.temp_dir, "signing", "signing-owner", "testing")
270- self.testcase_cn = '/CN=PPA signing-owner testing/'
271+ self.testcase_cn = 'PPA signing-owner testing'
272 pubconf = getPubConfig(self.archive)
273 if not os.path.exists(pubconf.temproot):
274 os.makedirs(pubconf.temproot)
275@@ -197,6 +222,15 @@
276 write_file(self.key, b"")
277 write_file(self.cert, b"")
278
279+ def setUpFitKeys(self, create=True):
280+ # We expect and need the fit keys to be in their own
281+ # directory as part of key protection for mkimage.
282+ self.fit_key = os.path.join(self.signing_dir, "fit", "fit.key")
283+ self.fit_cert = os.path.join(self.signing_dir, "fit", "fit.crt")
284+ if create:
285+ write_file(self.fit_key, b"")
286+ write_file(self.fit_cert, b"")
287+
288 def setUpKmodKeys(self, create=True):
289 self.kmod_pem = os.path.join(self.signing_dir, "kmod.pem")
290 self.kmod_x509 = os.path.join(self.signing_dir, "kmod.x509")
291@@ -211,6 +245,13 @@
292 write_file(self.opal_pem, b"")
293 write_file(self.opal_x509, b"")
294
295+ def setUpSiplKeys(self, create=True):
296+ self.sipl_pem = os.path.join(self.signing_dir, "sipl.pem")
297+ self.sipl_x509 = os.path.join(self.signing_dir, "sipl.x509")
298+ if create:
299+ write_file(self.sipl_pem, b"")
300+ write_file(self.sipl_x509, b"")
301+
302 def openArchive(self, loader_type, version, arch):
303 self.path = os.path.join(
304 self.temp_dir, "%s_%s_%s.tar.gz" % (loader_type, version, arch))
305@@ -234,6 +275,7 @@
306 upload = SigningUpload()
307 # Under no circumstances is it safe to execute actual commands.
308 self.fake_call = FakeMethod(result=0)
309+ self.fake_copyfile = FakeMethod(result=0)
310 upload.callLog = FakeMethodCallLog(upload=upload)
311 self.useFixture(MonkeyPatch("subprocess.call", self.fake_call))
312 upload.process(self.archive, self.path, self.suite)
313@@ -247,6 +289,8 @@
314 upload.signUefi = FakeMethod()
315 upload.signKmod = FakeMethod()
316 upload.signOpal = FakeMethod()
317+ upload.signSipl = FakeMethod()
318+ upload.signFit = FakeMethod()
319 # Under no circumstances is it safe to execute actual commands.
320 fake_call = FakeMethod(result=0)
321 self.useFixture(MonkeyPatch("subprocess.call", fake_call))
322@@ -267,6 +311,8 @@
323 self.tarfile.add_file("1.0/empty.efi", b"")
324 self.tarfile.add_file("1.0/empty.ko", b"")
325 self.tarfile.add_file("1.0/empty.opal", b"")
326+ self.tarfile.add_file("1.0/empty.sipl", b"")
327+ self.tarfile.add_file("1.0/empty.fit", b"")
328 upload = self.process_emulate()
329 self.assertContentEqual([], upload.callLog.caller_list())
330
331@@ -277,6 +323,8 @@
332 self.tarfile.add_file("1.0/empty.efi", b"")
333 self.tarfile.add_file("1.0/empty.ko", b"")
334 self.tarfile.add_file("1.0/empty.opal", b"")
335+ self.tarfile.add_file("1.0/empty.sipl", b"")
336+ self.tarfile.add_file("1.0/empty.fit", b"")
337 upload = self.process_emulate()
338 self.assertContentEqual([], upload.callLog.caller_list())
339
340@@ -289,6 +337,8 @@
341 self.tarfile.add_file("1.0/empty.efi", b"")
342 self.tarfile.add_file("1.0/empty.ko", b"")
343 self.tarfile.add_file("1.0/empty.opal", b"")
344+ self.tarfile.add_file("1.0/empty.sipl", b"")
345+ self.tarfile.add_file("1.0/empty.fit", b"")
346 upload = self.process_emulate()
347 expected_callers = [
348 ('UEFI signing', 1),
349@@ -304,6 +354,8 @@
350 self.tarfile.add_file("1.0/empty.efi", b"")
351 self.tarfile.add_file("1.0/empty.ko", b"")
352 self.tarfile.add_file("1.0/empty.opal", b"")
353+ self.tarfile.add_file("1.0/empty.sipl", b"")
354+ self.tarfile.add_file("1.0/empty.fit", b"")
355 upload = self.process_emulate()
356 expected_callers = [
357 ('UEFI keygen', 1),
358@@ -311,9 +363,14 @@
359 ('Kmod keygen cert', 1),
360 ('Opal keygen key', 1),
361 ('Opal keygen cert', 1),
362+ ('Sipl keygen key', 1),
363+ ('Sipl keygen cert', 1),
364+ ('Fit keygen', 1),
365 ('UEFI signing', 1),
366 ('Kmod signing', 1),
367 ('Opal signing', 1),
368+ ('Sipl signing', 1),
369+ ('Fit signing', 1),
370 ]
371 self.assertContentEqual(expected_callers, upload.callLog.caller_list())
372
373@@ -387,16 +444,22 @@
374 self.setUpUefiKeys()
375 self.setUpKmodKeys()
376 self.setUpOpalKeys()
377+ self.setUpSiplKeys()
378+ self.setUpFitKeys()
379 self.openArchive("test", "1.0", "amd64")
380 self.tarfile.add_file("1.0/empty.efi", b"")
381 self.tarfile.add_file("1.0/empty.ko", b"")
382 self.tarfile.add_file("1.0/empty.opal", b"")
383+ self.tarfile.add_file("1.0/empty.sipl", b"")
384+ self.tarfile.add_file("1.0/empty.fit", b"")
385 self.process_emulate()
386 self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([
387 "1.0/SHA256SUMS",
388 "1.0/empty.efi", "1.0/empty.efi.signed", "1.0/control/uefi.crt",
389 "1.0/empty.ko", "1.0/empty.ko.sig", "1.0/control/kmod.x509",
390 "1.0/empty.opal", "1.0/empty.opal.sig", "1.0/control/opal.x509",
391+ "1.0/empty.sipl", "1.0/empty.sipl.sig", "1.0/control/sipl.x509",
392+ "1.0/empty.fit", "1.0/empty.fit.signed", "1.0/control/fit.crt",
393 ]))
394
395 def test_options_tarball(self):
396@@ -405,11 +468,15 @@
397 self.setUpUefiKeys()
398 self.setUpKmodKeys()
399 self.setUpOpalKeys()
400+ self.setUpSiplKeys()
401+ self.setUpFitKeys()
402 self.openArchive("test", "1.0", "amd64")
403 self.tarfile.add_file("1.0/control/options", b"tarball")
404 self.tarfile.add_file("1.0/empty.efi", b"")
405 self.tarfile.add_file("1.0/empty.ko", b"")
406 self.tarfile.add_file("1.0/empty.opal", b"")
407+ self.tarfile.add_file("1.0/empty.sipl", b"")
408+ self.tarfile.add_file("1.0/empty.fit", b"")
409 self.process_emulate()
410 self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([
411 "1.0/SHA256SUMS",
412@@ -425,6 +492,10 @@
413 '1.0/empty.ko', '1.0/empty.ko.sig', '1.0/control/kmod.x509',
414 '1.0/empty.opal', '1.0/empty.opal.sig',
415 '1.0/control/opal.x509',
416+ '1.0/empty.sipl', '1.0/empty.sipl.sig',
417+ '1.0/control/sipl.x509',
418+ '1.0/empty.fit', '1.0/empty.fit.signed',
419+ '1.0/control/fit.crt',
420 ], tarball.getnames())
421
422 def test_options_signed_only(self):
423@@ -433,17 +504,23 @@
424 self.setUpUefiKeys()
425 self.setUpKmodKeys()
426 self.setUpOpalKeys()
427+ self.setUpSiplKeys()
428+ self.setUpFitKeys()
429 self.openArchive("test", "1.0", "amd64")
430 self.tarfile.add_file("1.0/control/options", b"signed-only")
431 self.tarfile.add_file("1.0/empty.efi", b"")
432 self.tarfile.add_file("1.0/empty.ko", b"")
433 self.tarfile.add_file("1.0/empty.opal", b"")
434+ self.tarfile.add_file("1.0/empty.sipl", b"")
435+ self.tarfile.add_file("1.0/empty.fit", b"")
436 self.process_emulate()
437 self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([
438 "1.0/SHA256SUMS", "1.0/control/options",
439 "1.0/empty.efi.signed", "1.0/control/uefi.crt",
440 "1.0/empty.ko.sig", "1.0/control/kmod.x509",
441 "1.0/empty.opal.sig", "1.0/control/opal.x509",
442+ "1.0/empty.sipl.sig", "1.0/control/sipl.x509",
443+ "1.0/empty.fit.signed", "1.0/control/fit.crt",
444 ]))
445
446 def test_options_tarball_signed_only(self):
447@@ -453,11 +530,15 @@
448 self.setUpUefiKeys()
449 self.setUpKmodKeys()
450 self.setUpOpalKeys()
451+ self.setUpSiplKeys()
452+ self.setUpFitKeys()
453 self.openArchive("test", "1.0", "amd64")
454 self.tarfile.add_file("1.0/control/options", b"tarball\nsigned-only")
455 self.tarfile.add_file("1.0/empty.efi", b"")
456 self.tarfile.add_file("1.0/empty.ko", b"")
457 self.tarfile.add_file("1.0/empty.opal", b"")
458+ self.tarfile.add_file("1.0/empty.sipl", b"")
459+ self.tarfile.add_file("1.0/empty.fit", b"")
460 self.process_emulate()
461 self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([
462 "1.0/SHA256SUMS",
463@@ -471,12 +552,18 @@
464 '1.0/empty.efi.signed', '1.0/control/uefi.crt',
465 '1.0/empty.ko.sig', '1.0/control/kmod.x509',
466 '1.0/empty.opal.sig', '1.0/control/opal.x509',
467+ '1.0/empty.sipl.sig', '1.0/control/sipl.x509',
468+ '1.0/empty.fit.signed', '1.0/control/fit.crt',
469 ], tarball.getnames())
470
471 def test_no_signed_files(self):
472 # Tarballs containing no *.efi files are extracted without complaint.
473 # Nothing is signed.
474 self.setUpUefiKeys()
475+ self.setUpKmodKeys()
476+ self.setUpOpalKeys()
477+ self.setUpSiplKeys()
478+ self.setUpFitKeys()
479 self.openArchive("empty", "1.0", "amd64")
480 self.tarfile.add_file("1.0/hello", b"world")
481 upload = self.process()
482@@ -484,6 +571,9 @@
483 self.getSignedPath("empty", "amd64"), "1.0", "hello")))
484 self.assertEqual(0, upload.signUefi.call_count)
485 self.assertEqual(0, upload.signKmod.call_count)
486+ self.assertEqual(0, upload.signOpal.call_count)
487+ self.assertEqual(0, upload.signSipl.call_count)
488+ self.assertEqual(0, upload.signFit.call_count)
489
490 def test_already_exists(self):
491 # If the target directory already exists, processing fails.
492@@ -551,7 +641,71 @@
493 args = fake_call.calls[0][0][0]
494 expected_cmd = [
495 'openssl', 'req', '-new', '-x509', '-newkey', 'rsa:2048',
496- '-subj', self.testcase_cn, '-keyout', self.key, '-out', self.cert,
497+ '-subj', '/CN=' + self.testcase_cn + ' UEFI/',
498+ '-keyout', self.key, '-out', self.cert,
499+ '-days', '3650', '-nodes', '-sha256',
500+ ]
501+ self.assertEqual(expected_cmd, args)
502+
503+ def test_correct_fit_signing_command_executed(self):
504+ # Check that calling signFit() will generate the expected command
505+ # when appropriate keys are present.
506+ self.setUpFitKeys()
507+ fake_call = FakeMethod(result=0)
508+ self.useFixture(MonkeyPatch("subprocess.call", fake_call))
509+ fake_copy = FakeMethod(result=0)
510+ self.useFixture(MonkeyPatch("shutil.copy", fake_copy))
511+ upload = SigningUpload()
512+ upload.generateFitKeys = FakeMethod()
513+ upload.setTargetDirectory(
514+ self.archive, "test_1.0_amd64.tar.gz", "distroseries")
515+ upload.signFit('t.fit')
516+ # Confirm the copy was performed.
517+ self.assertEqual(1, fake_copy.call_count)
518+ args = fake_copy.calls[0][0]
519+ expected_copy = ('t.fit', 't.fit.signed')
520+ self.assertEqual(expected_copy, args)
521+ # Assert command form.
522+ args = fake_call.calls[0][0][0]
523+ expected_cmd = [
524+ 'mkimage', '-F', '-k', os.path.dirname(self.fit_key), '-r',
525+ 't.fit.signed',
526+ ]
527+ self.assertEqual(expected_cmd, args)
528+ self.assertEqual(0, upload.generateFitKeys.call_count)
529+
530+ def test_correct_fit_signing_command_executed_no_keys(self):
531+ # Check that calling signFit() will generate no commands when
532+ # no keys are present.
533+ self.setUpFitKeys(create=False)
534+ fake_call = FakeMethod(result=0)
535+ self.useFixture(MonkeyPatch("subprocess.call", fake_call))
536+ upload = SigningUpload()
537+ upload.generateFitKeys = FakeMethod()
538+ upload.setTargetDirectory(
539+ self.archive, "test_1.0_amd64.tar.gz", "distroseries")
540+ upload.signUefi('t.fit')
541+ self.assertEqual(0, fake_call.call_count)
542+ self.assertEqual(0, upload.generateFitKeys.call_count)
543+
544+ def test_correct_fit_keygen_command_executed(self):
545+ # Check that calling generateFitKeys() will generate the
546+ # expected command.
547+ self.setUpPPA()
548+ self.setUpFitKeys(create=False)
549+ fake_call = FakeMethod(result=0)
550+ self.useFixture(MonkeyPatch("subprocess.call", fake_call))
551+ upload = SigningUpload()
552+ upload.setTargetDirectory(
553+ self.archive, "test_1.0_amd64.tar.gz", "distroseries")
554+ upload.generateFitKeys()
555+ self.assertEqual(1, fake_call.call_count)
556+ # Assert the actual command matches.
557+ args = fake_call.calls[0][0][0]
558+ expected_cmd = [
559+ 'openssl', 'req', '-new', '-x509', '-newkey', 'rsa:2048',
560+ '-subj', '/CN=' + self.testcase_cn + ' Fit/',
561+ '-keyout', self.fit_key, '-out', self.fit_cert,
562 '-days', '3650', '-nodes', '-sha256',
563 ]
564 self.assertEqual(expected_cmd, args)
565@@ -559,15 +713,20 @@
566 def test_correct_kmod_openssl_config(self):
567 # Check that calling generateOpensslConfig() will return an appropriate
568 # openssl configuration.
569+ self.setUpPPA()
570 upload = SigningUpload()
571- text = upload.generateOpensslConfig('Kmod', 'something-unique')
572+ upload.setTargetDirectory(
573+ self.archive, "test_1.0_amd64.tar.gz", "distroseries")
574+ text = upload.generateOpensslConfig('Kmod', upload.openssl_config_kmod)
575
576- cn_re = re.compile(r'\bCN\s*=\s*something-unique\b')
577+ id_re = re.compile(r'^# KMOD openssl config\b')
578+ cn_re = re.compile(r'\bCN\s*=\s*' + self.testcase_cn + '\s+Kmod')
579 eku_re = re.compile(
580 r'\bextendedKeyUsage\s*=\s*'
581 r'codeSigning,1.3.6.1.4.1.2312.16.1.2\s*\b')
582
583 self.assertIn('[ req ]', text)
584+ self.assertIsNotNone(id_re.search(text))
585 self.assertIsNotNone(cn_re.search(text))
586 self.assertIsNotNone(eku_re.search(text))
587
588@@ -640,12 +799,17 @@
589 def test_correct_opal_openssl_config(self):
590 # Check that calling generateOpensslConfig() will return an appropriate
591 # openssl configuration.
592+ self.setUpPPA()
593 upload = SigningUpload()
594- text = upload.generateOpensslConfig('Opal', 'something-unique')
595+ upload.setTargetDirectory(
596+ self.archive, "test_1.0_amd64.tar.gz", "distroseries")
597+ text = upload.generateOpensslConfig('Opal', upload.openssl_config_opal)
598
599- cn_re = re.compile(r'\bCN\s*=\s*something-unique\b')
600+ id_re = re.compile(r'^# OPAL openssl config\b')
601+ cn_re = re.compile(r'\bCN\s*=\s*' + self.testcase_cn + '\s+Opal')
602
603 self.assertIn('[ req ]', text)
604+ self.assertIsNotNone(id_re.search(text))
605 self.assertIsNotNone(cn_re.search(text))
606 self.assertNotIn('extendedKeyUsage', text)
607
608@@ -715,6 +879,89 @@
609 ]
610 self.assertEqual(expected_cmd, args)
611
612+ def test_correct_sipl_openssl_config(self):
613+ # Check that calling generateOpensslConfig() will return an appropriate
614+ # openssl configuration.
615+ self.setUpPPA()
616+ upload = SigningUpload()
617+ upload.setTargetDirectory(
618+ self.archive, "test_1.0_amd64.tar.gz", "distroseries")
619+ text = upload.generateOpensslConfig('Sipl', upload.openssl_config_sipl)
620+
621+ id_re = re.compile(r'^# SIPL openssl config\b')
622+ cn_re = re.compile(r'\bCN\s*=\s*' + self.testcase_cn + '\s+Sipl')
623+
624+ self.assertIn('[ req ]', text)
625+ self.assertIsNotNone(id_re.search(text))
626+ self.assertIsNotNone(cn_re.search(text))
627+ self.assertNotIn('extendedKeyUsage', text)
628+
629+ def test_correct_sipl_signing_command_executed(self):
630+ # Check that calling signSipl() will generate the expected command
631+ # when appropriate keys are present.
632+ self.setUpSiplKeys()
633+ fake_call = FakeMethod(result=0)
634+ self.useFixture(MonkeyPatch("subprocess.call", fake_call))
635+ upload = SigningUpload()
636+ upload.generateSiplKeys = FakeMethod()
637+ upload.setTargetDirectory(
638+ self.archive, "test_1.0_amd64.tar.gz", "distroseries")
639+ upload.signSipl('t.sipl')
640+ self.assertEqual(1, fake_call.call_count)
641+ # Assert command form.
642+ args = fake_call.calls[0][0][0]
643+ expected_cmd = [
644+ 'kmodsign', '-D', 'sha512', self.sipl_pem, self.sipl_x509,
645+ 't.sipl', 't.sipl.sig'
646+ ]
647+ self.assertEqual(expected_cmd, args)
648+ self.assertEqual(0, upload.generateSiplKeys.call_count)
649+
650+ def test_correct_sipl_signing_command_executed_no_keys(self):
651+ # Check that calling signSipl() will generate no commands when
652+ # no keys are present.
653+ self.setUpSiplKeys(create=False)
654+ fake_call = FakeMethod(result=0)
655+ self.useFixture(MonkeyPatch("subprocess.call", fake_call))
656+ upload = SigningUpload()
657+ upload.generateSiplKeys = FakeMethod()
658+ upload.setTargetDirectory(
659+ self.archive, "test_1.0_amd64.tar.gz", "distroseries")
660+ upload.signOpal('t.sipl')
661+ self.assertEqual(0, fake_call.call_count)
662+ self.assertEqual(0, upload.generateSiplKeys.call_count)
663+
664+ def test_correct_sipl_keygen_command_executed(self):
665+ # Check that calling generateSiplKeys() will generate the
666+ # expected command.
667+ self.setUpPPA()
668+ self.setUpSiplKeys(create=False)
669+ fake_call = FakeMethod(result=0)
670+ self.useFixture(MonkeyPatch("subprocess.call", fake_call))
671+ upload = SigningUpload()
672+ upload.setTargetDirectory(
673+ self.archive, "test_1.0_amd64.tar.gz", "distroseries")
674+ upload.generateSiplKeys()
675+ self.assertEqual(2, fake_call.call_count)
676+ # Assert the actual command matches.
677+ args = fake_call.calls[0][0][0]
678+ # Sanitise the keygen tmp file.
679+ if args[11].endswith('.keygen'):
680+ args[11] = 'XXX.keygen'
681+ expected_cmd = [
682+ 'openssl', 'req', '-new', '-nodes', '-utf8', '-sha512',
683+ '-days', '3650', '-batch', '-x509',
684+ '-config', 'XXX.keygen', '-outform', 'PEM',
685+ '-out', self.sipl_pem, '-keyout', self.sipl_pem
686+ ]
687+ self.assertEqual(expected_cmd, args)
688+ args = fake_call.calls[1][0][0]
689+ expected_cmd = [
690+ 'openssl', 'x509', '-in', self.sipl_pem, '-outform', 'DER',
691+ '-out', self.sipl_x509
692+ ]
693+ self.assertEqual(expected_cmd, args)
694+
695 def test_signs_uefi_image(self):
696 # Each image in the tarball is signed.
697 self.setUpUefiKeys()
698@@ -723,6 +970,14 @@
699 upload = self.process()
700 self.assertEqual(1, upload.signUefi.call_count)
701
702+ def test_signs_fit_image(self):
703+ # Each image in the tarball is signed.
704+ self.setUpFitKeys()
705+ self.openArchive("test", "1.0", "amd64")
706+ self.tarfile.add_file("1.0/empty.fit", b"")
707+ upload = self.process()
708+ self.assertEqual(1, upload.signFit.call_count)
709+
710 def test_signs_kmod_image(self):
711 # Each image in the tarball is signed.
712 self.setUpKmodKeys()
713@@ -739,9 +994,16 @@
714 upload = self.process()
715 self.assertEqual(1, upload.signOpal.call_count)
716
717+ def test_signs_sipl_image(self):
718+ # Each image in the tarball is signed.
719+ self.setUpSiplKeys()
720+ self.openArchive("test", "1.0", "amd64")
721+ self.tarfile.add_file("1.0/empty.sipl", b"")
722+ upload = self.process()
723+ self.assertEqual(1, upload.signSipl.call_count)
724+
725 def test_signs_combo_image(self):
726 # Each image in the tarball is signed.
727- self.setUpKmodKeys()
728 self.openArchive("test", "1.0", "amd64")
729 self.tarfile.add_file("1.0/empty.efi", b"")
730 self.tarfile.add_file("1.0/empty.ko", b"")
731@@ -749,10 +1011,21 @@
732 self.tarfile.add_file("1.0/empty.opal", b"")
733 self.tarfile.add_file("1.0/empty2.opal", b"")
734 self.tarfile.add_file("1.0/empty3.opal", b"")
735+ self.tarfile.add_file("1.0/empty.sipl", b"")
736+ self.tarfile.add_file("1.0/empty2.sipl", b"")
737+ self.tarfile.add_file("1.0/empty3.sipl", b"")
738+ self.tarfile.add_file("1.0/empty4.sipl", b"")
739+ self.tarfile.add_file("1.0/empty.fit", b"")
740+ self.tarfile.add_file("1.0/empty2.fit", b"")
741+ self.tarfile.add_file("1.0/empty3.fit", b"")
742+ self.tarfile.add_file("1.0/empty4.fit", b"")
743+ self.tarfile.add_file("1.0/empty5.fit", b"")
744 upload = self.process()
745 self.assertEqual(1, upload.signUefi.call_count)
746 self.assertEqual(2, upload.signKmod.call_count)
747 self.assertEqual(3, upload.signOpal.call_count)
748+ self.assertEqual(4, upload.signSipl.call_count)
749+ self.assertEqual(5, upload.signFit.call_count)
750
751 def test_installed(self):
752 # Files in the tarball are installed correctly.
753@@ -824,6 +1097,43 @@
754 self.assertEqual(stat.S_IMODE(os.stat(self.key).st_mode), 0o600)
755 self.assertEqual(stat.S_IMODE(os.stat(self.cert).st_mode), 0o644)
756
757+ def test_create_fit_keys_autokey_off(self):
758+ # Keys are not created.
759+ self.setUpFitKeys(create=False)
760+ self.assertFalse(os.path.exists(self.fit_key))
761+ self.assertFalse(os.path.exists(self.fit_cert))
762+ fake_call = FakeMethod(result=0)
763+ self.useFixture(MonkeyPatch("subprocess.call", fake_call))
764+ upload = SigningUpload()
765+ upload.callLog = FakeMethodCallLog(upload=upload)
766+ upload.setTargetDirectory(
767+ self.archive, "test_1.0_amd64.tar.gz", "distroseries")
768+ upload.signFit(os.path.join(self.makeTemporaryDirectory(), 'fit'))
769+ self.assertEqual(0, upload.callLog.caller_count('Fit keygen'))
770+ self.assertFalse(os.path.exists(self.fit_key))
771+ self.assertFalse(os.path.exists(self.fit_cert))
772+
773+ def test_create_fit_keys_autokey_on(self):
774+ # Keys are created on demand.
775+ self.setUpPPA()
776+ self.setUpFitKeys(create=False)
777+ self.assertFalse(os.path.exists(self.fit_key))
778+ self.assertFalse(os.path.exists(self.fit_cert))
779+ fake_call = FakeMethod(result=0)
780+ self.useFixture(MonkeyPatch("subprocess.call", fake_call))
781+ fake_copy = FakeMethod(result=0)
782+ self.useFixture(MonkeyPatch("shutil.copy", fake_copy))
783+ upload = SigningUpload()
784+ upload.callLog = FakeMethodCallLog(upload=upload)
785+ upload.setTargetDirectory(
786+ self.archive, "test_1.0_amd64.tar.gz", "distroseries")
787+ upload.signFit(os.path.join(self.makeTemporaryDirectory(), 't.fit'))
788+ self.assertEqual(1, upload.callLog.caller_count('Fit keygen'))
789+ self.assertTrue(os.path.exists(self.fit_key))
790+ self.assertTrue(os.path.exists(self.fit_cert))
791+ self.assertEqual(stat.S_IMODE(os.stat(self.fit_key).st_mode), 0o600)
792+ self.assertEqual(stat.S_IMODE(os.stat(self.fit_cert).st_mode), 0o644)
793+
794 def test_create_kmod_keys_autokey_off(self):
795 # Keys are not created.
796 self.setUpKmodKeys(create=False)
797@@ -898,16 +1208,55 @@
798 self.assertEqual(stat.S_IMODE(os.stat(self.opal_pem).st_mode), 0o600)
799 self.assertEqual(stat.S_IMODE(os.stat(self.opal_x509).st_mode), 0o644)
800
801+ def test_create_sipl_keys_autokey_off(self):
802+ # Keys are not created.
803+ self.setUpSiplKeys(create=False)
804+ self.assertFalse(os.path.exists(self.sipl_pem))
805+ self.assertFalse(os.path.exists(self.sipl_x509))
806+ fake_call = FakeMethod(result=0)
807+ self.useFixture(MonkeyPatch("subprocess.call", fake_call))
808+ upload = SigningUpload()
809+ upload.callLog = FakeMethodCallLog(upload=upload)
810+ upload.setTargetDirectory(
811+ self.archive, "test_1.0_amd64.tar.gz", "distroseries")
812+ upload.signOpal(os.path.join(self.makeTemporaryDirectory(), 't.sipl'))
813+ self.assertEqual(0, upload.callLog.caller_count('Sipl keygen key'))
814+ self.assertEqual(0, upload.callLog.caller_count('Sipl keygen cert'))
815+ self.assertFalse(os.path.exists(self.sipl_pem))
816+ self.assertFalse(os.path.exists(self.sipl_x509))
817+
818+ def test_create_sipl_keys_autokey_on(self):
819+ # Keys are created on demand.
820+ self.setUpPPA()
821+ self.setUpSiplKeys(create=False)
822+ self.assertFalse(os.path.exists(self.sipl_pem))
823+ self.assertFalse(os.path.exists(self.sipl_x509))
824+ fake_call = FakeMethod(result=0)
825+ self.useFixture(MonkeyPatch("subprocess.call", fake_call))
826+ upload = SigningUpload()
827+ upload.callLog = FakeMethodCallLog(upload=upload)
828+ upload.setTargetDirectory(
829+ self.archive, "test_1.0_amd64.tar.gz", "distroseries")
830+ upload.signSipl(os.path.join(self.makeTemporaryDirectory(), 't.sipl'))
831+ self.assertEqual(1, upload.callLog.caller_count('Sipl keygen key'))
832+ self.assertEqual(1, upload.callLog.caller_count('Sipl keygen cert'))
833+ self.assertTrue(os.path.exists(self.sipl_pem))
834+ self.assertTrue(os.path.exists(self.sipl_x509))
835+ self.assertEqual(stat.S_IMODE(os.stat(self.sipl_pem).st_mode), 0o600)
836+ self.assertEqual(stat.S_IMODE(os.stat(self.sipl_x509).st_mode), 0o644)
837+
838 def test_checksumming_tree(self):
839 # Specifying no options should leave us with an open tree,
840 # confirm it is checksummed.
841 self.setUpUefiKeys()
842 self.setUpKmodKeys()
843 self.setUpOpalKeys()
844+ self.setUpSiplKeys()
845 self.openArchive("test", "1.0", "amd64")
846 self.tarfile.add_file("1.0/empty.efi", b"")
847 self.tarfile.add_file("1.0/empty.ko", b"")
848 self.tarfile.add_file("1.0/empty.opal", b"")
849+ self.tarfile.add_file("1.0/empty.sipl", b"")
850 self.process_emulate()
851 sha256file = os.path.join(self.getSignedPath("test", "amd64"),
852 "1.0", "SHA256SUMS")
853@@ -926,6 +1275,7 @@
854 self.tarfile.add_file("1.0/empty.efi", b"")
855 self.tarfile.add_file("1.0/empty.ko", b"")
856 self.tarfile.add_file("1.0/empty.opal", b"")
857+ self.tarfile.add_file("1.0/empty.sipl", b"")
858 self.process_emulate()
859 sha256file = os.path.join(self.getSignedPath("test", "amd64"),
860 "1.0", "SHA256SUMS")
861@@ -949,6 +1299,7 @@
862 self.tarfile.add_file("1.0/empty.efi", b"")
863 self.tarfile.add_file("1.0/empty.ko", b"")
864 self.tarfile.add_file("1.0/empty.opal", b"")
865+ self.tarfile.add_file("1.0/empty.sipl", b"")
866 self.process_emulate()
867 sha256file = os.path.join(self.getSignedPath("test", "amd64"),
868 "1.0", "SHA256SUMS")
869@@ -982,6 +1333,7 @@
870 self.tarfile.add_file("1.0/empty.efi", "")
871 self.tarfile.add_file("1.0/empty.ko", "")
872 self.tarfile.add_file("1.0/empty.opal", "")
873+ self.tarfile.add_file("1.0/empty.sipl", "")
874 self.process_emulate()
875 sha256file = os.path.join(self.getSignedPath("test", "amd64"),
876 "1.0", "SHA256SUMS")