]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Add support for systemd-sbsign and --certificate-source
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Tue, 5 Nov 2024 12:56:28 +0000 (13:56 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Mon, 11 Nov 2024 15:20:30 +0000 (16:20 +0100)
Matching PR for https://github.com/systemd/systemd/pull/35021
and https://github.com/systemd/systemd/pull/35057

mkosi/__init__.py
mkosi/bootloader.py
mkosi/config.py
mkosi/resources/man/mkosi.1.md
tests/test_json.py

index afcc21b00416800cfd8fb68490ab2511ab04579f..688b23ebc87c19a42c06cbbec42bf48c4a2bc66c 100644 (file)
@@ -39,6 +39,7 @@ from mkosi.bootloader import (
     pesign_prepare,
     prepare_grub_config,
     python_binary,
+    run_systemd_sign_tool,
     shim_second_stage_binary,
     sign_efi_binary,
     want_efi,
@@ -53,6 +54,7 @@ from mkosi.config import (
     ArtifactOutput,
     Bootloader,
     Cacheonly,
+    CertificateSourceType,
     Compression,
     Config,
     ConfigFeature,
@@ -1519,20 +1521,40 @@ def run_ukify(
 
         if context.config.secure_boot_sign_tool != SecureBootSignTool.pesign:
             cmd += [
-                "--signtool", "sbsign",
-                "--secureboot-certificate", workdir(context.config.secure_boot_certificate),
-            ]  # fmt: skip
-            opt += [
-                "--ro-bind", context.config.secure_boot_certificate, workdir(context.config.secure_boot_certificate),  # noqa: E501
+                "--signtool", (
+                    "sbsign"
+                    if context.config.secure_boot_sign_tool == SecureBootSignTool.sbsign
+                    else "systemd-sbsign"
+                ),
             ]  # fmt: skip
+
+            if (
+                context.config.secure_boot_key_source.type != KeySourceType.file
+                or context.config.secure_boot_certificate_source.type != CertificateSourceType.file
+            ):
+                opt += ["--bind", "/run", "/run"]
+
             if context.config.secure_boot_key_source.type == KeySourceType.engine:
                 cmd += ["--signing-engine", context.config.secure_boot_key_source.source]
-                opt += ["--bind", "/run", "/run"]
+            elif context.config.secure_boot_key_source.type == KeySourceType.provider:
+                cmd += ["--signing-provider", context.config.secure_boot_key_source.source]
+
             if context.config.secure_boot_key.exists():
                 cmd += ["--secureboot-private-key", workdir(context.config.secure_boot_key)]
                 opt += ["--ro-bind", context.config.secure_boot_key, workdir(context.config.secure_boot_key)]
             else:
                 cmd += ["--secureboot-private-key", context.config.secure_boot_key]
+
+            if context.config.secure_boot_certificate_source.type == CertificateSourceType.provider:
+                cmd += ["--certificate-provider", context.config.secure_boot_certificate_source.source]
+
+            if context.config.secure_boot_certificate.exists():
+                cmd += ["--secureboot-certificate", workdir(context.config.secure_boot_certificate)]
+                opt += [
+                    "--ro-bind", context.config.secure_boot_certificate, workdir(context.config.secure_boot_certificate),  # noqa: E501
+                ]  # fmt: skip
+            else:
+                cmd += ["--secureboot-certificate", context.config.secure_boot_certificate]
         else:
             pesign_prepare(context)
             cmd += [
@@ -1601,15 +1623,31 @@ def build_uki(
             "--pcr-banks", "sha256",
         ]  # fmt: skip
 
+        # If we're providing the private key via an engine or provider, we have to pass in a X.509
+        # certificate via --pcr-public-key as well.
+        if context.config.sign_expected_pcr_key_source.type != KeySourceType.file:
+            if context.config.sign_expected_pcr_certificate_source.type == CertificateSourceType.provider:
+                arguments += [
+                    "--certificate-provider",
+                    f"provider:{context.config.sign_expected_pcr_certificate_source.source}",
+                ]
+
+            options += ["--bind", "/run", "/run"]
+
+            if context.config.sign_expected_pcr_certificate.exists():
+                arguments += [
+                    "--pcr-public-key", workdir(context.config.sign_expected_pcr_certificate),
+                ]  # fmt: skip
+                options += [
+                    "--ro-bind", context.config.sign_expected_pcr_certificate, workdir(context.config.sign_expected_pcr_certificate),  # noqa: E501
+                ]  # fmt: skip
+            else:
+                arguments += ["--pcr-public-key", context.config.sign_expected_pcr_certificate]
+
         if context.config.sign_expected_pcr_key_source.type == KeySourceType.engine:
-            arguments += [
-                "--signing-engine", context.config.sign_expected_pcr_key_source.source,
-                "--pcr-public-key", workdir(context.config.sign_expected_pcr_certificate),
-            ]  # fmt: skip
-            options += [
-                "--ro-bind", context.config.sign_expected_pcr_certificate, workdir(context.config.sign_expected_pcr_certificate),  # noqa: E501
-                "--bind", "/run", "/run",
-            ]  # fmt: skip
+            arguments += ["--signing-engine", context.config.sign_expected_pcr_key_source.source]
+        elif context.config.sign_expected_pcr_key_source.type == KeySourceType.provider:
+            arguments += ["--signing-provider", context.config.sign_expected_pcr_key_source.source]
 
         if context.config.sign_expected_pcr_key.exists():
             arguments += ["--pcr-private-key", workdir(context.config.sign_expected_pcr_key)]
@@ -2460,6 +2498,11 @@ def check_inputs(config: Config) -> None:
     if config.secure_boot_key_source != config.sign_expected_pcr_key_source:
         die("Secure boot key source and expected PCR signatures key source have to be the same")
 
+    if config.secure_boot_certificate_source != config.sign_expected_pcr_certificate_source:
+        die(
+            "Secure boot certificate source and expected PCR signatures certificate source have to be the same"  # noqa: E501
+        )  # fmt: skip
+
     if config.verity == ConfigFeature.enabled and not config.verity_key:
         die(
             "Verity= is enabled but no verity key is configured",
@@ -3067,23 +3110,6 @@ def make_image(
     if context.config.passphrase:
         cmdline += ["--key-file", workdir(context.config.passphrase)]
         opts += ["--ro-bind", context.config.passphrase, workdir(context.config.passphrase)]
-    if verity:
-        assert context.config.verity_key
-        assert context.config.verity_certificate
-
-        if context.config.verity_key_source.type != KeySourceType.file:
-            cmdline += ["--private-key-source", str(context.config.verity_key_source)]
-            opts += ["--bind", "/run", "/run"]
-        if context.config.verity_key.exists():
-            cmdline += ["--private-key", workdir(context.config.verity_key)]
-            opts += ["--ro-bind", context.config.verity_key, workdir(context.config.verity_key)]
-        else:
-            cmdline += ["--private-key", context.config.verity_key]
-
-        cmdline += ["--certificate", workdir(context.config.verity_certificate)]
-        opts += [
-            "--ro-bind", context.config.verity_certificate, workdir(context.config.verity_certificate),
-        ]  # fmt: skip
     if skip:
         cmdline += ["--defer-partitions", ",".join(skip)]
     if split:
@@ -3102,23 +3128,16 @@ def make_image(
 
     with complete_step(msg):
         output = json.loads(
-            run(
-                cmdline,
-                stdin=(
-                    sys.stdin
-                    if context.config.verity_key_source.type != KeySourceType.file
-                    else subprocess.DEVNULL
-                ),
+            run_systemd_sign_tool(
+                context.config,
+                cmdline=cmdline,
+                options=opts,
+                certificate=context.config.verity_certificate if verity else None,
+                certificate_source=context.config.verity_certificate_source,
+                key=context.config.verity_key if verity else None,
+                key_source=context.config.verity_key_source,
                 stdout=subprocess.PIPE,
-                env=context.config.environment,
-                sandbox=context.sandbox(
-                    binary="systemd-repart",
-                    devices=(
-                        not context.config.repart_offline
-                        or context.config.verity_key_source.type != KeySourceType.file
-                    ),
-                    options=opts,
-                ),
+                devices=not context.config.repart_offline,
             ).stdout
         )
 
@@ -3407,22 +3426,6 @@ def make_extension_image(context: Context, output: Path) -> None:
     if context.config.passphrase:
         cmdline += ["--key-file", context.config.passphrase]
         options += ["--ro-bind", context.config.passphrase, workdir(context.config.passphrase)]
-    if want_verity(context.config):
-        assert context.config.verity_key
-        assert context.config.verity_certificate
-
-        if context.config.verity_key_source.type != KeySourceType.file:
-            cmdline += ["--private-key-source", str(context.config.verity_key_source)]
-        if context.config.verity_key.exists():
-            cmdline += ["--private-key", workdir(context.config.verity_key)]
-            options += ["--ro-bind", context.config.verity_key, workdir(context.config.verity_key)]
-        else:
-            cmdline += ["--private-key", context.config.verity_key]
-
-        cmdline += ["--certificate", workdir(context.config.verity_certificate)]
-        options += [
-            "--ro-bind", context.config.verity_certificate, workdir(context.config.verity_certificate)
-        ]  # fmt: skip
     if context.config.sector_size:
         cmdline += ["--sector-size", str(context.config.sector_size)]
     if ArtifactOutput.partitions in context.config.split_artifacts:
@@ -3430,23 +3433,16 @@ def make_extension_image(context: Context, output: Path) -> None:
 
     with complete_step(f"Building {context.config.output_format} extension image"):
         j = json.loads(
-            run(
-                cmdline,
-                stdin=(
-                    sys.stdin
-                    if context.config.verity_key_source.type != KeySourceType.file
-                    else subprocess.DEVNULL
-                ),
+            run_systemd_sign_tool(
+                context.config,
+                cmdline=cmdline,
+                options=options,
+                certificate=context.config.verity_certificate if want_verity(context.config) else None,
+                certificate_source=context.config.verity_certificate_source,
+                key=context.config.verity_key if want_verity(context.config) else None,
+                key_source=context.config.verity_key_source,
                 stdout=subprocess.PIPE,
-                env=context.config.environment,
-                sandbox=context.sandbox(
-                    binary="systemd-repart",
-                    devices=(
-                        not context.config.repart_offline
-                        or context.config.verity_key_source.type != KeySourceType.file
-                    ),
-                    options=options,
-                ),
+                devices=not context.config.repart_offline,
             ).stdout
         )
 
@@ -4248,6 +4244,48 @@ def run_clean_scripts(config: Config) -> None:
                 )  # fmt: skip
 
 
+def validate_certificates_and_keys(config: Config) -> None:
+    keyutil = config.find_binary("systemd-keyutil", "/usr/lib/systemd/systemd-keyutil")
+    if not keyutil:
+        return
+
+    if want_verity(config):
+        run_systemd_sign_tool(
+            config,
+            cmdline=[keyutil, "validate"],
+            options=[],
+            certificate=config.verity_certificate,
+            certificate_source=config.verity_certificate_source,
+            key=config.verity_key,
+            key_source=config.verity_key_source,
+            stdout=subprocess.DEVNULL,
+        )
+
+    if config.bootable != ConfigFeature.disabled and config.secure_boot:
+        run_systemd_sign_tool(
+            config,
+            cmdline=[keyutil, "validate"],
+            options=[],
+            certificate=config.secure_boot_certificate,
+            certificate_source=config.secure_boot_certificate_source,
+            key=config.secure_boot_key,
+            key_source=config.secure_boot_key_source,
+            stdout=subprocess.DEVNULL,
+        )
+
+    if want_signed_pcrs(config):
+        run_systemd_sign_tool(
+            config,
+            cmdline=[keyutil, "validate"],
+            options=[],
+            certificate=config.sign_expected_pcr_certificate,
+            certificate_source=config.sign_expected_pcr_certificate_source,
+            key=config.sign_expected_pcr_key,
+            key_source=config.sign_expected_pcr_key_source,
+            stdout=subprocess.DEVNULL,
+        )
+
+
 def needs_build(args: Args, config: Config, force: int = 1) -> bool:
     return (
         args.force >= force
@@ -4667,19 +4705,6 @@ def run_verb(args: Args, images: Sequence[Config], *, resources: Path) -> None:
         for config in images:
             run_clean(args, config, resources=resources)
 
-    if args.verb != Verb.sandbox:
-        for config in images:
-            if any(
-                source.type != KeySourceType.file
-                for source in (
-                    config.verity_key_source,
-                    config.secure_boot_key_source,
-                    config.sign_expected_pcr_key_source,
-                )
-            ):
-                join_new_session_keyring()
-                break
-
     if tools and not (tools.output_dir_or_cwd() / tools.output).exists():
         with prepend_to_environ_path(tools):
             check_tools(tools, Verb.build)
@@ -4693,7 +4718,8 @@ def run_verb(args: Args, images: Sequence[Config], *, resources: Path) -> None:
                 fork_and_wait(run_build, args, tools, resources=resources, metadata_dir=Path(metadata_dir))
 
     if args.verb == Verb.sandbox:
-        return run_sandbox(args, last)
+        with prepend_to_environ_path(last):
+            return run_sandbox(args, last)
 
     for i, config in enumerate(images):
         with prepend_to_environ_path(config):
@@ -4704,6 +4730,23 @@ def run_verb(args: Args, images: Sequence[Config], *, resources: Path) -> None:
     last = images[-1]
 
     if not (last.output_dir_or_cwd() / last.output).exists() or last.output_format == OutputFormat.none:
+        for config in images:
+            if any(
+                source.type != KeySourceType.file
+                for source in (
+                    config.verity_key_source,
+                    config.secure_boot_key_source,
+                    config.sign_expected_pcr_key_source,
+                )
+            ):
+                join_new_session_keyring()
+                break
+
+        with complete_step("Validating certificates and keys"):
+            for config in images:
+                with prepend_to_environ_path(config):
+                    validate_certificates_and_keys(config)
+
         ensure_directories_exist(last)
 
         with (
index 4665bf881de46a9d52be82784cdf8b3e4ccec449..e3985d59889432b296e04deaf6a74f16d60076cd 100644 (file)
@@ -5,15 +5,18 @@ import subprocess
 import sys
 import tempfile
 import textwrap
-from collections.abc import Iterator, Sequence
+from collections.abc import Iterator, Mapping, Sequence
 from pathlib import Path
 from typing import Optional
 
 from mkosi.config import (
     BiosBootloader,
     Bootloader,
+    CertificateSource,
+    CertificateSourceType,
     Config,
     ConfigFeature,
+    KeySource,
     KeySourceType,
     OutputFormat,
     SecureBootSignTool,
@@ -27,7 +30,7 @@ from mkosi.partition import Partition
 from mkosi.qemu import KernelType
 from mkosi.run import run, workdir
 from mkosi.sandbox import umask
-from mkosi.types import PathString
+from mkosi.types import _FILE, CompletedProcess, PathString
 from mkosi.util import flatten
 from mkosi.versioncomp import GenericVersion
 
@@ -441,6 +444,67 @@ def certificate_common_name(context: Context, certificate: Path) -> str:
     die(f"Certificate {certificate} is missing Common Name")
 
 
+def run_systemd_sign_tool(
+    config: Config,
+    *,
+    cmdline: Sequence[PathString],
+    options: Sequence[PathString],
+    certificate: Optional[Path],
+    certificate_source: CertificateSource,
+    key: Optional[Path],
+    key_source: KeySource,
+    env: Mapping[str, str] = {},
+    stdout: _FILE = None,
+    devices: bool = False,
+) -> CompletedProcess:
+    if not certificate and not key:
+        return run(
+            cmdline,
+            stdout=stdout,
+            env={**config.environment, **env},
+            sandbox=config.sandbox(binary=cmdline[0], options=options, devices=devices),
+        )
+
+    assert certificate
+    assert key
+
+    cmd: list[PathString] = [*cmdline]
+    opt: list[PathString] = [*options]
+
+    if certificate_source.type != CertificateSourceType.file or key_source.type != KeySourceType.file:
+        opt += ["--bind", "/run", "/run"]
+
+    if certificate_source.type != CertificateSourceType.file:
+        cmd += ["--certificate-source", str(certificate_source)]
+
+    if certificate.exists():
+        cmd += ["--certificate", workdir(certificate)]
+        opt += ["--ro-bind", certificate, workdir(certificate)]
+    else:
+        cmd += ["--certificate", certificate]
+
+    if key_source.type != KeySourceType.file:
+        cmd += ["--private-key-source", str(key_source)]
+
+    if key.exists():
+        cmd += ["--private-key", workdir(key)]
+        opt += ["--ro-bind", key, workdir(key)]
+    else:
+        cmd += ["--private-key", key]
+
+    return run(
+        cmd,
+        stdin=(sys.stdin if key_source.type != KeySourceType.file else subprocess.DEVNULL),
+        stdout=stdout,
+        env={**config.environment, **env},
+        sandbox=config.sandbox(
+            binary=cmd[0],
+            options=opt,
+            devices=devices or key_source.type != KeySourceType.file,
+        ),
+    )
+
+
 def pesign_prepare(context: Context) -> None:
     assert context.config.secure_boot_key
     assert context.config.secure_boot_certificate
@@ -501,17 +565,38 @@ def sign_efi_binary(context: Context, input: Path, output: Path) -> Path:
     assert context.config.secure_boot_key
     assert context.config.secure_boot_certificate
 
-    if (
+    sbsign = context.config.find_binary("systemd-sbsign", "/usr/lib/systemd/systemd-sbsign")
+    if context.config.secure_boot_sign_tool == SecureBootSignTool.systemd and not sbsign:
+        die("Could not find systemd-sbsign")
+
+    cmd: list[PathString]
+    options: list[PathString]
+
+    if context.config.secure_boot_sign_tool == SecureBootSignTool.systemd or (
+        context.config.secure_boot_sign_tool == SecureBootSignTool.auto and sbsign
+    ):
+        assert sbsign
+
+        run_systemd_sign_tool(
+            context.config,
+            cmdline=[sbsign, "sign", "--output", workdir(output), workdir(input)],
+            options=["--ro-bind", input, workdir(input), "--bind", output.parent, workdir(output.parent)],
+            certificate=context.config.secure_boot_certificate,
+            certificate_source=context.config.secure_boot_certificate_source,
+            key=context.config.secure_boot_key,
+            key_source=context.config.secure_boot_key_source,
+        )
+    elif (
         context.config.secure_boot_sign_tool == SecureBootSignTool.sbsign
         or context.config.secure_boot_sign_tool == SecureBootSignTool.auto
         and context.config.find_binary("sbsign") is not None
     ):
-        cmd: list[PathString] = [
+        cmd = [
             "sbsign",
             "--cert", workdir(context.config.secure_boot_certificate),
             "--output", workdir(output),
         ]  # fmt: skip
-        options: list[PathString] = [
+        options = [
             "--ro-bind", context.config.secure_boot_certificate, workdir(context.config.secure_boot_certificate),  # noqa: E501
             "--ro-bind", input, workdir(input),
             "--bind", output.parent, workdir(output.parent),
@@ -532,6 +617,7 @@ def sign_efi_binary(context: Context, input: Path, output: Path) -> Path:
                 if context.config.secure_boot_key_source.type != KeySourceType.file
                 else subprocess.DEVNULL
             ),
+            env=context.config.environment,
             sandbox=context.sandbox(
                 binary="sbsign",
                 options=options,
@@ -559,6 +645,7 @@ def sign_efi_binary(context: Context, input: Path, output: Path) -> Path:
                 if context.config.secure_boot_key_source.type != KeySourceType.file
                 else subprocess.DEVNULL
             ),
+            env=context.config.environment,
             sandbox=context.sandbox(
                 binary="pesign",
                 options=[
@@ -692,40 +779,21 @@ def install_systemd_boot(context: Context) -> None:
 
     bootctlver = systemd_tool_version("bootctl", sandbox=context.sandbox)
 
-    if context.config.secure_boot and context.config.secure_boot_auto_enroll and bootctlver >= 257:
-        assert context.config.secure_boot_certificate
-        assert context.config.secure_boot_key
-
-        cmd += [
-            "--secure-boot-auto-enroll=yes",
-            "--certificate", workdir(context.config.secure_boot_certificate),
-        ]  # fmt: skip
-        options += [
-            "--ro-bind", context.config.secure_boot_certificate, workdir(context.config.secure_boot_certificate),  # noqa: E501
-        ]  # fmt: skip
-        if context.config.secure_boot_key_source.type != KeySourceType.file:
-            cmd += ["--private-key-source", str(context.config.secure_boot_key_source)]
-            options += ["--bind", "/run", "/run"]
-        if context.config.secure_boot_key.exists():
-            cmd += ["--private-key", workdir(context.config.secure_boot_key)]
-            options += ["--ro-bind", context.config.secure_boot_key, workdir(context.config.secure_boot_key)]
-        else:
-            cmd += ["--private-key", context.config.secure_boot_key]
+    if want_bootctl_auto_enroll := (
+        context.config.secure_boot and context.config.secure_boot_auto_enroll and bootctlver >= 257
+    ):
+        cmd += ["--secure-boot-auto-enroll=yes"]
 
     with complete_step("Installing systemd-boot…"):
-        run(
-            cmd,
-            stdin=(
-                sys.stdin
-                if context.config.secure_boot_key_source.type != KeySourceType.file
-                else subprocess.DEVNULL
-            ),
-            env=context.config.environment | {"SYSTEMD_ESP_PATH": "/efi", "SYSTEMD_XBOOTLDR_PATH": "/boot"},
-            sandbox=context.sandbox(
-                binary="bootctl",
-                options=options,
-                devices=context.config.secure_boot_key_source.type != KeySourceType.file,
-            ),
+        run_systemd_sign_tool(
+            context.config,
+            cmdline=cmd,
+            options=options,
+            certificate=context.config.secure_boot_certificate if want_bootctl_auto_enroll else None,
+            certificate_source=context.config.secure_boot_certificate_source,
+            key=context.config.secure_boot_key if want_bootctl_auto_enroll else None,
+            key_source=context.config.secure_boot_key_source,
+            env={"SYSTEMD_ESP_PATH": "/efi", "SYSTEMD_XBOOTLDR_PATH": "/boot"},
         )
         # TODO: Use --random-seed=no when we can depend on systemd 256.
         Path(context.root / "efi/loader/random-seed").unlink(missing_ok=True)
index 8653bf9a0b0da8afe5ddca044509cbef3fad7b72..54f67fbd732acff5b4b09144d70b2d8d4ce0f47f 100644 (file)
@@ -174,6 +174,7 @@ class SecureBootSignTool(StrEnum):
     auto = enum.auto()
     sbsign = enum.auto()
     pesign = enum.auto()
+    systemd = enum.auto()
 
 
 class OutputFormat(StrEnum):
@@ -631,6 +632,13 @@ def config_parse_key(value: Optional[str], old: Optional[str]) -> Optional[Path]
     return parse_path(value, secret=True) if Path(value).exists() else Path(value)
 
 
+def config_parse_certificate(value: Optional[str], old: Optional[str]) -> Optional[Path]:
+    if not value:
+        return None
+
+    return parse_path(value) if Path(value).exists() else Path(value)
+
+
 def make_tree_parser(absolute: bool = True, required: bool = False) -> Callable[[str], ConfigTree]:
     def parse_tree(value: str) -> ConfigTree:
         src, sep, tgt = value.partition(":")
@@ -1327,6 +1335,36 @@ def config_parse_key_source(value: Optional[str], old: Optional[KeySource]) -> O
     return KeySource(type=type, source=source)
 
 
+class CertificateSourceType(StrEnum):
+    file = enum.auto()
+    provider = enum.auto()
+
+
+@dataclasses.dataclass(frozen=True)
+class CertificateSource:
+    type: CertificateSourceType
+    source: str = ""
+
+    def __str__(self) -> str:
+        return f"{self.type}:{self.source}" if self.source else str(self.type)
+
+
+def config_parse_certificate_source(
+    value: Optional[str],
+    old: Optional[CertificateSource],
+) -> Optional[CertificateSource]:
+    if not value:
+        return old
+
+    typ, _, source = value.partition(":")
+    try:
+        type = CertificateSourceType(typ)
+    except ValueError:
+        die(f"'{value}' is not a valid certificate source")
+
+    return CertificateSource(type=type, source=source)
+
+
 def config_parse_artifact_output_list(
     value: Optional[str], old: Optional[list[ArtifactOutput]]
 ) -> Optional[list[ArtifactOutput]]:
@@ -1741,15 +1779,18 @@ class Config:
     secure_boot_key: Optional[Path]
     secure_boot_key_source: KeySource
     secure_boot_certificate: Optional[Path]
+    secure_boot_certificate_source: CertificateSource
     secure_boot_sign_tool: SecureBootSignTool
     verity: ConfigFeature
     verity_key: Optional[Path]
     verity_key_source: KeySource
     verity_certificate: Optional[Path]
+    verity_certificate_source: CertificateSource
     sign_expected_pcr: ConfigFeature
     sign_expected_pcr_key: Optional[Path]
     sign_expected_pcr_key_source: KeySource
     sign_expected_pcr_certificate: Optional[Path]
+    sign_expected_pcr_certificate_source: CertificateSource
     passphrase: Optional[Path]
     checksum: bool
     sign: bool
@@ -2894,10 +2935,19 @@ SETTINGS = (
         dest="secure_boot_certificate",
         metavar="PATH",
         section="Validation",
-        parse=config_make_path_parser(),
+        parse=config_parse_certificate,
         paths=("mkosi.crt",),
         help="UEFI SecureBoot certificate in X509 format",
     ),
+    ConfigSetting(
+        dest="secure_boot_certificate_source",
+        section="Validation",
+        metavar="SOURCE[:PROVIDER]",
+        parse=config_parse_certificate_source,
+        default=CertificateSource(type=CertificateSourceType.file),
+        help="The source to use to retrieve the secure boot signing certificate",
+        scope=SettingScope.universal,
+    ),
     ConfigSetting(
         dest="secure_boot_sign_tool",
         section="Validation",
@@ -2935,11 +2985,20 @@ SETTINGS = (
         dest="verity_certificate",
         metavar="PATH",
         section="Validation",
-        parse=config_make_path_parser(),
+        parse=config_parse_certificate,
         paths=("mkosi.crt",),
         help="Certificate for signing verity signature in X509 format",
         scope=SettingScope.universal,
     ),
+    ConfigSetting(
+        dest="verity_certificate_source",
+        section="Validation",
+        metavar="SOURCE[:PROVIDER]",
+        parse=config_parse_certificate_source,
+        default=CertificateSource(type=CertificateSourceType.file),
+        help="The source to use to retrieve the verity signing certificate",
+        scope=SettingScope.universal,
+    ),
     ConfigSetting(
         dest="sign_expected_pcr",
         metavar="FEATURE",
@@ -2970,11 +3029,20 @@ SETTINGS = (
         dest="sign_expected_pcr_certificate",
         metavar="PATH",
         section="Validation",
-        parse=config_make_path_parser(),
+        parse=config_parse_certificate,
         paths=("mkosi.crt",),
         help="Certificate for signing expected PCR signature in X509 format",
         scope=SettingScope.universal,
     ),
+    ConfigSetting(
+        dest="sign_expected_pcr_certificate_source",
+        section="Validation",
+        metavar="SOURCE[:PROVIDER]",
+        parse=config_parse_certificate_source,
+        default=CertificateSource(type=CertificateSourceType.file),
+        help="The source to use to retrieve the expected PCR signing certificate",
+        scope=SettingScope.universal,
+    ),
     ConfigSetting(
         dest="passphrase",
         metavar="PATH",
@@ -4678,15 +4746,18 @@ def summary(config: Config) -> str:
              SecureBoot Signing Key: {none_to_none(config.secure_boot_key)}
       SecureBoot Signing Key Source: {config.secure_boot_key_source}
              SecureBoot Certificate: {none_to_none(config.secure_boot_certificate)}
+      SecureBoot Certificate Source: {config.secure_boot_certificate_source}
                SecureBoot Sign Tool: {config.secure_boot_sign_tool}
                              Verity: {config.verity}
                  Verity Signing Key: {none_to_none(config.verity_key)}
           Verity Signing Key Source: {config.verity_key_source}
                  Verity Certificate: {none_to_none(config.verity_certificate)}
+          Verity Certificate Source: {config.verity_certificate_source}
                  Sign Expected PCRs: {config.sign_expected_pcr}
           Expected PCRs Signing Key: {none_to_none(config.sign_expected_pcr_key)}
            Expected PCRs Key Source: {config.sign_expected_pcr_key_source}
           Expected PCRs Certificate: {none_to_none(config.sign_expected_pcr_certificate)}
+   Expected PCRs Certificate Source: {config.sign_expected_pcr_certificate_source}
                          Passphrase: {none_to_none(config.passphrase)}
                            Checksum: {yes_no(config.checksum)}
                                Sign: {yes_no(config.sign)}
@@ -4861,6 +4932,15 @@ def json_type_transformer(refcls: Union[type[Args], type[Config]]) -> Callable[[
     ) -> Optional[GenericVersion]:
         return GenericVersion(version) if version is not None else None
 
+    def certificate_source_transformer(
+        certificate_source: dict[str, Any], fieldtype: type[CertificateSource]
+    ) -> CertificateSource:
+        assert "Type" in certificate_source
+        return CertificateSource(
+            type=CertificateSourceType(certificate_source["Type"]),
+            source=certificate_source.get("Source", ""),
+        )
+
     def key_source_transformer(keysource: dict[str, Any], fieldtype: type[KeySource]) -> KeySource:
         assert "Type" in keysource
         return KeySource(type=KeySourceType(keysource["Type"]), source=keysource.get("Source", ""))
@@ -4916,6 +4996,7 @@ def json_type_transformer(refcls: Union[type[Args], type[Config]]) -> Callable[[
         list[PEAddon]: pe_addon_transformer,
         list[UKIProfile]: uki_profile_transformer,
         list[ArtifactOutput]: enum_list_transformer,
+        CertificateSource: certificate_source_transformer,
     }
 
     def json_transformer(key: str, val: Any) -> Any:
index 9625ed22eed73d38f44ee80b21fc9e707f47340c..0c41998d345ca6d1cc46d43d9bc2ab733aac90d7 100644 (file)
@@ -1140,9 +1140,9 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
     UEFI kernel image, if `SecureBoot=` is used.
 
 `SecureBootSignTool=`, `--secure-boot-sign-tool`
-:   Tool to use to sign secure boot PE binaries. Takes one of `sbsign`, `pesign` or `auto`. Defaults to `auto`.
-    If set to `auto`, either sbsign or pesign are used if available, with sbsign being preferred if both are
-    installed.
+:   Tool to use to sign secure boot PE binaries. Takes one of `systemd`, `sbsign`, `pesign` or `auto`.
+    Defaults to `auto`. If set to `auto`, either `systemd-sbsign`, `sbsign` or `pesign` are used if
+    available, with `systemd-sbsign` being preferred.
 
 `Verity=`, `--verity=`
 :   Whether to enforce or disable signed verity for extension images.
@@ -1186,9 +1186,12 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
 :   Path to the X.509 file containing the certificate for signing the expected PCR signatures.
 
 `SecureBootKeySource=`, `--secure-boot-key-source=`, `VerityKeySource=`, `--verity-key-source=`, `SignExpectedPcrKeySource=`, `--sign-expected-key-source=`
-:   The source of the corresponding private key `SecureBootKey=`, to support OpenSSL engines and providers,
-    e.g. `--secure-boot-key-source=engine:pkcs11` or `--secure-boot-key-source=provider:pkcs11`. Note that
-    providers are currently only supported for the verity key.
+:   The source of the corresponding private key, to support OpenSSL engines and providers,
+    e.g. `--secure-boot-key-source=engine:pkcs11` or `--secure-boot-key-source=provider:pkcs11`.
+
+`SecureBootCertificateSource=`, `--secure-boot-certificate-source=`, `VerityCertificateSource=`, `--verity-certificate-source=`, `SignExpectedPcrCertificateSource=`, `--sign-expected-certificate-source=`
+:   The source of the corresponding certificate, to support OpenSSL providers,
+    e.g. `--secure-boot-certificate-source=provider:pkcs11`. Note that engines are not supported.
 
 `Passphrase=`, `--passphrase`
 :   Specify the path to a file containing the passphrase to use for LUKS
index 2b023acc9736cd3180d3038dcd93a59564ec0fd5..c26920f36378e0480f4d17266ecf6dc53a93f0df 100644 (file)
@@ -15,6 +15,8 @@ from mkosi.config import (
     BiosBootloader,
     Bootloader,
     Cacheonly,
+    CertificateSource,
+    CertificateSourceType,
     Compression,
     Config,
     ConfigFeature,
@@ -308,6 +310,10 @@ def test_config() -> None:
             "SecureBoot": true,
             "SecureBootAutoEnroll": true,
             "SecureBootCertificate": null,
+            "SecureBootCertificateSource": {
+                "Source": "",
+                "Type": "file"
+            },
             "SecureBootKey": "/path/to/keyfile",
             "SecureBootKeySource": {
                 "Source": "",
@@ -319,6 +325,10 @@ def test_config() -> None:
             "Sign": false,
             "SignExpectedPcr": "disabled",
             "SignExpectedPcrCertificate": "/my/cert",
+            "SignExpectedPcrCertificateSource": {
+                "Source": "",
+                "Type": "file"
+            },
             "SignExpectedPcrKey": "/my/key",
             "SignExpectedPcrKeySource": {
                 "Source": "",
@@ -380,6 +390,10 @@ def test_config() -> None:
             "UseSubvolumes": "auto",
             "Verity": "enabled",
             "VerityCertificate": "/path/to/cert",
+            "VerityCertificateSource": {
+                "Source": "",
+                "Type": "file"
+            },
             "VerityKey": null,
             "VerityKeySource": {
                 "Source": "",
@@ -526,6 +540,7 @@ def test_config() -> None:
         secure_boot=True,
         secure_boot_auto_enroll=True,
         secure_boot_certificate=None,
+        secure_boot_certificate_source=CertificateSource(type=CertificateSourceType.file),
         secure_boot_key=Path("/path/to/keyfile"),
         secure_boot_key_source=KeySource(type=KeySourceType.file),
         secure_boot_sign_tool=SecureBootSignTool.pesign,
@@ -537,6 +552,7 @@ def test_config() -> None:
         sign_expected_pcr_key=Path("/my/key"),
         sign_expected_pcr_key_source=KeySource(type=KeySourceType.file),
         sign_expected_pcr_certificate=Path("/my/cert"),
+        sign_expected_pcr_certificate_source=CertificateSource(type=CertificateSourceType.file),
         skeleton_trees=[ConfigTree(Path("/foo/bar"), Path("/")), ConfigTree(Path("/bar/baz"), Path("/qux"))],
         source_date_epoch=12345,
         split_artifacts=[ArtifactOutput.uki, ArtifactOutput.kernel],
@@ -563,6 +579,7 @@ def test_config() -> None:
         verity_certificate=Path("/path/to/cert"),
         verity_key=None,
         verity_key_source=KeySource(type=KeySourceType.file),
+        verity_certificate_source=CertificateSource(type=CertificateSourceType.file),
         volatile_package_directories=[Path("def")],
         volatile_packages=["abc"],
         with_docs=True,