← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~apw/launchpad/signing-sipl into lp:launchpad

 

Andy Whitcroft has proposed merging lp:~apw/launchpad/signing-sipl into lp:launchpad.

Commit message:
Add s390x Secure Initial Program Load signing support.  On newer s390x mainframes zipl implements signature verification for zipl stage 3 and for the loaded kernel binaries.  These signatures are essentially kernel module signing signatures.  Add support for performing SIPL signing against *.sipl files in the signing custom uploads.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~apw/launchpad/signing-sipl/+merge/368275
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~apw/launchpad/signing-sipl into lp:launchpad.
=== modified file 'lib/lp/archivepublisher/signing.py'
--- lib/lp/archivepublisher/signing.py	2018-08-03 16:10:41 +0000
+++ lib/lp/archivepublisher/signing.py	2019-06-03 14:49:08 +0000
@@ -95,6 +95,8 @@
             self.kmod_x509 = None
             self.opal_pem = None
             self.opal_x509 = None
+            self.sipl_pem = None
+            self.sipl_x509 = None
             self.autokey = False
         else:
             self.uefi_key = os.path.join(pubconf.signingroot, "uefi.key")
@@ -103,6 +105,8 @@
             self.kmod_x509 = os.path.join(pubconf.signingroot, "kmod.x509")
             self.opal_pem = os.path.join(pubconf.signingroot, "opal.pem")
             self.opal_x509 = os.path.join(pubconf.signingroot, "opal.x509")
+            self.sipl_pem = os.path.join(pubconf.signingroot, "sipl.pem")
+            self.sipl_x509 = os.path.join(pubconf.signingroot, "sipl.x509")
             self.autokey = pubconf.signingautokey
 
         self.setComponents(tarfile_path)
@@ -176,6 +180,8 @@
                     yield (os.path.join(dirpath, filename), self.signKmod)
                 elif filename.endswith(".opal"):
                     yield (os.path.join(dirpath, filename), self.signOpal)
+                elif filename.endswith(".sipl"):
+                    yield (os.path.join(dirpath, filename), self.signSipl)
 
     def getKeys(self, which, generate, *keynames):
         """Validate and return the uefi key and cert for encryption."""
@@ -242,7 +248,7 @@
         cmdl = ["sbsign", "--key", key, "--cert", cert, image]
         return self.callLog("UEFI signing", cmdl)
 
