From: Daan De Meyer Date: Tue, 5 Nov 2024 12:56:28 +0000 (+0100) Subject: Add support for systemd-sbsign and --certificate-source X-Git-Tag: v25~184 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=03420ea12787c659794dc4826611eda75af10079;p=thirdparty%2Fmkosi.git Add support for systemd-sbsign and --certificate-source Matching PR for https://github.com/systemd/systemd/pull/35021 and https://github.com/systemd/systemd/pull/35057 --- diff --git a/mkosi/__init__.py b/mkosi/__init__.py index afcc21b00..688b23ebc 100644 --- a/mkosi/__init__.py +++ b/mkosi/__init__.py @@ -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 ( diff --git a/mkosi/bootloader.py b/mkosi/bootloader.py index 4665bf881..e3985d598 100644 --- a/mkosi/bootloader.py +++ b/mkosi/bootloader.py @@ -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) diff --git a/mkosi/config.py b/mkosi/config.py index 8653bf9a0..54f67fbd7 100644 --- a/mkosi/config.py +++ b/mkosi/config.py @@ -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: diff --git a/mkosi/resources/man/mkosi.1.md b/mkosi/resources/man/mkosi.1.md index 9625ed22e..0c41998d3 100644 --- a/mkosi/resources/man/mkosi.1.md +++ b/mkosi/resources/man/mkosi.1.md @@ -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 diff --git a/tests/test_json.py b/tests/test_json.py index 2b023acc9..c26920f36 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -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,