identity_jose/jwk/
key.rs

1// Copyright 2020-2025 IOTA Stiftung, Fondazione LINKS
2// SPDX-License-Identifier: Apache-2.0
3
4use crypto::hashes::sha::SHA256;
5use crypto::hashes::sha::SHA256_LEN;
6use identity_core::common::Url;
7use zeroize::Zeroize;
8
9use crate::error::Error;
10use crate::error::Result;
11use crate::jwk::EcCurve;
12use crate::jwk::EcxCurve;
13use crate::jwk::EdCurve;
14use crate::jwk::JwkOperation;
15use crate::jwk::JwkParams;
16use crate::jwk::JwkParamsAkp;
17use crate::jwk::JwkParamsEc;
18use crate::jwk::JwkParamsOct;
19use crate::jwk::JwkParamsOkp;
20use crate::jwk::JwkParamsRsa;
21use crate::jwk::JwkType;
22use crate::jwk::JwkUse;
23use crate::jwu::encode_b64;
24
25/// A SHA256 JSON Web Key Thumbprint.
26pub type JwkThumbprintSha256 = [u8; SHA256_LEN];
27
28/// JSON Web Key.
29///
30/// [More Info](https://tools.ietf.org/html/rfc7517#section-4)
31#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
32pub struct Jwk {
33  /// Key Type.
34  ///
35  /// Identifies the cryptographic algorithm family used with the key.
36  ///
37  /// [More Info](https://tools.ietf.org/html/rfc7517#section-4.1)
38  pub(super) kty: JwkType,
39  /// Public Key Use.
40  ///
41  /// Identifies the intended use of the public key.
42  ///
43  /// [More Info](https://tools.ietf.org/html/rfc7517#section-4.2)
44  #[serde(rename = "use", skip_serializing_if = "Option::is_none")]
45  pub(super) use_: Option<JwkUse>,
46  /// Key Operations.
47  ///
48  /// Identifies the operation(s) for which the key is intended to be used.
49  ///
50  /// [More Info](https://tools.ietf.org/html/rfc7517#section-4.3)
51  #[serde(skip_serializing_if = "Option::is_none")]
52  pub(super) key_ops: Option<Vec<JwkOperation>>,
53  /// Algorithm.
54  ///
55  /// Identifies the algorithm intended for use with the key.
56  ///
57  /// [More Info](https://tools.ietf.org/html/rfc7517#section-4.4)
58  #[serde(skip_serializing_if = "Option::is_none")]
59  pub(super) alg: Option<String>,
60  /// Key ID.
61  ///
62  /// Used to match a specific key among a set of keys within a JWK Set.
63  ///
64  /// [More Info](https://tools.ietf.org/html/rfc7517#section-4.5)
65  #[serde(skip_serializing_if = "Option::is_none")]
66  pub(super) kid: Option<String>,
67  /// X.509 URL.
68  ///
69  /// A URI that refers to a resource for an X.509 public key certificate or
70  /// certificate chain.
71  ///
72  /// [More Info](https://tools.ietf.org/html/rfc7517#section-4.6)
73  #[serde(skip_serializing_if = "Option::is_none")]
74  pub(super) x5u: Option<Url>,
75  /// X.509 Certificate Chain.
76  ///
77  /// Contains a chain of one or more PKIX certificates.
78  ///
79  /// [More Info](https://tools.ietf.org/html/rfc7517#section-4.7)
80  #[serde(skip_serializing_if = "Option::is_none")]
81  pub(super) x5c: Option<Vec<String>>,
82  /// X.509 Certificate SHA-1 Thumbprint.
83  ///
84  /// A base64url-encoded SHA-1 thumbprint of the DER encoding of an X.509
85  /// certificate.
86  ///
87  /// [More Info](https://tools.ietf.org/html/rfc7517#section-4.8)
88  #[serde(skip_serializing_if = "Option::is_none")]
89  pub(super) x5t: Option<String>,
90  /// X.509 Certificate SHA-256 Thumbprint.
91  ///
92  /// A base64url-encoded SHA-256 thumbprint of the DER encoding of an X.509
93  /// certificate.
94  ///
95  /// [More Info](https://tools.ietf.org/html/rfc7517#section-4.9)
96  #[serde(rename = "x5t#S256", skip_serializing_if = "Option::is_none")]
97  pub(super) x5t_s256: Option<String>,
98  /// Type-Specific Key Properties.
99  ///
100  /// [More Info](https://tools.ietf.org/html/rfc7517#section-4)
101  #[serde(flatten)]
102  pub(super) params: JwkParams,
103}
104
105impl Jwk {
106  /// Creates a new `Jwk` with the given `kty` parameter.
107  pub const fn new(kty: JwkType) -> Self {
108    Self {
109      kty,
110      use_: None,
111      key_ops: None,
112      alg: None,
113      kid: None,
114      x5u: None,
115      x5c: None,
116      x5t: None,
117      x5t_s256: None,
118      params: JwkParams::new(kty),
119    }
120  }
121
122  /// Creates a new `Jwk` from the given params.
123  pub fn from_params(params: impl Into<JwkParams>) -> Self {
124    let params: JwkParams = params.into();
125
126    Self {
127      kty: params.kty(),
128      use_: None,
129      key_ops: None,
130      alg: None,
131      kid: None,
132      x5u: None,
133      x5c: None,
134      x5t: None,
135      x5t_s256: None,
136      params,
137    }
138  }
139
140  /// Returns the value for the key type parameter (kty).
141  pub fn kty(&self) -> JwkType {
142    self.kty
143  }
144
145  /// Sets a value for the key type parameter (kty).
146  ///
147  /// Removes any previously set `params`.
148  pub fn set_kty(&mut self, value: impl Into<JwkType>) {
149    self.kty = value.into();
150    self.params = JwkParams::new(self.kty);
151  }
152
153  /// Returns the value for the use property (use).
154  pub fn use_(&self) -> Option<JwkUse> {
155    self.use_
156  }
157
158  /// Sets a value for the key use parameter (use).
159  pub fn set_use(&mut self, value: impl Into<JwkUse>) {
160    self.use_ = Some(value.into());
161  }
162
163  /// Returns the value for the key operations parameter (key_ops).
164  pub fn key_ops(&self) -> Option<&[JwkOperation]> {
165    self.key_ops.as_deref()
166  }
167
168  /// Sets values for the key operations parameter (key_ops).
169  pub fn set_key_ops(&mut self, value: impl IntoIterator<Item = impl Into<JwkOperation>>) {
170    self.key_ops = Some(value.into_iter().map(Into::into).collect());
171  }
172
173  /// Returns the value for the algorithm property (alg).
174  pub fn alg(&self) -> Option<&str> {
175    self.alg.as_deref()
176  }
177
178  /// Sets a value for the algorithm property (alg).
179  pub fn set_alg(&mut self, value: impl Into<String>) {
180    self.alg = Some(value.into());
181  }
182
183  /// Returns the value of the key ID property (kid).
184  pub fn kid(&self) -> Option<&str> {
185    self.kid.as_deref()
186  }
187
188  /// Sets a value for the key ID property (kid).
189  pub fn set_kid(&mut self, value: impl Into<String>) {
190    self.kid = Some(value.into());
191  }
192
193  /// Returns the value of the X.509 URL property (x5u).
194  pub fn x5u(&self) -> Option<&Url> {
195    self.x5u.as_ref()
196  }
197
198  /// Sets a value for the X.509 URL property (x5u).
199  pub fn set_x5u(&mut self, value: impl Into<Url>) {
200    self.x5u = Some(value.into());
201  }
202
203  /// Returns the value of the X.509 certificate chain property (x5c).
204  pub fn x5c(&self) -> Option<&[String]> {
205    self.x5c.as_deref()
206  }
207
208  /// Sets values for the X.509 certificate chain property (x5c).
209  pub fn set_x5c(&mut self, value: impl IntoIterator<Item = impl Into<String>>) {
210    self.x5c = Some(value.into_iter().map(Into::into).collect());
211  }
212
213  /// Returns the value of the X.509 certificate SHA-1 thumbprint property
214  /// (x5t).
215  pub fn x5t(&self) -> Option<&str> {
216    self.x5t.as_deref()
217  }
218
219  /// Sets a value for the X.509 certificate SHA-1 thumbprint property (x5t).
220  pub fn set_x5t(&mut self, value: impl Into<String>) {
221    self.x5t = Some(value.into());
222  }
223
224  /// Returns the value of the X.509 certificate SHA-256 thumbprint property
225  /// (x5t#S256).
226  pub fn x5t_s256(&self) -> Option<&str> {
227    self.x5t_s256.as_deref()
228  }
229
230  /// Sets a value for the X.509 certificate SHA-256 thumbprint property
231  /// (x5t#S256).
232  pub fn set_x5t_s256(&mut self, value: impl Into<String>) {
233    self.x5t_s256 = Some(value.into());
234  }
235
236  /// Returns a reference to the custom JWK properties.
237  pub fn params(&self) -> &JwkParams {
238    &self.params
239  }
240
241  /// Returns a mutable reference to the custom JWK properties.
242  pub fn params_mut(&mut self) -> &mut JwkParams {
243    &mut self.params
244  }
245
246  /// Sets the value of the custom JWK properties.
247  ///
248  /// The passed `params` must be appropriate for the key type (`kty`), an error is returned otherwise.
249  ///
250  /// If you want to set `params` unchecked, use [`set_params_unchecked`](Self::set_params_unchecked).
251  pub fn set_params(&mut self, params: impl Into<JwkParams>) -> Result<()> {
252    match (self.kty, params.into()) {
253      (JwkType::Ec, value @ JwkParams::Ec(_)) => {
254        self.set_params_unchecked(value);
255      }
256      (JwkType::Rsa, value @ JwkParams::Rsa(_)) => {
257        self.set_params_unchecked(value);
258      }
259      (JwkType::Oct, value @ JwkParams::Oct(_)) => {
260        self.set_params_unchecked(value);
261      }
262      (JwkType::Okp, value @ JwkParams::Okp(_)) => {
263        self.set_params_unchecked(value);
264      }
265      (JwkType::Akp, value @ JwkParams::Akp(_)) => {
266        self.set_params_unchecked(value);
267      }
268      (_, _) => {
269        return Err(Error::InvalidParam("`params` type does not match `kty`"));
270      }
271    }
272    Ok(())
273  }
274
275  /// Sets the value of the custom JWK properties.
276  ///
277  /// Does not check whether the passed params are appropriate for the set key type (`kty`).
278  pub fn set_params_unchecked(&mut self, value: impl Into<JwkParams>) {
279    self.params = value.into();
280  }
281
282  /// Returns the [`JwkParamsEc`] in this JWK if it is of type `Ec`.
283  pub fn try_ec_params(&self) -> Result<&JwkParamsEc> {
284    match self.params() {
285      JwkParams::Ec(params) => Ok(params),
286      _ => Err(Error::KeyError("Ec")),
287    }
288  }
289
290  /// Returns a mutable reference to the [`JwkParamsEc`] in this JWK if it is of type `Ec`.
291  pub fn try_ec_params_mut(&mut self) -> Result<&mut JwkParamsEc> {
292    match self.params_mut() {
293      JwkParams::Ec(params) => Ok(params),
294      _ => Err(Error::KeyError("Ec")),
295    }
296  }
297
298  /// Returns the [`JwkParamsRsa`] in this JWK if it is of type `Rsa`.
299  pub fn try_rsa_params(&self) -> Result<&JwkParamsRsa> {
300    match self.params() {
301      JwkParams::Rsa(params) => Ok(params),
302      _ => Err(Error::KeyError("Rsa")),
303    }
304  }
305
306  /// Returns a mutable reference to the [`JwkParamsRsa`] in this JWK if it is of type `Rsa`.
307  pub fn try_rsa_params_mut(&mut self) -> Result<&mut JwkParamsRsa> {
308    match self.params_mut() {
309      JwkParams::Rsa(params) => Ok(params),
310      _ => Err(Error::KeyError("Rsa")),
311    }
312  }
313
314  /// Returns the [`JwkParamsOct`] in this JWK if it is of type `Oct`.
315  pub fn try_oct_params(&self) -> Result<&JwkParamsOct> {
316    match self.params() {
317      JwkParams::Oct(params) => Ok(params),
318      _ => Err(Error::KeyError("Oct")),
319    }
320  }
321
322  /// Returns a mutable reference to the [`JwkParamsOct`] in this JWK if it is of type `Oct`.
323  pub fn try_oct_params_mut(&mut self) -> Result<&mut JwkParamsOct> {
324    match self.params_mut() {
325      JwkParams::Oct(params) => Ok(params),
326      _ => Err(Error::KeyError("Oct")),
327    }
328  }
329
330  /// Returns the [`JwkParamsOkp`] in this JWK if it is of type `Okp`.
331  pub fn try_okp_params(&self) -> Result<&JwkParamsOkp> {
332    match self.params() {
333      JwkParams::Okp(params) => Ok(params),
334      _ => Err(Error::KeyError("Okp")),
335    }
336  }
337
338  /// Returns a mutable reference to the [`JwkParamsOkp`] in this JWK if it is of type `Okp`.
339  pub fn try_okp_params_mut(&mut self) -> Result<&mut JwkParamsOkp> {
340    match self.params_mut() {
341      JwkParams::Okp(params) => Ok(params),
342      _ => Err(Error::KeyError("Okp")),
343    }
344  }
345
346  /// Returns the [`JwkParamsAkp`] in this JWK if it is of type `Akp`.
347  pub fn try_akp_params(&self) -> Result<&JwkParamsAkp> {
348    match self.params() {
349      JwkParams::Akp(params) => Ok(params),
350      _ => Err(Error::KeyError("Akp")),
351    }
352  }
353
354  /// Returns a mutable reference to the [`JwkParamsAkp`] in this JWK if it is of type `Akp`.
355  pub fn try_akp_params_mut(&mut self) -> Result<&mut JwkParamsAkp> {
356    match self.params_mut() {
357      JwkParams::Akp(params) => Ok(params),
358      _ => Err(Error::KeyError("Akp")),
359    }
360  }
361
362  // ===========================================================================
363  // Thumbprint
364  // ===========================================================================
365
366  /// Creates a Thumbprint of the JSON Web Key according to [RFC7638](https://tools.ietf.org/html/rfc7638).
367  ///
368  /// `SHA2-256` is used as the hash function *H*.
369  ///
370  /// The thumbprint is returned as a base64url-encoded string.
371  pub fn thumbprint_sha256_b64(&self) -> String {
372    encode_b64(self.thumbprint_sha256())
373  }
374
375  /// Creates a Thumbprint of the JSON Web Key according to [RFC7638](https://tools.ietf.org/html/rfc7638).
376  ///
377  /// `SHA2-256` is used as the hash function *H*.
378  ///
379  /// The thumbprint is returned as an unencoded array of bytes.
380  pub fn thumbprint_sha256(&self) -> JwkThumbprintSha256 {
381    let json: String = self.thumbprint_hash_input();
382
383    let mut out: JwkThumbprintSha256 = Default::default();
384
385    SHA256(json.as_bytes(), &mut out);
386
387    out
388  }
389
390  /// Creates the JSON string of the JSON Web Key according to [RFC7638](https://tools.ietf.org/html/rfc7638),
391  /// which is used as the input for the JWK thumbprint hashing procedure.
392  /// This can be used as input for a custom hash function.
393  pub fn thumbprint_hash_input(&self) -> String {
394    let kty: &str = self.kty.name();
395
396    match self.params() {
397      JwkParams::Ec(JwkParamsEc { crv, x, y, .. }) => {
398        format!(r#"{{"crv":"{crv}","kty":"{kty}","x":"{x}","y":"{y}"}}"#)
399      }
400      JwkParams::Rsa(JwkParamsRsa { e, n, .. }) => {
401        format!(r#"{{"e":"{e}","kty":"{kty}","n":"{n}"}}"#)
402      }
403      JwkParams::Oct(JwkParamsOct { k }) => {
404        format!(r#"{{"k":"{k}","kty":"{kty}"}}"#)
405      }
406      // Implementation according to https://www.rfc-editor.org/rfc/rfc8037#section-2.
407      JwkParams::Okp(JwkParamsOkp { crv, x, .. }) => {
408        format!(r#"{{"crv":"{crv}","kty":"{kty}","x":"{x}"}}"#)
409      }
410      JwkParams::Akp(JwkParamsAkp { public, .. }) => {
411        format!(r#"{{"kty":"{kty}","pub":"{public}"}}"#)
412      }
413    }
414  }
415
416  // ===========================================================================
417  // Validations
418  // ===========================================================================
419
420  /// Checks if the `alg` claim of the JWK is equal to `expected`.
421  pub fn check_alg(&self, expected: impl AsRef<str>) -> Result<()> {
422    match self.alg() {
423      Some(value) if value == expected.as_ref() => Ok(()),
424      Some(_) => Err(Error::InvalidClaim("alg")),
425      None => Ok(()),
426    }
427  }
428
429  /// Returns the [`EcCurve`] of this JWK if it is of type `Ec`.
430  pub fn try_ec_curve(&self) -> Result<EcCurve> {
431    match self.params() {
432      JwkParams::Ec(inner) => inner.try_ec_curve(),
433      _ => Err(Error::KeyError("Ec Curve")),
434    }
435  }
436
437  /// Returns the [`EdCurve`] of this JWK if it is of type `Okp`.
438  pub fn try_ed_curve(&self) -> Result<EdCurve> {
439    match self.params() {
440      JwkParams::Okp(inner) => inner.try_ed_curve(),
441      _ => Err(Error::KeyError("Ed Curve")),
442    }
443  }
444
445  /// Returns the [`EcxCurve`] of this JWK if it is of type `Okp`.
446  pub fn try_ecx_curve(&self) -> Result<EcxCurve> {
447    match self.params() {
448      JwkParams::Okp(inner) => inner.try_ecx_curve(),
449      _ => Err(Error::KeyError("Ecx Curve")),
450    }
451  }
452
453  /// Returns `true` if _all_ private key components of the key are unset, `false` otherwise.
454  pub fn is_public(&self) -> bool {
455    self.params.is_public()
456  }
457
458  /// Returns `true` if _all_ private key components of the key are set, `false` otherwise.
459  pub fn is_private(&self) -> bool {
460    match self.params() {
461      JwkParams::Ec(params) => params.is_private(),
462      JwkParams::Rsa(params) => params.is_private(),
463      JwkParams::Oct(_) => true,
464      JwkParams::Okp(params) => params.is_private(),
465      JwkParams::Akp(params) => params.is_private(),
466    }
467  }
468
469  /// Returns a clone of the Jwk with _all_ private key components unset.
470  ///
471  /// The `None` variant is returned when `kty = oct` as this key type is not considered public by this library.
472  pub fn to_public(&self) -> Option<Jwk> {
473    let mut public: Jwk = Jwk::from_params(self.params().to_public()?);
474
475    if let Some(value) = self.use_() {
476      public.set_use(value);
477    }
478
479    if let Some(value) = self.key_ops() {
480      public.set_key_ops(value.iter().map(|op| op.invert()));
481    }
482
483    if let Some(value) = self.alg() {
484      public.set_alg(value);
485    }
486
487    if let Some(value) = self.kid() {
488      public.set_kid(value);
489    }
490
491    Some(public)
492  }
493
494  /// Removes all private key components.
495  /// In the case of [JwkParams::Oct], this method does nothing.
496  #[inline(always)]
497  pub fn strip_private(&mut self) {
498    self.params_mut().strip_private();
499  }
500
501  /// Returns this key with _all_ private key components unset.
502  /// In the case of [JwkParams::Oct], this method returns [None].
503  pub fn into_public(mut self) -> Option<Self> {
504    if matches!(&self.params, JwkParams::Oct(_)) {
505      None
506    } else {
507      self.params.strip_private();
508      Some(self)
509    }
510  }
511}
512
513impl Zeroize for Jwk {
514  fn zeroize(&mut self) {
515    self.params.zeroize();
516  }
517}
518
519impl Drop for Jwk {
520  fn drop(&mut self) {
521    self.zeroize();
522  }
523}
524
525#[cfg(test)]
526mod tests {
527  use identity_core::convert::FromJson;
528
529  use super::Jwk;
530
531  #[test]
532  fn into_public_and_to_public_return_the_same() {
533    let priv_jwk = Jwk::from_json_slice(
534      r#"
535      {
536        "kty": "OKP",
537        "crv": "Ed25519",
538        "d": "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A",
539        "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"
540      }
541    "#,
542    )
543    .unwrap();
544
545    assert_eq!(priv_jwk.to_public(), priv_jwk.into_public());
546  }
547}