-    openssl_config_opal = textwrap.dedent("""
+    openssl_config_base = textwrap.dedent("""
         [ req ]
         default_bits = 4096
         distinguished_name = req_distinguished_name
@@ -260,37 +266,35 @@
         authorityKeyIdentifier=keyid
         """)
 
-    openssl_config_kmod = openssl_config_opal + textwrap.dedent("""
+    openssl_config_opal = "# OPAL openssl config" + openssl_config_base
+
+    openssl_config_kmod = "# KMOD openssl config" + openssl_config_base + \
+        textwrap.dedent("""
         # codeSigning:  specifies that this key is used to sign code.
         # 1.3.6.1.4.1.2312.16.1.2:  defines this key as used for
         #   module signing only. See https://lkml.org/lkml/2015/8/26/741.
         extendedKeyUsage        = codeSigning,1.3.6.1.4.1.2312.16.1.2
         """)
 
-    def generateOpensslConfig(self, key_type, common_name):
-        if key_type == 'Kmod':
-            genkey_tmpl = self.openssl_config_kmod
-        elif key_type == 'Opal':
-            genkey_tmpl = self.openssl_config_opal
-        else:
-            raise ValueError("unknown key_type " + key_type)
+    openssl_config_sipl = "# SIPL openssl config" + openssl_config_base
+
+    def generateOpensslConfig(self, key_type, genkey_tmpl):
+        # Truncate name to 64 character maximum.
+        common_name = self.generateKeyCommonName(
+            self.archive.owner.name, self.archive.name, key_type)
 
         return genkey_tmpl.format(common_name=common_name)
 
-    def generatePemX509Pair(self, key_type, pem_filename, x509_filename):
+    def generatePemX509Pair(self, key_type, genkey_text, pem_filename,
+            x509_filename):
         """Generate new pem/x509 key pairs."""
         directory = os.path.dirname(pem_filename)
         if not os.path.exists(directory):
             os.makedirs(directory)
 
-        # Truncate name to 64 character maximum.
-        common_name = self.generateKeyCommonName(
-            self.archive.owner.name, self.archive.name, key_type)
-
         old_mask = os.umask(0o077)
         try:
             with tempfile.NamedTemporaryFile(suffix='.keygen') as tf:
-                genkey_text = self.generateOpensslConfig(key_type, common_name)
                 print(genkey_text, file=tf)
 
                 # Close out the underlying file so we know it is complete.
@@ -318,7 +322,8 @@
 
     def generateKmodKeys(self):
         """Generate new Kernel Signing Keys for this archive."""
-        self.generatePemX509Pair("Kmod", self.kmod_pem, self.kmod_x509)
+        config = self.generateOpensslConfig("Kmod", self.openssl_config_kmod)
+        self.generatePemX509Pair("Kmod", config, self.kmod_pem, self.kmod_x509)
 
     def signKmod(self, image):
         """Attempt to sign a kernel module."""
@@ -333,7 +338,8 @@
 
     def generateOpalKeys(self):
         """Generate new Opal Signing Keys for this archive."""
-        self.generatePemX509Pair("Opal", self.opal_pem, self.opal_x509)
+        config = self.generateOpensslConfig("Opal", self.openssl_config_opal)
+        self.generatePemX509Pair("Opal", config, self.opal_pem, self.opal_x509)
 
     def signOpal(self, image):
         """Attempt to sign a kernel image for Opal."""
@@ -346,6 +352,22 @@
         cmdl = ["kmodsign", "-D", "sha512", pem, cert, image, image + ".sig"]
         return self.callLog("Opal signing", cmdl)
 
+    def generateSiplKeys(self):
+        """Generate new Sipl Signing Keys for this archive."""
+        config = self.generateOpensslConfig("Sipl", self.openssl_config_sipl)
+        self.generatePemX509Pair("Sipl", config, self.sipl_pem, self.sipl_x509)
+
+    def signSipl(self, image):
+        """Attempt to sign a kernel image for Sipl."""
+        remove_if_exists("%s.sig" % image)
+        (pem, cert) = self.getKeys('Sipl Kernel', self.generateSiplKeys,
+            self.sipl_pem, self.sipl_x509)
+        if not pem or not cert:
+            return
+        self.publishPublicKey(cert)
+        cmdl = ["kmodsign", "-D", "sha512", pem, cert, image, image + ".sig"]
+        return self.callLog("Sipl signing", cmdl)
+
     def convertToTarball(self):
         """Convert unpacked output to signing tarball."""
         tarfilename = os.path.join(self.tmpdir, "signed.tar.gz")

=== modified file 'lib/lp/archivepublisher/tests/test_signing.py'
--- lib/lp/archivepublisher/tests/test_signing.py	2019-05-24 11:10:38 +0000
+++ lib/lp/archivepublisher/tests/test_signing.py	2019-06-03 14:49:08 +0000
@@ -91,6 +91,9 @@
             "Opal signing": 0,
             "Opal keygen key": 0,
             "Opal keygen cert": 0,
+            "Sipl signing": 0,
+            "Sipl keygen key": 0,
+            "Sipl keygen cert": 0,
             }
 
     def __call__(self, *args, **kwargs):
@@ -130,9 +133,20 @@
         elif description == "Opal keygen key":
             write_file(self.upload.opal_pem, b"")
 
+        elif description == "Sipl signing":
+            filename = cmdl[-1]
+            if filename.endswith(".sipl.sig"):
+                write_file(filename, b"")
+
+        elif description == "Sipl keygen cert":
+            write_file(self.upload.sipl_x509, b"")
+
+        elif description == "Sipl keygen key":
+            write_file(self.upload.sipl_pem, b"")
+
         else:
-            raise AssertionError("unknown command executed cmd=(%s)" %
-                " ".join(cmdl))
+            raise AssertionError("unknown command executed description=(%s) "
+                "cmd=(%s)" % (description, " ".join(cmdl)))
 
         return 0
 
