1use crate::SDJWTSerializationFormat;
6use crate::error::Error;
7use crate::error::Result;
8use jsonwebtoken::jwk::Jwk;
9use jsonwebtoken::{Algorithm, DecodingKey, Header, Validation};
10use log::debug;
11use serde_json::{Map, Value};
12use std::ops::Add;
13use std::option::Option;
14use std::str::FromStr;
15use std::string::String;
16use std::vec::Vec;
17
18use crate::utils::base64_hash;
19use crate::{
20 SDJWTCommon, CNF_KEY, COMBINED_SERIALIZATION_FORMAT_SEPARATOR, DEFAULT_DIGEST_ALG,
21 DEFAULT_SIGNING_ALG, DIGEST_ALG_KEY, JWK_KEY, KB_DIGEST_KEY, KB_JWT_TYP_HEADER, SD_DIGESTS_KEY,
22 SD_LIST_PREFIX,
23};
24
25type KeyResolver = dyn Fn(&str, &Header) -> DecodingKey;
26
27pub struct SDJWTVerifier {
28 sd_jwt_engine: SDJWTCommon,
29
30 sd_jwt_payload: Map<String, Value>,
31 _holder_public_key_payload: Option<Map<String, Value>>,
32 duplicate_hash_check: Vec<String>,
33 pub verified_claims: Value,
34
35 cb_get_issuer_key: Box<KeyResolver>,
36}
37
38impl SDJWTVerifier {
39 pub fn new(
51 sd_jwt_presentation: String,
52 cb_get_issuer_key: Box<KeyResolver>,
53 expected_aud: Option<String>,
54 expected_nonce: Option<String>,
55 serialization_format: SDJWTSerializationFormat,
56 ) -> Result<Self> {
57 let mut verifier = SDJWTVerifier {
58 sd_jwt_payload: serde_json::Map::new(),
59 _holder_public_key_payload: None,
60 duplicate_hash_check: Vec::new(),
61 cb_get_issuer_key,
62 sd_jwt_engine: SDJWTCommon {
63 serialization_format,
64 ..Default::default()
65 },
66 verified_claims: Value::Null,
67 };
68
69 verifier.sd_jwt_engine.parse_sd_jwt(sd_jwt_presentation)?;
70 verifier.sd_jwt_engine.create_hash_mappings()?;
71 let sign_alg = verifier.sd_jwt_engine.sign_alg.clone();
72 verifier.verify_sd_jwt(sign_alg.clone())?;
73 verifier.verified_claims = verifier.extract_sd_claims()?;
74
75 if let (Some(expected_aud), Some(expected_nonce)) = (&expected_aud, &expected_nonce) {
76 let sign_alg = verifier.sd_jwt_engine.unverified_input_key_binding_jwt
77 .as_ref()
78 .and_then(|value| {
79 SDJWTCommon::decode_header_and_get_sign_algorithm(&value)
80 });
81
82 verifier.verify_key_binding_jwt(
83 expected_aud.to_owned(),
84 expected_nonce.to_owned(),
85 sign_alg.as_deref(),
86 )?;
87 } else if expected_aud.is_some() || expected_nonce.is_some() {
88 return Err(Error::InvalidInput(
89 "Either both expected_aud and expected_nonce must be provided or both must be None"
90 .to_string(),
91 ));
92 }
93
94 Ok(verifier)
95 }
96
97 fn verify_sd_jwt(&mut self, sign_alg: Option<String>) -> Result<()> {
98 let sd_jwt = self
99 .sd_jwt_engine
100 .unverified_sd_jwt
101 .as_ref()
102 .ok_or(Error::ConversionError("reference".to_string()))?;
103 let parsed_header_sd_jwt = jsonwebtoken::decode_header(sd_jwt)
104 .map_err(|e| Error::DeserializationError(e.to_string()))?;
105
106 let unverified_issuer = self
107 .sd_jwt_engine
108 .unverified_input_sd_jwt_payload
109 .as_ref()
110 .ok_or(Error::ConversionError("reference".to_string()))?["iss"]
111 .as_str()
112 .ok_or(Error::ConversionError("str".to_string()))?;
113 let issuer_public_key = (self.cb_get_issuer_key)(unverified_issuer, &parsed_header_sd_jwt);
114 let algorithm: Algorithm = match sign_alg {
115 Some(alg_str) => Algorithm::from_str(&alg_str)
116 .map_err(|e| Error::DeserializationError(e.to_string()))?,
117 None => Algorithm::ES256, };
119 let claims = jsonwebtoken::decode(
120 sd_jwt,
121 &issuer_public_key,
122 &Validation::new(algorithm),
123 )
124 .map_err(|e| Error::DeserializationError(format!("Cannot decode jwt: {}", e)))?
125 .claims;
126
127 let _ = sign_alg; self.sd_jwt_payload = claims;
130 self._holder_public_key_payload = self
131 .sd_jwt_payload
132 .get(CNF_KEY)
133 .and_then(Value::as_object)
134 .cloned();
135
136 Ok(())
137 }
138
139 fn verify_key_binding_jwt(
140 &mut self,
141 expected_aud: String,
142 expected_nonce: String,
143 sign_alg: Option<&str>,
144 ) -> Result<()> {
145 let sign_alg = sign_alg.unwrap_or(DEFAULT_SIGNING_ALG);
146 let holder_public_key_payload_jwk = match &self._holder_public_key_payload {
147 None => {
148 return Err(Error::KeyNotFound(
149 "No holder public key in SD-JWT".to_string(),
150 ));
151 }
152 Some(payload) => {
153 if let Some(jwk) = payload.get(JWK_KEY) {
154 jwk.clone()
155 } else {
156 return Err(Error::InvalidInput("The holder_public_key_payload is malformed. It doesn't contain the claim jwk".to_string()));
157 }
158 }
159 };
160 let pubkey: DecodingKey = match serde_json::from_value::<Jwk>(holder_public_key_payload_jwk)
161 {
162 Ok(jwk) => {
163 if let Ok(pubkey) = DecodingKey::from_jwk(&jwk) {
164 pubkey
165 } else {
166 return Err(Error::DeserializationError(
167 "Cannot parse DecodingKey from json".to_string(),
168 ));
169 }
170 }
171 Err(_) => {
172 return Err(Error::DeserializationError(
173 "Cannot parse JWK from json".to_string(),
174 ));
175 }
176 };
177 let key_binding_jwt = match &self.sd_jwt_engine.unverified_input_key_binding_jwt {
178 Some(payload) => {
179 let mut validation = Validation::new(
180 Algorithm::from_str(sign_alg)
181 .map_err(|e| Error::DeserializationError(e.to_string()))?,
182 );
183 validation.set_audience(&[expected_aud.as_str()]);
184 validation.set_required_spec_claims(&["aud"]);
185
186 jsonwebtoken::decode::<Map<String, Value>>(payload.as_str(), &pubkey, &validation)
187 .map_err(|e| Error::DeserializationError(e.to_string()))?
188 }
189 None => {
190 return Err(Error::InvalidState(
191 "Cannot take Key Binding JWK from String".to_string(),
192 ));
193 }
194 };
195 if key_binding_jwt.header.typ != Some(KB_JWT_TYP_HEADER.to_string()) {
196 return Err(Error::InvalidInput("Invalid header type".to_string()));
197 }
198 if key_binding_jwt.claims.get("nonce") != Some(&Value::String(expected_nonce)) {
199 return Err(Error::InvalidInput("Invalid nonce".to_string()));
200 }
201 if self.sd_jwt_engine.serialization_format == SDJWTSerializationFormat::Compact {
202 let sd_hash = self._get_key_binding_digest_hash()?;
203 if key_binding_jwt.claims.get(KB_DIGEST_KEY) != Some(&Value::String(sd_hash)) {
204 return Err(Error::InvalidInput("Invalid digest in KB-JWT".to_string()));
205 }
206 }
207
208 Ok(())
209 }
210
211 fn _get_key_binding_digest_hash(&mut self) -> Result<String> {
212 let mut combined: Vec<&str> =
213 Vec::with_capacity(self.sd_jwt_engine.input_disclosures.len() + 1);
214 combined.push(
215 self.sd_jwt_engine
216 .unverified_sd_jwt
217 .as_ref()
218 .ok_or(Error::ConversionError("reference".to_string()))?
219 .as_str(),
220 );
221 combined.extend(
222 self.sd_jwt_engine
223 .input_disclosures
224 .iter()
225 .map(|s| s.as_str()),
226 );
227 let combined = combined
228 .join(COMBINED_SERIALIZATION_FORMAT_SEPARATOR)
229 .add(COMBINED_SERIALIZATION_FORMAT_SEPARATOR);
230
231 Ok(base64_hash(combined.as_bytes()))
232 }
233
234 fn extract_sd_claims(&mut self) -> Result<Value> {
235 if self.sd_jwt_payload.contains_key(DIGEST_ALG_KEY)
236 && self.sd_jwt_payload[DIGEST_ALG_KEY] != DEFAULT_DIGEST_ALG
237 {
238 return Err(Error::DeserializationError(format!(
239 "Invalid hash algorithm {}",
240 self.sd_jwt_payload[DIGEST_ALG_KEY]
241 )));
242 }
243
244 self.duplicate_hash_check = Vec::new();
245 let claims: Value = self.sd_jwt_payload.clone().into_iter().collect();
246 self.unpack_disclosed_claims(&claims)
247 }
248
249 fn unpack_disclosed_claims(&mut self, sd_jwt_claims: &Value) -> Result<Value> {
250 match sd_jwt_claims {
251 Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => {
252 Ok(sd_jwt_claims.to_owned())
253 }
254 Value::Array(arr) => {
255 self.unpack_disclosed_claims_in_array(arr)
256 }
257 Value::Object(obj) => {
258 self.unpack_disclosed_claims_in_object(obj)
259 }
260 }
261 }
262
263 fn unpack_disclosed_claims_in_array(&mut self, arr: &Vec<Value>) -> Result<Value> {
264 if arr.is_empty() {
265 return Err(Error::InvalidArrayDisclosureObject(
266 "Array of disclosed claims cannot be empty".to_string(),
267 ));
268 }
269
270 let mut claims = vec![];
271 for value in arr {
272
273 match value {
274 Value::Object(obj) if obj.contains_key(SD_LIST_PREFIX) => {
276 if obj.len() > 1 {
277 return Err(Error::InvalidDisclosure(
278 "Disclosed claim object in an array maust contain only one key".to_string(),
279 ));
280 }
281
282 let digest = obj.get(SD_LIST_PREFIX).unwrap();
283 let disclosed_claim = self.unpack_from_digest(digest)?;
284 if let Some(disclosed_claim) = disclosed_claim {
285 claims.push(disclosed_claim);
286 }
287 },
288 _ => {
289 let claim = self.unpack_disclosed_claims(value)?;
290 claims.push(claim);
291 },
292 }
293 }
294 Ok(Value::Array(claims))
295 }
296
297 fn unpack_disclosed_claims_in_object(&mut self, nested_sd_jwt_claims: &Map<String, Value>) -> Result<Value> {
298 let mut disclosed_claims: Map<String, Value> = serde_json::Map::new();
299
300 for (key, value) in nested_sd_jwt_claims {
301 if key != SD_DIGESTS_KEY && key != DIGEST_ALG_KEY {
302 disclosed_claims.insert(key.to_owned(), self.unpack_disclosed_claims(value)?);
303 }
304 }
305
306 if let Some(Value::Array(digest_of_disclosures)) = nested_sd_jwt_claims.get(SD_DIGESTS_KEY)
307 {
308 self.unpack_from_digests(&mut disclosed_claims, digest_of_disclosures)?;
309 }
310
311 Ok(Value::Object(disclosed_claims))
312 }
313
314 fn unpack_from_digests(
315 &mut self,
316 pre_output: &mut Map<String, Value>,
317 digests_of_disclosures: &Vec<Value>,
318 ) -> Result<()> {
319 for digest in digests_of_disclosures {
320 let digest = digest
321 .as_str()
322 .ok_or(Error::ConversionError("str".to_string()))?;
323 if self.duplicate_hash_check.contains(&digest.to_string()) {
324 return Err(Error::DuplicateDigestError(digest.to_string()));
325 }
326 self.duplicate_hash_check.push(digest.to_string());
327
328 if let Some(value_for_digest) =
329 self.sd_jwt_engine.hash_to_decoded_disclosure.get(digest)
330 {
331 let disclosure =
332 value_for_digest
333 .as_array()
334 .ok_or(Error::InvalidArrayDisclosureObject(
335 value_for_digest.to_string(),
336 ))?;
337 let key = disclosure[1]
338 .as_str()
339 .ok_or(Error::ConversionError("str".to_string()))?
340 .to_owned();
341 let value = disclosure[2].clone();
342 if pre_output.contains_key(&key) {
343 return Err(Error::DuplicateKeyError(key.to_string()));
344 }
345 let unpacked_value = self.unpack_disclosed_claims(&value)?;
346 pre_output.insert(key, unpacked_value);
347 } else {
348 debug!("Digest {:?} skipped as decoy", digest)
349 }
350 }
351
352 Ok(())
353 }
354
355 fn unpack_from_digest(
356 &mut self,
357 digest: &Value,
358 ) -> Result<Option<Value>> {
359 let digest = digest
360 .as_str()
361 .ok_or(Error::ConversionError("str".to_string()))?;
362 if self.duplicate_hash_check.contains(&digest.to_string()) {
363 return Err(Error::DuplicateDigestError(digest.to_string()));
364 }
365 self.duplicate_hash_check.push(digest.to_string());
366
367 if let Some(value_for_digest) =
368 self.sd_jwt_engine.hash_to_decoded_disclosure.get(digest)
369 {
370 let disclosure =
371 value_for_digest
372 .as_array()
373 .ok_or(Error::InvalidArrayDisclosureObject(
374 value_for_digest.to_string(),
375 ))?;
376
377 let value = disclosure[1].clone();
378 let unpacked_value = self.unpack_disclosed_claims(&value)?;
379 return Ok(Some(unpacked_value));
380 } else {
381 debug!("Digest {:?} skipped as decoy", digest)
382 }
383
384 Ok(None)
385 }
386}
387
388#[cfg(test)]
389mod tests {
390 use crate::issuer::ClaimsForSelectiveDisclosureStrategy;
391 use crate::{SDJWTHolder, SDJWTIssuer, SDJWTVerifier, SDJWTSerializationFormat};
392 use jsonwebtoken::{DecodingKey, EncodingKey};
393 use serde_json::{json, Value};
394
395 const PRIVATE_ISSUER_PEM: &str = "-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgUr2bNKuBPOrAaxsR\nnbSH6hIhmNTxSGXshDSUD1a1y7ihRANCAARvbx3gzBkyPDz7TQIbjF+ef1IsxUwz\nX1KWpmlVv+421F7+c1sLqGk4HUuoVeN8iOoAcE547pJhUEJyf5Asc6pP\n-----END PRIVATE KEY-----\n";
396 const PUBLIC_ISSUER_PEM: &str = "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEb28d4MwZMjw8+00CG4xfnn9SLMVM\nM19SlqZpVb/uNtRe/nNbC6hpOB1LqFXjfIjqAHBOeO6SYVBCcn+QLHOqTw==\n-----END PUBLIC KEY-----\n";
397 const PRIVATE_ISSUER_ED25519_PEM: &str = "-----BEGIN PRIVATE KEY-----\nMFECAQEwBQYDK2VwBCIEIF93k6rxZ8W38cm0rOwfGdH+YY3k10hP+7gd0falPLg0\ngSEAdW31QyWzfed4EPcw1rYuUa1QU+fXEL0HhdAfYZRkihc=\n-----END PRIVATE KEY-----\n";
398 const PUBLIC_ISSUER_ED25519_PEM: &str = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAdW31QyWzfed4EPcw1rYuUa1QU+fXEL0HhdAfYZRkihc=\n-----END PUBLIC KEY-----\n";
399
400 const HOLDER_KEY_ED25519: &str = "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIOeIDnHHMoPCUTiq206gR+FdCdNtc31SzF1nKX31hvhd\n-----END PRIVATE KEY-----";
402
403 const HOLDER_JWK_KEY_ED25519: &str = r#"{
404 "alg": "EdDSA",
405 "crv": "Ed25519",
406 "kid": "52128f2e-900e-414e-81c3-0b5f86f0f7b3",
407 "kty": "OKP",
408 "x": "24QLWXJ18wtbg3k_MDGhGM17Xh39UftuxbwJZzRLzkA"
409 }"#;
410
411 #[test]
412 fn verify_full_presentation() {
413 let user_claims = json!({
414 "sub": "6c5c0a49-b589-431d-bae7-219122a9ec2c",
415 "iss": "https://example.com/issuer",
416 "iat": 1683000000,
417 "exp": 1883000000,
418 "address": {
419 "street_address": "Schulstr. 12",
420 "locality": "Schulpforta",
421 "region": "Sachsen-Anhalt",
422 "country": "DE"
423 }
424 });
425 let private_issuer_bytes = PRIVATE_ISSUER_PEM.as_bytes();
426 let issuer_key = EncodingKey::from_ec_pem(private_issuer_bytes).unwrap();
427 let sd_jwt = SDJWTIssuer::new(issuer_key, None).issue_sd_jwt(
428 user_claims.clone(),
429 ClaimsForSelectiveDisclosureStrategy::AllLevels,
430 None,
431 false,
432 SDJWTSerializationFormat::Compact,
433 )
434 .unwrap();
435 let presentation = SDJWTHolder::new(sd_jwt.clone(), SDJWTSerializationFormat::Compact)
436 .unwrap()
437 .create_presentation(
438 user_claims.as_object().unwrap().clone(),
439 None,
440 None,
441 None,
442 None,
443 )
444 .unwrap();
445 assert_eq!(sd_jwt, presentation);
446 let verified_claims = SDJWTVerifier::new(
447 presentation,
448 Box::new(|_, _| {
449 let public_issuer_bytes = PUBLIC_ISSUER_PEM.as_bytes();
450 DecodingKey::from_ec_pem(public_issuer_bytes).unwrap()
451 }),
452 None,
453 None,
454 SDJWTSerializationFormat::Compact,
455 )
456 .unwrap()
457 .verified_claims;
458 assert_eq!(user_claims, verified_claims);
459 }
460
461 #[test]
462 fn verify_noclaim_presentation() {
463 let user_claims = json!({
464 "sub": "6c5c0a49-b589-431d-bae7-219122a9ec2c",
465 "iss": "https://example.com/issuer",
466 "iat": 1683000000,
467 "exp": 1883000000,
468 "address": {
469 "street_address": "Schulstr. 12",
470 "locality": "Schulpforta",
471 "region": "Sachsen-Anhalt",
472 "country": "DE"
473 }
474 });
475 let private_issuer_bytes = PRIVATE_ISSUER_PEM.as_bytes();
476 let issuer_key = EncodingKey::from_ec_pem(private_issuer_bytes).unwrap();
477 let sd_jwt = SDJWTIssuer::new(issuer_key, None).issue_sd_jwt(
478 user_claims.clone(),
479 ClaimsForSelectiveDisclosureStrategy::NoSDClaims,
480 None,
481 false,
482 SDJWTSerializationFormat::Compact,
483 )
484 .unwrap();
485
486 let presentation = SDJWTHolder::new(sd_jwt.clone(), SDJWTSerializationFormat::Compact)
487 .unwrap()
488 .create_presentation(
489 user_claims.as_object().unwrap().clone(),
490 None,
491 None,
492 None,
493 None,
494 )
495 .unwrap();
496 assert_eq!(sd_jwt, presentation);
497 let verified_claims = SDJWTVerifier::new(
498 presentation,
499 Box::new(|_, _| {
500 let public_issuer_bytes = PUBLIC_ISSUER_PEM.as_bytes();
501 DecodingKey::from_ec_pem(public_issuer_bytes).unwrap()
502 }),
503 None,
504 None,
505 SDJWTSerializationFormat::Compact,
506 )
507 .unwrap()
508 .verified_claims;
509 assert_eq!(user_claims, verified_claims);
510 }
511
512 #[test]
513 fn verify_arrayed_presentation() {
514 let user_claims = json!(
515 {
516 "sub": "6c5c0a49-b589-431d-bae7-219122a9ec2c",
517 "name": "Bois",
518 "iss": "https://example.com/issuer",
519 "iat": 1683000000,
520 "exp": 1883000000,
521 "addresses": [
522 {
523 "street_address": "Schulstr. 12",
524 "locality": "Schulpforta",
525 "region": "Sachsen-Anhalt",
526 "country": "DE"
527 },
528 {
529 "street_address": "456 Main St",
530 "locality": "Anytown",
531 "region": "NY",
532 "country": "US"
533 }
534 ],
535 "nationalities": [
536 "US",
537 "CA"
538 ]
539 }
540 );
541 let private_issuer_bytes = PRIVATE_ISSUER_PEM.as_bytes();
542 let issuer_key = EncodingKey::from_ec_pem(private_issuer_bytes).unwrap();
543 let strategy = ClaimsForSelectiveDisclosureStrategy::Custom(vec![
544 "$.name",
545 "$.addresses[1]",
546 "$.addresses[1].country",
547 "$.nationalities[0]",
548 ]);
549 let sd_jwt = SDJWTIssuer::new(issuer_key, None).issue_sd_jwt(
550 user_claims.clone(),
551 strategy,
552 None,
553 false,
554 SDJWTSerializationFormat::Compact,
555 )
556 .unwrap();
557
558 let mut claims_to_disclose = user_claims.clone();
559 claims_to_disclose["addresses"] = Value::Array(vec![Value::Bool(true), Value::Bool(true)]);
560 claims_to_disclose["nationalities"] =
561 Value::Array(vec![Value::Bool(true), Value::Bool(true)]);
562 let presentation = SDJWTHolder::new(sd_jwt, SDJWTSerializationFormat::Compact)
563 .unwrap()
564 .create_presentation(
565 claims_to_disclose.as_object().unwrap().clone(),
566 None,
567 None,
568 None,
569 None,
570 )
571 .unwrap();
572
573 let verified_claims = SDJWTVerifier::new(
574 presentation.clone(),
575 Box::new(|_, _| {
576 let public_issuer_bytes = PUBLIC_ISSUER_PEM.as_bytes();
577 DecodingKey::from_ec_pem(public_issuer_bytes).unwrap()
578 }),
579 None,
580 None,
581 SDJWTSerializationFormat::Compact,
582 )
583 .unwrap()
584 .verified_claims;
585
586 let expected_verified_claims = json!(
587 {
588 "sub": "6c5c0a49-b589-431d-bae7-219122a9ec2c",
589 "addresses": [
590 {
591 "street_address": "Schulstr. 12",
592 "locality": "Schulpforta",
593 "region": "Sachsen-Anhalt",
594 "country": "DE",
595 },
596 {
597 "street_address": "456 Main St",
598 "locality": "Anytown",
599 "region": "NY",
600 },
601 ],
602 "nationalities": [
603 "US",
604 "CA",
605 ],
606 "iss": "https://example.com/issuer",
607 "iat": 1683000000,
608 "exp": 1883000000,
609 "name": "Bois"
610 }
611 );
612
613 assert_eq!(verified_claims, expected_verified_claims);
614 }
615
616 #[test]
617 fn verify_arrayed_no_sd_presentation() {
618 let user_claims = json!(
619 {
620 "iss": "https://example.com/issuer",
621 "iat": 1683000000,
622 "exp": 1883000000,
623 "array_with_recursive_sd": [
624 "boring",
625 {
626 "foo": "bar",
627 "baz": {
628 "qux": "quux"
629 }
630 },
631 ["foo", "bar"]
632 ],
633 "test2": ["foo", "bar"]
634 }
635 );
636 let private_issuer_bytes = PRIVATE_ISSUER_PEM.as_bytes();
637 let issuer_key = EncodingKey::from_ec_pem(private_issuer_bytes).unwrap();
638 let strategy = ClaimsForSelectiveDisclosureStrategy::Custom(vec![
639 "$.array_with_recursive_sd[1]",
640 "$.array_with_recursive_sd[1].baz",
641 "$.array_with_recursive_sd[2][0]",
642 "$.array_with_recursive_sd[2][1]",
643 "$.test2[0]",
644 "$.test2[1]",
645 ]);
646 let sd_jwt = SDJWTIssuer::new(issuer_key, None).issue_sd_jwt(
647 user_claims.clone(),
648 strategy,
649 None,
650 false,
651 SDJWTSerializationFormat::Compact,
652 )
653 .unwrap();
654
655 let claims_to_disclose = json!({});
656
657 let presentation = SDJWTHolder::new(sd_jwt, SDJWTSerializationFormat::Compact)
658 .unwrap()
659 .create_presentation(
660 claims_to_disclose.as_object().unwrap().clone(),
661 None,
662 None,
663 None,
664 None,
665 )
666 .unwrap();
667
668 let verified_claims = SDJWTVerifier::new(
669 presentation.clone(),
670 Box::new(|_, _| {
671 let public_issuer_bytes = PUBLIC_ISSUER_PEM.as_bytes();
672 DecodingKey::from_ec_pem(public_issuer_bytes).unwrap()
673 }),
674 None,
675 None,
676 SDJWTSerializationFormat::Compact,
677 )
678 .unwrap()
679 .verified_claims;
680
681 let expected_verified_claims = json!(
682 {
683 "iss": "https://example.com/issuer",
684 "iat": 1683000000,
685 "exp": 1883000000,
686 "array_with_recursive_sd": [
687 "boring",
688 [],
689 ],
690 "test2": [],
691 }
692 );
693
694 assert_eq!(verified_claims, expected_verified_claims);
695 }
696
697 #[test]
698 fn verify_full_presentation_to_allow_other_algorithms_json_format() {
699
700 let user_claims = json!({
701 "sub": "6c5c0a49-b589-431d-bae7-219122a9ec2c",
702 "iss": "https://example.com/issuer",
703 "iat": 1683000000,
704 "exp": 1883000000,
705 "address": {
706 "street_address": "Schulstr. 12",
707 "locality": "Schulpforta",
708 "region": "Sachsen-Anhalt",
709 "country": "DE"
710 }
711 });
712 let private_issuer_bytes = PRIVATE_ISSUER_ED25519_PEM.as_bytes();
713 let issuer_key = EncodingKey::from_ed_pem(private_issuer_bytes).unwrap();
714 let sd_jwt = SDJWTIssuer::new(issuer_key, Some("EdDSA".to_string())).issue_sd_jwt(
715 user_claims.clone(),
716 ClaimsForSelectiveDisclosureStrategy::AllLevels,
717 None,
718 false,
719 SDJWTSerializationFormat::JSON, )
721 .unwrap();
722
723 let presentation = SDJWTHolder::new(sd_jwt.clone(), SDJWTSerializationFormat::JSON) .unwrap()
725 .create_presentation(
726 user_claims.as_object().unwrap().clone(),
727 None,
728 None,
729 None,
730 None
731 )
732 .unwrap();
733 assert_eq!(sd_jwt, presentation);
734 let verified_claims = SDJWTVerifier::new(
735 presentation,
736 Box::new(|_, _| {
737 let public_issuer_bytes = PUBLIC_ISSUER_ED25519_PEM.as_bytes();
738 DecodingKey::from_ed_pem(public_issuer_bytes).unwrap()
739 }),
740 None,
741 None,
742 SDJWTSerializationFormat::JSON, )
744 .unwrap()
745 .verified_claims;
746 assert_eq!(user_claims, verified_claims);
747 }
748 #[test]
749 fn verify_presentation_when_sd_jwt_uses_es256_and_key_binding_uses_eddsa() {
750
751 let user_claims = json!({
752 "address": {
753 "street_address": "Schulstr. 12",
754 "locality": "Schulpforta",
755 "region": "Sachsen-Anhalt",
756 "country": "DE"
757 },
758 "exp": 1883000000,
759 "iat": 1683000000,
760 "iss": "https://example.com/issuer",
761 "sub": "6c5c0a49-b589-431d-bae7-219122a9ec2c",
762
763 });
764
765 let private_issuer_bytes = PRIVATE_ISSUER_PEM.as_bytes();
766 let issuer_key = EncodingKey::from_ec_pem(private_issuer_bytes).unwrap();
767
768 let mut issuer = SDJWTIssuer::new(issuer_key, Some("ES256".to_string()));
769
770 let sd_jwt = issuer.issue_sd_jwt(
771 user_claims.clone(),
772 ClaimsForSelectiveDisclosureStrategy::AllLevels,
773 Some(serde_json::from_str(HOLDER_JWK_KEY_ED25519).unwrap()),
774 false,
775 SDJWTSerializationFormat::JSON, ).unwrap();
777
778 let private_holder_bytes = HOLDER_KEY_ED25519.as_bytes();
779 let holder_key = EncodingKey::from_ed_pem(private_holder_bytes).unwrap();
780
781 let nonce = Some(String::from("testNonce"));
782 let aud = Some(String::from("testAud"));
783
784 let mut holder = SDJWTHolder::new(sd_jwt.clone(), SDJWTSerializationFormat::JSON).unwrap(); let presentation = holder.create_presentation(
786 user_claims.as_object().unwrap().clone(),
787 nonce.clone(),
788 aud.clone(),
789 Some(holder_key),
790 Some("EdDSA".to_string())
791 )
792 .unwrap();
793 let verified_claims = SDJWTVerifier::new(
794 presentation,
795 Box::new(|_, _| {
796 let public_issuer_bytes = PUBLIC_ISSUER_PEM.as_bytes();
797 DecodingKey::from_ec_pem(public_issuer_bytes).unwrap()
798 }),
799 aud.clone(),
800 nonce.clone(),
801 SDJWTSerializationFormat::JSON, )
803 .unwrap()
804 .verified_claims;
805
806 let claims_to_check = json!({
807 "iss": user_claims["iss"].clone(),
808 "iat": user_claims["iat"].clone(),
809 "exp": user_claims["exp"].clone(),
810 "cnf": {
811 "jwk": serde_json::from_str::<Value>(HOLDER_JWK_KEY_ED25519).unwrap(),
812 },
813 "sub": user_claims["sub"].clone(),
814 "address": user_claims["address"].clone(),
815 });
816
817 assert_eq!(claims_to_check, verified_claims);
818 }
819}