Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

Repository files navigation

pkcs11-tpm

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.

What it does

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

Building

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-provider

Release 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.

Configuration

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.

Key requirements

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 0x81000100

Two TPM properties leak into what the module can offer:

  • Scheme. A key created with a NULL scheme (ecc256:null above) can sign with any hash. A key created with a pinned scheme, say ecc256: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.

Using it

pkcs11-tool

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

OpenSSL with pkcs11-provider

[pkcs11_sect]
module = /usr/lib/x86_64-linux-gnu/ossl-modules/pkcs11.so
pkcs11-module-path = /usr/local/lib/pkcs11-tpm.so
activate = 1
openssl dgst -sha256 -provider pkcs11 -provider default \
    -sign "pkcs11:token=tpm;object=key;type=private" -out sig msg

wpa_supplicant, wired EAP-TLS

pkcs11_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.

Design notes

  • internal/token is the entire Cryptoki state machine in pure Go behind a small Backend interface, so it is tested with software keys and without any TPM. internal/tpmbackend is the go-tpm implementation of that interface. cmd/pkcs11-tpm is the cgo boundary only.
  • cmd/pkcs11-tpm/functions.c is generated from the OASIS pkcs11f.h by hack/gen-functions.py: stubs for the 42 unimplemented functions and the CK_FUNCTION_LIST table 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 is TPM_RC_RETRY from 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 without noda would otherwise fail on first use after boot.
  • Forking a process after C_Initialize is 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.

License

Apache License 2.0. The PKCS #11 headers under cmd/pkcs11-tpm/include are copyright OASIS Open and distributed under the OASIS IPR Policy.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages