1use std::borrow::{Borrow as _, Cow};
8use std::fmt;
9
10use base64ct::{Base64UrlUnpadded, Encoding as _};
11use hmac::Mac as _;
12use rsa::{
13 pkcs8::{
14 DecodePrivateKey as _, DecodePublicKey as _, EncodePrivateKey as _, EncodePublicKey as _,
15 },
16 signature::{SignatureEncoding as _, Signer as _, Verifier as _},
17 traits::PublicKeyParts as _,
18};
19use serde::{
20 Deserialize, Deserializer, Serialize,
21 de::{DeserializeOwned, Visitor},
22};
23
24use crate::id;
25use crate::misc::time_ext;
26use crate::phcrypto;
27
28#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
32#[serde(transparent)]
33pub struct JWT {
34 inner: String,
35}
36
37#[derive(Debug, Clone, Default, serde::Serialize)]
39#[serde(transparent)]
40pub struct Claims {
41 inner: serde_json::Map<String, serde_json::Value>,
42}
43
44impl Claims {
45 pub fn new() -> Self {
46 Default::default()
47 }
48
49 pub fn check<'s, V: Deserialize<'s>>(
53 mut self,
54 name: &'static str,
55 expectation: impl FnOnce(&'static str, Option<V>) -> Result<(), Error>,
56 ) -> Result<Self, Error> {
57 let value: Option<V> = self
58 .inner
59 .remove(name)
60 .map(V::deserialize)
61 .transpose()
62 .map_err(|err| Error::DeserializingClaim {
63 claim_name: name,
64 source: err,
65 })?;
66
67 expectation(name, value)?;
68
69 Ok(self)
70 }
71
72 pub fn check_present_and<'s, V: Deserialize<'s>>(
75 self,
76 name: &'static str,
77 expectation: impl FnOnce(&'static str, V) -> Result<(), Error>,
78 ) -> Result<Self, Error> {
79 self.check(
80 name,
81 |claim_name: &'static str, v: Option<V>| -> Result<(), Error> {
82 if v.is_none() {
83 return Err(Error::MissingClaim(claim_name));
84 }
85 expectation(name, v.unwrap())
86 },
87 )
88 }
89
90 pub fn check_no(self, name: &'static str) -> Result<Self, Error> {
92 if self.inner.contains_key(name) {
93 return Err(Error::UnexpectedClaim(name));
94 }
95
96 Ok(self)
97 }
98
99 pub fn extract<V: DeserializeOwned>(&mut self, name: &'static str) -> Result<Option<V>, Error> {
101 let Some(json_value) = self.inner.remove(name) else {
102 return Ok(None);
103 };
104
105 let deserialized_value =
106 V::deserialize(json_value).map_err(|err| Error::DeserializingClaim {
107 claim_name: name,
108 source: err,
109 })?;
110
111 Ok(Some(deserialized_value))
112 }
113
114 pub fn ignore(mut self, name: &'static str) -> Self {
116 self.inner.remove(name);
117 self
118 }
119
120 pub fn check_iss(
122 self,
123 expectation: impl FnOnce(&'static str, Option<String>) -> Result<(), Error>,
124 ) -> Result<Self, Error> {
125 self.check("iss", expectation)
126 }
127
128 pub fn check_sub(
130 self,
131 expectation: impl FnOnce(&'static str, Option<String>) -> Result<(), Error>,
132 ) -> Result<Self, Error> {
133 self.check("sub", expectation)
134 }
135
136 pub fn default_check_timestamps(self) -> Result<Self, Error> {
139 let now = NumericDate::now();
140
141 self.check(
142 "iat",
143 |_claim_name: &'static str, _iat: Option<NumericDate>| -> Result<(), Error> {
144 Ok(())
146 },
147 )?
148 .check(
149 "exp",
150 |_claim_name: &'static str, exp: Option<NumericDate>| -> Result<(), Error> {
151 if let Some(exp) = exp
153 && exp < now
154 {
155 return Err(Error::Expired { when: exp });
156 }
157
158 Ok(())
159 },
160 )?
161 .check(
162 "nbf",
163 |_claim_name: &'static str, nbf: Option<NumericDate>| -> Result<(), Error> {
164 if let Some(nbf) = nbf
166 && now < nbf
167 {
168 return Err(Error::NotYetValid { valid_from: nbf });
169 }
170
171 Ok(())
172 },
173 )
174 }
175
176 pub fn default_check_common_claims(self) -> Result<Self, Error> {
179 self.default_check_timestamps()?
180 .check_no("iss")?
181 .check_no("sub")
182 }
183
184 pub fn visit_custom<C: DeserializeOwned, R>(
195 self,
196 visitor: impl FnOnce(C) -> R,
197 ) -> Result<R, Error> {
198 let self_ = self.default_check_common_claims()?;
200
201 let jso = serde_json::Value::Object(self_.inner);
202 let claims: C = C::deserialize(&jso).map_err(|err| {
203 let jso_str = serde_json::to_string_pretty(&jso).unwrap();
204
205 if let Err(better_err) = serde_json::from_str::<C>(&jso_str) {
206 return Error::DeserializingClaims {
207 source: better_err,
208 claims: jso_str,
209 };
210 }
211
212 log::error!("something fishy is going on here with this faulty json");
213 Error::DeserializingClaims {
214 source: err,
215 claims: "".to_string(),
216 }
217 })?;
218
219 Ok(visitor(claims))
220 }
221
222 pub fn into_custom<C: DeserializeOwned>(self) -> Result<C, Error> {
225 self.visit_custom(|c| c)
226 }
227
228 pub fn from_custom<C: Serialize>(claims: C) -> Result<Self, Error> {
230 let json_value = serde_json::to_value(claims).map_err(Error::SerializingClaims)?;
231
232 Ok(Self {
233 inner: match json_value {
234 serde_json::Value::Object(inner) => inner,
235 serde_json::Value::Null => {
236 return Err(Error::ClaimsDontSerializeToMapButNull {
237 claims_type: std::any::type_name::<C>(),
238 });
239 }
240 _ => {
241 return Err(Error::ClaimsDontSerializeToMap {
242 claims_type: std::any::type_name::<C>(),
243 });
244 }
245 },
246 })
247 }
248
249 pub fn claim<V: Serialize>(mut self, name: &'static str, value: V) -> Result<Self, Error> {
252 let old_value = self.inner.insert(
253 name.to_string(),
254 serde_json::to_value(value).map_err(|err| Error::SerializingClaim {
255 claim_name: name,
256 source: err,
257 })?,
258 );
259
260 if old_value.is_some() {
261 return Err(Error::ClaimAlreadyPresent(name));
262 }
263
264 Ok(self)
265 }
266
267 pub fn iat_now(self) -> Result<Self, Error> {
269 self.claim("iat", NumericDate::now())
270 }
271
272 pub fn exp_after(self, duration: std::time::Duration) -> Result<Self, Error> {
274 self.claim("exp", NumericDate::now().add_clamp(duration.as_secs()))
275 }
276
277 pub fn nbf(self) -> Result<Self, Error> {
279 self.claim("nbf", NumericDate::now().sub_clamp(30))
280 }
281
282 pub fn sign<SK: SigningKey>(&self, sk: &SK) -> Result<JWT, Error> {
284 JWT::create(&self.inner, sk)
285 }
286}
287
288#[derive(Serialize, Default, Clone, Copy, Eq, PartialEq, Debug, PartialOrd, Ord)]
322#[serde(transparent)]
323pub struct NumericDate {
324 timestamp: u64,
325}
326
327pub(crate) const MAX_TIMESTAMP_SECS: u64 = 253_402_300_799;
329
330impl NumericDate {
331 pub fn new_clamp(timestamp: u64) -> Self {
334 Self {
335 timestamp: timestamp.min(MAX_TIMESTAMP_SECS),
336 }
337 }
338
339 pub fn add_clamp(self, secs: u64) -> Self {
341 Self::new_clamp(self.timestamp.saturating_add(secs))
342 }
343
344 pub fn sub_clamp(self, secs: u64) -> Self {
346 Self::new_clamp(self.timestamp.saturating_sub(secs))
347 }
348
349 pub fn now() -> Self {
351 std::time::SystemTime::now()
352 .try_into()
353 .expect("system clock not between 1970 and 9999")
354 }
355
356 pub fn timestamp(&self) -> u64 {
358 self.timestamp
359 }
360
361 pub fn date(&self) -> String {
363 let mut datetime = humantime::format_rfc3339(self.into()).to_string();
366
367 let Some(idx) = datetime.find('T') else {
368 panic!("bug: expected date returned by humantime to contain a 'T'");
369 };
370
371 datetime.truncate(idx);
372
373 datetime
374 }
375}
376
377#[derive(Debug, thiserror::Error)]
381#[error("value is out of NumericDate range (before 1970, or after year 9999)")]
382pub struct OutOfRange;
383
384impl TryFrom<std::time::SystemTime> for NumericDate {
385 type Error = OutOfRange;
386
387 fn try_from(st: std::time::SystemTime) -> Result<Self, Self::Error> {
390 let secs = st
391 .duration_since(std::time::UNIX_EPOCH)
392 .map_err(|_| OutOfRange)?
393 .as_secs();
394
395 if secs > MAX_TIMESTAMP_SECS {
396 return Err(OutOfRange);
397 }
398
399 Ok(Self { timestamp: secs })
400 }
401}
402
403impl From<&NumericDate> for std::time::SystemTime {
404 fn from(nd: &NumericDate) -> Self {
405 std::time::UNIX_EPOCH + std::time::Duration::from_secs(nd.timestamp)
408 }
409}
410
411impl fmt::Display for NumericDate {
412 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
413 write!(f, "{}", time_ext::format_time(self.into()))
414 }
415}
416
417impl<'de> Deserialize<'de> for NumericDate {
418 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
419 d.deserialize_u64(NumericDateVisitor {})
420 }
421}
422
423struct NumericDateVisitor {}
425
426impl Visitor<'_> for NumericDateVisitor {
427 type Value = NumericDate;
428
429 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
430 write!(
431 f,
432 "a non-negative number no greater than {MAX_TIMESTAMP_SECS}"
433 )
434 }
435
436 fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<Self::Value, E> {
437 if v > MAX_TIMESTAMP_SECS {
440 return Err(E::invalid_value(serde::de::Unexpected::Unsigned(v), &self));
441 }
442 Ok(NumericDate::new_clamp(v))
443 }
444
445 fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<Self::Value, E> {
446 if v < 0 {
447 return Err(E::invalid_value(serde::de::Unexpected::Signed(v), &self));
448 }
449
450 self.visit_u64(v as u64)
451 }
452
453 fn visit_f64<E: serde::de::Error>(self, v: f64) -> Result<Self::Value, E> {
454 if v < 0.0 {
455 return Err(E::invalid_value(serde::de::Unexpected::Float(v), &self));
456 }
457
458 self.visit_u64(v as u64)
459 }
460
461 }
463
464impl From<String> for JWT {
465 fn from(s: String) -> Self {
466 Self { inner: s }
467 }
468}
469
470impl From<JWT> for String {
471 fn from(jwt: JWT) -> String {
472 jwt.inner
473 }
474}
475
476impl JWT {
477 pub fn create<C: Serialize, SK: SigningKey>(claims: &C, key: &SK) -> Result<JWT, Error> {
481 let to_be_signed: String = format!(
482 "{}.{}",
483 Base64UrlUnpadded::encode_string(
484 &serde_json::to_vec(&serde_json::json!({
485 "alg": SK::ALG,
486 }))
487 .map_err(Error::SerializingHeader)?
488 ),
489 &Base64UrlUnpadded::encode_string(
490 &serde_json::to_vec(claims).map_err(Error::SerializingClaims)?
491 )
492 );
493 Ok(JWT::from(format!(
494 "{}.{}",
495 to_be_signed,
496 Base64UrlUnpadded::encode_string(
497 key.sign(to_be_signed.as_bytes())
498 .map_err(Error::Signing)?
499 .as_ref()
500 )
501 )))
502 }
503
504 pub fn open<VK: VerifyingKey>(&self, key: &VK) -> Result<Claims, Error> {
510 let s = &self.inner;
511
512 let last_dot_pos: usize = s.rfind('.').ok_or(Error::MissingDot)?;
513 let signed: &str = &s[..last_dot_pos];
514 let first_dot_pos: usize = signed.find('.').ok_or(Error::MissingDot)?;
515
516 let header_vec: Vec<u8> =
518 Base64UrlUnpadded::decode_vec(&s[..first_dot_pos]).map_err(Error::InvalidBase64)?;
519
520 let header: Header =
521 serde_json::from_slice(&header_vec).map_err(Error::DeserializingHeader)?;
522
523 VK::check_alg(&header.alg)?;
524
525 let signature: Vec<u8> =
526 Base64UrlUnpadded::decode_vec(&s[last_dot_pos + 1..]).map_err(Error::InvalidBase64)?;
527
528 let claims_vec: Vec<u8> = Base64UrlUnpadded::decode_vec(&signed[first_dot_pos + 1..])
530 .map_err(Error::InvalidBase64)?;
531
532 let mut d = serde_json::Deserializer::from_slice(&claims_vec);
533
534 let claims = Claims {
535 inner: serde_json::Map::<String, serde_json::Value>::deserialize(&mut d)
536 .map_err(Error::ClaimsNotJsonMap)?,
537 };
538
539 if !key.is_valid_signature(signed.as_bytes(), signature) {
541 return Err(Error::InvalidSignature {
542 key: key.describe(),
543 claims,
544 });
545 }
546
547 Ok(claims)
548 }
549
550 pub fn as_str(&self) -> &str {
551 &self.inner
552 }
553
554 pub fn sha256(&self) -> sha2::Sha256 {
555 use sha2::Digest as _;
556 sha2::Sha256::new().chain_update(&self.inner)
557 }
558
559 pub fn id(&self) -> id::Id {
560 phcrypto::jwt_id(self)
561 }
562}
563
564impl fmt::Display for JWT {
565 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
566 write!(f, "{}", self.inner)
567 }
568}
569
570#[derive(thiserror::Error, Debug)]
571pub enum Error {
572 #[error("failed to serialize jwt header")]
573 SerializingHeader(#[source] serde_json::Error),
574
575 #[error("invalid jwt header")]
576 DeserializingHeader(#[source] serde_json::Error),
577
578 #[error("failed to serialize jwt claims")]
579 SerializingClaims(#[source] serde_json::Error),
580
581 #[error("failed to serialize claim {claim_name}")]
582 SerializingClaim {
583 claim_name: &'static str,
584 source: serde_json::Error,
585 },
586
587 #[error("claim {0} already present")]
588 ClaimAlreadyPresent(&'static str),
589
590 #[error("claims are not a valid json map")]
591 ClaimsNotJsonMap(#[source] serde_json::Error),
592
593 #[error("the given custom claims (of type {claims_type}) do not serialize to a json map")]
594 ClaimsDontSerializeToMap { claims_type: &'static str },
595
596 #[error(
597 "the given custom claims (of type {claims_type}) do not serialize to a json map, but to null. Hint: 'type Unit;' -> 'type Unit {{}}'"
598 )]
599 ClaimsDontSerializeToMapButNull { claims_type: &'static str },
600
601 #[error("invalid jwt claims: {source} in {claims}")]
602 DeserializingClaims {
603 source: serde_json::Error,
604 claims: String,
605 },
606
607 #[error("failed to deserialize claim {claim_name}")]
608 DeserializingClaim {
609 claim_name: &'static str,
610 source: serde_json::Error,
611 },
612
613 #[error("jwt contains unexpected/unhandled claim `{0}`")]
614 UnexpectedClaim(&'static str),
615
616 #[error("jwt is missing the claim `{0}'")]
617 MissingClaim(&'static str),
618
619 #[error("the claim `{claim_name}` is invalid")]
620 InvalidClaim {
621 claim_name: &'static str,
622 source: anyhow::Error,
623 },
624
625 #[error("expired at {when}")]
626 Expired { when: NumericDate },
627
628 #[error("only valid after {valid_from}")]
629 NotYetValid { valid_from: NumericDate },
630
631 #[error("signing jwt failed")]
632 Signing(#[source] anyhow::Error),
633
634 #[error("missing dot (.) in jwt (there should be two dots)")]
635 MissingDot,
636
637 #[error("jwt contains invalid unpadded urlsafe base64")]
638 InvalidBase64(#[source] base64ct::Error),
639
640 #[error("jwt signature is not valid (for this key, {key})")]
641 InvalidSignature { key: String, claims: Claims },
642
643 #[error("unexpected algorithm; got {got}, but expected {expected}")]
644 UnexpectedAlgorithm { got: String, expected: &'static str },
645}
646
647pub fn sign<SK: SigningKey>(claims: &impl Serialize, key: &SK) -> anyhow::Result<String> {
650 Ok(JWT::create(claims, key)?.inner)
651}
652
653pub fn get_current_timestamp() -> u64 {
656 std::time::SystemTime::now()
657 .duration_since(std::time::UNIX_EPOCH)
658 .expect("system clock reports a time before the Unix epoch")
659 .as_secs()
660}
661
662#[derive(Serialize, Deserialize, Debug)]
664#[serde(deny_unknown_fields)]
665struct Header<'a> {
666 #[serde(
667 rename = "typ",
668 skip_serializing, default )]
671 _typ: HeaderType,
672
673 #[serde(borrow)]
674 alg: Cow<'a, str>,
675 }
677
678#[derive(Default, Debug)]
680struct HeaderType {}
681
682impl<'de> Deserialize<'de> for HeaderType {
683 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
684 d.deserialize_str(HeaderType {})
685 }
686}
687
688impl Visitor<'_> for HeaderType {
689 type Value = Self;
690
691 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
692 write!(f, "the string \"JWT\" as \"typ\"")
693 }
694
695 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
696 if "JWT".eq_ignore_ascii_case(v) {
697 return Ok(self);
698 }
699
700 Err(E::invalid_value(serde::de::Unexpected::Str(v), &self))
701 }
702}
703
704pub trait SigningKey: Key {
706 type Signature: AsRef<[u8]>;
708
709 fn sign(&self, s: &[u8]) -> anyhow::Result<Self::Signature>;
711
712 fn jwk(&self) -> serde_json::Value;
714}
715
716pub trait VerifyingKey: Key {
718 fn is_valid_signature(&self, message: &[u8], signature: Vec<u8>) -> bool;
720
721 fn describe(&self) -> String;
723}
724
725pub trait Key {
727 const ALG: &'static str;
729
730 fn check_alg(alg: &str) -> Result<(), Error> {
734 if alg == Self::ALG {
735 return Ok(());
736 }
737 Err(Error::UnexpectedAlgorithm {
738 got: alg.to_string(),
739 expected: Self::ALG,
740 })
741 }
742}
743
744pub struct IgnoreSignature;
748
749impl Key for IgnoreSignature {
750 const ALG: &'static str = "WARNING! This should never appear in the 'alg' field of a JWT.";
751
752 fn check_alg(_alg: &str) -> Result<(), Error> {
753 Ok(())
754 }
755}
756
757impl VerifyingKey for IgnoreSignature {
758 fn is_valid_signature(&self, _message: &[u8], _signature: Vec<u8>) -> bool {
759 true
760 }
761
762 fn describe(&self) -> String {
763 "n/a".into()
764 }
765}
766
767impl SigningKey for ed25519_dalek::SigningKey {
803 type Signature = [u8; 64]; fn sign(&self, s: &[u8]) -> anyhow::Result<[u8; 64]> {
806 Ok(ed25519_dalek::Signer::sign(self, s).to_bytes())
807 }
808
809 fn jwk(&self) -> serde_json::Value {
810 serde_json::json!({
811 "kty": "OKP", "alg": Self::ALG,
813 "crv": "Ed25519",
814 "x": Base64UrlUnpadded::encode_string(AsRef::<ed25519_dalek::VerifyingKey>::as_ref(self).as_bytes()),
815 "use": "sig",
817 })
818 }
819}
820
821impl Key for ed25519_dalek::SigningKey {
822 const ALG: &'static str = "EdDSA";
823}
824
825impl Key for ed25519_dalek::VerifyingKey {
826 const ALG: &'static str = "EdDSA";
827}
828
829impl VerifyingKey for ed25519_dalek::VerifyingKey {
830 fn is_valid_signature(&self, message: &[u8], signature: Vec<u8>) -> bool {
831 if let Ok(signature) = ed25519_dalek::Signature::from_slice(&signature) {
832 return ed25519_dalek::Verifier::verify(self, message, &signature).is_ok();
833 }
834 false
835 }
836
837 fn describe(&self) -> String {
838 base16ct::lower::encode_string(self.as_bytes().as_slice())
839 }
840}
841
842#[derive(
844 serde::Serialize, serde::Deserialize, Clone, Debug, Eq, PartialEq, zeroize::ZeroizeOnDrop,
845)]
846#[serde(transparent)]
847pub struct HS256(#[serde(with = "serde_bytes")] pub Vec<u8>);
848
849impl SigningKey for HS256 {
861 type Signature = sha2::digest::generic_array::GenericArray<
862 u8,
863 <sha2::Sha256 as sha2::digest::OutputSizeUser>::OutputSize,
864 >;
865
866 fn sign(&self, s: &[u8]) -> anyhow::Result<Self::Signature> {
867 let mut mac = hmac::Hmac::<sha2::Sha256>::new_from_slice(&self.0)?;
868 mac.update(s);
869 Ok(mac.finalize().into_bytes())
870 }
871
872 fn jwk(&self) -> serde_json::Value {
873 panic!("HS256 has no public key to describe using JWK");
874 }
875}
876
877impl VerifyingKey for HS256 {
886 fn is_valid_signature(&self, message: &[u8], signature: Vec<u8>) -> bool {
887 let mut mac = hmac::Hmac::<sha2::Sha256>::new_from_slice(&self.0)
888 .expect("expect a sha256 mac to accept a key of any size");
889 mac.update(message);
890 mac.verify_slice(&signature).is_ok()
891 }
892
893 fn describe(&self) -> String {
894 base16ct::lower::encode_string(&self.0)
895 }
896}
897
898impl Key for HS256 {
899 const ALG: &'static str = "HS256";
900}
901
902#[derive(Clone, Debug)]
908pub struct RS256Vk(rsa::pkcs1v15::VerifyingKey<sha2::Sha256>);
909
910impl RS256Vk {
911 pub fn new(pk: rsa::RsaPublicKey) -> Self {
912 Self(rsa::pkcs1v15::VerifyingKey::<sha2::Sha256>::new(pk))
914 }
915
916 pub fn from_public_key_pem(pem: &str) -> anyhow::Result<Self> {
917 Ok(Self(
918 rsa::pkcs1v15::VerifyingKey::<sha2::Sha256>::from_public_key_pem(pem)?,
919 ))
920 }
921
922 pub fn to_public_key_pem(&self) -> anyhow::Result<String> {
923 Ok(self.0.to_public_key_pem(Default::default())?)
924 }
925
926 pub fn as_rsa_pk(&self) -> &rsa::RsaPublicKey {
928 AsRef::<rsa::RsaPublicKey>::as_ref(&self.0)
929 }
930}
931
932impl PartialEq for RS256Vk {
936 fn eq(&self, other: &Self) -> bool {
937 self.as_rsa_pk() == other.as_rsa_pk()
939 }
940}
941
942impl Eq for RS256Vk {}
944
945impl Key for RS256Vk {
946 const ALG: &'static str = "RS256";
947}
948
949impl VerifyingKey for RS256Vk {
950 fn is_valid_signature(&self, message: &[u8], signature: Vec<u8>) -> bool {
951 let signature: rsa::pkcs1v15::Signature = match signature.as_slice().try_into() {
952 Ok(signature) => signature,
953 Err(_) => return false,
954 };
955
956 self.0.verify(message, &signature).is_ok()
957 }
958
959 fn describe(&self) -> String {
960 format!("{self:?}")
961 }
962}
963
964#[derive(Clone, Debug)]
966pub struct RS256Sk(rsa::pkcs1v15::SigningKey<sha2::Sha256>);
967
968impl PartialEq for RS256Sk {
969 fn eq(&self, other: &Self) -> bool {
970 self.as_rsa_priv() == other.as_rsa_priv()
971 }
972}
973
974impl Eq for RS256Sk {}
975
976impl Key for RS256Sk {
977 const ALG: &'static str = RS256Vk::ALG;
978}
979
980impl SigningKey for RS256Sk {
981 type Signature = Box<[u8]>;
982
983 fn sign(&self, s: &[u8]) -> anyhow::Result<Self::Signature> {
984 Ok(self.0.sign(s).to_bytes())
985 }
986
987 fn jwk(&self) -> serde_json::Value {
988 let rsa_pub: &rsa::RsaPublicKey = self.as_rsa_pub();
989
990 serde_json::json!({
991 "kty": "RSA",
992 "alg": Self::ALG,
993 "mod": Base64UrlUnpadded::encode_string(&rsa_pub.n().to_bytes_be()),
994 "exp": Base64UrlUnpadded::encode_string(&rsa_pub.e().to_bytes_be()),
995 })
996 }
997}
998
999impl RS256Sk {
1000 pub fn new(pk: rsa::RsaPrivateKey) -> Self {
1001 Self(rsa::pkcs1v15::SigningKey::<sha2::Sha256>::new(pk))
1002 }
1003
1004 pub fn random(bit_size: usize) -> anyhow::Result<Self> {
1005 Ok(Self::new(rsa::RsaPrivateKey::new(
1006 &mut rsa::rand_core::OsRng,
1007 bit_size,
1008 )?))
1009 }
1010
1011 pub fn from_pkcs8_pem(pem: &str) -> anyhow::Result<Self> {
1012 Ok(Self(
1013 rsa::pkcs1v15::SigningKey::<sha2::Sha256>::from_pkcs8_pem(pem)?,
1014 ))
1015 }
1016
1017 pub fn to_pkcs8_pem(&self) -> anyhow::Result<zeroize::Zeroizing<String>> {
1018 Ok(self.0.to_pkcs8_pem(Default::default())?)
1019 }
1020
1021 pub fn as_rsa_priv(&self) -> &rsa::RsaPrivateKey {
1022 AsRef::<rsa::RsaPrivateKey>::as_ref(&self.0)
1023 }
1024
1025 pub fn as_rsa_pub(&self) -> &rsa::RsaPublicKey {
1026 AsRef::<rsa::RsaPublicKey>::as_ref(self.as_rsa_priv())
1027 }
1028}
1029
1030pub mod expecting {
1032 use super::*;
1033
1034 pub fn exactly<T>(
1036 what: &T,
1037 ) -> impl (FnOnce(&'static str, Option<T::Owned>) -> Result<(), Error>) + use<'_, T>
1038 where
1039 T: std::fmt::Debug + PartialEq + ToOwned + ?Sized,
1040 {
1041 move |claim_name: &'static str, val_maybe: Option<T::Owned>| {
1042 if let Some(val) = val_maybe {
1043 if *what == *val.borrow() {
1044 return Ok(());
1045 }
1046 return Err(Error::InvalidClaim {
1047 claim_name,
1048 source: anyhow::anyhow!("expected {:?}; got {:?}", what, val.borrow()),
1049 });
1050 }
1051 Err(Error::MissingClaim(claim_name))
1052 }
1053 }
1054}
1055
1056#[cfg(test)]
1057mod tests {
1058 use super::*;
1059
1060 #[test]
1061 fn test_jwt() {
1062 let jwt: JWT = serde_json::from_str("\"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk\"").unwrap();
1063
1064 let key = HS256(
1065 base64ct::Base64UrlUnpadded::decode_vec("AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow").unwrap(),
1066 );
1067
1068 let claims = jwt.open(&key).unwrap();
1069
1070 assert!(
1071 claims
1072 .clone()
1073 .into_custom::<serde_json::Value>()
1074 .unwrap_err()
1075 .to_string()
1076 .starts_with("expired at 2011-03-22T18:43:00Z (")
1077 );
1078
1079 assert_eq!(
1080 &claims
1081 .clone()
1082 .ignore("exp")
1083 .into_custom::<serde_json::Value>()
1084 .unwrap_err()
1085 .to_string(),
1086 "jwt contains unexpected/unhandled claim `iss`"
1087 );
1088
1089 #[derive(Deserialize, PartialEq, Eq, Debug)]
1090 #[serde(deny_unknown_fields)]
1091 struct Custom {
1092 #[serde(rename = "http://example.com/is_root")]
1093 is_root: bool,
1094 }
1095
1096 assert_eq!(
1097 claims
1098 .clone()
1099 .ignore("exp")
1100 .check_iss(
1101 |_claim_name: &'static str, iss: Option<String>| -> Result<(), Error> {
1102 assert_eq!(iss, Some("joe".to_string()));
1103 Ok(())
1104 }
1105 )
1106 .unwrap()
1107 .into_custom::<Custom>()
1108 .unwrap(),
1109 Custom { is_root: true }
1110 );
1111 }
1112
1113 #[test]
1114 fn test_header() {
1115 assert_eq!(
1117 serde_json::from_str::<Header>(r#"{}"#)
1118 .unwrap_err()
1119 .to_string(),
1120 "missing field `alg` at line 1 column 2".to_string()
1121 );
1122
1123 assert_eq!(
1125 serde_json::from_str::<Header>(r#"{"typ": "not JWT", "alg": ""}"#)
1126 .unwrap_err()
1127 .to_string(),
1128 "invalid value: string \"not JWT\", expected the string \"JWT\" as \"typ\" at line 1 column 17".to_string()
1129 );
1130
1131 assert_eq!(
1132 serde_json::from_str::<Header>(r#"{"typ": 12,"alg":""}"#)
1133 .unwrap_err()
1134 .to_string(),
1135 "invalid type: integer `12`, expected the string \"JWT\" as \"typ\" at line 1 column 10".to_string()
1136 );
1137
1138 assert!(serde_json::from_str::<Header>(r#"{"typ": "jWT","alg":""}"#).is_ok());
1140
1141 let header_a: Header = serde_json::from_str(r#"{"alg":"borrowed"}"#).unwrap();
1143 let header_b: Header = serde_json::from_str(r#"{"alg":"owned\u0020"}"#).unwrap();
1144
1145 assert!(matches!(header_a.alg, Cow::Borrowed(_)));
1146 assert!(matches!(header_b.alg, Cow::Owned(_)));
1147
1148 assert_eq!(
1150 serde_json::from_str::<Header>(r#"{"alg":"", "unknown_field": ""}"#)
1151 .unwrap_err()
1152 .to_string(),
1153 "unknown field `unknown_field`, expected `typ` or `alg` at line 1 column 26"
1154 .to_string()
1155 );
1156 }
1157
1158 #[test]
1159 fn test_numericdate() {
1160 assert!(NumericDate::deserialize(serde_json::json!(0u64)).is_ok());
1161 assert!(NumericDate::deserialize(serde_json::json!(0f64)).is_ok());
1162 assert!(NumericDate::deserialize(serde_json::json!(0f32)).is_ok());
1163 assert!(NumericDate::deserialize(serde_json::json!(i64::MIN)).is_err());
1164 assert!(NumericDate::deserialize(serde_json::json!(f32::MIN)).is_err());
1165 assert!(NumericDate::deserialize(serde_json::json!(f64::MIN)).is_err());
1166 assert_eq!(
1167 NumericDate::deserialize(serde_json::json!(1.9))
1168 .unwrap()
1169 .timestamp,
1170 1
1171 );
1172
1173 assert!(NumericDate::deserialize(serde_json::json!(MAX_TIMESTAMP_SECS)).is_ok());
1176 assert!(NumericDate::deserialize(serde_json::json!(MAX_TIMESTAMP_SECS + 1)).is_err());
1177 assert!(NumericDate::deserialize(serde_json::json!(u64::MAX)).is_err());
1178
1179 assert_eq!(
1181 NumericDate::new_clamp(MAX_TIMESTAMP_SECS).date(),
1182 "9999-12-31"
1183 );
1184 }
1185
1186 #[test]
1187 fn numericdate_clamps_and_saturates() {
1188 assert_eq!(
1189 NumericDate::new_clamp(u64::MAX).timestamp(),
1190 MAX_TIMESTAMP_SECS
1191 );
1192 assert_eq!(
1193 NumericDate::new_clamp(0).add_clamp(u64::MAX).timestamp(),
1194 MAX_TIMESTAMP_SECS
1195 );
1196 assert_eq!(NumericDate::new_clamp(5).sub_clamp(10).timestamp(), 0);
1197 }
1198
1199 #[test]
1200 fn test_rs256() {
1201 let sk = RS256Sk::new(
1203 rsa::RsaPrivateKey::from_components(
1204 rsa::BigUint::from_bytes_be(
1206 &base64ct::Base64UrlUnpadded::decode_vec(concat!(
1207 "ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddx",
1208 "HmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMs",
1209 "D1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSH",
1210 "SXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdV",
1211 "MTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8",
1212 "NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ",
1213 ))
1214 .unwrap(),
1215 ),
1216 rsa::BigUint::from_bytes_be(
1218 &base64ct::Base64UrlUnpadded::decode_vec("AQAB").unwrap(),
1219 ),
1220 rsa::BigUint::from_bytes_be(
1222 &base64ct::Base64UrlUnpadded::decode_vec(concat!(
1223 "Eq5xpGnNCivDflJsRQBXHx1hdR1k6Ulwe2JZD50LpXyWPEAeP88vLNO97I",
1224 "jlA7_GQ5sLKMgvfTeXZx9SE-7YwVol2NXOoAJe46sui395IW_GO-pWJ1O0",
1225 "BkTGoVEn2bKVRUCgu-GjBVaYLU6f3l9kJfFNS3E0QbVdxzubSu3Mkqzjkn",
1226 "439X0M_V51gfpRLI9JYanrC4D4qAdGcopV_0ZHHzQlBjudU2QvXt4ehNYT",
1227 "CBr6XCLQUShb1juUO1ZdiYoFaFQT5Tw8bGUl_x_jTj3ccPDVZFD9pIuhLh",
1228 "BOneufuBiB4cS98l2SR_RQyGWSeWjnczT0QU91p1DhOVRuOopznQ",
1229 ))
1230 .unwrap(),
1231 ),
1232 vec![
1234 rsa::BigUint::from_bytes_be(
1236 &base64ct::Base64UrlUnpadded::decode_vec(concat!(
1237 "4BzEEOtIpmVdVEZNCqS7baC4crd0pqnRH_5IB3jw3bcxGn6QLvnEtfdUdi",
1238 "YrqBdss1l58BQ3KhooKeQTa9AB0Hw_Py5PJdTJNPY8cQn7ouZ2KKDcmnPG",
1239 "BY5t7yLc1QlQ5xHdwW1VhvKn-nXqhJTBgIPgtldC-KDV5z-y2XDwGUc",
1240 ))
1241 .unwrap(),
1242 ),
1243 rsa::BigUint::from_bytes_be(
1245 &base64ct::Base64UrlUnpadded::decode_vec(concat!(
1246 "uQPEfgmVtjL0Uyyx88GZFF1fOunH3-7cepKmtH4pxhtCoHqpWmT8YAmZxa",
1247 "ewHgHAjLYsp1ZSe7zFYHj7C6ul7TjeLQeZD_YwD66t62wDmpe_HlB-TnBA",
1248 "-njbglfIsRLtXlnDzQkv5dTltRJ11BKBBypeeF6689rjcJIDEz9RWdc",
1249 ))
1250 .unwrap(),
1251 ),
1252 ],
1253 )
1254 .unwrap(),
1255 );
1256
1257 let to_sign: &str = concat!(
1258 "eyJhbGciOiJSUzI1NiJ9",
1259 ".",
1260 "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt",
1261 "cGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
1262 );
1263
1264 let signature = sk.sign(to_sign.as_bytes()).unwrap();
1265
1266 assert_eq!(
1267 signature.as_ref(),
1268 &Base64UrlUnpadded::decode_vec(concat!(
1269 "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7",
1270 "AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4",
1271 "BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K",
1272 "0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqv",
1273 "hJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrB",
1274 "p0igcN_IoypGlUPQGe77Rw",
1275 ))
1276 .unwrap()
1277 );
1278
1279 let jwt: JWT = concat!(
1280 "eyJhbGciOiJSUzI1NiJ9",
1281 ".",
1282 "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt",
1283 "cGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
1284 ".",
1285 "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7",
1286 "AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4",
1287 "BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K",
1288 "0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqv",
1289 "hJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrB",
1290 "p0igcN_IoypGlUPQGe77Rw",
1291 )
1292 .to_string()
1293 .into();
1294
1295 let pk = RS256Vk::new(sk.as_rsa_pub().clone());
1296
1297 let _ = jwt.open(&pk).unwrap();
1298 }
1299}