From b82554d2a42a1c9af8c8fbaaaf0d816354f1cc58 Mon Sep 17 00:00:00 2001 From: Arthur Gautier Date: Mon, 9 Dec 2024 10:21:52 -0800 Subject: [PATCH 1/2] qemu-vm: implement `virtualization.tpm.provisioning` This option allows for TPM to provisionned before the control is handed over to the qemu VM. This is useful to add EK certificates. The socket has been split in two, a server socket as well as the control socket for compatibility with the tpm2-tss swtpm TCTI. Because the control socket may now be used for provisioning, the swtpm does not terminate when a client disconnects, and the stop of the swtpm daemon is now controlled by a call to `swtpm_ioctl`. --- nixos/modules/virtualisation/qemu-vm.nix | 53 ++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix index be0f9afb0e5d..5a020f5520c5 100644 --- a/nixos/modules/virtualisation/qemu-vm.nix +++ b/nixos/modules/virtualisation/qemu-vm.nix @@ -228,11 +228,38 @@ let ${lib.getExe cfg.tpm.package} \ socket \ --tpmstate dir="$NIX_SWTPM_DIR" \ - --ctrl type=unixio,path="$NIX_SWTPM_DIR"/socket,terminate \ + --server type=unixio,path="$NIX_SWTPM_DIR"/socket \ + --ctrl type=unixio,path="$NIX_SWTPM_DIR"/socket.ctrl \ --pid file="$NIX_SWTPM_DIR"/pid --daemon \ + --flags not-need-init \ --tpm2 \ --log file="$NIX_SWTPM_DIR"/stdout,level=6 + ( + export TPM2TOOLS_TCTI=swtpm:path="$NIX_SWTPM_DIR"/socket + export PATH=${ + lib.makeBinPath [ + pkgs.tpm2-tools + ] + }:$PATH + + tpm2_startup --clear + tpm2_startup + + ${lib.optionalString (cfg.tpm.provisioning != null) + # Run provisioning in a subshell not to pollute vars + '' + ( + export TCTI=swtpm:path="$NIX_SWTPM_DIR"/socket + ${cfg.tpm.provisioning} + ) + '' + } + + tpm2_shutdown + tpm2_shutdown --clear + ) + # Enable `fdflags` builtin in Bash # We will need it to perform surgical modification of the file descriptor # passed in the coprocess to remove `FD_CLOEXEC`, i.e. close the file descriptor @@ -249,7 +276,7 @@ let # will stop it. coproc waitingswtpm { read || : - echo "" | ${lib.getExe hostPkgs.socat} STDIO UNIX-CONNECT:"$NIX_SWTPM_DIR"/socket + ${cfg.tpm.package}/bin/swtpm_ioctl --unix "$NIX_SWTPM_DIR"/socket.ctrl --stop 2>/dev/null } # Clear `FD_CLOEXEC` on the coprocess' file descriptor stdin. fdflags -s-cloexec ''${waitingswtpm[1]} @@ -980,6 +1007,26 @@ in example = "tpm-tis-device"; description = "QEMU device model for the TPM, uses the appropriate default based on th guest platform system and the package passed."; }; + + provisioning = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Script to provision the TPM before control is handed off to the VM. + + `TPM2TOOLS_TCTI` will be provided to configure tpm2-tools to use the + swtpm instance transparently. + `TCTI` is also provided as a generic value, consumer is expected to + re-export it however it may need (`TPM2OPENSSL_TCTI`, `TPM2_PKCS11_TCTI`, + ...). + ''; + example = literalExpression '' + tpm2_nvdefine 0xcafecafe \ + -C o \ + -a "ownerread|policyread|policywrite|ownerwrite|authread|authwrite" + echo "foobar" | tpm2_nvwrite 0xcafecafe -C o + ''; + }; }; virtualisation.useDefaultFilesystems = mkOption { @@ -1198,7 +1245,7 @@ in "-nographic" ]) (mkIf (cfg.tpm.enable) [ - "-chardev socket,id=chrtpm,path=\"$NIX_SWTPM_DIR\"/socket" + "-chardev socket,id=chrtpm,path=\"$NIX_SWTPM_DIR\"/socket.ctrl" "-tpmdev emulator,id=tpm_dev_0,chardev=chrtpm" "-device ${cfg.tpm.deviceModel},tpmdev=tpm_dev_0" ]) From 059a86b9f354889b67c5772578446d0ebe521b18 Mon Sep 17 00:00:00 2001 From: Arthur Gautier Date: Wed, 11 Dec 2024 19:09:20 -0800 Subject: [PATCH 2/2] nixosTests.tpm-ek: provide EK certificates to tests --- nixos/tests/all-tests.nix | 1 + nixos/tests/tpm-ek/ca.crt | 19 ++++++ nixos/tests/tpm-ek/ca.priv | 28 +++++++++ nixos/tests/tpm-ek/default.nix | 108 +++++++++++++++++++++++++++++++++ 4 files changed, 156 insertions(+) create mode 100644 nixos/tests/tpm-ek/ca.crt create mode 100644 nixos/tests/tpm-ek/ca.priv create mode 100644 nixos/tests/tpm-ek/default.nix diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 6ce214cc4ed6..c684cee1d544 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1065,6 +1065,7 @@ in { tmate-ssh-server = handleTest ./tmate-ssh-server.nix { }; tomcat = handleTest ./tomcat.nix {}; tor = handleTest ./tor.nix {}; + tpm-ek = handleTest ./tpm-ek {}; traefik = handleTestOn ["aarch64-linux" "x86_64-linux"] ./traefik.nix {}; trafficserver = handleTest ./trafficserver.nix {}; transfer-sh = handleTest ./transfer-sh.nix {}; diff --git a/nixos/tests/tpm-ek/ca.crt b/nixos/tests/tpm-ek/ca.crt new file mode 100644 index 000000000000..84a7fe58a114 --- /dev/null +++ b/nixos/tests/tpm-ek/ca.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDHTCCAgWgAwIBAgIUbCWkrXLAgC+z2vWzFcVaS/bHpkkwDQYJKoZIhvcNAQEL +BQAwHjEcMBoGA1UECgwTVmVyeSBsZWdpdCBDQSwgSW5jLjAeFw0yNDEyMTIwMzEy +MTNaFw0zNDEyMTAwMzEyMTNaMB4xHDAaBgNVBAoME1ZlcnkgbGVnaXQgQ0EsIElu +Yy4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1tBr/aneQCULixAST +/Gev/ITUYV7QHpgByNcF+yeqMkTigFVcknSwhM6pw++apPARrEjtf3YTzrFAlM7z +mo5M16exbDNKKgTQ90Ms8bvQbeAiHZneWFpT1kuQxcnb0veOsbzM7ksV7qRHCxUN +F4cVzqGu9SU8LyVzvwiw4HQoWBnX8vA19Fqa1U8mAfrDFyuXhDk5g01GKVRkmSmL +wI9gtlHmB8bQxp7nPrKWdQ89rQMsoa4O3rAXZ9zaazu2mygHoTV0J2vJiFUa95u3 +ZAfAdfq8mPjFa0cnd2v9IaIgB7cJHlYS1S/LcK9pomw9bQ5AeoRYiEmhX9DxCSH8 +r/EnAgMBAAGjUzBRMB0GA1UdDgQWBBQ5PCUBhf4sAWaf4sey5YLj6OWFKTAfBgNV +HSMEGDAWgBQ5PCUBhf4sAWaf4sey5YLj6OWFKTAPBgNVHRMBAf8EBTADAQH/MA0G +CSqGSIb3DQEBCwUAA4IBAQB4x0HI9okWr1/SJkeSQnjVC2QvoxhnoeIkCfXxzr08 +ePqAEvMSMocB2OJSZWm+2IXZe3M+ecc3fYPlCRMACghsof9RKwHGt9gyrbL70GBL +7ikJrJRoZ2JGva3AVvLj+bJts1c5j8jpZWK3dCrmJhevzO7agMweJUrj/7oPqqhH +L+VJmfXYK1S25cTei3BsD72gX/DhB0jwKo0Raaj5gO6wR01eR7JPS/E+3lthT8fC +BktxCN5RlMBiNiNfrmHNgg7FZ3ONsi6CIArNFj/wbTM/ic0MSXmkEyskY2NSzSWv +yJ6Z77Zh7MpVkmGsm4hyFzZ2cnTotGoFCd/AGfmj+GlW +-----END CERTIFICATE----- diff --git a/nixos/tests/tpm-ek/ca.priv b/nixos/tests/tpm-ek/ca.priv new file mode 100644 index 000000000000..918fa0488a23 --- /dev/null +++ b/nixos/tests/tpm-ek/ca.priv @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC1tBr/aneQCULi +xAST/Gev/ITUYV7QHpgByNcF+yeqMkTigFVcknSwhM6pw++apPARrEjtf3YTzrFA +lM7zmo5M16exbDNKKgTQ90Ms8bvQbeAiHZneWFpT1kuQxcnb0veOsbzM7ksV7qRH +CxUNF4cVzqGu9SU8LyVzvwiw4HQoWBnX8vA19Fqa1U8mAfrDFyuXhDk5g01GKVRk +mSmLwI9gtlHmB8bQxp7nPrKWdQ89rQMsoa4O3rAXZ9zaazu2mygHoTV0J2vJiFUa +95u3ZAfAdfq8mPjFa0cnd2v9IaIgB7cJHlYS1S/LcK9pomw9bQ5AeoRYiEmhX9Dx +CSH8r/EnAgMBAAECggEAUKW/1d3Lc4KozT1zSrucyd+qlRkim/z4OtKJnX37/O6S +5HVRbeUTJcXMdE0i6+CJLU7qj38jSWdUBPYHZNgUkManB3ieyywbNySIDEq+saQS +9xFsWeOdM9jJcVhYX9kjR5Jb2hlp+jIRd/bTQRxQOL2dxanI/Q1v8g+4K8lzxPOV +YF/QxD2QY9vQp3QJwB3/NGF0QyQtFjAem+SnOZbObQnLrfRcLufi4nu7sCJ8MUZg +t7BK2XMCvajYYt5a49cU/eFDzQqBWSs+GGwMnrM7UVdCXhPUDsPvR95QD7LJ1ZGI +G4E1J3WCTokrFX5ZvIMYnlye/S+6lyqFSBn46+zcAQKBgQDqwOjrj3jtgiIQz2il +d8ooR8Eo3JZdLI9ge4tCTt4IyxgBWFOOO1tTmcShj33vtBocj8JyAdla6JF8wVwM +jCGJI7wKFxtbY/stdHVp1X/X6BylL6+ROHmC4+aTAdfNkppNWVNRlrRDX9V8cUIN +Q2hesreyZbaJCVzhb1mLm/g2kQKBgQDGJg9/978d8kqUNMMk9VYH8ACqF24P+T0d +NDhm1LMkQyt5dlQYux8ZFOG4xezgR15cEZkWOhmkvK0QS0VGzalHZqgKkZXvafJW +FjnFpH9qqkezJRPFT1abUu+LsTHy5Q32GrrRbxRyKHpOBK9f3eIkIo4t0FTIIxc+ +eu7x2uS4NwKBgAllRS1AZcejwLdJhdexjq7ECHAZPA9onChxaWZy/6H8du5+2YFE +0OfsrJkGxDSW0cC45EBp4Igp7MDAgG2kIid5/amtuROUUdZE5fohaGd8y8C0wuMe +Dob1liHmHfwFVRWpcJNAY+CaclHzuoALZZ78qiuCtKaRcF05dq0Gxg1xAoGAJAAY +QtS9SXCS8jhf2CAm4ExPopedLJPI8bxiHvS4E3eMt4WzI8cjkEgF9q8nKVxuHWYp +HSuzKwYIn3Q9gu6sucdB8qGezx+9orxpBKqtZ7DGVBsBa5DNmGzKDuRDwfCxx6v1 +k0WOPmtyRSh+wHkstAn/MP2v2ajeeUCWlySA96MCgYAqnjyTiaWIK0HZqW3YQcfV +WDg/GqliRJg6mnR6uPHSbIIx2beQM3wowSvi7LEDo8/a27UDfCqL7RtsG/UJ6i4l +9Usuy/odRSzMz9HB2jLYwBkx5LQwT7MaoMHtqWS4T7LRdSdsUXUk9fvCMb4lDUWl +vwXYKH6kQJr7q+hBbBPikA== +-----END PRIVATE KEY----- diff --git a/nixos/tests/tpm-ek/default.nix b/nixos/tests/tpm-ek/default.nix new file mode 100644 index 000000000000..2af2bfbd40f9 --- /dev/null +++ b/nixos/tests/tpm-ek/default.nix @@ -0,0 +1,108 @@ +import ../make-test-python.nix ( + { lib, pkgs, ... }: + + let + inherit (pkgs) writeText tpm2-tools openssl; + ek_config = writeText "ek-sign.cnf" '' + [ tpm_policy ] + basicConstraints = CA:FALSE + + keyUsage = keyEncipherment + certificatePolicies = 2.23.133.2.1 + extendedKeyUsage = 2.23.133.8.1 + + subjectAltName = ASN1:SEQUENCE:dirname_tpm + + [ dirname_tpm ] + seq = EXPLICIT:4,SEQUENCE:dirname_tpm_seq + + [ dirname_tpm_seq ] + set = SET:dirname_tpm_set + + [ dirname_tpm_set ] + seq.1 = SEQUENCE:dirname_tpm_seq_manufacturer + seq.2 = SEQUENCE:dirname_tpm_seq_model + seq.3 = SEQUENCE:dirname_tpm_seq_version + + # We're going to mock up an STM TPM here + [dirname_tpm_seq_manufacturer] + oid = OID:2.23.133.2.1 + str = UTF8:"id:53544D20" + + [dirname_tpm_seq_model] + oid = OID:2.23.133.2.2 + str = UTF8:"ST33HTPHAHD4 + + [dirname_tpm_seq_version] + oid = OID:2.23.133.2.3 + str = UTF8:"id:00010101" + ''; + in + { + name = "tpm-ek"; + + meta = { + maintainers = with lib.maintainers; [ baloo ]; + }; + + nodes.machine = + { pkgs, ... }: + { + environment.systemPackages = [ + openssl + tpm2-tools + ]; + + security.tpm2 = { + enable = true; + tctiEnvironment.enable = true; + }; + + virtualisation.tpm = { + enable = true; + provisioning = '' + export PATH=${ + lib.makeBinPath [ + openssl + ] + }:$PATH + + tpm2_createek -G rsa -u ek.pub -c ek.ctx -f pem + + # Sign a certificate + # Pretend we're an STM TPM + openssl x509 \ + -extfile ${ek_config} \ + -new -days 365 \ + \ + -subj "/CN=this.is.required.but.it.should.not/" \ + -extensions tpm_policy \ + \ + -CA ${./ca.crt} -CAkey ${./ca.priv} \ + \ + -out device.der -outform der \ + -force_pubkey ek.pub + + # Create a nvram slot for the certificate, and we need the size + # to precisely match the length of the certificate we're going to + # put in. + tpm2_nvdefine 0x01c00002 \ + -C o \ + -a "ownerread|policyread|policywrite|ownerwrite|authread|authwrite" \ + -s "$(wc -c device.der| cut -f 1 -d ' ')" + + tpm2_nvwrite 0x01c00002 -C o -i device.der + ''; + }; + }; + + testScript = '' + start_all() + machine.wait_for_unit("multi-user.target") + + machine.succeed('tpm2_nvread 0x01c00002 | openssl x509 -inform der -out /tmp/ek.pem') + print(machine.succeed('openssl x509 -in /tmp/ek.pem -text')) + machine.succeed('openssl verify -CAfile ${./ca.crt} /tmp/ek.pem') + ''; + } +)