VA-14: TPM 2.0-backed agent identity (Linux) #60

Merged
nrupard merged 2 commits from feat/VA-14-tpm-identity into main 2026-06-13 15:08:32 +02:00
Owner

Summary

Agent identity hardening (VA-14): refactor the identity to a signing-backend abstraction and add a TPM-backed key on Linux so the private key never leaves the chip.

What changed

  • Identity holds a Signer (Software | Tpm) instead of a raw RsaPrivateKey; sign_handshake routes through it and is now fallible (a hardware signer can fail - one caller updated).
  • New identity::tpm (the tpm feature, Linux, tss-esapi/tpm2-tss): a non-exportable RSA-2048 signing key under the Owner hierarchy, evicted to a persistent handle. The TPM signs the handshake digest and the self-signed certificate (via rcgen's RemoteKeyPair); only the persistent handle + cert are stored.
  • Load precedence + migration: a persisted TPM identity wins; otherwise an existing software key is reused (upgrading an installed agent does not re-key it - AC4); a fresh install tries the TPM, falling back to software. Re-pair on reimage (resolved open question) - the key is host-bound, no exported blob.
  • The tpm feature is off by default, so the standard build carries no libtss2 dependency.

Decisions

Foundation refactor + Linux TPM now, with a swtpm CI lane; Windows NCrypt/CNG deferred. tss-esapi (mature, swtpm-tested) rather than the issue's tpm-rs, which is not production-ready - noted as a follow-up. Re-pair on reimage.

Verification

A CI lane runs the TPM backend against swtpm: create a sealed key, sign + verify the handshake against the cert key, reload it (AC1/AC2). oci-build/check.Dockerfile now also installs libtss2/swtpm and runs cargo test --features tpm against it. Full check (fmt, clippy --deny warnings, build, tests, the swtpm TPM test, Windows cross-check) is green. AC3 (software fallback) and AC4 (existing installs unchanged) are covered by the load precedence.

Follow-ups

Windows NCrypt/CNG backend (the other half of the issue); real-TPM hardware validation beyond swtpm.

Refs VA-14.

## Summary Agent identity hardening (VA-14): refactor the identity to a signing-backend abstraction and add a TPM-backed key on Linux so the private key never leaves the chip. ## What changed - `Identity` holds a `Signer` (`Software` | `Tpm`) instead of a raw `RsaPrivateKey`; `sign_handshake` routes through it and is now fallible (a hardware signer can fail - one caller updated). - New `identity::tpm` (the `tpm` feature, Linux, `tss-esapi`/tpm2-tss): a non-exportable RSA-2048 signing key under the Owner hierarchy, evicted to a persistent handle. The TPM signs the handshake digest and the self-signed certificate (via rcgen's `RemoteKeyPair`); only the persistent handle + cert are stored. - Load precedence + migration: a persisted TPM identity wins; otherwise an existing software key is reused (upgrading an installed agent does not re-key it - AC4); a fresh install tries the TPM, falling back to software. Re-pair on reimage (resolved open question) - the key is host-bound, no exported blob. - The `tpm` feature is off by default, so the standard build carries no libtss2 dependency. ## Decisions Foundation refactor + Linux TPM now, with a swtpm CI lane; Windows NCrypt/CNG deferred. `tss-esapi` (mature, swtpm-tested) rather than the issue's `tpm-rs`, which is not production-ready - noted as a follow-up. Re-pair on reimage. ## Verification A CI lane runs the TPM backend against `swtpm`: create a sealed key, sign + verify the handshake against the cert key, reload it (AC1/AC2). `oci-build/check.Dockerfile` now also installs libtss2/swtpm and runs `cargo test --features tpm` against it. Full check (fmt, clippy --deny warnings, build, tests, the swtpm TPM test, Windows cross-check) is green. AC3 (software fallback) and AC4 (existing installs unchanged) are covered by the load precedence. ## Follow-ups Windows NCrypt/CNG backend (the other half of the issue); real-TPM hardware validation beyond swtpm. Refs VA-14.
feat(identity): TPM 2.0-backed agent key on Linux (VA-14)
All checks were successful
Check / clippy + fmt + tests (pull_request) Successful in 4m0s
c054221a53
Refactors the agent identity to a signing-backend abstraction and adds a TPM-backed backend so the private key can stay non-exportable inside a TPM.

- `Identity` now holds a `Signer` (Software | Tpm) instead of a raw `RsaPrivateKey`; `sign_handshake` goes through it and is now fallible (a hardware signer can fail). The software backend is unchanged behaviour.
- New `identity::tpm` (the `tpm` feature, Linux, via `tss-esapi`): creates a non-exportable RSA-2048 signing key under the Owner hierarchy, evicts it to a persistent handle, and signs the handshake digest and the self-signed certificate (via rcgen's remote-key) by asking the TPM - the private bytes never leave the chip. Only the persistent handle + cert are stored.
- Load precedence + migration: a persisted TPM identity wins; otherwise an existing software key is reused (so upgrading an installed software agent does not re-key it - AC4); a fresh install tries the TPM and falls back to a software key. Re-pair on reimage (the resolved open question): the key is host-bound, no sealed blob is exported.
- The `tpm` feature is off by default, so the standard build carries no libtss2 dependency. Windows NCrypt/CNG is a follow-up.

Verification: a CI lane runs the TPM backend against `swtpm` - create a sealed key, sign + verify the handshake against the cert key, and reload it (AC1/AC2). The full `oci-build/check.Dockerfile` (fmt, clippy --deny warnings, build, tests, the swtpm TPM test, and the Windows cross-check) passes.

The Windows NCrypt/CNG backend and the upstream `tpm-rs` (vs the mature `tss-esapi` chosen here) are noted as follow-ups.

#VA-14

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(identity): address VA-14 TPM review findings
All checks were successful
Create release / Create release from merged PR (pull_request) Has been skipped
Check / clippy + fmt + tests (pull_request) Successful in 2m48s
326e675d33
Do not pre-clear an occupied persistent handle in the TPM create path. Blindly evicting whatever sits at 0x81010001 would destroy another application's persistent key if it uses the same handle. Now we just attempt the evict and, if the handle is taken, evict_control fails and load_or_create falls back to a software key (non-destructive) instead of clobbering the incumbent. The same fallback covers a TPM with a non-null owner-hierarchy auth.

Flush the transient primary handle after eviction so we don't leak a TPM object slot for the process lifetime; the persistent copy is what we keep.

Replace the fixed `sleep 1` in the swtpm CI lane with a TCP readiness poll on port 2321 so the test does not race swtpm startup on a loaded runner.

Document that the TPM path intentionally skips the software path's 32-leading-zero-bit node-id regeneration (re-minting a TPM key is costly; collision probability ~1/2^32).

#VA-14

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nrupard deleted branch feat/VA-14-tpm-identity 2026-06-13 15:08:33 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/vervain-agent!60
No description provided.