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 \