From ee7bb8550311080ed61acda636a7403d86ba1620 Mon Sep 17 00:00:00 2001 From: Scott Stephens Date: Thu, 4 Sep 2025 16:23:45 -0400 Subject: [PATCH 01/14] nixos/tpm2: add systemd tag to udev rules Updating the udev rules to match what is in recent versions of the suggested rules from the tpm2-tss package. The old rules were based on an old version of the suggested rules and never updated when they were updated in subsequent version upgrades. --- nixos/modules/security/tpm2.nix | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/nixos/modules/security/tpm2.nix b/nixos/modules/security/tpm2.nix index bb947cb32a56..915850fcedf3 100644 --- a/nixos/modules/security/tpm2.nix +++ b/nixos/modules/security/tpm2.nix @@ -12,9 +12,13 @@ let # the tssGroup is only allowed to access the kernel resource manager # Therefore, if either of the two are null, the respective part isn't generated udevRules = tssUser: tssGroup: '' - ${lib.optionalString (tssUser != null) ''KERNEL=="tpm[0-9]*", MODE="0660", OWNER="${tssUser}"''} + ${lib.optionalString ( + tssUser != null + ) ''KERNEL=="tpm[0-9]*", TAG+="systemd", MODE="0660", OWNER="${tssUser}"''} ${ - lib.optionalString (tssUser != null || tssGroup != null) ''KERNEL=="tpmrm[0-9]*", MODE="0660"'' + lib.optionalString ( + tssUser != null || tssGroup != null + ) ''KERNEL=="tpmrm[0-9]*", TAG+="systemd", MODE="0660"'' + lib.optionalString (tssUser != null) '', OWNER="${tssUser}"'' + lib.optionalString (tssGroup != null) '', GROUP="${tssGroup}"'' } From 40f65d338760930ae5d1c3b228dc090e1552bcf5 Mon Sep 17 00:00:00 2001 From: Scott Stephens Date: Fri, 29 Aug 2025 14:26:33 -0400 Subject: [PATCH 02/14] tpm2-tss: add FAPI config for testing --- pkgs/development/libraries/tpm2-tss/default.nix | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkgs/development/libraries/tpm2-tss/default.nix b/pkgs/development/libraries/tpm2-tss/default.nix index cd50c4014904..10c68cad19c3 100644 --- a/pkgs/development/libraries/tpm2-tss/default.nix +++ b/pkgs/development/libraries/tpm2-tss/default.nix @@ -143,6 +143,14 @@ stdenv.mkDerivation (finalAttrs: { # Do not install the upstream udev rules, they rely on specific # users/groups which aren't guaranteed to exist on the system. rm -R $out/lib/udev + + # write fapi-config suitable for testing + cat > $out/etc/tpm2-tss/fapi-config-test.json < Date: Fri, 29 Aug 2025 14:29:15 -0400 Subject: [PATCH 03/14] tpm2-pytss: allow using EV in tests, use EV There was a bug in upstream that prevented environment variables from being used to configure FAPI for testing. This incorporates a fix that is now in upstream but not yet released, and then switches to using environment variables instead of patching to configure the tests. --- .../python-modules/tpm2-pytss/default.nix | 17 +++++++++++------ .../python-modules/tpm2-pytss/fapi-config.patch | 12 ------------ 2 files changed, 11 insertions(+), 18 deletions(-) delete mode 100644 pkgs/development/python-modules/tpm2-pytss/fapi-config.patch diff --git a/pkgs/development/python-modules/tpm2-pytss/default.nix b/pkgs/development/python-modules/tpm2-pytss/default.nix index e8cdb0ad065a..35899915eb67 100644 --- a/pkgs/development/python-modules/tpm2-pytss/default.nix +++ b/pkgs/development/python-modules/tpm2-pytss/default.nix @@ -5,6 +5,7 @@ buildPythonPackage, fetchPypi, fetchpatch, + fetchpatch2, pythonOlder, asn1crypto, cffi, @@ -35,8 +36,6 @@ buildPythonPackage rec { }; patches = [ - # Fix hardcoded `fapi-config.json` configuration path - ./fapi-config.patch # libtpms (underneath swtpm) bumped the TPM revision # https://github.com/tpm2-software/tpm2-pytss/pull/593 (fetchpatch { @@ -49,6 +48,12 @@ buildPythonPackage rec { url = "https://github.com/tpm2-software/tpm2-pytss/commit/6ab4c74e6fb3da7cd38e97c1f8e92532312f8439.patch"; hash = "sha256-01Qe4qpD2IINc5Z120iVdPitiLBwdr8KNBjLFnGgE7E="; }) + # Properly restore environment variables upon exit from + # FAPIConfig context. Accepted into upstream, not yet released. + (fetchpatch2 { + url = "https://github.com/tpm2-software/tpm2-pytss/commit/afdee627d0639eb05711a2191f2f76e460793da9.patch?full_index=1"; + hash = "sha256-Y6drcBg4gnbSvnCGw69b42Q/QfLI3u56BGRUEkpdB0M="; + }) ] ++ lib.optionals isCross [ # pytss will regenerate files from headers of tpm2-tss. @@ -63,10 +68,6 @@ buildPythonPackage rec { }) ]; - postPatch = '' - sed -i "s#@TPM2_TSS@#${tpm2-tss.out}#" src/tpm2_pytss/FAPI.py - ''; - # Hardening has to be disabled # due to pycparsing handling it poorly. # See https://github.com/NixOS/nixpkgs/issues/252023 @@ -95,6 +96,10 @@ buildPythonPackage rec { swtpm ]; + preCheck = '' + export TSS2_FAPICONF=${tpm2-tss.out}/etc/tpm2-tss/fapi-config-test.json + ''; + pythonImportsCheck = [ "tpm2_pytss" ]; meta = with lib; { diff --git a/pkgs/development/python-modules/tpm2-pytss/fapi-config.patch b/pkgs/development/python-modules/tpm2-pytss/fapi-config.patch deleted file mode 100644 index f6245ef30467..000000000000 --- a/pkgs/development/python-modules/tpm2-pytss/fapi-config.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/src/tpm2_pytss/FAPI.py b/src/tpm2_pytss/FAPI.py -index 916eda6..1afba3f 100644 ---- a/src/tpm2_pytss/FAPI.py -+++ b/src/tpm2_pytss/FAPI.py -@@ -23,6 +23,7 @@ logger = logging.getLogger(__name__) - - FAPI_CONFIG_ENV = "TSS2_FAPICONF" - FAPI_CONFIG_PATHS = [ -+ "@TPM2_TSS@/etc/tpm2-tss/fapi-config.json", - "/etc/tpm2-tss/fapi-config.json", - "/usr/local/etc/tpm2-tss/fapi-config.json", - ] From 1d1e46f47822e8d9c380ca40f570418eb26492d8 Mon Sep 17 00:00:00 2001 From: Scott Stephens Date: Fri, 29 Aug 2025 17:34:40 -0400 Subject: [PATCH 04/14] nixos/tpm2: add fapi options section This allows a user to generate a fapi-config.json file and links /etc/tpm2-tss/fapi-config.json to it. --- nixos/modules/security/tpm2.nix | 116 ++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/nixos/modules/security/tpm2.nix b/nixos/modules/security/tpm2.nix index 915850fcedf3..68975b22cf28 100644 --- a/nixos/modules/security/tpm2.nix +++ b/nixos/modules/security/tpm2.nix @@ -24,6 +24,27 @@ let } ''; + fapiConfig = ( + pkgs.writeText "fapi-config.json" ( + builtins.toJSON ( + { + profile_name = cfg.fapi.profileName; + profile_dir = cfg.fapi.profileDir; + user_dir = cfg.fapi.userDir; + system_dir = cfg.fapi.systemDir; + tcti = cfg.fapi.tcti; + system_pcrs = cfg.fapi.systemPcrs; + log_dir = cfg.fapi.logDir; + firmware_log_file = cfg.fapi.firmwareLogFile; + ima_log_file = cfg.fapi.imaLogFile; + } + // lib.optionalAttrs (cfg.fapi.ekCertLess != null) { + ek_cert_less = if cfg.fapi.ekCertLess then "yes" else "no"; + } + // lib.optionalAttrs (cfg.fapi.ekFingerprint != null) { ek_fingerprint = cfg.fapi.ekFingerprint; } + ) + ) + ); in { options.security.tpm2 = { @@ -132,6 +153,97 @@ in default = "bus_name=com.intel.tss2.Tabrmd"; }; }; + + fapi = { + profileName = lib.mkOption { + description = '' + Name of the default cryptographic profile chosen from the profile_dir directory. + ''; + type = lib.types.str; + default = "P_ECCP256SHA256"; + }; + + profileDir = lib.mkOption { + description = '' + Directory that contains all cryptographic profiles known to FAPI. + ''; + type = lib.types.str; + default = "${pkgs.tpm2-tss}/etc/tpm2-tss/fapi-profiles/"; + defaultText = lib.literalExpression "\${pkgs.tpm2-tss}/etc/fapi-profiles/"; + }; + + userDir = lib.mkOption { + description = '' + The directory where user objects are stored. + ''; + type = lib.types.str; + default = "~/.local/share/tpm2-tss/user/keystore/"; + }; + + systemDir = lib.mkOption { + description = '' + The directory where system objects, policies, and imported objects are stored. + ''; + type = lib.types.str; + default = "/var/lib/tpm2-tss/keystore"; + }; + + tcti = lib.mkOption { + description = '' + The TCTI interface which will be used. + ''; + type = lib.types.str; + default = ""; + }; + + systemPcrs = lib.mkOption { + description = '' + The PCR registers which are used by the system. + ''; + type = lib.types.listOf lib.types.int; + default = [ ]; + }; + + logDir = lib.mkOption { + description = '' + The directory for the event log. + ''; + type = lib.types.str; + default = "/var/log/tpm2-tss/eventlog/"; + }; + + ekCertLess = lib.mkOption { + description = '' + A switch to disable certificate verification. + ''; + type = lib.types.nullOr lib.types.bool; + default = null; + }; + + ekFingerprint = lib.mkOption { + description = '' + The fingerprint of the endorsement key. + ''; + type = lib.types.nullOr lib.types.str; + default = null; + }; + + firmwareLogFile = lib.mkOption { + description = '' + The binary bios measurements. + ''; + type = lib.types.str; + default = "/sys/kernel/security/tpm0/binary_bios_measurements"; + }; + + imaLogFile = lib.mkOption { + description = '' + The binary IMA measurements (Integrity Measurement Architecture). + ''; + type = lib.types.str; + default = "/sys/kernel/security/ima/binary_runtime_measurements"; + }; + }; }; config = lib.mkIf cfg.enable ( @@ -186,6 +298,10 @@ in services.dbus.packages = lib.singleton cfg.abrmd.package; }) + + { + environment.etc."tpm2-tss/fapi-config.json".source = fapiConfig; + } ] ); From fa63c18d8835ac59e4cfa478d6eac31dcfe8177b Mon Sep 17 00:00:00 2001 From: Scott Stephens Date: Fri, 29 Aug 2025 14:37:15 -0400 Subject: [PATCH 05/14] tpm2-tss: change default path of fapi config file The default path is hard-coded at compile time. Previously it pointed to a location inside the nix store, which made it necessary to use environment variables to specify the path to a user-specified file. The nature of the config file is such that it's not possible to specify one that works for every system this package may be installed in, so user-configuration is usually necessary. The default is now /etc/tpm2-tss/fapi-config.json, which is more compatible with non-nixos config conventions. NixOS compatibility is now accomplished by linking from the well-known location to a config file in the nix store. --- pkgs/development/libraries/tpm2-tss/default.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkgs/development/libraries/tpm2-tss/default.nix b/pkgs/development/libraries/tpm2-tss/default.nix index 10c68cad19c3..524a3c1a8657 100644 --- a/pkgs/development/libraries/tpm2-tss/default.nix +++ b/pkgs/development/libraries/tpm2-tss/default.nix @@ -113,6 +113,8 @@ stdenv.mkDerivation (finalAttrs: { --replace-fail '"libtpms.so"' '"${libtpms.out}/lib/libtpms.so"' \ --replace-fail '"libtpms.so.0"' '"${libtpms.out}/lib/libtpms.so.0"' done + substituteInPlace src/tss2-fapi/ifapi_config.c \ + --replace-fail 'SYSCONFDIR' '"/etc"' '' # tcti tests rely on mocking function calls, which appears not to be supported # on clang From b293d9917a7c8f97ad9eca642f26c38a9646fb3c Mon Sep 17 00:00:00 2001 From: Scott Stephens Date: Fri, 29 Aug 2025 18:59:39 -0400 Subject: [PATCH 06/14] nixos/tpm2: trigger udev change on tpm devices The package changes udev rules. A device change must be triggered in order for these changes to be picked up, otherwise services which depend on the configuration will fail. --- nixos/modules/security/tpm2.nix | 49 +++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/nixos/modules/security/tpm2.nix b/nixos/modules/security/tpm2.nix index 68975b22cf28..a0dfb9534816 100644 --- a/nixos/modules/security/tpm2.nix +++ b/nixos/modules/security/tpm2.nix @@ -282,8 +282,57 @@ in ); } + { + systemd.services."tpm2-udev-trigger" = + let + udevHash = + if cfg.applyUdevRules then (builtins.hashString "md5" (udevRules cfg.tssUser cfg.tssGroup)) else ""; + in + { + description = "Trigger udev change for TPM devices"; + wants = [ "systemd-udevd.service" ]; + after = [ + "tpm2.target" + "systemd-udevd.service" + ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStart = pkgs.writeShellScript "tpm2-udev-trigger.sh" '' + stateDir=/var/lib/tpm2-udev-trigger + mkdir -p $stateDir + newHash=${udevHash} + hashFile=$stateDir/hash.txt + + # if file exists, read old hash + if [ -f $hashFile ]; then + oldHash=$(cat $hashFile) + else + oldHash="" + fi + + if [ "$oldHash" != "$newHash" ]; then + echo "TPM udev rules changed, triggering udev" + for i in $(seq 0 9); do + if [ -e /dev/tpm$i ]; then + ${config.systemd.package}/bin/udevadm trigger --name-match=tpm$i --action=change + ${config.systemd.package}/bin/udevadm trigger --name-match=tpmrm$i --action=change + fi + done + echo "$newHash" > $hashFile + else + echo "TPM udev rules unchanged, not triggering udev" + fi + ''; + }; + }; + } + (lib.mkIf cfg.abrmd.enable { systemd.services."tpm2-abrmd" = { + wants = [ "tpm2-udev-trigger.service" ]; + after = [ "tpm2-udev-trigger.service" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "dbus"; From 058f41488ebfe995d4c7ee62e6aade272ab72e7f Mon Sep 17 00:00:00 2001 From: Scott Stephens Date: Thu, 4 Sep 2025 16:18:58 -0400 Subject: [PATCH 07/14] nixos/tpm2: start tpm2-abrmd after dev-tpm0.device tpm2-abrmd can't start up until /dev/tpm0 has been created and its udev rules applied. This prevents a failure on startup by waiting for that to happen. --- nixos/modules/security/tpm2.nix | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/nixos/modules/security/tpm2.nix b/nixos/modules/security/tpm2.nix index a0dfb9534816..1c274842beec 100644 --- a/nixos/modules/security/tpm2.nix +++ b/nixos/modules/security/tpm2.nix @@ -331,8 +331,14 @@ in (lib.mkIf cfg.abrmd.enable { systemd.services."tpm2-abrmd" = { - wants = [ "tpm2-udev-trigger.service" ]; - after = [ "tpm2-udev-trigger.service" ]; + wants = [ + "tpm2-udev-trigger.service" + "dev-tpm0.device" + ]; + after = [ + "tpm2-udev-trigger.service" + "dev-tpm0.device" + ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "dbus"; From ddd27d1a4b2aa5012a7b2725b76e44f290944657 Mon Sep 17 00:00:00 2001 From: Scott Stephens Date: Tue, 2 Sep 2025 12:37:59 -0400 Subject: [PATCH 08/14] nixos/tpm2: create directories for FAPI --- nixos/modules/security/tpm2.nix | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nixos/modules/security/tpm2.nix b/nixos/modules/security/tpm2.nix index 1c274842beec..ca7dd967a531 100644 --- a/nixos/modules/security/tpm2.nix +++ b/nixos/modules/security/tpm2.nix @@ -356,6 +356,10 @@ in { environment.etc."tpm2-tss/fapi-config.json".source = fapiConfig; + systemd.tmpfiles.rules = [ + "d ${cfg.fapi.logDir} 2750 tss ${cfg.tssGroup} -" + "d ${cfg.fapi.systemDir} 2750 root ${cfg.tssGroup} -" + ]; } ] ); From a28fc3713db88a92c7d5b49b8892a2febc5c39ee Mon Sep 17 00:00:00 2001 From: Scott Stephens Date: Thu, 4 Sep 2025 12:05:52 -0400 Subject: [PATCH 09/14] nixos/tpm2: add tests --- nixos/tests/all-tests.nix | 1 + nixos/tests/tpm2.nix | 74 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 nixos/tests/tpm2.nix diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 93243f29082a..1f11ac13e676 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1509,6 +1509,7 @@ in tomcat = runTest ./tomcat.nix; tor = runTest ./tor.nix; tpm-ek = handleTest ./tpm-ek { }; + tpm2 = runTest ./tpm2.nix; # tracee requires bpf tracee = handleTestOn [ "x86_64-linux" ] ./tracee.nix { }; traefik = runTestOn [ "aarch64-linux" "x86_64-linux" ] ./traefik.nix; diff --git a/nixos/tests/tpm2.nix b/nixos/tests/tpm2.nix new file mode 100644 index 000000000000..e38020f6e5ba --- /dev/null +++ b/nixos/tests/tpm2.nix @@ -0,0 +1,74 @@ +{ lib, pkgs, ... }: +{ + name = "tpm2"; + + nodes.machine = + { config, pkgs, ... }: + { + virtualisation = { + mountHostNixStore = true; + useEFIBoot = true; + tpm.enable = true; + }; + + users.users = { + tss-user = { + isNormalUser = true; + extraGroups = [ "tss" ]; + }; + }; + + security.sudo.wheelNeedsPassword = false; + + security.tpm2 = { + enable = true; + pkcs11.enable = true; + abrmd.enable = true; + tctiEnvironment.enable = true; + tctiEnvironment.interface = "tabrmd"; + fapi.ekCertLess = true; + }; + + environment.systemPackages = [ + pkgs.tpm2-tools + pkgs.openssl + ]; + }; + + testScript = '' + machine.start() + machine.wait_for_unit("multi-user.target") + + with subtest("tabrmd service started properly"): + machine.succeed('[ `systemctl show tpm2-abrmd.service --property=Result` = "Result=success" ]') + machine.succeed('[ `journalctl -b -u tpm2-abrmd.service | grep -c "Starting"` = "1" ]') + + with subtest("tpm2 cli works"): + machine.succeed('tpm2 createprimary --hierarchy=o --key-algorithm=aes256 --attributes="fixedtpm|fixedparent|sensitivedataorigin|userwithauth|restricted|decrypt" --key-context=owner_root_key.ctx') + machine.succeed('tpm2 create --parent-context=owner_root_key.ctx --key-algorithm=ecc256:ecdsa-sha256:null --attributes="fixedtpm|fixedparent|sensitivedataorigin|userwithauth|restricted|sign" --key-context=ecc_sign_key.ctx --creation-ticket=ecc_sign_key-creation_ticket.bin -f pem --output=ecc_sign_key_public.pem') + machine.succeed('echo "A very important message." > message.txt') + machine.succeed('tpm2 sign --key-context=ecc_sign_key.ctx --hash-algorithm=sha256 -f plain --signature message_signature.bin message.txt') + machine.succeed('openssl dgst -verify ecc_sign_key_public.pem -signature message_signature.bin message.txt') + machine.succeed('echo "evil addition!" >> message.txt') + machine.fail('openssl dgst -verify ecc_sign_key_public.pem -signature message_signature.bin message.txt') + + def format_command(command, user): + return f"runuser -u {user} -- bash -c '{command}'" + def succeedu(command,user): + return machine.succeed(format_command(command,user)) + def failu(command,user): + return machine.fail(format_command(command,user)) + + with subtest("tss2 cli works"): + machine.succeed('tss2 provision') + succeedu('tss2 createkey --path=HS/SRK/sign --type=sign --authValue=""',"tss-user") + succeedu('tss2 gettpmblobs --path=HS/SRK/sign --tpm2bPublic=$HOME/sign_key_public.bin',"tss-user") + succeedu('tpm2 print -t TPM2B_PUBLIC -f pem $HOME/sign_key_public.bin > $HOME/sign_key_public.pem',"tss-user") + succeedu('echo "A very important message." > $HOME/message.txt',"tss-user") + succeedu('tpm2 hash --hash-algorithm=sha256 --output=$HOME/message_hash.bin $HOME/message.txt',"tss-user") + succeedu('tss2 sign --keyPath=HS/SRK/sign --digest=$HOME/message_hash.bin --signature=$HOME/message_signature.bin',"tss-user") + succeedu('openssl dgst -verify $HOME/sign_key_public.pem -signature $HOME/message_signature.bin $HOME/message.txt',"tss-user") + succeedu('echo "evil addition!" >> $HOME/message.txt',"tss-user") + failu('openssl dgst -verify $HOME/sign_key_public.pem -signature $HOME/message_signature.bin $HOME/message.txt',"tss-user") + ''; +} From 94976dee91ea62ca38c21fcda357af54b3565454 Mon Sep 17 00:00:00 2001 From: Scott Stephens Date: Thu, 11 Sep 2025 21:10:21 -0500 Subject: [PATCH 10/14] nixos/tpm2: use redirection instead of cat Co-authored-by: Philip Taron --- nixos/modules/security/tpm2.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/modules/security/tpm2.nix b/nixos/modules/security/tpm2.nix index ca7dd967a531..c69f903923b7 100644 --- a/nixos/modules/security/tpm2.nix +++ b/nixos/modules/security/tpm2.nix @@ -307,7 +307,7 @@ in # if file exists, read old hash if [ -f $hashFile ]; then - oldHash=$(cat $hashFile) + oldHash="$(< $hashFile)" else oldHash="" fi From 2b8e3c647e4f5dbe79174fbfbb807d9671b915dd Mon Sep 17 00:00:00 2001 From: Scott Stephens Date: Mon, 15 Sep 2025 14:09:20 -0400 Subject: [PATCH 11/14] nixos/tpm2: improve module options documentation --- nixos/modules/security/tpm2.nix | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/nixos/modules/security/tpm2.nix b/nixos/modules/security/tpm2.nix index c69f903923b7..f0d357d7b489 100644 --- a/nixos/modules/security/tpm2.nix +++ b/nixos/modules/security/tpm2.nix @@ -190,10 +190,18 @@ in tcti = lib.mkOption { description = '' - The TCTI interface which will be used. + The TCTI which will be used. + + An empty string indicates no TCTI is specified by the FAPI config. + + If not specified in the FAPI config it can be specified by environment + variable (TPM2TOOLS_TCTI, TPM2_PKCS11_TCTI, etc) or a TCTI will be chosen + by the FAPI library by searching for tabrmd, device, and mssim TCTIs in + that order. ''; type = lib.types.str; default = ""; + example = "device:/dev/tpmrm0"; }; systemPcrs = lib.mkOption { @@ -214,7 +222,17 @@ in ekCertLess = lib.mkOption { description = '' - A switch to disable certificate verification. + A switch to disable Endorsement Key (EK) certificate verification. + + A value of null indicates that the generated fapi config file does not + contain a ek_cert_less key. The effect of not having that key at all is + the same as setting its value to false. + + A value of false means that the tss2 cli will not work if there is no + EK Cert installed, or if the installed EK Cert can't be validated. + + A value of true means that the tss2 cli will work even if there's no EK + cert installed. ''; type = lib.types.nullOr lib.types.bool; default = null; @@ -223,6 +241,11 @@ in ekFingerprint = lib.mkOption { description = '' The fingerprint of the endorsement key. + + A value of null means that you have chosen not to specify the expected + fingerprint of the EK. You can still have an endorsement key, it just + won't get checked to see if it's fingerprint matches a particular value + before being used. ''; type = lib.types.nullOr lib.types.str; default = null; From db83ec4e2ab3788c64d9468da80af7c78bd5991f Mon Sep 17 00:00:00 2001 From: Scott Stephens Date: Mon, 15 Sep 2025 14:57:12 -0400 Subject: [PATCH 12/14] nixos/tpm2: add manual entry --- nixos/doc/manual/redirects.json | 24 +++++++ nixos/modules/security/tpm2.md | 116 ++++++++++++++++++++++++++++++++ nixos/modules/security/tpm2.nix | 1 + 3 files changed, 141 insertions(+) create mode 100644 nixos/modules/security/tpm2.md diff --git a/nixos/doc/manual/redirects.json b/nixos/doc/manual/redirects.json index 297956be881b..65538f95d773 100644 --- a/nixos/doc/manual/redirects.json +++ b/nixos/doc/manual/redirects.json @@ -1,4 +1,28 @@ { + "module-security-tpm2": [ + "index.html#module-security-tpm2" + ], + "module-security-tpm2-introduction": [ + "index.html#module-security-tpm2-introduction" + ], + "module-security-tpm2-introduction-hli": [ + "index.html#module-security-tpm2-introduction-hli" + ], + "module-security-tpm2-introduction-resourcemanagers": [ + "index.html#module-security-tpm2-introduction-resourcemanagers" + ], + "module-security-tpm2-introduction-softwarearchitecture": [ + "index.html#module-security-tpm2-introduction-softwarearchitecture" + ], + "module-security-tpm2-introduction-softwarearchitecture-tcti": [ + "index.html#module-security-tpm2-introduction-softwarearchitecture-tcti" + ], + "module-security-tpm2-nixosmodule": [ + "index.html#module-security-tpm2-nixosmodule" + ], + "module-security-tpm2-nixosmodule-fapiconfiguration": [ + "index.html#module-security-tpm2-nixosmodule-fapiconfiguration" + ], "module-boot-plymouth-tpm2-totp": [ "index.html#module-boot-plymouth-tpm2-totp" ], diff --git a/nixos/modules/security/tpm2.md b/nixos/modules/security/tpm2.md new file mode 100644 index 000000000000..4fff715ab039 --- /dev/null +++ b/nixos/modules/security/tpm2.md @@ -0,0 +1,116 @@ +# TPM2 {#module-security-tpm2} + +## Introduction {#module-security-tpm2-introduction} + +The `tpm2` module allows configuration of a number of programs and services associated with the use of a Trusted Platform Module (TPM). +A TPM is a hardware or firmware device that can be useful for security purposes. +One thing you can do with a TPM is to instruct it to create cryptographic keys which reside inside the TPM hardware, and which cannot be exported, not even to the computer hosting the TPM. +To make those keys useful, the TPM also exposes operations that you can perform using the keys. +So for instance, you can instruct the TPM to encrypt a piece of data using a cryptographic key protected by the TPM, or cryptographically sign a piece of data using a cryptographic key protected by the TPM. + +A key stored directly on the TPM is called a resident key. +Because the TPM has very limited storage, it's common to work with another kind of key, called a wrapped key. +When you instruct the TPM to create a wrapped key, it creates a new key, encrypts the secret material using a key stored directly on the TPM, and returns the encrypted secret material along with some metadata to the user, in a file called the key context. +The TPM also supports operations where you provide it with the key context along with whatever you want to encrypt or sign, and it will unwrap the key and do the operation. + +Another important concept is attestation. +The idea is that the user of a TPM may want to prove to a third party that a particular key is protected by the TPM hardware. +This is typically done by the manufacturer or distributor of the TPM hardware prior to distribution to the end user, and involves creating a resident key on the TPM, along with a certificate for that key signed by the manufacturer's or distributor's key. +The user can then provide the certificate to the third party along with the public portion of the resident key to prove that the key is protected by the TPM. + +Most physical TPMs come with one resident key that also has a certificate. +This key is known as the Endorsement Key, or EK, and the certificate is known as the EK Certificate. +For applications where you want to be able to prove properties of a key to third parties, you will want a key that is wrapped by the EK. +Such keys are described as residing in the Endorsement Hierarchy. +If you do not require attestation, you will generally wrap your keys with the storage root key (SRK). +Such keys are described as residing in the Storage Hierarchy, or the Owners Hierarchy. + +### Software Architecture {#module-security-tpm2-introduction-softwarearchitecture} + +#### TCTI {#module-security-tpm2-introduction-softwarearchitecture-tcti} + +TPM hardware uses a binary protocol called the TPM Command Transmission Interface, or TCTI, to communicate with the host computer. +The TPM kernel driver exposes a character device, typically `/dev/tpm0`, and one way of interacting with the TPM is to read and write the TCTI protocol to that device. +Of course doing that directly in your own code would be quite laborious, and there are a number of software libraries and programs that can help you do it. +The lowest level of these is C library called ESAPI, located inside the `tpm2-tss` package. + +#### Resource Managers {#module-security-tpm2-introduction-resourcemanagers} + +Another thing you need to know is that the TPM is a stateful device: operations can affect its state, and it's common to perform a sequence of operations where early operations modify the state, and later operations depend on that state to do other operations. +For instance, you may load a wrapped key to a particular storage location, then do an encryption operation using that loaded key. +This makes multi-user access challenging, as two users may modify the state in incompatible ways. +The solution to this is to use a resource manager. +The resource manager can handle multiple different sessions, keep track of the accumulated state of each session, and load and unload state as necessary to interleave multiple different sessions. +There are two resource managers available in the software stack surrounding the TPM: a kernel-space resource manager, and a user-space resource manager. + +The kernel-space resource manager is exposed via the tpmrm subsystem. +You can see more info about that subsystem in the sysfs at `/sys/class/tpmrm`. +In a typical system you will also see a `/dev/tpmrm0` device, but it is possible to give it a different name. + +The user-space resource manager is contained in the `tpm2-abrmd` package. +It is also frequently referred to as `tabrmd`. + +Both resource managers are designed so that they use the same TCTI protocol as a raw tpm device. +For the kernel RM, you access it via a character device (typically `/dev/tpmrm0`). +For the userspace RM, you access it over DBUS. +It is common to refer to a component that is on the "receiving" or "server" side of the TCTI protocol as "a TCTI". +So the raw tpm character device, the kernel RM, and `tabrmd` are all "TCTIs". + +The ESAPI library speaks the client side of the TCTI protocol, and can be connected to any server TCTI. +All of the other libraries or programs that work with TPM all use ESAPI under the hood, and so a common characteristic among all these libraries is that you will find you need to configure them in some way as to which TCTI they should be talking to. + +#### Higher Level Interfaces {#module-security-tpm2-introduction-hli} + +As alluded to previously, there are a number of ways of speaking the client side TCTI that all amount to wrappers around ESAPI. They include: +* FAPI - a C library with an easier API for managing cryptographic algorithms and keys, contained in the `tpm2-tss` package. +* pytss - a python library that wraps ESAPI and FAPI. Contained in the `tpm2-pytss` package. +* tpm2 tools - a command line interface exposed through the programs `tpm2`, which roughly corresponds to the ESAPI, and `tss2` which roughly corresponds to FAPI. Contained in the `tpm2-tools` package. +* pkcs11 - libraries that wrap TPM functionality into a pkcs11 interface. There are variants for the ESAPI, and the FAPI. See the `tpm2-pkcs11`, `tpm2-pkcs11-esapi` and `tpm2-pkcs11-fapi` packages. +* TPM2 OpenSSL - an OpenSSL provider allowing openssl to use the TPM as its cryptographic engine. + + +## Using the tpm2 NixOS module {#module-security-tpm2-nixosmodule} + +A typical configuration is: +``` +security.tpm2 = { + enable = true; + abrmd.enable = true; + pkcs11.enable = true; + + tctiEnvironment.enable = true; + tctiEnvironment.interface = "tabrmd"; +} +``` +`enable = true;` is required for any tpm functionality other than the raw character device and kernel resource manager to be available. + +`abrmd.enable = true;` causes the tpm2-abrmd program (the user-space resource manager) to run as a systemd service. +Generally you want this because the user-space resource manager gets more frequent updates than the kernel-space RM, and there aren't any kernel RM features that are unavailable in the user-space RM. + +`pkcs11.enable = true;` makes the PKCS11 tool and libraries available in the system path. +Generally you want this because it's unlikely to cause problems and it's required by one of the more common TPM use cases, which is protecting an ssh key using the TPM. + +`tctiEnvironment.enable = true;` and `tctiEnvironment.interface = "tabrmd";` causes the TPM2TOOLS_TCTI and TPM2_PKCS11_TCTI environment variables to be set properly to use the user-space resource manager. +In other words, it configures all users to use the user-space resource manager for tpm2 tools and tpm2 pkcs11 by default. + +### FAPI Configuration {#module-security-tpm2-nixosmodule-fapiconfiguration} + +A reasonable FAPI config file is shipped by default and available in `/etc/tpm2-tss/fapi-config.json`. +The `tss2` command line utility in `tpm2-tools` will use this file by default. +If you wish to customize it, you can do so like this: + +``` +security.tpm2.fapi = { + profileName = "P_RSA2048SHA256"; +}; +``` + +This example changes the cryptographic profile from the default P_ECCP256SHA256 to P_RSA2048SHA256. +The profiles specify a number of algorithmic details, but the short version is that P_ECCP256SHA256 uses elliptic curve cryptography over the NIST P256 curve and SHA256 for hashes, while P_RSA2048SHA256 uses RSA 2048 bit keys and SHA256 for hashes. + +Probably the most likely option you may want to use in the FAPI config would be `ekCertLess`. +If set to `null` (the default) or `false`, then FAPI calls will fail if the TPM does not have an EK certificate. +Most TPMs that come with physical computers will have an EK cert, so the default setting would not be a problem. +However, virtual TPMs may not have an EK certificate. +For instance, the Nitro TPM provided on some Amazon Web Services virtual machines does not come with an EK Cert. +In such cases, you may wish to set `ekCertLess = true;` so that FAPI is usable without an EK cert. diff --git a/nixos/modules/security/tpm2.nix b/nixos/modules/security/tpm2.nix index f0d357d7b489..9e3572123820 100644 --- a/nixos/modules/security/tpm2.nix +++ b/nixos/modules/security/tpm2.nix @@ -387,5 +387,6 @@ in ] ); + meta.doc = ./tpm2.md; meta.maintainers = with lib.maintainers; [ lschuermann ]; } From 95d8873397ae81e53091fc1ec73a01e64db50c90 Mon Sep 17 00:00:00 2001 From: Scott Stephens Date: Mon, 15 Sep 2025 14:49:31 -0400 Subject: [PATCH 13/14] nixos/tpm2: add comment describing purpose of script --- nixos/modules/security/tpm2.nix | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/nixos/modules/security/tpm2.nix b/nixos/modules/security/tpm2.nix index 9e3572123820..c5660e765f9a 100644 --- a/nixos/modules/security/tpm2.nix +++ b/nixos/modules/security/tpm2.nix @@ -306,6 +306,15 @@ in } { + # This script has the hash of the udev rules in it, + # and also writes that hash to + # /var/lib/tpm2-udev-trigger/hash.txt at the end. + # On each run, it checks to see if the hash embedded in the script + # matches the hash on disk. If they are different, that + # indicates that the udev rules created by this module + # have changed. In that case, a udev change is triggered + # for tpm and tpmrm devices so that the new rules are + # applied at the end of a nixos-rebuild switch or activate systemd.services."tpm2-udev-trigger" = let udevHash = From 0509fb6f9764f44f250496d1636fe8cfcf684028 Mon Sep 17 00:00:00 2001 From: Scott Stephens Date: Mon, 15 Sep 2025 14:50:08 -0400 Subject: [PATCH 14/14] nixos/tpm2: use better method for udev trigger --- nixos/modules/security/tpm2.nix | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/nixos/modules/security/tpm2.nix b/nixos/modules/security/tpm2.nix index c5660e765f9a..364343f05b60 100644 --- a/nixos/modules/security/tpm2.nix +++ b/nixos/modules/security/tpm2.nix @@ -346,12 +346,8 @@ in if [ "$oldHash" != "$newHash" ]; then echo "TPM udev rules changed, triggering udev" - for i in $(seq 0 9); do - if [ -e /dev/tpm$i ]; then - ${config.systemd.package}/bin/udevadm trigger --name-match=tpm$i --action=change - ${config.systemd.package}/bin/udevadm trigger --name-match=tpmrm$i --action=change - fi - done + ${config.systemd.package}/bin/udevadm trigger --subsystem-match=tpm --action=change + ${config.systemd.package}/bin/udevadm trigger --subsystem-match=tpmrm --action=change echo "$newHash" > $hashFile else echo "TPM udev rules unchanged, not triggering udev"