From f71bc8f6f35423d5ea65761434a24c07677d5b9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Hundeb=C3=B8ll?= Date: Tue, 28 Jan 2025 21:35:26 +0100 Subject: [PATCH] Support unsigned verity backed extension/portable images Building an unsigned extension image with verity hashes provides data integrity without needing a certificate on the target machine. Note that systemd-dissect and systemd-sysext doesn't automatically use the verity data has partition for validation. Both tools enables validation if the user.verity.roothash xattr is set for the image. For systemd-dissect, one can use the --root-hash option to enable the validation. The root hash can be obtained by concatenating the partition uuid's for the root and the root-verity partitions. --- mkosi/__init__.py | 21 ++++++++++++++------- mkosi/config.py | 15 +++++++++++++-- mkosi/resources/man/mkosi.1.md | 28 +++++++++++++++------------- mkosi/resources/man/mkosi.news.7.md | 8 +++++++- tests/test_json.py | 5 +++-- 5 files changed, 52 insertions(+), 25 deletions(-) diff --git a/mkosi/__init__.py b/mkosi/__init__.py index 694deb357..afcb919c9 100644 --- a/mkosi/__init__.py +++ b/mkosi/__init__.py @@ -65,6 +65,7 @@ SecureBootSignTool, ShimBootloader, Verb, + Verity, Vmm, cat_config, format_bytes, @@ -2590,13 +2591,13 @@ def check_inputs(config: Config) -> None: "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: + if config.verity == Verity.signature and not config.verity_key: die( "Verity= is enabled but no verity key is configured", hint="Run mkosi genkey to generate a key/certificate pair", ) - if config.verity == ConfigFeature.enabled and not config.verity_certificate: + if config.verity == Verity.signature and not config.verity_certificate: die( "Verity= is enabled but no verity certificate is configured", hint="Run mkosi genkey to generate a key/certificate pair", @@ -3240,7 +3241,7 @@ def make_image( partitions = [Partition.from_dict(d) for d in output] arch = context.config.architecture - if context.config.verity == ConfigFeature.enabled and not any( + if context.config.verity == Verity.signature and not any( p.type.startswith(f"usr-{arch}-verity-sig") or p.type.startswith(f"root-{arch}-verity-sig") for p in partitions ): @@ -3258,8 +3259,8 @@ def make_image( def want_verity(config: Config) -> bool: - return config.verity == ConfigFeature.enabled or bool( - config.verity == ConfigFeature.auto and config.verity_key and config.verity_certificate + return config.verity == Verity.signature or bool( + config.verity == Verity.auto and config.verity_key and config.verity_certificate ) @@ -3492,7 +3493,11 @@ def make_esp(context: Context, uki: Path) -> list[Partition]: def make_extension_or_portable_image(context: Context, output: Path) -> None: - unsigned = "-unsigned" if not want_verity(context.config) else "" + if want_verity(context.config) or context.config.verity == Verity.signature: + unsigned = "" + else: + unsigned = "-unsigned" + r = context.resources / f"repart/definitions/{context.config.output_format}{unsigned}.repart.d" cmdline: list[PathString] = [ @@ -3526,6 +3531,8 @@ def make_extension_or_portable_image(context: Context, output: Path) -> None: cmdline += ["--sector-size", str(context.config.sector_size)] if ArtifactOutput.partitions in context.config.split_artifacts: cmdline += ["--split=yes"] + if context.config.verity == Verity.verity: + cmdline += ["--exclude-partitions=root-verity-sig"] with complete_step(f"Building {context.config.output_format} extension image"): j = json.loads( @@ -4372,7 +4379,7 @@ def validate_certificates_and_keys(config: Config) -> None: if not keyutil: return - if config.verity != ConfigFeature.disabled and config.verity_certificate and config.verity_key: + if config.verity != Verity.off and config.verity_certificate and config.verity_key: run_systemd_sign_tool( config, cmdline=[keyutil, "validate"], diff --git a/mkosi/config.py b/mkosi/config.py index f4e3055b4..88f88d594 100644 --- a/mkosi/config.py +++ b/mkosi/config.py @@ -363,6 +363,14 @@ def __bool__(self) -> bool: return self != BuildSourcesEphemeral.no +class Verity(StrEnum): + off = enum.auto() + verity = enum.auto() + signature = enum.auto() + defer = enum.auto() + auto = enum.auto() + + class Architecture(StrEnum): alpha = enum.auto() arc = enum.auto() @@ -1872,7 +1880,7 @@ class Config: secure_boot_certificate: Optional[Path] secure_boot_certificate_source: CertificateSource secure_boot_sign_tool: SecureBootSignTool - verity: ConfigFeature + verity: Verity verity_key: Optional[Path] verity_key_source: KeySource verity_certificate: Optional[Path] @@ -3031,7 +3039,9 @@ def parse_ini(path: Path, only_sections: Collection[str] = ()) -> Iterator[tuple dest="verity", section="Validation", metavar="FEATURE", - parse=config_parse_feature, + parse=config_make_enum_parser_with_boolean(Verity, yes=Verity.signature, no=Verity.off), + default=Verity.auto, + choices=Verity.values(), help="Configure whether to enforce or disable verity partitions for disk images", ), ConfigSetting( @@ -5168,6 +5178,7 @@ def uki_profile_transformer( list[ArtifactOutput]: enum_list_transformer, CertificateSource: certificate_source_transformer, ConsoleMode: enum_transformer, + Verity: enum_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 2e2a8f002..77bc4aca8 100644 --- a/mkosi/resources/man/mkosi.1.md +++ b/mkosi/resources/man/mkosi.1.md @@ -1140,19 +1140,21 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`, available, with **systemd-sbsign** being preferred. `Verity=`, `--verity=` -: Whether to enforce or disable signed verity for extension images. - Takes a boolean value or `auto`. If enabled, a verity key and - certificate must be present and the build will fail if we don't - detect any verity partitions in the disk image produced by - **systemd-repart**. If disabled, verity partitions will be excluded from - the extension images produced by **systemd-repart**. If set to `auto` and - a verity key and certificate are present, **mkosi** will pass them to systemd-repart - and expects the generated disk image to contain verity partitions, - but the build won't fail if no verity partitions are found in the - disk image produced by **systemd-repart**. - - Note that explicitly disabling signed verity is not yet implemented - for the `disk` output and only works for extension images at the +: Whether to enforce or disable verity for extension images. Takes one of + `signature`, `verity`, `auto` or a boolean value. If set to `signature`, + a verity key and certificate must be present and the build will fail if + we don't detect any verity partitions in the disk image produced by + **systemd-repart**. If disabled, verity partitions will be excluded + from the extension images produced by **systemd-repart**. If set to + `verity`, **mkosi** configures **systemd-repart** to create a verity hash + partition, but no signature partition. If set to `auto` and a verity key + and certificate are present, **mkosi** will pass them to systemd-repart and + expects the generated disk image to contain verity partitions, but the build + won't fail if no verity partitions are found in the disk image produced by + **systemd-repart**. + + Note that explicitly disabling verity signature and/or hash is not yet + implemented for the `disk` output and only works for extension images at the moment. `VerityKey=`, `--verity-key=` diff --git a/mkosi/resources/man/mkosi.news.7.md b/mkosi/resources/man/mkosi.news.7.md index c2b04a8ab..5da4debfa 100644 --- a/mkosi/resources/man/mkosi.news.7.md +++ b/mkosi/resources/man/mkosi.news.7.md @@ -4,6 +4,12 @@ # mkosi Changelog +## v26 + +- Teach --verity a new new `hash` value, which skips the verity signature + partition for extension / portable images. To align the possible values, + `yes` is renamed to `signed`. + ## v25 - Instead of using bubblewrap, sandboxing is now done with a new tool @@ -1037,7 +1043,7 @@ configuration from the host system, instead of the internal list. - A new `SshAgent=` option configures the path to the ssh agent. - A new `SshPort=` option overrides the port used for ssh. -- The `Verity=` setting supports a new value `signed`. When set, verity data +- The `Verity=` setting supports a new value `signature`. When set, verity data will be signed and the result inserted as an additional partition in the image. See https://systemd.io/DISCOVERABLE_PARTITIONS for details about signed disk images. This information is used by `systemd-nspawn`, diff --git a/tests/test_json.py b/tests/test_json.py index 00a138a4c..955bf6562 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -36,6 +36,7 @@ ShimBootloader, UKIProfile, Verb, + Verity, Vmm, VsockCID, ) @@ -388,7 +389,7 @@ def test_config() -> None: "UseSubvolumes": "auto", "VSock": "enabled", "VSockCID": -2, - "Verity": "enabled", + "Verity": "signature", "VerityCertificate": "/path/to/cert", "VerityCertificateSource": { "Source": "", @@ -575,7 +576,7 @@ def test_config() -> None: verity_certificate=Path("/path/to/cert"), verity_key_source=KeySource(type=KeySourceType.file), verity_key=None, - verity=ConfigFeature.enabled, + verity=Verity.signature, vmm=Vmm.qemu, volatile_package_directories=[Path("def")], volatile_packages=["abc"],