identity_jose/jws/
decoder.rs

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