use std::pin::Pin;
use crate::{
crypto::{
KeyMatchStrength,
refreshable::Refreshable,
verifier::{JwsVerifier, KeyMatch, VerifyError},
},
error::Error,
platform::{MaybeSendBoxFuture, MaybeSendFuture, MaybeSendSync},
};
pub struct RefreshableVerifier<V> {
inner: Refreshable<V>,
}
impl<V: std::fmt::Debug> std::fmt::Debug for RefreshableVerifier<V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RefreshableVerifier")
.field("inner", &self.inner)
.finish()
}
}
#[bon::bon]
impl<V: JwsVerifier + 'static> RefreshableVerifier<V> {
#[builder]
pub async fn new(
factory: impl Fn() -> Pin<Box<dyn MaybeSendFuture<Output = Result<V, Error>>>>
+ MaybeSendSync
+ 'static,
) -> Result<Self, Error> {
let inner = Refreshable::builder().factory(factory).build().await?;
Ok(Self { inner })
}
pub async fn refresh(&self) -> Result<bool, Error> {
self.inner.refresh().await
}
}
impl<V: JwsVerifier + 'static> JwsVerifier for RefreshableVerifier<V> {
fn key_match(&self, key_match: &KeyMatch<'_>) -> Option<KeyMatchStrength> {
self.inner.load().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 {
self.inner
.load_full()
.verify(input, signature, key_match)
.await
})
}
fn try_refresh(&self) -> MaybeSendBoxFuture<'_, bool> {
Box::pin(async move { self.refresh().await.is_ok() })
}
}
#[cfg(test)]
mod tests {
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
use super::*;
#[derive(Debug)]
struct FlagVerifier {
accept: bool,
}
impl JwsVerifier for FlagVerifier {
fn key_match(&self, _key_match: &KeyMatch<'_>) -> Option<KeyMatchStrength> {
self.accept.then_some(KeyMatchStrength::ByAlgorithm)
}
fn verify<'a>(
&'a self,
_input: &'a [u8],
_signature: &'a [u8],
_key_match: &'a KeyMatch<'a>,
) -> MaybeSendBoxFuture<'a, Result<(), VerifyError>> {
let accept = self.accept;
Box::pin(async move {
if accept {
Ok(())
} else {
Err(VerifyError::NoMatchingKey)
}
})
}
}
async fn accept_then_reject() -> RefreshableVerifier<FlagVerifier> {
let counter = Arc::new(AtomicUsize::new(0));
RefreshableVerifier::builder()
.factory(move || {
let accept = counter.fetch_add(1, Ordering::SeqCst) == 0;
Box::pin(async move { Ok(FlagVerifier { accept }) })
})
.build()
.await
.unwrap()
}
fn key_match() -> KeyMatch<'static> {
KeyMatch {
alg: "ES256",
kid: None,
}
}
#[tokio::test]
async fn delegates_to_the_initial_verifier() {
let verifier = accept_then_reject().await;
assert_eq!(
verifier.key_match(&key_match()),
Some(KeyMatchStrength::ByAlgorithm)
);
verifier
.verify(b"input", b"sig", &key_match())
.await
.expect("initial verifier accepts");
}
#[tokio::test]
async fn refresh_swaps_the_inner_verifier() {
let verifier = accept_then_reject().await;
assert!(verifier.refresh().await.unwrap());
assert_eq!(verifier.key_match(&key_match()), None);
verifier
.verify(b"input", b"sig", &key_match())
.await
.expect_err("post-refresh verifier rejects");
}
#[tokio::test]
async fn try_refresh_reports_success_and_swaps() {
let verifier = accept_then_reject().await;
assert!(verifier.try_refresh().await, "try_refresh succeeded");
assert_eq!(verifier.key_match(&key_match()), None);
}
}