]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Support unsigned verity backed extension/portable images
authorMartin Hundebøll <martin@geanix.com>
Tue, 28 Jan 2025 20:35:26 +0000 (21:35 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Tue, 4 Feb 2025 13:14:36 +0000 (14:14 +0100)
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
mkosi/config.py
mkosi/resources/man/mkosi.1.md
mkosi/resources/man/mkosi.news.7.md
tests/test_json.py

index 51b06ab0542662481216d73cf7c3fa183bc828b6..e5e1234afbaf2fb1abe50d0376e704be501e972b 100644 (file)
@@ -65,6 +65,7 @@ from mkosi.config import (
     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"],
index 07644fc97a7a57b3fca67580463d3bf20fc3054c..c1cab71d316b1294322111219e3f48358f48efc2 100644 (file)
@@ -363,6 +363,13 @@ class BuildSourcesEphemeral(StrEnum):
         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 @@ SETTINGS: list[ConfigSetting[Any]] = [
         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 json_type_transformer(refcls: Union[type[Args], type[Config]]) -> Callable[[
         list[ArtifactOutput]: enum_list_transformer,
         CertificateSource: certificate_source_transformer,
         ConsoleMode: enum_transformer,
+        Verity: enum_transformer,
     }
 
     def json_transformer(key: str, val: Any) -> Any:
index 6d0f0d5c508e5cf9d894f2892a228c9b85c23d8f..b879696a798241f56347d635d071681cb7bf490b 100644 (file)
@@ -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=`
index c2b04a8ab20f3b9b44b4cc6d1b1ecb67ddbde7a4..3bc8df74a90263f5f8e27420de15a240db9d86a5 100644 (file)
@@ -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
index 00a138a4c89efc950f1a437fbda96aeb06b9aef9..2f3679a769ae439005e70bb79000e7413a29e197 100644 (file)
@@ -36,6 +36,7 @@ from mkosi.config import (
     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"],