Skip to main content

identity_kit/sd_jwt_vc/
disclosure.rs

1//! Disclosures (RFC 9901 §4.2).
2
3use core::fmt;
4use core::str::FromStr;
5
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7use serde_json::Value;
8
9use super::{HashAlgorithm, SdJwtError};
10use crate::jose::base64url;
11
12/// A disclosure: the salt, the claim name (for an object property; an array element has
13/// none) and the claim value, base64url-encoded as a JSON array.
14///
15/// A disclosure keeps the encoded string it was parsed from, since the digest is taken over
16/// that string and any JSON serialization of the array is as valid as another.
17///
18/// # Examples
19///
20/// ```
21/// use identity_kit::sd_jwt_vc::{Disclosure, HashAlgorithm};
22/// use serde_json::json;
23///
24/// // RFC 9901 §4.2.1.
25/// let disclosure: Disclosure =
26///     "WyJfMjZiYzRMVC1hYzZxMktJNmNCVzVlcyIsICJmYW1pbHlfbmFtZSIsICJNw7ZiaXVzIl0".parse()?;
27/// assert_eq!(disclosure.salt(), "_26bc4LT-ac6q2KI6cBW5es");
28/// assert_eq!(disclosure.name(), Some("family_name"));
29/// assert_eq!(disclosure.value(), &json!("Möbius"));
30/// assert_eq!(
31///     disclosure.digest(HashAlgorithm::Sha256),
32///     "X9yH0Ajrdm1Oij4tWso9UzzKJvPoDxwmuEcO3XAdRC0"
33/// );
34///
35/// // RFC 9901 §4.2.2: an array element.
36/// let element: Disclosure = "WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgIkZSIl0".parse()?;
37/// assert_eq!(element.name(), None);
38/// assert_eq!(element.value(), &json!("FR"));
39/// # Ok::<(), identity_kit::sd_jwt_vc::SdJwtError>(())
40/// ```
41#[derive(Clone, PartialEq, Eq)]
42pub struct Disclosure {
43    encoded: String,
44    salt: String,
45    name: Option<String>,
46    value: Value,
47}
48
49impl Disclosure {
50    /// A disclosure for the object property `name` with `value`, encoded as compact JSON.
51    pub fn property(salt: impl Into<String>, name: impl Into<String>, value: Value) -> Self {
52        let salt = salt.into();
53        let name = name.into();
54        let array = Value::Array(vec![
55            Value::String(salt.clone()),
56            Value::String(name.clone()),
57            value.clone(),
58        ]);
59        Self {
60            encoded: encode(&array),
61            salt,
62            name: Some(name),
63            value,
64        }
65    }
66
67    /// A disclosure for the array element `value`, encoded as compact JSON.
68    pub fn element(salt: impl Into<String>, value: Value) -> Self {
69        let salt = salt.into();
70        let array = Value::Array(vec![Value::String(salt.clone()), value.clone()]);
71        Self {
72            encoded: encode(&array),
73            salt,
74            name: None,
75            value,
76        }
77    }
78
79    /// Decodes the base64url string `encoded`.
80    ///
81    /// # Errors
82    ///
83    /// [`SdJwtError::InvalidDisclosure`] when the string is not base64url of a JSON array of
84    /// two elements (a string salt and a value) or three (a string salt, a string name and a
85    /// value).
86    pub fn parse(encoded: &str) -> Result<Self, SdJwtError> {
87        let invalid = |reason: &str| SdJwtError::InvalidDisclosure {
88            disclosure: encoded.to_owned(),
89            reason: reason.to_owned(),
90        };
91        let bytes = base64url::decode(encoded).map_err(|_| invalid("not base64url"))?;
92        let array: Vec<Value> =
93            serde_json::from_slice(&bytes).map_err(|_| invalid("not a JSON array"))?;
94        let mut array = array.into_iter();
95        let salt = match array.next() {
96            Some(Value::String(salt)) => salt,
97            _ => return Err(invalid("the salt is not a string")),
98        };
99        let (name, value) = match (array.next(), array.next(), array.next()) {
100            (Some(value), None, None) => (None, value),
101            (Some(Value::String(name)), Some(value), None) => (Some(name), value),
102            (Some(_), Some(_), None) => return Err(invalid("the claim name is not a string")),
103            _ => return Err(invalid("not an array of two or three elements")),
104        };
105        Ok(Self {
106            encoded: encoded.to_owned(),
107            salt,
108            name,
109            value,
110        })
111    }
112
113    /// The encoded disclosure, as it travels in an SD-JWT.
114    pub fn as_str(&self) -> &str {
115        &self.encoded
116    }
117
118    /// The salt.
119    pub fn salt(&self) -> &str {
120        &self.salt
121    }
122
123    /// The claim name; `None` for an array element.
124    pub fn name(&self) -> Option<&str> {
125        self.name.as_deref()
126    }
127
128    /// The claim value.
129    pub fn value(&self) -> &Value {
130        &self.value
131    }
132
133    /// Whether this discloses an array element rather than an object property.
134    pub fn is_element(&self) -> bool {
135        self.name.is_none()
136    }
137
138    /// The digest of the encoded disclosure under `alg` (RFC 9901 §4.2.3).
139    pub fn digest(&self, alg: HashAlgorithm) -> String {
140        alg.digest(self.encoded.as_bytes())
141    }
142}
143
144/// `array` as compact JSON, base64url-encoded.
145fn encode(array: &Value) -> String {
146    let json = serde_json::to_vec(array).expect("a JSON value serializes");
147    base64url::encode(&json)
148}
149
150impl fmt::Debug for Disclosure {
151    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152        f.debug_struct("Disclosure")
153            .field("salt", &self.salt)
154            .field("name", &self.name)
155            .field("value", &self.value)
156            .finish()
157    }
158}
159
160impl fmt::Display for Disclosure {
161    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162        f.write_str(&self.encoded)
163    }
164}
165
166impl AsRef<str> for Disclosure {
167    fn as_ref(&self) -> &str {
168        &self.encoded
169    }
170}
171
172impl FromStr for Disclosure {
173    type Err = SdJwtError;
174
175    fn from_str(s: &str) -> Result<Self, Self::Err> {
176        Self::parse(s)
177    }
178}
179
180impl Serialize for Disclosure {
181    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
182        serializer.serialize_str(&self.encoded)
183    }
184}
185
186impl<'de> Deserialize<'de> for Disclosure {
187    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
188        let encoded = String::deserialize(deserializer)?;
189        Self::parse(&encoded).map_err(serde::de::Error::custom)
190    }
191}