Provides a crypto.Signer
backed by a key in a TPM, so crypto/tls can
authenticate a client or server with a key that never leaves the TPM. It is a
small layer over
go-tpm and
go-tpm-tools.
It does not create keys. It attaches to one that already exists, usually provisioned by an attestation agent that generated it inside the TPM and had it certified.
crypto/tls asks a private key to sign the handshake transcript. The TPM signs
internally and hands back the signature. The key is never read out, so there is
nothing in process memory to leak.
The only input is the certificate you plan to present:
// The certificate picks the key, so there is nothing to configure. key, err := tpmtls.OpenForCertificate(tpmtls.DefaultDevice, cert) if err != nil { return err } defer key.Close() cfg := &tls.Config{ Certificates: []tls.Certificate{key.TLSCertificate(cert.Raw)}, MinVersion: tls.VersionTLS13, }
The same config serves a client or a server: pass it to tls.Dial,
tls.Listen, or an http.Server.
Install
go get github.com/bschaatsbergen/go-tpm-tls
The import path is hyphenated, the package is not:
import "github.com/bschaatsbergen/go-tpm-tls" // package tpmtls
Attaching to a key
Use OpenForCertificate and pass the certificate you plan to present. It opens
the TPM and picks the key whose public half matches, so you do not have to
configure a handle that differs from machine to machine. Use Open when you do
know the handle. Both close the device when you close the key.
New and NewForCertificate are the same two over a TPM connection you already
have, and they leave it open. Use them when the TPM is shared with other code in
the same process, or in tests against a simulator.
The full API — signing, CSRs, finding handles — is documented at pkg.go.dev.
Notes
Use an ECDSA key. RSA keys attach and sign, but not the way crypto/tls asks:
TLS wants RSA-PSS at a salt length the TPM will not use, so the handshake fails
at signing time with an error that does not mention any of this.
A TPM signature costs milliseconds where a software key costs microseconds, and it lands once per full handshake rather than once per request. Session resumption and connection reuse keep it off later connections, which is usually enough to make it irrelevant.
Signing is serialized, since a TPM runs one command at a time, so what a machine is limited to is new handshakes per second, not requests per second. That limit is per machine, since each machine has its own TPM.
Where the key sits matters as much as any of this. A transient object is swapped in and out by the kernel resource manager around each command, so every signature pays to load the key back in, roughly ten times the cost of a persistent one. Measurements are in go-tpm-tls-bench.
Tests
go test ./...
golangci-lint runTests run against the TPM 2.0 reference simulator, so they need no TPM and no root.
License
go-tpm-tls is released under a BSD-style license. See LICENSE.
Links
- TCG TPM 2.0 Library specification
- go-tpm and go-tpm-tools
- RFC 8446, TLS 1.3, for what
CertificateVerifysigns