1use crate::claims::AuthContext;
2use crate::error::VerifyError;
3use crate::keys::VerifyingKey;
4use crate::token::TokenVerifier;
5use std::collections::HashMap;
6use std::sync::Arc;
7use std::time::{Duration, Instant};
8use tokio::sync::RwLock;
9
10#[derive(Clone)]
12pub struct RotationKey {
13 pub key: VerifyingKey,
15 pub key_id: String,
17 pub added_at: Instant,
19 pub expires_at: Option<Instant>,
21 pub is_primary: bool,
23}
24
25impl RotationKey {
26 pub fn primary(key: VerifyingKey, key_id: impl Into<String>) -> Self {
28 Self {
29 key,
30 key_id: key_id.into(),
31 added_at: Instant::now(),
32 expires_at: None,
33 is_primary: true,
34 }
35 }
36
37 pub fn secondary(key: VerifyingKey, key_id: impl Into<String>, grace_period: Duration) -> Self {
39 Self {
40 key,
41 key_id: key_id.into(),
42 added_at: Instant::now(),
43 expires_at: Some(Instant::now() + grace_period),
44 is_primary: false,
45 }
46 }
47
48 pub fn is_expired(&self) -> bool {
50 self.expires_at
51 .map(|exp| Instant::now() > exp)
52 .unwrap_or(false)
53 }
54}
55
56fn build_inner_verifier(
87 key: crate::keys::VerifyingKey,
88 issuer: &str,
89 audiences: &crate::AudienceSet,
90 require_origin: bool,
91 claim_seal_keys: Option<&crate::sealed::ClaimSealKeys>,
92) -> TokenVerifier {
93 let verifier = match audiences.as_single() {
94 Some(single) => TokenVerifier::new(key, issuer, single),
95 None => TokenVerifier::with_audiences(key, issuer, audiences.iter())
96 .expect("an AudienceSet is never empty"),
97 };
98 let verifier = if require_origin {
99 verifier.with_origin_validation()
100 } else {
101 verifier
102 };
103 match claim_seal_keys {
104 Some(keys) => verifier.with_claim_seal_keys(keys.clone()),
105 None => verifier,
106 }
107}
108
109pub struct MultiKeyVerifier {
110 keys: Arc<RwLock<HashMap<String, RotationKey>>>,
111 issuer: String,
112 audiences: crate::AudienceSet,
113 require_origin: bool,
114 cleanup_interval: Duration,
115 last_cleanup: Arc<RwLock<Instant>>,
116 claim_seal_keys: Option<crate::sealed::ClaimSealKeys>,
117}
118
119impl MultiKeyVerifier {
120 pub fn new(
122 keys: Vec<RotationKey>,
123 issuer: impl Into<String>,
124 audience: impl Into<String>,
125 ) -> Self {
126 let key_map: HashMap<String, RotationKey> =
127 keys.into_iter().map(|k| (k.key_id.clone(), k)).collect();
128
129 Self {
130 keys: Arc::new(RwLock::new(key_map)),
131 issuer: issuer.into(),
132 audiences: crate::AudienceSet::single(audience),
133 require_origin: false,
134 claim_seal_keys: None,
135 cleanup_interval: Duration::from_secs(3600), last_cleanup: Arc::new(RwLock::new(Instant::now())),
137 }
138 }
139
140 pub fn from_single_key(
142 key: VerifyingKey,
143 key_id: impl Into<String>,
144 issuer: impl Into<String>,
145 audience: impl Into<String>,
146 ) -> Self {
147 Self::new(vec![RotationKey::primary(key, key_id)], issuer, audience)
148 }
149
150 pub fn with_origin_validation(mut self) -> Self {
152 self.require_origin = true;
153 self
154 }
155
156 pub fn with_claim_seal_keys(mut self, keys: crate::sealed::ClaimSealKeys) -> Self {
159 self.claim_seal_keys = Some(keys);
160 self
161 }
162
163 pub fn with_cleanup_interval(mut self, interval: Duration) -> Self {
165 self.cleanup_interval = interval;
166 self
167 }
168
169 pub async fn add_key(&self, key: RotationKey) {
171 let mut keys = self.keys.write().await;
172
173 if key.is_primary {
175 for existing in keys.values_mut() {
176 if existing.is_primary {
177 existing.is_primary = false;
178 existing.expires_at = Some(Instant::now() + Duration::from_secs(86400));
180 }
182 }
183 }
184
185 keys.insert(key.key_id.clone(), key);
186 }
187
188 pub async fn remove_key(&self, key_id: &str) {
190 let mut keys = self.keys.write().await;
191 keys.remove(key_id);
192 }
193
194 pub async fn key_ids(&self) -> Vec<String> {
196 let keys = self.keys.read().await;
197 keys.keys().cloned().collect()
198 }
199
200 pub async fn primary_key_id(&self) -> Option<String> {
202 let keys = self.keys.read().await;
203 keys.values()
204 .find(|k| k.is_primary)
205 .map(|k| k.key_id.clone())
206 }
207
208 async fn cleanup_expired_keys(&self) {
210 let should_cleanup = {
211 let last = self.last_cleanup.read().await;
212 last.elapsed() >= self.cleanup_interval
213 };
214
215 if !should_cleanup {
216 return;
217 }
218
219 let mut keys = self.keys.write().await;
220 let expired: Vec<String> = keys
221 .iter()
222 .filter(|(_, k)| k.is_expired())
223 .map(|(id, _)| id.clone())
224 .collect();
225
226 for key_id in expired {
227 keys.remove(&key_id);
228 }
229
230 let mut last = self.last_cleanup.write().await;
232 *last = Instant::now();
233 }
234
235 pub async fn verify(
237 &self,
238 token: &str,
239 expected_origin: Option<&str>,
240 expected_client_ip: Option<&str>,
241 ) -> Result<AuthContext, VerifyError> {
242 self.cleanup_expired_keys().await;
244
245 let keys = self.keys.read().await;
246
247 if keys.is_empty() {
248 return Err(VerifyError::KeyNotFound("no keys configured".to_string()));
249 }
250
251 let mut last_error = None;
252
253 let mut key_order: Vec<&RotationKey> = keys.values().collect();
255 key_order.sort_by_key(|k| !k.is_primary); for key_entry in key_order {
258 if key_entry.is_expired() {
259 continue;
260 }
261
262 let verifier = build_inner_verifier(
263 key_entry.key.clone(),
264 &self.issuer,
265 &self.audiences,
266 self.require_origin,
267 self.claim_seal_keys.as_ref(),
268 );
269
270 match verifier.verify(token, expected_origin, expected_client_ip) {
271 Ok(ctx) => {
272 return Ok(ctx);
273 }
274 Err(VerifyError::InvalidSignature) => {
275 last_error = Some(VerifyError::InvalidSignature);
277 continue;
278 }
279 Err(e) => {
280 return Err(e);
282 }
283 }
284 }
285
286 Err(last_error.unwrap_or(VerifyError::InvalidSignature))
288 }
289
290 pub async fn verify_fast(
292 &self,
293 token: &str,
294 expected_origin: Option<&str>,
295 expected_client_ip: Option<&str>,
296 ) -> Result<AuthContext, VerifyError> {
297 let keys = self.keys.read().await;
298
299 if keys.is_empty() {
300 return Err(VerifyError::KeyNotFound("no keys configured".to_string()));
301 }
302
303 let mut last_error = None;
304
305 let mut key_order: Vec<&RotationKey> = keys.values().collect();
307 key_order.sort_by_key(|k| !k.is_primary);
308
309 for key_entry in key_order {
310 if key_entry.is_expired() {
311 continue;
312 }
313
314 let verifier = build_inner_verifier(
315 key_entry.key.clone(),
316 &self.issuer,
317 &self.audiences,
318 self.require_origin,
319 self.claim_seal_keys.as_ref(),
320 );
321
322 match verifier.verify(token, expected_origin, expected_client_ip) {
323 Ok(ctx) => return Ok(ctx),
324 Err(VerifyError::InvalidSignature) => {
325 last_error = Some(VerifyError::InvalidSignature);
326 continue;
327 }
328 Err(e) => return Err(e),
329 }
330 }
331
332 Err(last_error.unwrap_or(VerifyError::InvalidSignature))
333 }
334}
335
336pub struct MultiKeyVerifierBuilder {
338 keys: Vec<RotationKey>,
339 issuer: String,
340 audience: String,
341 require_origin: bool,
342 cleanup_interval: Duration,
343}
344
345impl MultiKeyVerifierBuilder {
346 pub fn new(issuer: impl Into<String>, audience: impl Into<String>) -> Self {
348 Self {
349 keys: Vec::new(),
350 issuer: issuer.into(),
351 audience: audience.into(),
352 require_origin: false,
353 cleanup_interval: Duration::from_secs(3600),
354 }
355 }
356
357 pub fn with_primary_key(mut self, key: VerifyingKey, key_id: impl Into<String>) -> Self {
359 self.keys.push(RotationKey::primary(key, key_id));
360 self
361 }
362
363 pub fn with_secondary_key(
365 mut self,
366 key: VerifyingKey,
367 key_id: impl Into<String>,
368 grace_period: Duration,
369 ) -> Self {
370 self.keys
371 .push(RotationKey::secondary(key, key_id, grace_period));
372 self
373 }
374
375 pub fn with_origin_validation(mut self) -> Self {
377 self.require_origin = true;
378 self
379 }
380
381 pub fn with_cleanup_interval(mut self, interval: Duration) -> Self {
383 self.cleanup_interval = interval;
384 self
385 }
386
387 pub fn build(self) -> MultiKeyVerifier {
389 let mut verifier = MultiKeyVerifier::new(self.keys, self.issuer, self.audience);
390 if self.require_origin {
391 verifier = verifier.with_origin_validation();
392 }
393 verifier.with_cleanup_interval(self.cleanup_interval)
394 }
395}
396
397#[cfg(test)]
398mod tests {
399 use super::*;
400 use crate::claims::{KeyClass, SessionClaims};
401 use crate::keys::SigningKey;
402 use crate::token::TokenSigner;
403
404 #[tokio::test]
405 async fn test_multi_key_verifier_single_key() {
406 let signing_key = SigningKey::generate();
407 let verifying_key = signing_key.verifying_key();
408
409 let signer = TokenSigner::new(signing_key, "test-issuer");
410 let verifier = MultiKeyVerifier::from_single_key(
411 verifying_key,
412 "key-1",
413 "test-issuer",
414 "test-audience",
415 );
416
417 let claims = SessionClaims::builder("test-issuer", "test-subject", "test-audience")
418 .with_scope("read")
419 .with_metering_key("meter-123")
420 .with_key_class(KeyClass::Publishable)
421 .build();
422
423 let token = signer.sign(claims).unwrap();
424 let context = verifier.verify(&token, None, None).await.unwrap();
425
426 assert_eq!(context.subject, "test-subject");
427 assert_eq!(verifier.primary_key_id().await, Some("key-1".to_string()));
428 }
429
430 #[tokio::test]
431 async fn test_key_rotation() {
432 let old_signing_key = SigningKey::generate();
434 let old_verifying_key = old_signing_key.verifying_key();
435 let old_signer = TokenSigner::new(old_signing_key, "test-issuer");
436
437 let new_signing_key = SigningKey::generate();
439 let new_verifying_key = new_signing_key.verifying_key();
440 let new_signer = TokenSigner::new(new_signing_key, "test-issuer");
441
442 let old_key = RotationKey::primary(old_verifying_key.clone(), "key-old");
444 let verifier = MultiKeyVerifier::new(vec![old_key], "test-issuer", "test-audience");
445
446 let old_claims = SessionClaims::builder("test-issuer", "subject-1", "test-audience")
448 .with_scope("read")
449 .with_metering_key("meter-1")
450 .with_key_class(KeyClass::Publishable)
451 .build();
452 let old_token = old_signer.sign(old_claims).unwrap();
453
454 let ctx = verifier.verify(&old_token, None, None).await.unwrap();
456 assert_eq!(ctx.subject, "subject-1");
457
458 let new_key = RotationKey::primary(new_verifying_key, "key-new");
460 verifier.add_key(new_key).await;
461
462 let ctx = verifier.verify(&old_token, None, None).await.unwrap();
464 assert_eq!(ctx.subject, "subject-1");
465
466 let new_claims = SessionClaims::builder("test-issuer", "subject-2", "test-audience")
468 .with_scope("read")
469 .with_metering_key("meter-2")
470 .with_key_class(KeyClass::Publishable)
471 .build();
472 let new_token = new_signer.sign(new_claims).unwrap();
473
474 let ctx = verifier.verify(&new_token, None, None).await.unwrap();
475 assert_eq!(ctx.subject, "subject-2");
476
477 assert_eq!(verifier.primary_key_id().await, Some("key-new".to_string()));
479
480 let key_ids = verifier.key_ids().await;
482 assert!(key_ids.contains(&"key-old".to_string()));
483 assert!(key_ids.contains(&"key-new".to_string()));
484 }
485
486 #[tokio::test]
487 async fn test_verifier_builder() {
488 let signing_key = SigningKey::generate();
489 let verifying_key = signing_key.verifying_key();
490
491 let verifier = MultiKeyVerifierBuilder::new("test-issuer", "test-audience")
492 .with_primary_key(verifying_key, "key-1")
493 .with_origin_validation()
494 .build();
495
496 let signer = TokenSigner::new(signing_key, "test-issuer");
497 let claims = SessionClaims::builder("test-issuer", "test-subject", "test-audience")
498 .with_scope("read")
499 .with_origin("https://trusted.example.com")
500 .with_key_class(KeyClass::Secret)
501 .build();
502
503 let token = signer.sign(claims).unwrap();
504 let ctx = verifier
505 .verify(&token, Some("https://trusted.example.com"), None)
506 .await
507 .unwrap();
508 assert_eq!(ctx.subject, "test-subject");
509 }
510
511 #[tokio::test]
512 async fn test_invalid_signature_with_multiple_keys() {
513 let key1_signing = SigningKey::generate();
515
516 let key2_signing = SigningKey::generate();
517
518 let signer = TokenSigner::new(key1_signing, "test-issuer");
519
520 let verifier = MultiKeyVerifier::from_single_key(
522 key2_signing.verifying_key(),
523 "key-2",
524 "test-issuer",
525 "test-audience",
526 );
527
528 let claims = SessionClaims::builder("test-issuer", "test-subject", "test-audience")
529 .with_scope("read")
530 .with_key_class(KeyClass::Publishable)
531 .build();
532
533 let token = signer.sign(claims).unwrap();
534
535 let result = verifier.verify(&token, None, None).await;
537 assert!(matches!(result, Err(VerifyError::InvalidSignature)));
538 }
539
540 #[tokio::test]
541 async fn test_jwks_key_rotation_grace_period() {
542 use crate::token::{Jwk, Jwks};
543 use base64::Engine;
544
545 let old_signing_key = SigningKey::generate();
547 let old_verifying_key = old_signing_key.verifying_key();
548 let old_kid = old_verifying_key.key_id();
549 let old_signer = TokenSigner::new(old_signing_key, "test-issuer");
550
551 let new_signing_key = SigningKey::generate();
553 let new_verifying_key = new_signing_key.verifying_key();
554 let new_kid = new_verifying_key.key_id();
555 let new_signer = TokenSigner::new(new_signing_key, "test-issuer");
556
557 let old_key_b64 =
559 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(old_verifying_key.to_bytes());
560 let new_key_b64 =
561 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(new_verifying_key.to_bytes());
562
563 let jwks = Jwks {
564 keys: vec![
565 Jwk {
566 kty: "OKP".to_string(),
567 use_: Some("sig".to_string()),
568 kid: old_kid,
569 x: old_key_b64,
570 },
571 Jwk {
572 kty: "OKP".to_string(),
573 use_: Some("sig".to_string()),
574 kid: new_kid,
575 x: new_key_b64,
576 },
577 ],
578 };
579
580 let verifier =
582 crate::verifier::AsyncVerifier::with_jwks(jwks, "test-issuer", "test-audience");
583
584 let old_claims = SessionClaims::builder("test-issuer", "subject-old", "test-audience")
586 .with_scope("read")
587 .with_key_class(KeyClass::Secret)
588 .build();
589 let old_token = old_signer.sign(old_claims).unwrap();
590
591 let ctx = verifier.verify(&old_token, None, None).await.unwrap();
593 assert_eq!(ctx.subject, "subject-old");
594
595 let new_claims = SessionClaims::builder("test-issuer", "subject-new", "test-audience")
597 .with_scope("read")
598 .with_key_class(KeyClass::Secret)
599 .build();
600 let new_token = new_signer.sign(new_claims).unwrap();
601
602 let ctx = verifier.verify(&new_token, None, None).await.unwrap();
604 assert_eq!(ctx.subject, "subject-new");
605 }
606
607 #[tokio::test]
608 async fn test_jwks_key_not_found() {
609 use crate::token::{Jwk, Jwks};
610 use base64::Engine;
611
612 let signing_key = SigningKey::generate();
614 let _verifying_key = signing_key.verifying_key();
615 let signer = TokenSigner::new(signing_key, "test-issuer");
616
617 let different_key = SigningKey::generate();
619 let different_verifying_key = different_key.verifying_key();
620 let different_key_b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD
621 .encode(different_verifying_key.to_bytes());
622
623 let jwks = Jwks {
624 keys: vec![Jwk {
625 kty: "OKP".to_string(),
626 use_: Some("sig".to_string()),
627 kid: "different-key".to_string(),
628 x: different_key_b64,
629 }],
630 };
631
632 let verifier =
633 crate::verifier::AsyncVerifier::with_jwks(jwks, "test-issuer", "test-audience");
634
635 let claims = SessionClaims::builder("test-issuer", "test-subject", "test-audience")
636 .with_scope("read")
637 .with_key_class(KeyClass::Secret)
638 .build();
639 let token = signer.sign(claims).unwrap();
640
641 let result = verifier.verify(&token, None, None).await;
643 assert!(matches!(result, Err(VerifyError::KeyNotFound(_))));
644 }
645
646 #[tokio::test]
647 async fn test_jwks_with_origin_validation() {
648 use crate::token::{Jwk, Jwks};
649 use base64::Engine;
650
651 let signing_key = SigningKey::generate();
652 let verifying_key = signing_key.verifying_key();
653 let kid = verifying_key.key_id();
654 let signer = TokenSigner::new(signing_key, "test-issuer");
655
656 let key_b64 =
657 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(verifying_key.to_bytes());
658
659 let jwks = Jwks {
660 keys: vec![Jwk {
661 kty: "OKP".to_string(),
662 use_: Some("sig".to_string()),
663 kid,
664 x: key_b64,
665 }],
666 };
667
668 let verifier =
670 crate::verifier::AsyncVerifier::with_jwks(jwks, "test-issuer", "test-audience")
671 .with_origin_validation();
672
673 let claims = SessionClaims::builder("test-issuer", "test-subject", "test-audience")
675 .with_scope("read")
676 .with_key_class(KeyClass::Secret)
677 .with_origin("https://trusted.example.com")
678 .build();
679 let token = signer.sign(claims).unwrap();
680
681 let ctx = verifier
683 .verify(&token, Some("https://trusted.example.com"), None)
684 .await
685 .unwrap();
686 assert_eq!(ctx.subject, "test-subject");
687
688 let result = verifier
690 .verify(&token, Some("https://evil.example.com"), None)
691 .await;
692 assert!(matches!(result, Err(VerifyError::OriginMismatch { .. })));
693 }
694}