use std::sync::Arc;
use std::time::{Duration, Instant};
use arc_swap::ArcSwap;
use jsonwebtoken::jwk::{
AlgorithmParameters, Jwk, JwkSet, KeyAlgorithm, KeyOperations, PublicKeyUse,
};
use jsonwebtoken::{Algorithm, DecodingKey, Header};
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub(super) enum KeyError {
#[error("the access token names no key and the published set is ambiguous")]
NoKeyId,
#[error("the access token names a key this server does not publish")]
UnknownKey,
#[error("the named key is published for encryption, not signatures")]
NotForSignatures,
#[error("the named key is not published for verification")]
NotForVerification,
#[error("the named key is published for a different algorithm")]
AlgorithmMismatch,
#[error("the access token uses an algorithm this server does not accept")]
UnacceptedAlgorithm,
#[error("the named key is not usable as a verification key")]
Unusable,
}
#[derive(Debug, thiserror::Error)]
pub(super) enum KeyFailure {
#[error("no published key may verify the presented token: {0}")]
NoKey(#[from] KeyError),
#[error("the published key set could not be read: {0}")]
Unreadable(String),
}
pub(super) struct KeySet {
uri: Arc<str>,
http: reqwest::Client,
accepted: Arc<[Algorithm]>,
cached: ArcSwap<Cached>,
refresh_floor: Duration,
}
#[derive(Debug, Clone, Default)]
struct Cached {
keys: Option<Arc<JwkSet>>,
fetched_at: Option<Instant>,
}
impl Clone for KeySet {
fn clone(&self) -> Self {
Self {
uri: Arc::clone(&self.uri),
http: self.http.clone(),
accepted: Arc::clone(&self.accepted),
cached: ArcSwap::from(self.cached.load_full()),
refresh_floor: self.refresh_floor,
}
}
}
impl std::fmt::Debug for KeySet {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("KeySet")
.field("uri", &self.uri)
.field("accepted", &self.accepted)
.field("keys_cached", &self.cached.load().keys.is_some())
.finish_non_exhaustive()
}
}
impl KeySet {
pub(super) fn new(uri: impl Into<Arc<str>>, accepted: Vec<Algorithm>) -> Self {
Self {
uri: uri.into(),
http: reqwest::Client::new(),
accepted: accepted.into(),
cached: ArcSwap::from_pointee(Cached::default()),
refresh_floor: Duration::from_secs(60),
}
}
pub(super) fn accepted(&self) -> &[Algorithm] {
&self.accepted
}
pub(super) fn with_http_client(mut self, http: reqwest::Client) -> Self {
self.http = http;
self
}
pub(super) fn invalidate(&self) {
self.cached.store(Arc::new(Cached::default()));
}
pub(super) async fn warm(&self) -> Result<(), KeyFailure> {
self.fetch().await.map(|_| ())
}
pub(super) async fn key_for(
&self,
header: &Header,
allow_refresh: bool,
) -> Result<DecodingKey, KeyFailure> {
if !self.accepted.contains(&header.alg) {
return Err(KeyFailure::NoKey(KeyError::UnacceptedAlgorithm));
}
if let Some(keys) = self.cached.load().keys.clone() {
match select(&keys, header) {
Ok(jwk) => return decode(jwk, header).map_err(KeyFailure::NoKey),
Err(KeyError::UnknownKey) => {}
Err(other) => return Err(other.into()),
}
}
let floor = self.cached.load().fetched_at;
let due = floor.is_none_or(|at| at.elapsed() >= self.refresh_floor);
if !allow_refresh || !due {
return Err(KeyError::UnknownKey.into());
}
let keys = self.fetch().await?;
let jwk = select(&keys, header)?;
decode(jwk, header).map_err(KeyFailure::NoKey)
}
async fn fetch(&self) -> Result<Arc<JwkSet>, KeyFailure> {
let response = self
.http
.get(&*self.uri)
.send()
.await
.map_err(|error| KeyFailure::Unreadable(error.to_string()))?;
let status = response.status();
if !status.is_success() {
return Err(KeyFailure::Unreadable(format!(
"the key endpoint answered {status}"
)));
}
let body = response
.bytes()
.await
.map_err(|error| KeyFailure::Unreadable(error.to_string()))?;
let keys: JwkSet = serde_json::from_slice(&body)
.map_err(|error| KeyFailure::Unreadable(error.to_string()))?;
let keys = Arc::new(keys);
self.cached.store(Arc::new(Cached {
keys: Some(Arc::clone(&keys)),
fetched_at: Some(Instant::now()),
}));
Ok(keys)
}
}
pub(super) fn select<'set>(keys: &'set JwkSet, header: &Header) -> Result<&'set Jwk, KeyError> {
let chosen = match &header.kid {
Some(kid) => keys.find(kid).ok_or(KeyError::UnknownKey)?,
None => match keys.keys.as_slice() {
[only] => only,
_ => return Err(KeyError::NoKeyId),
},
};
if chosen.common.public_key_use == Some(PublicKeyUse::Encryption) {
return Err(KeyError::NotForSignatures);
}
if chosen
.common
.key_operations
.as_ref()
.is_some_and(|ops| !ops.contains(&KeyOperations::Verify))
{
return Err(KeyError::NotForVerification);
}
if chosen
.common
.key_algorithm
.is_some_and(|declared| declared != KeyAlgorithm::from(header.alg))
{
return Err(KeyError::AlgorithmMismatch);
}
Ok(chosen)
}
fn decode(jwk: &Jwk, header: &Header) -> Result<DecodingKey, KeyError> {
if family_of(jwk)? != Family::of(header.alg) {
return Err(KeyError::AlgorithmMismatch);
}
DecodingKey::from_jwk(jwk).map_err(|_| KeyError::Unusable)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Family {
Secret,
Asymmetric,
}
impl Family {
const fn of(alg: Algorithm) -> Self {
match alg {
Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => Self::Secret,
_ => Self::Asymmetric,
}
}
}
const fn family_of(jwk: &Jwk) -> Result<Family, KeyError> {
match &jwk.algorithm {
AlgorithmParameters::OctetKey(_) => Ok(Family::Secret),
AlgorithmParameters::RSA(_)
| AlgorithmParameters::EllipticCurve(_)
| AlgorithmParameters::OctetKeyPair(_) => Ok(Family::Asymmetric),
_ => Err(KeyError::Unusable),
}
}
#[cfg(test)]
impl KeySet {
pub(super) fn seed(&self, keys: JwkSet) {
self.cached.store(Arc::new(Cached {
keys: Some(Arc::new(keys)),
fetched_at: Some(Instant::now()),
}));
}
}