pub async fn verify(
request: &SignedRequest<'_>,
config: &VerifierConfig,
jwks: &IssuerJwks,
replay_store: &dyn ReplayStore,
) -> Result<DeviceIdentity, VerifyError>Expand description
Verifies a device-bound signed request against the configured trust policy.
request.signature and request.attestation carry the two credentials (see
SignedRequest); this function is deliberately framework-agnostic so it can be called from
a tower::Layer (see the optional axum feature), a future authkestra trait-based
integration, or a plain test harness — the algorithm itself does not care which.
Order, and why it is load-bearing:
- Presence — both credentials must be present. An attestation alone is a bearer token (it is public, travels in every request, and is likely logged); rejecting this case is what keeps the attestation from becoming exactly the weaker-than-normal scheme this design exists to avoid.
- Parse +
algcheck — for both credentials, cheap, before any cryptographic work. - Attestation trust — issuer,
kid, signature, expiry, device status. - The binding — recompute the embedded
jwk’s RFC 7638 thumbprint and compare it, constant-time, to the attestation’scnf.jkt. This is the step that cannot be inferred from the other two. An attacker holding a victim’s attestation (public, not secret) and their own genuinely-held keypair passes steps 3 and 5 independently and completely; only this comparison detects that the two credentials describe different keys. Skipping, reordering, or short-circuiting it is a total authentication bypass. - Request-signature verification against that now-bound
jwk. - Freshness — skew window, maximum signature lifetime.
- Request binding — method, path, audience, query hash, body hash.
- Replay — recorded last, and fails closed on any store error, not just “already
present”. A replay store that cannot be reached must reject exactly as if the
jtihad already been seen; falling back to “allow” would silently disable replay protection during an outage, which is worse than rejecting traffic.