@@ -211,6 +225,13 @@
             write_file(self.opal_pem, b"")
             write_file(self.opal_x509, b"")
 
+    def setUpSiplKeys(self, create=True):
+        self.sipl_pem = os.path.join(self.signing_dir, "sipl.pem")
+        self.sipl_x509 = os.path.join(self.signing_dir, "sipl.x509")
+        if create:
+            write_file(self.sipl_pem, b"")
+            write_file(self.sipl_x509, b"")
+
     def openArchive(self, loader_type, version, arch):
         self.path = os.path.join(
             self.temp_dir, "%s_%s_%s.tar.gz" % (loader_type, version, arch))
@@ -247,6 +268,7 @@
         upload.signUefi = FakeMethod()
         upload.signKmod = FakeMethod()
         upload.signOpal = FakeMethod()
+        upload.signSipl = FakeMethod()
         # Under no circumstances is it safe to execute actual commands.
         fake_call = FakeMethod(result=0)
         self.useFixture(MonkeyPatch("subprocess.call", fake_call))
@@ -267,6 +289,7 @@
         self.tarfile.add_file("1.0/empty.efi", b"")
         self.tarfile.add_file("1.0/empty.ko", b"")
         self.tarfile.add_file("1.0/empty.opal", b"")
+        self.tarfile.add_file("1.0/empty.sipl", b"")
         upload = self.process_emulate()
         self.assertContentEqual([], upload.callLog.caller_list())
 
@@ -277,6 +300,7 @@
         self.tarfile.add_file("1.0/empty.efi", b"")
         self.tarfile.add_file("1.0/empty.ko", b"")
         self.tarfile.add_file("1.0/empty.opal", b"")
+        self.tarfile.add_file("1.0/empty.sipl", b"")
         upload = self.process_emulate()
         self.assertContentEqual([], upload.callLog.caller_list())
 
@@ -289,6 +313,7 @@
         self.tarfile.add_file("1.0/empty.efi", b"")
         self.tarfile.add_file("1.0/empty.ko", b"")
         self.tarfile.add_file("1.0/empty.opal", b"")
+        self.tarfile.add_file("1.0/empty.sipl", b"")
         upload = self.process_emulate()
         expected_callers = [
             ('UEFI signing', 1),
@@ -304,6 +329,7 @@
         self.tarfile.add_file("1.0/empty.efi", b"")
         self.tarfile.add_file("1.0/empty.ko", b"")
         self.tarfile.add_file("1.0/empty.opal", b"")
+        self.tarfile.add_file("1.0/empty.sipl", b"")
         upload = self.process_emulate()
         expected_callers = [
             ('UEFI keygen', 1),
@@ -311,9 +337,12 @@
             ('Kmod keygen cert', 1),
             ('Opal keygen key', 1),
             ('Opal keygen cert', 1),
+            ('Sipl keygen key', 1),
+            ('Sipl keygen cert', 1),
             ('UEFI signing', 1),
             ('Kmod signing', 1),
             ('Opal signing', 1),
+            ('Sipl signing', 1),
         ]
         self.assertContentEqual(expected_callers, upload.callLog.caller_list())
 
@@ -387,16 +416,19 @@
         self.setUpUefiKeys()
         self.setUpKmodKeys()
         self.setUpOpalKeys()
+        self.setUpSiplKeys()
         self.openArchive("test", "1.0", "amd64")
         self.tarfile.add_file("1.0/empty.efi", b"")
         self.tarfile.add_file("1.0/empty.ko", b"")
         self.tarfile.add_file("1.0/empty.opal", b"")
+        self.tarfile.add_file("1.0/empty.sipl", b"")
         self.process_emulate()
         self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([
             "1.0/SHA256SUMS",
             "1.0/empty.efi", "1.0/empty.efi.signed", "1.0/control/uefi.crt",
             "1.0/empty.ko", "1.0/empty.ko.sig", "1.0/control/kmod.x509",
             "1.0/empty.opal", "1.0/empty.opal.sig", "1.0/control/opal.x509",
+            "1.0/empty.sipl", "1.0/empty.sipl.sig", "1.0/control/sipl.x509",
             ]))
 
     def test_options_tarball(self):
