1use core::fmt;
4
5use serde_json::{Map, Value};
6
7use super::sd_jwt::{ELLIPSIS_KEY, SD_KEY};
8use super::{ClaimPath, Disclosure, HashAlgorithm, SdJwt, SdJwtError};
9use crate::jose::{JwsHeader, Signer, base64url, encode_compact};
10
11pub struct SdJwtBuilder {
53 claims: Map<String, Value>,
54 concealed: Vec<ClaimPath>,
55 decoys: usize,
56 alg: HashAlgorithm,
57 salts: Box<dyn FnMut() -> String + Send>,
58}
59
60impl SdJwtBuilder {
61 pub const MAX_DEPTH: usize = 64;
68
69 pub fn new(claims: Map<String, Value>) -> Self {
71 Self {
72 claims,
73 concealed: Vec::new(),
74 decoys: 0,
75 alg: HashAlgorithm::default(),
76 salts: Box::new(random_salt),
77 }
78 }
79
80 #[must_use]
83 pub fn conceal(mut self, path: impl Into<ClaimPath>) -> Self {
84 self.concealed.push(path.into());
85 self
86 }
87
88 #[must_use]
90 pub fn with_decoys(mut self, count: usize) -> Self {
91 self.decoys = count;
92 self
93 }
94
95 #[must_use]
97 pub fn with_hash_algorithm(mut self, alg: HashAlgorithm) -> Self {
98 self.alg = alg;
99 self
100 }
101
102 #[must_use]
106 pub fn with_salt_generator(mut self, salts: impl FnMut() -> String + Send + 'static) -> Self {
107 self.salts = Box::new(salts);
108 self
109 }
110
111 pub fn claims(&self) -> &Map<String, Value> {
113 &self.claims
114 }
115
116 pub fn concealed(&self) -> &[ClaimPath] {
118 &self.concealed
119 }
120
121 pub fn encode(mut self) -> Result<Encoded, SdJwtError> {
130 if self.claims.contains_key(HashAlgorithm::CLAIM) {
131 return Err(SdJwtError::Payload(
132 "the claims already carry `_sd_alg`".into(),
133 ));
134 }
135 if self
136 .claims
137 .values()
138 .any(|value| nests_deeper_than(value, Self::MAX_DEPTH - 1))
139 {
140 return Err(SdJwtError::TooDeep {
141 max: Self::MAX_DEPTH,
142 });
143 }
144 let mut matched = vec![false; self.concealed.len()];
145 let mut disclosures = Vec::new();
146 let mut payload = self.object(
147 self.claims.clone(),
148 &ClaimPath::root(),
149 &mut matched,
150 &mut disclosures,
151 )?;
152 if let Some(unmatched) = matched.iter().position(|m| !m) {
153 return Err(SdJwtError::NoSuchClaim(self.concealed[unmatched].clone()));
154 }
155 if !disclosures.is_empty() || payload.contains_key(SD_KEY) {
156 payload.insert(
157 HashAlgorithm::CLAIM.to_owned(),
158 Value::String(self.alg.name().to_owned()),
159 );
160 }
161 Ok(Encoded {
162 payload,
163 disclosures,
164 })
165 }
166
167 pub async fn sign(self, header: &JwsHeader, signer: &dyn Signer) -> Result<SdJwt, SdJwtError> {
173 let encoded = self.encode()?;
174 let payload = serde_json::to_vec(&encoded.payload).expect("a JSON object serializes");
175 let jwt = encode_compact(header, &payload, signer).await?;
176 Ok(SdJwt::new(
177 crate::vc::Jwt::new(jwt).expect("a freshly encoded JWS decodes"),
178 encoded.disclosures,
179 ))
180 }
181
182 fn is_concealed(&self, path: &ClaimPath, matched: &mut [bool]) -> bool {
184 let mut concealed = false;
185 for (i, pattern) in self.concealed.iter().enumerate() {
186 if pattern.matches(path) {
187 matched[i] = true;
188 concealed = true;
189 }
190 }
191 concealed
192 }
193
194 fn value(
195 &mut self,
196 value: Value,
197 path: &ClaimPath,
198 matched: &mut [bool],
199 disclosures: &mut Vec<Disclosure>,
200 ) -> Result<Value, SdJwtError> {
201 match value {
202 Value::Object(object) => self
203 .object(object, path, matched, disclosures)
204 .map(Value::Object),
205 Value::Array(array) => self
206 .array(array, path, matched, disclosures)
207 .map(Value::Array),
208 other => Ok(other),
209 }
210 }
211
212 fn object(
213 &mut self,
214 object: Map<String, Value>,
215 path: &ClaimPath,
216 matched: &mut [bool],
217 disclosures: &mut Vec<Disclosure>,
218 ) -> Result<Map<String, Value>, SdJwtError> {
219 if object.contains_key(SD_KEY) {
220 return Err(SdJwtError::Payload(format!(
221 "the object at {path} already carries `_sd`"
222 )));
223 }
224 let mut out = Map::new();
225 let mut digests = Vec::new();
226 for (name, value) in object {
227 let child = path.join(name.as_str());
228 let value = self.value(value, &child, matched, disclosures)?;
229 if !self.is_concealed(&child, matched) {
230 out.insert(name, value);
231 continue;
232 }
233 if name == SD_KEY || name == ELLIPSIS_KEY {
234 return Err(SdJwtError::NotConcealable {
235 path: child,
236 reason: format!("`{name}` is reserved"),
237 });
238 }
239 let disclosure = Disclosure::property((self.salts)(), name, value);
240 digests.push(disclosure.digest(self.alg));
241 disclosures.push(disclosure);
242 }
243 if !digests.is_empty() {
244 for _ in 0..self.decoys {
245 digests.push(self.alg.digest((self.salts)().as_bytes()));
246 }
247 digests.sort_unstable();
248 out.insert(
249 SD_KEY.to_owned(),
250 Value::Array(digests.into_iter().map(Value::String).collect()),
251 );
252 }
253 Ok(out)
254 }
255
256 fn array(
257 &mut self,
258 array: Vec<Value>,
259 path: &ClaimPath,
260 matched: &mut [bool],
261 disclosures: &mut Vec<Disclosure>,
262 ) -> Result<Vec<Value>, SdJwtError> {
263 let mut out = Vec::with_capacity(array.len());
264 for (i, element) in array.into_iter().enumerate() {
265 let child = path.join(i);
266 let element = self.value(element, &child, matched, disclosures)?;
267 if !self.is_concealed(&child, matched) {
268 out.push(element);
269 continue;
270 }
271 let disclosure = Disclosure::element((self.salts)(), element);
272 let mut placeholder = Map::new();
273 placeholder.insert(
274 ELLIPSIS_KEY.to_owned(),
275 Value::String(disclosure.digest(self.alg)),
276 );
277 disclosures.push(disclosure);
278 out.push(Value::Object(placeholder));
279 }
280 Ok(out)
281 }
282}
283
284impl fmt::Debug for SdJwtBuilder {
285 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286 f.debug_struct("SdJwtBuilder")
287 .field("claims", &self.claims)
288 .field("concealed", &self.concealed)
289 .field("decoys", &self.decoys)
290 .field("alg", &self.alg)
291 .finish_non_exhaustive()
292 }
293}
294
295pub(super) fn random_salt() -> String {
297 let mut bytes = [0u8; 16];
298 getrandom::fill(&mut bytes).expect("the operating system provides randomness");
299 base64url::encode(&bytes)
300}
301
302#[derive(Debug, Clone, PartialEq, Eq)]
304#[non_exhaustive]
305pub struct Encoded {
306 pub payload: Map<String, Value>,
309 pub disclosures: Vec<Disclosure>,
311}
312
313fn nests_deeper_than(value: &Value, limit: usize) -> bool {
317 match value {
318 Value::Array(items) => {
319 limit == 0 || items.iter().any(|item| nests_deeper_than(item, limit - 1))
320 }
321 Value::Object(members) => {
322 limit == 0
323 || members
324 .values()
325 .any(|member| nests_deeper_than(member, limit - 1))
326 }
327 _ => false,
328 }
329}