You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Another issue coming out of me thinking through the API boundaries after our discussion on #85 .
Looking a bit more into the RATS model https://www.rfc-editor.org/info/rfc9334/ I started thinking that we should aim for this library to follow its general nomenclature and roles/objects breakdown. Have you considered doing so and rejected it already for some reason?
If not, what this issue basically argues for is separation of the Verifier and Relying Party roles, and to make the RP role stateless and injectable.
LLM SUMMARY
The coupling
AttestationVerifier::verify_attestation does two jobs in one call: it verifies
the evidence, then applies the measurement policy before returning
(crates/attestation/src/lib.rs:626):
let verified = match attestation_type {/* … verify … */};// Do a measurement / attestation type policy checkself.measurement_policy.check_measurement_with_gcp_cache(&verified.measurements,
platform_metadata.as_ref(),Some(&self.known_gcp_firmware),)?;Ok(Some(verified))
RFC 9334 splits these across two roles: the Verifier consumes Evidence and
produces Attestation Results, and the Relying Party applies an Appraisal Policy
to those Results. The crate merges them, with two consequences:
You cannot get a verified result without also passing a policy. For a
relying party that wants to know "is this evidence genuinely signed and
fresh?" and decide acceptance separately — or later, or against several
policies — the only path also demands an accept/reject answer up front.
You cannot re-appraise without re-verifying. Re-running a policy against
an already-verified result means redoing signature verification, and on a
cache miss an outbound collateral fetch.
MeasurementPolicy::check_measurement is already public, so a caller can
appraise on its own. What is missing is the ability to ask the verifier not to.
Appraisal is not a pure function today
Worth surfacing separately, because it surprised us. The policy step can make a
network call. On the ExpectedMeasurements::Image path:
So "check these measurements against my policy" may reach the network to resolve
GCP firmware by MRTD. Also note the asymmetry: the fetching variant is pub(crate), while the public check_measurement passes None for the cache. A
caller appraising standalone therefore gets different behaviour from the one the
verifier uses internally — it will miss the cache rather than share it.
Whatever happens to the split, it seems worth either documenting that appraisal
may fetch, or moving that resolution to the verification side where the other
network work already lives.
Rough shape: the policy as a per-call parameter, not a field
The change that follows from the role split is that measurement_policy stops
being a field on AttestationVerifier (crates/attestation/src/lib.rs:498) and
becomes an argument:
// verification: no policy involvedlet result = verifier.verify_attestation(evidence, binding)?;// appraisal: the relying party names its own policy
verifier.appraise(&result,&policy)?;
Why a parameter rather than moving the policy out of the crate's reach entirely:
The handshake path still works.verify_attestation_binding
(crates/attested-tls/src/lib.rs:653) runs inside rustls's certificate
verification callback and must return accept-or-reject, so something has to
hold a policy there. That something is the certificate verifier struct — which
is precisely the Relying Party in RFC 9334's terms. It holds the policy and
passes it. The roles land where the RFC puts them without the handshake losing
anything.
The GCP firmware cache stays where it belongs.known_gcp_firmware is
verifier state that the policy check needs. Keeping appraisal as a method on the
verifier means the cache is still shared; a policy layer wholly outside the
crate would strand it, and every caller would re-fetch.
The benefit that isn't about our use case: one verifier can serve several
relying parties with different policies, sharing its PCCS cache, GCP firmware
cache and trusted-certificate cache. Today that needs one AttestationVerifier
per policy, and therefore one set of caches per policy.
Keep a convenience method that does both, if the common handshake path prefers one
call.
The type-level nicety is that a verified-but-unappraised result and an appraised
one could be distinct types, so a relying party cannot forget the second step.
That may be more ceremony than it's worth; mentioning it for completeness.
Not asking for a crate split
The end state of this direction is arguably a policy crate separate from a
verification crate — measurements.rs is 1938 lines and drags http, attest_measure, attest_types and file/URL policy loading that a pure verifier
does not need. I'm deliberately not asking for that:
The dependency runs the wrong way. Policy consumes MultiMeasurements, which
verification produces, so a policy crate would depend on the verifier crate or
need a third shared-types crate.
Make it possible to change the MeasurementPolicy used by an AttestationVerifier #15 (make MeasurementPolicy swappable on a live AttestationVerifier) is
largely subsumed. Its use case — a measurement mismatch, fetch a newer policy,
retry the validation — becomes re-appraising a result you already hold, with no Arc<RwLock> and no re-verification.
AttestedVerifier should return matched ExpectedMeasurements on successful verification #79 (verifier returns matched ExpectedMeasurements) modifies exactly the
step this proposes moving. Whichever lands first, the other should account for
it — if appraisal moves out, "which policy matched" becomes the return value of
the appraisal call rather than of verify_attestation.
Our motivation, for context
We use the crate as a relying party for a one-time event rather than a live
handshake (background in #84). Founding evidence is verified once and archived.
Being able to re-run a policy against an archived, already-verified result — with
no network and no re-verification — is the property we want. It is not a blocker;
we can call the policy ourselves. The coupling just means the verifier also
insists on appraising with the policy it was built with.
Another issue coming out of me thinking through the API boundaries after our discussion on #85 .
Looking a bit more into the RATS model https://www.rfc-editor.org/info/rfc9334/ I started thinking that we should aim for this library to follow its general nomenclature and roles/objects breakdown. Have you considered doing so and rejected it already for some reason?
If not, what this issue basically argues for is separation of the
VerifierandRelying Partyroles, and to make the RP role stateless and injectable.LLM SUMMARY
The coupling
AttestationVerifier::verify_attestationdoes two jobs in one call: it verifiesthe evidence, then applies the measurement policy before returning
(
crates/attestation/src/lib.rs:626):RFC 9334 splits these across two roles: the Verifier consumes Evidence and
produces Attestation Results, and the Relying Party applies an Appraisal Policy
to those Results. The crate merges them, with two consequences:
relying party that wants to know "is this evidence genuinely signed and
fresh?" and decide acceptance separately — or later, or against several
policies — the only path also demands an accept/reject answer up front.
an already-verified result means redoing signature verification, and on a
cache miss an outbound collateral fetch.
MeasurementPolicy::check_measurementis already public, so a caller canappraise on its own. What is missing is the ability to ask the verifier not to.
Appraisal is not a pure function today
Worth surfacing separately, because it surprised us. The policy step can make a
network call. On the
ExpectedMeasurements::Imagepath:So "check these measurements against my policy" may reach the network to resolve
GCP firmware by MRTD. Also note the asymmetry: the fetching variant is
pub(crate), while the publiccheck_measurementpassesNonefor the cache. Acaller appraising standalone therefore gets different behaviour from the one the
verifier uses internally — it will miss the cache rather than share it.
Whatever happens to the split, it seems worth either documenting that appraisal
may fetch, or moving that resolution to the verification side where the other
network work already lives.
Rough shape: the policy as a per-call parameter, not a field
The change that follows from the role split is that
measurement_policystopsbeing a field on
AttestationVerifier(crates/attestation/src/lib.rs:498) andbecomes an argument:
Why a parameter rather than moving the policy out of the crate's reach entirely:
verify_attestation_binding(
crates/attested-tls/src/lib.rs:653) runs inside rustls's certificateverification callback and must return accept-or-reject, so something has to
hold a policy there. That something is the certificate verifier struct — which
is precisely the Relying Party in RFC 9334's terms. It holds the policy and
passes it. The roles land where the RFC puts them without the handshake losing
anything.
still refuse an attestation type the policy never accepts before doing any DCAP
work.
known_gcp_firmwareisverifier state that the policy check needs. Keeping appraisal as a method on the
verifier means the cache is still shared; a policy layer wholly outside the
crate would strand it, and every caller would re-fetch.
The benefit that isn't about our use case: one verifier can serve several
relying parties with different policies, sharing its PCCS cache, GCP firmware
cache and trusted-certificate cache. Today that needs one
AttestationVerifierper policy, and therefore one set of caches per policy.
Keep a convenience method that does both, if the common handshake path prefers one
call.
The type-level nicety is that a verified-but-unappraised result and an appraised
one could be distinct types, so a relying party cannot forget the second step.
That may be more ceremony than it's worth; mentioning it for completeness.
Not asking for a crate split
The end state of this direction is arguably a policy crate separate from a
verification crate —
measurements.rsis 1938 lines and dragshttp,attest_measure,attest_typesand file/URL policy loading that a pure verifierdoes not need. I'm deliberately not asking for that:
MultiMeasurements, whichverification produces, so a policy crate would depend on the verifier crate or
need a third shared-types crate.
attestrepo. #40 proposes moving generation and verification into theattestrepo, socrate boundaries are already in flux and this would be the wrong moment.
Mentioning it only so the direction is legible. The ask here is the parameter.
Relationship to existing issues
complementary, not overlapping. It wants an early policy consultation on the
attestation type, before verification. That check is compatible with this split
— the type-level pre-check is cheap and stays wherever you want it, while the
measurement appraisal is what moves out. If Verifier fetches collateral and verifies quotes the policy will always reject #87 lands first this issue should
adapt to it rather than the reverse.
MeasurementPolicyused by anAttestationVerifier#15 (makeMeasurementPolicyswappable on a liveAttestationVerifier) islargely subsumed. Its use case — a measurement mismatch, fetch a newer policy,
retry the validation — becomes re-appraising a result you already hold, with no
Arc<RwLock>and no re-verification.adjacent and touches the same call. If both happen, feat: decouple platform and measurements, allow flexible policies #28 changes what the
policy compares and this changes when it runs; they shouldn't conflict, but
ordering matters.
ExpectedMeasurements) modifies exactly thestep this proposes moving. Whichever lands first, the other should account for
it — if appraisal moves out, "which policy matched" becomes the return value of
the appraisal call rather than of
verify_attestation.Our motivation, for context
We use the crate as a relying party for a one-time event rather than a live
handshake (background in #84). Founding evidence is verified once and archived.
Being able to re-run a policy against an archived, already-verified result — with
no network and no re-verification — is the property we want. It is not a blocker;
we can call the policy ourselves. The coupling just means the verifier also
insists on appraising with the policy it was built with.