identity_credential/validator/jpt_credential_validation/
jpt_credential_validator.rs1use identity_core::convert::FromJson;
5use identity_core::convert::ToJson;
6use identity_did::CoreDID;
7use identity_did::DIDUrl;
8use identity_document::document::CoreDocument;
9use identity_document::verifiable::JwpVerificationOptions;
10use jsonprooftoken::encoding::SerializationType;
11use jsonprooftoken::jpt::claims::JptClaims;
12use jsonprooftoken::jwk::key::Jwk as JwkExt;
13use jsonprooftoken::jwp::issued::JwpIssuedDecoder;
14
15use super::DecodedJptCredential;
16use crate::credential::Credential;
17use crate::credential::CredentialJwtClaims;
18use crate::credential::Jpt;
19use crate::validator::jwt_credential_validation::SignerContext;
20use crate::validator::CompoundCredentialValidationError;
21use crate::validator::FailFast;
22use crate::validator::JptCredentialValidationOptions;
23use crate::validator::JwtCredentialValidatorUtils;
24use crate::validator::JwtValidationError;
25
26#[non_exhaustive]
28pub struct JptCredentialValidator;
29
30impl JptCredentialValidator {
31 pub fn validate<DOC, T>(
40 credential_jpt: &Jpt,
41 issuer: &DOC,
42 options: &JptCredentialValidationOptions,
43 fail_fast: FailFast,
44 ) -> Result<DecodedJptCredential<T>, CompoundCredentialValidationError>
45 where
46 T: ToOwned<Owned = T> + serde::Serialize + serde::de::DeserializeOwned,
47 DOC: AsRef<CoreDocument>,
48 {
49 let credential_token =
51 Self::verify_proof(credential_jpt, issuer, &options.verification_options).map_err(|err| {
52 CompoundCredentialValidationError {
53 validation_errors: [err].into(),
54 }
55 })?;
56
57 let credential: &Credential<T> = &credential_token.credential;
58
59 Self::validate_credential::<T>(credential, options, fail_fast)?;
60
61 Ok(credential_token)
62 }
63
64 pub(crate) fn validate_credential<T>(
65 credential: &Credential<T>,
66 options: &JptCredentialValidationOptions,
67 fail_fast: FailFast,
68 ) -> Result<(), CompoundCredentialValidationError>
69 where
70 T: ToOwned<Owned = T> + serde::Serialize + serde::de::DeserializeOwned,
71 {
72 let expiry_date_validation = std::iter::once_with(|| {
74 JwtCredentialValidatorUtils::check_expires_on_or_after(
75 credential,
76 options.earliest_expiry_date.unwrap_or_default(),
77 )
78 });
79
80 let issuance_date_validation = std::iter::once_with(|| {
81 JwtCredentialValidatorUtils::check_issued_on_or_before(
82 credential,
83 options.latest_issuance_date.unwrap_or_default(),
84 )
85 });
86
87 let structure_validation = std::iter::once_with(|| JwtCredentialValidatorUtils::check_structure(credential));
88
89 let subject_holder_validation = std::iter::once_with(|| {
90 options
91 .subject_holder_relationship
92 .as_ref()
93 .map(|(holder, relationship)| {
94 JwtCredentialValidatorUtils::check_subject_holder_relationship(credential, holder, *relationship)
95 })
96 .unwrap_or(Ok(()))
97 });
98
99 let validation_units_iter = issuance_date_validation
100 .chain(expiry_date_validation)
101 .chain(structure_validation)
102 .chain(subject_holder_validation);
103
104 let validation_units_error_iter = validation_units_iter.filter_map(|result| result.err());
105 let validation_errors: Vec<JwtValidationError> = match fail_fast {
106 FailFast::FirstError => validation_units_error_iter.take(1).collect(),
107 FailFast::AllErrors => validation_units_error_iter.collect(),
108 };
109
110 if validation_errors.is_empty() {
111 Ok(())
112 } else {
113 Err(CompoundCredentialValidationError { validation_errors })
114 }
115 }
116
117 fn verify_proof<DOC, T>(
119 credential: &Jpt,
120 issuer: &DOC,
121 options: &JwpVerificationOptions,
122 ) -> Result<DecodedJptCredential<T>, JwtValidationError>
123 where
124 T: ToOwned<Owned = T> + serde::Serialize + serde::de::DeserializeOwned,
125 DOC: AsRef<CoreDocument>,
126 {
127 let decoded = JwpIssuedDecoder::decode(credential.as_str(), SerializationType::COMPACT)
128 .map_err(JwtValidationError::JwpDecodingError)?;
129
130 let method_id: DIDUrl = match &options.method_id {
133 Some(method_id) => method_id.clone(),
134 None => {
135 let kid: &str = decoded
136 .get_header()
137 .kid()
138 .ok_or(JwtValidationError::MethodDataLookupError {
139 source: None,
140 message: "could not extract kid from protected header",
141 signer_ctx: SignerContext::Issuer,
142 })?;
143
144 DIDUrl::parse(kid).map_err(|err| JwtValidationError::MethodDataLookupError {
146 source: Some(err.into()),
147 message: "could not parse kid as a DID Url",
148 signer_ctx: SignerContext::Issuer,
149 })?
150 }
151 };
152
153 let issuer: &CoreDocument = issuer.as_ref();
155
156 if issuer.id() != method_id.did() {
157 return Err(JwtValidationError::DocumentMismatch(SignerContext::Issuer));
158 }
159
160 let public_key: JwkExt = issuer
162 .resolve_method(&method_id, options.method_scope)
163 .and_then(|method| method.data().public_key_jwk())
164 .and_then(|k| k.try_into().ok()) .ok_or_else(|| JwtValidationError::MethodDataLookupError {
166 source: None,
167 message: "could not extract JWK from a method identified by kid",
168 signer_ctx: SignerContext::Issuer,
169 })?;
170
171 let credential_token = Self::verify_decoded_jwp(decoded, &public_key)?;
172
173 let issuer_id: CoreDID = JwtCredentialValidatorUtils::extract_issuer(&credential_token.credential)?;
176 if &issuer_id != method_id.did() {
177 return Err(JwtValidationError::IdentifierMismatch {
178 signer_ctx: SignerContext::Issuer,
179 });
180 };
181 Ok(credential_token)
182 }
183
184 fn verify_decoded_jwp<T>(
186 decoded: JwpIssuedDecoder,
187 public_key: &JwkExt,
188 ) -> Result<DecodedJptCredential<T>, JwtValidationError>
189 where
190 T: ToOwned<Owned = T> + serde::Serialize + serde::de::DeserializeOwned,
191 {
192 let decoded_jwp = decoded
194 .verify(public_key)
195 .map_err(JwtValidationError::JwpProofVerificationError)?;
196
197 let claims = decoded_jwp.get_claims().ok_or("Claims not present").map_err(|err| {
198 JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into()))
199 })?;
200 let payloads = decoded_jwp.get_payloads();
201 let jpt_claims = JptClaims::from_claims_and_payloads(claims, payloads);
202 let jpt_claims_json = jpt_claims.to_json_vec().map_err(|err| {
203 JwtValidationError::CredentialStructure(crate::Error::JptClaimsSetDeserializationError(err.into()))
204 })?;
205
206 let credential_claims: CredentialJwtClaims<'_, T> = CredentialJwtClaims::from_json_slice(&jpt_claims_json)
208 .map_err(|err| {
209 JwtValidationError::CredentialStructure(crate::Error::JwtClaimsSetDeserializationError(err.into()))
210 })?;
211
212 let custom_claims = credential_claims.custom.clone();
213
214 let credential: Credential<T> = credential_claims
216 .try_into_credential()
217 .map_err(JwtValidationError::CredentialStructure)?;
218
219 Ok(DecodedJptCredential {
220 credential,
221 custom_claims,
222 decoded_jwp,
223 })
224 }
225}