@@ -405,11 +437,13 @@
         self.setUpUefiKeys()
         self.setUpKmodKeys()
         self.setUpOpalKeys()
+        self.setUpSiplKeys()
         self.openArchive("test", "1.0", "amd64")
         self.tarfile.add_file("1.0/control/options", b"tarball")
         self.tarfile.add_file("1.0/empty.efi", b"")
         self.tarfile.add_file("1.0/empty.ko", b"")
         self.tarfile.add_file("1.0/empty.opal", b"")
+        self.tarfile.add_file("1.0/empty.sipl", b"")
         self.process_emulate()
         self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([
             "1.0/SHA256SUMS",
@@ -425,6 +459,8 @@
                 '1.0/empty.ko', '1.0/empty.ko.sig', '1.0/control/kmod.x509',
                 '1.0/empty.opal', '1.0/empty.opal.sig',
                 '1.0/control/opal.x509',
+                '1.0/empty.sipl', '1.0/empty.sipl.sig',
+                '1.0/control/sipl.x509',
                 ], tarball.getnames())
 
     def test_options_signed_only(self):
@@ -433,17 +469,20 @@
         self.setUpUefiKeys()
         self.setUpKmodKeys()
         self.setUpOpalKeys()
+        self.setUpSiplKeys()
         self.openArchive("test", "1.0", "amd64")
         self.tarfile.add_file("1.0/control/options", b"signed-only")
         self.tarfile.add_file("1.0/empty.efi", b"")
         self.tarfile.add_file("1.0/empty.ko", b"")
         self.tarfile.add_file("1.0/empty.opal", b"")
+        self.tarfile.add_file("1.0/empty.sipl", b"")
         self.process_emulate()
         self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([
             "1.0/SHA256SUMS", "1.0/control/options",
             "1.0/empty.efi.signed", "1.0/control/uefi.crt",
             "1.0/empty.ko.sig", "1.0/control/kmod.x509",
             "1.0/empty.opal.sig", "1.0/control/opal.x509",
+            "1.0/empty.sipl.sig", "1.0/control/sipl.x509",
             ]))
 
     def test_options_tarball_signed_only(self):
@@ -453,11 +492,13 @@
         self.setUpUefiKeys()
         self.setUpKmodKeys()
         self.setUpOpalKeys()
+        self.setUpSiplKeys()
         self.openArchive("test", "1.0", "amd64")
         self.tarfile.add_file("1.0/control/options", b"tarball\nsigned-only")
         self.tarfile.add_file("1.0/empty.efi", b"")
         self.tarfile.add_file("1.0/empty.ko", b"")
         self.tarfile.add_file("1.0/empty.opal", b"")
+        self.tarfile.add_file("1.0/empty.sipl", b"")
         self.process_emulate()
         self.assertThat(self.getSignedPath("test", "amd64"), SignedMatches([
             "1.0/SHA256SUMS",
@@ -471,6 +512,7 @@
                 '1.0/empty.efi.signed', '1.0/control/uefi.crt',
                 '1.0/empty.ko.sig', '1.0/control/kmod.x509',
                 '1.0/empty.opal.sig', '1.0/control/opal.x509',
+                '1.0/empty.sipl.sig', '1.0/control/sipl.x509',
                 ], tarball.getnames())
 
     def test_no_signed_files(self):
@@ -484,6 +526,8 @@
             self.getSignedPath("empty", "amd64"), "1.0", "hello")))
         self.assertEqual(0, upload.signUefi.call_count)
         self.assertEqual(0, upload.signKmod.call_count)
+        self.assertEqual(0, upload.signOpal.call_count)
+        self.assertEqual(0, upload.signSipl.call_count)
 
     def test_already_exists(self):
         # If the target directory already exists, processing fails.
@@ -559,15 +603,20 @@
     def test_correct_kmod_openssl_config(self):
         # Check that calling generateOpensslConfig() will return an appropriate
         # openssl configuration.
+        self.setUpPPA()
         upload = SigningUpload()
