]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
ukify: Introduce --certificate-provider= option 35057/head
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Wed, 6 Nov 2024 17:09:37 +0000 (18:09 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Thu, 7 Nov 2024 19:33:08 +0000 (20:33 +0100)
This translates to --certificate-source=provider:<provider> for
signing tools invoked by ukify.

man/ukify.xml
src/ukify/test/test_ukify.py
src/ukify/ukify.py

index 6a697ee6e149ef3194729b5a260cbaf06cfd85dc..c42d6ae5c757f58d5f56f477022d10372e1b64df 100644 (file)
           <xi:include href="version-info.xml" xpointer="v257"/></listitem>
         </varlistentry>
 
+        <varlistentry>
+          <term><varname>CertificateProvider=<replaceable>PROVIDER</replaceable></varname></term>
+          <term><option>--certificate-provider=<replaceable>PROVIDER</replaceable></option></term>
+
+          <listitem><para>An OpenSSL provider to be used for loading the certificate used to sign the
+          resulting binary and PCR measurements. This option can only be used when using
+          <command>systemd-sbsign</command> as the signing tool.</para>
+
+          <xi:include href="version-info.xml" xpointer="v257"/></listitem>
+        </varlistentry>
+
         <varlistentry>
           <term><varname>SignKernel=<replaceable>BOOL</replaceable></varname></term>
           <term><option>--sign-kernel</option></term>
index 70e1a4e1d0a37cbe7e2353232fe6df375c7466ae..9eebf7eca1b22f7001aa10b9215f76478f4efd74 100755 (executable)
@@ -138,7 +138,7 @@ def test_apply_config(tmp_path):
 
     assert ns._groups == ['NAME']
     assert ns.pcr_private_keys == ['some/path7']
-    assert ns.pcr_public_keys == [pathlib.Path('some/path8')]
+    assert ns.pcr_public_keys == ['some/path8']
     assert ns.phase_path_groups == [['enter-initrd:leave-initrd:sysinit:ready:shutdown:final']]
 
     ukify.finalize_options(ns)
@@ -156,12 +156,12 @@ def test_apply_config(tmp_path):
     assert ns.pcr_banks == ['sha512', 'sha1']
     assert ns.signing_engine == 'engine1'
     assert ns.sb_key == 'some/path5'
-    assert ns.sb_cert == 'some/path6'
+    assert ns.sb_cert == pathlib.Path('some/path6')
     assert ns.sign_kernel is False
 
     assert ns._groups == ['NAME']
     assert ns.pcr_private_keys == ['some/path7']
-    assert ns.pcr_public_keys == [pathlib.Path('some/path8')]
+    assert ns.pcr_public_keys == ['some/path8']
     assert ns.phase_path_groups == [['enter-initrd:leave-initrd:sysinit:ready:shutdown:final']]
 
 def test_parse_args_minimal():
@@ -207,11 +207,11 @@ def test_parse_args_many_deprecated():
     assert opts.uname == '1.2.3'
     assert opts.stub == pathlib.Path('STUBPATH')
     assert opts.pcr_private_keys == ['PKEY1']
-    assert opts.pcr_public_keys == [pathlib.Path('PKEY2')]
+    assert opts.pcr_public_keys == ['PKEY2']
     assert opts.pcr_banks == ['SHA1', 'SHA256']
     assert opts.signing_engine == 'ENGINE'
     assert opts.sb_key == 'SBKEY'
-    assert opts.sb_cert == 'SBCERT'
+    assert opts.sb_cert == pathlib.Path('SBCERT')
     assert opts.sign_kernel is False
     assert opts.tools == [pathlib.Path('TOOLZ/')]
     assert opts.output == pathlib.Path('OUTPUT')
@@ -253,11 +253,11 @@ def test_parse_args_many():
     assert opts.uname == '1.2.3'
     assert opts.stub == pathlib.Path('STUBPATH')
     assert opts.pcr_private_keys == ['PKEY1']
-    assert opts.pcr_public_keys == [pathlib.Path('PKEY2')]
+    assert opts.pcr_public_keys == ['PKEY2']
     assert opts.pcr_banks == ['SHA1', 'SHA256']
     assert opts.signing_engine == 'ENGINE'
     assert opts.sb_key == 'SBKEY'
-    assert opts.sb_cert == 'SBCERT'
+    assert opts.sb_cert == pathlib.Path('SBCERT')
     assert opts.sign_kernel is False
     assert opts.tools == [pathlib.Path('TOOLZ/')]
     assert opts.output == pathlib.Path('OUTPUT')
@@ -360,13 +360,12 @@ def test_config_priority(tmp_path):
     assert opts.uname == '1.2.3'
     assert opts.stub == pathlib.Path('STUBPATH')
     assert opts.pcr_private_keys == ['PKEY1', 'some/path7']
-    assert opts.pcr_public_keys == [pathlib.Path('PKEY2'),
-                                    pathlib.Path('some/path8')]
+    assert opts.pcr_public_keys == ['PKEY2', 'some/path8']
     assert opts.pcr_banks == ['SHA1', 'SHA256']
     assert opts.signing_engine == 'ENGINE'
     assert opts.signtool == ukify.SbSign # from args
     assert opts.sb_key == 'SBKEY' # from args
-    assert opts.sb_cert == 'SBCERT' # from args
+    assert opts.sb_cert == pathlib.Path('SBCERT') # from args
     assert opts.sb_certdir == 'some/path5' # from config
     assert opts.sb_cert_name == 'some/name1' # from config
     assert opts.sign_kernel is False
index ef4e9264c28a5fa7a16c10f9582c484f31b3df3d..355e3f99f4132b6e3a754446f439541c64db9275 100755 (executable)
@@ -249,21 +249,22 @@ class UkifyConfig:
     output: Optional[str]
     pcr_banks: list[str]
     pcr_private_keys: list[str]
-    pcr_public_keys: list[Path]
+    pcr_public_keys: list[str]
     pcrpkey: Optional[Path]
     phase_path_groups: Optional[list[str]]
     profile: Union[str, Path, None]
-    sb_cert: Path
+    sb_cert: Union[str, Path, None]
     sb_cert_name: Optional[str]
     sb_cert_validity: int
     sb_certdir: Path
-    sb_key: Optional[Path]
+    sb_key: Union[str, Path, None]
     sbat: Optional[list[str]]
     sections: list['Section']
     sections_by_name: dict[str, 'Section']
     sign_kernel: bool
     signing_engine: Optional[str]
     signing_provider: Optional[str]
+    certificate_provider: Optional[str]
     signtool: Optional[type['SignTool']]
     splash: Optional[Path]
     stub: Path
@@ -554,6 +555,11 @@ class SystemdSbSign(SignTool):
                 if opts.signing_provider is not None
                 else []
             ),
