1use core::fmt;
4use core::str::FromStr;
5use std::collections::{HashMap, HashSet};
6
7use chrono::{DateTime, Utc};
8use indexmap::IndexMap;
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10use serde_json::{Map, Value};
11
12use super::{ClaimPath, Disclosure, HashAlgorithm, SdJwtBuilder, SdJwtError};
13use crate::jose::{JwsHeader, Signer};
14use crate::vc::{Jwt, JwtClaims};
15
16pub const SD_KEY: &str = "_sd";
18
19pub const ELLIPSIS_KEY: &str = "...";
21
22pub const KB_JWT_TYP: &str = "kb+jwt";
24
25#[derive(Clone, PartialEq, Eq)]
75pub struct SdJwt {
76 jwt: Jwt,
77 disclosures: Vec<Disclosure>,
78 key_binding: Option<Jwt>,
79}
80
81impl SdJwt {
82 pub fn new(jwt: Jwt, disclosures: Vec<Disclosure>) -> Self {
84 Self {
85 jwt,
86 disclosures,
87 key_binding: None,
88 }
89 }
90
91 pub fn parse(s: &str) -> Result<Self, SdJwtError> {
99 let parts: Vec<&str> = s.split('~').collect();
100 let [jwt, middle @ .., last] = parts.as_slice() else {
101 return Err(SdJwtError::Format(
102 "no `~` after the issuer-signed JWT".into(),
103 ));
104 };
105 let jwt = Jwt::new(*jwt)?;
106 let key_binding = if last.is_empty() {
107 None
108 } else {
109 Some(Jwt::new(*last)?)
110 };
111 let mut disclosures = Vec::with_capacity(middle.len());
112 let mut seen = HashSet::new();
113 for part in middle {
114 if part.is_empty() {
115 return Err(SdJwtError::Format("empty disclosure".into()));
116 }
117 if !seen.insert(*part) {
118 return Err(SdJwtError::DuplicateDisclosure((*part).to_owned()));
119 }
120 disclosures.push(Disclosure::parse(part)?);
121 }
122 Ok(Self {
123 jwt,
124 disclosures,
125 key_binding,
126 })
127 }
128
129 pub fn jwt(&self) -> &Jwt {
131 &self.jwt
132 }
133
134 pub fn disclosures(&self) -> &[Disclosure] {
136 &self.disclosures
137 }
138
139 pub fn key_binding_jwt(&self) -> Option<&Jwt> {
141 self.key_binding.as_ref()
142 }
143
144 #[must_use]
146 pub fn with_key_binding(mut self, key_binding: Jwt) -> Self {
147 self.key_binding = Some(key_binding);
148 self
149 }
150
151 #[must_use]
153 pub fn without_key_binding(mut self) -> Self {
154 self.key_binding = None;
155 self
156 }
157
158 pub fn payload(&self) -> Result<Map<String, Value>, SdJwtError> {
165 let jws = self.jwt.decode()?;
166 serde_json::from_slice(jws.payload())
167 .map_err(|e| SdJwtError::Payload(format!("not a JSON object: {e}")))
168 }
169
170 pub fn hash_algorithm(&self) -> Result<HashAlgorithm, SdJwtError> {
178 hash_algorithm(&self.payload()?)
179 }
180
181 pub fn to_string_without_key_binding(&self) -> String {
183 let mut s = String::from(self.jwt.as_str());
184 s.push('~');
185 for disclosure in &self.disclosures {
186 s.push_str(disclosure.as_str());
187 s.push('~');
188 }
189 s
190 }
191
192 pub fn sd_hash(&self, alg: HashAlgorithm) -> String {
195 alg.digest(self.to_string_without_key_binding().as_bytes())
196 }
197
198 pub fn process(&self) -> Result<Processed, SdJwtError> {
214 let mut payload = self.payload()?;
215 let alg = hash_algorithm(&payload)?;
216 payload.shift_remove(HashAlgorithm::CLAIM);
217 let mut processor = Processor::new(alg, &self.disclosures);
218 let issued = processor.object(payload, &ClaimPath::root(), None, 1)?;
219 if let Some(unused) = processor.used.iter().position(|used| !used) {
220 return Err(SdJwtError::UnusedDisclosure(
221 self.disclosures[unused].as_str().to_owned(),
222 ));
223 }
224 let mut payload = Value::Object(issued.clone());
225 strip_placeholders(&mut payload);
226 let Value::Object(payload) = payload else {
227 unreachable!("an object stays an object")
228 };
229 Ok(Processed {
230 payload,
231 disclosed: processor.disclosed,
232 undisclosed: processor.undisclosed,
233 issued,
234 parents: processor.parents,
235 })
236 }
237
238 pub fn select(&self, mut keep: impl FnMut(&Disclosure) -> bool) -> Result<Self, SdJwtError> {
246 let processed = self.process()?;
247 let kept: Vec<bool> = self.disclosures.iter().map(&mut keep).collect();
248 self.selected(&processed, &kept)
249 }
250
251 pub fn disclose<'a>(
259 &self,
260 paths: impl IntoIterator<Item = &'a ClaimPath>,
261 ) -> Result<Self, SdJwtError> {
262 let processed = self.process()?;
263 let mut kept = vec![false; self.disclosures.len()];
264 for path in paths {
265 let mut found = false;
266 for claim in &processed.disclosed {
267 if path.is_prefix_of(&claim.path) {
268 found = true;
269 let mut index = Some(claim.disclosure);
270 while let Some(i) = index {
271 kept[i] = true;
272 index = processed.parents[i];
273 }
274 }
275 }
276 if !found {
277 return Err(SdJwtError::NoSuchClaim(path.clone()));
278 }
279 }
280 self.selected(&processed, &kept)
281 }
282
283 fn selected(&self, processed: &Processed, kept: &[bool]) -> Result<Self, SdJwtError> {
285 for (i, disclosure) in self.disclosures.iter().enumerate() {
286 if !kept[i] {
287 continue;
288 }
289 if let Some(parent) = processed.parents[i]
290 && !kept[parent]
291 {
292 return Err(SdJwtError::MissingParentDisclosure {
293 disclosure: disclosure.as_str().to_owned(),
294 parent: self.disclosures[parent].as_str().to_owned(),
295 });
296 }
297 }
298 let disclosures = self
299 .disclosures
300 .iter()
301 .zip(kept)
302 .filter(|(_, kept)| **kept)
303 .map(|(d, _)| d.clone())
304 .collect();
305 Ok(Self::new(self.jwt.clone(), disclosures))
306 }
307
308 pub async fn sign_key_binding(
316 self,
317 binding: &KeyBinding,
318 signer: &dyn Signer,
319 ) -> Result<Self, SdJwtError> {
320 let sd_jwt = self.without_key_binding();
321 let alg = sd_jwt.hash_algorithm()?;
322 let claims = binding.claims(sd_jwt.sd_hash(alg));
323 let header = JwsHeader::new(signer.alg()).with_typ(KB_JWT_TYP);
324 let key_binding = Jwt::sign(&header, &claims, signer).await?;
325 Ok(sd_jwt.with_key_binding(key_binding))
326 }
327}
328
329fn hash_algorithm(payload: &Map<String, Value>) -> Result<HashAlgorithm, SdJwtError> {
331 match payload.get(HashAlgorithm::CLAIM) {
332 None => Ok(HashAlgorithm::default()),
333 Some(Value::String(name)) => HashAlgorithm::parse(name),
334 Some(_) => Err(SdJwtError::Payload("`_sd_alg` is not a string".into())),
335 }
336}
337
338impl fmt::Debug for SdJwt {
339 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
340 f.debug_struct("SdJwt")
341 .field("jwt", &self.jwt)
342 .field("disclosures", &self.disclosures)
343 .field("key_binding", &self.key_binding)
344 .finish()
345 }
346}
347
348impl fmt::Display for SdJwt {
349 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350 f.write_str(&self.to_string_without_key_binding())?;
351 if let Some(key_binding) = &self.key_binding {
352 f.write_str(key_binding.as_str())?;
353 }
354 Ok(())
355 }
356}
357
358impl FromStr for SdJwt {
359 type Err = SdJwtError;
360
361 fn from_str(s: &str) -> Result<Self, Self::Err> {
362 Self::parse(s)
363 }
364}
365
366impl Serialize for SdJwt {
367 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
368 serializer.collect_str(self)
369 }
370}
371
372impl<'de> Deserialize<'de> for SdJwt {
373 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
374 let s = String::deserialize(deserializer)?;
375 Self::parse(&s).map_err(serde::de::Error::custom)
376 }
377}
378
379#[derive(Debug, Clone, PartialEq, Eq)]
381#[non_exhaustive]
382pub struct Processed {
383 pub payload: Map<String, Value>,
386 pub disclosed: Vec<DisclosedClaim>,
388 pub undisclosed: Vec<ClaimPath>,
391 issued: Map<String, Value>,
392 parents: Vec<Option<usize>>,
393}
394
395impl Processed {
396 pub fn as_issued(&self) -> &Map<String, Value> {
400 &self.issued
401 }
402
403 pub fn disclosed_paths(&self) -> impl Iterator<Item = &ClaimPath> {
405 self.disclosed.iter().map(|claim| &claim.path)
406 }
407
408 pub fn is_disclosed(&self, path: &ClaimPath) -> bool {
410 self.disclosed.iter().any(|claim| claim.path == *path)
411 }
412}
413
414#[derive(Debug, Clone, PartialEq, Eq)]
416#[non_exhaustive]
417pub struct DisclosedClaim {
418 pub path: ClaimPath,
420 pub disclosure: usize,
422}
423
424fn digest_placeholder(value: &Value) -> Option<&str> {
427 let object = value.as_object()?;
428 if object.len() != 1 {
429 return None;
430 }
431 object.get(ELLIPSIS_KEY)?.as_str()
432}
433
434fn strip_placeholders(value: &mut Value) {
436 match value {
437 Value::Object(object) => object.values_mut().for_each(strip_placeholders),
438 Value::Array(array) => {
439 array.retain(|element| digest_placeholder(element).is_none());
440 array.iter_mut().for_each(strip_placeholders);
441 }
442 _ => {}
443 }
444}
445
446struct Processor<'a> {
448 disclosures: &'a [Disclosure],
449 digests: HashMap<String, usize>,
450 used: Vec<bool>,
451 parents: Vec<Option<usize>>,
452 seen: HashSet<String>,
453 disclosed: Vec<DisclosedClaim>,
454 undisclosed: Vec<ClaimPath>,
455}
456
457impl<'a> Processor<'a> {
458 fn new(alg: HashAlgorithm, disclosures: &'a [Disclosure]) -> Self {
459 let digests = disclosures
460 .iter()
461 .enumerate()
462 .map(|(i, d)| (d.digest(alg), i))
463 .collect();
464 Self {
465 disclosures,
466 digests,
467 used: vec![false; disclosures.len()],
468 parents: vec![None; disclosures.len()],
469 seen: HashSet::new(),
470 disclosed: Vec::new(),
471 undisclosed: Vec::new(),
472 }
473 }
474
475 fn lookup(&mut self, digest: &str, owner: Option<usize>) -> Result<Option<usize>, SdJwtError> {
477 if !self.seen.insert(digest.to_owned()) {
478 return Err(SdJwtError::DuplicateDigest(digest.to_owned()));
479 }
480 let Some(&index) = self.digests.get(digest) else {
481 return Ok(None);
482 };
483 self.used[index] = true;
484 self.parents[index] = owner;
485 Ok(Some(index))
486 }
487
488 fn value(
490 &mut self,
491 value: Value,
492 path: &ClaimPath,
493 owner: Option<usize>,
494 depth: usize,
495 ) -> Result<Value, SdJwtError> {
496 match value {
497 Value::Object(_) | Value::Array(_) if depth > SdJwtBuilder::MAX_DEPTH => {
498 Err(SdJwtError::TooDeep {
499 max: SdJwtBuilder::MAX_DEPTH,
500 })
501 }
502 Value::Object(object) => self.object(object, path, owner, depth).map(Value::Object),
503 Value::Array(array) => self.array(array, path, owner, depth).map(Value::Array),
504 other => Ok(other),
505 }
506 }
507
508 fn object(
509 &mut self,
510 object: Map<String, Value>,
511 path: &ClaimPath,
512 owner: Option<usize>,
513 depth: usize,
514 ) -> Result<Map<String, Value>, SdJwtError> {
515 let mut out = Map::new();
516 let mut digests = None;
517 for (name, value) in object {
518 if name == SD_KEY {
519 let Value::Array(array) = value else {
520 return Err(SdJwtError::Payload(format!(
521 "`_sd` at {path} is not an array"
522 )));
523 };
524 digests = Some(array);
525 continue;
526 }
527 let value = self.value(value, &path.join(name.as_str()), owner, depth + 1)?;
528 out.insert(name, value);
529 }
530 for digest in digests.unwrap_or_default() {
531 let Value::String(digest) = digest else {
532 return Err(SdJwtError::Payload(format!(
533 "`_sd` at {path} holds a non-string"
534 )));
535 };
536 let Some(index) = self.lookup(&digest, owner)? else {
537 continue;
538 };
539 let disclosure = &self.disclosures[index];
540 let Some(name) = disclosure.name() else {
541 return Err(SdJwtError::WrongDisclosureKind {
542 disclosure: disclosure.as_str().to_owned(),
543 expected: "an object property disclosure",
544 });
545 };
546 if name == SD_KEY || name == ELLIPSIS_KEY {
547 return Err(SdJwtError::ReservedClaimName(name.to_owned()));
548 }
549 if out.contains_key(name) {
550 return Err(SdJwtError::DuplicateClaim {
551 name: name.to_owned(),
552 path: path.clone(),
553 });
554 }
555 let name = name.to_owned();
556 let claim_path = path.join(name.as_str());
557 let value = self.value(
558 disclosure.value().clone(),
559 &claim_path,
560 Some(index),
561 depth + 1,
562 )?;
563 out.insert(name, value);
564 self.disclosed.push(DisclosedClaim {
565 path: claim_path,
566 disclosure: index,
567 });
568 }
569 Ok(out)
570 }
571
572 fn array(
573 &mut self,
574 array: Vec<Value>,
575 path: &ClaimPath,
576 owner: Option<usize>,
577 depth: usize,
578 ) -> Result<Vec<Value>, SdJwtError> {
579 let mut out = Vec::with_capacity(array.len());
580 for (i, element) in array.into_iter().enumerate() {
581 let element_path = path.join(i);
582 let Some(digest) = digest_placeholder(&element).map(str::to_owned) else {
583 out.push(self.value(element, &element_path, owner, depth + 1)?);
584 continue;
585 };
586 let Some(index) = self.lookup(&digest, owner)? else {
587 self.undisclosed.push(element_path);
588 out.push(element);
589 continue;
590 };
591 let disclosure = &self.disclosures[index];
592 if !disclosure.is_element() {
593 return Err(SdJwtError::WrongDisclosureKind {
594 disclosure: disclosure.as_str().to_owned(),
595 expected: "an array element disclosure",
596 });
597 }
598 let value = self.value(
599 disclosure.value().clone(),
600 &element_path,
601 Some(index),
602 depth + 1,
603 )?;
604 out.push(value);
605 self.disclosed.push(DisclosedClaim {
606 path: element_path,
607 disclosure: index,
608 });
609 }
610 Ok(out)
611 }
612}
613
614#[derive(Debug, Clone, PartialEq, Eq)]
633#[non_exhaustive]
634pub struct KeyBinding {
635 pub audience: String,
637 pub nonce: String,
639 pub issued_at: i64,
641 pub additional: IndexMap<String, Value>,
643}
644
645impl KeyBinding {
646 pub fn new(
648 audience: impl Into<String>,
649 nonce: impl Into<String>,
650 issued_at: DateTime<Utc>,
651 ) -> Self {
652 Self {
653 audience: audience.into(),
654 nonce: nonce.into(),
655 issued_at: JwtClaims::numeric_date(issued_at),
656 additional: IndexMap::new(),
657 }
658 }
659
660 #[must_use]
662 pub fn with_claim(mut self, name: impl Into<String>, value: impl Into<Value>) -> Self {
663 self.additional.insert(name.into(), value.into());
664 self
665 }
666
667 pub fn claims(&self, sd_hash: String) -> JwtClaims {
669 let mut claims = JwtClaims::new().with_aud(self.audience.clone());
670 claims.iat = Some(self.issued_at);
671 claims
672 .additional
673 .insert("nonce".to_owned(), Value::String(self.nonce.clone()));
674 claims
675 .additional
676 .insert("sd_hash".to_owned(), Value::String(sd_hash));
677 claims.additional.extend(self.additional.clone());
678 claims
679 }
680}