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()).map_err(|e| SdJwtError::Payload {
167 reason: "not a JSON object".to_owned(),
168 source: Some(e),
169 })
170 }
171
172 pub fn hash_algorithm(&self) -> Result<HashAlgorithm, SdJwtError> {
180 hash_algorithm(&self.payload()?)
181 }
182
183 pub fn to_string_without_key_binding(&self) -> String {
185 let mut s = String::from(self.jwt.as_str());
186 s.push('~');
187 for disclosure in &self.disclosures {
188 s.push_str(disclosure.as_str());
189 s.push('~');
190 }
191 s
192 }
193
194 pub fn sd_hash(&self, alg: HashAlgorithm) -> String {
197 alg.digest(self.to_string_without_key_binding().as_bytes())
198 }
199
200 pub fn process(&self) -> Result<Processed, SdJwtError> {
216 let mut payload = self.payload()?;
217 let alg = hash_algorithm(&payload)?;
218 payload.shift_remove(HashAlgorithm::CLAIM);
219 let mut processor = Processor::new(alg, &self.disclosures);
220 let issued = processor.object(payload, &ClaimPath::root(), None, 1)?;
221 if let Some(unused) = processor.used.iter().position(|used| !used) {
222 return Err(SdJwtError::UnusedDisclosure(
223 self.disclosures[unused].as_str().to_owned(),
224 ));
225 }
226 let mut payload = Value::Object(issued.clone());
227 strip_placeholders(&mut payload);
228 let Value::Object(payload) = payload else {
229 unreachable!("an object stays an object")
230 };
231 Ok(Processed {
232 payload,
233 disclosed: processor.disclosed,
234 undisclosed: processor.undisclosed,
235 issued,
236 parents: processor.parents,
237 })
238 }
239
240 pub fn select(&self, mut keep: impl FnMut(&Disclosure) -> bool) -> Result<Self, SdJwtError> {
248 let processed = self.process()?;
249 let kept: Vec<bool> = self.disclosures.iter().map(&mut keep).collect();
250 self.selected(&processed, &kept)
251 }
252
253 pub fn disclose<'a>(
261 &self,
262 paths: impl IntoIterator<Item = &'a ClaimPath>,
263 ) -> Result<Self, SdJwtError> {
264 let processed = self.process()?;
265 let mut kept = vec![false; self.disclosures.len()];
266 for path in paths {
267 let mut found = false;
268 for claim in &processed.disclosed {
269 if path.is_prefix_of(&claim.path) {
270 found = true;
271 let mut index = Some(claim.disclosure);
272 while let Some(i) = index {
273 kept[i] = true;
274 index = processed.parents[i];
275 }
276 }
277 }
278 if !found {
279 return Err(SdJwtError::NoSuchClaim(path.clone()));
280 }
281 }
282 self.selected(&processed, &kept)
283 }
284
285 fn selected(&self, processed: &Processed, kept: &[bool]) -> Result<Self, SdJwtError> {
287 for (i, disclosure) in self.disclosures.iter().enumerate() {
288 if !kept[i] {
289 continue;
290 }
291 if let Some(parent) = processed.parents[i]
292 && !kept[parent]
293 {
294 return Err(SdJwtError::MissingParentDisclosure {
295 disclosure: disclosure.as_str().to_owned(),
296 parent: self.disclosures[parent].as_str().to_owned(),
297 });
298 }
299 }
300 let disclosures = self
301 .disclosures
302 .iter()
303 .zip(kept)
304 .filter(|(_, kept)| **kept)
305 .map(|(d, _)| d.clone())
306 .collect();
307 Ok(Self::new(self.jwt.clone(), disclosures))
308 }
309
310 pub async fn sign_key_binding(
318 self,
319 binding: &KeyBinding,
320 signer: &dyn Signer,
321 ) -> Result<Self, SdJwtError> {
322 let sd_jwt = self.without_key_binding();
323 let alg = sd_jwt.hash_algorithm()?;
324 let claims = binding.claims(sd_jwt.sd_hash(alg));
325 let header = JwsHeader::new(signer.alg()).with_typ(KB_JWT_TYP);
326 let key_binding = Jwt::sign(&header, &claims, signer).await?;
327 Ok(sd_jwt.with_key_binding(key_binding))
328 }
329}
330
331fn hash_algorithm(payload: &Map<String, Value>) -> Result<HashAlgorithm, SdJwtError> {
333 match payload.get(HashAlgorithm::CLAIM) {
334 None => Ok(HashAlgorithm::default()),
335 Some(Value::String(name)) => HashAlgorithm::parse(name),
336 Some(_) => Err(SdJwtError::payload("`_sd_alg` is not a string")),
337 }
338}
339
340impl fmt::Debug for SdJwt {
341 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
342 f.debug_struct("SdJwt")
343 .field("jwt", &self.jwt)
344 .field("disclosures", &self.disclosures)
345 .field("key_binding", &self.key_binding)
346 .finish()
347 }
348}
349
350impl fmt::Display for SdJwt {
351 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
352 f.write_str(&self.to_string_without_key_binding())?;
353 if let Some(key_binding) = &self.key_binding {
354 f.write_str(key_binding.as_str())?;
355 }
356 Ok(())
357 }
358}
359
360impl FromStr for SdJwt {
361 type Err = SdJwtError;
362
363 fn from_str(s: &str) -> Result<Self, Self::Err> {
364 Self::parse(s)
365 }
366}
367
368impl Serialize for SdJwt {
369 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
370 serializer.collect_str(self)
371 }
372}
373
374impl<'de> Deserialize<'de> for SdJwt {
375 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
376 let s = String::deserialize(deserializer)?;
377 Self::parse(&s).map_err(serde::de::Error::custom)
378 }
379}
380
381#[derive(Debug, Clone, PartialEq, Eq)]
383#[non_exhaustive]
384pub struct Processed {
385 pub payload: Map<String, Value>,
388 pub disclosed: Vec<DisclosedClaim>,
390 pub undisclosed: Vec<ClaimPath>,
393 issued: Map<String, Value>,
394 parents: Vec<Option<usize>>,
395}
396
397impl Processed {
398 pub fn as_issued(&self) -> &Map<String, Value> {
402 &self.issued
403 }
404
405 pub fn disclosed_paths(&self) -> impl Iterator<Item = &ClaimPath> {
407 self.disclosed.iter().map(|claim| &claim.path)
408 }
409
410 pub fn is_disclosed(&self, path: &ClaimPath) -> bool {
412 self.disclosed.iter().any(|claim| claim.path == *path)
413 }
414}
415
416#[derive(Debug, Clone, PartialEq, Eq)]
418#[non_exhaustive]
419pub struct DisclosedClaim {
420 pub path: ClaimPath,
422 pub disclosure: usize,
424}
425
426fn digest_placeholder(value: &Value) -> Option<&str> {
429 let object = value.as_object()?;
430 if object.len() != 1 {
431 return None;
432 }
433 object.get(ELLIPSIS_KEY)?.as_str()
434}
435
436fn strip_placeholders(value: &mut Value) {
438 match value {
439 Value::Object(object) => object.values_mut().for_each(strip_placeholders),
440 Value::Array(array) => {
441 array.retain(|element| digest_placeholder(element).is_none());
442 array.iter_mut().for_each(strip_placeholders);
443 }
444 _ => {}
445 }
446}
447
448struct Processor<'a> {
450 disclosures: &'a [Disclosure],
451 digests: HashMap<String, usize>,
452 used: Vec<bool>,
453 parents: Vec<Option<usize>>,
454 seen: HashSet<String>,
455 disclosed: Vec<DisclosedClaim>,
456 undisclosed: Vec<ClaimPath>,
457}
458
459impl<'a> Processor<'a> {
460 fn new(alg: HashAlgorithm, disclosures: &'a [Disclosure]) -> Self {
461 let digests = disclosures
462 .iter()
463 .enumerate()
464 .map(|(i, d)| (d.digest(alg), i))
465 .collect();
466 Self {
467 disclosures,
468 digests,
469 used: vec![false; disclosures.len()],
470 parents: vec![None; disclosures.len()],
471 seen: HashSet::new(),
472 disclosed: Vec::new(),
473 undisclosed: Vec::new(),
474 }
475 }
476
477 fn lookup(&mut self, digest: &str, owner: Option<usize>) -> Result<Option<usize>, SdJwtError> {
479 if !self.seen.insert(digest.to_owned()) {
480 return Err(SdJwtError::DuplicateDigest(digest.to_owned()));
481 }
482 let Some(&index) = self.digests.get(digest) else {
483 return Ok(None);
484 };
485 self.used[index] = true;
486 self.parents[index] = owner;
487 Ok(Some(index))
488 }
489
490 fn value(
492 &mut self,
493 value: Value,
494 path: &ClaimPath,
495 owner: Option<usize>,
496 depth: usize,
497 ) -> Result<Value, SdJwtError> {
498 match value {
499 Value::Object(_) | Value::Array(_) if depth > SdJwtBuilder::MAX_DEPTH => {
500 Err(SdJwtError::TooDeep {
501 max: SdJwtBuilder::MAX_DEPTH,
502 })
503 }
504 Value::Object(object) => self.object(object, path, owner, depth).map(Value::Object),
505 Value::Array(array) => self.array(array, path, owner, depth).map(Value::Array),
506 other => Ok(other),
507 }
508 }
509
510 fn object(
511 &mut self,
512 object: Map<String, Value>,
513 path: &ClaimPath,
514 owner: Option<usize>,
515 depth: usize,
516 ) -> Result<Map<String, Value>, SdJwtError> {
517 let mut out = Map::new();
518 let mut digests = None;
519 for (name, value) in object {
520 if name == SD_KEY {
521 let Value::Array(array) = value else {
522 return Err(SdJwtError::payload(format!(
523 "`_sd` at {path} is not an array"
524 )));
525 };
526 digests = Some(array);
527 continue;
528 }
529 let value = self.value(value, &path.join(name.as_str()), owner, depth + 1)?;
530 out.insert(name, value);
531 }
532 for digest in digests.unwrap_or_default() {
533 let Value::String(digest) = digest else {
534 return Err(SdJwtError::payload(format!(
535 "`_sd` at {path} holds a non-string"
536 )));
537 };
538 let Some(index) = self.lookup(&digest, owner)? else {
539 continue;
540 };
541 let disclosure = &self.disclosures[index];
542 let Some(name) = disclosure.name() else {
543 return Err(SdJwtError::WrongDisclosureKind {
544 disclosure: disclosure.as_str().to_owned(),
545 expected: "an object property disclosure",
546 });
547 };
548 if name == SD_KEY || name == ELLIPSIS_KEY {
549 return Err(SdJwtError::ReservedClaimName(name.to_owned()));
550 }
551 if out.contains_key(name) {
552 return Err(SdJwtError::DuplicateClaim {
553 name: name.to_owned(),
554 path: path.clone(),
555 });
556 }
557 let name = name.to_owned();
558 let claim_path = path.join(name.as_str());
559 let value = self.value(
560 disclosure.value().clone(),
561 &claim_path,
562 Some(index),
563 depth + 1,
564 )?;
565 out.insert(name, value);
566 self.disclosed.push(DisclosedClaim {
567 path: claim_path,
568 disclosure: index,
569 });
570 }
571 Ok(out)
572 }
573
574 fn array(
575 &mut self,
576 array: Vec<Value>,
577 path: &ClaimPath,
578 owner: Option<usize>,
579 depth: usize,
580 ) -> Result<Vec<Value>, SdJwtError> {
581 let mut out = Vec::with_capacity(array.len());
582 for (i, element) in array.into_iter().enumerate() {
583 let element_path = path.join(i);
584 let Some(digest) = digest_placeholder(&element).map(str::to_owned) else {
585 out.push(self.value(element, &element_path, owner, depth + 1)?);
586 continue;
587 };
588 let Some(index) = self.lookup(&digest, owner)? else {
589 self.undisclosed.push(element_path);
590 out.push(element);
591 continue;
592 };
593 let disclosure = &self.disclosures[index];
594 if !disclosure.is_element() {
595 return Err(SdJwtError::WrongDisclosureKind {
596 disclosure: disclosure.as_str().to_owned(),
597 expected: "an array element disclosure",
598 });
599 }
600 let value = self.value(
601 disclosure.value().clone(),
602 &element_path,
603 Some(index),
604 depth + 1,
605 )?;
606 out.push(value);
607 self.disclosed.push(DisclosedClaim {
608 path: element_path,
609 disclosure: index,
610 });
611 }
612 Ok(out)
613 }
614}
615
616#[derive(Debug, Clone, PartialEq, Eq)]
635#[non_exhaustive]
636pub struct KeyBinding {
637 pub audience: String,
639 pub nonce: String,
641 pub issued_at: i64,
643 pub additional: IndexMap<String, Value>,
645}
646
647impl KeyBinding {
648 pub fn new(
650 audience: impl Into<String>,
651 nonce: impl Into<String>,
652 issued_at: DateTime<Utc>,
653 ) -> Self {
654 Self {
655 audience: audience.into(),
656 nonce: nonce.into(),
657 issued_at: JwtClaims::numeric_date(issued_at),
658 additional: IndexMap::new(),
659 }
660 }
661
662 #[must_use]
664 pub fn with_claim(mut self, name: impl Into<String>, value: impl Into<Value>) -> Self {
665 self.additional.insert(name.into(), value.into());
666 self
667 }
668
669 pub fn claims(&self, sd_hash: String) -> JwtClaims {
671 let mut claims = JwtClaims::new().with_aud(self.audience.clone());
672 claims.iat = Some(self.issued_at);
673 claims
674 .additional
675 .insert("nonce".to_owned(), Value::String(self.nonce.clone()));
676 claims
677 .additional
678 .insert("sd_hash".to_owned(), Value::String(sd_hash));
679 claims.additional.extend(self.additional.clone());
680 claims
681 }
682}