identity_credential/validator/jwt_credential_validation/
jwt_credential_validator_utils.rsuse std::str::FromStr;
use identity_core::common::Object;
use identity_core::common::OneOrMany;
use identity_core::common::Timestamp;
use identity_core::common::Url;
use identity_core::convert::FromJson;
use identity_did::DID;
use identity_verification::jws::Decoder;
use super::JwtValidationError;
use super::SignerContext;
use crate::credential::Credential;
use crate::credential::CredentialJwtClaims;
use crate::credential::Jwt;
#[cfg(feature = "status-list-2021")]
use crate::revocation::status_list_2021::StatusList2021Credential;
use crate::validator::SubjectHolderRelationship;
#[derive(Debug)]
#[non_exhaustive]
pub struct JwtCredentialValidatorUtils;
type ValidationUnitResult<T = ()> = std::result::Result<T, JwtValidationError>;
impl JwtCredentialValidatorUtils {
pub fn check_structure<T>(credential: &Credential<T>) -> ValidationUnitResult {
credential
.check_structure()
.map_err(JwtValidationError::CredentialStructure)
}
pub fn check_expires_on_or_after<T>(credential: &Credential<T>, timestamp: Timestamp) -> ValidationUnitResult {
let expiration_date: Option<Timestamp> = credential.expiration_date;
(expiration_date.is_none() || expiration_date >= Some(timestamp))
.then_some(())
.ok_or(JwtValidationError::ExpirationDate)
}
pub fn check_issued_on_or_before<T>(credential: &Credential<T>, timestamp: Timestamp) -> ValidationUnitResult {
(credential.issuance_date <= timestamp)
.then_some(())
.ok_or(JwtValidationError::IssuanceDate)
}
pub fn check_subject_holder_relationship<T>(
credential: &Credential<T>,
holder: &Url,
relationship: SubjectHolderRelationship,
) -> ValidationUnitResult {
let url_matches: bool = match &credential.credential_subject {
OneOrMany::One(ref credential_subject) => credential_subject.id.as_ref() == Some(holder),
OneOrMany::Many(subjects) => {
if let [credential_subject] = subjects.as_slice() {
credential_subject.id.as_ref() == Some(holder)
} else {
false
}
}
};
Some(relationship)
.filter(|relationship| match relationship {
SubjectHolderRelationship::AlwaysSubject => url_matches,
SubjectHolderRelationship::SubjectOnNonTransferable => {
url_matches || !credential.non_transferable.unwrap_or(false)
}
SubjectHolderRelationship::Any => true,
})
.map(|_| ())
.ok_or(JwtValidationError::SubjectHolderRelationship)
}
#[cfg(feature = "status-list-2021")]
pub fn check_status_with_status_list_2021<T>(
credential: &Credential<T>,
status_list_credential: &StatusList2021Credential,
status_check: crate::validator::StatusCheck,
) -> ValidationUnitResult {
use crate::revocation::status_list_2021::CredentialStatus;
use crate::revocation::status_list_2021::StatusList2021Entry;
if status_check == crate::validator::StatusCheck::SkipAll {
return Ok(());
}
match &credential.credential_status {
None => Ok(()),
Some(status) => {
let status = StatusList2021Entry::try_from(status)
.map_err(|e| JwtValidationError::InvalidStatus(crate::Error::InvalidStatus(e.to_string())))?;
if Some(status.status_list_credential()) == status_list_credential.id.as_ref()
&& status.purpose() == status_list_credential.purpose()
{
let entry_status = status_list_credential
.entry(status.index())
.map_err(|e| JwtValidationError::InvalidStatus(crate::Error::InvalidStatus(e.to_string())))?;
match entry_status {
CredentialStatus::Revoked => Err(JwtValidationError::Revoked),
CredentialStatus::Suspended => Err(JwtValidationError::Suspended),
CredentialStatus::Valid => Ok(()),
}
} else {
Err(JwtValidationError::InvalidStatus(crate::Error::InvalidStatus(
"The given statusListCredential doesn't match the credential's status".to_owned(),
)))
}
}
}
}
#[cfg(feature = "revocation-bitmap")]
pub fn check_status<DOC: AsRef<identity_document::document::CoreDocument>, T>(
credential: &Credential<T>,
trusted_issuers: &[DOC],
status_check: crate::validator::StatusCheck,
) -> ValidationUnitResult {
use identity_did::CoreDID;
use identity_document::document::CoreDocument;
if status_check == crate::validator::StatusCheck::SkipAll {
return Ok(());
}
match &credential.credential_status {
None => Ok(()),
Some(status) => {
if status.type_ != crate::revocation::RevocationBitmap::TYPE {
if status_check == crate::validator::StatusCheck::SkipUnsupported {
return Ok(());
}
return Err(JwtValidationError::InvalidStatus(crate::Error::InvalidStatus(format!(
"unsupported type '{}'",
status.type_
))));
}
let status: crate::credential::RevocationBitmapStatus =
crate::credential::RevocationBitmapStatus::try_from(status.clone())
.map_err(JwtValidationError::InvalidStatus)?;
let issuer_did: CoreDID = Self::extract_issuer(credential)?;
trusted_issuers
.iter()
.find(|issuer| <CoreDocument>::id(issuer.as_ref()) == &issuer_did)
.ok_or(JwtValidationError::DocumentMismatch(SignerContext::Issuer))
.and_then(|issuer| Self::check_revocation_bitmap_status(issuer, status))
}
}
}
#[cfg(feature = "revocation-bitmap")]
pub fn check_revocation_bitmap_status<DOC: AsRef<identity_document::document::CoreDocument> + ?Sized>(
issuer: &DOC,
status: crate::credential::RevocationBitmapStatus,
) -> ValidationUnitResult {
use crate::revocation::RevocationDocumentExt;
let issuer_service_url: identity_did::DIDUrl = status.id().map_err(JwtValidationError::InvalidStatus)?;
let revocation_bitmap: crate::revocation::RevocationBitmap = issuer
.as_ref()
.resolve_revocation_bitmap(issuer_service_url.into())
.map_err(|_| JwtValidationError::ServiceLookupError)?;
let index: u32 = status.index().map_err(JwtValidationError::InvalidStatus)?;
if revocation_bitmap.is_revoked(index) {
Err(JwtValidationError::Revoked)
} else {
Ok(())
}
}
pub fn extract_issuer<D, T>(credential: &Credential<T>) -> std::result::Result<D, JwtValidationError>
where
D: DID,
<D as FromStr>::Err: std::error::Error + Send + Sync + 'static,
{
D::from_str(credential.issuer.url().as_str()).map_err(|err| JwtValidationError::SignerUrl {
signer_ctx: SignerContext::Issuer,
source: err.into(),
})
}
pub fn extract_issuer_from_jwt<D>(credential: &Jwt) -> std::result::Result<D, JwtValidationError>
where
D: DID,
<D as FromStr>::Err: std::error::Error + Send + Sync + 'static,
{
let validation_item = Decoder::new()
.decode_compact_serialization(credential.as_str().as_bytes(), None)
.map_err(JwtValidationError::JwsDecodingError)?;
let claims: CredentialJwtClaims<'_, Object> = CredentialJwtClaims::from_json_slice(&validation_item.claims())
.map_err(|err| {
JwtValidationError::CredentialStructure(crate::Error::JwtClaimsSetDeserializationError(err.into()))
})?;
D::from_str(claims.iss.url().as_str()).map_err(|err| JwtValidationError::SignerUrl {
signer_ctx: SignerContext::Issuer,
source: err.into(),
})
}
}