Skip to content

Bundle DCAP collateral together with attestation to avoid fetching on the verifier side - #65

Draft
ameba23 wants to merge 5 commits into
mainfrom
peg/attester-includes-collateral
Draft

Bundle DCAP collateral together with attestation to avoid fetching on the verifier side#65
ameba23 wants to merge 5 commits into
mainfrom
peg/attester-includes-collateral

Conversation

@ameba23

@ameba23 ameba23 commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

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:

  • This is a protocol breaking change. Could be made not protocol breaking by making it optional to include collateral.
  • Increases payload size/complexity
  • Diverges from standards
  • Is quite opinionated / use-case-specific. This specifically caters to a nuance in our attested-tls protocol, making the attestation crate 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 Certificate handshake 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 #75

TODO:

  • This treats collateral as an optional field in the evidence payload, meaning the verifier still has to do a PCCS fetch when handling the case that it is not there. Could make it mandatory.
  • Does not yet include collateral in the Azure payload.

@ameba23
ameba23 marked this pull request as draft June 29, 2026 08:28
@ameba23 ameba23 added the breaks protocol This is a protocol breaking change label Jul 2, 2026
@samlaf

samlaf commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

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 attestation as a relying party for a one-time event rather than a live handshake:
at network founding we verify each node's TDX + vTPM quote and archive the evidence as
permanent provenance. That archive stops being re-verifiable once the collateral's
nextUpdate passes, so we need two things this crate almost has already — verification at
an explicit now (the collection instant, which everything below the public API already
threads), and the verifier reporting which collateral bundle it used. I'll open that as its
own PR since it's orthogonal to both #63 and #65: no wire change, nothing added to the
payload, so it's also neutral wrt the 64 kb cap in #75.

Where this one matters to us: if collateral ends up in the evidence payload, our archive
keeps it verbatim for free and we'd drop a separate archived file. We'd still need the
explicit now, since bundling collateral doesn't stop it expiring.

So — you wrote you're not suggesting this be merged, and on #58 you lean toward option 1
(#63). Is that still your position, or did @Ruteri's interest move it? It decides whether
we plan around payload-bundled collateral or not.

@ameba23

ameba23 commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator Author

So — you wrote you're not suggesting this be merged, and on #58 you lean toward option 1 (#63). Is that still your position, or did @Ruteri's interest move it? It decides whether we plan around payload-bundled collateral or not.

@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

samlaf added a commit to SeismicSystems/attested-tls that referenced this pull request Aug 31, 2026
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>.
samlaf added a commit to SeismicSystems/attested-tls that referenced this pull request Aug 31, 2026
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>.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaks protocol This is a protocol breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants