Unverified Commit 28e1cce5 authored by Arthur Gautier's avatar Arthur Gautier Committed by GitHub
Browse files

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

parents b5e12ffe 059a86b9
Loading
Loading
Loading
Loading
+50 −3
Original line number Diff line number Diff line
@@ -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"
      ])
+1 −0
Original line number Diff line number Diff line
@@ -1086,6 +1086,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 {};
+19 −0
Original line number Diff line number Diff line
-----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-----
+28 −0
Original line number Diff line number Diff line
-----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-----
+108 −0
Original line number Diff line number Diff line
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')
    '';
  }
)