A small PKCS #11 module that exposes one TPM 2.0 signing key, held at a
persistent handle, plus its X.509 certificate, to any Cryptoki client:
OpenSSL's pkcs11-provider, libp11's engine_pkcs11, pkcs11-tool,
wpa_supplicant, Java, Go's crypto11, and so on.
It is written in Go and talks to the TPM directly through the kernel resource manager with go-tpm. There is no tpm2-tss, no tpm2-openssl, no tpm2-pkcs11 store database and no daemon: the whole module is a single shared object that depends only on libc.
The use case it was built for is wired 802.1X EAP-TLS with a TPM-held identity on minimal systems (Talos Linux system extensions, initramfs images) where every extra C library is a cost. It is deliberately read-only and single-key: nothing in here can generate, import, delete or export key material.
| Key | one persistent handle (default 0x81000100), ECC P-256/384/521 or RSA |
| Certificate | from a TPM NV index (default none) or a DER/PEM file |
| Objects | CKO_PRIVATE_KEY, CKO_PUBLIC_KEY, CKO_CERTIFICATE, sharing one CKA_ID |
| Mechanisms, ECC | CKM_ECDSA, CKM_ECDSA_SHA{1,256,384,512} |
| Mechanisms, RSA | CKM_RSA_PKCS, CKM_SHA{1,256,384,512}_RSA_PKCS, CKM_RSA_PKCS_PSS, CKM_SHA{256,384,512}_RSA_PKCS_PSS, CKM_RSA_X_509 for decrypt-capable keys |
| Login | not required; a PIN passed to C_Login becomes the key's TPM auth value |
| RNG | C_GenerateRandom from the TPM |
| Not implemented | everything else: key generation, decrypt, verify, digest, object management, wrapping |
make # build/pkcs11-tpm.so for the host (needs CGO + gcc)
make test # unit tests, no TPM required
make simtest # backend test against the go-tpm software simulator
scripts/it.sh # full ring with swtpm + pkcs11-tool + pkcs11-providerRelease builds for linux/amd64 and linux/arm64 are attached to each
GitHub release together with SHA256SUMS. They link against glibc; for a
musl system (Alpine, Talos extensions) build from source with the target
toolchain, -buildmode=c-shared works the same way.
A PKCS #11 module has no configuration channel of its own, so everything comes from the environment of the process that loads it.
| Variable | Default | Meaning |
|---|---|---|
PKCS11_TPM_DEVICE |
/dev/tpmrm0 |
TPM device. Also unix:///path or tcp://host:port for an swtpm socket. |
PKCS11_TPM_KEY_HANDLE |
0x81000100 |
Persistent handle of the signing key. |
PKCS11_TPM_CERT_NV |
unset | NV index holding the DER certificate. |
PKCS11_TPM_CERT_FILE |
unset | Certificate file (DER or PEM) if not in NV. |
PKCS11_TPM_KEY_AUTH |
empty | Authorization value of the key, if it has one. |
PKCS11_TPM_TOKEN_LABEL |
tpm |
CKA_LABEL of the token (pkcs11:token=...). |
PKCS11_TPM_KEY_LABEL |
key |
Label of the private and public key objects. |
PKCS11_TPM_CERT_LABEL |
cert |
Label of the certificate object. |
PKCS11_TPM_ID |
derived | Hex CKA_ID for all objects; default is 20 bytes of SHA-256 over the SubjectPublicKeyInfo. |
PKCS11_TPM_DEBUG |
unset | Log to stderr when set. |
Access control is the permission on the TPM device node, exactly as it is
for tpm2-tools.
The key must be a non-restricted signing key at a persistent handle. Restricted keys (the EK, an SRK, attestation keys) are refused. Create one with tpm2-tools, for example:
tpm2_createprimary -C o -G ecc256:null -g sha256 -c key.ctx \
-a 'fixedtpm|fixedparent|sensitivedataorigin|userwithauth|sign|noda'
tpm2_evictcontrol -C o -c key.ctx 0x81000100Two TPM properties leak into what the module can offer:
- Scheme. A key created with a NULL scheme (
ecc256:nullabove) can sign with any hash. A key created with a pinned scheme, sayecc256:ecdsa-sha256, can only sign SHA-256 digests; the module hides the other mechanisms and refuses digests of other lengths. TLS 1.3 uses SHA-256 with P-256, so a pinned SHA-256 key is fine for EAP-TLS. - RSA-PSS salt length. PKCS #11 lets the client choose the salt length; a TPM does not. Most TPMs use the largest salt that fits, FIPS-mode TPMs use the hash length. TLS 1.3 verifiers require salt length = hash length, so RSA-PSS through a non-FIPS TPM may fail verification. Prefer ECC keys, or RSA with TLS 1.2 where PKCS #1 v1.5 is negotiated.
export PKCS11_TPM_CERT_NV=0x01800100
pkcs11-tool --module ./pkcs11-tpm.so --list-objects
pkcs11-tool --module ./pkcs11-tpm.so --sign --mechanism ECDSA \
--input-file msg.sha256 --output-file sig --label key[pkcs11_sect]
module = /usr/lib/x86_64-linux-gnu/ossl-modules/pkcs11.so
pkcs11-module-path = /usr/local/lib/pkcs11-tpm.so
activate = 1openssl dgst -sha256 -provider pkcs11 -provider default \
-sign "pkcs11:token=tpm;object=key;type=private" -out sig msgpkcs11_module_path=/usr/local/lib/pkcs11-tpm.so
network={
key_mgmt=IEEE8021X
eap=TLS
identity="host"
ca_cert="/etc/wpa_supplicant/ca.pem"
client_cert="pkcs11:token=tpm;object=cert;type=cert"
private_key="pkcs11:token=tpm;object=key;type=private"
}
wpa_supplicant enables its PKCS #11 engine, or the pkcs11 provider when
built without ENGINE support, automatically for pkcs11: URIs. Set the
PKCS11_TPM_* variables in the supplicant's environment.
internal/tokenis the entire Cryptoki state machine in pure Go behind a smallBackendinterface, so it is tested with software keys and without any TPM.internal/tpmbackendis the go-tpm implementation of that interface.cmd/pkcs11-tpmis the cgo boundary only.cmd/pkcs11-tpm/functions.cis generated from the OASISpkcs11f.hbyhack/gen-functions.py: stubs for the 42 unimplemented functions and theCK_FUNCTION_LISTtable built by re-including the header, so the entry order cannot drift from the struct.- The module never writes to the TPM.
- Retry-class warnings (
TPM_RC_RETRY,TPM_RC_YIELDED,TPM_RC_TESTING,TPM_RC_NV_RATE) are retried a bounded number of times. The common one isTPM_RC_RETRYfrom the spec's daUsed rule: the first DA-protected authorization after a TPM start is always answered with it, so a key or NV index created withoutnodawould otherwise fail on first use after boot. - Forking a process after
C_Initializeis not supported, as with any Go shared library; clients that fork (pkcs11-provider, p11-kit) already re-initialise in the child. - CK_ULONG is encoded at the platform width in host byte order, as the spec requires; the module is built per target architecture.
Apache License 2.0. The PKCS #11 headers under cmd/pkcs11-tpm/include are
copyright OASIS Open and distributed under the OASIS IPR Policy.