From 05b1f995bf4ccccda44f858509866a49992d64f7 Mon Sep 17 00:00:00 2001 From: Frederik Rothenberger Date: Thu, 23 Nov 2023 16:12:59 +0100 Subject: [PATCH] Refactor credential spec verification in issuer (#2069) * Refactor credential spec verification in issuer Introduces an enum to distill down the information in the credential spec to actually supported credentials. This gets rid of unnecessary match arms and is also done in preparation for the consent message rework. * Use arguments from credential spec --- demos/vc_issuer/src/main.rs | 81 +++++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 31 deletions(-) diff --git a/demos/vc_issuer/src/main.rs b/demos/vc_issuer/src/main.rs index 3d4476ae48..e5185b90cb 100644 --- a/demos/vc_issuer/src/main.rs +++ b/demos/vc_issuer/src/main.rs @@ -27,6 +27,7 @@ use vc_util::{ did_for_principal, get_verified_id_alias_from_jws, vc_jwt_to_jws, vc_signing_input, vc_signing_input_hash, AliasTuple, }; +use SupportedCredentialType::{UniversityDegreeCredential, VerifiedEmployee}; /// We use restricted memory in order to ensure the separation between non-managed config memory (first page) /// and the managed memory for potential other data of the canister. @@ -38,6 +39,14 @@ const CERTIFICATE_VALIDITY_PERIOD_NS: u64 = 5 * MINUTE_NS; const PROD_II_CANISTER_ID: &str = "rdmx6-jaaaa-aaaaa-aaadq-cai"; // The expiration of issued verifiable credentials. const VC_EXPIRATION_PERIOD_NS: u64 = 15 * MINUTE_NS; +const VC_EMPLOYER_NAME: &str = "DFINITY Foundation"; +const VC_INSTITUTION_NAME: &str = "DFINITY College of Engineering"; + +#[derive(Debug)] +enum SupportedCredentialType { + VerifiedEmployee(String), + UniversityDegreeCredential(String), +} thread_local! { /// Static configuration of the canister set by init() or post_upgrade(). @@ -167,8 +176,16 @@ async fn prepare_credential(req: PrepareCredentialRequest) -> PrepareCredentialR err, )); } + let credential_type = match verify_credential_spec(&req.credential_spec) { + Ok(credential_type) => credential_type, + Err(err) => { + return PrepareCredentialResponse::Err( + IssueCredentialError::UnsupportedCredentialSpec(err), + ); + } + }; - let credential = match prepare_credential_payload(&req, &alias_tuple) { + let credential = match prepare_credential_payload(&credential_type, &alias_tuple) { Ok(credential) => credential, Err(err) => return PrepareCredentialResponse::Err(err), }; @@ -257,18 +274,24 @@ async fn vc_consent_message(req: Icrc21VcConsentMessageRequest) -> Icrc21Consent }) } -fn verify_credential_spec(spec: &CredentialSpec) -> Result<(), String> { +fn verify_credential_spec(spec: &CredentialSpec) -> Result { match spec.credential_name.as_str() { - "VerifiedEmployee" => verify_single_argument( - spec, - "employerName", - ArgumentValue::String("DFINITY Foundation".to_string()), - ), - "UniversityDegreeCredential" => verify_single_argument( - spec, - "institutionName", - ArgumentValue::String("DFINITY College of Engineering".to_string()), - ), + "VerifiedEmployee" => { + verify_single_argument( + spec, + "employerName", + ArgumentValue::String(VC_EMPLOYER_NAME.to_string()), + )?; + Ok(VerifiedEmployee(VC_EMPLOYER_NAME.to_string())) + } + "UniversityDegreeCredential" => { + verify_single_argument( + spec, + "institutionName", + ArgumentValue::String(VC_INSTITUTION_NAME.to_string()), + )?; + Ok(UniversityDegreeCredential(VC_INSTITUTION_NAME.to_string())) + } other => Err(format!("Credential {} is not supported", other)), } } @@ -402,13 +425,13 @@ fn calculate_seed(principal: &Principal) -> Hash { hash_bytes(bytes) } -fn bachelor_degree_credential(subject_principal: Principal) -> Credential { +fn bachelor_degree_credential(subject_principal: Principal, institution_name: &str) -> Credential { let subject: Subject = Subject::from_json_value(json!({ "id": did_for_principal(subject_principal), "degree": { "type": "BachelorDegree", "name": "Bachelor of Engineering", - "institutionName": "DFINITY College of Engineering", + "institutionName": institution_name, }, })) .unwrap(); @@ -424,12 +447,12 @@ fn bachelor_degree_credential(subject_principal: Principal) -> Credential { .unwrap() } -fn dfinity_employment_credential(subject_principal: Principal) -> Credential { +fn dfinity_employment_credential(subject_principal: Principal, employer_name: &str) -> Credential { let subject: Subject = Subject::from_json_value(json!({ "id": did_for_principal(subject_principal), "employee_of": { "employerId" : "did:web:dfinity.org", - "employerName": "DFINITY Foundation", + "employerName": employer_name, }, })) .unwrap(); @@ -451,31 +474,27 @@ fn exp_timestamp() -> Timestamp { } fn prepare_credential_payload( - req: &PrepareCredentialRequest, + credential_type: &SupportedCredentialType, alias_tuple: &AliasTuple, ) -> Result { - match req.credential_spec.credential_name.as_str() { - "VerifiedEmployee" => { + match credential_type { + VerifiedEmployee(employer_name) => { EMPLOYEES.with_borrow(|employees| { - verify_authorized_principal(&req.credential_spec, alias_tuple, employees) + verify_authorized_principal(credential_type, alias_tuple, employees) })?; - Ok(dfinity_employment_credential(alias_tuple.id_alias)) + Ok(dfinity_employment_credential(alias_tuple.id_alias, employer_name.as_str())) } - "UniversityDegreeCredential" => { + UniversityDegreeCredential(institution_name) => { GRADUATES.with_borrow(|graduates| { - verify_authorized_principal(&req.credential_spec, alias_tuple, graduates) + verify_authorized_principal(credential_type, alias_tuple, graduates) })?; - Ok(bachelor_degree_credential(alias_tuple.id_alias)) + Ok(bachelor_degree_credential(alias_tuple.id_alias, institution_name.as_str())) } - other => Err(IssueCredentialError::UnsupportedCredentialSpec(format!( - "credential {} is not supported", - other - ))), } } fn verify_authorized_principal( - credential_spec: &CredentialSpec, + credential_type: &SupportedCredentialType, alias_tuple: &AliasTuple, authorized_principals: &HashSet, ) -> Result<(), IssueCredentialError> { @@ -483,9 +502,9 @@ fn verify_authorized_principal( Ok(()) } else { println!( - "*** principal {} it is not authorized for credential {}", + "*** principal {} it is not authorized for credential type {:?}", alias_tuple.id_dapp.to_text(), - credential_spec.credential_name + credential_type ); Err(IssueCredentialError::UnauthorizedSubject(format!( "unauthorized principal {}",