sd_jwt_payload/
key_binding_jwt_claims.rs1use 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#[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 pub fn builder() -> KeyBindingJwtBuilder {
56 KeyBindingJwtBuilder::default()
57 }
58 pub fn claims(&self) -> &KeyBindingJwtClaims {
60 &self.0.claims
61 }
62}
63
64#[derive(Debug, Default, Clone)]
66pub struct KeyBindingJwtBuilder {
67 headers: JsonObject,
68 payload: JsonObject,
69}
70
71impl KeyBindingJwtBuilder {
72 pub fn new() -> Self {
74 Self::default()
75 }
76
77 pub fn from_object(object: JsonObject) -> Self {
79 Self {
80 headers: JsonObject::default(),
81 payload: object,
82 }
83 }
84
85 pub fn headers(mut self, headers: JsonObject) -> Self {
87 self.headers = headers;
88 self
89 }
90
91 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 pub fn iat(mut self, iat: i64) -> Self {
99 self.payload.insert("iat".to_string(), iat.into());
100 self
101 }
102
103 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 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 pub fn insert_property(mut self, name: &str, value: Value) -> Self {
125 self.payload.insert(name.to_string(), value);
126 self
127 }
128
129 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 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#[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#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
204#[serde(rename_all = "camelCase")]
205pub enum RequiredKeyBinding {
206 Jwk(JsonObject),
208 Jwe(String),
210 Kid(String),
212 Jwu {
214 jwu: String,
216 kid: String,
218 },
219 #[serde(untagged)]
221 Custom(Value),
222}