use std::collections::BTreeMap;
use serde_json::Value;
use crate::did::{Uri, VerificationRelationship};
use crate::jose::{DefaultVerifier, JwsHeader, JwsVerifier};
use crate::vc::{Jwt, JwtClaims, Party, Presentation};
use super::credential::{check_controller, check_typ, decode};
use super::{
DecodedCredential, FailFast, JwtCredentialValidator, KeyResolver, PresentationValidationError,
PresentationValidationOptions, StatusCheck, SubjectHolderRelationship, ValidationError,
};
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct DecodedPresentation {
pub header: JwsHeader,
pub claims: JwtClaims,
pub presentation: Presentation,
pub credentials: Vec<DecodedCredential>,
}
impl DecodedPresentation {
pub fn holder(&self) -> Option<&Uri> {
self.presentation.holder.as_ref().map(Party::id)
}
}
pub struct JwtPresentationValidator<R, V = DefaultVerifier> {
credentials: JwtCredentialValidator<R, V>,
}
impl<R: KeyResolver, V: JwsVerifier> JwtPresentationValidator<R, V> {
pub fn new(resolver: R, verifier: V) -> Self {
Self {
credentials: JwtCredentialValidator::new(resolver, verifier),
}
}
pub fn from_credential_validator(credentials: JwtCredentialValidator<R, V>) -> Self {
Self { credentials }
}
#[must_use]
pub fn with_status_check(mut self, check: impl StatusCheck + 'static) -> Self {
self.credentials = self.credentials.with_status_check(check);
self
}
pub fn credential_validator(&self) -> &JwtCredentialValidator<R, V> {
&self.credentials
}
pub async fn validate(
&self,
jwt: &Jwt,
options: &PresentationValidationOptions,
) -> Result<DecodedPresentation, PresentationValidationError> {
let mut decoded = self
.validate_presentation(jwt, options)
.await
.map_err(PresentationValidationError::presentation)?;
let holder = decoded.holder().cloned();
let mut errors = BTreeMap::new();
for (index, credential) in decoded
.presentation
.verifiable_credential
.iter()
.enumerate()
{
let result = self
.credentials
.validate(credential, &options.credentials)
.await
.and_then(|validated| {
check_subject_holder(&validated, holder.as_ref(), options.subject_holder)?;
Ok(validated)
});
match result {
Ok(validated) => decoded.credentials.push(validated),
Err(error) => {
errors.insert(index, error);
if options.fail_fast == FailFast::FirstError {
break;
}
}
}
}
if errors.is_empty() {
Ok(decoded)
} else {
Err(PresentationValidationError::credentials(errors))
}
}
async fn validate_presentation(
&self,
jwt: &Jwt,
options: &PresentationValidationOptions,
) -> Result<DecodedPresentation, ValidationError> {
let jws = jwt.decode()?;
check_typ(jws.header(), "JWT")?;
let claims: JwtClaims = serde_json::from_slice(jws.payload())
.map_err(|e| ValidationError::Payload(e.to_string()))?;
let presentation = Presentation::from_jwt_claims(&claims)?;
let kid = self
.credentials
.verify(&jws, VerificationRelationship::Authentication)
.await?;
let holder = presentation
.holder
.as_ref()
.ok_or(crate::vc::CredentialError::MissingClaim("iss"))?;
check_controller(holder, &kid, "holder")?;
options.time.check_claims(&claims)?;
if let Some(expected) = &options.audience
&& !claims.audiences().any(|aud| aud == expected)
{
return Err(ValidationError::AudienceMismatch {
expected: expected.clone(),
found: claims.aud.clone(),
});
}
if let Some(expected) = &options.nonce {
let found = claims.additional.get("nonce").and_then(Value::as_str);
if found != Some(expected.as_str()) {
return Err(ValidationError::NonceMismatch {
expected: expected.clone(),
found: found.map(str::to_owned),
});
}
}
Ok(DecodedPresentation {
header: jws.header().clone(),
claims,
presentation,
credentials: Vec::new(),
})
}
}
impl<R: core::fmt::Debug, V: core::fmt::Debug> core::fmt::Debug for JwtPresentationValidator<R, V> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("JwtPresentationValidator")
.field("credentials", &self.credentials)
.finish()
}
}
pub fn decode_presentation(jwt: &Jwt) -> Result<DecodedPresentation, ValidationError> {
let jws = jwt.decode()?;
check_typ(jws.header(), "JWT")?;
let claims: JwtClaims = serde_json::from_slice(jws.payload())
.map_err(|e| ValidationError::Payload(e.to_string()))?;
let presentation = Presentation::from_jwt_claims(&claims)?;
let credentials = presentation
.verifiable_credential
.iter()
.map(|credential| decode(&credential.decode()?))
.collect::<Result<_, _>>()?;
Ok(DecodedPresentation {
header: jws.header().clone(),
claims,
presentation,
credentials,
})
}
fn check_subject_holder(
credential: &DecodedCredential,
holder: Option<&Uri>,
relationship: SubjectHolderRelationship,
) -> Result<(), ValidationError> {
let required = match relationship {
SubjectHolderRelationship::AlwaysSubject => true,
SubjectHolderRelationship::SubjectOnNonTransferable => credential
.credential
.properties
.get("nonTransferable")
.is_some_and(|value| value == &Value::Bool(true)),
SubjectHolderRelationship::Any => false,
};
if !required {
return Ok(());
}
let holder = holder.expect("a validated presentation has a holder");
for subject in credential.credential.credential_subject.iter() {
if subject.id.as_ref() != Some(holder) {
return Err(ValidationError::SubjectHolderMismatch {
subject: subject.id.clone(),
holder: holder.clone(),
});
}
}
Ok(())
}