Skip to content

feat(attestation)!: pin verification to archived collateral with VerifyMode - #93

Open
samlaf wants to merge 1 commit into
flashbots:mainfrom
SeismicSystems:sei-208/pr2-verify-mode
Open

feat(attestation)!: pin verification to archived collateral with VerifyMode#93
samlaf wants to merge 1 commit into
flashbots:mainfrom
SeismicSystems:sei-208/pr2-verify-mode

Conversation

@samlaf

@samlaf samlaf commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Closes #84. Second of two changes for that issue, on top of #85.

This finalizes the feature that I actually needed to re-verify archived quotes. Made some small cleanups to the function names and architecture (for example passing a QuoteVerifier to verifyQuote) at the same time, lmkwyt.

LLM Summary

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

API before and after

AttestationVerifier
  verify_attestation(msg, input)                -> verify_attestation(msg, input, mode)
  verify_attestation_sync(msg, input)           -> verify_attestation_sync(msg, input, mode)

dcap
  verify_dcap_attestation(q, input, pccs)       -> verify_dcap_attestation(q, input, mode, pccs)
  verify_dcap_attestation_sync(q, input, pccs)  -> verify_dcap_attestation_sync(q, input, mode, pccs)
  verify_dcap_attestation_with_given_timestamp(
      q, input, pccs, Option<collateral>, now,
      override_azure_outdated_tcb)              -> (removed)
  verify_dcap_attestation_with_timestamp_sync(
      q, input, pccs, Option<collateral>, now,
      override_azure_outdated_tcb)              -> (removed)

azure
  verify_azure_attestation(a, input, pccs, override)
                                                -> verify_azure_attestation(a, input, mode, pccs, override)
  verify_azure_attestation_sync(a, input, pccs, override)
                                                -> verify_azure_attestation_sync(a, input, mode, pccs, override)

new
  enum VerifyMode { Live, Archived(EndorsementSnapshot) }
  DcapVerificationError::ArchivedWithoutDcapCollateral

Why this shape

One input instead of two. The removed variants took the collateral and the instant as separate arguments, so a caller could pair a pinned bundle with the wrong instant, or a live fetch with a pinned instant, and get a verdict that reproduces nothing. VerifyMode::Archived takes the EndorsementSnapshot that #85 hands back, so the bundle and the instant it was held to travel together and the mistake has no spelling. The mixed case is refused too: an archived snapshot with no bundle for the DCAP leg fails with ArchivedWithoutDcapCollateral rather than being completed by a fetch.

The mode reaches the verifier. The measurement-policy check lives on AttestationVerifier, and a relying party re-checking archived evidence needs both it and the pinned instant. The removed variants sat below the verifier, so that combination did not exist. The mode is a parameter of the call rather than the builder because it is a fact about one verification, not about the verifier: the same instance serves a live handshake and an archive replay.

One instant for both Azure legs. The DCAP leg reports the instant it evaluated at, and the vTPM AK chain is checked at that same instant, in either mode. The wall clock is read in exactly one place.

The Azure TCB override leaves the public surface. It rode along on the removed variants only because the Azure verifier and the fixture tests shared them. Both now call the crate-private body that the two public entry points wrap, so the override is an argument of the Azure leg and nothing else. Only Azure has a reason to relax TCB checks.

Live is behaviour-preserving. Every existing caller passes VerifyMode::Live and gets what it got before: collateral from the PCCS or Intel, freshness at the wall clock. The two in-tree callers, attested-tls and attestation-provider-server, needed only that argument.

GCP checks and Archived mode

The GCP host provenance check from #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.

BREAKING CHANGE: verify_attestation and verify_attestation_sync take a VerifyMode; the DCAP and Azure entry points take mode before pccs; the *_with_given_timestamp variants are gone, replaced by VerifyMode::Archived, which fails with
DcapVerificationError::ArchivedWithoutDcapCollateral when its snapshot carries no DCAP bundle.

…fyMode

Closes flashbots#84

Second of two changes for that issue, on top of flashbots#85.

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

API before and after
--------------------

    AttestationVerifier
      verify_attestation(msg, input)                -> verify_attestation(msg, input, mode)
      verify_attestation_sync(msg, input)           -> verify_attestation_sync(msg, input, mode)

    dcap
      verify_dcap_attestation(q, input, pccs)       -> verify_dcap_attestation(q, input, mode, pccs)
      verify_dcap_attestation_sync(q, input, pccs)  -> verify_dcap_attestation_sync(q, input, mode, pccs)
      verify_dcap_attestation_with_given_timestamp(
          q, input, pccs, Option<collateral>, now,
          override_azure_outdated_tcb)              -> (removed)
      verify_dcap_attestation_with_timestamp_sync(
          q, input, pccs, Option<collateral>, now,
          override_azure_outdated_tcb)              -> (removed)

    azure
      verify_azure_attestation(a, input, pccs, override)
                                                    -> verify_azure_attestation(a, input, mode, pccs, override)
      verify_azure_attestation_sync(a, input, pccs, override)
                                                    -> verify_azure_attestation_sync(a, input, mode, pccs, override)

    new
      enum VerifyMode { Live, Archived(EndorsementSnapshot) }
      DcapVerificationError::ArchivedWithoutDcapCollateral

Why this shape
--------------

One input instead of two. The removed variants took the collateral and
the instant as separate arguments, so a caller could pair a pinned bundle
with the wrong instant, or a live fetch with a pinned instant, and get a
verdict that reproduces nothing. VerifyMode::Archived takes the
EndorsementSnapshot that flashbots#85 hands back, so the bundle and the instant it
was held to travel together and the mistake has no spelling. The mixed
case is refused too: an archived snapshot with no bundle for the DCAP leg
fails with ArchivedWithoutDcapCollateral rather than being completed by a
fetch.

The mode reaches the verifier. The measurement-policy check lives on
AttestationVerifier, and a relying party re-checking archived evidence
needs both it and the pinned instant. The removed variants sat below the
verifier, so that combination did not exist. The mode is a parameter of
the call rather than the builder because it is a fact about one
verification, not about the verifier: the same instance serves a live
handshake and an archive replay.

One instant for both Azure legs. The DCAP leg reports the instant it
evaluated at, and the vTPM AK chain is checked at that same instant, in
either mode. The wall clock is read in exactly one place.

The Azure TCB override leaves the public surface. It rode along on the
removed variants only because the Azure verifier and the fixture tests
shared them. Both now call the crate-private body that the two public
entry points wrap, so the override is an argument of the Azure leg and
nothing else. Only Azure has a reason to relax TCB checks.

Live is behaviour-preserving. Every existing caller passes
VerifyMode::Live and gets what it got before: collateral from the PCCS or
Intel, freshness at the wall clock. The two in-tree callers, attested-tls
and attestation-provider-server, needed only that argument.

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.

BREAKING CHANGE: verify_attestation and verify_attestation_sync take a
VerifyMode; the DCAP and Azure entry points take mode before pccs; the
*_with_given_timestamp variants are gone, replaced by
VerifyMode::Archived, which fails with
DcapVerificationError::ArchivedWithoutDcapCollateral when its snapshot
carries no DCAP bundle.
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.

Allow verification at an explicit timestamp, and report which collateral was used

1 participant