-        text = upload.generateOpensslConfig('Kmod', 'something-unique')
+        upload.setTargetDirectory(
+            self.archive, "test_1.0_amd64.tar.gz", "distroseries")
+        text = upload.generateOpensslConfig('Kmod', upload.openssl_config_kmod)
 
-        cn_re = re.compile(r'\bCN\s*=\s*something-unique\b')
+        id_re = re.compile(r'^# KMOD openssl config\b')
+        cn_re = re.compile(r'\bCN\s*=\s*' + self.testcase_cn[4:-1] + '\s+Kmod')
         eku_re = re.compile(
             r'\bextendedKeyUsage\s*=\s*'
             r'codeSigning,1.3.6.1.4.1.2312.16.1.2\s*\b')
 
         self.assertIn('[ req ]', text)
+        self.assertIsNotNone(id_re.search(text))
         self.assertIsNotNone(cn_re.search(text))
         self.assertIsNotNone(eku_re.search(text))
 
@@ -640,12 +689,17 @@
     def test_correct_opal_openssl_config(self):
         # Check that calling generateOpensslConfig() will return an appropriate
         # openssl configuration.
+        self.setUpPPA()
         upload = SigningUpload()
-        text = upload.generateOpensslConfig('Opal', 'something-unique')
+        upload.setTargetDirectory(
+            self.archive, "test_1.0_amd64.tar.gz", "distroseries")
+        text = upload.generateOpensslConfig('Opal', upload.openssl_config_opal)
 
-        cn_re = re.compile(r'\bCN\s*=\s*something-unique\b')
+        id_re = re.compile(r'^# OPAL openssl config\b')
+        cn_re = re.compile(r'\bCN\s*=\s*' + self.testcase_cn[4:-1] + '\s+Opal')
 
         self.assertIn('[ req ]', text)
+        self.assertIsNotNone(id_re.search(text))
         self.assertIsNotNone(cn_re.search(text))
         self.assertNotIn('extendedKeyUsage', text)
 
@@ -715,6 +769,89 @@
             ]
         self.assertEqual(expected_cmd, args)
 
