diff --git a/mkosi/__init__.py b/mkosi/__init__.py index 51b06ab05..1f2125adb 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 ) @@ -3352,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, @@ -3509,7 +3517,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 +3556,15 @@ 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)}"] + 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( run_systemd_sign_tool( @@ -4389,7 +4410,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..5bab619fb 100644 --- a/mkosi/config.py +++ b/mkosi/config.py @@ -363,6 +363,14 @@ def __bool__(self) -> bool: return self != BuildSourcesEphemeral.no +class Verity(StrEnum): + disabled = enum.auto() + hash = enum.auto() + signed = 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] @@ -3034,7 +3042,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 +5181,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 809bfa25b..3e904f94d 100644 --- a/mkosi/resources/man/mkosi.1.md +++ b/mkosi/resources/man/mkosi.1.md @@ -1140,19 +1140,22 @@ 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 +: Whether to enforce or disable verity for extension images. Takes one of + `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 `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 signed verity is not yet implemented - for the `disk` output and only works for extension images at the + 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..a9b6276c2 100644 --- a/mkosi/resources/man/mkosi.news.7.md +++ b/mkosi/resources/man/mkosi.news.7.md @@ -4,6 +4,16 @@ # 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`. +- 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 - 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"],