Skip to main content

sd_jwt_payload/
key_binding_jwt_claims.rs

1// Copyright 2020-2024 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::jwt::Jwt;
5use crate::Error;
6use crate::Hasher;
7use crate::JsonObject;
8use crate::JwsSigner;
9use crate::SdJwt;
10use crate::SHA_ALG_NAME;
11use anyhow::Context as _;
12use serde::Deserialize;
13use serde::Serialize;
14use serde_json::Value;
15use std::borrow::Cow;
16use std::fmt::Display;
17use std::ops::Deref;
18use std::str::FromStr;
19
20pub const KB_JWT_HEADER_TYP: &str = "kb+jwt";
21
22/// Representation of a [KB-JWT](https://www.rfc-editor.org/rfc/rfc9901.html#name-key-binding-jwt).
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct KeyBindingJwt(Jwt<KeyBindingJwtClaims>);
25
26impl Display for KeyBindingJwt {
27  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28    write!(f, "{}", &self.0)
29  }
30}
31
32impl FromStr for KeyBindingJwt {
33  type Err = Error;
34  fn from_str(s: &str) -> Result<Self, Self::Err> {
35    let jwt = Jwt::<KeyBindingJwtClaims>::from_str(s)?;
36    let valid_jwt_type = jwt.header.get("typ").is_some_and(|typ| typ == KB_JWT_HEADER_TYP);
37    if !valid_jwt_type {
38      return Err(Error::DeserializationError(format!(
39        "invalid KB-JWT: typ must be \"{KB_JWT_HEADER_TYP}\""
40      )));
41    }
42    let valid_alg = jwt.header.get("alg").is_some_and(|alg| alg != "none");
43    if !valid_alg {
44      return Err(Error::DeserializationError(
45        "invalid KB-JWT: alg must be set and cannot be \"none\"".to_string(),
46      ));
47    }
48
49    Ok(Self(jwt))
50  }
51}
52
53impl KeyBindingJwt {
54  /// Returns a [`KeyBindingJwtBuilder`] that allows the creation of a [`KeyBindingJwt`].
55  pub fn builder() -> KeyBindingJwtBuilder {
56    KeyBindingJwtBuilder::default()
57  }
58  /// Returns a reference to this [`KeyBindingJwt`] claim set.
59  pub fn claims(&self) -> &KeyBindingJwtClaims {
60    &self.0.claims
61  }
62}
63
64/// Builder-style struct to ease the creation of an [`KeyBindingJwt`].
65#[derive(Debug, Default, Clone)]
66pub struct KeyBindingJwtBuilder {
67  headers: JsonObject,
68  payload: JsonObject,
69}
70
71impl KeyBindingJwtBuilder {
72  /// Creates a new [`KeyBindingJwtBuilder`].
73  pub fn new() -> Self {
74    Self::default()
75  }
76
77  /// Creates a new [`KeyBindingJwtBuilder`] using `object` as its payload.
78  pub fn from_object(object: JsonObject) -> Self {
79    Self {
80      headers: JsonObject::default(),
81      payload: object,
82    }
83  }
84
85  /// Sets the JWT's headers.
86  pub fn headers(mut self, headers: JsonObject) -> Self {
87    self.headers = headers;
88    self
89  }
90
91  /// Adds a new header entry to the JWT headers.
92  pub fn header(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
93    self.headers.insert(key.into(), value.into());
94    self
95  }
96
97  /// Sets the [iat](https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.6) property.
98  pub fn iat(mut self, iat: i64) -> Self {
99    self.payload.insert("iat".to_string(), iat.into());
100    self
101  }
102
103  /// Sets the [aud](https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.3) property.
104  pub fn aud<'a, S>(mut self, aud: S) -> Self
105  where
106    S: Into<Cow<'a, str>>,
107  {
108    self.payload.insert("aud".to_string(), aud.into().into_owned().into());
109    self
110  }
111
112  /// Sets the `nonce` property.
113  pub fn nonce<'a, S>(mut self, nonce: S) -> Self
114  where
115    S: Into<Cow<'a, str>>,
116  {
117    self
118      .payload
119      .insert("nonce".to_string(), nonce.into().into_owned().into());
120    self
121  }
122
123  /// Inserts a given property with key `name` and value `value` in the payload.
124  pub fn insert_property(mut self, name: &str, value: Value) -> Self {
125    self.payload.insert(name.to_string(), value);
126    self
127  }
128
129  /// Builds an [`KeyBindingJwt`] from the data provided to builder.
130  pub async fn finish<S>(
131    self,
132    sd_jwt: &SdJwt,
133    hasher: &dyn Hasher,
134    alg: &str,
135    signer: &S,
136  ) -> Result<KeyBindingJwt, Error>
137  where
138    S: JwsSigner,
139  {
140    let mut claims = self.payload;
141    if alg == "none" {
142      return Err(Error::DataTypeMismatch(
143        "A KeyBindingJwt cannot use algorithm \"none\"".to_string(),
144      ));
145    }
146    if sd_jwt.key_binding_jwt().is_some() {
147      return Err(Error::DataTypeMismatch(
148        "the provided SD-JWT already has a KB-JWT attached".to_string(),
149      ));
150    }
151    if sd_jwt.claims()._sd_alg.as_deref().unwrap_or(SHA_ALG_NAME) != hasher.alg_name() {
152      return Err(Error::InvalidHasher(format!(
153        "invalid hashing algorithm \"{}\"",
154        hasher.alg_name()
155      )));
156    }
157    let sd_hash = hasher.encoded_digest(&sd_jwt.to_string());
158    claims.insert("sd_hash".to_string(), sd_hash.into());
159
160    let mut header = self.headers;
161    header.insert("alg".to_string(), alg.to_owned().into());
162    header
163      .entry("typ")
164      .or_insert_with(|| KB_JWT_HEADER_TYP.to_owned().into());
165
166    // Validate claims
167    let parsed_claims = serde_json::from_value::<KeyBindingJwtClaims>(claims.clone().into())
168      .map_err(|e| Error::DeserializationError(format!("invalid KB-JWT claims: {e}")))?;
169    let jws = signer
170      .sign(&header, &claims)
171      .await
172      .map_err(|e| anyhow::anyhow!("{e}"))
173      .and_then(|jws_bytes| String::from_utf8(jws_bytes).context("invalid JWS"))
174      .map_err(|e| Error::JwsSignerFailure(e.to_string()))?;
175
176    Ok(KeyBindingJwt(Jwt {
177      header,
178      claims: parsed_claims,
179      jws,
180    }))
181  }
182}
183
184/// Claims set for key binding JWT.
185#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
186pub struct KeyBindingJwtClaims {
187  pub iat: i64,
188  pub aud: String,
189  pub nonce: String,
190  pub sd_hash: String,
191  #[serde(flatten)]
192  properties: JsonObject,
193}
194
195impl Deref for KeyBindingJwtClaims {
196  type Target = JsonObject;
197  fn deref(&self) -> &Self::Target {
198    &self.properties
199  }
200}
201
202/// Proof of possession of a given key. See [RFC7800](https://www.rfc-editor.org/rfc/rfc7800.html#section-3) for more details.
203#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
204#[serde(rename_all = "camelCase")]
205pub enum RequiredKeyBinding {
206  /// Json Web Key (JWK).
207  Jwk(JsonObject),
208  /// Encoded JWK in its compact serialization form.
209  Jwe(String),
210  /// Key ID.
211  Kid(String),
212  /// JWK from a JWK set identified by `kid`.
213  Jwu {
214    /// URL of the JWK Set.
215    jwu: String,
216    /// kid of the referenced JWK.
217    kid: String,
218  },
219  /// Non standard key-bind.
220  #[serde(untagged)]
221  Custom(Value),
222}