Smart Cards (PIV / CAC)

Tetrapus accepts US Government PIV and DoD CAC smart cards as a primary authenticator. Hardware-backed mTLS at the perimeter authenticates the card holder; the certificate's subject DN binds to a Tetrapus user record via the smart_card_links table. Designed for FedRAMP / FISMA-aligned tenants and forward-deployed environments.

PKCS#11 driver matrix

Tested combinations. Any token that exposes a standards-compliant PKCS#11 module will work — the list below is what we run integration tests against.

HardwareDriverUse
YubiKey 5 Series (PIV applet)ykcs11Operator workstations
DoD CACCoolKey / OpenSCDoD workforce
PIV-II cardsOpenSC pkcs11-toolFederal civilian agencies
AWS CloudHSMcloudhsm-pkcs11Server-side signing keys
Thales Luna Network HSMlibCryptoki2_64Hardware root-of-trust deployments
SoftHSM2libsofthsm2CI / dev / testing only

Architecture: gateway terminates mTLS, server binds DN

Tetrapus's data plane never touches the smart card directly. mTLS is terminated at the Tetrapus Gateway (a separate process), which extracts subject DN, issuer DN, serial, and SHA-256 fingerprint from the validated client certificate. The gateway then forwards the request to the data plane with these values in trusted headers.

graph LR USER["User w/ PIV card"] --> PCSC["pcscd / OpenSC"] PCSC --> BROWSER["Browser TLS stack\nclient_cert via PKCS#11"] BROWSER -->|mTLS handshake| GW["tetrapus-gateway\n(rustls + verify chain)"] GW --> EXTRACT["Extract subject DN, issuer DN,\nserial, fingerprint"] EXTRACT -->|forward + X-DM-SmartCard-* headers| API["tetrapus-server"] API --> LOOKUP["SELECT user_id FROM smart_card_links WHERE fingerprint = ?"] LOOKUP --> SESSION["Mint session"] LOOKUP -.no row.-> ENROLL["First-time bind:\nPOST /api/v1/admin/smartcards"]

smart_card_links table

The certificate fingerprint is the primary key — exact, hardware-bound, immutable until the card is re-issued. The subject DN and issuer DN are stored for auditing and human-readable display.

SQL
CREATE TABLE smart_card_links (
    fingerprint   TEXT PRIMARY KEY,            -- SHA-256 hex of the cert
    user_id       TEXT NOT NULL REFERENCES users(id),
    org_id        TEXT NOT NULL REFERENCES orgs(id),
    subject_dn    TEXT NOT NULL,               -- "CN=DOE.JOHN.A.1234567890,..."
    issuer_dn     TEXT NOT NULL,               -- "CN=DOD CA-59,..."
    serial        TEXT NOT NULL,               -- cert serial in hex
    not_before    TEXT NOT NULL,
    not_after     TEXT NOT NULL,
    label         TEXT,                        -- user-friendly: "Primary CAC"
    enabled       INTEGER NOT NULL DEFAULT 1,
    created_at    TEXT NOT NULL,
    last_used_at  TEXT
);

REST routes

MethodPathPurpose
POST/api/v1/admin/smartcardsBind a cert to a user
GET/api/v1/admin/smartcardsList bindings (Org-scoped)
DELETE/api/v1/admin/smartcards/{fingerprint}Revoke a binding

Bind a smart card

The gateway extracts the cert fields and POSTs them to the admin endpoint on behalf of the operator completing first-time enrolment. The user must already exist (provisioned via SCIM, SAML, or manually).

Bash
curl -X POST https://tetrapus.example.com/api/v1/admin/smartcards \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id":     "01HV4ABC...",
    "fingerprint": "5f:9c:e3:...:7a:2b",
    "subject_dn":  "CN=DOE.JOHN.A.1234567890,OU=USA,OU=PKI,OU=DoD,O=U.S. Government,C=US",
    "issuer_dn":   "CN=DOD ID CA-59,OU=PKI,OU=DoD,O=U.S. Government,C=US",
    "serial":      "0x12abcdef...",
    "not_before":  "2025-01-15T00:00:00Z",
    "not_after":   "2028-01-15T00:00:00Z",
    "label":       "Primary CAC"
  }'

List active bindings

Bash
curl https://tetrapus.example.com/api/v1/admin/smartcards \
  -H "Authorization: Bearer $ADMIN_TOKEN"

Revoke (lost / stolen card)

Bash
curl -X DELETE https://tetrapus.example.com/api/v1/admin/smartcards/5f:9c:e3:...:7a:2b \
  -H "Authorization: Bearer $ADMIN_TOKEN"
# All sessions issued via this fingerprint are revoked immediately.

Status — separation of concerns

Critical to understand: the actual mTLS handshake, certificate chain validation, CRL/OCSP checks, and PKCS#11 plumbing all live in the Tetrapus Gateway — not in the data-plane server. The data plane only sees the gateway-extracted DN/fingerprint and trusts them because the gateway is in the same trust boundary. If you front Tetrapus with a different reverse proxy (nginx, Envoy, F5), you are responsible for replicating the gateway's X-DM-SmartCard-Fingerprint / -Subject / -Issuer / -Serial header convention. CRL pre-fetch and OCSP stapling are gateway responsibilities; the data plane will not re-validate.

Related

Questions?

Reach out for help with integration, deployment, or custom domain codecs.