+            *(
+                ['--certificate-source', f'provider:{opts.certificate_provider}']
+                if opts.certificate_provider is not None
+                else []
+            ),
             input_f,
             '--output', output_f,
         ]  # fmt: skip
@@ -666,7 +672,7 @@ def combine_signatures(pcrsigs: list[dict[str, str]]) -> str:
     return json.dumps(combined)
 
 
-def key_path_groups(opts: UkifyConfig) -> Iterator[tuple[str, Optional[Path], Optional[str]]]:
+def key_path_groups(opts: UkifyConfig) -> Iterator[tuple[str, Optional[str], Optional[str]]]:
     if not opts.pcr_private_keys:
         return
 
@@ -757,6 +763,10 @@ def call_systemd_measure(uki: UKI, opts: UkifyConfig, profile_start: int = 0) ->
                 extra += [f'--certificate={pub_key}']
             elif pub_key:
                 extra += [f'--public-key={pub_key}']
+
+            if opts.certificate_provider is not None:
+                extra += [f'--certificate-source=provider:{opts.certificate_provider}']
+
             extra += [f'--phase={phase_path}' for phase_path in group or ()]
 
             print('+', shell_join(cmd + extra))  # type: ignore
@@ -1007,34 +1017,30 @@ def make_uki(opts: UkifyConfig) -> None:
 
     pcrpkey: Union[bytes, Path, None] = opts.pcrpkey
     if pcrpkey is None:
+        measure_tool = find_tool('systemd-measure', '/usr/lib/systemd/systemd-measure')
+        cmd = [measure_tool, 'pcrpkey']
+
         if opts.pcr_public_keys and len(opts.pcr_public_keys) == 1:
-            pcrpkey = opts.pcr_public_keys[0]
-            # If we are getting a certificate when using an engine or provider, we need to convert it to
-            # public key format.
-            if (opts.signing_engine or opts.signing_provider) and Path(pcrpkey).exists():
-                from cryptography.hazmat.primitives import serialization
-                from cryptography.x509 import load_pem_x509_certificate
-
-                try:
-                    cert = load_pem_x509_certificate(Path(pcrpkey).read_bytes())
-                except ValueError:
-                    raise ValueError(f'{pcrpkey} must be an X.509 certificate when signing with an engine')
-                else:
-                    pcrpkey = cert.public_key().public_bytes(
-                        encoding=serialization.Encoding.PEM,
-                        format=serialization.PublicFormat.SubjectPublicKeyInfo,
-                    )
+            # If we're using an engine or provider, the public key will be an X.509 certificate.
+            if opts.signing_engine or opts.signing_provider:
+                cmd += ['--certificate', opts.pcr_public_keys[0]]
+                if opts.certificate_provider:
+                    cmd += ['--certificate-source', f'provider:{opts.certificate_provider}']
+            else:
+                cmd += ['--public-key', opts.pcr_public_keys[0]]
+
+            print('+', shell_join(cmd))
+            pcrpkey = subprocess.check_output(cmd)
         elif opts.pcr_private_keys and len(opts.pcr_private_keys) == 1:
-            from cryptography.hazmat.primitives import serialization
+            cmd += ['--private-key', Path(opts.pcr_private_keys[0])]
 
-            privkey = serialization.load_pem_private_key(
-                Path(opts.pcr_private_keys[0]).read_bytes(),
-                password=None,
-            )
-            pcrpkey = privkey.public_key().public_bytes(
-                encoding=serialization.Encoding.PEM,
-                format=serialization.PublicFormat.SubjectPublicKeyInfo,
-            )
+            if opts.signing_engine:
+                cmd += ['--private-key-source', f'engine:{opts.signing_engine}']
+            if opts.signing_provider:
+                cmd += ['--private-key-source', f'provider:{opts.signing_provider}']
+
+            print('+', shell_join(cmd))
+            pcrpkey = subprocess.check_output(cmd)
 
     sections = [
         # name,      content,         measure?
@@ -1270,9 +1276,9 @@ def generate_keys(opts: UkifyConfig) -> None:
         )
         print(f'Writing SecureBoot private key to {opts.sb_key}')
         with temporary_umask(0o077):
-            opts.sb_key.write_bytes(key_pem)
+            Path(opts.sb_key).write_bytes(key_pem)
         print(f'Writing SecureBoot certificate to {opts.sb_cert}')
-        opts.sb_cert.write_bytes(cert_pem)
+        Path(opts.sb_cert).write_bytes(cert_pem)
 
         work = True
 
@@ -1284,7 +1290,7 @@ def generate_keys(opts: UkifyConfig) -> None:
             Path(priv_key).write_bytes(priv_key_pem)
         if pub_key:
             print(f'Writing public key for PCR signing to {pub_key}')
-            pub_key.write_bytes(pub_key_pem)
+            Path(pub_key).write_bytes(pub_key_pem)
 
         work = True
 
@@ -1674,6 +1680,12 @@ CONFIG_ITEMS = [
         help='OpenSSL provider to use for signing',
         config_key='UKI/SigningProvider',
     ),
+    ConfigItem(
+        '--certificate-provider',
+        metavar='PROVIDER',
+        help='OpenSSL provider to load certificate from',
+        config_key='UKI/CertificateProvider',
+    ),
     ConfigItem(
         '--signtool',
         choices=('sbsign', 'pesign', 'systemd-sbsign'),
@@ -1746,7 +1758,6 @@ CONFIG_ITEMS = [
         '--pcr-public-key',
         dest='pcr_public_keys',
         metavar='PATH',
-        type=Path,
         action='append',
         help='public part of the keypair or engine/provider designation for signing PCR signatures',
         config_key='PCRSignature:/PCRPublicKey',
@@ -1982,11 +1993,11 @@ def finalize_options(opts: argparse.Namespace) -> None:
     if opts.signing_engine and opts.signing_provider:
         raise ValueError('Only one of --signing-engine= and --signing-provider= may be specified')
 
-    if opts.signing_engine is None and opts.signing_provider is None:
-        if opts.sb_key:
-            opts.sb_key = Path(opts.sb_key)
-        if opts.sb_cert:
-            opts.sb_cert = Path(opts.sb_cert)
+    if opts.signing_engine is None and opts.signing_provider is None and opts.sb_key:
+        opts.sb_key = Path(opts.sb_key)
+
+    if opts.certificate_provider is None and opts.sb_cert:
+        opts.sb_cert = Path(opts.sb_cert)
 
     if bool(opts.sb_key) ^ bool(opts.sb_cert):
         # one param only given, sbsign needs both
@@ -2012,6 +2023,9 @@ def finalize_options(opts: argparse.Namespace) -> None:
     if opts.signing_provider and opts.signtool != SystemdSbSign:
         raise ValueError('--signing-provider= can only be used with--signtool=systemd-sbsign')
 
+    if opts.certificate_provider and opts.signtool != SystemdSbSign:
+        raise ValueError('--certificate-provider= can only be used with--signtool=systemd-sbsign')
+
     if opts.sign_kernel and not opts.sb_key and not opts.sb_cert_name:
         raise ValueError(
             '--sign-kernel requires either --secureboot-private-key= and --secureboot-certificate= (for sbsign) or --secureboot-certificate-name= (for pesign) to be specified'  # noqa: E501