identity_jose/jws/
decoder.rs

1// Copyright 2020-2025 IOTA Stiftung, Fondazione LINKS
2// SPDX-License-Identifier: Apache-2.0
3
4use core::str;
5use std::borrow::Cow;
6
7use crate::error::Error;
8use crate::error::Result;
9use crate::jwk::CompositeAlgId;
10use crate::jwk::Jwk;
11use crate::jwk::PostQuantumJwk;
12use crate::jwk::TraditionalJwk;
13use crate::jws::JwsAlgorithm;
14use crate::jws::JwsHeader;
15use crate::jwu::create_message;
16use crate::jwu::decode_b64;
17use crate::jwu::decode_b64_json;
18use crate::jwu::filter_non_empty_bytes;
19use crate::jwu::parse_utf8;
20use crate::jwu::validate_jws_headers;
21
22use super::JwsVerifier;
23use super::VerificationInput;
24
25/// A cryptographically verified decoded token from a JWS.
26///
27/// Contains the decoded headers and the raw claims.
28#[derive(Clone, Debug, PartialEq, Eq)]
29#[non_exhaustive]
30pub struct DecodedJws<'a> {
31  /// The decoded protected header.
32  pub protected: JwsHeader,
33  /// The decoded unprotected header.
34  pub unprotected: Option<Box<JwsHeader>>,
35  /// The decoded raw claims.
36  pub claims: Cow<'a, [u8]>,
37}
38
39enum DecodedHeaders {
40  Protected(JwsHeader),
41  Unprotected(JwsHeader),
42  Both {
43    protected: JwsHeader,
44    // Use box to reduce size
45    unprotected: Box<JwsHeader>,
46  },
47}
48
49impl DecodedHeaders {
50  fn new(protected: Option<JwsHeader>, unprotected: Option<JwsHeader>) -> Result<Self> {
51    match (protected, unprotected) {
52      (Some(protected), Some(unprotected)) => Ok(Self::Both {
53        protected,
54        unprotected: Box::new(unprotected),
55      }),
56      (Some(protected), None) => Ok(Self::Protected(protected)),
57      (None, Some(unprotected)) => Ok(Self::Unprotected(unprotected)),
58      (None, None) => Err(Error::MissingHeader("no headers were decoded")),
59    }
60  }
61
62  fn protected_header(&self) -> Option<&JwsHeader> {
63    match self {
64      DecodedHeaders::Protected(ref header) => Some(header),
65      DecodedHeaders::Both { ref protected, .. } => Some(protected),
66      DecodedHeaders::Unprotected(_) => None,
67    }
68  }
69
70  fn unprotected_header(&self) -> Option<&JwsHeader> {
71    match self {
72      DecodedHeaders::Unprotected(ref header) => Some(header),
73      DecodedHeaders::Both { ref unprotected, .. } => Some(unprotected.as_ref()),
74      DecodedHeaders::Protected(_) => None,
75    }
76  }
77}
78
79/// A partially decoded JWS containing claims, and the decoded verification data
80/// for its corresponding signature (headers, signing input and signature). This data
81/// can be cryptographically verified using a [`JwsVerifier`]. See [`Self::verify`](Self::verify).
82pub struct JwsValidationItem<'a> {
83  headers: DecodedHeaders,
84  signing_input: Box<[u8]>,
85  decoded_signature: Box<[u8]>,
86  claims: Cow<'a, [u8]>,
87}
88impl<'a> JwsValidationItem<'a> {
89  /// Returns the decoded protected header if it exists.
90  pub fn protected_header(&self) -> Option<&JwsHeader> {
91    self.headers.protected_header()
92  }
93
94  /// Returns the Nonce from the protected header if it is set.
95  pub fn nonce(&self) -> Option<&str> {
96    self.protected_header().and_then(|header| header.nonce())
97  }
98
99  /// Returns the kid from the protected header if it is set.
100  pub fn kid(&self) -> Option<&str> {
101    self.protected_header().and_then(|header| header.kid())
102  }
103
104  /// Returns the decoded unprotected header if it exists.
105  pub fn unprotected_header(&self) -> Option<&JwsHeader> {
106    self.headers.unprotected_header()
107  }
108
109  /// The algorithm parsed from the protected header if it exists.
110  pub fn alg(&self) -> Option<JwsAlgorithm> {
111    self.protected_header().and_then(|protected| protected.alg())
112  }
113
114  /// Returns the JWS claims.
115  pub fn claims(&self) -> &[u8] {
116    &self.claims
117  }
118
119  /// Returns the signing input .
120  ///
121  /// See [RFC 7515: section 5.2 part 8.](https://www.rfc-editor.org/rfc/rfc7515#section-5.2) and
122  /// [RFC 7797 section 3](https://www.rfc-editor.org/rfc/rfc7797#section-3).
123  pub fn signing_input(&self) -> &[u8] {
124    &self.signing_input
125  }
126
127  /// Returns the decoded JWS signature.
128  pub fn decoded_signature(&self) -> &[u8] {
129    &self.decoded_signature
130  }
131
132  /// Constructs [`VerificationInput`] from this data and passes it to the given `verifier` along with the
133  /// provided `public_key`.
134  ///
135  /// # Errors
136  /// Apart from the fallible call to [`JwsVerifier::verify`] this method can also error if there is no
137  /// `alg` present in the protected header (in which case the verifier cannot be called) or if the given `public_key`
138  /// has a different value present in its `alg` field.
139  ///
140  /// # Note
141  /// One may want to perform other validations before calling this method, such as for instance checking the nonce
142  /// (see [`Self::nonce`](Self::nonce())).
143  pub fn verify<T>(self, verifier: &T, public_key: &Jwk) -> Result<DecodedJws<'a>>
144  where
145    T: JwsVerifier,
146  {
147    // Destructure data
148    let JwsValidationItem {
149      headers,
150      claims,
151      signing_input,
152      decoded_signature,
153    } = self;
154    let (protected, unprotected): (JwsHeader, Option<Box<JwsHeader>>) = match headers {
155      DecodedHeaders::Protected(protected) => (protected, None),
156      DecodedHeaders::Both { protected, unprotected } => (protected, Some(unprotected)),
157      DecodedHeaders::Unprotected(_) => return Err(Error::MissingHeader("missing protected header")),
158    };
159
160    // Extract and validate alg from the protected header.
161    let alg: JwsAlgorithm = protected.alg().ok_or(Error::ProtectedHeaderWithoutAlg)?;
162    public_key.check_alg(alg.name())?;
163
164    // Construct verification input
165    let input = VerificationInput {
166      alg,
167      signing_input,
168      decoded_signature,
169    };
170    // Call verifier
171    verifier
172      .verify(input, public_key)
173      .map_err(Error::SignatureVerificationError)?;
174
175    Ok(DecodedJws {
176      protected,
177      unprotected,
178      claims,
179    })
180  }
181
182  /// Hybrid signature verification.
183  pub fn verify_hybrid<TRV, PQV>(
184    self,
185    traditional_verifier: &TRV,
186    pq_verifier: &PQV,
187    traditional_pk: &TraditionalJwk,
188    pq_pk: &PostQuantumJwk,
189  ) -> Result<DecodedJws<'a>>
190  where
191    TRV: JwsVerifier,
192    PQV: JwsVerifier,
193  {
194    // Destructure data
195    let JwsValidationItem {
196      headers,
197      claims,
198      signing_input,
199      decoded_signature,
200    } = self;
201
202    let (protected, unprotected): (JwsHeader, Option<Box<JwsHeader>>) = match headers {
203      DecodedHeaders::Protected(protected) => (protected, None),
204      DecodedHeaders::Both { protected, unprotected } => (protected, Some(unprotected)),
205      DecodedHeaders::Unprotected(_) => return Err(Error::MissingHeader("missing protected header")),
206    };
207
208    // Extract and validate alg from the protected header.
209    let alg: JwsAlgorithm = protected.alg().ok_or(Error::ProtectedHeaderWithoutAlg)?;
210
211    let domain = match alg {
212      JwsAlgorithm::IdMldsa44Ed25519 => CompositeAlgId::IdMldsa44Ed25519.domain(),
213      JwsAlgorithm::IdMldsa65Ed25519 => CompositeAlgId::IdMldsa65Ed25519.domain(),
214      _ => return Err(Error::JwsAlgorithmParsingError),
215    };
216
217    // M' = Prefix || Domain || len(ctx) || ctx || M
218    //Prefix: CompositeAlgorithmSignatures2025
219    let mut input = CompositeAlgId::COMPOSITE_SIGNATURE_PREFIX.to_vec();
220
221    //Domain: id-MLDSA44-Ed25519 or id-MLDSA65-Ed25519
222    input.extend_from_slice(domain);
223
224    //len(ctx) = 0
225    input.push(0x00);
226
227    //M
228    input.extend(signing_input);
229
230    traditional_pk.check_alg(JwsAlgorithm::EdDSA.name())?;
231
232    let (extracted_signature_t, extracted_signature_pq) =
233      decoded_signature.split_at(crypto::signatures::ed25519::Signature::LENGTH);
234
235    // Construct verification input
236    let input1 = VerificationInput {
237      alg: JwsAlgorithm::EdDSA,
238      signing_input: input.clone().into(),
239      decoded_signature: extracted_signature_t.into(),
240    };
241
242    // Call the traditional verifier
243    traditional_verifier
244      .verify(input1, traditional_pk)
245      .map_err(Error::SignatureVerificationError)?;
246
247    let input2 = VerificationInput {
248      alg,
249      signing_input: input.into(),
250      decoded_signature: extracted_signature_pq.into(),
251    };
252
253    // Call the PQ verifier
254    pq_verifier
255      .verify(input2, pq_pk)
256      .map_err(Error::SignatureVerificationError)?;
257
258    Ok(DecodedJws {
259      protected,
260      unprotected,
261      claims,
262    })
263  }
264}
265
266// =============================================================================================
267// Format dependent deserializable helper structs used by the decoder
268// =============================================================================================
269#[derive(serde::Deserialize)]
270#[serde(deny_unknown_fields)]
271struct JwsSignature<'a> {
272  header: Option<JwsHeader>,
273  protected: Option<&'a str>,
274  signature: &'a str,
275}
276
277#[derive(serde::Deserialize)]
278#[serde(deny_unknown_fields)]
279struct General<'a> {
280  payload: Option<&'a str>,
281  signatures: Vec<JwsSignature<'a>>,
282}
283
284#[derive(serde::Deserialize)]
285#[serde(deny_unknown_fields)]
286struct Flatten<'a> {
287  payload: Option<&'a str>,
288  #[serde(flatten)]
289  signature: JwsSignature<'a>,
290}
291
292// =============================================================================
293// Decoder
294// =============================================================================
295
296/// The [`Decoder`] is responsible for decoding a JWS into one or more [`JwsValidationItems`](JwsValidationItem).
297#[derive(Debug, Clone)]
298pub struct Decoder;
299
300impl Decoder {
301  /// Constructs a new [`Decoder`].
302  pub fn new() -> Decoder {
303    Self
304  }
305
306  /// Decode a JWS encoded with the [JWS compact serialization format](https://www.rfc-editor.org/rfc/rfc7515#section-3.1).
307  ///
308  ///
309  /// ### Working with detached payloads
310  ///
311  /// A detached payload can be supplied in the `detached_payload` parameter.
312  /// [More Info](https://tools.ietf.org/html/rfc7515#appendix-F)
313  pub fn decode_compact_serialization<'b>(
314    &self,
315    jws_bytes: &'b [u8],
316    detached_payload: Option<&'b [u8]>,
317  ) -> Result<JwsValidationItem<'b>> {
318    let mut segments = jws_bytes.split(|byte| *byte == b'.');
319
320    let (Some(protected), Some(payload), Some(signature), None) =
321      (segments.next(), segments.next(), segments.next(), segments.next())
322    else {
323      return Err(Error::InvalidContent("invalid segments count"));
324    };
325
326    let signature: JwsSignature<'_> = JwsSignature {
327      header: None,
328      protected: Some(parse_utf8(protected)?),
329      signature: parse_utf8(signature)?,
330    };
331
332    let payload = Self::expand_payload(detached_payload, Some(payload))?;
333
334    self.decode_signature(payload, signature)
335  }
336
337  /// Decode a JWS encoded with the [flattened JWS JSON serialization format](https://www.rfc-editor.org/rfc/rfc7515#section-7.2.2).
338  ///
339  /// ### Working with detached payloads
340  ///
341  /// A detached payload can be supplied in the `detached_payload` parameter.
342  /// [More Info](https://tools.ietf.org/html/rfc7515#appendix-F)
343  pub fn decode_flattened_serialization<'b>(
344    &self,
345    jws_bytes: &'b [u8],
346    detached_payload: Option<&'b [u8]>,
347  ) -> Result<JwsValidationItem<'b>> {
348    let data: Flatten<'_> = serde_json::from_slice(jws_bytes).map_err(Error::InvalidJson)?;
349    let payload = Self::expand_payload(detached_payload, data.payload)?;
350    let signature = data.signature;
351    self.decode_signature(payload, signature)
352  }
353
354  fn decode_signature<'a, 'b>(
355    &self,
356    payload: &'b [u8],
357    jws_signature: JwsSignature<'a>,
358  ) -> Result<JwsValidationItem<'b>> {
359    let JwsSignature {
360      header: unprotected_header,
361      protected,
362      signature,
363    } = jws_signature;
364
365    let protected_header: Option<JwsHeader> = protected.map(decode_b64_json).transpose()?;
366    validate_jws_headers(protected_header.as_ref(), unprotected_header.as_ref())?;
367
368    let protected_bytes: &[u8] = protected.map(str::as_bytes).unwrap_or_default();
369    let signing_input: Box<[u8]> = create_message(protected_bytes, payload).into();
370    let decoded_signature: Box<[u8]> = decode_b64(signature)?.into();
371
372    let claims: Cow<'b, [u8]> = if protected_header.as_ref().and_then(|value| value.b64()).unwrap_or(true) {
373      Cow::Owned(decode_b64(payload)?)
374    } else {
375      Cow::Borrowed(payload)
376    };
377
378    Ok(JwsValidationItem {
379      headers: DecodedHeaders::new(protected_header, unprotected_header)?,
380      signing_input,
381      decoded_signature,
382      claims,
383    })
384  }
385
386  fn expand_payload<'b>(
387    detached_payload: Option<&'b [u8]>,
388    parsed_payload: Option<&'b (impl AsRef<[u8]> + ?Sized)>,
389  ) -> Result<&'b [u8]> {
390    match (detached_payload, filter_non_empty_bytes(parsed_payload)) {
391      (Some(payload), None) => Ok(payload),
392      (None, Some(payload)) => Ok(payload),
393      (Some(_), Some(_)) => Err(Error::InvalidContent("multiple payloads")),
394      (None, None) => Err(Error::InvalidContent("missing payload")),
395    }
396  }
397}
398
399// =======================================
400// General JWS JSON serialization support
401// =======================================
402
403/// An iterator over the [`JwsValidationItems`](JwsValidationItem) corresponding to the
404/// signatures in a JWS encoded with the general JWS JSON serialization format.  
405pub struct JwsValidationIter<'decoder, 'payload, 'signatures> {
406  decoder: &'decoder Decoder,
407  signatures: std::vec::IntoIter<JwsSignature<'signatures>>,
408  payload: &'payload [u8],
409}
410
411impl<'payload> Iterator for JwsValidationIter<'_, 'payload, '_> {
412  type Item = Result<JwsValidationItem<'payload>>;
413
414  fn next(&mut self) -> Option<Self::Item> {
415    self
416      .signatures
417      .next()
418      .map(|signature| self.decoder.decode_signature(self.payload, signature))
419  }
420}
421
422impl Decoder {
423  /// Decode a JWS encoded with the [general JWS JSON serialization format](https://www.rfc-editor.org/rfc/rfc7515#section-7.2.1)
424  ///
425  ///  
426  /// ### Working with detached payloads
427  /// A detached payload can be supplied in the `detached_payload` parameter.
428  /// [More Info](https://tools.ietf.org/html/rfc7515#appendix-F)
429  pub fn decode_general_serialization<'decoder, 'data>(
430    &'decoder self,
431    jws_bytes: &'data [u8],
432    detached_payload: Option<&'data [u8]>,
433  ) -> Result<JwsValidationIter<'decoder, 'data, 'data>> {
434    let data: General<'data> = serde_json::from_slice(jws_bytes).map_err(Error::InvalidJson)?;
435
436    let payload = Self::expand_payload(detached_payload, data.payload)?;
437    let signatures = data.signatures;
438
439    Ok(JwsValidationIter {
440      decoder: self,
441      payload,
442      signatures: signatures.into_iter(),
443    })
444  }
445}
446
447impl Default for Decoder {
448  fn default() -> Self {
449    Self::new()
450  }
451}
452
453#[cfg(test)]
454mod tests {
455  use crate::jwt::JwtClaims;
456
457  use super::*;
458
459  const RFC_7515_APPENDIX_EXAMPLE_CLAIMS: &str = r#"
460  {
461    "iss":"joe",
462    "exp":1300819380,
463    "http://example.com/is_root":true
464  }
465  "#;
466
467  const SIGNING_INPUT_ES256_RFC_7515_APPENDIX_EXAMPLE: &[u8] = &[
468    101, 121, 74, 104, 98, 71, 99, 105, 79, 105, 74, 70, 85, 122, 73, 49, 78, 105, 74, 57, 46, 101, 121, 74, 112, 99,
469    51, 77, 105, 79, 105, 74, 113, 98, 50, 85, 105, 76, 65, 48, 75, 73, 67, 74, 108, 101, 72, 65, 105, 79, 106, 69,
470    122, 77, 68, 65, 52, 77, 84, 107, 122, 79, 68, 65, 115, 68, 81, 111, 103, 73, 109, 104, 48, 100, 72, 65, 54, 76,
471    121, 57, 108, 101, 71, 70, 116, 99, 71, 120, 108, 76, 109, 78, 118, 98, 83, 57, 112, 99, 49, 57, 121, 98, 50, 57,
472    48, 73, 106, 112, 48, 99, 110, 86, 108, 102, 81,
473  ];
474
475  // Test https://www.rfc-editor.org/rfc/rfc7515#appendix-A.6
476  #[test]
477  fn rfc7515_appendix_a_6() {
478    let general_jws_json_serialized: &str = r#"
479    {
480      "payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
481      "signatures": [
482        {
483          "protected": "eyJhbGciOiJSUzI1NiJ9",
484          "header": {
485            "kid": "2010-12-29"
486          },
487          "signature": "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw"
488        },
489        {
490          "protected": "eyJhbGciOiJFUzI1NiJ9",
491          "header": {
492            "kid": "e9bc097a-ce51-4036-9562-d2ade882db0d"
493          },
494          "signature": "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q"
495        }
496      ]
497    }"#;
498
499    let claims: JwtClaims<serde_json::Value> = serde_json::from_str(RFC_7515_APPENDIX_EXAMPLE_CLAIMS).unwrap();
500
501    let decoder = Decoder::new();
502
503    let mut signature_iter = decoder
504      .decode_general_serialization(general_jws_json_serialized.as_bytes(), None)
505      .unwrap()
506      .filter_map(|decoded| decoded.ok());
507
508    // Check that the lifetimes are not overly restrictive:
509    let first_signature_decoding = signature_iter.next().unwrap();
510    let second_signature_decoding = signature_iter.next().unwrap();
511    drop(signature_iter);
512
513    // Check assertions for the first signature:
514    assert_eq!(first_signature_decoding.alg().unwrap(), JwsAlgorithm::RS256);
515    assert_eq!(
516      first_signature_decoding
517        .unprotected_header()
518        .and_then(|value| value.kid())
519        .unwrap(),
520      "2010-12-29"
521    );
522    let decoded_claims: JwtClaims<serde_json::Value> =
523      serde_json::from_slice(first_signature_decoding.claims()).unwrap();
524    assert_eq!(claims, decoded_claims);
525
526    // Check assertions for the second signature:
527    assert_eq!(second_signature_decoding.alg().unwrap(), JwsAlgorithm::ES256);
528    assert_eq!(
529      second_signature_decoding
530        .unprotected_header()
531        .and_then(|value| value.kid())
532        .unwrap(),
533      "e9bc097a-ce51-4036-9562-d2ade882db0d"
534    );
535
536    let decoded_claims: JwtClaims<serde_json::Value> =
537      serde_json::from_slice(second_signature_decoding.claims()).unwrap();
538    assert_eq!(decoded_claims, claims);
539    assert_eq!(
540      SIGNING_INPUT_ES256_RFC_7515_APPENDIX_EXAMPLE,
541      second_signature_decoding.signing_input()
542    );
543  }
544
545  // Test https://www.rfc-editor.org/rfc/rfc7515#appendix-A.7
546  #[test]
547  fn rfc7515_appendix_a_7() {
548    let flattened_jws_json_serialized: &str = r#"
549    {
550      "payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
551      "protected":"eyJhbGciOiJFUzI1NiJ9",
552      "header": {"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"},
553      "signature": "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q"
554     }
555    "#;
556
557    let claims: JwtClaims<serde_json::Value> = serde_json::from_str(RFC_7515_APPENDIX_EXAMPLE_CLAIMS).unwrap();
558    let decoder = Decoder::new();
559    let decoded = decoder
560      .decode_flattened_serialization(flattened_jws_json_serialized.as_bytes(), None)
561      .unwrap();
562    assert_eq!(decoded.alg().unwrap(), JwsAlgorithm::ES256);
563    assert_eq!(
564      decoded.unprotected_header().and_then(|value| value.kid()).unwrap(),
565      "e9bc097a-ce51-4036-9562-d2ade882db0d"
566    );
567
568    assert_eq!(decoded.signing_input(), SIGNING_INPUT_ES256_RFC_7515_APPENDIX_EXAMPLE);
569    let decoded_claims: JwtClaims<serde_json::Value> = serde_json::from_slice(decoded.claims()).unwrap();
570    assert_eq!(decoded_claims, claims);
571  }
572}