diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix index f87d3b07a3601..082a760522ae1 100644 --- a/nixos/modules/system/boot/luksroot.nix +++ b/nixos/modules/system/boot/luksroot.nix @@ -439,10 +439,30 @@ let } ''} + ${optionalString (luks.tpm2Support && (dev.tpm2KeyFile != null)) '' + open_with_hardware() { + export TPM2TOOLS_TCTI="device:/dev/tpm0" + + tpm2_unseal -c ${dev.tpm2KeyFile.persistentObject} -p ${dev.tpm2KeyFile.authString} > /crypt-ramfs/tpmKeyfile + if [ $? -ne 0 ]; then + echo "TPM keyfile could not be unsealed, falling back to normal open procedure" + open_normally + return + fi + + ${csopen} --key-file=/crypt-ramfs/tpmKeyfile + if [ $? -ne 0 ]; then + echo "Cannot unlock with TPM keyfile, falling back to normal open procedure" + open_normally + return + fi + } + ''} + # commands to run right before we mount our device ${dev.preOpenCommands} - ${if (luks.yubikeySupport && (dev.yubikey != null)) || (luks.gpgSupport && (dev.gpgCard != null)) || (luks.fido2Support && (dev.fido2.credential != null)) then '' + ${if (luks.yubikeySupport && (dev.yubikey != null)) || (luks.gpgSupport && (dev.gpgCard != null)) || (luks.fido2Support && (dev.fido2.credential != null)) || (luks.tpm2Support && (dev.tpm2KeyFile != null)) then '' open_with_hardware '' else '' open_normally @@ -647,6 +667,31 @@ in ''; }; + tpm2KeyFile = mkOption { + description = '' + Use a TPM-sealed object as a keyfile. + Specify the keyfile object with either tpm2KeyFile.persistentObject or tpm2KeyFile.transientObject + ''; + default = null; + type = types.nullOr (types.submodule { options = { + authString = mkOption { + description = '' + The object's authorization value as defined in tpm2_unseal (1). + For PCR-sealed objects, it would be pcr:[hash algorithm]:[register numbers, comma-separated]. + ''; + type = types.str; + example = "pcr:sha256:0,1,2,3,4,5,6,7"; + }; + persistentObject = mkOption { + description = '' + The handle as a string of the keyfile object stored in NVRAM. + ''; + example = "0x81000000"; + type = types.str; + }; + };}); + }; + gpgCard = mkOption { default = null; description = '' @@ -833,28 +878,28 @@ in ''; }; + boot.initrd.luks.tpm2Support = mkOption { + default = false; + type = types.bool; + description = '' + Enables support for authenticating with TPM-sealed keys. + ''; + }; + }; config = mkIf (luks.devices != {} || luks.forceLuksSupportInInitrd) { - assertions = - [ { assertion = !(luks.gpgSupport && luks.yubikeySupport); - message = "YubiKey and GPG Card may not be used at the same time."; - } + assertions = [ + { assertion = builtins.length (builtins.filter (a: a) [ luks.gpgSupport luks.yubikeySupport luks.fido2Support luks.tpm2Support ]) <= 1; + message = "Only one hardware unlocking method (GPG, Yubikey, FIDO2, TPM keyfile) can be used at once."; + } - { assertion = !(luks.gpgSupport && luks.fido2Support); - message = "FIDO2 and GPG Card may not be used at the same time."; - } - - { assertion = !(luks.fido2Support && luks.yubikeySupport); - message = "FIDO2 and YubiKey may not be used at the same time."; - } - - { assertion = any (dev: dev.bypassWorkqueues) (attrValues luks.devices) - -> versionAtLeast kernelPackages.kernel.version "5.9"; - message = "boot.initrd.luks.devices..bypassWorkqueues is not supported for kernels older than 5.9"; - } - ]; + { assertion = any (dev: dev.bypassWorkqueues) (attrValues luks.devices) + -> versionAtLeast kernelPackages.kernel.version "5.9"; + message = "boot.initrd.luks.devices..bypassWorkqueues is not supported for kernels older than 5.9"; + } + ]; # actually, sbp2 driver is the one enabling the DMA attack, but this needs to be tested boot.blacklistedKernelModules = optionals luks.mitigateDMAAttacks @@ -865,7 +910,8 @@ in ++ luks.cryptoModules # workaround until https://marc.info/?l=linux-crypto-vger&m=148783562211457&w=4 is merged # remove once 'modprobe --show-depends xts' shows ecb as a dependency - ++ (if builtins.elem "xts" luks.cryptoModules then ["ecb"] else []); + ++ (if builtins.elem "xts" luks.cryptoModules then ["ecb"] else []) + ++ (lib.optionals luks.tpm2Support ["rng-core" "tpm" "tpm-tis-core" "tpm-tis"]); # copy the cryptsetup binary and it's dependencies boot.initrd.extraUtilsCommands = '' @@ -914,6 +960,24 @@ in ) (attrValues luks.devices) } ''} + + ${optionalString luks.tpm2Support ( + # tpm2-tools uses the busybox technique of multiple commands symlinked to a single executable. + # But the symlinks in this package point to an intermediary wrapper, bin/tpm2, which calls bash. + # So we should manually build the symlinks. + + # tpm2-tcti patches in hardcoded nix store paths for the tcti drivers '.so's, + # which doesn't work in the initrd structure. We need to disable that. + let + tpm2-tools' = pkgs.tpm2-tools.override { tpm2-tss = pkgs.tpm2-tss.override { loader-path-patch = false; }; }; + in '' + copy_bin_and_libs ${tpm2-tools'}/bin/.tpm2-wrapped + ln -s $out/bin/.tpm2-wrapped $out/bin/tpm2_createprimary + ln -s $out/bin/.tpm2-wrapped $out/bin/tpm2_load + ln -s $out/bin/.tpm2-wrapped $out/bin/tpm2_unseal + cp -pv ${pkgs.tpm2-tss}/lib/libtss2-tcti-device.so $out/lib/libtss2-tcti-device.so + '' + )} ''; boot.initrd.extraUtilsCommandsTest = '' @@ -931,6 +995,11 @@ in ${optionalString luks.fido2Support '' $out/bin/fido2luks --version ''} + ${optionalString luks.tpm2Support '' + $out/bin/tpm2_createprimary --version + $out/bin/tpm2_load --version + $out/bin/tpm2_unseal --version + ''} ''; boot.initrd.preFailCommands = postCommands; diff --git a/pkgs/development/libraries/tpm2-tss/default.nix b/pkgs/development/libraries/tpm2-tss/default.nix index a272cf8b93400..e9d1f6b1fb7b4 100644 --- a/pkgs/development/libraries/tpm2-tss/default.nix +++ b/pkgs/development/libraries/tpm2-tss/default.nix @@ -2,6 +2,7 @@ , autoreconfHook, autoconf-archive, pkg-config, doxygen, perl , openssl, json_c, curl, libgcrypt , cmocka, uthash, ibm-sw-tpm2, iproute2, procps, which +, loader-path-patch ? true }: stdenv.mkDerivation rec { @@ -27,7 +28,7 @@ stdenv.mkDerivation rec { enableParallelBuilding = true; - patches = [ + patches = lib.optional loader-path-patch [ # Do not rely on dynamic loader path # TCTI loader relies on dlopen(), this patch prefixes all calls with the output directory ./no-dynamic-loader-path.patch @@ -35,6 +36,7 @@ stdenv.mkDerivation rec { postPatch = '' patchShebangs script + '' + lib.optionalString loader-path-patch '' substituteInPlace src/tss2-tcti/tctildr-dl.c \ --replace '@PREFIX@' $out/lib/ substituteInPlace ./test/unit/tctildr-dl.c \