structured_proxy/auth/
jwks.rs1use std::collections::HashMap;
10use std::sync::Arc;
11use std::time::{Duration, Instant};
12
13use jsonwebtoken::jwk::{AlgorithmParameters, EllipticCurve, Jwk, JwkSet, KeyAlgorithm};
14use jsonwebtoken::{Algorithm, DecodingKey};
15use tokio::sync::{Mutex, RwLock};
16
17#[derive(Clone)]
19pub struct VerifyingKey {
20 pub key: Arc<DecodingKey>,
21 pub algorithm: Algorithm,
22}
23
24#[derive(Default)]
26struct KeySet {
27 keys: HashMap<String, VerifyingKey>,
28 fetched: Option<Instant>,
30}
31
32pub struct JwksCache {
34 uri: String,
35 client: reqwest::Client,
36 set: RwLock<KeySet>,
37 last_refresh: Mutex<Option<Instant>>,
38 max_age: Duration,
39 min_refresh_interval: Duration,
40}
41
42pub(crate) const MIN_REFRESH_INTERVAL: Duration = Duration::from_secs(60);
45
46pub const DEFAULT_MAX_AGE: Duration = Duration::from_secs(300);
49
50const JWKS_HTTP_TIMEOUT: Duration = Duration::from_secs(5);
52
53impl JwksCache {
54 pub fn new(uri: String) -> Self {
57 let client = reqwest::Client::builder()
58 .timeout(JWKS_HTTP_TIMEOUT)
59 .tls_backend_preconfigured(crate::tls::client_config())
63 .build()
64 .unwrap_or_default();
65 Self {
66 uri,
67 client,
68 set: RwLock::new(KeySet::default()),
69 last_refresh: Mutex::new(None),
70 max_age: DEFAULT_MAX_AGE,
71 min_refresh_interval: MIN_REFRESH_INTERVAL,
72 }
73 }
74
75 pub fn with_max_age(mut self, max_age: Duration) -> Self {
78 self.max_age = max_age;
79 self
80 }
81
82 #[cfg(test)]
83 fn with_min_refresh_interval(mut self, interval: Duration) -> Self {
84 self.min_refresh_interval = interval;
85 self
86 }
87
88 pub async fn key_for(&self, kid: &str) -> Option<VerifyingKey> {
91 {
92 let set = self.set.read().await;
93 if set.fetched.is_some_and(|t| t.elapsed() < self.max_age) {
94 if let Some(key) = set.keys.get(kid) {
95 return Some(key.clone());
96 }
97 }
98 }
99 if let Err(reason) = self.refresh().await {
100 tracing::debug!(%reason, "JWKS not refetched; using the keys already known");
101 }
102 self.set.read().await.keys.get(kid).cloned()
108 }
109
110 async fn refresh(&self) -> Result<(), String> {
114 let mut last = self.last_refresh.lock().await;
120 if let Some(t) = *last {
121 let loaded = self.set.read().await.fetched.is_some();
122 if loaded && t.elapsed() < self.min_refresh_interval {
123 return Err("refresh throttled".to_string());
124 }
125 }
126 *last = Some(Instant::now());
127
128 let response = self
129 .client
130 .get(&self.uri)
131 .send()
132 .await
133 .and_then(reqwest::Response::error_for_status)
134 .map_err(|e| format!("JWKS fetch failed: {e}"))?;
135 let set: JwkSet = response
136 .json()
137 .await
138 .map_err(|e| format!("JWKS decode failed: {e}"))?;
139
140 *self.set.write().await = KeySet {
141 keys: parse_jwks(&set),
142 fetched: Some(Instant::now()),
143 };
144 Ok(())
145 }
146}
147
148fn parse_jwks(set: &JwkSet) -> HashMap<String, VerifyingKey> {
151 let mut map = HashMap::new();
152 for jwk in &set.keys {
153 let Some(kid) = jwk.common.key_id.clone() else {
154 continue;
155 };
156 let Some(algorithm) = algorithm_for(jwk) else {
157 continue;
158 };
159 if let Ok(key) = DecodingKey::from_jwk(jwk) {
160 map.insert(
161 kid,
162 VerifyingKey {
163 key: Arc::new(key),
164 algorithm,
165 },
166 );
167 }
168 }
169 map
170}
171
172fn algorithm_for(jwk: &Jwk) -> Option<Algorithm> {
178 if let Some(alg) = jwk.common.key_algorithm.and_then(key_algorithm_to_alg) {
179 return Some(alg);
180 }
181 match &jwk.algorithm {
182 AlgorithmParameters::RSA(_) => Some(Algorithm::RS256),
183 AlgorithmParameters::EllipticCurve(ec) => match ec.curve {
184 EllipticCurve::P256 => Some(Algorithm::ES256),
185 EllipticCurve::P384 => Some(Algorithm::ES384),
186 _ => None,
188 },
189 AlgorithmParameters::OctetKeyPair(_) => Some(Algorithm::EdDSA),
190 AlgorithmParameters::OctetKey(_) => None,
191 _ => None,
194 }
195}
196
197fn key_algorithm_to_alg(ka: KeyAlgorithm) -> Option<Algorithm> {
200 Some(match ka {
201 KeyAlgorithm::ES256 => Algorithm::ES256,
202 KeyAlgorithm::ES384 => Algorithm::ES384,
203 KeyAlgorithm::RS256 => Algorithm::RS256,
204 KeyAlgorithm::RS384 => Algorithm::RS384,
205 KeyAlgorithm::RS512 => Algorithm::RS512,
206 KeyAlgorithm::PS256 => Algorithm::PS256,
207 KeyAlgorithm::PS384 => Algorithm::PS384,
208 KeyAlgorithm::PS512 => Algorithm::PS512,
209 KeyAlgorithm::EdDSA => Algorithm::EdDSA,
210 _ => return None,
211 })
212}
213
214#[cfg(test)]
215mod tests;