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