+    def test_correct_sipl_openssl_config(self):
+        # Check that calling generateOpensslConfig() will return an appropriate
+        # openssl configuration.
+        self.setUpPPA()
+        upload = SigningUpload()
+        upload.setTargetDirectory(
+            self.archive, "test_1.0_amd64.tar.gz", "distroseries")
+        text = upload.generateOpensslConfig('Sipl', upload.openssl_config_sipl)
+
+        id_re = re.compile(r'^# SIPL openssl config\b')
+        cn_re = re.compile(r'\bCN\s*=\s*' + self.testcase_cn[4:-1] + '\s+Sipl')
+
+        self.assertIn('[ req ]', text)
+        self.assertIsNotNone(id_re.search(text))
+        self.assertIsNotNone(cn_re.search(text))
+        self.assertNotIn('extendedKeyUsage', text)
+
+    def test_correct_sipl_signing_command_executed(self):
+        # Check that calling signSipl() will generate the expected command
+        # when appropriate keys are present.
+        self.setUpSiplKeys()
+        fake_call = FakeMethod(result=0)
+        self.useFixture(MonkeyPatch("subprocess.call", fake_call))
+        upload = SigningUpload()
+        upload.generateSiplKeys = FakeMethod()
+        upload.setTargetDirectory(
+            self.archive, "test_1.0_amd64.tar.gz", "distroseries")
+        upload.signSipl('t.sipl')
+        self.assertEqual(1, fake_call.call_count)
+        # Assert command form.
+        args = fake_call.calls[0][0][0]
+        expected_cmd = [
+            'kmodsign', '-D', 'sha512', self.sipl_pem, self.sipl_x509,
+            't.sipl', 't.sipl.sig'
+            ]
+        self.assertEqual(expected_cmd, args)
+        self.assertEqual(0, upload.generateSiplKeys.call_count)
+
+    def test_correct_sipl_signing_command_executed_no_keys(self):
+        # Check that calling signSipl() will generate no commands when
+        # no keys are present.
+        self.setUpSiplKeys(create=False)
+        fake_call = FakeMethod(result=0)
+        self.useFixture(MonkeyPatch("subprocess.call", fake_call))
+        upload = SigningUpload()
+        upload.generateSiplKeys = FakeMethod()
+        upload.setTargetDirectory(
+            self.archive, "test_1.0_amd64.tar.gz", "distroseries")
+        upload.signOpal('t.sipl')
+        self.assertEqual(0, fake_call.call_count)
+        self.assertEqual(0, upload.generateSiplKeys.call_count)
+
+    def test_correct_sipl_keygen_command_executed(self):
+        # Check that calling generateSiplKeys() will generate the
+        # expected command.
+        self.setUpPPA()
+        self.setUpSiplKeys(create=False)
+        fake_call = FakeMethod(result=0)
+        self.useFixture(MonkeyPatch("subprocess.call", fake_call))
+        upload = SigningUpload()
+        upload.setTargetDirectory(
+            self.archive, "test_1.0_amd64.tar.gz", "distroseries")
+        upload.generateSiplKeys()
+        self.assertEqual(2, fake_call.call_count)
+        # Assert the actual command matches.
+        args = fake_call.calls[0][0][0]
+        # Sanitise the keygen tmp file.
+        if args[11].endswith('.keygen'):
+            args[11] = 'XXX.keygen'
+        expected_cmd = [
+            'openssl', 'req', '-new', '-nodes', '-utf8', '-sha512',
+            '-days', '3650', '-batch', '-x509',
+            '-config', 'XXX.keygen', '-outform', 'PEM',
+            '-out', self.sipl_pem, '-keyout', self.sipl_pem
+            ]
+        self.assertEqual(expected_cmd, args)
+        args = fake_call.calls[1][0][0]
+        expected_cmd = [
+            'openssl', 'x509', '-in', self.sipl_pem, '-outform', 'DER',
+            '-out', self.sipl_x509
+            ]
+        self.assertEqual(expected_cmd, args)
+
     def test_signs_uefi_image(self):
         # Each image in the tarball is signed.
         self.setUpUefiKeys()
@@ -739,6 +876,14 @@
         upload = self.process()
         self.assertEqual(1, upload.signOpal.call_count)
 
+    def test_signs_sipl_image(self):
+        # Each image in the tarball is signed.
+        self.setUpSiplKeys()
+        self.openArchive("test", "1.0", "amd64")
+        self.tarfile.add_file("1.0/empty.sipl", b"")
+        upload = self.process()
+        self.assertEqual(1, upload.signSipl.call_count)
+
     def test_signs_combo_image(self):
         # Each image in the tarball is signed.
         self.setUpKmodKeys()
@@ -749,10 +894,15 @@
         self.tarfile.add_file("1.0/empty.opal", b"")
         self.tarfile.add_file("1.0/empty2.opal", b"")
         self.tarfile.add_file("1.0/empty3.opal", b"")
+        self.tarfile.add_file("1.0/empty.sipl", b"")
+        self.tarfile.add_file("1.0/empty2.sipl", b"")
+        self.tarfile.add_file("1.0/empty3.sipl", b"")
+        self.tarfile.add_file("1.0/empty4.sipl", b"")
         upload = self.process()
         self.assertEqual(1, upload.signUefi.call_count)
         self.assertEqual(2, upload.signKmod.call_count)
         self.assertEqual(3, upload.signOpal.call_count)
+        self.assertEqual(4, upload.signSipl.call_count)
 
     def test_installed(self):
         # Files in the tarball are installed correctly.
@@ -898,16 +1048,55 @@
         self.assertEqual(stat.S_IMODE(os.stat(self.opal_pem).st_mode), 0o600)
         self.assertEqual(stat.S_IMODE(os.stat(self.opal_x509).st_mode), 0o644)
 
