ssi_sd_jwt/
conceal.rs

1use std::borrow::Borrow;
2
3use base64::Engine;
4use rand::{thread_rng, CryptoRng, RngCore};
5use serde::Serialize;
6use serde_json::Value;
7use ssi_claims_core::SignatureError;
8use ssi_core::JsonPointer;
9use ssi_jws::JwsSigner;
10use ssi_jwt::JWTClaims;
11
12use crate::{
13    DecodedDisclosure, Disclosure, DisclosureDescription, SdAlg, SdJwtBuf, SdJwtPayload,
14    ARRAY_CLAIM_ITEM_PROPERTY_NAME, SD_CLAIM_NAME,
15};
16
17/// Error that can occur during concealing.
18#[derive(Debug, thiserror::Error)]
19pub enum ConcealError {
20    /// Serialization failed.
21    #[error(transparent)]
22    Serialization(#[from] serde_json::Error),
23
24    /// Concealed JSON value is not an object.
25    #[error("concealed JSON value is not an object")]
26    NotAnObject,
27
28    /// Tried to conceal the root object.
29    #[error("cannot conceal root")]
30    CannotConcealRoot,
31
32    /// Value to conceal not found.
33    #[error("value not found")]
34    NotFound,
35
36    /// The `_sd` entry is not an array.
37    #[error("the `_sd` entry is not an array")]
38    SdEntryNotAnArray,
39}
40
41/// JWT claims concealing methods.
42pub trait ConcealJwtClaims {
43    /// Conceals these JWT claims.
44    fn conceal(
45        &self,
46        sd_alg: SdAlg,
47        pointers: &[impl Borrow<JsonPointer>],
48    ) -> Result<(SdJwtPayload, Vec<DecodedDisclosure<'static>>), ConcealError>;
49
50    /// Conceals these JWT claims with the given `rng`.
51    fn conceal_with(
52        &self,
53        sd_alg: SdAlg,
54        pointers: &[impl Borrow<JsonPointer>],
55        rng: impl CryptoRng + RngCore,
56    ) -> Result<(SdJwtPayload, Vec<DecodedDisclosure<'static>>), ConcealError>;
57
58    /// Conceals and signs these JWT claims.
59    #[allow(async_fn_in_trait)]
60    async fn conceal_and_sign(
61        &self,
62        sd_alg: SdAlg,
63        pointers: &[impl Borrow<JsonPointer>],
64        signer: impl JwsSigner,
65    ) -> Result<SdJwtBuf, SignatureError>;
66
67    /// Conceals and signs these JWT claims with the given `rng`.
68    #[allow(async_fn_in_trait)]
69    async fn conceal_and_sign_with(
70        &self,
71        sd_alg: SdAlg,
72        pointers: &[impl Borrow<JsonPointer>],
73        signer: impl JwsSigner,
74        rng: impl CryptoRng + RngCore,
75    ) -> Result<SdJwtBuf, SignatureError>;
76}
77
78impl<T: Serialize> ConcealJwtClaims for JWTClaims<T> {
79    fn conceal(
80        &self,
81        sd_alg: SdAlg,
82        pointers: &[impl Borrow<JsonPointer>],
83    ) -> Result<(SdJwtPayload, Vec<DecodedDisclosure<'static>>), ConcealError> {
84        SdJwtPayload::conceal(self, sd_alg, pointers)
85    }
86
87    fn conceal_with(
88        &self,
89        sd_alg: SdAlg,
90        pointers: &[impl Borrow<JsonPointer>],
91        rng: impl CryptoRng + RngCore,
92    ) -> Result<(SdJwtPayload, Vec<DecodedDisclosure<'static>>), ConcealError> {
93        SdJwtPayload::conceal_with(self, sd_alg, pointers, rng)
94    }
95
96    async fn conceal_and_sign(
97        &self,
98        sd_alg: SdAlg,
99        pointers: &[impl Borrow<JsonPointer>],
100        signer: impl JwsSigner,
101    ) -> Result<SdJwtBuf, SignatureError> {
102        SdJwtBuf::conceal_and_sign(self, sd_alg, pointers, signer).await
103    }
104
105    async fn conceal_and_sign_with(
106        &self,
107        sd_alg: SdAlg,
108        pointers: &[impl Borrow<JsonPointer>],
109        signer: impl JwsSigner,
110        rng: impl CryptoRng + RngCore,
111    ) -> Result<SdJwtBuf, SignatureError> {
112        SdJwtBuf::conceal_and_sign_with(self, sd_alg, pointers, signer, rng).await
113    }
114}
115
116impl SdJwtPayload {
117    /// Conceal a value using the given JSON pointers, returning a SD-JWT
118    /// payload and disclosures.
119    pub fn conceal<T: Serialize>(
120        value: &T,
121        sd_alg: SdAlg,
122        pointers: &[impl Borrow<JsonPointer>],
123    ) -> Result<(Self, Vec<DecodedDisclosure<'static>>), ConcealError> {
124        Self::conceal_with(value, sd_alg, pointers, thread_rng())
125    }
126
127    /// Conceal a value using the given JSON pointers, returning a SD-JWT
128    /// payload and disclosures.
129    pub fn conceal_with<T: Serialize>(
130        value: &T,
131        sd_alg: SdAlg,
132        pointers: &[impl Borrow<JsonPointer>],
133        rng: impl CryptoRng + RngCore,
134    ) -> Result<(Self, Vec<DecodedDisclosure<'static>>), ConcealError> {
135        match serde_json::to_value(value)? {
136            Value::Object(obj) => Self::conceal_claims(obj, rng, sd_alg, pointers),
137            _ => Err(ConcealError::NotAnObject),
138        }
139    }
140
141    /// Conceal a JSON object using the given JSON pointers, returning a SD-JWT
142    /// payload and disclosures.
143    pub fn conceal_claims(
144        mut claims: serde_json::Map<String, Value>,
145        mut rng: impl CryptoRng + RngCore,
146        sd_alg: SdAlg,
147        pointers: &[impl Borrow<JsonPointer>],
148    ) -> Result<(Self, Vec<DecodedDisclosure<'static>>), ConcealError> {
149        let mut disclosures = Vec::with_capacity(pointers.len());
150
151        // We sort the pointers here in order to visit parent pointers *after*
152        // child pointers (e.g. `/foo` after `/foo/bar`). Pointers are sorted
153        // parents-first in `sorted_pointers`, so we iterate over it in reverse.
154        let mut sorted_pointers: Vec<_> = pointers.iter().map(Borrow::borrow).collect();
155        sorted_pointers.sort_unstable();
156
157        for pointer in sorted_pointers.into_iter().rev() {
158            disclosures.push(conceal_object_at(&mut claims, &mut rng, sd_alg, pointer)?);
159        }
160
161        let concealed = Self { sd_alg, claims };
162
163        Ok((concealed, disclosures))
164    }
165}
166
167fn generate_salt(rng: &mut (impl CryptoRng + RngCore)) -> String {
168    // TODO: link to rfc wrt suggested bit size of salt
169    const DEFAULT_SALT_SIZE: usize = 128 / 8;
170    let mut salt_bytes = [0u8; DEFAULT_SALT_SIZE];
171    rng.fill_bytes(&mut salt_bytes);
172    base64::prelude::BASE64_URL_SAFE_NO_PAD.encode(salt_bytes)
173}
174
175fn conceal_at(
176    value: &mut Value,
177    rng: &mut (impl CryptoRng + RngCore),
178    sd_alg: SdAlg,
179    pointer: &JsonPointer,
180) -> Result<DecodedDisclosure<'static>, ConcealError> {
181    match value {
182        Value::Object(object) => conceal_object_at(object, rng, sd_alg, pointer),
183        Value::Array(array) => conceal_array_at(array, rng, sd_alg, pointer),
184        _ => Err(ConcealError::CannotConcealRoot),
185    }
186}
187
188fn conceal_object_at(
189    object: &mut serde_json::Map<String, Value>,
190    rng: &mut (impl CryptoRng + RngCore),
191    sd_alg: SdAlg,
192    pointer: &JsonPointer,
193) -> Result<DecodedDisclosure<'static>, ConcealError> {
194    let (token, rest) = pointer
195        .split_first()
196        .ok_or(ConcealError::CannotConcealRoot)?;
197
198    let key = token.to_decoded();
199
200    if rest.is_empty() {
201        let value = object.remove(&*key).ok_or(ConcealError::NotFound)?;
202
203        let disclosure = DecodedDisclosure::from_parts(
204            generate_salt(rng),
205            DisclosureDescription::ObjectEntry {
206                key: key.into_owned(),
207                value,
208            },
209        );
210
211        add_disclosure(object, sd_alg, &disclosure.encoded)?;
212        Ok(disclosure)
213    } else {
214        let value = object.get_mut(&*key).ok_or(ConcealError::NotFound)?;
215
216        conceal_at(value, rng, sd_alg, rest)
217    }
218}
219
220fn conceal_array_at(
221    array: &mut [Value],
222    rng: &mut (impl CryptoRng + RngCore),
223    sd_alg: SdAlg,
224    pointer: &JsonPointer,
225) -> Result<DecodedDisclosure<'static>, ConcealError> {
226    let (token, rest) = pointer
227        .split_first()
228        .ok_or(ConcealError::CannotConcealRoot)?;
229
230    let i = token.as_array_index().ok_or(ConcealError::NotFound)?;
231
232    let value = array.get_mut(i).ok_or(ConcealError::NotFound)?;
233
234    if rest.is_empty() {
235        let disclosure = DecodedDisclosure::from_parts(
236            generate_salt(rng),
237            DisclosureDescription::ArrayItem(value.take()),
238        );
239
240        *value = new_concealed_array_item(sd_alg, &disclosure.encoded);
241        Ok(disclosure)
242    } else {
243        conceal_at(value, rng, sd_alg, pointer)
244    }
245}
246
247fn new_concealed_array_item(sd_alg: SdAlg, disclosure: &Disclosure) -> Value {
248    let mut object = serde_json::Map::new();
249    object.insert(
250        ARRAY_CLAIM_ITEM_PROPERTY_NAME.into(),
251        sd_alg.hash(disclosure).into(),
252    );
253    Value::Object(object)
254}
255
256fn add_disclosure(
257    object: &mut serde_json::Map<String, Value>,
258    sd_alg: SdAlg,
259    disclosure: &Disclosure,
260) -> Result<(), ConcealError> {
261    let sd = object
262        .entry(SD_CLAIM_NAME.to_owned())
263        .or_insert_with(|| Value::Array(Vec::new()))
264        .as_array_mut()
265        .ok_or(ConcealError::SdEntryNotAnArray)?;
266
267    sd.push(sd_alg.hash(disclosure).into());
268    Ok(())
269}