identity_jose/jws/encoding/
encoder.rs

1// Copyright 2020-2023 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use self::encoder_state::ReadyState;
5use self::encoder_state::RecipientProcessingState;
6
7use crate::error::Error;
8use crate::error::Result;
9use crate::jws::encoding::utils::JwsSignature;
10use crate::jws::CharSet;
11use crate::jws::JwsHeader;
12use crate::jws::Recipient;
13use std::borrow::Cow;
14
15use crate::jwu;
16
17use super::utils;
18use super::utils::Flatten;
19use super::utils::General;
20use super::utils::MaybeEncodedPayload;
21use super::utils::SigningData;
22
23/// A JWS encoder supporting the Compact JWS serialization format.
24///
25/// See (<https://www.rfc-editor.org/rfc/rfc7515#section-3.1>).  
26pub struct CompactJwsEncoder<'a> {
27  protected_header: String,
28  processed_payload: Option<Cow<'a, str>>,
29  signing_input: Box<[u8]>,
30}
31
32#[derive(Debug, Copy, Clone)]
33/// Options determining whether the payload is detached and if not
34/// which additional requirements the payload must satisfy.
35pub enum CompactJwsEncodingOptions {
36  /// Includes the payload in the JWS, i.e. non-detached mode.
37  NonDetached {
38    /// The requirements towards the character set when encoding a JWS.
39    charset_requirements: CharSet,
40  },
41  /// Does not include the payload in the JWS, i.e. detached mode.
42  Detached,
43}
44
45impl<'payload> CompactJwsEncoder<'payload> {
46  /// Start the process of encoding a JWS. This prepares the values that need to be signed. See
47  /// [`Self::into_jws`](CompactJwsEncoder::into_jws()) for information on how to proceed.
48  ///
49  /// # Options
50  /// This will prepare a JWS with a non-detached payload that satisfies the requirements of [`CharSet::Default`]. See
51  /// [`Self::new_with_options`](CompactJwsEncoder::new_with_options()) for alternative configurations.  
52  pub fn new(payload: &'payload [u8], protected_header: &JwsHeader) -> Result<Self> {
53    Self::new_with_options(
54      payload,
55      protected_header,
56      CompactJwsEncodingOptions::NonDetached {
57        charset_requirements: CharSet::Default,
58      },
59    )
60  }
61
62  /// Similar to [`Self::new`](CompactJwsEncoder::new), but with more configuration options. See
63  /// [`CompactJwsEncodingOptions`].
64  pub fn new_with_options(
65    payload: &'payload [u8],
66    protected_header: &JwsHeader,
67    options: CompactJwsEncodingOptions,
68  ) -> Result<Self> {
69    Self::validate_header(protected_header)?;
70    let encoded_protected_header: String = jwu::encode_b64_json(protected_header)?;
71    let maybe_encoded: MaybeEncodedPayload<'_> = MaybeEncodedPayload::encode_if_b64(payload, Some(protected_header));
72    let signing_input: Box<[u8]> =
73      jwu::create_message(encoded_protected_header.as_bytes(), maybe_encoded.as_bytes()).into();
74
75    let processed_payload: Option<Cow<'payload, str>> = {
76      if let CompactJwsEncodingOptions::NonDetached { charset_requirements } = options {
77        Some(maybe_encoded.into_non_detached(|input| charset_requirements.validate(input))?)
78      } else {
79        None
80      }
81    };
82
83    Ok(Self {
84      protected_header: encoded_protected_header,
85      processed_payload,
86      signing_input,
87    })
88  }
89
90  /// The signing input. This has been computed according to the
91  /// [JWS Signing Input Formula](https://www.rfc-editor.org/rfc/rfc7797#section-3) using the
92  /// protected header and payload given in the constructor.
93  pub fn signing_input(&self) -> &[u8] {
94    &self.signing_input
95  }
96
97  fn validate_header(protected_header: &JwsHeader) -> Result<()> {
98    jwu::validate_jws_headers(Some(protected_header), None)
99  }
100
101  /// convert this into a JWS. The `signature` value is expected to be
102  /// the signature on [`Self::signing_input`] by the private key corresponding to the public key
103  /// referenced in the JWS header in accordance with the `alg` value of said header.
104  pub fn into_jws(self, signature: &[u8]) -> String {
105    let signature = jwu::encode_b64(signature);
106    if let Some(payload) = self.processed_payload {
107      format!("{}.{}.{}", self.protected_header, payload, &signature)
108    } else {
109      format!("{}..{}", self.protected_header, &signature)
110    }
111  }
112}
113
114// ===============================================================================================================================
115//  JWS JSON Serialization
116// ===============================================================================================================================
117
118// ===================================================================================
119//  Flattened JWS Json Serialization
120// ===================================================================================
121
122/// An encoder supporting the Flattened JWS JSON Serialiazion format.
123///
124/// See (<https://www.rfc-editor.org/rfc/rfc7515#section-7.2.2>).
125pub struct FlattenedJwsEncoder<'payload, 'unprotected> {
126  processed_payload: Option<Cow<'payload, str>>,
127  signing_data: SigningData,
128  unprotected_header: Option<&'unprotected JwsHeader>,
129}
130
131impl<'payload, 'unprotected> FlattenedJwsEncoder<'payload, 'unprotected> {
132  /// Start the process of encoding a JWS. This prepares the values that need to be signed. See
133  /// [`Self::into_jws`](CompactJwsEncoder::into_jws()) for information on how to proceed.
134  pub fn new(payload: &'payload [u8], recipient: Recipient<'unprotected>, detached: bool) -> Result<Self> {
135    utils::validate_headers_json_serialization(recipient)?;
136    let maybe_encoded: MaybeEncodedPayload<'_> = MaybeEncodedPayload::encode_if_b64(payload, recipient.protected);
137    let signing_data: SigningData = SigningData::new(maybe_encoded.as_bytes(), recipient.protected)?;
138    let processed_payload: Option<Cow<'payload, str>> = if !detached {
139      // The only additional validation required when processing the payload to be used with the flattened JWS Json
140      // serialization is that it is a valid JSON value, but we check for that later during serialization.
141      Some(maybe_encoded.into_non_detached(|bytes| std::str::from_utf8(bytes).map_err(Error::InvalidUtf8))?)
142    } else {
143      None
144    };
145
146    Ok(Self {
147      processed_payload,
148      signing_data,
149      unprotected_header: recipient.unprotected,
150    })
151  }
152
153  /// The signing input. This has been computed according to the
154  /// [JWS Signing Input Formula](https://www.rfc-editor.org/rfc/rfc7797#section-3) using the
155  /// protected header from the [`Recipient`] and payload given in the constructor respectively.
156  pub fn signing_input(&self) -> &[u8] {
157    &self.signing_data.signing_input
158  }
159
160  /// Convert this into a JWS. The `signature` value is expected to be
161  /// the signature on [`Self::signing_input`] by the private key corresponding to the public key
162  /// referenced in the JWS header in accordance with the `alg` value of said header.
163  pub fn into_jws(self, signature: &[u8]) -> Result<String> {
164    let FlattenedJwsEncoder {
165      processed_payload,
166      signing_data,
167      unprotected_header,
168    } = self;
169
170    Flatten {
171      payload: processed_payload.as_deref(),
172      signature: signing_data.into_signature(signature, unprotected_header),
173    }
174    .to_json()
175  }
176}
177
178// ===================================================================================
179//  General JWS Json Serialization
180// ===================================================================================
181
182mod encoder_state {
183  use super::utils::SigningData;
184  use crate::jws::JwsHeader;
185  pub struct ReadyState;
186  pub struct RecipientProcessingState<'unprotected> {
187    pub(super) signing_data: SigningData,
188    pub(super) unprotected_header: Option<&'unprotected JwsHeader>,
189  }
190}
191
192/// A General JWS Encoder that is currently processing the latest [`Recipient`].
193pub type RecipientProcessingEncoder<'payload, 'unprotected> =
194  GeneralJwsEncoder<'payload, 'unprotected, RecipientProcessingState<'unprotected>>;
195
196/// An encoder for the General JWS JSON Serialization format.
197pub struct GeneralJwsEncoder<'payload, 'unprotected, STATE = ReadyState> {
198  partially_processed_payload: Cow<'payload, [u8]>,
199  signatures: Vec<JwsSignature<'unprotected>>,
200  detached: bool,
201  b64: bool,
202  state: STATE,
203}
204
205impl<'payload, 'unprotected> GeneralJwsEncoder<'payload, 'unprotected> {
206  /// Start encoding a JWS with the General JWS JSON serialization format.
207  /// This will prepare the the signing input which must be signed as a next step. See
208  /// [`RecipientProcessingEncoder::set_signature`](RecipientProcessingEncoder::set_signature()).
209  pub fn new(
210    payload: &'payload [u8],
211    first_recipient: Recipient<'unprotected>,
212    detached: bool,
213  ) -> Result<RecipientProcessingEncoder<'payload, 'unprotected>> {
214    utils::validate_headers_json_serialization(first_recipient)?;
215    let partially_processed_payload: Cow<'payload, [u8]> =
216      MaybeEncodedPayload::encode_if_b64(payload, first_recipient.protected).into();
217    let signing_data = SigningData::new(&partially_processed_payload, first_recipient.protected)?;
218    Ok(RecipientProcessingEncoder {
219      partially_processed_payload,
220      signatures: Vec::new(),
221      detached,
222      b64: jwu::extract_b64(first_recipient.protected),
223      state: RecipientProcessingState {
224        signing_data,
225        unprotected_header: first_recipient.unprotected,
226      },
227    })
228  }
229
230  /// Start the process of creating an additional signature entry corresponding to the
231  /// given `recipient`.
232  pub fn add_recipient(
233    self,
234    recipient: Recipient<'unprotected>,
235  ) -> Result<RecipientProcessingEncoder<'payload, 'unprotected>> {
236    // Ensure that the b64 value is consistent across all signatures.
237    let new_b64 = jwu::extract_b64(recipient.protected);
238    if new_b64 != self.b64 {
239      return Err(Error::InvalidParam("b64"));
240    };
241    // Validate headers
242    utils::validate_headers_json_serialization(recipient)?;
243
244    let signing_data = SigningData::new(&self.partially_processed_payload, recipient.protected)?;
245    let state = RecipientProcessingState {
246      signing_data,
247      unprotected_header: recipient.unprotected,
248    };
249    let Self {
250      partially_processed_payload,
251      signatures,
252      detached,
253      b64,
254      ..
255    } = self;
256    Ok(RecipientProcessingEncoder {
257      partially_processed_payload,
258      signatures,
259      detached,
260      b64,
261      state,
262    })
263  }
264
265  /// Finalize the encoding process and obtain a JWS serialized
266  /// according to the General JWS JSON serialization format.
267  pub fn into_jws(self) -> Result<String> {
268    let GeneralJwsEncoder {
269      partially_processed_payload,
270      signatures,
271      detached,
272      ..
273    } = self;
274    let general: General<'_, '_> = {
275      if detached {
276        General {
277          payload: None,
278          signatures,
279        }
280      } else {
281        General {
282          payload: Some(
283            std::str::from_utf8(&partially_processed_payload).map_err(|_| Error::InvalidContent("invalid utf8"))?,
284          ),
285          signatures,
286        }
287      }
288    };
289    general.to_json()
290  }
291}
292
293impl<'payload, 'unprotected> RecipientProcessingEncoder<'payload, 'unprotected> {
294  /// The signing input to be signed for the recipient currently being processed.
295  /// This has been computed according to the
296  /// [JWS Signing Input Formula](https://www.rfc-editor.org/rfc/rfc7797#section-3) using the
297  /// protected header from the latest [`Recipient`] and the payload set in the
298  /// [`GeneralJwsEncoder::new`](GeneralJwsEncoder::new()).
299  pub fn signing_input(&self) -> &[u8] {
300    &self.state.signing_data.signing_input
301  }
302
303  /// Set the signature computed in the manner defined for the algorithm present in the
304  /// recipient's protected header over the [`signing input`](RecipientProcessingEncoder::signing_input()).
305  pub fn set_signature(self, signature: &[u8]) -> GeneralJwsEncoder<'payload, 'unprotected> {
306    let Self {
307      partially_processed_payload,
308      mut signatures,
309      b64,
310      detached,
311      state: RecipientProcessingState {
312        unprotected_header,
313        signing_data,
314      },
315    } = self;
316    let new_signature = signing_data.into_signature(signature, unprotected_header);
317    signatures.push(new_signature);
318    GeneralJwsEncoder {
319      partially_processed_payload,
320      signatures,
321      b64,
322      detached,
323      state: ReadyState {},
324    }
325  }
326}