From f42cdfe227d9b16a3036c3b568b12f3f21bc47bf Mon Sep 17 00:00:00 2001 From: Zhaofeng Li Date: Fri, 23 Sep 2022 15:47:05 -0600 Subject: [PATCH 1/7] cryptsetup: Allow reading tokens from relative path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Janne Heß Co-authored-by: Ilan Joselevich --- pkgs/os-specific/linux/cryptsetup/default.nix | 5 ++ .../cryptsetup/relative-token-path.patch | 50 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 pkgs/os-specific/linux/cryptsetup/relative-token-path.patch diff --git a/pkgs/os-specific/linux/cryptsetup/default.nix b/pkgs/os-specific/linux/cryptsetup/default.nix index 62f3f0c4db1..697791446ab 100644 --- a/pkgs/os-specific/linux/cryptsetup/default.nix +++ b/pkgs/os-specific/linux/cryptsetup/default.nix @@ -13,6 +13,11 @@ stdenv.mkDerivation rec { sha256 = "sha256-kYSm672c5+shEVLn90GmyC8tHMDiSoTsnFKTnu4PBUI="; }; + patches = [ + # Allow reading tokens from a relative path, see #167994 + ./relative-token-path.patch + ]; + postPatch = '' patchShebangs tests diff --git a/pkgs/os-specific/linux/cryptsetup/relative-token-path.patch b/pkgs/os-specific/linux/cryptsetup/relative-token-path.patch new file mode 100644 index 00000000000..dffd0ba3bb5 --- /dev/null +++ b/pkgs/os-specific/linux/cryptsetup/relative-token-path.patch @@ -0,0 +1,50 @@ +From 4f95ab1f8110a8ab9d7b0e192731ce467f6e5c26 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Janne=20He=C3=9F?= +Date: Sun, 4 Sep 2022 11:15:02 -0600 +Subject: [PATCH] Allow loading token handlers from the default search path + +Since [1] landed in cryptsetup, token handlers (libcryptsetup-token-*.so) +are loaded from a fixed path defined at compile-time. This is +problematic with NixOS since it introduces a dependency cycle +between cryptsetup and systemd. + +This downstream patch [2] allows loading token plugins from the +default library search path. This approach is not accepted upstream [3] +due to security concerns, but the potential attack vectors require +root access and they are sufficiently addressed: + +* cryptsetup could be used as a setuid binary (not used in NixOS). + In this case, LD_LIBRARY_PATH is ignored because of secure-execution + mode. +* cryptsetup running as root could lead to a malicious token handler + being loaded through LD_LIBRARY_PATH. However, fixing the path + doesn't prevent the same malicious .so being loaded through LD_PRELOAD. + +[1] https://gitlab.com/cryptsetup/cryptsetup/-/commit/5b9e98f94178d3cd179d9f6e2a0a68c7d9eb6507 +[2] https://github.com/NixOS/nixpkgs/issues/167994#issuecomment-1094249369 +[3] https://gitlab.com/cryptsetup/cryptsetup/-/issues/733 +--- + lib/luks2/luks2_token.c | 4 +--- + 1 file changed, 1 insertion(+), 3 deletions(-) + +diff --git a/lib/luks2/luks2_token.c b/lib/luks2/luks2_token.c +index 26467253..6f8329f0 100644 +--- a/lib/luks2/luks2_token.c ++++ b/lib/luks2/luks2_token.c +@@ -151,12 +151,10 @@ crypt_token_load_external(struct crypt_device *cd, const char *name, struct cryp + + token = &ret->u.v2; + +- r = snprintf(buf, sizeof(buf), "%s/libcryptsetup-token-%s.so", crypt_token_external_path(), name); ++ r = snprintf(buf, sizeof(buf), "libcryptsetup-token-%s.so", name); + if (r < 0 || (size_t)r >= sizeof(buf)) + return -EINVAL; + +- assert(*buf == '/'); +- + log_dbg(cd, "Trying to load %s.", buf); + + h = dlopen(buf, RTLD_LAZY); +-- +2.37.2 + From 570824e10253f59c9a2332002b061415b2567627 Mon Sep 17 00:00:00 2001 From: Zhaofeng Li Date: Fri, 23 Sep 2022 15:47:05 -0600 Subject: [PATCH 2/7] systemd: Wrap in LUKS2 tokens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update pkgs/os-specific/linux/systemd/default.nix Co-authored-by: Janne Heß Co-authored-by: Ilan Joselevich Co-authored-by: Jörg Thalheim --- nixos/modules/system/boot/systemd/initrd.nix | 3 +++ pkgs/os-specific/linux/systemd/default.nix | 11 ++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/nixos/modules/system/boot/systemd/initrd.nix b/nixos/modules/system/boot/systemd/initrd.nix index 03f94c426cb..2dfa2926fe1 100644 --- a/nixos/modules/system/boot/systemd/initrd.nix +++ b/nixos/modules/system/boot/systemd/initrd.nix @@ -403,6 +403,9 @@ in { # so NSS can look up usernames "${pkgs.glibc}/lib/libnss_files.so.2" + ] ++ optionals cfg.package.withCryptsetup [ + # the unwrapped systemd-cryptsetup executable + "${cfg.package}/lib/systemd/.systemd-cryptsetup-wrapped" ] ++ jobScripts; targets.initrd.aliases = ["default.target"]; diff --git a/pkgs/os-specific/linux/systemd/default.nix b/pkgs/os-specific/linux/systemd/default.nix index 1cde12f2022..26a916ad819 100644 --- a/pkgs/os-specific/linux/systemd/default.nix +++ b/pkgs/os-specific/linux/systemd/default.nix @@ -7,6 +7,7 @@ , fetchpatch , fetchzip , buildPackages +, makeBinaryWrapper , ninja , meson , m4 @@ -332,6 +333,7 @@ stdenv.mkDerivation { nativeBuildInputs = [ pkg-config + makeBinaryWrapper gperf ninja meson @@ -666,7 +668,14 @@ stdenv.mkDerivation { preFixup = lib.optionalString withEfi '' mv $out/lib/systemd/boot/efi $out/dont-strip-me ''; - postFixup = lib.optionalString withEfi '' + + # Wrap in the correct path for LUKS2 tokens. + postFixup = lib.optionalString withCryptsetup '' + for f in lib/systemd/systemd-cryptsetup bin/systemd-cryptenroll; do + # This needs to be in LD_LIBRARY_PATH because rpath on a binary is not propagated to libraries using dlopen, in this case `libcryptsetup.so` + wrapProgram $out/$f --prefix LD_LIBRARY_PATH : ${placeholder "out"}/lib/cryptsetup + done + '' + lib.optionalString withEfi '' mv $out/dont-strip-me $out/lib/systemd/boot/efi ''; From 19c34ac44b29f0115ba515c3e2ae724d0360b3f7 Mon Sep 17 00:00:00 2001 From: Zhaofeng Li Date: Fri, 23 Sep 2022 15:47:05 -0600 Subject: [PATCH 3/7] systemd/initrd: Add files required by TPM2 and FIDO2 support to the initramfs --- nixos/modules/system/boot/systemd/initrd.nix | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/nixos/modules/system/boot/systemd/initrd.nix b/nixos/modules/system/boot/systemd/initrd.nix index 2dfa2926fe1..d5fa5864cf3 100644 --- a/nixos/modules/system/boot/systemd/initrd.nix +++ b/nixos/modules/system/boot/systemd/initrd.nix @@ -404,6 +404,14 @@ in { # so NSS can look up usernames "${pkgs.glibc}/lib/libnss_files.so.2" ] ++ optionals cfg.package.withCryptsetup [ + # tpm2 support + "${cfg.package}/lib/cryptsetup/libcryptsetup-token-systemd-tpm2.so" + pkgs.tpm2-tss + + # fido2 support + "${cfg.package}/lib/cryptsetup/libcryptsetup-token-systemd-fido2.so" + "${pkgs.libfido2}/lib/libfido2.so.1" + # the unwrapped systemd-cryptsetup executable "${cfg.package}/lib/systemd/.systemd-cryptsetup-wrapped" ] ++ jobScripts; From 9e9637ecb6f254f4e659ee5c291908ec3e977303 Mon Sep 17 00:00:00 2001 From: Jamie McClymont Date: Fri, 23 Sep 2022 15:47:05 -0600 Subject: [PATCH 4/7] nixos/tests/systemd-initrd-luks-tpm2: init --- nixos/tests/all-tests.nix | 1 + nixos/tests/systemd-initrd-luks-tpm2.nix | 72 ++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 nixos/tests/systemd-initrd-luks-tpm2.nix diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 14267cd8afb..021889d1e43 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -597,6 +597,7 @@ in { systemd-initrd-btrfs-raid = handleTest ./systemd-initrd-btrfs-raid.nix {}; systemd-initrd-luks-keyfile = handleTest ./systemd-initrd-luks-keyfile.nix {}; systemd-initrd-luks-password = handleTest ./systemd-initrd-luks-password.nix {}; + systemd-initrd-luks-tpm2 = handleTest ./systemd-initrd-luks-tpm2.nix {}; systemd-initrd-modprobe = handleTest ./systemd-initrd-modprobe.nix {}; systemd-initrd-shutdown = handleTest ./systemd-shutdown.nix { systemdStage1 = true; }; systemd-initrd-simple = handleTest ./systemd-initrd-simple.nix {}; diff --git a/nixos/tests/systemd-initrd-luks-tpm2.nix b/nixos/tests/systemd-initrd-luks-tpm2.nix new file mode 100644 index 00000000000..085088d2ee2 --- /dev/null +++ b/nixos/tests/systemd-initrd-luks-tpm2.nix @@ -0,0 +1,72 @@ +import ./make-test-python.nix ({ lib, pkgs, ... }: { + name = "systemd-initrd-luks-tpm2"; + + nodes.machine = { pkgs, ... }: { + # Use systemd-boot + virtualisation = { + emptyDiskImages = [ 512 ]; + useBootLoader = true; + useEFIBoot = true; + qemu.options = ["-chardev socket,id=chrtpm,path=/tmp/mytpm1/swtpm-sock -tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0"]; + }; + boot.loader.systemd-boot.enable = true; + + boot.initrd.availableKernelModules = [ "tpm_tis" ]; + + environment.systemPackages = with pkgs; [ cryptsetup ]; + boot.initrd.systemd = { + enable = true; + }; + + specialisation.boot-luks.configuration = { + boot.initrd.luks.devices = lib.mkVMOverride { + cryptroot = { + device = "/dev/vdc"; + crypttabExtraOpts = [ "tpm2-device=auto" ]; + }; + }; + virtualisation.bootDevice = "/dev/mapper/cryptroot"; + }; + }; + + testScript = '' + import subprocess + import os + import time + + + class Tpm: + def __init__(self): + os.mkdir("/tmp/mytpm1") + self.start() + + def start(self): + self.proc = subprocess.Popen(["${pkgs.swtpm}/bin/swtpm", "socket", "--tpmstate", "dir=/tmp/mytpm1", "--ctrl", "type=unixio,path=/tmp/mytpm1/swtpm-sock", "--log", "level=20", "--tpm2"]) + + def wait_for_death_then_restart(self): + while self.proc.poll() is None: + print("waiting for tpm to die") + time.sleep(1) + assert self.proc.returncode == 0 + self.start() + + tpm = Tpm() + + + # Create encrypted volume + machine.wait_for_unit("multi-user.target") + machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdc -") + machine.succeed("PASSWORD=supersecret SYSTEMD_LOG_LEVEL=debug systemd-cryptenroll --tpm2-pcrs= --tpm2-device=auto /dev/vdc |& systemd-cat") + + # Boot from the encrypted disk + machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks.conf") + machine.succeed("sync") + machine.crash() + + tpm.wait_for_death_then_restart() + + # Boot and decrypt the disk + machine.wait_for_unit("multi-user.target") + assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount") + ''; +}) From 21bbef95481db371a60d3ff900385e44662b648d Mon Sep 17 00:00:00 2001 From: Zhaofeng Li Date: Fri, 23 Sep 2022 15:47:05 -0600 Subject: [PATCH 5/7] nixos/luksroot: Reword message on FIDO2 support with systemd stage 1 --- nixos/modules/system/boot/luksroot.nix | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix index 38f8b6fd87c..b0c841f4fe5 100644 --- a/nixos/modules/system/boot/luksroot.nix +++ b/nixos/modules/system/boot/luksroot.nix @@ -905,9 +905,11 @@ in { assertion = config.boot.initrd.systemd.enable -> !luks.gpgSupport; message = "systemd stage 1 does not support GPG smartcards yet."; } - # TODO { assertion = config.boot.initrd.systemd.enable -> !luks.fido2Support; - message = "systemd stage 1 does not support FIDO2 yet."; + message = '' + systemd stage 1 does not support configuring FIDO2 unlocking through `boot.initrd.luks.devices..fido2`. + Use systemd-cryptenroll(1) to configure FIDO2 support. + ''; } # TODO { assertion = config.boot.initrd.systemd.enable -> !luks.yubikeySupport; From b9b454820a1c587eb646732fc13c7ebd49edfc27 Mon Sep 17 00:00:00 2001 From: Zhaofeng Li Date: Fri, 23 Sep 2022 15:47:05 -0600 Subject: [PATCH 6/7] systemd/initrd: Add TPM modules into initrd This improves the out-of-box experience of TPM2 unlocking at a small (50K) overhead. --- nixos/modules/system/boot/systemd/initrd.nix | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nixos/modules/system/boot/systemd/initrd.nix b/nixos/modules/system/boot/systemd/initrd.nix index d5fa5864cf3..31702499b0f 100644 --- a/nixos/modules/system/boot/systemd/initrd.nix +++ b/nixos/modules/system/boot/systemd/initrd.nix @@ -332,7 +332,10 @@ in { config = mkIf (config.boot.initrd.enable && cfg.enable) { system.build = { inherit initialRamdisk; }; - boot.initrd.availableKernelModules = [ "autofs4" ]; # systemd needs this for some features + boot.initrd.availableKernelModules = [ + "autofs4" # systemd needs this for some features + "tpm-tis" "tpm-crb" # systemd-cryptenroll + ]; boot.initrd.systemd = { initrdBin = [pkgs.bash pkgs.coreutils cfg.package.kmod cfg.package] ++ config.system.fsPackages; From 78f929c5a6ac6320f5c99eadd805bac4d208c863 Mon Sep 17 00:00:00 2001 From: oxalica Date: Tue, 4 Oct 2022 01:10:03 +0800 Subject: [PATCH 7/7] nixos/tests/systemd-initrd-luks-fido2: init --- nixos/tests/all-tests.nix | 1 + nixos/tests/systemd-initrd-luks-fido2.nix | 45 +++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 nixos/tests/systemd-initrd-luks-fido2.nix diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 021889d1e43..29d82025feb 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -595,6 +595,7 @@ in { systemd-cryptenroll = handleTest ./systemd-cryptenroll.nix {}; systemd-escaping = handleTest ./systemd-escaping.nix {}; systemd-initrd-btrfs-raid = handleTest ./systemd-initrd-btrfs-raid.nix {}; + systemd-initrd-luks-fido2 = handleTest ./systemd-initrd-luks-fido2.nix {}; systemd-initrd-luks-keyfile = handleTest ./systemd-initrd-luks-keyfile.nix {}; systemd-initrd-luks-password = handleTest ./systemd-initrd-luks-password.nix {}; systemd-initrd-luks-tpm2 = handleTest ./systemd-initrd-luks-tpm2.nix {}; diff --git a/nixos/tests/systemd-initrd-luks-fido2.nix b/nixos/tests/systemd-initrd-luks-fido2.nix new file mode 100644 index 00000000000..133e552a3dc --- /dev/null +++ b/nixos/tests/systemd-initrd-luks-fido2.nix @@ -0,0 +1,45 @@ +import ./make-test-python.nix ({ lib, pkgs, ... }: { + name = "systemd-initrd-luks-fido2"; + + nodes.machine = { pkgs, config, ... }: { + # Use systemd-boot + virtualisation = { + emptyDiskImages = [ 512 ]; + useBootLoader = true; + useEFIBoot = true; + qemu.package = lib.mkForce (pkgs.qemu_test.override { canokeySupport = true; }); + qemu.options = [ "-device canokey,file=/tmp/canokey-file" ]; + }; + boot.loader.systemd-boot.enable = true; + + boot.initrd.systemd.enable = true; + + environment.systemPackages = with pkgs; [ cryptsetup ]; + + specialisation.boot-luks.configuration = { + boot.initrd.luks.devices = lib.mkVMOverride { + cryptroot = { + device = "/dev/vdc"; + crypttabExtraOpts = [ "fido2-device=auto" ]; + }; + }; + virtualisation.bootDevice = "/dev/mapper/cryptroot"; + }; + }; + + testScript = '' + # Create encrypted volume + machine.wait_for_unit("multi-user.target") + machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdc -") + machine.succeed("PASSWORD=supersecret SYSTEMD_LOG_LEVEL=debug systemd-cryptenroll --fido2-device=auto /dev/vdc |& systemd-cat") + + # Boot from the encrypted disk + machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks.conf") + machine.succeed("sync") + machine.crash() + + # Boot and decrypt the disk + machine.wait_for_unit("multi-user.target") + assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount") + ''; +})