sd_jwt_payload/
builder.rs1use std::borrow::Cow;
5
6use anyhow::Context as _;
7use itertools::Itertools;
8use serde::Serialize;
9use serde_json::Value;
10
11use crate::jwt::Jwt;
12use crate::Disclosure;
13use crate::Error;
14use crate::Hasher;
15use crate::JsonObject;
16use crate::JwsSigner;
17use crate::RequiredKeyBinding;
18use crate::Result;
19use crate::SdJwt;
20use crate::SdJwtClaims;
21use crate::SdObjectEncoder;
22#[cfg(feature = "sha")]
23use crate::Sha256Hasher;
24use crate::DEFAULT_SALT_SIZE;
25use crate::HEADER_TYP;
26
27#[derive(Debug)]
29pub struct SdJwtBuilder<H> {
30 encoder: SdObjectEncoder<H>,
31 header: JsonObject,
32 disclosures: Vec<Disclosure>,
33 key_bind: Option<RequiredKeyBinding>,
34}
35
36#[cfg(feature = "sha")]
37impl SdJwtBuilder<Sha256Hasher> {
38 pub fn new<T: Serialize>(object: T) -> Result<Self> {
43 Self::new_with_hasher(object, Sha256Hasher::new())
44 }
45}
46
47impl<H: Hasher> SdJwtBuilder<H> {
48 pub fn new_with_hasher<T: Serialize>(object: T, hasher: H) -> Result<Self> {
50 Self::new_with_hasher_and_salt_size(object, hasher, DEFAULT_SALT_SIZE)
51 }
52
53 pub fn new_with_hasher_and_salt_size<T: Serialize>(object: T, hasher: H, salt_size: usize) -> Result<Self> {
55 let object = serde_json::to_value(object).map_err(|e| Error::Unspecified(e.to_string()))?;
56 let encoder = SdObjectEncoder::with_custom_hasher_and_salt_size(object, hasher, salt_size)?;
57 Ok(Self {
58 encoder,
59 disclosures: vec![],
60 key_bind: None,
61 header: JsonObject::default(),
62 })
63 }
64
65 pub fn make_concealable(mut self, path: &str) -> Result<Self> {
94 let disclosure = self.encoder.conceal(path)?;
95 self.disclosures.push(disclosure);
96
97 Ok(self)
98 }
99
100 pub fn header(mut self, header: JsonObject) -> Self {
106 self.header = header;
107 self
108 }
109
110 pub fn insert_claim<'a, K, V>(mut self, key: K, value: V) -> Result<Self>
112 where
113 K: Into<Cow<'a, str>>,
114 V: Serialize,
115 {
116 let key = key.into().into_owned();
117 let value = serde_json::to_value(value).map_err(|e| Error::DeserializationError(e.to_string()))?;
118 self
119 .encoder
120 .object
121 .as_object_mut()
122 .expect("encoder::object is a JSON Object")
123 .insert(key, value);
124
125 Ok(self)
126 }
127
128 pub fn add_decoys(mut self, path: &str, number_of_decoys: usize) -> Result<Self> {
135 self.encoder.add_decoys(path, number_of_decoys)?;
136
137 Ok(self)
138 }
139
140 pub fn require_key_binding(mut self, key_bind: RequiredKeyBinding) -> Self {
145 self.key_bind = Some(key_bind);
146 self
147 }
148
149 pub async fn finish<S>(self, signer: &S, alg: &str) -> Result<SdJwt>
151 where
152 S: JwsSigner,
153 {
154 let SdJwtBuilder {
155 mut encoder,
156 disclosures,
157 key_bind,
158 mut header,
159 } = self;
160 encoder.add_sd_alg_property();
161 let mut object = encoder.object;
162 if let Some(key_bind) = key_bind {
164 let key_bind = serde_json::to_value(key_bind).map_err(|e| Error::DeserializationError(e.to_string()))?;
165 object
166 .as_object_mut()
167 .expect("encoder::object is a JSON Object")
168 .insert("cnf".to_string(), key_bind);
169 }
170
171 if let Some(Value::String(typ)) = header.get("typ") {
173 if !typ.split('+').contains(&HEADER_TYP) {
174 return Err(Error::DataTypeMismatch(
175 "invalid header: \"typ\" must contain type \"sd-jwt\"".to_string(),
176 ));
177 }
178 } else {
179 header.insert("typ".to_string(), Value::String(HEADER_TYP.to_string()));
180 }
181 header.insert("alg".to_string(), Value::String(alg.to_string()));
182
183 let jws = signer
184 .sign(&header, object.as_object().expect("encoder::object is a JSON Object"))
185 .await
186 .map_err(|e| anyhow::anyhow!("jws failed: {e}"))
187 .and_then(|jws_bytes| String::from_utf8(jws_bytes).context("invalid JWS"))
188 .map_err(|e| Error::JwsSignerFailure(e.to_string()))?;
189
190 let claims = serde_json::from_value::<SdJwtClaims>(object)
191 .map_err(|e| Error::DeserializationError(format!("invalid SD-JWT claims: {e}")))?;
192 let jwt = Jwt { header, claims, jws };
193
194 Ok(SdJwt::new(jwt, disclosures, None))
195 }
196}
197
198#[cfg(test)]
199mod test {
200 use serde_json::json;
201
202 use super::*;
203
204 mod marking_properties_as_concealable {
205 use super::*;
206
207 mod that_exist {
208 use super::*;
209
210 mod on_top_level {
211 use super::*;
212
213 #[test]
214 fn can_be_done_for_object_values() {
215 let result = SdJwtBuilder::new(json!({ "address": {} }))
216 .unwrap()
217 .make_concealable("/address");
218
219 assert!(result.is_ok());
220 }
221
222 #[test]
223 fn can_be_done_for_array_elements() {
224 let result = SdJwtBuilder::new(json!({ "nationalities": ["US", "DE"] }))
225 .unwrap()
226 .make_concealable("/nationalities");
227
228 assert!(result.is_ok());
229 }
230 }
231
232 mod as_subproperties {
233 use super::*;
234
235 #[test]
236 fn can_be_done_for_object_values() {
237 let result = SdJwtBuilder::new(json!({ "address": { "country": "US" } }))
238 .unwrap()
239 .make_concealable("/address/country");
240
241 assert!(result.is_ok());
242 }
243
244 #[test]
245 fn can_be_done_for_array_elements() {
246 let result = SdJwtBuilder::new(json!({
247 "address": { "contact_person": [ "Jane Dow", "John Doe" ] }
248 }))
249 .unwrap()
250 .make_concealable("/address/contact_person/0");
251
252 assert!(result.is_ok());
253 }
254 }
255 }
256
257 mod that_do_not_exist {
258 use super::*;
259 mod on_top_level {
260 use super::*;
261
262 #[test]
263 fn returns_an_error_for_nonexistant_object_paths() {
264 let result = SdJwtBuilder::new(json!({})).unwrap().make_concealable("/email");
265
266 assert_eq!(result.unwrap_err(), Error::InvalidPath("/email".to_string()),);
267 }
268
269 #[test]
270 fn returns_an_error_for_nonexistant_array_paths() {
271 let result = SdJwtBuilder::new(json!({}))
272 .unwrap()
273 .make_concealable("/nationalities/0");
274
275 assert_eq!(result.unwrap_err(), Error::InvalidPath("/nationalities/0".to_string()),);
276 }
277
278 #[test]
279 fn returns_an_error_for_nonexistant_array_entries() {
280 let result = SdJwtBuilder::new(json!({
281 "nationalities": ["US", "DE"]
282 }))
283 .unwrap()
284 .make_concealable("/nationalities/2");
285
286 assert_eq!(result.unwrap_err(), Error::InvalidPath("/nationalities/2".to_string()),);
287 }
288 }
289
290 mod as_subproperties {
291 use super::*;
292
293 #[test]
294 fn returns_an_error_for_nonexistant_object_paths() {
295 let result = SdJwtBuilder::new(json!({
296 "address": {}
297 }))
298 .unwrap()
299 .make_concealable("/address/region");
300
301 assert_eq!(result.unwrap_err(), Error::InvalidPath("/address/region".to_string()),);
302 }
303
304 #[test]
305 fn returns_an_error_for_nonexistant_array_paths() {
306 let result = SdJwtBuilder::new(json!({
307 "address": {}
308 }))
309 .unwrap()
310 .make_concealable("/address/contact_person/2");
311
312 assert_eq!(
313 result.unwrap_err(),
314 Error::InvalidPath("/address/contact_person/2".to_string()),
315 );
316 }
317
318 #[test]
319 fn returns_an_error_for_nonexistant_array_entries() {
320 let result = SdJwtBuilder::new(json!({
321 "address": { "contact_person": [ "Jane Dow", "John Doe" ] }
322 }))
323 .unwrap()
324 .make_concealable("/address/contact_person/2");
325
326 assert_eq!(
327 result.unwrap_err(),
328 Error::InvalidPath("/address/contact_person/2".to_string()),
329 );
330 }
331 }
332 }
333 }
334
335 mod adding_decoys {
336 use super::*;
337
338 mod on_top_level {
339 use super::*;
340
341 #[test]
342 fn can_add_zero_object_value_decoys_for_a_path() {
343 let result = SdJwtBuilder::new(json!({})).unwrap().add_decoys("", 0);
344
345 assert!(result.is_ok());
346 }
347
348 #[test]
349 fn can_add_object_value_decoys_for_a_path() {
350 let result = SdJwtBuilder::new(json!({})).unwrap().add_decoys("", 2);
351
352 assert!(result.is_ok());
353 }
354 }
355
356 mod for_subproperties {
357 use super::*;
358
359 #[test]
360 fn can_add_zero_object_value_decoys_for_a_path() {
361 let result = SdJwtBuilder::new(json!({ "address": {} }))
362 .unwrap()
363 .add_decoys("/address", 0);
364
365 assert!(result.is_ok());
366 }
367
368 #[test]
369 fn can_add_object_value_decoys_for_a_path() {
370 let result = SdJwtBuilder::new(json!({ "address": {} }))
371 .unwrap()
372 .add_decoys("/address", 2);
373
374 assert!(result.is_ok());
375 }
376
377 #[test]
378 fn can_add_zero_array_element_decoys_for_a_path() {
379 let result = SdJwtBuilder::new(json!({ "nationalities": ["US", "DE"] }))
380 .unwrap()
381 .add_decoys("/nationalities", 0);
382
383 assert!(result.is_ok());
384 }
385
386 #[test]
387 fn can_add_array_element_decoys_for_a_path() {
388 let result = SdJwtBuilder::new(json!({ "nationalities": ["US", "DE"] }))
389 .unwrap()
390 .add_decoys("/nationalities", 2);
391
392 assert!(result.is_ok());
393 }
394 }
395 }
396}