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

Proposed by Andy Whitcroft
Status: Merged
Merged at revision: 18987
Proposed branch: lp:~apw/launchpad/signing-fit
Merge into: lp:launchpad
Prerequisite: lp:~apw/launchpad/signing-sipl
Diff against target: 513 lines (+208/-15)
2 files modified
lib/lp/archivepublisher/signing.py (+42/-9)
lib/lp/archivepublisher/tests/test_signing.py (+166/-6)
To merge this branch: bzr merge lp:~apw/launchpad/signing-fit
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+368277@code.launchpad.net

This proposal supersedes a proposal from 2019-06-03.

Commit message

Add u-boot Flat Image Tree signing support.

Description of the change

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.
Revision history for this message
Colin Watson (cjwatson) wrote :

This looks OK to me, thanks.

Prerequisites for landing this MP:

 * u-boot security updates must be landed in xenial-updates and bionic-updates (Andy is on top of this)
 * launchpad-soyuz-dependencies 0.135 must be installed on pepo and haetae (I'll deal with this)

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

The launchpad-dependencies upgrade is https://portal.admin.canonical.com/C119320.

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

The u-boot updates containing the key-name hardening are now all released to -updates:

 u-boot | 2016.01+dfsg1-2ubuntu5 | xenial-updates | source
 u-boot | 2018.07~rc3+dfsg1-0ubuntu3~18.04.1 | bionic-updates | source
 u-boot | 2018.07~rc3+dfsg1-0ubuntu3~18.10.1 | cosmic-updates | source
 u-boot | 2018.07~rc3+dfsg1-0ubuntu3 | disco-updates | source

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/archivepublisher/signing.py'
--- lib/lp/archivepublisher/signing.py 2019-06-06 10:57:20 +0000
+++ lib/lp/archivepublisher/signing.py 2019-06-06 10:57:20 +0000
@@ -97,6 +97,8 @@
97 self.opal_x509 = None97 self.opal_x509 = None
98 self.sipl_pem = None98 self.sipl_pem = None
99 self.sipl_x509 = None99 self.sipl_x509 = None
100 self.fit_key = None
101 self.fit_cert = None
100 self.autokey = False102 self.autokey = False
101 else:103 else:
102 self.uefi_key = os.path.join(pubconf.signingroot, "uefi.key")104 self.uefi_key = os.path.join(pubconf.signingroot, "uefi.key")
@@ -107,6 +109,11 @@
107 self.opal_x509 = os.path.join(pubconf.signingroot, "opal.x509")109 self.opal_x509 = os.path.join(pubconf.signingroot, "opal.x509")
108 self.sipl_pem = os.path.join(pubconf.signingroot, "sipl.pem")110 self.sipl_pem = os.path.join(pubconf.signingroot, "sipl.pem")
109 self.sipl_x509 = os.path.join(pubconf.signingroot, "sipl.x509")111 self.sipl_x509 = os.path.join(pubconf.signingroot, "sipl.x509")
112 # Note: the signature tool allows a collection of keys and takes
113 # a directory name with all valid keys. Avoid mixing the
114 # other signing types' keys with the fit keys.
115 self.fit_key = os.path.join(pubconf.signingroot, "fit", "fit.key")
116 self.fit_cert = os.path.join(pubconf.signingroot, "fit", "fit.crt")
110 self.autokey = pubconf.signingautokey117 self.autokey = pubconf.signingautokey
111118
112 self.setComponents(tarfile_path)119 self.setComponents(tarfile_path)
@@ -182,6 +189,8 @@
182 yield (os.path.join(dirpath, filename), self.signOpal)189 yield (os.path.join(dirpath, filename), self.signOpal)
183 elif filename.endswith(".sipl"):190 elif filename.endswith(".sipl"):
184 yield (os.path.join(dirpath, filename), self.signSipl)191 yield (os.path.join(dirpath, filename), self.signSipl)
192 elif filename.endswith(".fit"):
193 yield (os.path.join(dirpath, filename), self.signFit)
185194
186 def getKeys(self, which, generate, *keynames):195 def getKeys(self, which, generate, *keynames):
187 """Validate and return the uefi key and cert for encryption."""196 """Validate and return the uefi key and cert for encryption."""
@@ -213,29 +222,33 @@
213 common_name = "PPA %s %s" % (owner, archive)222 common_name = "PPA %s %s" % (owner, archive)
214 return common_name[0:64 - len(suffix)] + suffix223 return common_name[0:64 - len(suffix)] + suffix
215224
216 def generateUefiKeys(self):225 def generateKeyCrtPair(self, key_type, key_filename, cert_filename):
217 """Generate new UEFI Keys for this archive."""226 """Generate new Key/Crt key pairs."""
218 directory = os.path.dirname(self.uefi_key)227 directory = os.path.dirname(key_filename)
219 if not os.path.exists(directory):228 if not os.path.exists(directory):
220 os.makedirs(directory)229 os.makedirs(directory)
221230
222 common_name = self.generateKeyCommonName(231 common_name = self.generateKeyCommonName(
223 self.archive.owner.name, self.archive.name)232 self.archive.owner.name, self.archive.name, key_type)
224 subject = '/CN=' + common_name + '/'233 subject = '/CN=' + common_name + '/'
225234
226 old_mask = os.umask(0o077)235 old_mask = os.umask(0o077)
227 try:236 try:
228 new_key_cmd = [237 new_key_cmd = [
229 'openssl', 'req', '-new', '-x509', '-newkey', 'rsa:2048',238 'openssl', 'req', '-new', '-x509', '-newkey', 'rsa:2048',
230 '-subj', subject, '-keyout', self.uefi_key,239 '-subj', subject, '-keyout', key_filename,
231 '-out', self.uefi_cert, '-days', '3650', '-nodes', '-sha256',240 '-out', cert_filename, '-days', '3650', '-nodes', '-sha256',
232 ]241 ]
233 self.callLog("UEFI keygen", new_key_cmd)242 self.callLog(key_type + " keygen", new_key_cmd)
234 finally:243 finally:
235 os.umask(old_mask)244 os.umask(old_mask)
236245
237 if os.path.exists(self.uefi_cert):246 if os.path.exists(cert_filename):
238 os.chmod(self.uefi_cert, 0o644)247 os.chmod(cert_filename, 0o644)
248
249 def generateUefiKeys(self):
250 """Generate new UEFI Keys for this archive."""
251 self.generateKeyCrtPair("UEFI", self.uefi_key, self.uefi_cert)
239252
240 def signUefi(self, image):253 def signUefi(self, image):
241 """Attempt to sign an image."""254 """Attempt to sign an image."""
@@ -368,6 +381,26 @@
368 cmdl = ["kmodsign", "-D", "sha512", pem, cert, image, image + ".sig"]381 cmdl = ["kmodsign", "-D", "sha512", pem, cert, image, image + ".sig"]
369 return self.callLog("SIPL signing", cmdl)382 return self.callLog("SIPL signing", cmdl)
370383
384 def generateFitKeys(self):
385 """Generate new FIT Keys for this archive."""
386 self.generateKeyCrtPair("FIT", self.fit_key, self.fit_cert)
387
388 def signFit(self, image):
389 """Attempt to sign an image."""
390 image_signed = "%s.signed" % image
391 remove_if_exists(image_signed)
392 (key, cert) = self.getKeys('FIT', self.generateFitKeys,
393 self.fit_key, self.fit_cert)
394 if not key or not cert:
395 return
396 self.publishPublicKey(cert)
397 # Make a copy of the image as mkimage signs in place and in
398 # signed-only mode we will remove the original file.
399 shutil.copy(image, image_signed)
400 cmdl = ["mkimage", "-F", "-k", os.path.dirname(key), "-r",
401 image_signed]
402 return self.callLog("FIT signing", cmdl)
403
371 def convertToTarball(self):404 def convertToTarball(self):
372 """Convert unpacked output to signing tarball."""405 """Convert unpacked output to signing tarball."""
373 tarfilename = os.path.join(self.tmpdir, "signed.tar.gz")406 tarfilename = os.path.join(self.tmpdir, "signed.tar.gz")
374407
=== modified file 'lib/lp/archivepublisher/tests/test_signing.py'
--- lib/lp/archivepublisher/tests/test_signing.py 2019-06-06 10:57:20 +0000
+++ lib/lp/archivepublisher/tests/test_signing.py 2019-06-06 10:57:20 +0000
@@ -85,6 +85,8 @@
85 self.callers = {85 self.callers = {
86 "UEFI signing": 0,86 "UEFI signing": 0,
87 "UEFI keygen": 0,87 "UEFI keygen": 0,
88 "FIT signing": 0,
89 "FIT keygen": 0,
88 "Kmod signing": 0,90 "Kmod signing": 0,
89 "Kmod keygen key": 0,91 "Kmod keygen key": 0,
90 "Kmod keygen cert": 0,92 "Kmod keygen cert": 0,
@@ -111,6 +113,15 @@
111 write_file(self.upload.uefi_key, b"")113 write_file(self.upload.uefi_key, b"")
112 write_file(self.upload.uefi_cert, b"")114 write_file(self.upload.uefi_cert, b"")
113115
116 elif description == "FIT signing":
117 filename = cmdl[-1]
118 if filename.endswith(".fit"):
119 write_file(filename + ".signed", b"")
120
121 elif description == "FIT keygen":
122 write_file(self.upload.fit_key, b"")
123 write_file(self.upload.fit_cert, b"")
124
114 elif description == "Kmod signing":125 elif description == "Kmod signing":
115 filename = cmdl[-1]126 filename = cmdl[-1]
116 if filename.endswith(".ko.sig"):127 if filename.endswith(".ko.sig"):
@@ -191,7 +202,7 @@
191 purpose=ArchivePurpose.PPA)202 purpose=ArchivePurpose.PPA)
192 self.signing_dir = os.path.join(203 self.signing_dir = os.path.join(
193 self.temp_dir, "signing", "signing-owner", "testing")204 self.temp_dir, "signing", "signing-owner", "testing")
194 self.testcase_cn = '/CN=PPA signing-owner testing/'205 self.testcase_cn = 'PPA signing-owner testing'
195 pubconf = getPubConfig(self.archive)206 pubconf = getPubConfig(self.archive)
196 if not os.path.exists(pubconf.temproot):207 if not os.path.exists(pubconf.temproot):
197 os.makedirs(pubconf.temproot)208 os.makedirs(pubconf.temproot)
@@ -211,6 +222,15 @@
211 write_file(self.key, b"")222 write_file(self.key, b"")
212 write_file(self.cert, b"")223 write_file(self.cert, b"")
213224
225 def setUpFitKeys(self, create=True):
226 # We expect and need the fit keys to be in their own
227 # directory as part of key protection for mkimage.
228 self.fit_key = os.path.join(self.signing_dir, "fit", "fit.key")
229 self.fit_cert = os.path.join(self.signing_dir, "fit", "fit.crt")
230 if create:
231 write_file(self.fit_key, b"")
232 write_file(self.fit_cert, b"")
233
214 def setUpKmodKeys(self, create=True):234 def setUpKmodKeys(self, create=True):
215 self.kmod_pem = os.path.join(self.signing_dir, "kmod.pem")235 self.kmod_pem = os.path.join(self.signing_dir, "kmod.pem")
216 self.kmod_x509 = os.path.join(self.signing_dir, "kmod.x509")236 self.kmod_x509 = os.path.join(self.signing_dir, "kmod.x509")
@@ -255,6 +275,7 @@
255 upload = SigningUpload()275 upload = SigningUpload()
256 # Under no circumstances is it safe to execute actual commands.276 # Under no circumstances is it safe to execute actual commands.
257 self.fake_call = FakeMethod(result=0)277 self.fake_call = FakeMethod(result=0)
278 self.fake_copyfile = FakeMethod(result=0)
258 upload.callLog = FakeMethodCallLog(upload=upload)279 upload.callLog = FakeMethodCallLog(upload=upload)
259 self.useFixture(MonkeyPatch("subprocess.call", self.fake_call))280 self.useFixture(MonkeyPatch("subprocess.call", self.fake_call))
260 upload.process(self.archive, self.path, self.suite)281 upload.process(self.archive, self.path, self.suite)
@@ -269,6 +290,7 @@
269 upload.signKmod = FakeMethod()290 upload.signKmod = FakeMethod()
270 upload.signOpal = FakeMethod()291 upload.signOpal = FakeMethod()
271 upload.signSipl = FakeMethod()292 upload.signSipl = FakeMethod()
293 upload.signFit = FakeMethod()
272 # Under no circumstances is it safe to execute actual commands.294 # Under no circumstances is it safe to execute actual commands.
273 fake_call = FakeMethod(result=0)295 fake_call = FakeMethod(result=0)
274 self.useFixture(MonkeyPatch("subprocess.call", fake_call))296 self.useFixture(MonkeyPatch("subprocess.call", fake_call))
@@ -290,6 +312,7 @@
290 self.tarfile.add_file("1.0/empty.ko", b"")312 self.tarfile.add_file("1.0/empty.ko", b"")
291 self.tarfile.add_file("1.0/empty.opal", b"")313 self.tarfile.add_file("1.0/empty.opal", b"")
292 self.tarfile.add_file("1.0/empty.sipl", b"")314 self.tarfile.add_file("1.0/empty.sipl", b"")
315 self.tarfile.add_file("1.0/empty.fit", b"")
293 upload = self.process_emulate()316 upload = self.process_emulate()
294 self.assertContentEqual([], upload.callLog.caller_list())317 self.assertContentEqual([], upload.callLog.caller_list())
295318
@@ -301,6 +324,7 @@
301 self.tarfile.add_file("1.0/empty.ko", b"")324 self.tarfile.add_file("1.0/empty.ko", b"")
302 self.tarfile.add_file("1.0/empty.opal", b"")325 self.tarfile.add_file("1.0/empty.opal", b"")
303 self.tarfile.add_file("1.0/empty.sipl", b"")326 self.tarfile.add_file("1.0/empty.sipl", b"")
327 self.tarfile.add_file("1.0/empty.fit", b"")
304 upload = self.process_emulate()328 upload = self.process_emulate()
305 self.assertContentEqual([], upload.callLog.caller_list())329 self.assertContentEqual([], upload.callLog.caller_list())
306330
@@ -314,6 +338,7 @@
314 self.tarfile.add_file("1.0/empty.ko", b"")338 self.tarfile.add_file("1.0/empty.ko", b"")
315 self.tarfile.add_file("1.0/empty.opal", b"")339 self.tarfile.add_file("1.0/empty.opal", b"")
316 self.tarfile.add_file("1.0/empty.sipl", b"")340 self.tarfile.add_file("1.0/empty.sipl", b"")
341 self.tarfile.add_file("1.0/empty.fit", b"")
317 upload = self.process_emulate()342 upload = self.process_emulate()
318 expected_callers = [343 expected_callers = [
319 ('UEFI signing', 1),344 ('UEFI signing', 1),
@@ -330,6 +355,7 @@
330 self.tarfile.add_file("1.0/empty.ko", b"")355 self.tarfile.add_file("1.0/empty.ko", b"")
331 self.tarfile.add_file("1.0/empty.opal", b"")356 self.tarfile.add_file("1.0/empty.opal", b"")
332 self.tarfile.add_file("1.0/empty.sipl", b"")357 self.tarfile.add_file("1.0/empty.sipl", b"")
358 self.tarfile.add_file("1.0/empty.fit", b"")
333 upload = self.process_emulate()359 upload = self.process_emulate()
334 expected_callers = [360 expected_callers = [
335 ('UEFI keygen', 1),361 ('UEFI keygen', 1),
@@ -339,10 +365,12 @@
339 ('Opal keygen cert', 1),365 ('Opal keygen cert', 1),
340 ('SIPL keygen key', 1),366 ('SIPL keygen key', 1),
341 ('SIPL keygen cert', 1),367 ('SIPL keygen cert', 1),
368 ('FIT keygen', 1),
342 ('UEFI signing', 1),369 ('UEFI signing', 1),
343 ('Kmod signing', 1),370 ('Kmod signing', 1),
344 ('Opal signing', 1),371 ('Opal signing', 1),
345 ('SIPL signing', 1),372 ('SIPL signing', 1),
373 ('FIT signing', 1),
346 ]374 ]
347 self.assertContentEqual(expected_callers, upload.callLog.caller_list())375 self.assertContentEqual(expected_callers, upload.callLog.caller_list())
348376
@@ -417,11 +445,13 @@
417 self.setUpKmodKeys()445 self.setUpKmodKeys()
418 self.setUpOpalKeys()446 self.setUpOpalKeys()
419 self.setUpSiplKeys()447 self.setUpSiplKeys()
448 self.setUpFitKeys()
420 self.openArchive("test", "1.0", "amd64")449 self.openArchive("test", "1.0", "amd64")
421 self.tarfile.add_file("1.0/empty.efi", b"")450 self.tarfile.add_file("1.0/empty.efi", b"")
422 self.tarfile.add_file("1.0/empty.ko", b"")451 self.tarfile.add_file("1.0/empty.ko", b"")
423 self.tarfile.add_file("1.0/empty.opal", b"")452 self.tarfile.add_file("1.0/empty.opal", b"")
424 self.tarfile.add_file("1.0/empty.sipl", b"")453 self.tarfile.add_file("1.0/empty.sipl", b"")
454 self.tarfile.add_file("1.0/empty.fit", b"")
425 self.process_emulate()455 self.process_emulate()
426 self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([456 self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([
427 "1.0/SHA256SUMS",457 "1.0/SHA256SUMS",
@@ -429,6 +459,7 @@
429 "1.0/empty.ko", "1.0/empty.ko.sig", "1.0/control/kmod.x509",459 "1.0/empty.ko", "1.0/empty.ko.sig", "1.0/control/kmod.x509",
430 "1.0/empty.opal", "1.0/empty.opal.sig", "1.0/control/opal.x509",460 "1.0/empty.opal", "1.0/empty.opal.sig", "1.0/control/opal.x509",
431 "1.0/empty.sipl", "1.0/empty.sipl.sig", "1.0/control/sipl.x509",461 "1.0/empty.sipl", "1.0/empty.sipl.sig", "1.0/control/sipl.x509",
462 "1.0/empty.fit", "1.0/empty.fit.signed", "1.0/control/fit.crt",
432 ]))463 ]))
433464
434 def test_options_tarball(self):465 def test_options_tarball(self):
@@ -438,12 +469,14 @@
438 self.setUpKmodKeys()469 self.setUpKmodKeys()
439 self.setUpOpalKeys()470 self.setUpOpalKeys()
440 self.setUpSiplKeys()471 self.setUpSiplKeys()
472 self.setUpFitKeys()
441 self.openArchive("test", "1.0", "amd64")473 self.openArchive("test", "1.0", "amd64")
442 self.tarfile.add_file("1.0/control/options", b"tarball")474 self.tarfile.add_file("1.0/control/options", b"tarball")
443 self.tarfile.add_file("1.0/empty.efi", b"")475 self.tarfile.add_file("1.0/empty.efi", b"")
444 self.tarfile.add_file("1.0/empty.ko", b"")476 self.tarfile.add_file("1.0/empty.ko", b"")
445 self.tarfile.add_file("1.0/empty.opal", b"")477 self.tarfile.add_file("1.0/empty.opal", b"")
446 self.tarfile.add_file("1.0/empty.sipl", b"")478 self.tarfile.add_file("1.0/empty.sipl", b"")
479 self.tarfile.add_file("1.0/empty.fit", b"")
447 self.process_emulate()480 self.process_emulate()
448 self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([481 self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([
449 "1.0/SHA256SUMS",482 "1.0/SHA256SUMS",
@@ -461,6 +494,8 @@
461 '1.0/control/opal.x509',494 '1.0/control/opal.x509',
462 '1.0/empty.sipl', '1.0/empty.sipl.sig',495 '1.0/empty.sipl', '1.0/empty.sipl.sig',
463 '1.0/control/sipl.x509',496 '1.0/control/sipl.x509',
497 '1.0/empty.fit', '1.0/empty.fit.signed',
498 '1.0/control/fit.crt',
464 ], tarball.getnames())499 ], tarball.getnames())
465500
466 def test_options_signed_only(self):501 def test_options_signed_only(self):
@@ -470,12 +505,14 @@
470 self.setUpKmodKeys()505 self.setUpKmodKeys()
471 self.setUpOpalKeys()506 self.setUpOpalKeys()
472 self.setUpSiplKeys()507 self.setUpSiplKeys()
508 self.setUpFitKeys()
473 self.openArchive("test", "1.0", "amd64")509 self.openArchive("test", "1.0", "amd64")
474 self.tarfile.add_file("1.0/control/options", b"signed-only")510 self.tarfile.add_file("1.0/control/options", b"signed-only")
475 self.tarfile.add_file("1.0/empty.efi", b"")511 self.tarfile.add_file("1.0/empty.efi", b"")
476 self.tarfile.add_file("1.0/empty.ko", b"")512 self.tarfile.add_file("1.0/empty.ko", b"")
477 self.tarfile.add_file("1.0/empty.opal", b"")513 self.tarfile.add_file("1.0/empty.opal", b"")
478 self.tarfile.add_file("1.0/empty.sipl", b"")514 self.tarfile.add_file("1.0/empty.sipl", b"")
515 self.tarfile.add_file("1.0/empty.fit", b"")
479 self.process_emulate()516 self.process_emulate()
480 self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([517 self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([
481 "1.0/SHA256SUMS", "1.0/control/options",518 "1.0/SHA256SUMS", "1.0/control/options",
@@ -483,6 +520,7 @@
483 "1.0/empty.ko.sig", "1.0/control/kmod.x509",520 "1.0/empty.ko.sig", "1.0/control/kmod.x509",
484 "1.0/empty.opal.sig", "1.0/control/opal.x509",521 "1.0/empty.opal.sig", "1.0/control/opal.x509",
485 "1.0/empty.sipl.sig", "1.0/control/sipl.x509",522 "1.0/empty.sipl.sig", "1.0/control/sipl.x509",
523 "1.0/empty.fit.signed", "1.0/control/fit.crt",
486 ]))524 ]))
487525
488 def test_options_tarball_signed_only(self):526 def test_options_tarball_signed_only(self):
@@ -493,12 +531,14 @@
493 self.setUpKmodKeys()531 self.setUpKmodKeys()
494 self.setUpOpalKeys()532 self.setUpOpalKeys()
495 self.setUpSiplKeys()533 self.setUpSiplKeys()
534 self.setUpFitKeys()
496 self.openArchive("test", "1.0", "amd64")535 self.openArchive("test", "1.0", "amd64")
497 self.tarfile.add_file("1.0/control/options", b"tarball\nsigned-only")536 self.tarfile.add_file("1.0/control/options", b"tarball\nsigned-only")
498 self.tarfile.add_file("1.0/empty.efi", b"")537 self.tarfile.add_file("1.0/empty.efi", b"")
499 self.tarfile.add_file("1.0/empty.ko", b"")538 self.tarfile.add_file("1.0/empty.ko", b"")
500 self.tarfile.add_file("1.0/empty.opal", b"")539 self.tarfile.add_file("1.0/empty.opal", b"")
501 self.tarfile.add_file("1.0/empty.sipl", b"")540 self.tarfile.add_file("1.0/empty.sipl", b"")
541 self.tarfile.add_file("1.0/empty.fit", b"")
502 self.process_emulate()542 self.process_emulate()
503 self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([543 self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([
504 "1.0/SHA256SUMS",544 "1.0/SHA256SUMS",
@@ -513,12 +553,17 @@
513 '1.0/empty.ko.sig', '1.0/control/kmod.x509',553 '1.0/empty.ko.sig', '1.0/control/kmod.x509',
514 '1.0/empty.opal.sig', '1.0/control/opal.x509',554 '1.0/empty.opal.sig', '1.0/control/opal.x509',
515 '1.0/empty.sipl.sig', '1.0/control/sipl.x509',555 '1.0/empty.sipl.sig', '1.0/control/sipl.x509',
556 '1.0/empty.fit.signed', '1.0/control/fit.crt',
516 ], tarball.getnames())557 ], tarball.getnames())
517558
518 def test_no_signed_files(self):559 def test_no_signed_files(self):
519 # Tarballs containing no *.efi files are extracted without complaint.560 # Tarballs containing no *.efi files are extracted without complaint.
520 # Nothing is signed.561 # Nothing is signed.
521 self.setUpUefiKeys()562 self.setUpUefiKeys()
563 self.setUpKmodKeys()
564 self.setUpOpalKeys()
565 self.setUpSiplKeys()
566 self.setUpFitKeys()
522 self.openArchive("empty", "1.0", "amd64")567 self.openArchive("empty", "1.0", "amd64")
523 self.tarfile.add_file("1.0/hello", b"world")568 self.tarfile.add_file("1.0/hello", b"world")
524 upload = self.process()569 upload = self.process()
@@ -528,6 +573,7 @@
528 self.assertEqual(0, upload.signKmod.call_count)573 self.assertEqual(0, upload.signKmod.call_count)
529 self.assertEqual(0, upload.signOpal.call_count)574 self.assertEqual(0, upload.signOpal.call_count)
530 self.assertEqual(0, upload.signSipl.call_count)575 self.assertEqual(0, upload.signSipl.call_count)
576 self.assertEqual(0, upload.signFit.call_count)
531577
532 def test_already_exists(self):578 def test_already_exists(self):
533 # If the target directory already exists, processing fails.579 # If the target directory already exists, processing fails.
@@ -595,7 +641,71 @@
595 args = fake_call.calls[0][0][0]641 args = fake_call.calls[0][0][0]
596 expected_cmd = [642 expected_cmd = [
597 'openssl', 'req', '-new', '-x509', '-newkey', 'rsa:2048',643 'openssl', 'req', '-new', '-x509', '-newkey', 'rsa:2048',
598 '-subj', self.testcase_cn, '-keyout', self.key, '-out', self.cert,644 '-subj', '/CN=' + self.testcase_cn + ' UEFI/',
645 '-keyout', self.key, '-out', self.cert,
646 '-days', '3650', '-nodes', '-sha256',
647 ]
648 self.assertEqual(expected_cmd, args)
649
650 def test_correct_fit_signing_command_executed(self):
651 # Check that calling signFit() will generate the expected command
652 # when appropriate keys are present.
653 self.setUpFitKeys()
654 fake_call = FakeMethod(result=0)
655 self.useFixture(MonkeyPatch("subprocess.call", fake_call))
656 fake_copy = FakeMethod(result=0)
657 self.useFixture(MonkeyPatch("shutil.copy", fake_copy))
658 upload = SigningUpload()
659 upload.generateFitKeys = FakeMethod()
660 upload.setTargetDirectory(
661 self.archive, "test_1.0_amd64.tar.gz", "distroseries")
662 upload.signFit('t.fit')
663 # Confirm the copy was performed.
664 self.assertEqual(1, fake_copy.call_count)
665 args = fake_copy.calls[0][0]
666 expected_copy = ('t.fit', 't.fit.signed')
667 self.assertEqual(expected_copy, args)
668 # Assert command form.
669 args = fake_call.calls[0][0][0]
670 expected_cmd = [
671 'mkimage', '-F', '-k', os.path.dirname(self.fit_key), '-r',
672 't.fit.signed',
673 ]
674 self.assertEqual(expected_cmd, args)
675 self.assertEqual(0, upload.generateFitKeys.call_count)
676
677 def test_correct_fit_signing_command_executed_no_keys(self):
678 # Check that calling signFit() will generate no commands when
679 # no keys are present.
680 self.setUpFitKeys(create=False)
681 fake_call = FakeMethod(result=0)
682 self.useFixture(MonkeyPatch("subprocess.call", fake_call))
683 upload = SigningUpload()
684 upload.generateFitKeys = FakeMethod()
685 upload.setTargetDirectory(
686 self.archive, "test_1.0_amd64.tar.gz", "distroseries")
687 upload.signUefi('t.fit')
688 self.assertEqual(0, fake_call.call_count)
689 self.assertEqual(0, upload.generateFitKeys.call_count)
690
691 def test_correct_fit_keygen_command_executed(self):
692 # Check that calling generateFitKeys() will generate the
693 # expected command.
694 self.setUpPPA()
695 self.setUpFitKeys(create=False)
696 fake_call = FakeMethod(result=0)
697 self.useFixture(MonkeyPatch("subprocess.call", fake_call))
698 upload = SigningUpload()
699 upload.setTargetDirectory(
700 self.archive, "test_1.0_amd64.tar.gz", "distroseries")
701 upload.generateFitKeys()
702 self.assertEqual(1, fake_call.call_count)
703 # Assert the actual command matches.
704 args = fake_call.calls[0][0][0]
705 expected_cmd = [
706 'openssl', 'req', '-new', '-x509', '-newkey', 'rsa:2048',
707 '-subj', '/CN=' + self.testcase_cn + ' FIT/',
708 '-keyout', self.fit_key, '-out', self.fit_cert,
599 '-days', '3650', '-nodes', '-sha256',709 '-days', '3650', '-nodes', '-sha256',
600 ]710 ]
601 self.assertEqual(expected_cmd, args)711 self.assertEqual(expected_cmd, args)
@@ -610,7 +720,7 @@
610 text = upload.generateOpensslConfig('Kmod', upload.openssl_config_kmod)720 text = upload.generateOpensslConfig('Kmod', upload.openssl_config_kmod)
611721
612 id_re = re.compile(r'^# KMOD OpenSSL config\n')722 id_re = re.compile(r'^# KMOD OpenSSL config\n')
613 cn_re = re.compile(r'\bCN\s*=\s*' + self.testcase_cn[4:-1] + '\s+Kmod')723 cn_re = re.compile(r'\bCN\s*=\s*' + self.testcase_cn + '\s+Kmod')
614 eku_re = re.compile(724 eku_re = re.compile(
615 r'\bextendedKeyUsage\s*=\s*'725 r'\bextendedKeyUsage\s*=\s*'
616 r'codeSigning,1.3.6.1.4.1.2312.16.1.2\s*\b')726 r'codeSigning,1.3.6.1.4.1.2312.16.1.2\s*\b')
@@ -696,7 +806,7 @@
696 text = upload.generateOpensslConfig('Opal', upload.openssl_config_opal)806 text = upload.generateOpensslConfig('Opal', upload.openssl_config_opal)
697807
698 id_re = re.compile(r'^# OPAL OpenSSL config\n')808 id_re = re.compile(r'^# OPAL OpenSSL config\n')
699 cn_re = re.compile(r'\bCN\s*=\s*' + self.testcase_cn[4:-1] + '\s+Opal')809 cn_re = re.compile(r'\bCN\s*=\s*' + self.testcase_cn + '\s+Opal')
700810
701 self.assertIn('[ req ]', text)811 self.assertIn('[ req ]', text)
702 self.assertIsNotNone(id_re.search(text))812 self.assertIsNotNone(id_re.search(text))
@@ -779,7 +889,7 @@
779 text = upload.generateOpensslConfig('SIPL', upload.openssl_config_sipl)889 text = upload.generateOpensslConfig('SIPL', upload.openssl_config_sipl)
780890
781 id_re = re.compile(r'^# SIPL OpenSSL config\n')891 id_re = re.compile(r'^# SIPL OpenSSL config\n')
782 cn_re = re.compile(r'\bCN\s*=\s*' + self.testcase_cn[4:-1] + '\s+SIPL')892 cn_re = re.compile(r'\bCN\s*=\s*' + self.testcase_cn + '\s+SIPL')
783893
784 self.assertIn('[ req ]', text)894 self.assertIn('[ req ]', text)
785 self.assertIsNotNone(id_re.search(text))895 self.assertIsNotNone(id_re.search(text))
@@ -860,6 +970,14 @@
860 upload = self.process()970 upload = self.process()
861 self.assertEqual(1, upload.signUefi.call_count)971 self.assertEqual(1, upload.signUefi.call_count)
862972
973 def test_signs_fit_image(self):
974 # Each image in the tarball is signed.
975 self.setUpFitKeys()
976 self.openArchive("test", "1.0", "amd64")
977 self.tarfile.add_file("1.0/empty.fit", b"")
978 upload = self.process()
979 self.assertEqual(1, upload.signFit.call_count)
980
863 def test_signs_kmod_image(self):981 def test_signs_kmod_image(self):
864 # Each image in the tarball is signed.982 # Each image in the tarball is signed.
865 self.setUpKmodKeys()983 self.setUpKmodKeys()
@@ -886,7 +1004,6 @@
8861004
887 def test_signs_combo_image(self):1005 def test_signs_combo_image(self):
888 # Each image in the tarball is signed.1006 # Each image in the tarball is signed.
889 self.setUpKmodKeys()
890 self.openArchive("test", "1.0", "amd64")1007 self.openArchive("test", "1.0", "amd64")
891 self.tarfile.add_file("1.0/empty.efi", b"")1008 self.tarfile.add_file("1.0/empty.efi", b"")
892 self.tarfile.add_file("1.0/empty.ko", b"")1009 self.tarfile.add_file("1.0/empty.ko", b"")
@@ -898,11 +1015,17 @@
898 self.tarfile.add_file("1.0/empty2.sipl", b"")1015 self.tarfile.add_file("1.0/empty2.sipl", b"")
899 self.tarfile.add_file("1.0/empty3.sipl", b"")1016 self.tarfile.add_file("1.0/empty3.sipl", b"")
900 self.tarfile.add_file("1.0/empty4.sipl", b"")1017 self.tarfile.add_file("1.0/empty4.sipl", b"")
1018 self.tarfile.add_file("1.0/empty.fit", b"")
1019 self.tarfile.add_file("1.0/empty2.fit", b"")
1020 self.tarfile.add_file("1.0/empty3.fit", b"")
1021 self.tarfile.add_file("1.0/empty4.fit", b"")
1022 self.tarfile.add_file("1.0/empty5.fit", b"")
901 upload = self.process()1023 upload = self.process()
902 self.assertEqual(1, upload.signUefi.call_count)1024 self.assertEqual(1, upload.signUefi.call_count)
903 self.assertEqual(2, upload.signKmod.call_count)1025 self.assertEqual(2, upload.signKmod.call_count)
904 self.assertEqual(3, upload.signOpal.call_count)1026 self.assertEqual(3, upload.signOpal.call_count)
905 self.assertEqual(4, upload.signSipl.call_count)1027 self.assertEqual(4, upload.signSipl.call_count)
1028 self.assertEqual(5, upload.signFit.call_count)
9061029
907 def test_installed(self):1030 def test_installed(self):
908 # Files in the tarball are installed correctly.1031 # Files in the tarball are installed correctly.
@@ -974,6 +1097,43 @@
974 self.assertEqual(stat.S_IMODE(os.stat(self.key).st_mode), 0o600)1097 self.assertEqual(stat.S_IMODE(os.stat(self.key).st_mode), 0o600)
975 self.assertEqual(stat.S_IMODE(os.stat(self.cert).st_mode), 0o644)1098 self.assertEqual(stat.S_IMODE(os.stat(self.cert).st_mode), 0o644)
9761099
1100 def test_create_fit_keys_autokey_off(self):
1101 # Keys are not created.
1102 self.setUpFitKeys(create=False)
1103 self.assertFalse(os.path.exists(self.fit_key))
1104 self.assertFalse(os.path.exists(self.fit_cert))
1105 fake_call = FakeMethod(result=0)
1106 self.useFixture(MonkeyPatch("subprocess.call", fake_call))
1107 upload = SigningUpload()
1108 upload.callLog = FakeMethodCallLog(upload=upload)
1109 upload.setTargetDirectory(
1110 self.archive, "test_1.0_amd64.tar.gz", "distroseries")
1111 upload.signFit(os.path.join(self.makeTemporaryDirectory(), 'fit'))
1112 self.assertEqual(0, upload.callLog.caller_count('FIT keygen'))
1113 self.assertFalse(os.path.exists(self.fit_key))
1114 self.assertFalse(os.path.exists(self.fit_cert))
1115
1116 def test_create_fit_keys_autokey_on(self):
1117 # Keys are created on demand.
1118 self.setUpPPA()
1119 self.setUpFitKeys(create=False)
1120 self.assertFalse(os.path.exists(self.fit_key))
1121 self.assertFalse(os.path.exists(self.fit_cert))
1122 fake_call = FakeMethod(result=0)
1123 self.useFixture(MonkeyPatch("subprocess.call", fake_call))
1124 fake_copy = FakeMethod(result=0)
1125 self.useFixture(MonkeyPatch("shutil.copy", fake_copy))
1126 upload = SigningUpload()
1127 upload.callLog = FakeMethodCallLog(upload=upload)
1128 upload.setTargetDirectory(
1129 self.archive, "test_1.0_amd64.tar.gz", "distroseries")
1130 upload.signFit(os.path.join(self.makeTemporaryDirectory(), 't.fit'))
1131 self.assertEqual(1, upload.callLog.caller_count('FIT keygen'))
1132 self.assertTrue(os.path.exists(self.fit_key))
1133 self.assertTrue(os.path.exists(self.fit_cert))
1134 self.assertEqual(stat.S_IMODE(os.stat(self.fit_key).st_mode), 0o600)
1135 self.assertEqual(stat.S_IMODE(os.stat(self.fit_cert).st_mode), 0o644)
1136
977 def test_create_kmod_keys_autokey_off(self):1137 def test_create_kmod_keys_autokey_off(self):
978 # Keys are not created.1138 # Keys are not created.
979 self.setUpKmodKeys(create=False)1139 self.setUpKmodKeys(create=False)