identity_credential/validator/jpt_presentation_validation/
jpt_presentation_validator.rs1use std::str::FromStr;
5
6use identity_core::common::Url;
7use identity_core::convert::FromJson;
8use identity_core::convert::ToJson;
9use identity_did::CoreDID;
10use identity_did::DIDUrl;
11use identity_document::document::CoreDocument;
12use jsonprooftoken::encoding::SerializationType;
13use jsonprooftoken::jpt::claims::JptClaims;
14use jsonprooftoken::jwk::key::Jwk as JwkExt;
15use jsonprooftoken::jwp::presented::JwpPresentedDecoder;
16
17use crate::credential::Credential;
18use crate::credential::CredentialJwtClaims;
19use crate::credential::Jpt;
20use crate::validator::CompoundCredentialValidationError;
21use crate::validator::FailFast;
22use crate::validator::JwtCredentialValidatorUtils;
23use crate::validator::JwtValidationError;
24use crate::validator::SignerContext;
25
26use super::DecodedJptPresentation;
27use super::JptPresentationValidationOptions;
28
29#[non_exhaustive]
31pub struct JptPresentationValidator;
32
33impl JptPresentationValidator {
34 pub fn validate<DOC, T>(
43 presentation_jpt: &Jpt,
44 issuer: &DOC,
45 options: &JptPresentationValidationOptions,
46 fail_fast: FailFast,
47 ) -> Result<DecodedJptPresentation<T>, CompoundCredentialValidationError>
48 where
49 T: ToOwned<Owned = T> + serde::Serialize + serde::de::DeserializeOwned,
50 DOC: AsRef<CoreDocument>,
51 {
52 let presented_credential_token =
55 Self::verify_proof(presentation_jpt, issuer, options).map_err(|err| CompoundCredentialValidationError {
56 validation_errors: [err].into(),
57 })?;
58
59 let credential: &Credential<T> = &presented_credential_token.credential;
60
61 Self::validate_presented_credential::<T>(credential, fail_fast)?;
62
63 Ok(presented_credential_token)
64 }
65
66 pub(crate) fn validate_presented_credential<T>(
67 credential: &Credential<T>,
68 fail_fast: FailFast,
69 ) -> Result<(), CompoundCredentialValidationError>
70 where
71 T: ToOwned<Owned = T> + serde::Serialize + serde::de::DeserializeOwned,
72 {
73 let structure_validation = std::iter::once_with(|| JwtCredentialValidatorUtils::check_structure(credential));
74
75 let validation_units_iter = structure_validation;
76
77 let validation_units_error_iter = validation_units_iter.filter_map(|result| result.err());
78 let validation_errors: Vec<JwtValidationError> = match fail_fast {
79 FailFast::FirstError => validation_units_error_iter.take(1).collect(),
80 FailFast::AllErrors => validation_units_error_iter.collect(),
81 };
82
83 if validation_errors.is_empty() {
84 Ok(())
85 } else {
86 Err(CompoundCredentialValidationError { validation_errors })
87 }
88 }
89
90 fn verify_proof<DOC, T>(
92 presentation_jpt: &Jpt,
93 issuer: &DOC,
94 options: &JptPresentationValidationOptions,
95 ) -> Result<DecodedJptPresentation<T>, JwtValidationError>
96 where
97 T: ToOwned<Owned = T> + serde::Serialize + serde::de::DeserializeOwned,
98 DOC: AsRef<CoreDocument>,
99 {
100 let decoded: JwpPresentedDecoder =
101 JwpPresentedDecoder::decode(presentation_jpt.as_str(), SerializationType::COMPACT)
102 .map_err(JwtValidationError::JwpDecodingError)?;
103
104 let nonce: Option<&String> = options.nonce.as_ref();
105 if decoded.get_presentation_header().nonce() != nonce {
107 return Err(JwtValidationError::JwsDecodingError(
108 identity_verification::jose::error::Error::InvalidParam("invalid nonce value"),
109 ));
110 }
111
112 let method_id: DIDUrl = match &options.verification_options.method_id {
115 Some(method_id) => method_id.clone(),
116 None => {
117 let kid: &str = decoded
118 .get_issuer_header()
119 .kid()
120 .ok_or(JwtValidationError::MethodDataLookupError {
121 source: None,
122 message: "could not extract kid from protected header",
123 signer_ctx: SignerContext::Issuer,
124 })?;
125
126 DIDUrl::parse(kid).map_err(|err| JwtValidationError::MethodDataLookupError {
128 source: Some(err.into()),
129 message: "could not parse kid as a DID Url",
130 signer_ctx: SignerContext::Issuer,
131 })?
132 }
133 };
134
135 let issuer: &CoreDocument = issuer.as_ref();
137
138 if issuer.id() != method_id.did() {
139 return Err(JwtValidationError::DocumentMismatch(SignerContext::Issuer));
140 }
141
142 let public_key: JwkExt = issuer
144 .resolve_method(&method_id, options.verification_options.method_scope)
145 .and_then(|method| method.data().public_key_jwk())
146 .and_then(|k| k.try_into().ok()) .ok_or_else(|| JwtValidationError::MethodDataLookupError {
148 source: None,
149 message: "could not extract JWK from a method identified by kid",
150 signer_ctx: SignerContext::Issuer,
151 })?;
152
153 let credential_token = Self::verify_decoded_jwp(decoded, &public_key)?;
154
155 let issuer_id: CoreDID = JwtCredentialValidatorUtils::extract_issuer(&credential_token.credential)?;
158 if &issuer_id != method_id.did() {
159 return Err(JwtValidationError::IdentifierMismatch {
160 signer_ctx: SignerContext::Issuer,
161 });
162 };
163 Ok(credential_token)
164 }
165
166 fn verify_decoded_jwp<T>(
168 decoded: JwpPresentedDecoder,
169 public_key: &JwkExt,
170 ) -> Result<DecodedJptPresentation<T>, JwtValidationError>
171 where
172 T: ToOwned<Owned = T> + serde::Serialize + serde::de::DeserializeOwned,
173 {
174 let decoded_jwp = decoded
176 .verify(public_key)
177 .map_err(JwtValidationError::JwpProofVerificationError)?;
178
179 let claims = decoded_jwp.get_claims().ok_or("Claims not present").map_err(|err| {
180 JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into()))
181 })?;
182 let payloads = decoded_jwp.get_payloads();
183 let mut jpt_claims = JptClaims::from_claims_and_payloads(claims, payloads);
184 jpt_claims.nbf.map_or_else(
186 || {
187 jpt_claims.set_nbf(0);
188 },
189 |_| (),
190 );
191
192 let jpt_claims_json = jpt_claims.to_json_vec().map_err(|err| {
193 JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into()))
194 })?;
195
196 let credential_claims: CredentialJwtClaims<'_, T> = CredentialJwtClaims::from_json_slice(&jpt_claims_json)
198 .map_err(|err| {
199 JwtValidationError::CredentialStructure(crate::Error::JwtClaimsSetDeserializationError(err.into()))
200 })?;
201
202 let custom_claims = credential_claims.custom.clone();
203
204 let credential: Credential<T> = credential_claims
206 .try_into_credential()
207 .map_err(JwtValidationError::CredentialStructure)?;
208
209 let aud: Option<Url> = decoded_jwp.get_presentation_protected_header().aud().and_then(|aud| {
210 Url::from_str(aud)
211 .map_err(|_| {
212 JwtValidationError::JwsDecodingError(identity_verification::jose::error::Error::InvalidParam(
213 "invalid audience value",
214 ))
215 })
216 .ok()
217 });
218
219 Ok(DecodedJptPresentation {
220 credential,
221 aud,
222 custom_claims,
223 decoded_jwp,
224 })
225 }
226}