Skip to content

Commit

Permalink
Support unsigned verity backed extension/portable images
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
hundeboll committed Jan 28, 2025
1 parent 0b9ba06 commit 64d0b7b
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 21 deletions.
21 changes: 14 additions & 7 deletions mkosi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
SecureBootSignTool,
ShimBootloader,
Verb,
Verity,
Vmm,
cat_config,
format_bytes,
Expand Down Expand Up @@ -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.yes 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.yes 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",
Expand Down Expand Up @@ -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.yes and not any(
p.type.startswith(f"usr-{arch}-verity-sig") or p.type.startswith(f"root-{arch}-verity-sig")
for p in partitions
):
Expand All @@ -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.yes or bool(
config.verity == Verity.auto and config.verity_key and config.verity_certificate
)


Expand Down Expand Up @@ -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.hash:
unsigned = ""
else:
unsigned = "-unsigned"

r = context.resources / f"repart/definitions/{context.config.output_format}{unsigned}.repart.d"

cmdline: list[PathString] = [
Expand Down Expand Up @@ -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.hash:
cmdline += ["--exclude-partitions=root-verity-sig"]

with complete_step(f"Building {context.config.output_format} extension image"):
j = json.loads(
Expand Down Expand Up @@ -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.no and config.verity_certificate and config.verity_key:
run_systemd_sign_tool(
config,
cmdline=[keyutil, "validate"],
Expand Down
14 changes: 12 additions & 2 deletions mkosi/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,13 @@ def __bool__(self) -> bool:
return self != BuildSourcesEphemeral.no


class Verity(StrEnum):
yes = enum.auto()
no = enum.auto()
hash = enum.auto()
auto = enum.auto()


class Architecture(StrEnum):
alpha = enum.auto()
arc = enum.auto()
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -3031,7 +3038,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.yes, no=Verity.no),
default=Verity.auto,
choices=Verity.values(),
help="Configure whether to enforce or disable verity partitions for disk images",
),
ConfigSetting(
Expand Down Expand Up @@ -5168,6 +5177,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:
Expand Down
21 changes: 11 additions & 10 deletions mkosi/resources/man/mkosi.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -1140,16 +1140,17 @@ 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**.
: Whether to enforce or disable verity for extension images.
Takes a boolean value or one of `hash` and `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 `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 signed verity is not yet implemented
for the `disk` output and only works for extension images at the
Expand Down
5 changes: 5 additions & 0 deletions mkosi/resources/man/mkosi.news.7.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@

# mkosi Changelog

## v26

- Teach --verity a new new `hash` value, which skips the verity signature
partition for extension / portable images.

## v25

- Instead of using bubblewrap, sandboxing is now done with a new tool
Expand Down
5 changes: 3 additions & 2 deletions tests/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
ShimBootloader,
UKIProfile,
Verb,
Verity,
Vmm,
VsockCID,
)
Expand Down Expand Up @@ -388,7 +389,7 @@ def test_config() -> None:
"UseSubvolumes": "auto",
"VSock": "enabled",
"VSockCID": -2,
"Verity": "enabled",
"Verity": "yes",
"VerityCertificate": "/path/to/cert",
"VerityCertificateSource": {
"Source": "",
Expand Down Expand Up @@ -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.yes,
vmm=Vmm.qemu,
volatile_package_directories=[Path("def")],
volatile_packages=["abc"],
Expand Down

0 comments on commit 64d0b7b

Please sign in to comment.