TPM Device Attestation (Linux/Windows)
- Dominant language
- Rust
- Stars
- 2.8k
- Forks
- 115
- Avg merge
- 1d 4h
- Merged PRs (30d)
- 51
Description
# Device Attestation - Hardware-Anchored Enrollment (TPM 2.0, Windows/Linux)
This is an epic with initial process and architecture - based on this we need to add:
- posture checks (Attested, Attested & Verified)
- add MFA using certificate issued during Attestation
- activity logs
## 1. Goals
- Prove, cryptographically, that a device's identity key resides in a genuine TPM 2.0 from a known manufacturer.
- Anchor each device record to a durable hardware identity (EK) that survives OS reinstall and TPM clear.
- Issue an X.509 device certificate from the built-in Defguard CA **only** after successful attestation; the certificate is later usable as a device factor (MFA composition) and for mTLS.
## 2. Components
| Component | Role |
|---|---|
| **Desktop client & DG cli network device client** | Talks to TPM via a single raw-TPM2 stack behind a TCTI abstraction: `/dev/tpmrm0` (Linux), TBS (Windows). No OS-native crypto providers (no CNG PCP) - one protocol, one verifier. |
| Defguard core | Verifier + built-in CA. Holds the TPM trust store, validates evidence, issues device certificates, stores audit evidence. Proxy only relays; no trust decisions in DMZ. |
| TPM trust store | Versioned, signed bundle of TPM manufacturer root/intermediate CAs, shipped with releases; admin-extensible. |
## 3. TPM manufacturer trust store
### 3.1 Sources (in order of authority)
1. **Vendor PKI (authoritative):**
- Infineon - OPTIGA TPM certificates page; intermediates at `pki.infineon.com`
- STMicroelectronics - TN1330 (EK certificates; roots chain to GlobalSign)
- Nuvoton - TPM EK Certificate Chain publication
- Intel - ODCA chain in NV (11th gen+); EKOP service `ekop.intel.com/ekcertservice` (pre-11th gen)
- AMD, NationZ, Microsoft (Pluton) - via vendor publications / aggregate below
2. **Aggregate (cross-check):** Microsoft `TrustedTpm.cab` (used by Autopilot, HGS, AD CS).
3. **Community (watchdog only):** e.g. `1id-com/tpm-manufacturer-cas` - diff signal, never a source of truth.
### 3.2 Build pipeline (CI)
```mermaid
flowchart LR
V[Vendor PKI pages] --> D{"Diff + review"}
M[TrustedTpm.cab] --> D
C[Community repos] -. alerts .-> D
D --> B["Versioned signed bundle
+ per-cert metadata"]
B --> R[Defguard release]
A[Admin custom CAs] --> S[Server trust store]
R --> S
```
- Pipeline fetches (1) and (2), diffs them; any mismatch ⇒ manual review, never auto-merge.
- Bundle metadata per certificate: vendor, source URL, fetch date, SHA-256 fingerprint.
- Bundle is signed and versioned; verifier records bundle version in every attestation evidence record.
- Admins may add custom CAs (unlisted vendors, air-gapped OEM certs) and disable vendors by policy.
### 3.3 Runtime
- **AIA chasing:** if an EK cert references an intermediate via Authority Information Access, the verifier fetches and caches it (no bundle is ever complete).
- **Intel legacy path:** if EK cert absent in NV, client attempts vendor fetch (EKOP); failures degrade per §7, never hard-fail enrollment.
### 3.4 Unknown CA review flow
When an EK chain does not terminate at a trust-store root, the enrollment is **not rejected** - it enters admin review:
1. Server assembles the full chain (client-provided certs + AIA fetching) and completes the remaining ceremony steps (§5, steps 5–10) so the evidence is internally consistent before a human looks at it.
2. Enrollment is held in `pending`; certificate issuance (step 11) is gated.
3. Admin is shown the fetched chain: subject/issuer per certificate, validity, SHA-256 fingerprints, and the TPM vendor/model/firmware parsed from the EK cert SAN OIDs.
4. Admin decision:
- **Accept once** - approve this attestation only; the CA is not added to the trust store.
- **Trust CA** - approve and add the root/intermediate to the org-local trust store (marked `admin-approved`, distinct from the shipped bundle); future devices with this chain validate automatically.
- **Reject** - enrollment denied; evidence retained for audit.
5. Certificates issued via this path carry `chain-trust=admin-approved` (vs `vendor`) in the policy OID so consumers can distinguish provenance.
## 4. Key hierarchy (per device)
| Key | Template | Purpose |
|---|---|---|
| EK | RSA-2048 (NV cert `0x01C00002`) or ECC P-256 (`0x01C0000A`); decrypt-only | Durable device anchor; never signs |
| AK | ECC P-256; `restricted \| sign \| fixedTPM \| fixedParent \| sensitiveDataOrigin \| userWithAuth`; fixed, versioned template | Signs TPM-internal structures (certify, future quotes) |
| Identity key | ECC P-256; `sign \| fixedTPM \| fixedParent \| sensitiveDataOrigin`; non-restricted | Signs CSR; device authentication (mTLS / device factor) |
Server enforces exact template match (by TPM *name*); non-conforming keys are rejected. Templates are versioned so they can evolve without breaking the verifier.
## 5. Enrollment ceremony
Pattern: TCG credential activation + `TPM2_Certify`, extended with CSR issuance (aligned with draft-ietf-lamps-csr-attestation).
```mermaid
sequenceDiagram
participant A as Admin
participant S as Defguard core (verifier + CA)
participant C as Client
participant T as TPM 2.0
S->>C: 1. nonce + attestation request (bound to enrollment session)
C->>T: 2. read EK cert from NV · create AK · create identity key
C->>S: 3. EK cert (chain), AK pub
S->>S: 4. validate EK chain vs trust store · validate EK Credential Profile fields
S->>C: 5. credential blob = MakeCredential(EK pub, AK name, secret)
C->>T: 6. ActivateCredential(EK, AK)
T-->>C: decrypted secret
C->>S: 7. secret (proves AK + EK co-resident in one TPM)
C->>T: 8. Certify(identity key, AK, qualifying nonce)
C->>S: 9. certify structure + AK signature + CSR (signed by identity key)
S->>S: 10. verify: certify sig by AK · certified pubkey == CSR pubkey · templates · nonce
alt EK root not in trust store (§3.4)
S->>A: display fetched chain, fingerprints, TPM vendor/model
A-->>S: accept once / trust CA / reject
end
S->>C: 11. X.509 device certificate (policy OID: attested, tier, chain-trust)
S->>S: 12. persist device record (anchor = SHA-256(EK pub)) + full evidence
```
### Verification rules (server, step 4 & 10)
- EK chain terminates at a trust-store root; expiry checked; revocation best-effort (vendor CRLs are patchy - record, don't block). Unknown root ⇒ ceremony continues through step 10, issuance gated on admin review (§3.4).
- EK cert fields per TCG EK Credential Profile: EKU `2.23.133.8.1`; SAN OIDs `2.23.133.2.1/.2/.3` (manufacturer, model, firmware) - parsed into the device record.
- Credential activation secret must match (step 7) - **mandatory**; an EK certificate alone is public and replayable.
- `TPM2_Certify` signature verifies under AK; `qualifiedData` == server nonce; certified key name matches the identity-key template.
- CSR public key == certified public key (residency ↔ possession binding).
- All steps bound to one enrollment session (token) with one server nonce; evidence expires with the session.
### 5.1 Post-enrollment verification state
Attestation outcome and organizational verification are **two orthogonal states**:
| State | Decided by | Meaning |
|---|---|---|
| `attested` (tier, chain-trust) | Machine (verifier) | Cryptographic facts: key residency, TPM genuineness |
| `verified` / `unverified` | Human (admin) | Organizational trust: "this is the machine and person we think it is" |
Deployment/group policy `post_enrollment_verification`:
- `auto` - enrollments with a known (trust-store) EK chain become `verified` immediately on issuance.
- `manual` - every enrollment lands as `unverified`, even with a valid vendor chain; an admin must verify it (process in §5.2). Unknown-CA enrollments (§3.4) are always `manual`.
The `verified` flag lives **server-side in the device record, not in the certificate**: the certificate encodes immutable cryptographic provenance; verification is mutable organizational state (can be granted or withdrawn without reissuing). Access policy may require `verified` in addition to a tier.
### 5.2 Device verification
Enrollment is remote; the admin approving an `unverified` device must confirm that the pending record on their screen corresponds to the physical machine in front of the actual user - not to an attacker who used a stolen enrollment token from their own hardware.
#### short authentication string (SAS)
**short authentication string (SAS)**, same pattern as Signal safety numeric comparison or Matrix / Element like: few words / emojics.
1. After the ceremony completes, the client derives and displays a short human-comparable code:
`SAS = wordlist_encode( SHA-256("dg-verify-v1" ‖ SHA-256(EK pub) ‖ identity pub ‖ server nonce)[0..5] )`
40 bits → 4 words from a fixed wordlist (or 8 digits). The admin console shows the same code next to the pending device record - both sides derive it from the attestation evidence; nothing extra is exchanged.
2. Admin contacts the user over any out-of-band channel the org trusts - video call (recommended for remote), phone, in person. The user reads the code from the client UI; the admin compares and clicks verify/reject.
3. Binding argument: an attacker enrolling their own device with a stolen token produces a *different* pending record with a *different* SAS. The real user either has no pending enrollment or reads a code that does not match the record the admin is looking at. The SAS authenticates the **machine↔record** link; the call authenticates the **human** (that part is organizational, not cryptographic - the spec mandates out-of-band, not a specific channel).
4. The code is comparison-only (admin never types it in), so 40 bits suffices; it is session-bound via the nonce and cannot be precomputed.
#### Manual code sending
Admin sends a one-time code to the user (email? Sms?) to type into the client - it authenticates the channel but binds nothing to the attestation evidence.
## 6. Certificate issuance
- Issuer: Defguard built-in CA (dedicated device-identity intermediate recommended).
- Subject/SAN: device ID (Defguard UUID); no PII in subject.
- Policy OIDs: `attested=true`, trust tier, attestation protocol version, `chain-trust=vendor|admin-approved` (§3.4).
- Validity: short (e.g. 90 days). **Renewal = lightweight re-attestation**: re-run steps 1, 8–12 under the already-known EK (no chain re-validation needed unless bundle version changed).
- Revocation: internal DB check (Defguard controls both ends); CRL/OCSP optional later for third-party consumers.
## 7. Failure policy (degrade, don't block)
| Condition | Result |
|---|---|
| No TPM / TPM inaccessible (e.g. missing `tss` group on Linux) | Tier T0 (software), flagged |
| EK present, no EK cert (older AMD fTPM) | Pending admin review → max T1 |
| EK cert chain unknown (root not in trust store) | Unknown CA review flow (§3.4): chain fetched + displayed; admin accepts once / trusts CA / rejects |
| EK cert chain invalid (broken signature, expired) | Pending admin review; alert |
| vTPM (Hyper-V, VMware roots) | Per-policy: allow as distinct class / pending / deny |
| Intel EKOP unreachable | Retry with backoff; enroll as pending until resolved |
Enforcement mode is a deployment setting: `monitor` (log only - default) → `enforce`.
## 8. Device record & evidence (audit)
Anchor: `SHA-256(EK public)` - raw EK is not stored in plaintext (privacy: EK is a permanent hardware identifier).
Stored evidence per attestation: EK cert chain, AK pub + template version, certify structure + signature, CSR, issued certificate serial, server nonce, trust-store bundle version, timestamps. Immutable, exportable for audit.
Lifecycle: same EK hash on re-enrollment ⇒ same device record (identity continuity across OS reinstall). Motherboard/TPM replacement ⇒ new EK ⇒ new record; old record must be explicitly revoked/merged by admin.
## 9. Security considerations
- **Credential activation is non-negotiable** - without it, EK cert validation is theater (certs are public).
- **Relay (cuckoo) attack**: malware on an enrolled host can proxy TPM operations. Accepted residual risk in v1; documented. Mitigations later: locality, key-use auditing.
- The client is untrusted code; all guarantees derive from TPM semantics + server-side verification, never from client claims.
- Trust-store compromise = enrollment compromise: bundle is signed, updates reviewed, mismatches alerted (§3.2).
- The device certificate is a **device factor**, not a user factor. MFA composition = user factor (password/passkey) + device factor (this certificate). Never presented as standalone user MFA.
## 10. Reserved extensions
- `evidence.quote` field reserved for PCR quotes (posture) - not implemented in v1.
- Issuance endpoint may later speak ACME `device-attest-01` (WebAuthn `tpm` format), unifying this flow with Apple Managed Device Attestation on macOS.
## 11. Rust pseudocode
Client side uses `tss-esapi` (`abstraction::{ek, ak}`, `activate_credential`, `certify`). Server side needs no TPM; `make_credential` is computed in software (TPM 2.0 Part 1 KDFs - reference logic: go-attestation).
### Client - enrollment ceremony
```rust
fn enroll(server: &mut EnrollmentSession) -> Result {
// TCTI: /dev/tpmrm0 on Linux, TBS on Windows
let mut ctx = Context::new(TctiNameConf::from_environment_variable()?)?;
let AttestReq { nonce, .. } = server.request_attestation()?;
// Step 2 - EK cert from NV (Intel EKOP fallback), EK + AK + identity key
let ek_cert = ek::retrieve_ek_pubcert(&mut ctx, AsymmetricAlgorithm::Ecc)
.or_else(|_| vendor_fetch_ek_cert())?;
let ek = ek::create_ek_object(&mut ctx, AsymmetricAlgorithm::Ecc, None)?;
let ak = ak::create_ak(&mut ctx, ek, HashingAlgorithm::Sha256,
SignatureSchemeAlgorithm::EcDsa, None, None)?; // AK_TEMPLATE_V1
let ak_h = ak::load_ak(&mut ctx, ek, None, ak.out_private, ak.out_public.clone())?;
let id_key = create_key(&mut ctx, ID_TEMPLATE_V1)?; // P-256, sign, fixedTPM|fixedParent
// Steps 3–7 - credential activation (proves AK and EK share one TPM)
let blob = server.send(ek_cert, &ak.out_public)?; // server: software MakeCredential
let secret = ctx.activate_credential(ak_h, ek, blob.credential, blob.secret)?;
server.prove_activation(secret)?;
// Steps 8–9 - certify identity key under server nonce, CSR signed in-TPM
let (attest, sig) = ctx.certify(id_key.into(), ak_h,
Data::try_from(nonce.clone())?, SignatureScheme::Null)?;
let csr = build_csr(&mut ctx, id_key)?;
let outcome = server.submit(attest, sig, csr)?; // Issued | PendingReview
// §5.2 - display SAS for out-of-band verification
display(sas_code(&ek_cert, &id_key.public, &nonce));
Ok(outcome)
}
fn sas_code(ek_cert: &[u8], id_pub: &[u8], nonce: &[u8]) -> String {
let d = sha256(&[b"dg-verify-v1", &sha256(ek_cert), id_pub, nonce].concat());
wordlist_encode(&d[..5]) // 40 bits -> 4 words
}
```
### Server - verification & issuance
```rust
fn verify_enrollment(ev: Evidence, sess: &Session, st: &TrustStore) -> Outcome {
// §3 - chain to a known root; unknown root -> admin review, not rejection
let chain = assemble_chain(&ev.ek_cert).with_aia_fetch()?;
let chain_trust = match validate_chain(&chain, st) {
Ok(root) => ChainTrust::Vendor(root),
Err(UnknownRoot(c)) => return Outcome::PendingAdminReview(c), // §3.4
Err(e) => return Outcome::Reject(e.into()),
};
ensure_ek_profile(&ev.ek_cert)?; // EKU 2.23.133.8.1, SAN vendor OIDs
ensure_template(&ev.ak_public, AK_TEMPLATE_V1)?;
ensure(sess.activation_secret_matches(&ev.secret))?; // steps 5–7
// Step 10 - bindings: AK signature, nonce freshness, certify <-> CSR key
let attest = parse_attest(&ev.certify_blob)?; // tss-esapi types, no TPM needed
ensure(verify_signature(&ev.ak_public, &ev.certify_sig, &ev.certify_blob))?;
ensure(attest.extra_data() == sess.nonce)?;
ensure(attest.certified_name() == tpm_name(&ev.csr.public_key(), ID_TEMPLATE_V1))?;
ensure(ev.csr.self_signature_valid())?;
// Steps 11–12 - issue + persist; §5.1 verification state by policy
let record = upsert_device(sha256(&ek_pub(&ev.ek_cert)), &ev); // anchor
let cert = ca.issue(&ev.csr, PolicyOids { tier: T2, chain_trust, attested: true })?;
let verified = match sess.policy.post_enrollment_verification {
Auto => Verified::Yes,
Manual => Verified::No, // await §5.2 SAS check by admin
};
Outcome::Issued { cert, record, verified, sas: sas_code_for(&ev, &sess.nonce) }
}
```
Contributor guide
Assessment
This issue has not been assessed yet.