From 4f562c3a19a8c01f6df12a9e26a6e0b7bfa13e1b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 9 Oct 2024 13:46:21 +0200 Subject: [PATCH] Add Verity= feature This allows explicitly enabling/disabling use of verity for disk and extension images as requested in #3113.. --- mkosi/__init__.py | 61 ++++++++++++++++--- mkosi/config.py | 9 +++ mkosi/resources/man/mkosi.1.md | 15 +++++ .../confext-unsigned.repart.d/10-root.conf | 6 ++ .../portable-unsigned.repart.d/10-root.conf | 6 ++ .../sysext-unsigned.repart.d/10-root.conf | 7 +++ tests/test_json.py | 2 + 7 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 mkosi/resources/repart/definitions/confext-unsigned.repart.d/10-root.conf create mode 100644 mkosi/resources/repart/definitions/portable-unsigned.repart.d/10-root.conf create mode 100644 mkosi/resources/repart/definitions/sysext-unsigned.repart.d/10-root.conf diff --git a/mkosi/__init__.py b/mkosi/__init__.py index 5aafbb797..133c41da7 100644 --- a/mkosi/__init__.py +++ b/mkosi/__init__.py @@ -2407,6 +2407,18 @@ 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.verity == ConfigFeature.enabled 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: + die( + "Verity= is enabled but no verity certificate is configured", + hint="Run mkosi genkey to generate a key/certificate pair", + ) + for addon in config.pe_addons: if not addon.output: die( @@ -2968,6 +2980,7 @@ def make_image( skip: Sequence[str] = [], split: bool = False, tabs: bool = False, + verity: bool = False, root: Optional[Path] = None, definitions: Sequence[Path] = [], ) -> list[Partition]: @@ -2999,7 +3012,10 @@ def make_image( if context.config.passphrase: cmdline += ["--key-file", workdir(context.config.passphrase)] options += ["--ro-bind", context.config.passphrase, workdir(context.config.passphrase)] - if context.config.verity_key: + 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)] options += ["--bind-try", "/run/pcscd", "/run/pcscd"] @@ -3008,7 +3024,7 @@ def make_image( options += ["--ro-bind", context.config.verity_key, workdir(context.config.verity_key)] else: cmdline += ["--private-key", context.config.verity_key] - if context.config.verity_certificate: + cmdline += ["--certificate", workdir(context.config.verity_certificate)] options += [ "--ro-bind", context.config.verity_certificate, workdir(context.config.verity_certificate), @@ -3050,6 +3066,12 @@ def make_image( partitions = [Partition.from_dict(d) for d in output] + if context.config.verity == ConfigFeature.enabled and not any(p.roothash for p in partitions): + die( + "Verity is explicitly enabled but no verity partitions found", + hint="Make sure to add verity partitions in mkosi.repart if building a disk image", + ) + if split: for p in partitions: if p.split_path: @@ -3058,6 +3080,12 @@ def make_image( return partitions +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 + ) + + def make_disk( context: Context, msg: str, @@ -3131,7 +3159,14 @@ def make_disk( definitions = [defaults] return make_image( - context, msg=msg, skip=skip, split=split, tabs=tabs, root=context.root, definitions=definitions + context, + msg=msg, + skip=skip, + split=split, + tabs=tabs, + verity=want_verity(context.config), + root=context.root, + definitions=definitions, ) @@ -3275,7 +3310,8 @@ def make_esp(context: Context, uki: Path) -> list[Partition]: def make_extension_image(context: Context, output: Path) -> None: - r = context.resources / f"repart/definitions/{context.config.output_format}.repart.d" + unsigned = "-unsigned" if not want_verity(context.config) else "" + r = context.resources / f"repart/definitions/{context.config.output_format}{unsigned}.repart.d" cmdline: list[PathString] = [ "systemd-repart", @@ -3304,7 +3340,10 @@ 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 context.config.verity_key: + 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(): @@ -3312,7 +3351,7 @@ def make_extension_image(context: Context, output: Path) -> None: options += ["--ro-bind", context.config.verity_key, workdir(context.config.verity_key)] else: cmdline += ["--private-key", context.config.verity_key] - if context.config.verity_certificate: + cmdline += ["--certificate", workdir(context.config.verity_certificate)] options += [ "--ro-bind", context.config.verity_certificate, workdir(context.config.verity_certificate) @@ -3341,8 +3380,16 @@ def make_extension_image(context: Context, output: Path) -> None: logging.debug(json.dumps(j, indent=4)) + partitions = [Partition.from_dict(d) for d in j] + + if context.config.verity == ConfigFeature.enabled and not any(p.roothash for p in partitions): + die( + "Verity is explicitly enabled but no verity partitions found", + hint="Make sure to add verity partitions in mkosi.repart if building a disk image", + ) + if context.config.split_artifacts: - for p in (Partition.from_dict(d) for d in j): + for p in partitions: if p.split_path: maybe_compress(context, context.config.compress_output, p.split_path) diff --git a/mkosi/config.py b/mkosi/config.py index 5180ae687..d8f491dc2 100644 --- a/mkosi/config.py +++ b/mkosi/config.py @@ -1666,6 +1666,7 @@ class Config: secure_boot_key_source: KeySource secure_boot_certificate: Optional[Path] secure_boot_sign_tool: SecureBootSignTool + verity: ConfigFeature verity_key: Optional[Path] verity_key_source: KeySource verity_certificate: Optional[Path] @@ -2829,6 +2830,13 @@ def parse_ini(path: Path, only_sections: Collection[str] = ()) -> Iterator[tuple choices=SecureBootSignTool.choices(), help="Tool to use for signing PE binaries for secure boot", ), + ConfigSetting( + dest="verity", + section="Validation", + metavar="FEATURE", + parse=config_parse_feature, + help="Configure whether to enforce or disable verity partitions for disk images", + ), ConfigSetting( dest="verity_key", metavar="KEY", @@ -4552,6 +4560,7 @@ def summary(config: Config) -> str: SecureBoot Signing Key Source: {config.secure_boot_key_source} SecureBoot Certificate: {none_to_none(config.secure_boot_certificate)} 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)} diff --git a/mkosi/resources/man/mkosi.1.md b/mkosi/resources/man/mkosi.1.md index 028127c22..95fa5a853 100644 --- a/mkosi/resources/man/mkosi.1.md +++ b/mkosi/resources/man/mkosi.1.md @@ -1130,6 +1130,21 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`, If set to `auto`, either sbsign or pesign are used if available, with sbsign being preferred if both are installed. +`Verity=`, `--verity=` +: Whether to enforce or disable verity for disk and 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 + disk images produced by systemd-repart even if the partition + definitions contain verity partitions. If set to `auto`, the verity + key and certificate will be passed to systemd-repart if available, + 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 is not yet implemented for the + `disk` output and only works for extension images at the moment. + `VerityKey=`, `--verity-key=` : Path to the PEM file containing the secret key for signing the verity signature, if a verity signature partition is added with systemd-repart. When `VerityKeySource=` is specified, the input type depends on diff --git a/mkosi/resources/repart/definitions/confext-unsigned.repart.d/10-root.conf b/mkosi/resources/repart/definitions/confext-unsigned.repart.d/10-root.conf new file mode 100644 index 000000000..3ccd9090c --- /dev/null +++ b/mkosi/resources/repart/definitions/confext-unsigned.repart.d/10-root.conf @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Partition] +Type=root +Format=erofs +CopyFiles=/etc/ +Minimize=best diff --git a/mkosi/resources/repart/definitions/portable-unsigned.repart.d/10-root.conf b/mkosi/resources/repart/definitions/portable-unsigned.repart.d/10-root.conf new file mode 100644 index 000000000..904819806 --- /dev/null +++ b/mkosi/resources/repart/definitions/portable-unsigned.repart.d/10-root.conf @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Partition] +Type=root +Format=erofs +CopyFiles=/ +Minimize=best diff --git a/mkosi/resources/repart/definitions/sysext-unsigned.repart.d/10-root.conf b/mkosi/resources/repart/definitions/sysext-unsigned.repart.d/10-root.conf new file mode 100644 index 000000000..40743a12b --- /dev/null +++ b/mkosi/resources/repart/definitions/sysext-unsigned.repart.d/10-root.conf @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Partition] +Type=root +Format=erofs +CopyFiles=/opt/ +CopyFiles=/usr/ +Minimize=best diff --git a/tests/test_json.py b/tests/test_json.py index 317152e33..3b3375576 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -372,6 +372,7 @@ def test_config() -> None: "PROPERTY=VALUE" ], "UseSubvolumes": "auto", + "Verity": "enabled", "VerityCertificate": "/path/to/cert", "VerityKey": null, "VerityKeySource": { @@ -550,6 +551,7 @@ def test_config() -> None: unified_kernel_images=ConfigFeature.auto, unit_properties=["PROPERTY=VALUE"], use_subvolumes=ConfigFeature.auto, + verity=ConfigFeature.enabled, verity_certificate=Path("/path/to/cert"), verity_key=None, verity_key_source=KeySource(type=KeySourceType.file),