1use std::{
18 collections::HashMap,
19 sync::{
20 Arc,
21 atomic::{AtomicI64, Ordering},
22 },
23};
24
25use arc_swap::ArcSwap;
26use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD as B64URL};
27use ring::signature;
28use serde_json::Value;
29use tokio::sync::mpsc;
30
31use crate::AuthError;
32
33#[derive(Debug, Clone, PartialEq, Eq)]
35enum JwkKey {
36 Rsa { n: Vec<u8>, e: Vec<u8> },
40 EcP256 { point: Vec<u8> },
43}
44
45#[derive(Debug, Clone, Default, PartialEq, Eq)]
51pub struct Jwks {
52 keys: HashMap<String, JwkKey>,
53}
54
55impl Jwks {
56 #[must_use]
59 #[cfg_attr(test, mutants::skip)]
61 pub fn empty() -> Self {
62 Self {
63 keys: HashMap::new(),
64 }
65 }
66
67 #[must_use]
69 pub fn is_empty(&self) -> bool {
70 self.keys.is_empty()
71 }
72
73 #[must_use]
75 pub fn len(&self) -> usize {
76 self.keys.len()
77 }
78
79 #[tracing::instrument(level = "info", skip_all, fields(ignore_key_use = ignore_key_use), err)]
101 pub fn from_json(s: &str, ignore_key_use: bool) -> Result<Self, AuthError> {
102 let doc: Value = serde_json::from_str(s).map_err(|_| AuthError::MalformedMessage)?;
103 let arr = doc
104 .get("keys")
105 .and_then(Value::as_array)
106 .ok_or(AuthError::MalformedMessage)?;
107 let mut keys = HashMap::new();
108 for jwk in arr {
109 let Some((kid, key)) = parse_one_jwk(jwk, ignore_key_use) else {
110 continue;
111 };
112 keys.insert(kid, key);
113 }
114 Ok(Self { keys })
115 }
116
117 #[must_use]
121 pub fn contains_kid(&self, kid: &str) -> bool {
122 self.keys.contains_key(kid)
123 }
124
125 #[tracing::instrument(
143 level = "debug",
144 skip_all,
145 fields(alg = %alg, kid = kid.unwrap_or("<none>")),
146 err
147 )]
148 pub fn verify(
149 &self,
150 kid: Option<&str>,
151 alg: &str,
152 signing_input: &[u8],
153 signature: &[u8],
154 ) -> Result<(), AuthError> {
155 let key = self.select_key(kid)?;
156 match (alg, key) {
157 ("RS256", JwkKey::Rsa { n, e }) => {
158 let pk = signature::RsaPublicKeyComponents { n, e };
159 pk.verify(
160 &signature::RSA_PKCS1_2048_8192_SHA256,
161 signing_input,
162 signature,
163 )
164 .map_err(|_| AuthError::InvalidToken)
165 }
166 ("ES256", JwkKey::EcP256 { point }) => {
167 let pk =
168 signature::UnparsedPublicKey::new(&signature::ECDSA_P256_SHA256_FIXED, point);
169 pk.verify(signing_input, signature)
170 .map_err(|_| AuthError::InvalidToken)
171 }
172 _ => Err(AuthError::InvalidToken),
174 }
175 }
176
177 fn select_key(&self, kid: Option<&str>) -> Result<&JwkKey, AuthError> {
180 kid.map_or_else(
181 || {
182 if self.keys.len() == 1 {
183 self.keys.values().next().ok_or(AuthError::InvalidToken)
184 } else {
185 Err(AuthError::InvalidToken)
186 }
187 },
188 |kid| self.keys.get(kid).ok_or(AuthError::InvalidToken),
189 )
190 }
191}
192
193fn parse_one_jwk(jwk: &Value, ignore_key_use: bool) -> Option<(String, JwkKey)> {
200 if !ignore_key_use && jwk.get("use").and_then(Value::as_str) == Some("enc") {
201 return None;
202 }
203 let kid = jwk
204 .get("kid")
205 .and_then(Value::as_str)
206 .unwrap_or("")
207 .to_owned();
208 match jwk.get("kty").and_then(Value::as_str)? {
209 "RSA" => {
210 let n = b64url_field(jwk, "n")?;
211 let e = b64url_field(jwk, "e")?;
212 Some((kid, JwkKey::Rsa { n, e }))
213 }
214 "EC" => {
215 if jwk.get("crv").and_then(Value::as_str)? != "P-256" {
216 return None;
217 }
218 let x = left_pad_32(&b64url_field(jwk, "x")?)?;
221 let y = left_pad_32(&b64url_field(jwk, "y")?)?;
222 let mut point = Vec::with_capacity(65);
223 point.push(0x04);
224 point.extend_from_slice(&x);
225 point.extend_from_slice(&y);
226 Some((kid, JwkKey::EcP256 { point }))
227 }
228 _ => None,
229 }
230}
231
232fn b64url_field(jwk: &Value, key: &str) -> Option<Vec<u8>> {
234 let s = jwk.get(key).and_then(Value::as_str)?;
235 B64URL.decode(s).ok()
236}
237
238fn left_pad_32(bytes: &[u8]) -> Option<[u8; 32]> {
243 let start = 32usize.checked_sub(bytes.len())?;
244 let mut out = [0u8; 32];
245 out.get_mut(start..)?.copy_from_slice(bytes);
246 Some(out)
247}
248
249#[derive(Debug, Clone)]
263pub struct JwksHandle {
264 keys: Arc<ArcSwap<Jwks>>,
265 last_successful_fetch_ms: Arc<AtomicI64>,
269 signal_tx: Option<mpsc::Sender<()>>,
275}
276
277impl JwksHandle {
278 #[must_use]
285 pub fn new(jwks: Jwks) -> Self {
286 Self {
287 keys: Arc::new(ArcSwap::from_pointee(jwks)),
288 last_successful_fetch_ms: Arc::new(AtomicI64::new(0)),
289 signal_tx: None,
290 }
291 }
292
293 #[must_use]
300 pub fn new_with_refresher_handles(
301 jwks: Jwks,
302 last_successful_fetch_ms: Arc<AtomicI64>,
303 signal_tx: mpsc::Sender<()>,
304 ) -> Self {
305 Self {
306 keys: Arc::new(ArcSwap::from_pointee(jwks)),
307 last_successful_fetch_ms,
308 signal_tx: Some(signal_tx),
309 }
310 }
311
312 pub fn store(&self, jwks: Jwks) {
318 self.keys.store(Arc::new(jwks));
319 }
320
321 #[must_use]
323 pub fn load(&self) -> Arc<Jwks> {
324 self.keys.load_full()
325 }
326
327 #[must_use]
333 pub fn last_successful_fetch_ms(&self) -> i64 {
334 self.last_successful_fetch_ms.load(Ordering::Relaxed)
335 }
336
337 pub fn signal_refresh(&self) {
345 if let Some(tx) = &self.signal_tx {
346 let _ = tx.try_send(());
347 }
348 }
349}
350
351impl Default for JwksHandle {
352 fn default() -> Self {
353 Self::new(Jwks::empty())
354 }
355}
356
357#[cfg(test)]
360pub use tests::{mint_es256, mint_rs256, mint_rs256_with_header};
361
362#[cfg(test)]
363mod tests {
364 use assert2::check;
365 use ring::{
366 rand::SystemRandom,
367 signature::{EcdsaKeyPair, KeyPair, RsaKeyPair},
368 };
369
370 use super::*;
371
372 const RSA_PKCS8_B64: &str = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC1Ekoc++7sSsH55QXBCq/aj71helk6ZCTkzYxfLRZXbox0FcV7vOkLNodetJLY7nAUekZLltQ7Q6FJ42geqGV+vgttF63Ue9OP24mPmn/OiFqVYhBaJDRI5BMBLCqZbUfpNBDh7ZOCczwlX8Z5FQS0QJBA4F26H9AKzFRvofwHFk1wxqiGdgwDyClgi+eDnhEGGhBEHuTl1edvTRif88rLDfPHKG1TRqKC6LMXCZQdNy7lrDEGPKHqfW4mb2mq7Vj6h2Jjv+1SpsSxdqX8Tsua4/LrAKvFIXfoZAnjzhACbhXqf1DdSdInZ0i1adY8JpgJQ+WtJ0i9aIOnnmDYwgMvAgMBAAECggEAHqBqUr62Kdd3Odpn/7/cAL7hTHSSVRMNPnoZ7RtGNSGothXcolJQpKnjebxXPkQORxhrfWuUmDWXOVUyjkTzbd2dNyWTLGaJYULD4LtENN3RXIUKuQR4p3+US1V6Gxtl12cMF/rEQYNWQAgUHPTWJ9rny2Fn2Qx6dukauwsOAvCU47fL873sm06SYgPJsLm7MKVeifl8dDudgpURxeC9z37cm9kjjE6n6aiBTNAuBEkMaAbcfgJ0RZfzaMo7IpsOeyOwp932JDlKROpQWKA+lz08YzhkU81qHJYOS/js2F0jxzFz31D9IN+OLu7vRCANFLJl/qnin1JEgVPh7gxSKQKBgQDfrQEsutvH1746ytfE+4jUXyv7Fuaz9MML8uaJbC4hMFdCJuMuLBY07bDE23+4byuWY7JHrgsLRaZ+qpNGWs3LH2x6xsHiK8Ivpuy8TVUJ6hgkPK1cr8yUJxaDcyV8tJAZ+mFmyyWx7wUdlgJFCa2MQF1HnrlBKZvSLWV4CjctZQKBgQDPPR2wLwyk6JlyapsVnCpNBGcXqbJxPh1TM7uPqlODxTzegUK+TMJDZ840u2aBNXf2D5WIJMl+/ohYefOOqK9z2OJUGObnJMgGusH04rdbBoDCdBwfwjiluU7vxbuQKBu8JNXzeb7HJhmgxtXWdJuFYcYbmGu8leFvmUxZTPRfAwKBgQCm6Gpf/m/SiGMjbAnmq+xGzV38V/J/hr2lRPRSx68EhRYX/vy3j55ikJu/yitcbViROIPoiS8kkizTiGWtskSuthw04ev74btd46n0OaCjbVPmdoDHEUgPpbtfC6WFkReWyweztRPD2yBuG2pGKhqe9cilkQOcZHgqNkXpdXYHIQKBgQCO0BQkdNfm0O/l3DdRdhPkjVMqCGSTC3YT/0OS5pK07PhccYF4ONdqsh91UWt7QUiRBf5LGubMoEV/i1LfjbmTQPP/dkWxJjS+Bndg9dfbX6jd2DwFWsfE1OXj8ESoPCuYxV23cr+Y59WjaUK1jhgam9106N3d0P/Q8zidFZ4V1wKBgQDFvIqMLnpaInWhb7kP+X6o0tPQSg+6odMWPnjhwnpSIiUjPUTZV4ijc/d1tPsUemFQxDe+ZreQXDMVGcAVldFnoEMyL8iAtMAHtsSmq2E80RNZfc6nUgy5esQ9rJeX2pH9aZCVvKv6iVTeUtAxS+ltjmEG9BSEI2WQI1WDzPbKiA==";
378
379 fn rsa_pkcs8() -> Vec<u8> {
380 use base64::engine::general_purpose::STANDARD;
381 STANDARD.decode(RSA_PKCS8_B64).unwrap()
382 }
383
384 fn split_pkcs1_public(der: &[u8]) -> (Vec<u8>, Vec<u8>) {
390 let mut p = 0usize;
392 assert2::assert!(der[p] == 0x30);
393 p += 1;
394 let (_seq_len, adv) = read_der_len(&der[p..]);
395 p += adv;
396 let n = read_der_integer(der, &mut p);
397 let e = read_der_integer(der, &mut p);
398 (n, e)
399 }
400
401 fn read_der_len(b: &[u8]) -> (usize, usize) {
402 if b[0] & 0x80 == 0 {
403 (b[0] as usize, 1)
404 } else {
405 let nbytes = (b[0] & 0x7f) as usize;
406 let mut len = 0usize;
407 for i in 0..nbytes {
408 len = (len << 8) | b[1 + i] as usize;
409 }
410 (len, 1 + nbytes)
411 }
412 }
413
414 fn read_der_integer(der: &[u8], p: &mut usize) -> Vec<u8> {
415 assert2::assert!(der[*p] == 0x02);
416 *p += 1;
417 let (len, adv) = read_der_len(&der[*p..]);
418 *p += adv;
419 let mut bytes = der[*p..*p + len].to_vec();
420 *p += len;
421 if bytes.first() == Some(&0) {
424 bytes.remove(0);
425 }
426 bytes
427 }
428
429 fn b64(b: &[u8]) -> String {
430 B64URL.encode(b)
431 }
432
433 fn rs256(kid: &str, claims: &str) -> (String, String) {
438 let der = rsa_pkcs8();
439 let kp = RsaKeyPair::from_pkcs8(&der).unwrap();
440 let header = format!("{{\"alg\":\"RS256\",\"kid\":\"{kid}\"}}");
441 let signing_input = format!("{}.{}", b64(header.as_bytes()), b64(claims.as_bytes()));
442 let mut sig = vec![0u8; kp.public().modulus_len()];
443 kp.sign(
444 &signature::RSA_PKCS1_SHA256,
445 &SystemRandom::new(),
446 signing_input.as_bytes(),
447 &mut sig,
448 )
449 .unwrap();
450 let token = format!("{signing_input}.{}", b64(&sig));
451
452 let pkcs1 = kp.public().as_ref();
453 let (n, e) = split_pkcs1_public(pkcs1);
454 let jwks = format!(
455 "{{\"keys\":[{{\"kty\":\"RSA\",\"kid\":\"{kid}\",\"n\":\"{}\",\"e\":\"{}\"}}]}}",
456 b64(&n),
457 b64(&e),
458 );
459 (token, jwks)
460 }
461
462 fn es256_key(kid: &str) -> (EcdsaKeyPair, String) {
464 let rng = SystemRandom::new();
465 let pkcs8 = EcdsaKeyPair::generate_pkcs8(&signature::ECDSA_P256_SHA256_FIXED_SIGNING, &rng)
466 .unwrap();
467 let kp = EcdsaKeyPair::from_pkcs8(
468 &signature::ECDSA_P256_SHA256_FIXED_SIGNING,
469 pkcs8.as_ref(),
470 &rng,
471 )
472 .unwrap();
473 let point = kp.public_key().as_ref(); let x = &point[1..33];
475 let y = &point[33..65];
476 let jwks = format!(
477 "{{\"keys\":[{{\"kty\":\"EC\",\"crv\":\"P-256\",\"kid\":\"{kid}\",\"x\":\"{}\",\"y\":\"{}\"}}]}}",
478 b64(x),
479 b64(y),
480 );
481 (kp, jwks)
482 }
483
484 fn es256_token(kp: &EcdsaKeyPair, kid: &str, claims: &str) -> String {
485 let header = format!("{{\"alg\":\"ES256\",\"kid\":\"{kid}\"}}");
486 let signing_input = format!("{}.{}", b64(header.as_bytes()), b64(claims.as_bytes()));
487 let sig = kp
488 .sign(&SystemRandom::new(), signing_input.as_bytes())
489 .unwrap();
490 format!("{signing_input}.{}", b64(sig.as_ref()))
491 }
492
493 fn parts(token: &str) -> (Option<String>, String, Vec<u8>, Vec<u8>) {
496 let segs: Vec<&str> = token.split('.').collect();
497 let header: Value = serde_json::from_slice(&B64URL.decode(segs[0]).unwrap()).unwrap();
498 let kid = header.get("kid").and_then(Value::as_str).map(str::to_owned);
499 let alg = header
500 .get("alg")
501 .and_then(Value::as_str)
502 .unwrap()
503 .to_owned();
504 let signing_input = format!("{}.{}", segs[0], segs[1]).into_bytes();
505 let sig = B64URL.decode(segs[2]).unwrap();
506 (kid, alg, signing_input, sig)
507 }
508
509 #[test]
510 fn empty_jwks_has_no_keys() {
511 let jwks = Jwks::empty();
512 check!((jwks.is_empty(), jwks.len(), jwks.contains_kid("k1")) == (true, 0, false));
513 }
514
515 #[test]
516 fn jwks_with_key_is_not_empty() {
517 let (_token, jwks_json) = rs256("k1", "{\"sub\":\"alice\",\"exp\":9999999999}");
518 let jwks = Jwks::from_json(&jwks_json, false).expect("parse jwks");
519 check!((jwks.is_empty(), jwks.len(), jwks.contains_kid("k1")) == (false, 1, true));
520 }
521
522 #[test]
523 fn left_pad_32_accepts_short_and_exact_coordinates_only() {
524 assert2::assert!(left_pad_32(&[0xAA; 33]).is_none());
525
526 let short = left_pad_32(&[0x01, 0x02]).expect("short coordinate padded");
527 assert2::assert!(&short[..30] == &[0u8; 30][..]);
528 assert2::assert!(&short[30..] == &[0x01, 0x02][..]);
529
530 let exact = [0x7Fu8; 32];
531 assert2::assert!(left_pad_32(&exact).expect("exact coordinate") == exact);
532 }
533
534 #[test]
535 fn parses_mixed_rsa_and_ec_set() {
536 let (_kp, ec_jwks) = es256_key("ec1");
537 let (_t, rsa_jwks) = rs256("rsa1", "{\"sub\":\"a\",\"exp\":9999999999}");
538 let ec: Value = serde_json::from_str(&ec_jwks).unwrap();
540 let rsa: Value = serde_json::from_str(&rsa_jwks).unwrap();
541 let mut keys = ec["keys"].as_array().unwrap().clone();
542 keys.extend(rsa["keys"].as_array().unwrap().clone());
543 let merged = serde_json::json!({ "keys": keys }).to_string();
544 let jwks = Jwks::from_json(&merged, false).unwrap();
545 assert2::assert!(jwks.len() == 2);
546 }
547
548 #[test]
549 fn skips_unsupported_and_enc_keys() {
550 let jwks = Jwks::from_json(
551 r#"{"keys":[
552 {"kty":"oct","k":"AAAA","kid":"sym"},
553 {"kty":"EC","crv":"P-384","kid":"big"},
554 {"kty":"RSA","use":"enc","kid":"enc1","n":"AQAB","e":"AQAB"}
555 ]}"#,
556 false,
557 )
558 .unwrap();
559 assert2::assert!(jwks.is_empty());
560 }
561
562 #[test]
563 fn rejects_non_json_and_missing_keys_array() {
564 for (_name, input) in [("invalid JSON", "not json"), ("missing keys array", "{}")] {
565 assert2::assert!(Jwks::from_json(input, false) == Err(AuthError::MalformedMessage));
566 }
567 }
568
569 #[test]
570 fn verifies_valid_rs256() {
571 let (token, jwks_json) = rs256("rsa1", "{\"sub\":\"admin\",\"exp\":9999999999}");
572 let jwks = Jwks::from_json(&jwks_json, false).unwrap();
573 let (kid, alg, si, sig) = parts(&token);
574 assert2::assert!(jwks.verify(kid.as_deref(), &alg, &si, &sig).is_ok());
575 }
576
577 #[test]
578 fn verifies_valid_es256() {
579 let (kp, jwks_json) = es256_key("ec1");
580 let token = es256_token(&kp, "ec1", "{\"sub\":\"admin\",\"exp\":9999999999}");
581 let jwks = Jwks::from_json(&jwks_json, false).unwrap();
582 let (kid, alg, si, sig) = parts(&token);
583 assert2::assert!(jwks.verify(kid.as_deref(), &alg, &si, &sig).is_ok());
584 }
585
586 #[test]
587 fn rejects_tampered_rs256_signature() {
588 let (token, jwks_json) = rs256("rsa1", "{\"sub\":\"admin\",\"exp\":9999999999}");
589 let jwks = Jwks::from_json(&jwks_json, false).unwrap();
590 let (kid, alg, si, mut sig) = parts(&token);
591 sig[0] ^= 0xff;
592 assert2::assert!(
593 jwks.verify(kid.as_deref(), &alg, &si, &sig) == Err(AuthError::InvalidToken)
594 );
595 }
596
597 #[test]
598 fn rejects_unknown_kid() {
599 let (token, jwks_json) = rs256("rsa1", "{\"sub\":\"a\",\"exp\":9999999999}");
600 let jwks = Jwks::from_json(&jwks_json, false).unwrap();
601 let (_kid, alg, si, sig) = parts(&token);
602 assert2::assert!(
603 jwks.verify(Some("other"), &alg, &si, &sig) == Err(AuthError::InvalidToken)
604 );
605 }
606
607 #[test]
608 fn rejects_missing_kid_when_set_has_multiple_keys() {
609 let (kp, ec_jwks) = es256_key("ec1");
610 let (_t, rsa_jwks) = rs256("rsa1", "{\"sub\":\"a\",\"exp\":9999999999}");
611 let ec: Value = serde_json::from_str(&ec_jwks).unwrap();
612 let rsa: Value = serde_json::from_str(&rsa_jwks).unwrap();
613 let mut keys = ec["keys"].as_array().unwrap().clone();
614 keys.extend(rsa["keys"].as_array().unwrap().clone());
615 let merged = serde_json::json!({ "keys": keys }).to_string();
616 let jwks = Jwks::from_json(&merged, false).unwrap();
617 let header = b64(b"{\"alg\":\"ES256\"}");
619 let payload = b64(b"{\"sub\":\"a\",\"exp\":9999999999}");
620 let signing_input = format!("{header}.{payload}");
621 let sig = kp
622 .sign(&SystemRandom::new(), signing_input.as_bytes())
623 .unwrap();
624 assert2::assert!(
625 jwks.verify(None, "ES256", signing_input.as_bytes(), sig.as_ref())
626 == Err(AuthError::InvalidToken)
627 );
628 }
629
630 #[test]
631 fn accepts_missing_kid_when_set_has_single_key() {
632 let (token, jwks_json) =
633 mint_rs256_with_header("{\"alg\":\"RS256\"}", "{\"sub\":\"a\",\"exp\":9999999999}");
634 let jwks = Jwks::from_json(&jwks_json, false).unwrap();
635 let (kid, alg, si, sig) = parts(&token);
636 assert2::assert!(kid.is_none());
637 jwks.verify(kid.as_deref(), &alg, &si, &sig)
638 .expect("single-key JWKS should verify a token without kid");
639 }
640
641 #[test]
642 fn rejects_es256_signed_by_wrong_key() {
643 let (kp_a, _jwks_a) = es256_key("ec1");
644 let (_kp_b, jwks_b) = es256_key("ec1"); let token = es256_token(&kp_a, "ec1", "{\"sub\":\"a\",\"exp\":9999999999}");
646 let jwks = Jwks::from_json(&jwks_b, false).unwrap();
647 let (kid, alg, si, sig) = parts(&token);
648 assert2::assert!(
649 jwks.verify(kid.as_deref(), &alg, &si, &sig) == Err(AuthError::InvalidToken)
650 );
651 }
652
653 #[test]
654 fn rejects_alg_key_type_mismatch() {
655 let (token, jwks_json) = rs256("rsa1", "{\"sub\":\"a\",\"exp\":9999999999}");
657 let jwks = Jwks::from_json(&jwks_json, false).unwrap();
658 let (kid, _alg, si, sig) = parts(&token);
659 assert2::assert!(
660 jwks.verify(kid.as_deref(), "ES256", &si, &sig) == Err(AuthError::InvalidToken)
661 );
662 }
663
664 #[test]
665 fn handle_store_and_load_round_trips() {
666 let h = JwksHandle::default();
667 assert2::assert!(h.load().is_empty());
668 let (_t, jwks_json) = rs256("rsa1", "{\"sub\":\"a\",\"exp\":9999999999}");
669 h.store(Jwks::from_json(&jwks_json, false).unwrap());
670 assert2::assert!(h.load().len() == 1);
671 }
672
673 #[test]
676 fn parse_jwks_filters_use_enc_by_default() {
677 let json = r#"{
678 "keys": [
679 {"kty":"RSA","kid":"sig-key","use":"sig","n":"AQAB","e":"AQAB"},
680 {"kty":"RSA","kid":"enc-key","use":"enc","n":"AQAB","e":"AQAB"}
681 ]
682 }"#;
683 let jwks = Jwks::from_json(json, false).expect("parses");
684 assert2::assert!(
685 (jwks.contains_kid("sig-key"), jwks.contains_kid("enc-key")) == (true, false)
686 );
687 }
688
689 #[test]
690 fn parse_jwks_keeps_use_enc_when_ignore_key_use_true() {
691 let json = r#"{
692 "keys": [
693 {"kty":"RSA","kid":"sig-key","use":"sig","n":"AQAB","e":"AQAB"},
694 {"kty":"RSA","kid":"enc-key","use":"enc","n":"AQAB","e":"AQAB"}
695 ]
696 }"#;
697 let jwks = Jwks::from_json(json, true).expect("parses");
698 assert2::assert!(
699 (jwks.contains_kid("sig-key"), jwks.contains_kid("enc-key")) == (true, true)
700 );
701 }
702
703 #[test]
704 fn parse_jwks_keeps_keys_with_absent_use_field_regardless() {
705 let json = r#"{
706 "keys": [
707 {"kty":"RSA","kid":"no-use","n":"AQAB","e":"AQAB"}
708 ]
709 }"#;
710 assert2::assert!(Jwks::from_json(json, false).unwrap().contains_kid("no-use"));
711 assert2::assert!(Jwks::from_json(json, true).unwrap().contains_kid("no-use"));
712 }
713
714 #[test]
715 fn default_handle_signal_refresh_is_silent_noop() {
716 let h = JwksHandle::default();
720 h.signal_refresh();
721 assert2::assert!(h.last_successful_fetch_ms() == 0);
722 }
723
724 #[tokio::test(flavor = "current_thread")]
725 async fn paired_handle_signal_refresh_delivers_to_receiver() {
726 let (tx, mut rx) = mpsc::channel::<()>(1);
727 let ts = Arc::new(AtomicI64::new(0));
728 let h = JwksHandle::new_with_refresher_handles(Jwks::empty(), ts, tx);
729 h.signal_refresh();
730 assert2::assert!(rx.try_recv().is_ok());
731 }
732
733 pub fn mint_rs256(kid: &str, claims: &str) -> (String, String) {
735 rs256(kid, claims)
736 }
737
738 pub fn mint_rs256_with_header(header_json: &str, claims: &str) -> (String, String) {
743 let der = rsa_pkcs8();
744 let kp = RsaKeyPair::from_pkcs8(&der).unwrap();
745 let signing_input = format!("{}.{}", b64(header_json.as_bytes()), b64(claims.as_bytes()));
746 let mut sig = vec![0u8; kp.public().modulus_len()];
747 kp.sign(
748 &signature::RSA_PKCS1_SHA256,
749 &SystemRandom::new(),
750 signing_input.as_bytes(),
751 &mut sig,
752 )
753 .unwrap();
754 let token = format!("{signing_input}.{}", b64(&sig));
755
756 let pkcs1 = kp.public().as_ref();
759 let (n, e) = split_pkcs1_public(pkcs1);
760 let jwks = format!(
761 "{{\"keys\":[{{\"kty\":\"RSA\",\"kid\":\"k1\",\"n\":\"{}\",\"e\":\"{}\"}}]}}",
762 b64(&n),
763 b64(&e),
764 );
765 (token, jwks)
766 }
767
768 pub fn mint_es256(kid: &str, claims: &str) -> (String, String) {
773 let (kp, jwks) = es256_key(kid);
774 let token = es256_token(&kp, kid, claims);
775 (token, jwks)
776 }
777}