qemu-vm: implement `virtualization.tpm.provisioning` (#364379)

authored by

Arthur Gautier and committed by
GitHub
28e1cce5 b5e12ffe

+206 -3
+50 -3
nixos/modules/virtualisation/qemu-vm.nix
··· 228 ${lib.getExe cfg.tpm.package} \ 229 socket \ 230 --tpmstate dir="$NIX_SWTPM_DIR" \ 231 - --ctrl type=unixio,path="$NIX_SWTPM_DIR"/socket,terminate \ 232 --pid file="$NIX_SWTPM_DIR"/pid --daemon \ 233 --tpm2 \ 234 --log file="$NIX_SWTPM_DIR"/stdout,level=6 235 236 # Enable `fdflags` builtin in Bash 237 # We will need it to perform surgical modification of the file descriptor 238 # passed in the coprocess to remove `FD_CLOEXEC`, i.e. close the file descriptor ··· 249 # will stop it. 250 coproc waitingswtpm { 251 read || : 252 - echo "" | ${lib.getExe hostPkgs.socat} STDIO UNIX-CONNECT:"$NIX_SWTPM_DIR"/socket 253 } 254 # Clear `FD_CLOEXEC` on the coprocess' file descriptor stdin. 255 fdflags -s-cloexec ''${waitingswtpm[1]} ··· 980 example = "tpm-tis-device"; 981 description = "QEMU device model for the TPM, uses the appropriate default based on th guest platform system and the package passed."; 982 }; 983 }; 984 985 virtualisation.useDefaultFilesystems = mkOption { ··· 1198 "-nographic" 1199 ]) 1200 (mkIf (cfg.tpm.enable) [ 1201 - "-chardev socket,id=chrtpm,path=\"$NIX_SWTPM_DIR\"/socket" 1202 "-tpmdev emulator,id=tpm_dev_0,chardev=chrtpm" 1203 "-device ${cfg.tpm.deviceModel},tpmdev=tpm_dev_0" 1204 ])
··· 228 ${lib.getExe cfg.tpm.package} \ 229 socket \ 230 --tpmstate dir="$NIX_SWTPM_DIR" \ 231 + --server type=unixio,path="$NIX_SWTPM_DIR"/socket \ 232 + --ctrl type=unixio,path="$NIX_SWTPM_DIR"/socket.ctrl \ 233 --pid file="$NIX_SWTPM_DIR"/pid --daemon \ 234 + --flags not-need-init \ 235 --tpm2 \ 236 --log file="$NIX_SWTPM_DIR"/stdout,level=6 237 238 + ( 239 + export TPM2TOOLS_TCTI=swtpm:path="$NIX_SWTPM_DIR"/socket 240 + export PATH=${ 241 + lib.makeBinPath [ 242 + pkgs.tpm2-tools 243 + ] 244 + }:$PATH 245 + 246 + tpm2_startup --clear 247 + tpm2_startup 248 + 249 + ${lib.optionalString (cfg.tpm.provisioning != null) 250 + # Run provisioning in a subshell not to pollute vars 251 + '' 252 + ( 253 + export TCTI=swtpm:path="$NIX_SWTPM_DIR"/socket 254 + ${cfg.tpm.provisioning} 255 + ) 256 + '' 257 + } 258 + 259 + tpm2_shutdown 260 + tpm2_shutdown --clear 261 + ) 262 + 263 # Enable `fdflags` builtin in Bash 264 # We will need it to perform surgical modification of the file descriptor 265 # passed in the coprocess to remove `FD_CLOEXEC`, i.e. close the file descriptor ··· 276 # will stop it. 277 coproc waitingswtpm { 278 read || : 279 + ${cfg.tpm.package}/bin/swtpm_ioctl --unix "$NIX_SWTPM_DIR"/socket.ctrl --stop 2>/dev/null 280 } 281 # Clear `FD_CLOEXEC` on the coprocess' file descriptor stdin. 282 fdflags -s-cloexec ''${waitingswtpm[1]} ··· 1007 example = "tpm-tis-device"; 1008 description = "QEMU device model for the TPM, uses the appropriate default based on th guest platform system and the package passed."; 1009 }; 1010 + 1011 + provisioning = mkOption { 1012 + type = types.nullOr types.str; 1013 + default = null; 1014 + description = '' 1015 + Script to provision the TPM before control is handed off to the VM. 1016 + 1017 + `TPM2TOOLS_TCTI` will be provided to configure tpm2-tools to use the 1018 + swtpm instance transparently. 1019 + `TCTI` is also provided as a generic value, consumer is expected to 1020 + re-export it however it may need (`TPM2OPENSSL_TCTI`, `TPM2_PKCS11_TCTI`, 1021 + ...). 1022 + ''; 1023 + example = literalExpression '' 1024 + tpm2_nvdefine 0xcafecafe \ 1025 + -C o \ 1026 + -a "ownerread|policyread|policywrite|ownerwrite|authread|authwrite" 1027 + echo "foobar" | tpm2_nvwrite 0xcafecafe -C o 1028 + ''; 1029 + }; 1030 }; 1031 1032 virtualisation.useDefaultFilesystems = mkOption { ··· 1245 "-nographic" 1246 ]) 1247 (mkIf (cfg.tpm.enable) [ 1248 + "-chardev socket,id=chrtpm,path=\"$NIX_SWTPM_DIR\"/socket.ctrl" 1249 "-tpmdev emulator,id=tpm_dev_0,chardev=chrtpm" 1250 "-device ${cfg.tpm.deviceModel},tpmdev=tpm_dev_0" 1251 ])
+1
nixos/tests/all-tests.nix
··· 1086 tmate-ssh-server = handleTest ./tmate-ssh-server.nix { }; 1087 tomcat = handleTest ./tomcat.nix {}; 1088 tor = handleTest ./tor.nix {}; 1089 traefik = handleTestOn ["aarch64-linux" "x86_64-linux"] ./traefik.nix {}; 1090 trafficserver = handleTest ./trafficserver.nix {}; 1091 transfer-sh = handleTest ./transfer-sh.nix {};
··· 1086 tmate-ssh-server = handleTest ./tmate-ssh-server.nix { }; 1087 tomcat = handleTest ./tomcat.nix {}; 1088 tor = handleTest ./tor.nix {}; 1089 + tpm-ek = handleTest ./tpm-ek {}; 1090 traefik = handleTestOn ["aarch64-linux" "x86_64-linux"] ./traefik.nix {}; 1091 trafficserver = handleTest ./trafficserver.nix {}; 1092 transfer-sh = handleTest ./transfer-sh.nix {};
+19
nixos/tests/tpm-ek/ca.crt
···
··· 1 + -----BEGIN CERTIFICATE----- 2 + MIIDHTCCAgWgAwIBAgIUbCWkrXLAgC+z2vWzFcVaS/bHpkkwDQYJKoZIhvcNAQEL 3 + BQAwHjEcMBoGA1UECgwTVmVyeSBsZWdpdCBDQSwgSW5jLjAeFw0yNDEyMTIwMzEy 4 + MTNaFw0zNDEyMTAwMzEyMTNaMB4xHDAaBgNVBAoME1ZlcnkgbGVnaXQgQ0EsIElu 5 + Yy4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1tBr/aneQCULixAST 6 + /Gev/ITUYV7QHpgByNcF+yeqMkTigFVcknSwhM6pw++apPARrEjtf3YTzrFAlM7z 7 + mo5M16exbDNKKgTQ90Ms8bvQbeAiHZneWFpT1kuQxcnb0veOsbzM7ksV7qRHCxUN 8 + F4cVzqGu9SU8LyVzvwiw4HQoWBnX8vA19Fqa1U8mAfrDFyuXhDk5g01GKVRkmSmL 9 + wI9gtlHmB8bQxp7nPrKWdQ89rQMsoa4O3rAXZ9zaazu2mygHoTV0J2vJiFUa95u3 10 + ZAfAdfq8mPjFa0cnd2v9IaIgB7cJHlYS1S/LcK9pomw9bQ5AeoRYiEmhX9DxCSH8 11 + r/EnAgMBAAGjUzBRMB0GA1UdDgQWBBQ5PCUBhf4sAWaf4sey5YLj6OWFKTAfBgNV 12 + HSMEGDAWgBQ5PCUBhf4sAWaf4sey5YLj6OWFKTAPBgNVHRMBAf8EBTADAQH/MA0G 13 + CSqGSIb3DQEBCwUAA4IBAQB4x0HI9okWr1/SJkeSQnjVC2QvoxhnoeIkCfXxzr08 14 + ePqAEvMSMocB2OJSZWm+2IXZe3M+ecc3fYPlCRMACghsof9RKwHGt9gyrbL70GBL 15 + 7ikJrJRoZ2JGva3AVvLj+bJts1c5j8jpZWK3dCrmJhevzO7agMweJUrj/7oPqqhH 16 + L+VJmfXYK1S25cTei3BsD72gX/DhB0jwKo0Raaj5gO6wR01eR7JPS/E+3lthT8fC 17 + BktxCN5RlMBiNiNfrmHNgg7FZ3ONsi6CIArNFj/wbTM/ic0MSXmkEyskY2NSzSWv 18 + yJ6Z77Zh7MpVkmGsm4hyFzZ2cnTotGoFCd/AGfmj+GlW 19 + -----END CERTIFICATE-----
+28
nixos/tests/tpm-ek/ca.priv
···
··· 1 + -----BEGIN PRIVATE KEY----- 2 + MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC1tBr/aneQCULi 3 + xAST/Gev/ITUYV7QHpgByNcF+yeqMkTigFVcknSwhM6pw++apPARrEjtf3YTzrFA 4 + lM7zmo5M16exbDNKKgTQ90Ms8bvQbeAiHZneWFpT1kuQxcnb0veOsbzM7ksV7qRH 5 + CxUNF4cVzqGu9SU8LyVzvwiw4HQoWBnX8vA19Fqa1U8mAfrDFyuXhDk5g01GKVRk 6 + mSmLwI9gtlHmB8bQxp7nPrKWdQ89rQMsoa4O3rAXZ9zaazu2mygHoTV0J2vJiFUa 7 + 95u3ZAfAdfq8mPjFa0cnd2v9IaIgB7cJHlYS1S/LcK9pomw9bQ5AeoRYiEmhX9Dx 8 + CSH8r/EnAgMBAAECggEAUKW/1d3Lc4KozT1zSrucyd+qlRkim/z4OtKJnX37/O6S 9 + 5HVRbeUTJcXMdE0i6+CJLU7qj38jSWdUBPYHZNgUkManB3ieyywbNySIDEq+saQS 10 + 9xFsWeOdM9jJcVhYX9kjR5Jb2hlp+jIRd/bTQRxQOL2dxanI/Q1v8g+4K8lzxPOV 11 + YF/QxD2QY9vQp3QJwB3/NGF0QyQtFjAem+SnOZbObQnLrfRcLufi4nu7sCJ8MUZg 12 + t7BK2XMCvajYYt5a49cU/eFDzQqBWSs+GGwMnrM7UVdCXhPUDsPvR95QD7LJ1ZGI 13 + G4E1J3WCTokrFX5ZvIMYnlye/S+6lyqFSBn46+zcAQKBgQDqwOjrj3jtgiIQz2il 14 + d8ooR8Eo3JZdLI9ge4tCTt4IyxgBWFOOO1tTmcShj33vtBocj8JyAdla6JF8wVwM 15 + jCGJI7wKFxtbY/stdHVp1X/X6BylL6+ROHmC4+aTAdfNkppNWVNRlrRDX9V8cUIN 16 + Q2hesreyZbaJCVzhb1mLm/g2kQKBgQDGJg9/978d8kqUNMMk9VYH8ACqF24P+T0d 17 + NDhm1LMkQyt5dlQYux8ZFOG4xezgR15cEZkWOhmkvK0QS0VGzalHZqgKkZXvafJW 18 + FjnFpH9qqkezJRPFT1abUu+LsTHy5Q32GrrRbxRyKHpOBK9f3eIkIo4t0FTIIxc+ 19 + eu7x2uS4NwKBgAllRS1AZcejwLdJhdexjq7ECHAZPA9onChxaWZy/6H8du5+2YFE 20 + 0OfsrJkGxDSW0cC45EBp4Igp7MDAgG2kIid5/amtuROUUdZE5fohaGd8y8C0wuMe 21 + Dob1liHmHfwFVRWpcJNAY+CaclHzuoALZZ78qiuCtKaRcF05dq0Gxg1xAoGAJAAY 22 + QtS9SXCS8jhf2CAm4ExPopedLJPI8bxiHvS4E3eMt4WzI8cjkEgF9q8nKVxuHWYp 23 + HSuzKwYIn3Q9gu6sucdB8qGezx+9orxpBKqtZ7DGVBsBa5DNmGzKDuRDwfCxx6v1 24 + k0WOPmtyRSh+wHkstAn/MP2v2ajeeUCWlySA96MCgYAqnjyTiaWIK0HZqW3YQcfV 25 + WDg/GqliRJg6mnR6uPHSbIIx2beQM3wowSvi7LEDo8/a27UDfCqL7RtsG/UJ6i4l 26 + 9Usuy/odRSzMz9HB2jLYwBkx5LQwT7MaoMHtqWS4T7LRdSdsUXUk9fvCMb4lDUWl 27 + vwXYKH6kQJr7q+hBbBPikA== 28 + -----END PRIVATE KEY-----
+108
nixos/tests/tpm-ek/default.nix
···
··· 1 + import ../make-test-python.nix ( 2 + { lib, pkgs, ... }: 3 + 4 + let 5 + inherit (pkgs) writeText tpm2-tools openssl; 6 + ek_config = writeText "ek-sign.cnf" '' 7 + [ tpm_policy ] 8 + basicConstraints = CA:FALSE 9 + 10 + keyUsage = keyEncipherment 11 + certificatePolicies = 2.23.133.2.1 12 + extendedKeyUsage = 2.23.133.8.1 13 + 14 + subjectAltName = ASN1:SEQUENCE:dirname_tpm 15 + 16 + [ dirname_tpm ] 17 + seq = EXPLICIT:4,SEQUENCE:dirname_tpm_seq 18 + 19 + [ dirname_tpm_seq ] 20 + set = SET:dirname_tpm_set 21 + 22 + [ dirname_tpm_set ] 23 + seq.1 = SEQUENCE:dirname_tpm_seq_manufacturer 24 + seq.2 = SEQUENCE:dirname_tpm_seq_model 25 + seq.3 = SEQUENCE:dirname_tpm_seq_version 26 + 27 + # We're going to mock up an STM TPM here 28 + [dirname_tpm_seq_manufacturer] 29 + oid = OID:2.23.133.2.1 30 + str = UTF8:"id:53544D20" 31 + 32 + [dirname_tpm_seq_model] 33 + oid = OID:2.23.133.2.2 34 + str = UTF8:"ST33HTPHAHD4 35 + 36 + [dirname_tpm_seq_version] 37 + oid = OID:2.23.133.2.3 38 + str = UTF8:"id:00010101" 39 + ''; 40 + in 41 + { 42 + name = "tpm-ek"; 43 + 44 + meta = { 45 + maintainers = with lib.maintainers; [ baloo ]; 46 + }; 47 + 48 + nodes.machine = 49 + { pkgs, ... }: 50 + { 51 + environment.systemPackages = [ 52 + openssl 53 + tpm2-tools 54 + ]; 55 + 56 + security.tpm2 = { 57 + enable = true; 58 + tctiEnvironment.enable = true; 59 + }; 60 + 61 + virtualisation.tpm = { 62 + enable = true; 63 + provisioning = '' 64 + export PATH=${ 65 + lib.makeBinPath [ 66 + openssl 67 + ] 68 + }:$PATH 69 + 70 + tpm2_createek -G rsa -u ek.pub -c ek.ctx -f pem 71 + 72 + # Sign a certificate 73 + # Pretend we're an STM TPM 74 + openssl x509 \ 75 + -extfile ${ek_config} \ 76 + -new -days 365 \ 77 + \ 78 + -subj "/CN=this.is.required.but.it.should.not/" \ 79 + -extensions tpm_policy \ 80 + \ 81 + -CA ${./ca.crt} -CAkey ${./ca.priv} \ 82 + \ 83 + -out device.der -outform der \ 84 + -force_pubkey ek.pub 85 + 86 + # Create a nvram slot for the certificate, and we need the size 87 + # to precisely match the length of the certificate we're going to 88 + # put in. 89 + tpm2_nvdefine 0x01c00002 \ 90 + -C o \ 91 + -a "ownerread|policyread|policywrite|ownerwrite|authread|authwrite" \ 92 + -s "$(wc -c device.der| cut -f 1 -d ' ')" 93 + 94 + tpm2_nvwrite 0x01c00002 -C o -i device.der 95 + ''; 96 + }; 97 + }; 98 + 99 + testScript = '' 100 + start_all() 101 + machine.wait_for_unit("multi-user.target") 102 + 103 + machine.succeed('tpm2_nvread 0x01c00002 | openssl x509 -inform der -out /tmp/ek.pem') 104 + print(machine.succeed('openssl x509 -in /tmp/ek.pem -text')) 105 + machine.succeed('openssl verify -CAfile ${./ca.crt} /tmp/ek.pem') 106 + ''; 107 + } 108 + )