Bundle DCAP collateral together with attestation to avoid fetching on the verifier side - #65
Bundle DCAP collateral together with attestation to avoid fetching on the verifier side#65ameba23 wants to merge 5 commits into
Conversation
…JSON for serializing payloads to save space
|
Hey @ameba23, working on some other change I need in here and found this. Don't understand this PR very well but it seems to have cross-concerns with my upcoming PR, so leaving a msg from Claude here for you: Quick context and a status question. (Claude authored)We use Where this one matters to us: if collateral ends up in the evidence payload, our archive So — you wrote you're not suggesting this be merged, and on #58 you lean toward option 1 |
@samlaf its complicated, and i think TLDR is - don't expect this to be merged soon. This way of doing things has some notable advantages, especially for your use case. The reason we want it here is to avoid doing collateral fetching during TLS handshake, as rustls trait is not really designed for doing network calls there and its causing issues. But we have another issue which makes this unappealing long term for putting collateral in TLS cert extensions - see: #75 |
AttestationResult sat at the boundary that is meant to hide which platform produced the evidence, and two of its three fields were Intel types: a dcap-qvl Quote and a QuoteCollateralV3. The AWS Nitro work in flashbots#45 has neither, so the struct would break a second time the moment a non-TDX platform lands. flashbots#65 breaks it with no new platform at all - it moves the collateral into the evidence, so the verifier fetches none. The parsed quote leaves the shared type. It had exactly one consumer: AttestationVerifier binds it only to hand to the GCP provenance check, which reads the PPID out of the PCK leaf. gcp/firmware.rs discards it, and so does our own downstream. dcap.rs still hands it back, now as the second element of a tuple, so nothing re-parses the same bytes (34346c6) and the module gains no public type of its own - flashbots#40 may replace that module with attest-verify, which already owns a ValidatedDcapQuote one letter away. CollateralSnapshot becomes EndorsementSnapshot, and the bundle becomes an optional field on it. What a verifier fetches varies; the instant it holds that material to does not. So the instant stays a plain field and the fetched material hangs off it, with #[non_exhaustive] and a ::dcap() constructor keeping the pairing structural and later fields additive. A struct rather than an enum keyed by platform, because a verification consumes a set of fetched material rather than one of several alternatives, and because the axis it varies along is not the platform: whether endorsements ride in the evidence or get fetched is a transport choice of the protocol. flashbots#49 already made that choice one way for the Azure AK chain, and flashbots#65 proposes the opposite for DCAP collateral. "Collateral" is Intel's DCAP word, and would read as a category error the moment the snapshot holds Azure vTPM roots or whatever Nitro needs. "Endorsements" is the loose umbrella rather than RFC 9334's strict term - a DCAP bundle spans Endorsements and Reference Values both - and the doc comment says so, so the imprecision is deliberate rather than sloppy. The type is called VerifiedAttestation again, which reverses the rename in the commit before this one. That rename argued the value is the far end of one appraisal. It is not: the appraisal policy runs after the value is built, and a caller can configure it to check nothing, which is exactly what our enclave does before appraising in its own admission predicate. So no Reference Value comparison is implied, and what comes back is verified evidence rather than an Attestation Result. Leaving the RFC's name unspent keeps it for the type that would earn it if appraisal is ever separated from verification. Result is also taken in Rust, with AttestationError in this same crate. The docs on both types are shorter for it. The cache-refresh caveat is stated once instead of three times, and where the value sits in the RATS pipeline is stated once, linked to RFC 9334. Addresses review feedback on flashbots#85. BREAKING CHANGE: AttestationResult is renamed VerifiedAttestation and loses its quote field. CollateralSnapshot is renamed EndorsementSnapshot, is #[non_exhaustive], and its collateral field becomes dcap: Option<QuoteCollateralV3>, built through EndorsementSnapshot::dcap. The dcap::verify_* functions return (VerifiedAttestation, Quote), the azure::verify_* functions return VerifiedAttestation, and AttestationVerifier::verify_attestation{,_sync} return Option<VerifiedAttestation>.
AttestationResult sat at the boundary that is meant to hide which platform produced the evidence, and two of its three fields were Intel types: a dcap-qvl Quote and a QuoteCollateralV3. The AWS Nitro work in flashbots#45 has neither, so the struct would break a second time the moment a non-TDX platform lands. flashbots#65 breaks it with no new platform at all - it moves the collateral into the evidence, so the verifier fetches none. The parsed quote leaves the shared type. It had exactly one consumer: AttestationVerifier binds it only to hand to the GCP provenance check, which reads the PPID out of the PCK leaf. gcp/firmware.rs discards it, and so does our own downstream. dcap.rs still hands it back, now as the second element of a tuple, so nothing re-parses the same bytes (34346c6) and the module gains no public type of its own - flashbots#40 may replace that module with attest-verify, which already owns a ValidatedDcapQuote one letter away. CollateralSnapshot becomes EndorsementSnapshot, and the bundle becomes an optional field on it. What a verifier fetches varies; the instant it holds that material to does not. So the instant stays a plain field and the fetched material hangs off it, with #[non_exhaustive] and a ::dcap() constructor keeping the pairing structural and later fields additive. A struct rather than an enum keyed by platform, because a verification consumes a set of fetched material rather than one of several alternatives, and because the axis it varies along is not the platform: whether endorsements ride in the evidence or get fetched is a transport choice of the protocol. flashbots#49 already made that choice one way for the Azure AK chain, and flashbots#65 proposes the opposite for DCAP collateral. "Collateral" is Intel's DCAP word, and would read as a category error the moment the snapshot holds Azure vTPM roots or whatever Nitro needs. "Endorsements" is the loose umbrella rather than RFC 9334's strict term - a DCAP bundle spans Endorsements and Reference Values both - and the doc comment says so, so the imprecision is deliberate rather than sloppy. The type is called VerifiedAttestation again, which reverses the rename in the commit before this one. That rename argued the value is the far end of one appraisal. It is not: the appraisal policy runs after the value is built, and a caller can configure it to check nothing, which is exactly what our enclave does before appraising in its own admission predicate. So no Reference Value comparison is implied, and what comes back is verified evidence rather than an Attestation Result. Leaving the RFC's name unspent keeps it for the type that would earn it if appraisal is ever separated from verification. Result is also taken in Rust, with AttestationError in this same crate. The docs on both types are shorter for it. The cache-refresh caveat is stated once instead of three times, and where the value sits in the RATS pipeline is stated once, linked to RFC 9334. Addresses review feedback on flashbots#85. BREAKING CHANGE: AttestationResult is renamed VerifiedAttestation and loses its quote field. CollateralSnapshot is renamed EndorsementSnapshot, is #[non_exhaustive], and its collateral field becomes dcap: Option<QuoteCollateralV3>, built through EndorsementSnapshot::dcap. The dcap::verify_* functions return (VerifiedAttestation, Quote), the azure::verify_* functions return VerifiedAttestation, and AttestationVerifier::verify_attestation{,_sync} return Option<VerifiedAttestation>.
This is a possible solution to #58
In order to avoid needing to do a network fetch to retrieve DCAP collateral during verification, in this PR the attester fetches collateral at the point of DCAP attestation generation, and includes it in the evidence payload.
Since collateral is signed by Intel it does not matter from a trust perspective whether it is provided by the (untrusted) attester or retrieved by the verifier.
The advantage is we do just one PCCS fetch per attestation regardless of how many times it is verified, even if the verifications are by different remote peers.
The disadvantages:
attestationcrate less useful as a general purpose library.For these reasons i am not suggesting we merge this, but i wanted to provide a complete possible solution.
Edit: Looking into this more as @Ruteri thinks this is maybe worthwhile. Since we since merged the PR integrating with
attest, this is going to look quite different as we have both platform metadata and collateral in the attestation evidence payload.An additional concern for including evidence in x509 certificate extension: Rustls caps
Certificatehandshake messages at 64kb. Thats probably ok for the evidence/collateral sizes we see right now, but this could very well be a problem in the future. For this reason, this PR also switches the attestation payload encoding from JSON to scale to significantly decrease payload size. Backwards compatibility for JSON encoded payloads is provided. See #75TODO: