Skip to content

feat!(attestation): report the DCAP collateral a verification consumed - #85

Open
samlaf wants to merge 2 commits into
flashbots:mainfrom
SeismicSystems:sei-208/pr1-report-verified-collateral
Open

feat!(attestation): report the DCAP collateral a verification consumed#85
samlaf wants to merge 2 commits into
flashbots:mainfrom
SeismicSystems:sei-208/pr1-report-verified-collateral

Conversation

@samlaf

@samlaf samlaf commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Fixes half of #84. Second half is SeismicSystems#3 (github doesn't allow stacked PRs on upstream repos so will make an upstream PR once this one lands).

TLDR is that we need to archive the collateral needed to reverify a quote at a later time (so we can reverify the quotes from the genesis machines on our chain). This PR is only the part that returns the archive. A separate PR needs to follow that allows passing the archived collateral as input. Kept separate because I found it hard to review both changes in one, and both introduce separate API breaking changes, so easier to keep them separate imo.

LLM Summary

The crate is usable as a relying party for a one-time event rather than a live handshake: evidence is verified once against a measurement policy and archived as permanent provenance. Nothing reported which collateral bundle a verification used, so there was nothing to archive alongside the evidence it verified.

Fetching a second copy next to the verification is the obvious workaround, and it is subtly wrong. A PCCS cache refresh between the two fetches makes the archived bundle a bundle rather than the bundle the verification consumed, and for provenance that distinction is the whole point.

Verification now returns VerifiedAttestation: the measurements, the collateral it consumed, and the instant every freshness check was evaluated at. The public entry points return Option

  • None when the evidence carried no attestation and none was expected, the one case with no bundle and no instant to report. Nesting the absence in one Option keeps the three fields from ever disagreeing.

One type serves every platform. A GCP TDX quote is a DCAP quote, and Azure wraps one in an HCL report and a vTPM attestation, so a verification always consumes exactly one collateral bundle, whichever platform produced the evidence. Azure holds its vTPM leg to the instant its DCAP leg reported, so verified_at is the single instant behind every freshness check rather than one per leg.

That instant is the other half of what archiving buys: with the bundle, the same evidence and the same instant give the same answer forever. QuoteCollateralV3 is re-exported so callers can keep the bundle without taking a direct dependency on dcap-qvl.

The return type of verify_attestation and verify_attestation_sync changes from Option to Option. Both in-tree callers discard the value, so neither needed a change.

@samlaf samlaf changed the title attestation!: report the DCAP collateral a verification consumed feat!(attestation): report the DCAP collateral a verification consumed Aug 25, 2026
The crate is usable as a relying party for a one-time event rather than
a live handshake: evidence is verified once against a measurement
policy and archived as permanent provenance. Nothing reported which
collateral bundle a verification used, so there was nothing to archive
alongside the evidence it verified.

Fetching a second copy next to the verification is the obvious
workaround, and it is subtly wrong. A PCCS cache refresh between the
two fetches makes the archived bundle *a* bundle rather than *the*
bundle the verification consumed, and for provenance that distinction
is the whole point.

Verification now returns VerifiedAttestation: the measurements, the
collateral it consumed, and the instant every freshness check was
evaluated at. The public entry points return Option<VerifiedAttestation>
- None when the evidence carried no attestation and none was expected,
the one case with no bundle and no instant to report. Nesting the
absence in one Option keeps the three fields from ever disagreeing.

One type serves every platform. A GCP TDX quote is a DCAP quote, and
Azure wraps one in an HCL report and a vTPM attestation, so a
verification always consumes exactly one collateral bundle, whichever
platform produced the evidence. Azure holds its vTPM leg to the instant
its DCAP leg reported, so verified_at is the single instant behind every
freshness check rather than one per leg.

That instant is the other half of what archiving buys: with the bundle,
the same evidence and the same instant give the same answer forever.
QuoteCollateralV3 is re-exported so callers can keep the bundle without
taking a direct dependency on dcap-qvl.

The return type of verify_attestation and verify_attestation_sync
changes from Option<MultiMeasurements> to Option<VerifiedAttestation>.
Both in-tree callers discard the value, so neither needed a change.

Addresses the reporting half of flashbots#84. Verifying archived evidence
against a pinned bundle at an explicit instant, the other half,
follows separately.
The bundle and the instant were two independent public fields, so the
pair a later replay needs arrived pre-split. Taking one without the
other is not a mistake a caller has to work at: it is the shape of
least resistance, and nothing objects. The input side already refuses
that - VerifyMode::Archived carries both or neither - so the output
was the one place the pair could come apart.

CollateralSnapshot binds them. It is one value coming out and, in the
change that follows, the same value going back in, so archiving is
"keep the snapshot" and re-verifying is "hand it back".

VerifiedAttestation becomes AttestationResult. RFC 9334 calls what an
attester produces Evidence and what a verifier produces from it an
Attestation Result. The crate already takes AttestationEvidence in, so
this names the far end of one appraisal; "attestation" on its own named
no field of the struct.

The measurements docs now say where the values come from, which the
Azure path made worth stating. They are read out of the quote on DCAP
and GCP. On Azure they are the vTPM PCRs, measuring the guest boot
rather than the launched TD - chained to the quote, whose report data
commits to the HCL var data carrying the AK public key that signs the
vTPM quote, but no field of it. The fixture test now asserts the whole
snapshot, so the pairing itself is covered.
samlaf added a commit to SeismicSystems/attested-tls that referenced this pull request Aug 25, 2026
…fyMode

Second of two changes for flashbots#84, stacked on
flashbots#85.

Reporting the collateral a verification consumed is half of provenance.
The other half is running the same verification again later, against
that bundle, and getting the same answer. Nothing exposed that: the
public entry points always fetched, and the only way to supply a bundle
was through test-only variants that also took a bare timestamp.

Verification now takes a VerifyMode. Live fetches collateral and reads
the wall clock. Archived carries the bundle and the instant it was
collected, so the two can never be supplied apart - a pinned bundle
evaluated at the wrong instant is the mistake the old
(Option<collateral>, now) pair permitted. Azure holds its AK certificate
chain to the instant the DCAP leg reported, so both legs evaluate at one
time in either mode.

The Azure TCB override leaves the public DCAP entry points. Only the
Azure verifier has a reason to relax TCB checks, so it reaches the
override through a crate-private variant; verify_dcap_attestation always
verifies at full strictness. The mock verifier likewise becomes its own
function rather than a cfg switch inside the production one, so
verify_dcap_attestation means Intel-rooted in every build and the
fixture tests replay real captures against it. AttestationVerifier is
the one place that picks between them.

BREAKING CHANGE: verify_attestation and verify_attestation_sync take a
VerifyMode; the DCAP and Azure entry points take mode before pccs and
the DCAP ones drop override_azure_outdated_tcb; the
*_with_given_timestamp variants are gone, replaced by
VerifyMode::Archived.

GCP checks and Archived mode
----------------------------

The GCP host provenance check from flashbots#54 stays live
in either mode, as does the firmware fetch for the quote's MRTD. Neither
rests on signed material a replay could re-verify: the provenance
document is an unsigned JSON object whose trust is the TLS connection to
Google's bucket, so archiving it would not make a replay stronger. The
docs on VerifyMode::Archived and verify_attestation state the carve-out.
Whether Archived should skip the provenance lookup instead is left open.

Closes flashbots#84
@samlaf

samlaf commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

@ameba23 could you take a look at this and its followup SeismicSystems#3 when you get a chance? Would like to know if this aligns with the various issues you've been battling and with your current direction for the crate.

/// verification always consumes exactly one collateral bundle, whichever
/// platform produced the evidence.
#[derive(Clone, Debug)]
pub struct AttestationResult {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having tdx quote and collateral is TDX specific. Which might make it tricky to later add support for other TEE platforms (eg: AWS Nitro which there is already a PR for).

Im not sure what the best way is to make this extendable later for other possible supported platforms. Probably we want an attestation evidence enum with TDX variant - but don't know if this should encompass the measurements as well or be a separate enum.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants