From 002c4060869db08047bc32dd74ff44b4b482ecd2 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 4 Feb 2025 11:16:01 +0100 Subject: [PATCH 1/3] Documentation fix --- mkosi/resources/man/mkosi.1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkosi/resources/man/mkosi.1.md b/mkosi/resources/man/mkosi.1.md index adcf90c09..6d0f0d5c5 100644 --- a/mkosi/resources/man/mkosi.1.md +++ b/mkosi/resources/man/mkosi.1.md @@ -1146,7 +1146,7 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`, 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 + 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**. From d9c64f05569d4f752c1423bf0416376c54534c75 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 2/3] 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 | 26 +++++++++++++++++++------- mkosi/config.py | 14 ++++++++++++-- mkosi/resources/man/mkosi.1.md | 28 +++++++++++++++------------- mkosi/resources/man/mkosi.news.7.md | 6 ++++++ tests/test_json.py | 5 +++-- 5 files changed, 55 insertions(+), 24 deletions(-) diff --git a/mkosi/__init__.py b/mkosi/__init__.py index 51b06ab05..e5e1234af 100644 --- a/mkosi/__init__.py +++ b/mkosi/__init__.py @@ -65,6 +65,7 @@ SecureBootSignTool, ShimBootloader, Verb, + Verity, Vmm, cat_config, format_bytes, @@ -2603,13 +2604,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.signed 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.signed 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", @@ -3257,7 +3258,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.signed and not any( p.type.startswith(f"usr-{arch}-verity-sig") or p.type.startswith(f"root-{arch}-verity-sig") for p in partitions ): @@ -3275,8 +3276,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.signed or bool( + config.verity == Verity.auto and config.verity_key and config.verity_certificate ) @@ -3509,7 +3510,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.signed: + unsigned = "" + else: + unsigned = "-unsigned" + r = context.resources / f"repart/definitions/{context.config.output_format}{unsigned}.repart.d" cmdline: list[PathString] = [ @@ -3544,6 +3549,13 @@ def make_extension_or_portable_image(context: Context, output: Path) -> None: if ArtifactOutput.partitions in context.config.split_artifacts: cmdline += ["--split=yes"] + verity = [ + f"root-{context.config.architecture}-verity-sig", + f"usr-{context.config.architecture}-verity-sig", + ] + if context.config.verity == Verity.hash: + cmdline += [f"--exclude-partitions={','.join(verity)}"] + with complete_step(f"Building {context.config.output_format} extension image"): j = json.loads( run_systemd_sign_tool( @@ -4389,7 +4401,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.disabled 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 07644fc97..c1cab71d3 100644 --- a/mkosi/config.py +++ b/mkosi/config.py @@ -363,6 +363,13 @@ def __bool__(self) -> bool: return self != BuildSourcesEphemeral.no +class Verity(StrEnum): + disabled = enum.auto() + hash = enum.auto() + signed = enum.auto() + auto = enum.auto() + + class Architecture(StrEnum): alpha = enum.auto() arc = enum.auto() @@ -1872,7 +1879,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] @@ -3034,7 +3041,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.signed, no=Verity.disabled), + default=Verity.auto, + choices=Verity.values(), help="Configure whether to enforce or disable verity partitions for disk images", ), ConfigSetting( @@ -5171,6 +5180,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 6d0f0d5c5..b879696a7 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 + `signed`, `hash`, `auto` or a boolean value. If set to `signed`, + 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 + `hash`, **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..3bc8df74a 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 `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 diff --git a/tests/test_json.py b/tests/test_json.py index 00a138a4c..2f3679a76 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": "signed", "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.signed, vmm=Vmm.qemu, volatile_package_directories=[Path("def")], volatile_packages=["abc"], From 1f604962a97ac9e977770bbc9865a6d87eb5fe2d Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 3 Feb 2025 17:22:30 +0100 Subject: [PATCH 3/3] Add "defer" setting for Verity This defers the creation of the verity-sig partition which is useful when doing offline signing. --- mkosi/__init__.py | 9 +++++++++ mkosi/config.py | 1 + mkosi/resources/man/mkosi.1.md | 13 +++++++------ mkosi/resources/man/mkosi.news.7.md | 4 ++++ 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/mkosi/__init__.py b/mkosi/__init__.py index e5e1234af..1f2125adb 100644 --- a/mkosi/__init__.py +++ b/mkosi/__init__.py @@ -3353,6 +3353,13 @@ def make_disk( definitions = [defaults] + if context.config.verity == Verity.defer: + skip = [ + *skip, + f"root-{context.config.architecture}-verity-sig", + f"usr-{context.config.architecture}-verity-sig", + ] + return make_image( context, msg=msg, @@ -3555,6 +3562,8 @@ def make_extension_or_portable_image(context: Context, output: Path) -> None: ] if context.config.verity == Verity.hash: cmdline += [f"--exclude-partitions={','.join(verity)}"] + elif context.config.verity == Verity.defer: + cmdline += [f"--defer-partitions={','.join(verity)}"] with complete_step(f"Building {context.config.output_format} extension image"): j = json.loads( diff --git a/mkosi/config.py b/mkosi/config.py index c1cab71d3..5bab619fb 100644 --- a/mkosi/config.py +++ b/mkosi/config.py @@ -367,6 +367,7 @@ class Verity(StrEnum): disabled = enum.auto() hash = enum.auto() signed = enum.auto() + defer = enum.auto() auto = enum.auto() diff --git a/mkosi/resources/man/mkosi.1.md b/mkosi/resources/man/mkosi.1.md index b879696a7..0c6beca73 100644 --- a/mkosi/resources/man/mkosi.1.md +++ b/mkosi/resources/man/mkosi.1.md @@ -1141,17 +1141,18 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`, `Verity=`, `--verity=` : Whether to enforce or disable verity for extension images. Takes one of - `signed`, `hash`, `auto` or a boolean value. If set to `signed`, + `signed`, `hash`, `defer`, `auto` or a boolean value. If set to `signed`, 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 `hash`, **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**. + partition, but no signature partition. If set to `defer`, space for the verity + sig partition will be allocated but it will not be populated yet. 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 diff --git a/mkosi/resources/man/mkosi.news.7.md b/mkosi/resources/man/mkosi.news.7.md index 3bc8df74a..a9b6276c2 100644 --- a/mkosi/resources/man/mkosi.news.7.md +++ b/mkosi/resources/man/mkosi.news.7.md @@ -9,6 +9,10 @@ - Teach `--verity` a new `hash` value, which skips the verity signature partition for extension / portable images. To align the possible values, `yes` is renamed to `signed`. +- Teach `--verity` a new `defer` value, which defers creation of the + verity signature partition for disk, extension and portable images (but + still allocates space for it). This is useful to implement offline + signing of the verity roothash. ## v25