Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,11 @@ x509-cert = { version = "0.2", features = ["pem"] }
p256 = { version = "0.13", features = ["ecdsa", "pem", "pkcs8"] }
p384 = { version = "0.13", features = ["ecdsa", "pem", "pkcs8"] }
ecdsa = { version = "0.16", features = ["verifying", "der"] }
rsa = { version = "0.9", features = ["sha2"] }
coset = "0.3"
ciborium = "0.2"

# Certificate chain validation (rustls ecosystem)
rustls-rustcrypto = { version = "=0.0.2-alpha", default-features = false, features = ["std", "zeroize"] }
webpki = { package = "rustls-webpki", version = "=0.102.8", default-features = false, features = ["alloc"] }
# Time types for certificate validation
pki-types = { package = "rustls-pki-types", version = "=1.13.0", default-features = false, features = ["std"] }

[profile.release]
Expand Down
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ build:
release-x86:
cargo build --workspace --release --target x86_64-unknown-linux-musl

release-aarch64:
cargo build --workspace --release --target aarch64-unknown-linux-musl

check:
cargo check --workspace --all-targets

Expand Down
32 changes: 11 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,10 @@
# v[apor]TPM

> What does the "v" in vTPM stand for?

Cloud vTPM attestation library for Rust. Zero C dependencies.

## The Name
**Cloud vTPM attestation library for Rust. Zero C dependencies.**

```
vTPM → v[apor]TPM
lockboot → [g]lockboot
```

Physical TPM trust is vapor. It evaporates under scrutiny - supply chain attacks, firmware vulnerabilities, the whole theater. The only meaningful TPM trust lives in cloud vTPMs, where the hypervisor **is** the root of trust.
> What does the "v" in vTPM stand for?

The "v" always stood for vapor. Everyone just forgot.
Physical TPM trust is vapor. It evaporates under scrutiny - supply chain attacks, firmware vulnerabilities, the whole theater. The only meaningful TPM trust lives in cloud vTPMs, where the hypervisor **is** the root of trust. The "v" always stood for vapor. Everyone just forgot.

## Crates

Expand All @@ -36,10 +27,12 @@ You handle **policy decisions**:

| Platform | Status | Trust Anchor |
|----------|--------|--------------|
| AWS Nitro | ✅ Working | Nitro Root CA |
| GCP Shielded VM | 🔜 Planned | Google AK certificate |
| AWS EC2 with Nitro v4+ | ✅ Working | Nitro Root CA |
| GCP Confidential VM | ✅ Working | Google EK/AK CA Root |
| Azure Trusted Launch | 🔜 Planned | Microsoft AK certificate |

Please note that GCP 'Shielded VM' with vTPM isn't enough, a 'Confidential VM' is necessary as Google doesn't provision AK certificates without that feature enabled (be it Intel TDX or AMD SEV)

## Quick Start

### Generate Attestation (on cloud instance)
Expand All @@ -54,15 +47,12 @@ let json = attest(b"challenge-nonce")?;
### Verify Attestation (anywhere)

```rust
use vaportpm_verify::{verify_attestation_json, VerificationResult};
use vaportpm_verify::verify_attestation_json;

let result = verify_attestation_json(&json)?;

// Check the trust root is acceptable
if result.root_pubkey_hash == KNOWN_AWS_NITRO_ROOT_HASH {
println!("Verified via: {:?}", result.method);
println!("Nonce: {}", result.nonce);
}
// Verification succeeded - attestation is from a supported cloud provider
println!("Provider: {:?}", result.provider);
println!("PCRs: {:?}", result.pcrs);
```

## License
Expand Down
61 changes: 25 additions & 36 deletions crates/vaportpm-attest/AWS-NITRO.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ By combining these, we get a chain of trust from AWS hardware to arbitrary appli
```mermaid
flowchart TD
A["<b>AWS Nitro Root CA</b><br/>Verification returns root pubkey hash"]
B["<b>Nitro Attestation Document</b><br/>COSE Sign1 structure containing:<br/>• nitrotpm_pcrs: SHA-384 PCR values<br/>• public_key: Application key binding<br/>• nonce: Freshness proof<br/>• Certificate chain to AWS root"]
C["<b>TPM Attestation Key (AK)</b><br/>• ECC P-256 signing key<br/>• authPolicy = PolicyPCR(SHA-384 bank)<br/>• Can only sign when PCRs match"]
D["<b>TPM2B_ATTEST</b><br/>• extraData: nonce (matches Nitro)<br/>• certifiedName: includes authPolicy<br/>• Proves: AK bound to PCR values"]
B["<b>Nitro Attestation Document</b><br/>COSE Sign1 structure containing:<br/>• nitrotpm_pcrs: SHA-384 PCR values<br/>• public_key: AK public key binding<br/>• nonce: Freshness proof<br/>• Certificate chain to AWS root"]
C["<b>TPM Attestation Key (AK)</b><br/>• ECC P-256 signing key<br/>• Long-term key (no PCR binding)<br/>• Bound to Nitro document via public_key"]
D["<b>TPM2_Quote (TPMS_ATTEST)</b><br/>• extraData: nonce (matches Nitro)<br/>• pcrDigest: hash of quoted PCRs<br/>• Proves: PCR values at quote time"]

A -->|"Signs (ECDSA P-384)"| B
B -->|"public_key field binds"| C
Expand All @@ -29,9 +29,9 @@ flowchart TD

The Nitro document signs `nitrotpm_pcrs` which contains **SHA-384** PCR values. For a coherent chain of trust:

- The AK's `authPolicy` references the SHA-384 PCR bank
- Verification computes policy from SHA-384 PCRs
- The signed Nitro PCRs match what the AK is bound to
- TPM2_Quote includes SHA-384 PCRs in the signed attestation
- The Nitro document contains signed SHA-384 PCR values
- Verification compares the Quote's PCRs against the signed Nitro values

This ensures a single, verifiable path from AWS hardware to the attested data.

Expand All @@ -40,26 +40,21 @@ This ensures a single, verifiable path from AWS hardware to the attested data.
### Generation (on Nitro instance)

```rust
// 1. Detect Nitro and choose PCR bank
// 1. Detect Nitro and read PCR values (SHA-384)
let is_nitro = tpm.is_nitro_tpm()?;
let pcr_alg = if is_nitro { TpmAlg::Sha384 } else { TpmAlg::Sha256 };
let pcr_values = tpm.read_all_allocated_pcrs()?; // Reads SHA-384 bank

// 2. Read PCR values from chosen bank
let pcr_values = tpm.read_pcrs(pcr_alg)?;
// 2. Create restricted AK in endorsement hierarchy (TCG-compliant AK profile)
let ak = tpm.create_restricted_ak(TPM_RH_ENDORSEMENT)?;

// 3. Compute PCR policy digest
let auth_policy = Tpm::calculate_pcr_policy_digest(&pcr_values, pcr_alg)?;
// 3. Quote PCRs with AK (signs PCR values)
// Note: nonce is caller-provided for freshness/replay protection
let quote_result = tpm.quote(ak.handle, &nonce, &pcr_selection)?;

// 4. Create AK bound to this policy
let ak = tpm.create_primary_ecc_key_with_policy(TPM_RH_OWNER, &auth_policy)?;

// 5. AK self-certifies (proves it exists with this policy)
let certify_result = tpm.certify(ak.handle, ak.handle, &nonce)?;

// 6. Get Nitro attestation binding the AK public key
// 4. Get Nitro attestation binding the AK public key
let nitro_doc = tpm.nsm_attest(
None, // user_data
Some(nonce.to_vec()), // nonce (same as TPM)
Some(nonce.to_vec()), // nonce (same as TPM Quote)
Some(ak_public_key_secg), // public_key (binds AK)
)?;
```
Expand All @@ -76,20 +71,14 @@ let nitro_result = verify_nitro_attestation(&nitro_doc)?;
// 3. Verify AK public key matches Nitro's public_key binding
assert!(ak_pubkey == nitro_result.document.public_key);

// 4. Verify TPM signature over TPM2B_ATTEST
verify_ecdsa_p256(&attest_data, &signature, &ak_pubkey)?;

// 5. Compute expected AK name from PCR policy
let policy = calculate_pcr_policy(&sha384_pcrs, TpmAlg::Sha384)?;
let expected_name = compute_ecc_p256_name(&ak_x, &ak_y, &policy);

// 6. Verify certified name matches (proves PCR binding)
assert!(attest_info.certified_name == expected_name);
// 4. Verify TPM Quote signature
verify_ecdsa_p256(&quote_attest_data, &signature, &ak_pubkey)?;

// 7. Verify nonces match (proves freshness and binding)
assert!(tpm_nonce == nitro_nonce);
// 5. Verify nonces match (proves freshness and binding)
let quote_info = parse_quote_attest(&quote_attest_data)?;
assert!(quote_info.nonce == nitro_nonce);

// 8. Verify PCR values match signed Nitro document
// 6. Verify PCR values match signed Nitro document
assert!(output.pcrs["sha384"] == nitro_result.document.pcrs);
```

Expand All @@ -99,11 +88,11 @@ assert!(output.pcrs["sha384"] == nitro_result.document.pcrs);

1. **Hardware Root of Trust** - The Nitro document is signed by AWS hardware (certificate chain to AWS root CA)

2. **PCR Integrity** - The SHA-384 PCR values in the attestation match what AWS hardware measured
2. **PCR Integrity** - The SHA-384 PCR values in the attestation match what AWS hardware measured (signed in Nitro document)

3. **Key Binding** - The AK is bound to specific PCR values via `authPolicy` (it cannot sign unless PCRs match)
3. **Key Binding** - The AK is bound to the Nitro document via the `public_key` field

4. **Freshness** - The nonce in both TPM and Nitro attestations proves the attestation is fresh
4. **Freshness** - The nonce in both TPM Quote and Nitro document proves the attestation is fresh

5. **AK Authenticity** - The Nitro document's `public_key` field proves the AK belongs to this Nitro instance

Expand Down Expand Up @@ -193,7 +182,7 @@ COSE_Sign1 = [
}
```

Note: Only SHA-384 PCRs are included because that's the bank bound to the AK and signed in the Nitro document.
Note: Only SHA-384 PCRs are included because that's the bank signed in the Nitro document.

## References

Expand Down
30 changes: 0 additions & 30 deletions crates/vaportpm-attest/CLAUDE.md

This file was deleted.

8 changes: 8 additions & 0 deletions crates/vaportpm-attest/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ serde_bytes = { workspace = true }
hex = { workspace = true }
serde_json = { workspace = true }

# X.509 certificate parsing
der = { workspace = true }
x509-cert = { workspace = true }

[lib]
name = "vaportpm_attest"
path = "src/lib.rs"

[[bin]]
name = "vaportpm-attest"
path = "src/bin/attest.rs"
Loading