1use core::{cmp, fmt};
4
5use base64ct::{Base64UrlUnpadded, Encoding};
6use serde::{
7 Deserialize, Deserializer, Serialize, Serializer,
8 de::{DeserializeOwned, Error as DeError, Visitor},
9};
10use smallvec::{SmallVec, smallvec};
11
12#[cfg(feature = "ciborium")]
13use crate::error::CborDeError;
14use crate::{
15 Algorithm, Claims, Empty, ParseError, ValidationError,
16 alloc::{Cow, String, Vec, format},
17};
18
19const SIGNATURE_SIZE: usize = 128;
21
22#[derive(Debug, Clone, PartialEq, Eq, Hash)]
64#[non_exhaustive]
65pub enum Thumbprint<const N: usize> {
66 Bytes([u8; N]),
68 String(String),
71}
72
73impl<const N: usize> From<[u8; N]> for Thumbprint<N> {
74 fn from(value: [u8; N]) -> Self {
75 Self::Bytes(value)
76 }
77}
78
79impl<const N: usize> From<String> for Thumbprint<N> {
80 fn from(s: String) -> Self {
81 Self::String(s)
82 }
83}
84
85impl<const N: usize> From<&str> for Thumbprint<N> {
86 fn from(s: &str) -> Self {
87 Self::String(s.into())
88 }
89}
90
91impl<const N: usize> Serialize for Thumbprint<N> {
92 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
93 let input = match self {
94 Self::Bytes(bytes) => bytes.as_slice(),
95 Self::String(s) => s.as_bytes(),
96 };
97 serializer.serialize_str(&Base64UrlUnpadded::encode_string(input))
98 }
99}
100
101impl<'de, const N: usize> Deserialize<'de> for Thumbprint<N> {
102 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
103 struct Base64Visitor<const L: usize>;
104
105 impl<const L: usize> Visitor<'_> for Base64Visitor<L> {
106 type Value = Thumbprint<L>;
107
108 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
109 write!(formatter, "base64url-encoded thumbprint")
110 }
111
112 fn visit_str<E: DeError>(self, mut value: &str) -> Result<Self::Value, E> {
113 for _ in 0..2 {
123 if value.as_bytes().last() == Some(&b'=') {
124 value = &value[..value.len() - 1];
125 }
126 }
127
128 let decoded_len = value.len() * 3 / 4;
129 match decoded_len.cmp(&L) {
130 cmp::Ordering::Less => Err(E::custom(format!(
131 "thumbprint must contain at least {L} bytes"
132 ))),
133 cmp::Ordering::Equal => {
134 let mut bytes = [0_u8; L];
135 let len = Base64UrlUnpadded::decode(value, &mut bytes)
136 .map_err(E::custom)?
137 .len();
138 debug_assert_eq!(len, L);
139 Ok(bytes.into())
140 }
141 cmp::Ordering::Greater => {
142 let decoded = Base64UrlUnpadded::decode_vec(value).map_err(E::custom)?;
143 let decoded = String::from_utf8(decoded)
144 .map_err(|err| E::custom(err.utf8_error()))?;
145 Ok(decoded.into())
146 }
147 }
148 }
149 }
150
151 deserializer.deserialize_str(Base64Visitor)
152 }
153}
154
155#[derive(Debug, Clone, Default, Serialize, Deserialize)]
178#[non_exhaustive]
179pub struct Header<T = Empty> {
180 #[serde(rename = "jku", default, skip_serializing_if = "Option::is_none")]
185 pub key_set_url: Option<String>,
186
187 #[serde(rename = "kid", default, skip_serializing_if = "Option::is_none")]
192 pub key_id: Option<String>,
193
194 #[serde(rename = "x5u", default, skip_serializing_if = "Option::is_none")]
199 pub certificate_url: Option<String>,
200
201 #[serde(rename = "x5t", default, skip_serializing_if = "Option::is_none")]
206 pub certificate_sha1_thumbprint: Option<Thumbprint<20>>,
207
208 #[serde(rename = "x5t#S256", default, skip_serializing_if = "Option::is_none")]
213 pub certificate_thumbprint: Option<Thumbprint<32>>,
214
215 #[serde(rename = "typ", default, skip_serializing_if = "Option::is_none")]
219 pub token_type: Option<String>,
220
221 #[serde(flatten)]
233 pub other_fields: T,
234}
235
236impl Header {
237 pub const fn empty() -> Self {
239 Self {
240 key_set_url: None,
241 key_id: None,
242 certificate_url: None,
243 certificate_sha1_thumbprint: None,
244 certificate_thumbprint: None,
245 token_type: None,
246 other_fields: Empty {},
247 }
248 }
249}
250
251impl<T> Header<T> {
252 pub const fn new(fields: T) -> Header<T> {
254 Header {
255 key_set_url: None,
256 key_id: None,
257 certificate_url: None,
258 certificate_sha1_thumbprint: None,
259 certificate_thumbprint: None,
260 token_type: None,
261 other_fields: fields,
262 }
263 }
264
265 #[must_use]
267 pub fn with_key_set_url(mut self, key_set_url: impl Into<String>) -> Self {
268 self.key_set_url = Some(key_set_url.into());
269 self
270 }
271
272 #[must_use]
274 pub fn with_key_id(mut self, key_id: impl Into<String>) -> Self {
275 self.key_id = Some(key_id.into());
276 self
277 }
278
279 #[must_use]
281 pub fn with_certificate_url(mut self, certificate_url: impl Into<String>) -> Self {
282 self.certificate_url = Some(certificate_url.into());
283 self
284 }
285
286 #[must_use]
288 pub fn with_certificate_sha1_thumbprint(
289 mut self,
290 certificate_thumbprint: impl Into<Thumbprint<20>>,
291 ) -> Self {
292 self.certificate_sha1_thumbprint = Some(certificate_thumbprint.into());
293 self
294 }
295
296 #[must_use]
298 pub fn with_certificate_thumbprint(
299 mut self,
300 certificate_thumbprint: impl Into<Thumbprint<32>>,
301 ) -> Self {
302 self.certificate_thumbprint = Some(certificate_thumbprint.into());
303 self
304 }
305
306 #[must_use]
308 pub fn with_token_type(mut self, token_type: impl Into<String>) -> Self {
309 self.token_type = Some(token_type.into());
310 self
311 }
312}
313
314#[derive(Debug, Clone, Serialize, Deserialize)]
315pub(crate) struct CompleteHeader<'a, T> {
316 #[serde(rename = "alg")]
317 pub algorithm: Cow<'a, str>,
318 #[serde(rename = "cty", default, skip_serializing_if = "Option::is_none")]
319 pub content_type: Option<String>,
320 #[serde(flatten)]
321 pub inner: T,
322}
323
324#[derive(Debug, Clone, Copy, PartialEq, Eq)]
325enum ContentType {
326 Json,
327 #[cfg(feature = "ciborium")]
328 Cbor,
329}
330
331#[derive(Debug, Clone)]
379pub struct UntrustedToken<'a, H = Empty> {
380 pub(crate) signed_data: Cow<'a, [u8]>,
381 header: Header<H>,
382 algorithm: String,
383 content_type: ContentType,
384 serialized_claims: Vec<u8>,
385 signature: SmallVec<[u8; SIGNATURE_SIZE]>,
386}
387
388#[derive(Debug, Clone)]
393pub struct Token<T, H = Empty> {
394 header: Header<H>,
395 claims: Claims<T>,
396}
397
398impl<T, H> Token<T, H> {
399 pub(crate) fn new(header: Header<H>, claims: Claims<T>) -> Self {
400 Self { header, claims }
401 }
402
403 pub fn header(&self) -> &Header<H> {
405 &self.header
406 }
407
408 pub fn claims(&self) -> &Claims<T> {
410 &self.claims
411 }
412
413 pub fn into_parts(self) -> (Header<H>, Claims<T>) {
415 (self.header, self.claims)
416 }
417}
418
419#[non_exhaustive]
453pub struct SignedToken<A: Algorithm + ?Sized, T, H = Empty> {
454 pub signature: A::Signature,
456 pub token: Token<T, H>,
458}
459
460impl<A, T, H> fmt::Debug for SignedToken<A, T, H>
461where
462 A: Algorithm,
463 A::Signature: fmt::Debug,
464 T: fmt::Debug,
465 H: fmt::Debug,
466{
467 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
468 formatter
469 .debug_struct("SignedToken")
470 .field("token", &self.token)
471 .field("signature", &self.signature)
472 .finish()
473 }
474}
475
476impl<A, T, H> Clone for SignedToken<A, T, H>
477where
478 A: Algorithm,
479 A::Signature: Clone,
480 T: Clone,
481 H: Clone,
482{
483 fn clone(&self) -> Self {
484 Self {
485 signature: self.signature.clone(),
486 token: self.token.clone(),
487 }
488 }
489}
490
491impl<'a, H: DeserializeOwned> TryFrom<&'a str> for UntrustedToken<'a, H> {
492 type Error = ParseError;
493
494 fn try_from(s: &'a str) -> Result<Self, Self::Error> {
495 let token_parts: Vec<_> = s.splitn(4, '.').collect();
496 match &token_parts[..] {
497 [header, claims, signature] => {
498 let header = Base64UrlUnpadded::decode_vec(header)
499 .map_err(|_| ParseError::InvalidBase64Encoding)?;
500 let serialized_claims = Base64UrlUnpadded::decode_vec(claims)
501 .map_err(|_| ParseError::InvalidBase64Encoding)?;
502
503 let mut decoded_signature = smallvec![0; 3 * (signature.len() + 3) / 4];
504 let signature_len =
505 Base64UrlUnpadded::decode(signature, &mut decoded_signature[..])
506 .map_err(|_| ParseError::InvalidBase64Encoding)?
507 .len();
508 decoded_signature.truncate(signature_len);
509
510 let header: CompleteHeader<_> =
511 serde_json::from_slice(&header).map_err(ParseError::MalformedHeader)?;
512 let content_type = match header.content_type {
513 None => ContentType::Json,
514 Some(s) if s.eq_ignore_ascii_case("json") => ContentType::Json,
515 #[cfg(feature = "ciborium")]
516 Some(s) if s.eq_ignore_ascii_case("cbor") => ContentType::Cbor,
517 Some(s) => return Err(ParseError::UnsupportedContentType(s)),
518 };
519 let signed_data = s.rsplit_once('.').unwrap().0.as_bytes();
520 Ok(Self {
521 signed_data: Cow::Borrowed(signed_data),
522 header: header.inner,
523 algorithm: header.algorithm.into_owned(),
524 content_type,
525 serialized_claims,
526 signature: decoded_signature,
527 })
528 }
529 _ => Err(ParseError::InvalidTokenStructure),
530 }
531 }
532}
533
534impl<'a> UntrustedToken<'a> {
535 pub fn new<S: AsRef<str> + ?Sized>(s: &'a S) -> Result<Self, ParseError> {
538 Self::try_from(s.as_ref())
539 }
540}
541
542impl<H> UntrustedToken<'_, H> {
543 pub fn into_owned(self) -> UntrustedToken<'static, H> {
545 UntrustedToken {
546 signed_data: Cow::Owned(self.signed_data.into_owned()),
547 header: self.header,
548 algorithm: self.algorithm,
549 content_type: self.content_type,
550 serialized_claims: self.serialized_claims,
551 signature: self.signature,
552 }
553 }
554
555 pub fn header(&self) -> &Header<H> {
557 &self.header
558 }
559
560 pub fn algorithm(&self) -> &str {
562 &self.algorithm
563 }
564
565 pub fn signature_bytes(&self) -> &[u8] {
568 &self.signature
569 }
570
571 pub fn deserialize_claims_unchecked<T>(&self) -> Result<Claims<T>, ValidationError>
574 where
575 T: DeserializeOwned,
576 {
577 match self.content_type {
578 ContentType::Json => serde_json::from_slice(&self.serialized_claims)
579 .map_err(ValidationError::MalformedClaims),
580
581 #[cfg(feature = "ciborium")]
582 ContentType::Cbor => {
583 ciborium::from_reader(&self.serialized_claims[..]).map_err(|err| {
584 ValidationError::MalformedCborClaims(match err {
585 CborDeError::Io(err) => CborDeError::Io(anyhow::anyhow!(err)),
586 CborDeError::Syntax(offset) => CborDeError::Syntax(offset),
589 CborDeError::Semantic(offset, description) => {
590 CborDeError::Semantic(offset, description)
591 }
592 CborDeError::RecursionLimitExceeded => CborDeError::RecursionLimitExceeded,
593 })
594 })
595 }
596 }
597 }
598}
599
600#[cfg(test)]
601mod tests {
602 use assert_matches::assert_matches;
603 use base64ct::{Base64UrlUnpadded, Encoding};
604
605 use super::*;
606 use crate::{
607 AlgorithmExt, Empty,
608 alg::{Hs256, Hs256Key},
609 alloc::{ToOwned, ToString},
610 };
611
612 type Obj = serde_json::Map<String, serde_json::Value>;
613
614 const HS256_TOKEN: &str = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.\
615 eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt\
616 cGxlLmNvbS9pc19yb290Ijp0cnVlfQ.\
617 dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk";
618 const HS256_KEY: &str = "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75\
619 aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow";
620
621 #[test]
622 fn invalid_token_structure() {
623 let mangled_str = HS256_TOKEN.replace('.', "");
624 assert_matches!(
625 UntrustedToken::new(&mangled_str).unwrap_err(),
626 ParseError::InvalidTokenStructure
627 );
628
629 let mut mangled_str = HS256_TOKEN.to_owned();
630 let signature_start = mangled_str.rfind('.').unwrap();
631 mangled_str.truncate(signature_start);
632 assert_matches!(
633 UntrustedToken::new(&mangled_str).unwrap_err(),
634 ParseError::InvalidTokenStructure
635 );
636
637 let mut mangled_str = HS256_TOKEN.to_owned();
638 mangled_str.push('.');
639 assert_matches!(
640 UntrustedToken::new(&mangled_str).unwrap_err(),
641 ParseError::InvalidTokenStructure
642 );
643 }
644
645 #[test]
646 fn base64_error_during_parsing() {
647 let mangled_str = HS256_TOKEN.replace('0', "+");
648 assert_matches!(
649 UntrustedToken::new(&mangled_str).unwrap_err(),
650 ParseError::InvalidBase64Encoding
651 );
652 }
653
654 #[test]
655 fn base64_padding_error_during_parsing() {
656 let mut mangled_str = HS256_TOKEN.to_owned();
657 mangled_str.pop();
658 mangled_str.push('_'); assert_matches!(
660 UntrustedToken::new(&mangled_str).unwrap_err(),
661 ParseError::InvalidBase64Encoding
662 );
663 }
664
665 #[test]
666 fn header_fields_are_not_serialized_if_not_present() {
667 let header = Header::empty();
668 let json = serde_json::to_string(&header).unwrap();
669 assert_eq!(json, "{}");
670 }
671
672 #[test]
673 fn header_with_x5t_field() {
674 let header = r#"{"alg":"HS256","x5t":"lDpwLQbzRZmu4fjajvn3KWAx1pk"}"#;
675 let header: CompleteHeader<Header<Empty>> = serde_json::from_str(header).unwrap();
676 let thumbprint = header.inner.certificate_sha1_thumbprint.as_ref().unwrap();
677 let Thumbprint::Bytes(thumbprint) = thumbprint else {
678 unreachable!();
679 };
680
681 assert_eq!(thumbprint[0], 0x94);
682 assert_eq!(thumbprint[19], 0x99);
683
684 let json = serde_json::to_value(header).unwrap();
685 assert_eq!(
686 json,
687 serde_json::json!({
688 "alg": "HS256",
689 "x5t": "lDpwLQbzRZmu4fjajvn3KWAx1pk",
690 })
691 );
692 }
693
694 #[test]
695 fn header_with_padded_x5t_field() {
696 let header = r#"{"alg":"HS256","x5t":"lDpwLQbzRZmu4fjajvn3KWAx1pk=="}"#;
697 let header: CompleteHeader<Header<Empty>> = serde_json::from_str(header).unwrap();
698 let thumbprint = header.inner.certificate_sha1_thumbprint.as_ref().unwrap();
699 let Thumbprint::Bytes(thumbprint) = thumbprint else {
700 unreachable!()
701 };
702
703 assert_eq!(thumbprint[0], 0x94);
704 assert_eq!(thumbprint[19], 0x99);
705 }
706
707 #[test]
708 fn header_with_hex_x5t_field() {
709 let header =
710 r#"{"alg":"HS256","x5t":"NjVBRjY5MDlCMUIwNzU4RTA2QzZFMDQ4QzQ2MDAyQjVDNjk1RTM2Qg"}"#;
711 let header: CompleteHeader<Header<Empty>> = serde_json::from_str(header).unwrap();
712 let thumbprint = header.inner.certificate_sha1_thumbprint.as_ref().unwrap();
713 let Thumbprint::String(thumbprint) = thumbprint else {
714 unreachable!()
715 };
716
717 assert_eq!(thumbprint, "65AF6909B1B0758E06C6E048C46002B5C695E36B");
718
719 let json = serde_json::to_value(header).unwrap();
720 assert_eq!(
721 json,
722 serde_json::json!({
723 "alg": "HS256",
724 "x5t": "NjVBRjY5MDlCMUIwNzU4RTA2QzZFMDQ4QzQ2MDAyQjVDNjk1RTM2Qg",
725 })
726 );
727 }
728
729 #[test]
730 fn header_with_padded_hex_x5t_field() {
731 let header =
732 r#"{"alg":"HS256","x5t":"NjVBRjY5MDlCMUIwNzU4RTA2QzZFMDQ4QzQ2MDAyQjVDNjk1RTM2Qg=="}"#;
733 let header: CompleteHeader<Header<Empty>> = serde_json::from_str(header).unwrap();
734 let thumbprint = header.inner.certificate_sha1_thumbprint.as_ref().unwrap();
735 let Thumbprint::String(thumbprint) = thumbprint else {
736 unreachable!()
737 };
738
739 assert_eq!(thumbprint, "65AF6909B1B0758E06C6E048C46002B5C695E36B");
740 }
741
742 #[test]
743 fn header_with_overly_short_x5t_field() {
744 let header = r#"{"alg":"HS256","x5t":"aGk="}"#;
745 let err = serde_json::from_str::<CompleteHeader<Header<Empty>>>(header).unwrap_err();
746 let err = err.to_string();
747 assert!(
748 err.contains("thumbprint must contain at least 20 bytes"),
749 "{err}"
750 );
751 }
752
753 #[test]
754 fn header_with_non_base64_x5t_field() {
755 let headers = [
756 r#"{"alg":"HS256","x5t":"lDpwLQbzRZmu4fjajvn3KWAx1p?"}"#,
757 r#"{"alg":"HS256","x5t":"NjVBRjY5MDlCMUIwNzU4RTA2QzZFMDQ4QzQ2MDAyQjVDNjk!RTM2Qg"}"#,
758 ];
759 for header in headers {
760 let err = serde_json::from_str::<CompleteHeader<Header<Empty>>>(header).unwrap_err();
761 let err = err.to_string();
762 assert!(err.contains("Base64"), "{err}");
763 }
764 }
765
766 #[test]
767 fn header_with_x5t_sha256_field() {
768 let header = r#"{"alg":"HS256","x5t#S256":"MV9b23bQeMQ7isAGTkoBZGErH853yGk0W_yUx1iU7dM"}"#;
769 let header: CompleteHeader<Header<Empty>> = serde_json::from_str(header).unwrap();
770 let thumbprint = header.inner.certificate_thumbprint.as_ref().unwrap();
771 let Thumbprint::Bytes(thumbprint) = thumbprint else {
772 unreachable!()
773 };
774
775 assert_eq!(thumbprint[0], 0x31);
776 assert_eq!(thumbprint[31], 0xd3);
777
778 let json = serde_json::to_value(header).unwrap();
779 assert_eq!(
780 json,
781 serde_json::json!({
782 "alg": "HS256",
783 "x5t#S256": "MV9b23bQeMQ7isAGTkoBZGErH853yGk0W_yUx1iU7dM",
784 })
785 );
786 }
787
788 #[test]
789 fn malformed_header() {
790 let mangled_headers = [
791 r#"{"alg":"HS256""#,
793 "{}",
795 r#"{"alg":5}"#,
797 r#"{"alg":[1,"foo"]}"#,
798 r#"{"alg":false}"#,
799 r#"{"alg":"HS256","alg":"none"}"#,
801 r#"{"alg":"HS256","x5t":"lDpwLQbzRZmu4fjajvn3KWAx1p"}"#,
803 r#"{"alg":"HS256","x5t":["lDpwLQbzRZmu4fjajvn3KWAx1pk"]}"#,
804 r#"{"alg":"HS256","x5t":"lDpwLQbzRZmu4fjajvn3KWAx1 k"}"#,
805 r#"{"alg":"HS256","x5t":"lDpwLQbzRZmu4fjajvn3KWAx1pk==="}"#,
806 r#"{"alg":"HS256","x5t":"lDpwLQbzRZmu4fjajvn3KWAx1pkk"}"#,
807 r#"{"alg":"HS256","x5t":"MV9b23bQeMQ7isAGTkoBZGErH853yGk0W_yUx1iU7dM"}"#,
808 r#"{"alg":"HS256","x5t#S256":"lDpwLQbzRZmu4fjajvn3KWAx1pk"}"#,
809 ];
810
811 for mangled_header in &mangled_headers {
812 let mangled_header = Base64UrlUnpadded::encode_string(mangled_header.as_bytes());
813 let mut mangled_str = HS256_TOKEN.to_owned();
814 mangled_str.replace_range(..mangled_str.find('.').unwrap(), &mangled_header);
815 assert_matches!(
816 UntrustedToken::new(&mangled_str).unwrap_err(),
817 ParseError::MalformedHeader(_)
818 );
819 }
820 }
821
822 #[test]
823 fn unsupported_content_type() {
824 let mangled_header = br#"{"alg":"HS256","cty":"txt"}"#;
825 let mangled_header = Base64UrlUnpadded::encode_string(mangled_header);
826 let mut mangled_str = HS256_TOKEN.to_owned();
827 mangled_str.replace_range(..mangled_str.find('.').unwrap(), &mangled_header);
828 assert_matches!(
829 UntrustedToken::new(&mangled_str).unwrap_err(),
830 ParseError::UnsupportedContentType(s) if s == "txt"
831 );
832 }
833
834 #[test]
835 fn extracting_custom_header_fields() {
836 let header = r#"{"alg":"HS256","custom":[1,"field"],"x5t":"lDpwLQbzRZmu4fjajvn3KWAx1pk"}"#;
837 let header: CompleteHeader<Header<Obj>> = serde_json::from_str(header).unwrap();
838 assert_eq!(header.algorithm, "HS256");
839 assert!(header.inner.certificate_sha1_thumbprint.is_some());
840 assert_eq!(header.inner.other_fields.len(), 1);
841 assert!(header.inner.other_fields["custom"].is_array());
842 }
843
844 #[test]
845 fn malformed_json_claims() {
846 let malformed_claims = [
847 r#"{"exp":1500000000"#,
849 r#"{"exp":"1500000000"}"#,
851 r#"{"exp":false}"#,
852 r#"{"exp":1500000000,"nbf":1400000000,"exp":1510000000}"#,
854 r#"{"exp":1500000000000000000000000000000000}"#,
856 ];
857
858 let claims_start = HS256_TOKEN.find('.').unwrap() + 1;
859 let claims_end = HS256_TOKEN.rfind('.').unwrap();
860 let key = Base64UrlUnpadded::decode_vec(HS256_KEY).unwrap();
861 let key = Hs256Key::new(key);
862
863 for claims in &malformed_claims {
864 let encoded_claims = Base64UrlUnpadded::encode_string(claims.as_bytes());
865 let mut mangled_str = HS256_TOKEN.to_owned();
866 mangled_str.replace_range(claims_start..claims_end, &encoded_claims);
867 let token = UntrustedToken::new(&mangled_str).unwrap();
868 assert_matches!(
869 Hs256.validator::<Obj>(&key).validate(&token).unwrap_err(),
870 ValidationError::MalformedClaims(_),
871 "Failing claims: {claims}"
872 );
873 }
874 }
875
876 fn test_invalid_signature_len(mangled_str: &str, actual_len: usize) {
877 let token = UntrustedToken::new(&mangled_str).unwrap();
878 let key = Base64UrlUnpadded::decode_vec(HS256_KEY).unwrap();
879 let key = Hs256Key::new(key);
880
881 let err = Hs256.validator::<Empty>(&key).validate(&token).unwrap_err();
882 assert_matches!(
883 err,
884 ValidationError::InvalidSignatureLen { actual, expected: 32 }
885 if actual == actual_len
886 );
887 }
888
889 #[test]
890 fn short_signature_error() {
891 test_invalid_signature_len(&HS256_TOKEN[..HS256_TOKEN.len() - 3], 30);
892 }
893
894 #[test]
895 fn long_signature_error() {
896 let mut mangled_string = HS256_TOKEN.to_owned();
897 mangled_string.push('a');
898 test_invalid_signature_len(&mangled_string, 33);
899 }
900}