use crate::{
crypto::{
KeyMatchStrength,
verifier::{JwsVerifier, KeyMatch, VerifyError},
},
platform::MaybeSendBoxFuture,
};
#[derive(Debug, Clone)]
pub struct RetryingVerifier<V> {
inner: V,
}
impl<V: JwsVerifier> RetryingVerifier<V> {
pub fn new(inner: V) -> Self {
Self { inner }
}
}
impl<V: JwsVerifier + 'static> JwsVerifier for RetryingVerifier<V> {
fn key_match(&self, key_match: &KeyMatch<'_>) -> Option<KeyMatchStrength> {
self.inner.key_match(key_match)
}
fn verify<'a>(
&'a self,
input: &'a [u8],
signature: &'a [u8],
key_match: &'a KeyMatch<'a>,
) -> MaybeSendBoxFuture<'a, Result<(), VerifyError>> {
Box::pin(async move {
match self.inner.verify(input, signature, key_match).await {
Err(VerifyError::NoMatchingKey) => {
if self.inner.try_refresh().await {
self.inner.verify(input, signature, key_match).await
} else {
Err(VerifyError::NoMatchingKey)
}
}
other => other,
}
})
}
fn try_refresh(&self) -> MaybeSendBoxFuture<'_, bool> {
self.inner.try_refresh()
}
}
#[cfg(test)]
mod tests {
use std::sync::{
Arc,
atomic::{AtomicBool, AtomicUsize, Ordering},
};
use super::*;
#[derive(Debug, Clone, Copy)]
enum Fail {
NoMatchingKey,
SignatureMismatch,
}
fn make_err(fail: Fail) -> VerifyError {
match fail {
Fail::NoMatchingKey => VerifyError::NoMatchingKey,
Fail::SignatureMismatch => VerifyError::SignatureMismatch,
}
}
#[derive(Debug, Clone)]
struct FakeInner {
fail: Fail,
recover_on_refresh: bool,
refreshed: Arc<AtomicBool>,
refresh_calls: Arc<AtomicUsize>,
}
impl FakeInner {
fn new(fail: Fail, recover_on_refresh: bool) -> Self {
Self {
fail,
recover_on_refresh,
refreshed: Arc::new(AtomicBool::new(false)),
refresh_calls: Arc::new(AtomicUsize::new(0)),
}
}
fn refresh_calls(&self) -> usize {
self.refresh_calls.load(Ordering::SeqCst)
}
}
impl JwsVerifier for FakeInner {
fn key_match(&self, _key_match: &KeyMatch<'_>) -> Option<KeyMatchStrength> {
Some(KeyMatchStrength::ByAlgorithm)
}
fn verify<'a>(
&'a self,
_input: &'a [u8],
_signature: &'a [u8],
_key_match: &'a KeyMatch<'a>,
) -> MaybeSendBoxFuture<'a, Result<(), VerifyError>> {
let recovered = self.refreshed.load(Ordering::SeqCst);
let fail = self.fail;
Box::pin(async move {
if recovered {
Ok(())
} else {
Err(make_err(fail))
}
})
}
fn try_refresh(&self) -> MaybeSendBoxFuture<'_, bool> {
self.refresh_calls.fetch_add(1, Ordering::SeqCst);
let recover = self.recover_on_refresh;
let refreshed = self.refreshed.clone();
Box::pin(async move {
if recover {
refreshed.store(true, Ordering::SeqCst);
true
} else {
false
}
})
}
}
fn km() -> KeyMatch<'static> {
KeyMatch {
alg: "ES256",
kid: None,
}
}
#[tokio::test]
async fn no_matching_key_refreshes_and_recovers() {
let inner = FakeInner::new(Fail::NoMatchingKey, true);
let verifier = RetryingVerifier::new(inner.clone());
verifier
.verify(b"input", b"sig", &km())
.await
.expect("a miss reloads and the added key verifies");
assert_eq!(inner.refresh_calls(), 1);
}
#[tokio::test]
async fn no_matching_key_without_new_material_returns_the_miss() {
let inner = FakeInner::new(Fail::NoMatchingKey, false);
let verifier = RetryingVerifier::new(inner.clone());
assert!(verifier.verify(b"input", b"sig", &km()).await.is_err());
assert_eq!(inner.refresh_calls(), 1);
}
#[tokio::test]
async fn signature_mismatch_does_not_reload() {
let inner = FakeInner::new(Fail::SignatureMismatch, true);
let verifier = RetryingVerifier::new(inner.clone());
assert!(verifier.verify(b"input", b"sig", &km()).await.is_err());
assert_eq!(inner.refresh_calls(), 0);
}
}