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