identity_credential/validator/sd_jwt/
validator.rs1use crate::credential::CredentialJwtClaims;
5use crate::validator::CompoundCredentialValidationError;
6use crate::validator::DecodedJwtCredential;
7use crate::validator::FailFast;
8use crate::validator::JwtCredentialValidationOptions;
9use crate::validator::JwtCredentialValidator;
10use crate::validator::JwtCredentialValidatorUtils;
11use crate::validator::JwtValidationError;
12use crate::validator::SignerContext;
13use identity_core::common::Timestamp;
14use identity_core::convert::FromJson;
15use identity_did::CoreDID;
16use identity_did::DIDUrl;
17use identity_document::document::CoreDocument;
18use identity_document::verifiable::JwsVerificationOptions;
19use identity_verification::jwk::Jwk;
20use identity_verification::jws::DecodedJws;
21use identity_verification::jws::Decoder;
22use identity_verification::jws::JwsValidationItem;
23use identity_verification::jws::JwsVerifier;
24use itertools::Itertools;
25use sd_jwt_payload::KeyBindingJwtClaims;
26use sd_jwt_payload::SdJwt;
27use sd_jwt_payload::SdObjectDecoder;
28use serde_json::Value;
29
30use super::KeyBindingJWTValidationOptions;
31use super::KeyBindingJwtError;
32
33#[non_exhaustive]
35pub struct SdJwtCredentialValidator<V: JwsVerifier>(V, SdObjectDecoder);
36
37impl<V: JwsVerifier> SdJwtCredentialValidator<V> {
38 pub fn with_signature_verifier(signature_verifier: V, sd_decoder: SdObjectDecoder) -> Self {
41 Self(signature_verifier, sd_decoder)
42 }
43
44 pub fn validate_credential<DOC, T>(
72 &self,
73 sd_jwt: &SdJwt,
74 issuer: &DOC,
75 options: &JwtCredentialValidationOptions,
76 fail_fast: FailFast,
77 ) -> Result<DecodedJwtCredential<T>, CompoundCredentialValidationError>
78 where
79 T: ToOwned<Owned = T> + serde::Serialize + serde::de::DeserializeOwned,
80 DOC: AsRef<CoreDocument>,
81 {
82 let issuers = std::slice::from_ref(issuer.as_ref());
83 let credential = self
84 .verify_signature(sd_jwt, issuers, &options.verification_options)
85 .map_err(|err| CompoundCredentialValidationError {
86 validation_errors: [err].into(),
87 })?;
88
89 JwtCredentialValidator::<V>::validate_decoded_credential(credential, issuers, options, fail_fast)
90 }
91
92 pub fn verify_signature<DOC, T>(
109 &self,
110 credential: &SdJwt,
111 trusted_issuers: &[DOC],
112 options: &JwsVerificationOptions,
113 ) -> Result<DecodedJwtCredential<T>, JwtValidationError>
114 where
115 T: ToOwned<Owned = T> + serde::Serialize + serde::de::DeserializeOwned,
116 DOC: AsRef<CoreDocument>,
117 {
118 let SdJwt { jwt, disclosures, .. } = credential;
119 let signature = JwtCredentialValidator::<V>::decode(jwt.as_str())?;
120 let (public_key, method_id) = JwtCredentialValidator::<V>::parse_jwk(&signature, trusted_issuers, options)?;
121
122 let DecodedJws { protected, claims, .. } =
123 JwtCredentialValidator::<V>::verify_signature_raw(signature, public_key, &self.0)?;
124
125 let value: Value = serde_json::from_slice(&claims).map_err(|err| {
126 JwtValidationError::CredentialStructure(crate::Error::JwtClaimsSetDeserializationError(err.into()))
127 })?;
128 let obj = value.as_object().ok_or(JwtValidationError::JwsDecodingError(
129 identity_verification::jose::error::Error::InvalidClaim("sd-jwt claims could not be deserialized"),
130 ))?;
131 let decoded: String = Value::Object(self.1.decode(obj, disclosures).map_err(|e| {
132 let err_str = format!("sd-jwt claims decoding failed, {}", e);
133 let err: &'static str = Box::leak(err_str.into_boxed_str());
134 JwtValidationError::JwsDecodingError(identity_verification::jose::error::Error::InvalidClaim(err))
135 })?)
136 .to_string();
137
138 let claims = CredentialJwtClaims::from_json(&decoded).map_err(|err| {
139 JwtValidationError::CredentialStructure(crate::Error::JwtClaimsSetDeserializationError(err.into()))
140 })?;
141 let custom_claims = claims.custom.clone();
142 let credential = claims
143 .try_into_credential()
144 .map_err(JwtValidationError::CredentialStructure)?;
145
146 let decoded_credential = DecodedJwtCredential {
147 credential,
148 header: Box::new(protected),
149 custom_claims,
150 };
151
152 let issuer_id = JwtCredentialValidatorUtils::extract_issuer::<CoreDID, _>(&decoded_credential.credential)?;
155 if &issuer_id != method_id.did() {
156 return Err(JwtValidationError::IdentifierMismatch {
157 signer_ctx: SignerContext::Issuer,
158 });
159 };
160
161 Ok(decoded_credential)
162 }
163
164 pub fn validate_key_binding_jwt<DOC>(
171 &self,
172 sd_jwt: &SdJwt,
173 holder: &DOC,
174 options: &KeyBindingJWTValidationOptions,
175 ) -> Result<KeyBindingJwtClaims, KeyBindingJwtError>
176 where
177 DOC: AsRef<CoreDocument>,
178 {
179 let kb_jwt = if let Some(kb_jwt) = &sd_jwt.key_binding_jwt {
181 kb_jwt.clone()
182 } else {
183 return Err(KeyBindingJwtError::MissingKeyBindingJwt);
184 };
185
186 let jws_decoder = Decoder::new();
188 let decoded: JwsValidationItem<'_> = jws_decoder
189 .decode_compact_serialization(sd_jwt.jwt.as_bytes(), None)
190 .map_err(|err| KeyBindingJwtError::JwtValidationError(JwtValidationError::JwsDecodingError(err)))?;
191 let sd_jwt_claims: Value = serde_json::from_slice(decoded.claims())
192 .map_err(|_| KeyBindingJwtError::DeserializationError("failed to deserialize sd-jwt claims".to_string()))?;
193 let sd_jwt_claims_object = sd_jwt_claims
194 .as_object()
195 .ok_or(KeyBindingJwtError::DeserializationError(
196 "failed to deserialize sd-jwt claims".to_string(),
197 ))?;
198 let hasher = self.1.determine_hasher(sd_jwt_claims_object)?;
199 let disclosures = sd_jwt.disclosures.iter().join("~");
200 let hash_payload = format!("{}~{}~", sd_jwt.jwt, disclosures);
201 let digest = hasher.encoded_digest(&hash_payload);
202
203 let kb_decoded: JwsValidationItem<'_> = jws_decoder
205 .decode_compact_serialization(kb_jwt.as_bytes(), None)
206 .map_err(JwtValidationError::JwsDecodingError)?;
207 let typ: &str = kb_decoded
208 .protected_header()
209 .ok_or(KeyBindingJwtError::InvalidHeaderTypValue)?
210 .typ()
211 .ok_or(KeyBindingJwtError::InvalidHeaderTypValue)?;
212
213 if typ != KeyBindingJwtClaims::KB_JWT_HEADER_TYP {
214 return Err(KeyBindingJwtError::InvalidHeaderTypValue);
215 }
216 let method_id: DIDUrl = match &options.jws_options.method_id {
217 Some(method_id) => method_id.clone(),
218 None => {
219 let kid: &str = kb_decoded.protected_header().and_then(|header| header.kid()).ok_or(
220 JwtValidationError::MethodDataLookupError {
221 source: None,
222 message: "could not extract kid from protected header",
223 signer_ctx: SignerContext::Holder,
224 },
225 )?;
226
227 DIDUrl::parse(kid).map_err(|err| JwtValidationError::MethodDataLookupError {
229 source: Some(err.into()),
230 message: "could not parse kid as a DID Url",
231 signer_ctx: SignerContext::Issuer,
232 })?
233 }
234 };
235
236 let public_key: &Jwk = holder
238 .as_ref()
239 .resolve_method(&method_id, options.jws_options.method_scope)
240 .and_then(|method| method.data().public_key_jwk())
241 .ok_or_else(|| JwtValidationError::MethodDataLookupError {
242 source: None,
243 message: "could not extract JWK from a method identified by kid",
244 signer_ctx: SignerContext::Holder,
245 })?;
246 let decoded: JwsValidationItem<'_> = jws_decoder
247 .decode_compact_serialization(kb_jwt.as_bytes(), None)
248 .map_err(|err| KeyBindingJwtError::JwtValidationError(JwtValidationError::JwsDecodingError(err)))?;
249 let decoded_kb_jws = decoded.verify(&self.0, public_key).unwrap();
250
251 let kb_jwt_claims: KeyBindingJwtClaims = serde_json::from_slice(&decoded_kb_jws.claims)
252 .map_err(|_| KeyBindingJwtError::DeserializationError("failed to deserialize kb-jwt claims".into()))?;
253
254 if kb_jwt_claims.sd_hash != digest {
256 return Err(KeyBindingJwtError::InvalidDigest);
257 }
258
259 if let Some(nonce) = &options.nonce {
260 if *nonce != kb_jwt_claims.nonce {
261 return Err(KeyBindingJwtError::InvalidNonce);
262 }
263 }
264
265 if let Some(aud) = &options.aud {
266 if *aud != kb_jwt_claims.aud {
267 return Err(KeyBindingJwtError::AudienceMismatch);
268 }
269 }
270
271 let issuance_date = Timestamp::from_unix(kb_jwt_claims.iat)
272 .map_err(|_| KeyBindingJwtError::IssuanceDate("deserialization of `iat` failed".to_string()))?;
273
274 if let Some(earliest_issuance_date) = options.earliest_issuance_date {
275 if issuance_date < earliest_issuance_date {
276 return Err(KeyBindingJwtError::IssuanceDate(
277 "value is earlier than `earliest_issuance_date`".to_string(),
278 ));
279 }
280 }
281
282 if let Some(latest_issuance_date) = options.latest_issuance_date {
283 if issuance_date > latest_issuance_date {
284 return Err(KeyBindingJwtError::IssuanceDate(
285 "value is later than `latest_issuance_date`".to_string(),
286 ));
287 }
288 } else if issuance_date > Timestamp::now_utc() {
289 return Err(KeyBindingJwtError::IssuanceDate("value is in the future".to_string()));
290 }
291
292 Ok(kb_jwt_claims)
293 }
294}