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, 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> {
209 let mut payload = self.payload()?;
210 let alg = hash_algorithm(&payload)?;
211 payload.shift_remove(HashAlgorithm::CLAIM);
212 let mut processor = Processor::new(alg, &self.disclosures);
213 let issued = processor.object(payload, &ClaimPath::root(), None)?;
214 if let Some(unused) = processor.used.iter().position(|used| !used) {
215 return Err(SdJwtError::UnusedDisclosure(
216 self.disclosures[unused].as_str().to_owned(),
217 ));
218 }
219 let mut payload = Value::Object(issued.clone());
220 strip_placeholders(&mut payload);
221 let Value::Object(payload) = payload else {
222 unreachable!("an object stays an object")
223 };
224 Ok(Processed {
225 payload,
226 disclosed: processor.disclosed,
227 undisclosed: processor.undisclosed,
228 issued,
229 parents: processor.parents,
230 })
231 }
232
233 pub fn select(&self, mut keep: impl FnMut(&Disclosure) -> bool) -> Result<Self, SdJwtError> {
241 let processed = self.process()?;
242 let kept: Vec<bool> = self.disclosures.iter().map(&mut keep).collect();
243 self.selected(&processed, &kept)
244 }
245
246 pub fn disclose<'a>(
254 &self,
255 paths: impl IntoIterator<Item = &'a ClaimPath>,
256 ) -> Result<Self, SdJwtError> {
257 let processed = self.process()?;
258 let mut kept = vec![false; self.disclosures.len()];
259 for path in paths {
260 let mut found = false;
261 for claim in &processed.disclosed {
262 if path.is_prefix_of(&claim.path) {
263 found = true;
264 let mut index = Some(claim.disclosure);
265 while let Some(i) = index {
266 kept[i] = true;
267 index = processed.parents[i];
268 }
269 }
270 }
271 if !found {
272 return Err(SdJwtError::NoSuchClaim(path.clone()));
273 }
274 }
275 self.selected(&processed, &kept)
276 }
277
278 fn selected(&self, processed: &Processed, kept: &[bool]) -> Result<Self, SdJwtError> {
280 for (i, disclosure) in self.disclosures.iter().enumerate() {
281 if !kept[i] {
282 continue;
283 }
284 if let Some(parent) = processed.parents[i]
285 && !kept[parent]
286 {
287 return Err(SdJwtError::MissingParentDisclosure {
288 disclosure: disclosure.as_str().to_owned(),
289 parent: self.disclosures[parent].as_str().to_owned(),
290 });
291 }
292 }
293 let disclosures = self
294 .disclosures
295 .iter()
296 .zip(kept)
297 .filter(|(_, kept)| **kept)
298 .map(|(d, _)| d.clone())
299 .collect();
300 Ok(Self::new(self.jwt.clone(), disclosures))
301 }
302
303 pub async fn sign_key_binding(
311 self,
312 binding: &KeyBinding,
313 signer: &dyn Signer,
314 ) -> Result<Self, SdJwtError> {
315 let sd_jwt = self.without_key_binding();
316 let alg = sd_jwt.hash_algorithm()?;
317 let claims = binding.claims(sd_jwt.sd_hash(alg));
318 let header = JwsHeader::new(signer.alg()).with_typ(KB_JWT_TYP);
319 let key_binding = Jwt::sign(&header, &claims, signer).await?;
320 Ok(sd_jwt.with_key_binding(key_binding))
321 }
322}
323
324fn hash_algorithm(payload: &Map<String, Value>) -> Result<HashAlgorithm, SdJwtError> {
326 match payload.get(HashAlgorithm::CLAIM) {
327 None => Ok(HashAlgorithm::default()),
328 Some(Value::String(name)) => HashAlgorithm::parse(name),
329 Some(_) => Err(SdJwtError::Payload("`_sd_alg` is not a string".into())),
330 }
331}
332
333impl fmt::Debug for SdJwt {
334 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
335 f.debug_struct("SdJwt")
336 .field("jwt", &self.jwt)
337 .field("disclosures", &self.disclosures)
338 .field("key_binding", &self.key_binding)
339 .finish()
340 }
341}
342
343impl fmt::Display for SdJwt {
344 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
345 f.write_str(&self.to_string_without_key_binding())?;
346 if let Some(key_binding) = &self.key_binding {
347 f.write_str(key_binding.as_str())?;
348 }
349 Ok(())
350 }
351}
352
353impl FromStr for SdJwt {
354 type Err = SdJwtError;
355
356 fn from_str(s: &str) -> Result<Self, Self::Err> {
357 Self::parse(s)
358 }
359}
360
361impl Serialize for SdJwt {
362 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
363 serializer.collect_str(self)
364 }
365}
366
367impl<'de> Deserialize<'de> for SdJwt {
368 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
369 let s = String::deserialize(deserializer)?;
370 Self::parse(&s).map_err(serde::de::Error::custom)
371 }
372}
373
374#[derive(Debug, Clone, PartialEq, Eq)]
376#[non_exhaustive]
377pub struct Processed {
378 pub payload: Map<String, Value>,
381 pub disclosed: Vec<DisclosedClaim>,
383 pub undisclosed: Vec<ClaimPath>,
386 issued: Map<String, Value>,
387 parents: Vec<Option<usize>>,
388}
389
390impl Processed {
391 pub fn as_issued(&self) -> &Map<String, Value> {
395 &self.issued
396 }
397
398 pub fn disclosed_paths(&self) -> impl Iterator<Item = &ClaimPath> {
400 self.disclosed.iter().map(|claim| &claim.path)
401 }
402
403 pub fn is_disclosed(&self, path: &ClaimPath) -> bool {
405 self.disclosed.iter().any(|claim| claim.path == *path)
406 }
407}
408
409#[derive(Debug, Clone, PartialEq, Eq)]
411#[non_exhaustive]
412pub struct DisclosedClaim {
413 pub path: ClaimPath,
415 pub disclosure: usize,
417}
418
419fn digest_placeholder(value: &Value) -> Option<&str> {
422 let object = value.as_object()?;
423 if object.len() != 1 {
424 return None;
425 }
426 object.get(ELLIPSIS_KEY)?.as_str()
427}
428
429fn strip_placeholders(value: &mut Value) {
431 match value {
432 Value::Object(object) => object.values_mut().for_each(strip_placeholders),
433 Value::Array(array) => {
434 array.retain(|element| digest_placeholder(element).is_none());
435 array.iter_mut().for_each(strip_placeholders);
436 }
437 _ => {}
438 }
439}
440
441struct Processor<'a> {
443 disclosures: &'a [Disclosure],
444 digests: HashMap<String, usize>,
445 used: Vec<bool>,
446 parents: Vec<Option<usize>>,
447 seen: HashSet<String>,
448 disclosed: Vec<DisclosedClaim>,
449 undisclosed: Vec<ClaimPath>,
450}
451
452impl<'a> Processor<'a> {
453 fn new(alg: HashAlgorithm, disclosures: &'a [Disclosure]) -> Self {
454 let digests = disclosures
455 .iter()
456 .enumerate()
457 .map(|(i, d)| (d.digest(alg), i))
458 .collect();
459 Self {
460 disclosures,
461 digests,
462 used: vec![false; disclosures.len()],
463 parents: vec![None; disclosures.len()],
464 seen: HashSet::new(),
465 disclosed: Vec::new(),
466 undisclosed: Vec::new(),
467 }
468 }
469
470 fn lookup(&mut self, digest: &str, owner: Option<usize>) -> Result<Option<usize>, SdJwtError> {
472 if !self.seen.insert(digest.to_owned()) {
473 return Err(SdJwtError::DuplicateDigest(digest.to_owned()));
474 }
475 let Some(&index) = self.digests.get(digest) else {
476 return Ok(None);
477 };
478 self.used[index] = true;
479 self.parents[index] = owner;
480 Ok(Some(index))
481 }
482
483 fn value(
484 &mut self,
485 value: Value,
486 path: &ClaimPath,
487 owner: Option<usize>,
488 ) -> Result<Value, SdJwtError> {
489 match value {
490 Value::Object(object) => self.object(object, path, owner).map(Value::Object),
491 Value::Array(array) => self.array(array, path, owner).map(Value::Array),
492 other => Ok(other),
493 }
494 }
495
496 fn object(
497 &mut self,
498 object: Map<String, Value>,
499 path: &ClaimPath,
500 owner: Option<usize>,
501 ) -> Result<Map<String, Value>, SdJwtError> {
502 let mut out = Map::new();
503 let mut digests = None;
504 for (name, value) in object {
505 if name == SD_KEY {
506 let Value::Array(array) = value else {
507 return Err(SdJwtError::Payload(format!(
508 "`_sd` at {path} is not an array"
509 )));
510 };
511 digests = Some(array);
512 continue;
513 }
514 let value = self.value(value, &path.join(name.as_str()), owner)?;
515 out.insert(name, value);
516 }
517 for digest in digests.unwrap_or_default() {
518 let Value::String(digest) = digest else {
519 return Err(SdJwtError::Payload(format!(
520 "`_sd` at {path} holds a non-string"
521 )));
522 };
523 let Some(index) = self.lookup(&digest, owner)? else {
524 continue;
525 };
526 let disclosure = &self.disclosures[index];
527 let Some(name) = disclosure.name() else {
528 return Err(SdJwtError::WrongDisclosureKind {
529 disclosure: disclosure.as_str().to_owned(),
530 expected: "an object property disclosure",
531 });
532 };
533 if name == SD_KEY || name == ELLIPSIS_KEY {
534 return Err(SdJwtError::ReservedClaimName(name.to_owned()));
535 }
536 if out.contains_key(name) {
537 return Err(SdJwtError::DuplicateClaim {
538 name: name.to_owned(),
539 path: path.clone(),
540 });
541 }
542 let name = name.to_owned();
543 let claim_path = path.join(name.as_str());
544 let value = self.value(disclosure.value().clone(), &claim_path, Some(index))?;
545 out.insert(name, value);
546 self.disclosed.push(DisclosedClaim {
547 path: claim_path,
548 disclosure: index,
549 });
550 }
551 Ok(out)
552 }
553
554 fn array(
555 &mut self,
556 array: Vec<Value>,
557 path: &ClaimPath,
558 owner: Option<usize>,
559 ) -> Result<Vec<Value>, SdJwtError> {
560 let mut out = Vec::with_capacity(array.len());
561 for (i, element) in array.into_iter().enumerate() {
562 let element_path = path.join(i);
563 let Some(digest) = digest_placeholder(&element).map(str::to_owned) else {
564 out.push(self.value(element, &element_path, owner)?);
565 continue;
566 };
567 let Some(index) = self.lookup(&digest, owner)? else {
568 self.undisclosed.push(element_path);
569 out.push(element);
570 continue;
571 };
572 let disclosure = &self.disclosures[index];
573 if !disclosure.is_element() {
574 return Err(SdJwtError::WrongDisclosureKind {
575 disclosure: disclosure.as_str().to_owned(),
576 expected: "an array element disclosure",
577 });
578 }
579 let value = self.value(disclosure.value().clone(), &element_path, Some(index))?;
580 out.push(value);
581 self.disclosed.push(DisclosedClaim {
582 path: element_path,
583 disclosure: index,
584 });
585 }
586 Ok(out)
587 }
588}
589
590#[derive(Debug, Clone, PartialEq, Eq)]
609#[non_exhaustive]
610pub struct KeyBinding {
611 pub audience: String,
613 pub nonce: String,
615 pub issued_at: i64,
617 pub additional: IndexMap<String, Value>,
619}
620
621impl KeyBinding {
622 pub fn new(
624 audience: impl Into<String>,
625 nonce: impl Into<String>,
626 issued_at: DateTime<Utc>,
627 ) -> Self {
628 Self {
629 audience: audience.into(),
630 nonce: nonce.into(),
631 issued_at: JwtClaims::numeric_date(issued_at),
632 additional: IndexMap::new(),
633 }
634 }
635
636 #[must_use]
638 pub fn with_claim(mut self, name: impl Into<String>, value: impl Into<Value>) -> Self {
639 self.additional.insert(name.into(), value.into());
640 self
641 }
642
643 pub fn claims(&self, sd_hash: String) -> JwtClaims {
645 let mut claims = JwtClaims::new().with_aud(self.audience.clone());
646 claims.iat = Some(self.issued_at);
647 claims
648 .additional
649 .insert("nonce".to_owned(), Value::String(self.nonce.clone()));
650 claims
651 .additional
652 .insert("sd_hash".to_owned(), Value::String(sd_hash));
653 claims.additional.extend(self.additional.clone());
654 claims
655 }
656}