+    def test_create_sipl_keys_autokey_off(self):
+        # Keys are not created.
+        self.setUpSiplKeys(create=False)
+        self.assertFalse(os.path.exists(self.sipl_pem))
+        self.assertFalse(os.path.exists(self.sipl_x509))
+        fake_call = FakeMethod(result=0)
+        self.useFixture(MonkeyPatch("subprocess.call", fake_call))
+        upload = SigningUpload()
+        upload.callLog = FakeMethodCallLog(upload=upload)
+        upload.setTargetDirectory(
+            self.archive, "test_1.0_amd64.tar.gz", "distroseries")
+        upload.signOpal(os.path.join(self.makeTemporaryDirectory(), 't.sipl'))
+        self.assertEqual(0, upload.callLog.caller_count('Sipl keygen key'))
+        self.assertEqual(0, upload.callLog.caller_count('Sipl keygen cert'))
+        self.assertFalse(os.path.exists(self.sipl_pem))
+        self.assertFalse(os.path.exists(self.sipl_x509))
+
+    def test_create_sipl_keys_autokey_on(self):
+        # Keys are created on demand.
+        self.setUpPPA()
+        self.setUpSiplKeys(create=False)
+        self.assertFalse(os.path.exists(self.sipl_pem))
+        self.assertFalse(os.path.exists(self.sipl_x509))
+        fake_call = FakeMethod(result=0)
+        self.useFixture(MonkeyPatch("subprocess.call", fake_call))
+        upload = SigningUpload()
+        upload.callLog = FakeMethodCallLog(upload=upload)
+        upload.setTargetDirectory(
+            self.archive, "test_1.0_amd64.tar.gz", "distroseries")
+        upload.signSipl(os.path.join(self.makeTemporaryDirectory(), 't.sipl'))
+        self.assertEqual(1, upload.callLog.caller_count('Sipl keygen key'))
+        self.assertEqual(1, upload.callLog.caller_count('Sipl keygen cert'))
+        self.assertTrue(os.path.exists(self.sipl_pem))
+        self.assertTrue(os.path.exists(self.sipl_x509))
+        self.assertEqual(stat.S_IMODE(os.stat(self.sipl_pem).st_mode), 0o600)
+        self.assertEqual(stat.S_IMODE(os.stat(self.sipl_x509).st_mode), 0o644)
+
     def test_checksumming_tree(self):
         # Specifying no options should leave us with an open tree,
         # confirm it is checksummed.
         self.setUpUefiKeys()
         self.setUpKmodKeys()
         self.setUpOpalKeys()
+        self.setUpSiplKeys()
         self.openArchive("test", "1.0", "amd64")
         self.tarfile.add_file("1.0/empty.efi", b"")
         self.tarfile.add_file("1.0/empty.ko", b"")
         self.tarfile.add_file("1.0/empty.opal", b"")
+        self.tarfile.add_file("1.0/empty.sipl", b"")
         self.process_emulate()
         sha256file = os.path.join(self.getSignedPath("test", "amd64"),
              "1.0", "SHA256SUMS")
@@ -926,6 +1115,7 @@
         self.tarfile.add_file("1.0/empty.efi", b"")
         self.tarfile.add_file("1.0/empty.ko", b"")
         self.tarfile.add_file("1.0/empty.opal", b"")
+        self.tarfile.add_file("1.0/empty.sipl", b"")
         self.process_emulate()
         sha256file = os.path.join(self.getSignedPath("test", "amd64"),
              "1.0", "SHA256SUMS")
@@ -949,6 +1139,7 @@
         self.tarfile.add_file("1.0/empty.efi", b"")
         self.tarfile.add_file("1.0/empty.ko", b"")
         self.tarfile.add_file("1.0/empty.opal", b"")
+        self.tarfile.add_file("1.0/empty.sipl", b"")
         self.process_emulate()
         sha256file = os.path.join(self.getSignedPath("test", "amd64"),
              "1.0", "SHA256SUMS")
@@ -982,6 +1173,7 @@
         self.tarfile.add_file("1.0/empty.efi", "")
         self.tarfile.add_file("1.0/empty.ko", "")
         self.tarfile.add_file("1.0/empty.opal", "")
+        self.tarfile.add_file("1.0/empty.sipl", "")
         self.process_emulate()
         sha256file = os.path.join(self.getSignedPath("test", "amd64"),
              "1.0", "SHA256SUMS")


Follow ups