1#![forbid(unsafe_code)]
2
3use serde::Serialize;
37use serde_json::Value;
38use std::fmt;
39
40#[derive(Clone, Serialize)]
42pub struct Jwks {
43 pub keys: Vec<AnyJwk>,
45}
46
47impl Jwks {
48 pub fn to_value(&self) -> Value {
50 serde_json::to_value(self).expect("serialize JWKS")
51 }
52}
53
54impl fmt::Display for Jwks {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 let s = serde_json::to_string(self).expect("serialize JWKS");
57 f.write_str(&s)
58 }
59}
60
61#[derive(Clone, Serialize)]
63pub struct RsaPublicJwk {
64 pub kty: &'static str,
65 #[serde(rename = "use")]
66 pub use_: &'static str,
67 pub alg: &'static str,
68 pub kid: String,
69 pub n: String,
70 pub e: String,
71}
72
73impl RsaPublicJwk {
74 pub fn kid(&self) -> &str {
76 &self.kid
77 }
78}
79
80#[derive(Clone, Serialize)]
82pub struct RsaPrivateJwk {
83 pub kty: &'static str,
84 #[serde(rename = "use")]
85 pub use_: &'static str,
86 pub alg: &'static str,
87 pub kid: String,
88 pub n: String,
89 pub e: String,
90 pub d: String,
91 pub p: String,
92 pub q: String,
93 pub dp: String,
94 pub dq: String,
95 #[serde(rename = "qi")]
96 pub qi: String,
97}
98
99impl RsaPrivateJwk {
100 pub fn kid(&self) -> &str {
102 &self.kid
103 }
104}
105
106impl fmt::Debug for RsaPrivateJwk {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 f.debug_struct("RsaPrivateJwk")
109 .field("kid", &self.kid)
110 .field("alg", &self.alg)
111 .finish_non_exhaustive()
112 }
113}
114
115#[derive(Clone, Serialize)]
117pub struct EcPublicJwk {
118 pub kty: &'static str,
119 #[serde(rename = "use")]
120 pub use_: &'static str,
121 pub alg: &'static str,
122 pub crv: &'static str,
123 pub kid: String,
124 pub x: String,
125 pub y: String,
126}
127
128impl EcPublicJwk {
129 pub fn kid(&self) -> &str {
131 &self.kid
132 }
133}
134
135#[derive(Clone, Serialize)]
137pub struct EcPrivateJwk {
138 pub kty: &'static str,
139 #[serde(rename = "use")]
140 pub use_: &'static str,
141 pub alg: &'static str,
142 pub crv: &'static str,
143 pub kid: String,
144 pub x: String,
145 pub y: String,
146 pub d: String,
147}
148
149impl EcPrivateJwk {
150 pub fn kid(&self) -> &str {
152 &self.kid
153 }
154}
155
156impl fmt::Debug for EcPrivateJwk {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 f.debug_struct("EcPrivateJwk")
159 .field("kid", &self.kid)
160 .field("alg", &self.alg)
161 .field("crv", &self.crv)
162 .finish_non_exhaustive()
163 }
164}
165
166#[derive(Clone, Serialize)]
168pub struct OkpPublicJwk {
169 pub kty: &'static str,
170 #[serde(rename = "use")]
171 pub use_: &'static str,
172 pub alg: &'static str,
173 pub crv: &'static str,
174 pub kid: String,
175 pub x: String,
176}
177
178impl OkpPublicJwk {
179 pub fn kid(&self) -> &str {
181 &self.kid
182 }
183}
184
185#[derive(Clone, Serialize)]
187pub struct OkpPrivateJwk {
188 pub kty: &'static str,
189 #[serde(rename = "use")]
190 pub use_: &'static str,
191 pub alg: &'static str,
192 pub crv: &'static str,
193 pub kid: String,
194 pub x: String,
195 pub d: String,
196}
197
198impl OkpPrivateJwk {
199 pub fn kid(&self) -> &str {
201 &self.kid
202 }
203}
204
205impl fmt::Debug for OkpPrivateJwk {
206 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207 f.debug_struct("OkpPrivateJwk")
208 .field("kid", &self.kid)
209 .field("alg", &self.alg)
210 .field("crv", &self.crv)
211 .finish_non_exhaustive()
212 }
213}
214
215#[derive(Clone, Serialize)]
217pub struct OctJwk {
218 pub kty: &'static str,
219 #[serde(rename = "use")]
220 pub use_: &'static str,
221 pub alg: &'static str,
222 pub kid: String,
223 pub k: String,
224}
225
226impl OctJwk {
227 pub fn kid(&self) -> &str {
229 &self.kid
230 }
231}
232
233impl fmt::Debug for OctJwk {
234 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235 f.debug_struct("OctJwk")
236 .field("kid", &self.kid)
237 .field("alg", &self.alg)
238 .finish_non_exhaustive()
239 }
240}
241
242#[derive(Clone, Serialize)]
244#[serde(untagged)]
245pub enum PublicJwk {
246 Rsa(RsaPublicJwk),
248 Ec(EcPublicJwk),
250 Okp(OkpPublicJwk),
252}
253
254impl PublicJwk {
255 pub fn kid(&self) -> &str {
257 match self {
258 PublicJwk::Rsa(jwk) => jwk.kid(),
259 PublicJwk::Ec(jwk) => jwk.kid(),
260 PublicJwk::Okp(jwk) => jwk.kid(),
261 }
262 }
263
264 pub fn to_value(&self) -> Value {
266 serde_json::to_value(self).expect("serialize JWK")
267 }
268}
269
270impl fmt::Display for PublicJwk {
271 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272 let s = serde_json::to_string(self).expect("serialize JWK");
273 f.write_str(&s)
274 }
275}
276
277#[derive(Clone, Serialize)]
279#[serde(untagged)]
280pub enum PrivateJwk {
281 Rsa(RsaPrivateJwk),
283 Ec(EcPrivateJwk),
285 Okp(OkpPrivateJwk),
287 Oct(OctJwk),
289}
290
291impl PrivateJwk {
292 pub fn kid(&self) -> &str {
294 match self {
295 PrivateJwk::Rsa(jwk) => jwk.kid(),
296 PrivateJwk::Ec(jwk) => jwk.kid(),
297 PrivateJwk::Okp(jwk) => jwk.kid(),
298 PrivateJwk::Oct(jwk) => jwk.kid(),
299 }
300 }
301
302 pub fn to_value(&self) -> Value {
304 serde_json::to_value(self).expect("serialize JWK")
305 }
306}
307
308impl fmt::Display for PrivateJwk {
309 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310 let s = serde_json::to_string(self).expect("serialize JWK");
311 f.write_str(&s)
312 }
313}
314
315impl fmt::Debug for PrivateJwk {
316 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317 match self {
318 PrivateJwk::Rsa(jwk) => jwk.fmt(f),
319 PrivateJwk::Ec(jwk) => jwk.fmt(f),
320 PrivateJwk::Okp(jwk) => jwk.fmt(f),
321 PrivateJwk::Oct(jwk) => jwk.fmt(f),
322 }
323 }
324}
325
326#[derive(Clone, Serialize)]
328#[serde(untagged)]
329pub enum AnyJwk {
330 Public(PublicJwk),
332 Private(PrivateJwk),
334}
335
336impl AnyJwk {
337 pub fn kid(&self) -> &str {
339 match self {
340 AnyJwk::Public(jwk) => jwk.kid(),
341 AnyJwk::Private(jwk) => jwk.kid(),
342 }
343 }
344
345 pub fn to_value(&self) -> Value {
347 serde_json::to_value(self).expect("serialize JWK")
348 }
349}
350
351impl fmt::Display for AnyJwk {
352 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
353 let s = serde_json::to_string(self).expect("serialize JWK");
354 f.write_str(&s)
355 }
356}
357
358impl From<PublicJwk> for AnyJwk {
359 fn from(value: PublicJwk) -> Self {
360 AnyJwk::Public(value)
361 }
362}
363
364impl From<PrivateJwk> for AnyJwk {
365 fn from(value: PrivateJwk) -> Self {
366 AnyJwk::Private(value)
367 }
368}
369
370#[cfg(test)]
371mod tests {
372 use super::*;
373
374 fn sample_rsa_public(kid: &str, n: &str) -> PublicJwk {
375 PublicJwk::Rsa(RsaPublicJwk {
376 kty: "RSA",
377 use_: "sig",
378 alg: "RS256",
379 kid: kid.to_string(),
380 n: n.to_string(),
381 e: "AQAB".to_string(),
382 })
383 }
384
385 fn sample_oct_private(kid: &str, k: &str) -> PrivateJwk {
386 PrivateJwk::Oct(OctJwk {
387 kty: "oct",
388 use_: "sig",
389 alg: "HS256",
390 kid: kid.to_string(),
391 k: k.to_string(),
392 })
393 }
394
395 fn sample_rsa_private(kid: &str, d: &str) -> RsaPrivateJwk {
396 RsaPrivateJwk {
397 kty: "RSA",
398 use_: "sig",
399 alg: "RS256",
400 kid: kid.to_string(),
401 n: "n".to_string(),
402 e: "e".to_string(),
403 d: d.to_string(),
404 p: "p".to_string(),
405 q: "q".to_string(),
406 dp: "dp".to_string(),
407 dq: "dq".to_string(),
408 qi: "qi".to_string(),
409 }
410 }
411
412 fn sample_ec_private(kid: &str, d: &str) -> EcPrivateJwk {
413 EcPrivateJwk {
414 kty: "EC",
415 use_: "sig",
416 alg: "ES256",
417 crv: "P-256",
418 kid: kid.to_string(),
419 x: "x".to_string(),
420 y: "y".to_string(),
421 d: d.to_string(),
422 }
423 }
424
425 fn sample_okp_public(kid: &str, x: &str) -> OkpPublicJwk {
426 OkpPublicJwk {
427 kty: "OKP",
428 use_: "sig",
429 alg: "EdDSA",
430 crv: "Ed25519",
431 kid: kid.to_string(),
432 x: x.to_string(),
433 }
434 }
435
436 fn sample_okp_private(kid: &str, d: &str) -> OkpPrivateJwk {
437 OkpPrivateJwk {
438 kty: "OKP",
439 use_: "sig",
440 alg: "EdDSA",
441 crv: "Ed25519",
442 kid: kid.to_string(),
443 x: "x".to_string(),
444 d: d.to_string(),
445 }
446 }
447
448 #[test]
449 fn display_outputs_json() {
450 let jwk = sample_rsa_public("kid-1", "n1");
451 let json = jwk.to_string();
452 let v: Value = serde_json::from_str(&json).expect("valid JSON");
453 assert_eq!(v["kty"], "RSA");
454
455 let private = sample_oct_private("kid-2", "secret");
456 let json = private.to_string();
457 let v: Value = serde_json::from_str(&json).expect("valid JSON");
458 assert_eq!(v["kty"], "oct");
459 }
460
461 #[test]
462 fn debug_omits_private_material() {
463 let secret = "super-secret-value";
464 let jwk = sample_oct_private("kid-3", secret);
465 let dbg = format!("{:?}", jwk);
466 assert!(dbg.contains("OctJwk"));
467 assert!(!dbg.contains(secret));
468 }
469
470 #[test]
471 fn any_jwk_from_conversions_work() {
472 let pub_jwk = sample_rsa_public("kid-4", "n4");
473 let any_pub = AnyJwk::from(pub_jwk.clone());
474 assert_eq!(any_pub.kid(), pub_jwk.kid());
475
476 let priv_jwk = sample_oct_private("kid-5", "k5");
477 let any_priv = AnyJwk::from(priv_jwk.clone());
478 assert_eq!(any_priv.kid(), priv_jwk.kid());
479 }
480
481 #[test]
482 fn kid_helpers_return_expected_kid() {
483 let rsa = sample_rsa_private("kid-rsa", "d-rsa");
484 assert_eq!(rsa.kid(), "kid-rsa");
485
486 let ec = sample_ec_private("kid-ec", "d-ec");
487 assert_eq!(ec.kid(), "kid-ec");
488
489 let okp_pub = sample_okp_public("kid-okp", "x-okp");
490 assert_eq!(okp_pub.kid(), "kid-okp");
491
492 let okp_priv = sample_okp_private("kid-okp-priv", "d-okp");
493 assert_eq!(okp_priv.kid(), "kid-okp-priv");
494
495 let oct = OctJwk {
496 kty: "oct",
497 use_: "sig",
498 alg: "HS256",
499 kid: "kid-oct".to_string(),
500 k: "secret".to_string(),
501 };
502 assert_eq!(oct.kid(), "kid-oct");
503 }
504
505 #[test]
506 fn enum_kid_and_to_value_cover_all_variants() {
507 let okp_pub = PublicJwk::Okp(sample_okp_public("kid-okp", "x-okp"));
508 assert_eq!(okp_pub.kid(), "kid-okp");
509 assert_eq!(okp_pub.to_value()["kty"], "OKP");
510
511 let okp_priv = PrivateJwk::Okp(sample_okp_private("kid-okp-priv", "d-okp"));
512 assert_eq!(okp_priv.kid(), "kid-okp-priv");
513 assert_eq!(okp_priv.to_value()["kty"], "OKP");
514
515 let oct = PrivateJwk::Oct(OctJwk {
516 kty: "oct",
517 use_: "sig",
518 alg: "HS256",
519 kid: "kid-oct".to_string(),
520 k: "secret".to_string(),
521 });
522 assert_eq!(oct.kid(), "kid-oct");
523 assert_eq!(oct.to_value()["kty"], "oct");
524 }
525
526 #[test]
527 fn enum_kid_covers_all_variants() {
528 let rsa_pub = PublicJwk::Rsa(RsaPublicJwk {
529 kty: "RSA",
530 use_: "sig",
531 alg: "RS256",
532 kid: "kid-rsa".to_string(),
533 n: "n".to_string(),
534 e: "e".to_string(),
535 });
536 assert_eq!(rsa_pub.kid(), "kid-rsa");
537
538 let ec_pub = PublicJwk::Ec(EcPublicJwk {
539 kty: "EC",
540 use_: "sig",
541 alg: "ES256",
542 crv: "P-256",
543 kid: "kid-ec".to_string(),
544 x: "x".to_string(),
545 y: "y".to_string(),
546 });
547 assert_eq!(ec_pub.kid(), "kid-ec");
548
549 let okp_pub = PublicJwk::Okp(sample_okp_public("kid-okp", "x-okp"));
550 assert_eq!(okp_pub.kid(), "kid-okp");
551
552 let rsa_priv = PrivateJwk::Rsa(sample_rsa_private("kid-rsa-priv", "d"));
553 assert_eq!(rsa_priv.kid(), "kid-rsa-priv");
554
555 let ec_priv = PrivateJwk::Ec(sample_ec_private("kid-ec-priv", "d"));
556 assert_eq!(ec_priv.kid(), "kid-ec-priv");
557
558 let okp_priv = PrivateJwk::Okp(sample_okp_private("kid-okp-priv", "d"));
559 assert_eq!(okp_priv.kid(), "kid-okp-priv");
560
561 let oct = PrivateJwk::Oct(OctJwk {
562 kty: "oct",
563 use_: "sig",
564 alg: "HS256",
565 kid: "kid-oct".to_string(),
566 k: "secret".to_string(),
567 });
568 assert_eq!(oct.kid(), "kid-oct");
569 }
570
571 #[test]
572 fn any_jwk_to_value_round_trips() {
573 let pub_any = AnyJwk::from(sample_rsa_public("kid-a", "n"));
574 assert_eq!(pub_any.to_value()["kid"], "kid-a");
575
576 let priv_any = AnyJwk::from(sample_oct_private("kid-b", "secret"));
577 assert_eq!(priv_any.to_value()["kid"], "kid-b");
578 }
579
580 #[test]
581 fn any_jwk_display_round_trips() {
582 let pub_any = AnyJwk::from(sample_rsa_public("kid-a", "n"));
583 let json = pub_any.to_string();
584 let v: Value = serde_json::from_str(&json).expect("valid JSON");
585 assert_eq!(v["kid"], "kid-a");
586
587 let priv_any = AnyJwk::from(sample_oct_private("kid-b", "secret"));
588 let json = priv_any.to_string();
589 let v: Value = serde_json::from_str(&json).expect("valid JSON");
590 assert_eq!(v["kid"], "kid-b");
591 }
592
593 #[test]
594 fn private_jwk_enum_debug_uses_inner_formatters() {
595 let rsa = PrivateJwk::Rsa(sample_rsa_private("kid-rsa", "secret"));
596 let dbg = format!("{:?}", rsa);
597 assert!(dbg.contains("RsaPrivateJwk"));
598
599 let ec = PrivateJwk::Ec(sample_ec_private("kid-ec", "secret"));
600 let dbg = format!("{:?}", ec);
601 assert!(dbg.contains("EcPrivateJwk"));
602
603 let okp = PrivateJwk::Okp(sample_okp_private("kid-okp", "secret"));
604 let dbg = format!("{:?}", okp);
605 assert!(dbg.contains("OkpPrivateJwk"));
606
607 let oct = PrivateJwk::Oct(OctJwk {
608 kty: "oct",
609 use_: "sig",
610 alg: "HS256",
611 kid: "kid-oct".to_string(),
612 k: "secret".to_string(),
613 });
614 let dbg = format!("{:?}", oct);
615 assert!(dbg.contains("OctJwk"));
616 }
617
618 #[test]
619 fn private_jwk_debug_omits_private_material() {
620 let secret = "super-secret";
621
622 let rsa = sample_rsa_private("kid-rsa", secret);
623 let dbg = format!("{:?}", rsa);
624 assert!(dbg.contains("RsaPrivateJwk"));
625 assert!(!dbg.contains(secret));
626
627 let ec = sample_ec_private("kid-ec", secret);
628 let dbg = format!("{:?}", ec);
629 assert!(dbg.contains("EcPrivateJwk"));
630 assert!(!dbg.contains(secret));
631
632 let okp = sample_okp_private("kid-okp", secret);
633 let dbg = format!("{:?}", okp);
634 assert!(dbg.contains("OkpPrivateJwk"));
635 assert!(!dbg.contains(secret));
636 }
637
638 proptest::proptest! {
639 #[test]
640 fn to_string_and_to_value_are_idempotent(
641 kid in "[a-zA-Z0-9._-]{1,24}",
642 n in "[A-Za-z0-9+/]{1,64}",
643 e in "[A-Za-z0-9+/]{1,64}",
644 ) {
645 let pub_jwk = PublicJwk::Rsa(RsaPublicJwk {
646 kty: "RSA",
647 use_: "sig",
648 alg: "RS256",
649 kid: kid.to_string(),
650 n: n.to_string(),
651 e: e.to_string(),
652 });
653
654 let pub_value = pub_jwk.to_value();
655 let pub_text = pub_jwk.to_string();
656 let pub_round_trip: Value = serde_json::from_str(&pub_text).expect("pub JWK should be JSON");
657
658 assert_eq!(pub_value["kid"], pub_round_trip["kid"]);
659 assert_eq!(pub_value["n"], pub_round_trip["n"]);
660
661 let private = PrivateJwk::Oct(OctJwk {
662 kty: "oct",
663 use_: "sig",
664 alg: "HS256",
665 kid: kid.to_string(),
666 k: n,
667 });
668
669 let private_value = private.to_value();
670 let private_text = private.to_string();
671 let private_round_trip: Value =
672 serde_json::from_str(&private_text).expect("private JWK should be JSON");
673
674 assert_eq!(private_value["kid"], private_round_trip["kid"]);
675 assert_eq!(private_value["k"], private_round_trip["k"]);
676 }
677 }
678}