identity_jose/jws/
header.rs

1// Copyright 2020-2023 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use core::ops::Deref;
5use core::ops::DerefMut;
6use std::collections::BTreeMap;
7
8use serde_json::Value;
9
10use crate::jose::JoseHeader;
11use crate::jws::JwsAlgorithm;
12use crate::jwt::JwtHeader;
13
14/// JSON Web Signature JOSE Header.
15///
16/// [More Info](https://tools.ietf.org/html/rfc7515#section-4)
17#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
18pub struct JwsHeader {
19  /// Common JOSE Header Parameters.
20  #[serde(flatten)]
21  common: JwtHeader,
22  /// Algorithm.
23  ///
24  /// Identifies the cryptographic algorithm used to secure the JWS.
25  ///
26  /// [More Info](https://tools.ietf.org/html/rfc7515#section-4.1.1)
27  #[serde(skip_serializing_if = "Option::is_none")]
28  alg: Option<JwsAlgorithm>,
29  /// Base64url-Encode Payload.
30  ///
31  /// Determines whether the payload is represented in the JWS and the JWS
32  /// signing input as ASCII(BASE64URL(JWS Payload)) or as the JWS Payload
33  /// value itself with no encoding performed.
34  ///
35  /// [More Info](https://tools.ietf.org/html/rfc7797#section-3)
36  ///
37  /// The following table shows the JWS Signing Input computation, depending
38  /// upon the value of this parameter:
39  ///
40  /// +-------+-----------------------------------------------------------+
41  /// | "b64" | JWS Signing Input Formula                                 |
42  /// +-------+-----------------------------------------------------------+
43  /// | true  | ASCII(BASE64URL(UTF8(JWS Protected Header)) || '.' ||     |
44  /// |       | BASE64URL(JWS Payload))                                   |
45  /// |       |                                                           |
46  /// | false | ASCII(BASE64URL(UTF8(JWS Protected Header)) || '.') ||    |
47  /// |       | JWS Payload                                               |
48  /// +-------+-----------------------------------------------------------+
49  #[serde(skip_serializing_if = "Option::is_none")]
50  b64: Option<bool>,
51
52  /// Additional header parameters.
53  #[serde(flatten, skip_serializing_if = "Option::is_none")]
54  custom: Option<BTreeMap<String, Value>>,
55}
56
57impl JwsHeader {
58  /// Create a new empty `JwsHeader`.
59  pub const fn new() -> Self {
60    Self {
61      common: JwtHeader::new(),
62      alg: None,
63      b64: None,
64      custom: None,
65    }
66  }
67
68  /// Returns the value for the algorithm claim (alg).
69  pub fn alg(&self) -> Option<JwsAlgorithm> {
70    self.alg.as_ref().cloned()
71  }
72
73  /// Sets a value for the algorithm claim (alg).
74  pub fn set_alg(&mut self, value: impl Into<JwsAlgorithm>) {
75    self.alg = Some(value.into());
76  }
77
78  /// Returns the value of the base64url-encode payload claim (b64).
79  pub fn b64(&self) -> Option<bool> {
80    self.b64
81  }
82
83  /// Sets a value for the base64url-encode payload claim (b64).
84  pub fn set_b64(&mut self, value: impl Into<bool>) {
85    self.b64 = Some(value.into());
86  }
87
88  /// Returns the additional parameters in the header.
89  pub fn custom(&self) -> Option<&BTreeMap<String, Value>> {
90    self.custom.as_ref()
91  }
92
93  /// Sets additional parameters in the header.
94  pub fn set_custom(&mut self, value: BTreeMap<String, Value>) {
95    self.custom = Some(value)
96  }
97
98  /// Returns `true` if the header contains the given `claim`, `false` otherwise.
99  pub fn has(&self, claim: &str) -> bool {
100    match claim {
101      "alg" => self.alg().is_some(),
102      "b64" => self.b64().is_some(),
103      _ => {
104        self.common.has(claim)
105          || self
106            .custom
107            .as_ref()
108            .map(|custom| custom.get(claim).is_some())
109            .unwrap_or(false)
110      }
111    }
112  }
113
114  /// Returns `true` if none of the fields are set in both `self` and `other`.
115  pub fn is_disjoint(&self, other: &JwsHeader) -> bool {
116    let has_duplicate: bool = self.alg().is_some() && other.alg.is_some() || self.b64.is_some() && other.b64.is_some();
117
118    !has_duplicate && self.common.is_disjoint(other.common()) && self.is_custom_disjoint(other)
119  }
120
121  /// Returns `true` if none of the fields are set in both `self.custom` and `other.custom`.
122  fn is_custom_disjoint(&self, other: &JwsHeader) -> bool {
123    match (&self.custom, &other.custom) {
124      (Some(self_custom), Some(other_custom)) => {
125        for self_key in self_custom.keys() {
126          if other_custom.contains_key(self_key) {
127            return false;
128          }
129        }
130        true
131      }
132      _ => true,
133    }
134  }
135}
136
137impl Deref for JwsHeader {
138  type Target = JwtHeader;
139
140  fn deref(&self) -> &Self::Target {
141    &self.common
142  }
143}
144
145impl DerefMut for JwsHeader {
146  fn deref_mut(&mut self) -> &mut Self::Target {
147    &mut self.common
148  }
149}
150
151impl JoseHeader for JwsHeader {
152  fn common(&self) -> &JwtHeader {
153    self
154  }
155
156  fn has_claim(&self, claim: &str) -> bool {
157    self.has(claim)
158  }
159}
160
161impl Default for JwsHeader {
162  fn default() -> Self {
163    Self::new()
164  }
165}
166
167#[cfg(test)]
168mod tests {
169  use super::*;
170
171  #[test]
172  fn test_custom() {
173    let header1: JwsHeader = serde_json::from_value(serde_json::json!({
174      "alg": "ES256",
175      "b64": false,
176      "test": "tst-value",
177      "test-bool": false
178    }))
179    .unwrap();
180
181    assert_eq!(
182      header1.custom().unwrap().get("test").unwrap().as_str().unwrap(),
183      "tst-value".to_owned()
184    );
185
186    assert!(!header1.custom().unwrap().get("test-bool").unwrap().as_bool().unwrap());
187    assert!(header1.has("test"));
188    assert!(!header1.has("invalid"));
189  }
190
191  #[test]
192  fn test_header_disjoint() {
193    let header1: JwsHeader = serde_json::from_value(serde_json::json!({
194      "alg": "ES256",
195      "b64": false,
196    }))
197    .unwrap();
198    let header2: JwsHeader = serde_json::from_value(serde_json::json!({
199      "alg": "ES256",
200      "crit": ["b64"],
201    }))
202    .unwrap();
203    let header3: JwsHeader = serde_json::from_value(serde_json::json!({
204      "kid": "kid value",
205      "cty": "mediatype",
206      "custom": "test value",
207    }))
208    .unwrap();
209    let header4: JwsHeader = serde_json::from_value(serde_json::json!({
210      "custom": "test value",
211    }))
212    .unwrap();
213
214    assert!(!header1.is_disjoint(&header2));
215    assert!(header1.is_disjoint(&header3));
216    assert!(header2.is_disjoint(&header3));
217    assert!(header1.is_disjoint(&JwsHeader::new()));
218    assert!(!header4.is_disjoint(&header3));
219    assert!(header4.is_disjoint(&header2));
220  }
221}