identity_jose/jwk/
key_hybrid.rs

1// Copyright 2020-2025 IOTA Stiftung, Fondazione LINKS
2// SPDX-License-Identifier: Apache-2.0
3
4use std::ops::Deref;
5use std::str::FromStr;
6
7use identity_core::common::Url;
8
9use crate::error::Error;
10use crate::error::Result;
11use crate::jwk::Jwk;
12use crate::jwk::JwkOperation;
13use crate::jwk::JwkParams;
14use crate::jwk::JwkParamsAkp;
15use crate::jwk::JwkParamsEc;
16use crate::jwk::JwkParamsOct;
17use crate::jwk::JwkParamsOkp;
18use crate::jwk::JwkParamsRsa;
19use crate::jwk::JwkType;
20use crate::jwk::JwkUse;
21use crate::jws::JwsAlgorithm;
22
23/// A post-quantum key encoded as JWK.
24#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
25#[serde(transparent)]
26pub struct PostQuantumJwk(Jwk);
27
28impl PostQuantumJwk {
29  /// Creates a new `[PostQuantumJwk]`.
30  pub fn new(paramsakp: JwkParamsAkp) -> Self {
31    Self(Jwk {
32      kty: JwkType::Akp,
33      use_: None,
34      key_ops: None,
35      alg: None,
36      kid: None,
37      x5u: None,
38      x5c: None,
39      x5t: None,
40      x5t_s256: None,
41      params: JwkParams::Akp(paramsakp),
42    })
43  }
44
45  /// Creates a new `[PostQuantumJwk]` from the given kty.
46  pub fn from_kty(kty: impl Into<JwkType>) -> Result<Self> {
47    let kty: JwkType = kty.into();
48    if kty != JwkType::Akp {
49      return Err(Error::KeyError("PostQuantumJwk can only be created with JwkType::Akp"));
50    }
51
52    Ok(Self(Jwk {
53      kty,
54      use_: None,
55      key_ops: None,
56      alg: None,
57      kid: None,
58      x5u: None,
59      x5c: None,
60      x5t: None,
61      x5t_s256: None,
62      params: JwkParams::new(kty),
63    }))
64  }
65
66  /// Creates a new `[PostQuantumJwk]` from the given params.
67  pub fn from_params(params: impl Into<JwkParams>) -> Result<Self> {
68    let params: JwkParams = params.into();
69
70    if params.kty() != JwkType::Akp {
71      return Err(Error::KeyError(
72        "PostQuantumJwk can only be created from a JwkParamsAkp",
73      ));
74    }
75
76    Ok(Self(Jwk {
77      kty: params.kty(),
78      use_: None,
79      key_ops: None,
80      alg: None,
81      kid: None,
82      x5u: None,
83      x5c: None,
84      x5t: None,
85      x5t_s256: None,
86      params,
87    }))
88  }
89
90  /// Sets a value for the key use parameter (use).
91  pub fn set_use(&mut self, value: impl Into<JwkUse>) {
92    self.0.set_use(value)
93  }
94
95  /// Sets values for the key operations parameter (key_ops).
96  pub fn set_key_ops(&mut self, value: impl IntoIterator<Item = impl Into<JwkOperation>>) {
97    self.0.set_key_ops(value);
98  }
99
100  /// Sets a value for the algorithm property (alg).
101  pub fn set_alg(&mut self, value: impl Into<String>) -> Result<()> {
102    let alg = JwsAlgorithm::from_str(&value.into()).map_err(|_| Error::InvalidParam("Invalid JWS algorithm"))?;
103    if !is_post_quantum(&alg) {
104      return Err(Error::InvalidParam(
105        "PostQuantumJwk can only be created with a post-quantum JWS algorithm",
106      ));
107    }
108    self.0.set_alg(alg.to_string());
109    Ok(())
110  }
111
112  /// Sets a value for the key ID property (kid).
113  pub fn set_kid(&mut self, value: impl Into<String>) {
114    self.0.set_kid(value);
115  }
116
117  /// Sets a value for the X.509 URL property (x5u).
118  pub fn set_x5u(&mut self, value: impl Into<Url>) {
119    self.0.set_x5u(value);
120  }
121
122  /// Sets values for the X.509 certificate chain property (x5c).
123  pub fn set_x5c(&mut self, value: impl IntoIterator<Item = impl Into<String>>) {
124    self.0.set_x5c(value);
125  }
126
127  /// Sets a value for the X.509 certificate SHA-1 thumbprint property (x5t).
128  pub fn set_x5t(&mut self, value: impl Into<String>) {
129    self.0.set_x5t(value);
130  }
131
132  /// Sets a value for the X.509 certificate SHA-256 thumbprint property
133  /// (x5t#S256).
134  pub fn set_x5t_s256(&mut self, value: impl Into<String>) {
135    self.0.set_x5t_s256(value);
136  }
137
138  /// Sets the value of the custom inner JWK properties. Only for `Akp` keys.
139  pub fn set_params(&mut self, params: impl Into<JwkParams>) -> Result<()> {
140    let params: JwkParams = params.into();
141    if params.kty() != JwkType::Akp {
142      return Err(Error::InvalidParam("`params` type does not match `Akp`"));
143    }
144    self.0.set_params_unchecked(params);
145    Ok(())
146  }
147
148  /// Returns the [`JwkParamsAkp`] in this JWK.
149  pub fn akp_params(&self) -> &JwkParamsAkp {
150    self
151      .0
152      .try_akp_params()
153      .expect("PostQuantumJwk must have JwkParamsAkp as params")
154  }
155
156  /// Returns a mutable reference to the [`JwkParamsAkp`] in this JWK.
157  pub fn akp_params_mut(&mut self) -> &mut JwkParamsAkp {
158    self
159      .0
160      .try_akp_params_mut()
161      .expect("PostQuantumJwk must have JwkParamsAkp as params")
162  }
163
164  /// Removes all private key components.
165  #[inline(always)]
166  pub fn strip_private(&mut self) {
167    self.0.params_mut().strip_private();
168  }
169
170  /// Returns this key with _all_ private key components unset.
171  pub fn into_public(mut self) -> Option<Self> {
172    self.0.params.strip_private();
173    Some(self)
174  }
175}
176
177impl Deref for PostQuantumJwk {
178  type Target = Jwk;
179
180  fn deref(&self) -> &Self::Target {
181    &self.0
182  }
183}
184
185impl TryFrom<Jwk> for PostQuantumJwk {
186  type Error = Error;
187
188  fn try_from(value: Jwk) -> Result<Self> {
189    let alg = JwsAlgorithm::from_str(value.alg().ok_or(Error::KeyError("Missing JWK algorithm"))?)?;
190    if value.kty != JwkType::Akp && !is_post_quantum(&alg) {
191      return Err(Error::KeyError(
192        "PostQuantumJwk can only be created from a post quantum Jwk",
193      ));
194    }
195
196    Ok(Self(value))
197  }
198}
199
200impl AsRef<Jwk> for PostQuantumJwk {
201  fn as_ref(&self) -> &Jwk {
202    &self.0
203  }
204}
205
206impl From<PostQuantumJwk> for Jwk {
207  fn from(value: PostQuantumJwk) -> Self {
208    value.0
209  }
210}
211
212/// Wrapper to the [`Jwk`] structure to enforce the exclusive use of traditional JWK encoded keys in the
213/// [`CompositeJwk`]
214#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
215#[serde(transparent)]
216pub struct TraditionalJwk(Jwk);
217
218impl TraditionalJwk {
219  /// Creates a new `[TraditionalJwk]` from a `[JwkParamsOkp]`.
220  pub fn new(kty: JwkParamsOkp) -> Self {
221    Self(Jwk {
222      kty: JwkType::Okp,
223      use_: None,
224      key_ops: None,
225      alg: None,
226      kid: None,
227      x5u: None,
228      x5c: None,
229      x5t: None,
230      x5t_s256: None,
231      params: JwkParams::Okp(kty),
232    })
233  }
234
235  /// Creates a new `[TraditionalJwk]` from the given kty.
236  pub fn from_kty(kty: JwkType) -> Result<Self> {
237    if kty == JwkType::Akp {
238      return Err(Error::KeyError(
239        "TraditionalJwk can only be created with different from JwkType::Akp",
240      ));
241    }
242
243    Ok(Self(Jwk {
244      kty,
245      use_: None,
246      key_ops: None,
247      alg: None,
248      kid: None,
249      x5u: None,
250      x5c: None,
251      x5t: None,
252      x5t_s256: None,
253      params: JwkParams::new(kty),
254    }))
255  }
256
257  /// Creates a new `[TraditionalJwk]` from the given params.
258  pub fn from_params(params: impl Into<JwkParams>) -> Result<Self> {
259    let params: JwkParams = params.into();
260
261    if params.kty() == JwkType::Akp {
262      return Err(Error::KeyError(
263        "TraditionalJwk can only be created with different from JwkType::Akp",
264      ));
265    }
266
267    Ok(Self(Jwk {
268      kty: params.kty(),
269      use_: None,
270      key_ops: None,
271      alg: None,
272      kid: None,
273      x5u: None,
274      x5c: None,
275      x5t: None,
276      x5t_s256: None,
277      params,
278    }))
279  }
280
281  /// Sets a value for the key use parameter (use).
282  pub fn set_use(&mut self, value: impl Into<JwkUse>) {
283    self.0.set_use(value)
284  }
285
286  /// Sets values for the key operations parameter (key_ops).
287  pub fn set_key_ops(&mut self, value: impl IntoIterator<Item = impl Into<JwkOperation>>) {
288    self.0.set_key_ops(value);
289  }
290
291  /// Sets a value for the algorithm property (alg).
292  pub fn set_alg(&mut self, value: impl Into<String>) -> Result<()> {
293    let alg = JwsAlgorithm::from_str(&value.into()).map_err(|_| Error::InvalidParam("Invalid JWS algorithm"))?;
294    if is_post_quantum(&alg) {
295      return Err(Error::InvalidParam(
296        "TraditionalJwk can only be created with a traditional JWS algorithm",
297      ));
298    }
299    self.0.set_alg(alg.to_string());
300    Ok(())
301  }
302
303  /// Sets a value for the key ID property (kid).
304  pub fn set_kid(&mut self, value: impl Into<String>) {
305    self.0.set_kid(value);
306  }
307
308  /// Sets a value for the X.509 URL property (x5u).
309  pub fn set_x5u(&mut self, value: impl Into<Url>) {
310    self.0.set_x5u(value);
311  }
312
313  /// Sets values for the X.509 certificate chain property (x5c).
314  pub fn set_x5c(&mut self, value: impl IntoIterator<Item = impl Into<String>>) {
315    self.0.set_x5c(value);
316  }
317
318  /// Sets a value for the X.509 certificate SHA-1 thumbprint property (x5t).
319  pub fn set_x5t(&mut self, value: impl Into<String>) {
320    self.0.set_x5t(value);
321  }
322
323  /// Sets a value for the X.509 certificate SHA-256 thumbprint property
324  /// (x5t#S256).
325  pub fn set_x5t_s256(&mut self, value: impl Into<String>) {
326    self.0.set_x5t_s256(value);
327  }
328
329  /// Sets the value of the custom JWK properties.
330  ///
331  /// The passed `params` must be appropriate for the key type (`kty`), an error is returned otherwise.
332  ///
333  /// If you want to set `params` unchecked, use [`set_params_unchecked`](Self::set_params_unchecked).
334  pub fn set_params(&mut self, params: impl Into<JwkParams>) -> Result<()> {
335    self.0.set_params(params)
336  }
337
338  /// Sets the value of the custom JWK properties.
339  ///
340  /// Does not check whether the passed params are appropriate for the set key type (`kty`).
341  pub fn set_params_unchecked(&mut self, value: impl Into<JwkParams>) {
342    self.0.set_params_unchecked(value)
343  }
344
345  /// Returns a mutable reference to the [`JwkParamsEc`] in this inner JWK if it is of type `Ec`.
346  pub fn try_ec_params_mut(&mut self) -> Result<&mut JwkParamsEc> {
347    self.0.try_ec_params_mut()
348  }
349
350  /// Returns a mutable reference to the [`JwkParamsRsa`] in this inner JWK if it is of type `Rsa`.
351  pub fn try_rsa_params_mut(&mut self) -> Result<&mut JwkParamsRsa> {
352    self.0.try_rsa_params_mut()
353  }
354
355  /// Returns a mutable reference to the [`JwkParamsOct`] in this JWK if it is of type `Oct`.
356  pub fn try_oct_params_mut(&mut self) -> Result<&mut JwkParamsOct> {
357    self.0.try_oct_params_mut()
358  }
359
360  /// Returns a mutable reference to the [`JwkParamsOkp`] in this inner JWK if it is of type `Okp`.
361  pub fn try_okp_params_mut(&mut self) -> Result<&mut JwkParamsOkp> {
362    self.0.try_okp_params_mut()
363  }
364
365  /// Removes all private key components.
366  /// In the case of [JwkParams::Oct], this method does nothing.
367  #[inline(always)]
368  pub fn strip_private(&mut self) {
369    self.0.params_mut().strip_private();
370  }
371
372  /// Returns this key with _all_ private key components unset.
373  /// In the case of [JwkParams::Oct], this method returns [None].
374  pub fn into_public(mut self) -> Option<Self> {
375    if matches!(&self.params, JwkParams::Oct(_)) {
376      None
377    } else {
378      self.0.params.strip_private();
379      Some(self)
380    }
381  }
382}
383
384impl Deref for TraditionalJwk {
385  type Target = Jwk;
386
387  fn deref(&self) -> &Self::Target {
388    &self.0
389  }
390}
391
392impl TryFrom<Jwk> for TraditionalJwk {
393  type Error = Error;
394
395  fn try_from(value: Jwk) -> Result<Self> {
396    let alg = JwsAlgorithm::from_str(value.alg().ok_or(Error::KeyError("Missing JWK algorithm"))?)?;
397    if value.kty == JwkType::Akp && is_post_quantum(&alg) {
398      return Err(Error::KeyError(
399        "TraditionalJwk can only be created from a traditional Jwk",
400      ));
401    }
402    Ok(Self(value))
403  }
404}
405
406impl AsRef<Jwk> for TraditionalJwk {
407  fn as_ref(&self) -> &Jwk {
408    &self.0
409  }
410}
411
412impl From<TraditionalJwk> for Jwk {
413  fn from(value: TraditionalJwk) -> Self {
414    value.0
415  }
416}
417
418fn is_post_quantum(alg: &JwsAlgorithm) -> bool {
419  matches!(
420    alg,
421    JwsAlgorithm::FALCON1024
422      | JwsAlgorithm::FALCON512
423      | JwsAlgorithm::ML_DSA_44
424      | JwsAlgorithm::ML_DSA_65
425      | JwsAlgorithm::ML_DSA_87
426      | JwsAlgorithm::SLH_DSA_SHA2_128s
427      | JwsAlgorithm::SLH_DSA_SHAKE_128s
428      | JwsAlgorithm::SLH_DSA_SHA2_128f
429      | JwsAlgorithm::SLH_DSA_SHAKE_128f
430      | JwsAlgorithm::SLH_DSA_SHA2_192s
431      | JwsAlgorithm::SLH_DSA_SHAKE_192s
432      | JwsAlgorithm::SLH_DSA_SHA2_192f
433      | JwsAlgorithm::SLH_DSA_SHAKE_192f
434      | JwsAlgorithm::SLH_DSA_SHA2_256s
435      | JwsAlgorithm::SLH_DSA_SHAKE_256s
436      | JwsAlgorithm::SLH_DSA_SHA2_256f
437      | JwsAlgorithm::SLH_DSA_SHAKE_256f
438  )
439}