From 2e64f4d5edcc9b0b7d0223639139ddc7fe582cd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Christ?= Date: Wed, 22 Oct 2025 18:49:16 +0200 Subject: [PATCH] init userspace verity Enable using systemd's userspace verity certificate store for signature authentication of verity hashes. --- nix/dm-verity.nix | 35 ++++++++-- nix/tests/default.nix | 3 +- .../{dm-verity.nix => dm-verity-kernel.nix} | 5 +- nix/tests/dm-verity-userspace.nix | 66 +++++++++++++++++++ nix/tests/fixtures/{cert.pem => cert.crt} | 0 5 files changed, 102 insertions(+), 7 deletions(-) rename nix/tests/{dm-verity.nix => dm-verity-kernel.nix} (95%) create mode 100644 nix/tests/dm-verity-userspace.nix rename nix/tests/fixtures/{cert.pem => cert.crt} (100%) diff --git a/nix/dm-verity.nix b/nix/dm-verity.nix index 8d91c3f..e3d530d 100644 --- a/nix/dm-verity.nix +++ b/nix/dm-verity.nix @@ -30,21 +30,48 @@ in { options.dm-verity = { enable = lib.mkEnableOption "support for the dm-verity roothash signature verification mechanism."; + where = lib.mkOption { + type = lib.types.enum [ + "kernel" + "userspace" + "kernel+userspace" + ]; + }; trustedKeys = lib.mkOption { type = with lib.types; listOf path; default = [ ]; example = [ ./certificate.pem ]; description = '' - Trusted certificates for the signature verification of dm-verity partitions. The listed - certificate files will be provided as input for the kernel build process in order - to embed them in the kernel keyring. + Trusted certificates for the signature verification of dm-verity partitions. + They need to be PEM encoded. For userspace validation, only certificate files + with the .crt file extension are recognized by systemd. ''; }; }; + config = lib.mkIf cfg.enable { boot.kernelPatches = [ enableVerityRoothashSig ] - ++ lib.optional (cfg.trustedKeys != [ ]) (provideTrustedKeys (mergeCertificates cfg.trustedKeys)); + ++ lib.optional ( + ((cfg.where == "kernel") || (cfg.where == "kernel+userspace")) && cfg.trustedKeys != [ ] + ) (provideTrustedKeys (mergeCertificates cfg.trustedKeys)); + + environment.etc = + let + checkedTrustedKeys = + lib.warnIf (!lib.lists.all (lib.hasSuffix ".crt") cfg.trustedKeys) + "dm-verity: trustedKeys for userspace validation require .crt extension to be considered" + cfg.trustedKeys; + in + + lib.listToAttrs ( + lib.map (keyPath: { + name = "verity.d/${builtins.baseNameOf keyPath}"; + value = { + source = keyPath; + }; + }) checkedTrustedKeys + ); }; } diff --git a/nix/tests/default.nix b/nix/tests/default.nix index 7c8e0d7..00528e6 100644 --- a/nix/tests/default.nix +++ b/nix/tests/default.nix @@ -9,7 +9,8 @@ let heavyTests = if enableHeavyTests then { - dm-verity = runNixosTest ./dm-verity.nix; + dm-verity-userspace = runNixosTest ./dm-verity-userspace.nix; + dm-verity-kernel = runNixosTest ./dm-verity-kernel.nix; } else { }; diff --git a/nix/tests/dm-verity.nix b/nix/tests/dm-verity-kernel.nix similarity index 95% rename from nix/tests/dm-verity.nix rename to nix/tests/dm-verity-kernel.nix index e3bdace..6e83e85 100644 --- a/nix/tests/dm-verity.nix +++ b/nix/tests/dm-verity-kernel.nix @@ -7,7 +7,7 @@ }: let cert_id = "a6b64f7fdadc8ba80554f9da58363cbee9b48d88"; - cert = ./fixtures/cert.pem; + cert = ./fixtures/cert.crt; privk = ./fixtures/privk.pem; in { @@ -26,7 +26,8 @@ in # Embed certificate in the kernel's keyring dm-verity = { enable = true; - trustedKeys = [ ./fixtures/cert.pem ]; + where = "kernel"; + trustedKeys = [ cert ]; }; # Define our extension image. diff --git a/nix/tests/dm-verity-userspace.nix b/nix/tests/dm-verity-userspace.nix new file mode 100644 index 0000000..00e99ca --- /dev/null +++ b/nix/tests/dm-verity-userspace.nix @@ -0,0 +1,66 @@ +# 1. Copy confext created with naext into confext search path +# 2. systemd-confext refresh +# 3. Assert that the confext has been activated and the provided file has the expected content +{ + pkgs, + ... +}: +let + cert_id = "a6b64f7fdadc8ba80554f9da58363cbee9b48d88"; + cert = ./fixtures/cert.crt; + privk = ./fixtures/privk.pem; +in +{ + name = "confext-dm-verity"; + nodes = { + machine = + { + modulesPath, + ... + }: + { + imports = [ + "${modulesPath}/image/repart.nix" + ]; + + # Embed certificate in the kernel's keyring + dm-verity = { + enable = true; + trustedKeys = [ + cert + ]; + where = "userspace"; + }; + + # Define our extension image. + naext = { + seed = "12345678-1234-1234-1234-123456789123"; + privateKey = privk; + certificate = cert; + extensions = { + test = { + extensionType = "confext"; + imageFormat = "verity"; + files = { + "/etc/test".source = pkgs.writeText "example" ''Hello''; + }; + }; + }; + }; + }; + }; + testScript = + { nodes, ... }: + let + testExt = nodes.machine.naext.extensions.test; + in + # python + '' + with subtest("Confext gets applied successfully"): + machine.copy_from_host("${testExt.image}", "/var/lib/confexts/${testExt.name}.${testExt.extensionType}.raw") + machine.succeed("systemd-confext refresh") + machine.wait_for_file("/etc/test") + content=machine.succeed("cat /etc/test") + assert content=="Hello", "File provided by confext has expected content" + ''; +} diff --git a/nix/tests/fixtures/cert.pem b/nix/tests/fixtures/cert.crt similarity index 100% rename from nix/tests/fixtures/cert.pem rename to nix/tests/fixtures/cert.crt