pub struct DpopVerifier {
skew: Duration,
nonce_ttl: Duration,
require_nonce: bool,
started: OffsetDateTime,
replay: Mutex<SpentKeys>,
nonces: Mutex<NonceState>,
}Expand description
Proof checking, nonce issuance, and the replay cache, held together
because the replay window and the clock-skew window are the same number
– see DpopVerifier::new.
One instance covers one nonce space. This deployment’s authorization server and resource server are one origin and hold one verifier between them, which the atproto profile allows: “The Resource Server and Authorization Server may share nonces (especially if they are the same server)”.
Fields§
§skew: DurationHow far from this verifier’s own clock a proof’s iat may sit,
either direction, and also how long a jti is retained in the replay
cache: past that point the proof itself would fail the clock check on
a second presentation, so retaining it longer catches nothing a resend
would not already be refused for.
nonce_ttl: DurationHow long a nonce is handed out before a fresh one replaces it. The replaced nonce is accepted for as long again.
require_nonce: boolWhether DpopVerifier::verify requires a nonce, and so whether
this verifier hands any out.
started: OffsetDateTimeWhen this verifier began accepting proofs, to the whole second.
The replay window is in memory, so a restart empties it and a proof
captured just before the restart would verify a second time. For
the first skew after this instant a proof stamped earlier than it
is refused, which is exactly the window the emptied cache could
have covered. Past that, no proof old enough to matter still
passes the clock check.
Whole seconds because iat is whole seconds: a proof minted inside
the second this verifier started reads as minted at the start, and
is accepted.
replay: Mutex<SpentKeys>jti values already accepted, and when each one falls out of the
window in which it could still be replayed usefully.
An entry is spent by DpopVerifier::accept, which a caller reaches
only once the credential the proof arrived with has checked out, so
filling REPLAY_CAPACITY takes that many authenticated requests
inside one clock-skew window.
nonces: Mutex<NonceState>Implementations§
Source§impl DpopVerifier
impl DpopVerifier
Sourcepub fn new(skew: Duration, nonce_ttl: Duration, require_nonce: bool) -> Self
pub fn new(skew: Duration, nonce_ttl: Duration, require_nonce: bool) -> Self
Builds a verifier with the given clock-skew/replay window, nonce
lifetime, and whether a nonce is required at all.
OAuthState::default supplies this deployment’s numbers.
Sourcefn started_at(
skew: Duration,
nonce_ttl: Duration,
require_nonce: bool,
started: OffsetDateTime,
) -> Self
fn started_at( skew: Duration, nonce_ttl: Duration, require_nonce: bool, started: OffsetDateTime, ) -> Self
DpopVerifier::new with the instant it began accepting proofs
given rather than read off the clock, so a test can put a proof on
either side of it.
Sourcepub fn current_nonce(&self) -> String
pub fn current_nonce(&self) -> String
The nonce to hand a client in DPoP-Nonce: the one this verifier
hands out now, replaced with a fresh one once it has been handed out
for nonce_ttl.
fn current_nonce_at(&self, now: OffsetDateTime) -> String
Sourcepub fn verify(
&self,
proof_jwt: &str,
method: &Method,
htu: &str,
access_token: Option<&str>,
expected_thumbprint: Option<&str>,
) -> Result<VerifiedProof, DpopError>
pub fn verify( &self, proof_jwt: &str, method: &Method, htu: &str, access_token: Option<&str>, expected_thumbprint: Option<&str>, ) -> Result<VerifiedProof, DpopError>
Verifies a DPoP proof JWT against a request.
method and htu describe the request this proof must be bound to;
htu is a caller-built canonical absolute URL (scheme, host, and
path – no query, no fragment) because assembling one correctly behind
a reverse proxy is a deployment concern this module has no way to get
right on its own; see axum::extract::OriginalUri and the Host
header for how a caller typically builds it.
access_token, if given, is checked against the proof’s ath claim
(RFC 9449 §4.3’s binding of a proof to a specific bearer of a
specific access token). expected_thumbprint, if given, is checked
against the RFC 7638 thumbprint of the proof’s own key – the check
that makes a copied access token useless to whoever copied it,
because their proof is signed by a different key with a different
thumbprint.
fn verify_at( &self, proof_jwt: &str, method: &Method, htu: &str, access_token: Option<&str>, expected_thumbprint: Option<&str>, now: OffsetDateTime, ) -> Result<VerifiedProof, DpopError>
Sourcepub fn accept(&self, proof: &VerifiedProof) -> Result<(), DpopError>
pub fn accept(&self, proof: &VerifiedProof) -> Result<(), DpopError>
Records a verified proof’s jti, so that presenting the same proof
again is DpopError::Replayed.
Called once the credential the proof arrived with has checked out —
the access token at a repo write, the authorization code or the
refresh token at POST /oauth/token. Before that, nothing about the
caller is known: POST /oauth/token’s refresh grant is reachable
with any freshly generated key, so an entry spent at
DpopVerifier::verify is an entry an unauthenticated caller spends,
and the window would fill with proofs naming no credential at all.
DpopError::ReplayWindowFull rather than an eviction, for
REPLAY_CAPACITY’s own reason: dropping an entry that still names
a live proof is how a full cache becomes a way to get a replay
accepted.
fn accept_at( &self, proof: &VerifiedProof, now: OffsetDateTime, ) -> Result<(), DpopError>
Trait Implementations§
Source§impl DpopVerifier for DpopVerifier
impl DpopVerifier for DpopVerifier
Source§fn verify(
&self,
proof: &str,
http_method: &str,
http_uri: &str,
access_token: Option<&str>,
) -> Result<VerifiedProof, DpopError>
fn verify( &self, proof: &str, http_method: &str, http_uri: &str, access_token: Option<&str>, ) -> Result<VerifiedProof, DpopError>
proof (the DPoP request header) against the request it
was presented on, returning the key it proves possession of. Read moreAuto Trait Implementations§
impl !Freeze for DpopVerifier
impl RefUnwindSafe for DpopVerifier
impl Send for DpopVerifier
impl Sync for DpopVerifier
impl Unpin for DpopVerifier
impl UnsafeUnpin for DpopVerifier
impl UnwindSafe for DpopVerifier
Blanket Implementations§
§impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedExplicit<'a, E> for Twhere
T: 'a,
§impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
impl<'a, T, E> AsTaggedImplicit<'a, E> for Twhere
T: 'a,
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more