]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
ukify: Derive public key from private key if not specified
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Mon, 10 Jul 2023 11:50:30 +0000 (13:50 +0200)
committerLuca Boccassi <luca.boccassi@gmail.com>
Thu, 13 Jul 2023 15:31:05 +0000 (16:31 +0100)
src/ukify/test/test_ukify.py
src/ukify/ukify.py

index f79a13a36f858db27cb74db6f1f0b368e9a7c634..0b1d7f17f151c18e042d529d811d7a81cd91e383 100755 (executable)
@@ -609,46 +609,48 @@ def test_pcr_signing(kernel_initrd, tmpdir):
         '--cmdline=ARG1 ARG2 ARG3',
         '--os-release=ID=foobar\n',
         '--pcr-banks=sha1',   # use sha1 because it doesn't really matter
-        f'--pcrpkey={pub.name}',
-        f'--pcr-public-key={pub.name}',
         f'--pcr-private-key={priv.name}',
     ])
 
-    try:
-        ukify.check_inputs(opts)
-    except OSError as e:
-        pytest.skip(str(e))
-
-    ukify.make_uki(opts)
-
-    # let's check that objdump likes the resulting file
-    dump = subprocess.check_output(['objdump', '-h', output], text=True)
-
-    for sect in 'text osrel cmdline linux initrd uname pcrsig'.split():
-        assert re.search(fr'^\s*\d+\s+.{sect}\s+0', dump, re.MULTILINE)
-
-    # objcopy fails when called without an output argument (EPERM).
-    # It also fails when called with /dev/null (file truncated).
-    # It also fails when called with /dev/zero (because it reads the
-    # output file, infinitely in this case.)
-    # So let's just call it with a dummy output argument.
-    subprocess.check_call([
-        'objcopy',
-        *(f'--dump-section=.{n}={tmpdir}/out.{n}' for n in (
-            'pcrpkey', 'pcrsig', 'osrel', 'uname', 'cmdline')),
-        output,
-        tmpdir / 'dummy',
-    ],
-        text=True)
-
-    assert open(tmpdir / 'out.pcrpkey').read() == open(pub.name).read()
-    assert open(tmpdir / 'out.osrel').read() == 'ID=foobar\n'
-    assert open(tmpdir / 'out.uname').read() == '1.2.3'
-    assert open(tmpdir / 'out.cmdline').read() == 'ARG1 ARG2 ARG3'
-    sig = open(tmpdir / 'out.pcrsig').read()
-    sig = json.loads(sig)
-    assert list(sig.keys()) == ['sha1']
-    assert len(sig['sha1']) == 4   # four items for four phases
+    # If the public key is not explicitly specified, it is derived automatically. Let's make sure everything
+    # works as expected both when the public keys is specified explicitly and when it is derived from the
+    # private key.
+    for extra in ([f'--pcrpkey={pub.name}', f'--pcr-public-key={pub.name}'], []):
+        try:
+            ukify.check_inputs(opts + extra)
+        except OSError as e:
+            pytest.skip(str(e))
+
+        ukify.make_uki(opts + extra)
+
+        # let's check that objdump likes the resulting file
+        dump = subprocess.check_output(['objdump', '-h', output], text=True)
+
+        for sect in 'text osrel cmdline linux initrd uname pcrsig'.split():
+            assert re.search(fr'^\s*\d+\s+.{sect}\s+0', dump, re.MULTILINE)
+
+        # objcopy fails when called without an output argument (EPERM).
+        # It also fails when called with /dev/null (file truncated).
+        # It also fails when called with /dev/zero (because it reads the
+        # output file, infinitely in this case.)
+        # So let's just call it with a dummy output argument.
+        subprocess.check_call([
+            'objcopy',
+            *(f'--dump-section=.{n}={tmpdir}/out.{n}' for n in (
+                'pcrpkey', 'pcrsig', 'osrel', 'uname', 'cmdline')),
+            output,
+            tmpdir / 'dummy',
+        ],
+            text=True)
+
+        assert open(tmpdir / 'out.pcrpkey').read() == open(pub.name).read()
+        assert open(tmpdir / 'out.osrel').read() == 'ID=foobar\n'
+        assert open(tmpdir / 'out.uname').read() == '1.2.3'
+        assert open(tmpdir / 'out.cmdline').read() == 'ARG1 ARG2 ARG3'
+        sig = open(tmpdir / 'out.pcrsig').read()
+        sig = json.loads(sig)
+        assert list(sig.keys()) == ['sha1']
+        assert len(sig['sha1']) == 4   # four items for four phases
 
 def test_pcr_signing2(kernel_initrd, tmpdir):
     if kernel_initrd is None:
index 9ee591d593142ca13bc84101c1c9b89ed5185cf0..282f46f4c9f1287d7659ee7ad97370b3c86ee393 100755 (executable)
@@ -729,11 +729,17 @@ def make_uki(opts):
     uki = UKI(opts.stub)
     initrd = join_initrds(opts.initrd)
 
-    # TODO: derive public key from opts.pcr_private_keys?
     pcrpkey = opts.pcrpkey
     if pcrpkey is None:
         if opts.pcr_public_keys and len(opts.pcr_public_keys) == 1:
             pcrpkey = opts.pcr_public_keys[0]
+        elif opts.pcr_private_keys and len(opts.pcr_private_keys) == 1:
+            import cryptography.hazmat.primitives.serialization as serialization
+            privkey = serialization.load_pem_private_key(opts.pcr_private_keys[0].read_bytes(), password=None)
+            pcrpkey = privkey.public_key().public_bytes(
+                encoding=serialization.Encoding.PEM,
+                format=serialization.PublicFormat.SubjectPublicKeyInfo,
+            )
 
     sections = [
         # name,      content,         measure?