Skip to content

Commit

Permalink
validation of StatusList2020-revoked credentials
Browse files Browse the repository at this point in the history
  • Loading branch information
UMR1352 committed Mar 18, 2024
1 parent d6e0e85 commit 3352f96
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 10 deletions.
42 changes: 35 additions & 7 deletions bindings/grpc/src/services/credential/validation.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use identity_eddsa_verifier::EdDSAJwsVerifier;
use identity_iota::core::FromJson;
use identity_iota::core::Object;
use identity_iota::core::ToJson;
use identity_iota::credential::status_list_2021::StatusList2021Credential;
use identity_iota::credential::FailFast;
use identity_iota::credential::Jwt;
use identity_iota::credential::JwtCredentialValidationOptions;
use identity_iota::credential::JwtCredentialValidator;
use identity_iota::credential::JwtCredentialValidatorUtils;
use identity_iota::credential::JwtValidationError;
use identity_iota::credential::StatusCheck;
use identity_iota::iota::IotaDID;
use identity_iota::resolver;
use identity_iota::resolver::Resolver;
Expand All @@ -16,6 +19,7 @@ use _credentials::vc_validation_server::VcValidation;
use _credentials::vc_validation_server::VcValidationServer;
use _credentials::VcValidationRequest;
use _credentials::VcValidationResponse;
use tonic::Code;
use tonic::Request;
use tonic::Response;
use tonic::Status;
Expand All @@ -30,15 +34,24 @@ pub enum VcValidationError {
JwtValidationError(#[from] JwtValidationError),
#[error("DID resolution error")]
DidResolutionError(#[source] resolver::Error),
#[error("Provided an invalid StatusList2021Credential")]
InvalidStatusList2020Credential(#[source] identity_iota::core::Error),
#[error("The provided credential has been revoked")]
RevokedCredential,
#[error("The provided credential has expired")]
ExpiredCredential,
#[error("The provided credential has been suspended")]
SuspendedCredential,
}

impl From<VcValidationError> for Status {
fn from(error: VcValidationError) -> Self {
Status::unknown(error.to_string())
let code = match &error {
VcValidationError::InvalidStatusList2020Credential(_) => Code::InvalidArgument,
_ => Code::Internal,
};

Status::new(code, error.to_string())
}
}

Expand Down Expand Up @@ -77,20 +90,35 @@ impl VcValidation for VcValidator {
.await
.map_err(VcValidationError::DidResolutionError)?;

let mut validation_option = JwtCredentialValidationOptions::default();
if status_list_credential_json.is_some() {
validation_option = validation_option.status_check(StatusCheck::SkipAll);
}

let validator = JwtCredentialValidator::with_signature_verifier(EdDSAJwsVerifier::default());
let decoded_credential = validator
.validate::<_, Object>(
&jwt,
&issuer_doc,
&JwtCredentialValidationOptions::default(),
FailFast::FirstError,
)
.validate::<_, Object>(&jwt, &issuer_doc, &validation_option, FailFast::FirstError)
.map_err(|mut e| match e.validation_errors.swap_remove(0) {
JwtValidationError::Revoked => VcValidationError::RevokedCredential,
JwtValidationError::ExpirationDate | JwtValidationError::IssuanceDate => VcValidationError::ExpiredCredential,
e => VcValidationError::JwtValidationError(e),
})?;

if let Some(status_list_json) = status_list_credential_json {
let status_list = StatusList2021Credential::from_json(&status_list_json)
.map_err(VcValidationError::InvalidStatusList2020Credential)?;
JwtCredentialValidatorUtils::check_status_with_status_list_2021(
&decoded_credential.credential,
&status_list,
StatusCheck::Strict,
)
.map_err(|e| match e {
JwtValidationError::Revoked => VcValidationError::RevokedCredential,
JwtValidationError::Suspended => VcValidationError::SuspendedCredential,
e => VcValidationError::JwtValidationError(e),
})?;
}

let response = Response::new(VcValidationResponse {
credential_json: decoded_credential.credential.to_json().unwrap(),
});
Expand Down
4 changes: 1 addition & 3 deletions bindings/grpc/src/services/status_list_2021.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,7 @@ impl StatusList2021Svc for StatusList2021Service {
status_list_credential
.update(move |status_list| {
for (idx, value) in entries {
if let Err(e) = status_list.set_entry(idx as usize, value) {
return Err(e);
}
status_list.set_entry(idx as usize, value)?
}

Ok(())
Expand Down
69 changes: 69 additions & 0 deletions bindings/grpc/tests/api/credential_validation.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
use _credentials::vc_validation_client::VcValidationClient;
use _credentials::VcValidationRequest;
use identity_iota::core::FromJson;
use identity_iota::core::ToJson;
use identity_iota::core::Url;
use identity_iota::credential::status_list_2021::StatusList2021;
use identity_iota::credential::status_list_2021::StatusList2021CredentialBuilder;
use identity_iota::credential::status_list_2021::StatusPurpose;
use identity_iota::credential::Credential;
use identity_iota::credential::CredentialBuilder;
use identity_iota::credential::Issuer;
use identity_iota::credential::Subject;
use identity_iota::did::DID;
use identity_storage::JwkDocumentExt;
Expand Down Expand Up @@ -77,3 +82,67 @@ async fn credential_validation() -> anyhow::Result<()> {

Ok(())
}

#[tokio::test]
async fn revoked_credential_validation() -> anyhow::Result<()> {
let stronghold = StrongholdStorage::new(make_stronghold());
let server = TestServer::new_with_stronghold(stronghold.clone()).await;
let api_client = server.client();

let mut issuer = Entity::new_with_stronghold(stronghold);
issuer.create_did(api_client).await?;

let mut holder = Entity::new();
holder.create_did(api_client).await?;

let subject = Subject::from_json_value(json!({
"id": holder.document().unwrap().id().as_str(),
"name": "Alice",
"degree": {
"type": "BachelorDegree",
"name": "Bachelor of Science and Arts",
},
"GPA": "4.0",
}))?;

let mut status_list_credential = StatusList2021CredentialBuilder::new(StatusList2021::default())
.issuer(Issuer::Url(Url::parse(issuer.document().unwrap().id().as_str())?))
.purpose(StatusPurpose::Revocation)
.subject_id(Url::parse("https://example.edu/credentials/status/1")?)
.build()?;

// Build credential using subject above and issuer.
let mut credential: Credential = CredentialBuilder::default()
.id(Url::parse("https://example.edu/credentials/3732")?)
.issuer(Url::parse(issuer.document().unwrap().id().as_str())?)
.type_("UniversityDegreeCredential")
.subject(subject)
.build()?;
status_list_credential.set_credential_status(&mut credential, 0, true)?;

let credential_jwt = issuer
.document()
.unwrap()
.create_credential_jwt(
&credential,
&issuer.storage(),
&issuer.fragment().unwrap(),
&JwsSignatureOptions::default(),
None,
)
.await?
.into();

let mut grpc_client = VcValidationClient::connect(server.endpoint()).await?;
let error = grpc_client
.validate(VcValidationRequest {
credential_jwt,
status_list_credential_json: Some(status_list_credential.to_json()?),
})
.await
.unwrap_err();

assert!(error.message().contains("revoked"));

Ok(())
}

0 comments on commit 3352f96

Please sign in to comment.