diff --git a/src/data_types/schema.rs b/src/data_types/schema.rs index d160f5ea..8f4433e6 100644 --- a/src/data_types/schema.rs +++ b/src/data_types/schema.rs @@ -72,7 +72,7 @@ impl Validatable for AttributeNames { self.0.len(), MAX_ATTRIBUTES_COUNT ) - .into()); + .into()); } Ok(()) } diff --git a/src/data_types/w3c/credential.rs b/src/data_types/w3c/credential.rs index f8d18009..cbe4402b 100644 --- a/src/data_types/w3c/credential.rs +++ b/src/data_types/w3c/credential.rs @@ -105,7 +105,7 @@ impl Validatable for CredentialAttributes { "CredentialAttributes validation failed: {} value format is not supported", attribute ) - .into()) + .into()); } } } @@ -137,12 +137,6 @@ impl CredentialAttributes { self.0.insert(attribute, json!(value)); } - pub fn get_attribute(&self, attribute: &str) -> Result<&Value> { - self.0 - .get(attribute) - .ok_or_else(|| err_msg!("Credential attribute {} not found", attribute)) - } - pub fn encode(&self, encoding: &CredentialValuesEncoding) -> Result { match encoding { CredentialValuesEncoding::Auto => { diff --git a/src/data_types/w3c/presentation_proof.rs b/src/data_types/w3c/presentation_proof.rs index 25c36ac2..bfaabb27 100644 --- a/src/data_types/w3c/presentation_proof.rs +++ b/src/data_types/w3c/presentation_proof.rs @@ -7,11 +7,6 @@ use std::collections::HashSet; pub struct CredentialPresentationProof { #[serde(rename = "type")] pub type_: PresentationProofType, - /// Uniform Resource Identifier - https://www.w3.org/TR/vc-data-model/#dfn-uri - // FIXME: Consider either removing or moving under proof_value - // In fact, it's only needed to make attributes validation on the verifier side - // Revealed attributes and predicates can be restored from credential subject, but not unrevealed attributes - pub mapping: CredentialAttributesMapping, pub proof_value: String, #[serde(skip_serializing_if = "Option::is_none")] // Timestamp is needed to query revocation registry at the specific moment in time @@ -40,12 +35,10 @@ impl CredentialPresentationProofValue { impl CredentialPresentationProof { pub fn new( proof_value: CredentialPresentationProofValue, - mapping: CredentialAttributesMapping, timestamp: Option, ) -> CredentialPresentationProof { CredentialPresentationProof { type_: PresentationProofType::AnonCredsPresentationProof2023, - mapping, timestamp, proof_value: proof_value.encode(), } @@ -75,10 +68,10 @@ pub struct PresentationProofValue { } impl PresentationProof { - pub fn new(proof_value: PresentationProofValue, nonce: String) -> PresentationProof { + pub fn new(proof_value: PresentationProofValue, challenge: String) -> PresentationProof { PresentationProof { type_: PresentationProofType::AnonCredsPresentationProof2023, - challenge: nonce, + challenge, proof_value: proof_value.encode(), } } diff --git a/src/services/helpers.rs b/src/services/helpers.rs index df0f9686..c3d0a5f3 100644 --- a/src/services/helpers.rs +++ b/src/services/helpers.rs @@ -5,7 +5,7 @@ use crate::cl::{ use crate::data_types::presentation::RequestedProof; use crate::data_types::rev_reg_def::RevocationRegistryDefinitionId; use crate::data_types::schema::Schema; -use crate::data_types::w3c::credential::W3CCredential; +use crate::data_types::w3c::credential::{PredicateAttribute, W3CCredential}; use crate::data_types::{ credential::CredentialValues, link_secret::LinkSecret, @@ -218,27 +218,43 @@ pub fn get_non_revoked_interval( &HashMap>, >, ) -> Option { - let mut interval: Option = None; + // Collapse to the most stringent local interval for the attributes / predicates, + // we can do this because there is only 1 revocation status list for this credential + // if it satisfies the most stringent interval, it will satisfy all intervals + let interval = match (attrs_nonrevoked_interval, pred_nonrevoked_interval) { + (Some(attr), None) => Some(attr), + (None, Some(pred)) => Some(pred), + (Some(mut attr), Some(pred)) => { + attr.compare_and_set(&pred); + Some(attr) + } + _ => None, + }; + + get_requested_non_revoked_interval( + rev_reg_id, + interval.as_ref(), + pres_req.non_revoked.as_ref(), + nonrevoke_interval_override, + ) +} - if let Some(rev_reg_id) = rev_reg_id { - // Collapse to the most stringent local interval for the attributes / predicates, - // we can do this because there is only 1 revocation status list for this credential - // if it satisfies the most stringent interval, it will satisfy all intervals - interval = match (attrs_nonrevoked_interval, pred_nonrevoked_interval) { - (Some(attr), None) => Some(attr), - (None, Some(pred)) => Some(pred), - (Some(mut attr), Some(pred)) => { - attr.compare_and_set(&pred); - Some(attr) - } - _ => None, - }; +pub fn get_requested_non_revoked_interval( + rev_reg_id: Option<&RevocationRegistryDefinitionId>, + local_nonrevoked_interval: Option<&NonRevokedInterval>, + global_nonrevoked_interval: Option<&NonRevokedInterval>, + nonrevoke_interval_override: Option< + &HashMap>, + >, +) -> Option { + let mut interval: Option = local_nonrevoked_interval.cloned(); + if let Some(rev_reg_id) = rev_reg_id { // Global interval is override by the local one, // we only need to update if local is None and Global is Some, // do not need to update if global is more stringent - if let (Some(global), None) = (pres_req.non_revoked.clone(), interval.as_mut()) { - interval = Some(global); + if let (Some(global), None) = (global_nonrevoked_interval, interval.as_mut()) { + interval = Some(global.clone()); }; // Override Interval if an earlier `from` value is accepted by the verifier @@ -280,6 +296,17 @@ impl RequestedProof { } } +impl Schema { + pub(crate) fn has_attribute(&self, requested_attribute: &str) -> bool { + for attribute in self.attr_names.0.iter() { + if attr_common_view(attribute) == attr_common_view(requested_attribute) { + return true; + } + } + return false; + } +} + impl W3CCredential { pub(crate) fn get_attribute(&self, requested_attribute: &str) -> Result<(String, Value)> { for (attribute, value) in self.credential_subject.attributes.0.iter() { @@ -287,9 +314,50 @@ impl W3CCredential { return Ok((attribute.to_owned(), value.to_owned())); } } - Err(err_msg!( - "Credential attribute {} not found", - requested_attribute - )) + return Err(err_msg!("Credential attribute {} not found", requested_attribute)); + } + + pub(crate) fn has_attribute(&self, requested_attribute: &str) -> bool { + for attribute in self.credential_subject.attributes.0.keys() { + if attr_common_view(attribute) == attr_common_view(requested_attribute) { + return true; + } + } + return false; + } + + pub(crate) fn attributes(&self) -> Vec { + self.credential_subject + .attributes + .0 + .iter() + .filter(|(_, value)| value.as_str().is_some()) + .map(|(attribute, _)| AttributeInfo { + name: Some(attribute.to_owned()), + names: None, + restrictions: None, + non_revoked: None, + }) + .collect() + } + + pub(crate) fn predicates(&self) -> Result> { + self.credential_subject + .attributes + .0 + .iter() + .filter(|(_, value)| value.as_object().is_some()) + .map(|(attribute, value)| { + serde_json::from_value::(value.to_owned()) + .map(|predicate| PredicateInfo { + name: attribute.to_owned(), + p_type: predicate.p_type, + p_value: predicate.p_value, + restrictions: None, + non_revoked: None, + }) + .map_err(|_| err_msg!("Unable to parse predicate from credential attribute")) + }) + .collect() } } diff --git a/src/services/prover.rs b/src/services/prover.rs index 3c9a0a57..4e67ffd4 100644 --- a/src/services/prover.rs +++ b/src/services/prover.rs @@ -33,8 +33,8 @@ use crate::utils::validation::Validatable; use crate::data_types::rev_reg_def::RevocationRegistryDefinitionId; use crate::data_types::w3c::credential_proof::{CredentialProof, CredentialSignature}; use crate::data_types::w3c::presentation_proof::{ - CredentialAttributesMapping, CredentialPresentationProof, CredentialPresentationProofValue, - PresentationProof, PresentationProofValue, + CredentialPresentationProof, CredentialPresentationProofValue, PresentationProof, + PresentationProofValue, }; use anoncreds_clsignatures::{ CredentialSignature as CLCredentialSignature, NonCredentialSchema, Proof, ProofBuilder, @@ -386,7 +386,7 @@ pub fn process_w3c_credential( signature.rev_reg, signature.witness, ) - .encode(); + .encode(); trace!("w3c_process_credential <<< "); @@ -575,6 +575,7 @@ pub fn create_presentation( }; let pres_req_val = pres_req.value(); + let mut sub_proof_index = 0; let mut identifiers: Vec = Vec::with_capacity(credentials.len()); @@ -622,15 +623,18 @@ pub fn create_presentation( let proof = proof_builder.build()?; - let full_proof = Presentation { + let presentation = Presentation { proof, requested_proof, identifiers, }; - trace!("create_proof <<< full_proof: {:?}", secret!(&full_proof)); + trace!( + "create_proof <<< presentation: {:?}", + secret!(&presentation) + ); - Ok(full_proof) + Ok(presentation) } /// Create W3C presentation @@ -641,13 +645,11 @@ pub fn create_w3c_presentation( schemas: &HashMap, cred_defs: &HashMap, ) -> Result { - trace!("create_proof >>> credentials: {:?}, pres_req: {:?}, credentials: {:?}, link_secret: {:?}, schemas: {:?}, cred_defs: {:?}", + trace!("create_w3c_presentation >>> credentials: {:?}, pres_req: {:?}, credentials: {:?}, link_secret: {:?}, schemas: {:?}, cred_defs: {:?}", credentials, pres_req, credentials, secret!(&link_secret), schemas, cred_defs); if credentials.is_empty() { - return Err(err_msg!( - "No credential mapping or self-attested attributes presented" - )); + return Err(err_msg!("No credential mapping")); } // check for duplicate referents credentials.validate()?; @@ -667,15 +669,18 @@ pub fn create_w3c_presentation( .encode(&credential.credential_schema.encoding)?; let proof = credential.get_credential_signature_proof()?; let signature = proof.get_credential_signature()?; + let schema_id = &credential.credential_schema.schema; + let cred_def_id = &credential.credential_schema.definition; + let rev_reg_id = credential.credential_schema.revocation_registry.as_ref(); proof_builder.add_sub_proof( &credential_values, &signature.signature, link_secret, present, - &credential.credential_schema.schema, - &credential.credential_schema.definition, - credential.credential_schema.revocation_registry.as_ref(), + schema_id, + cred_def_id, + rev_reg_id, )?; } @@ -691,10 +696,9 @@ pub fn create_w3c_presentation( // cl signatures generates sub proofs and aggregated at once at the end // so we need to iterate over credentials again an put sub proofs into their proofs for (present, sub_proof) in credentials.0.iter().zip(cl_proof.proofs) { - let mapping = build_mapping(pres_req, present)?; let credential_subject = build_credential_subject(pres_req, present)?; let proof_value = CredentialPresentationProofValue::new(sub_proof); - let proof = CredentialPresentationProof::new(proof_value, mapping, present.timestamp); + let proof = CredentialPresentationProof::new(proof_value, present.timestamp); let verifiable_credential = W3CCredential { credential_subject, proof: OneOrMany::One(CredentialProof::AnonCredsCredentialPresentationProof(proof)), @@ -704,19 +708,13 @@ pub fn create_w3c_presentation( } trace!( - "create_proof <<< presentation: {:?}", + "create_w3c_presentation <<< presentation: {:?}", secret!(&presentation) ); Ok(presentation) } -fn _proof_builder() -> Result { - let mut proof_builder = Prover::new_proof_builder()?; - proof_builder.add_common_attribute("master_secret")?; - Ok(proof_builder) -} - /// Create a [`CredentialRevocationState`] based on a [`Witness`], [`RevocationStatusList`] and /// timestamp. pub fn create_revocation_state_with_witness( @@ -1007,39 +1005,6 @@ fn update_requested_proof( Ok(()) } -fn build_mapping<'p>( - pres_req: &PresentationRequestPayload, - credential: &PresentCredential<'p, W3CCredential>, -) -> Result { - let mut mapping = CredentialAttributesMapping::default(); - - for (referent, reveal) in credential.requested_attributes.iter() { - let requested_attribute = pres_req.requested_attributes.get(referent).ok_or_else(|| { - err_msg!( - "Attribute with referent \"{}\" not found in ProofRequests", - referent - ) - })?; - if requested_attribute.name.is_some() { - if *reveal { - mapping.revealed_attributes.insert(referent.to_string()); - } else { - mapping.unrevealed_attributes.insert(referent.to_string()); - } - } - if requested_attribute.names.is_some() { - mapping - .revealed_attribute_groups - .insert(referent.to_string()); - } - } - for referent in credential.requested_predicates.iter() { - mapping.predicates.insert(referent.to_string()); - } - - Ok(mapping) -} - fn build_credential_subject<'p>( pres_req_val: &PresentationRequestPayload, credentials: &PresentCredential<'p, W3CCredential>, @@ -1055,17 +1020,13 @@ fn build_credential_subject<'p>( if let Some(ref name) = requested_attribute.name { let (attribute, value) = credentials.cred.get_attribute(name)?; if *reveal { - credential_subject - .attributes - .add_attribute(attribute, value); + credential_subject.attributes.add_attribute(attribute, value); } } if let Some(ref names) = requested_attribute.names { for name in names { let (attribute, value) = credentials.cred.get_attribute(name)?; - credential_subject - .attributes - .add_attribute(attribute, value); + credential_subject.attributes.add_attribute(attribute, value); } } } @@ -1074,13 +1035,11 @@ fn build_credential_subject<'p>( let predicate_info = pres_req_val .requested_predicates .get(referent) - .unwrap() + .ok_or_else(|| err_msg!("predicate {} not found request", referent))? .clone(); let (attribute, _) = credentials.cred.get_attribute(&predicate_info.name)?; let predicate = PredicateAttribute::from(predicate_info); - credential_subject - .attributes - .add_predicate(attribute, predicate); + credential_subject.attributes.add_predicate(attribute, predicate); } Ok(credential_subject) @@ -1099,7 +1058,7 @@ impl<'a> CLProofBuilder<'a> { presentation_request: &'a PresentationRequestPayload, schemas: &'a HashMap, cred_defs: &'a HashMap, - ) -> Result> { + ) -> Result { let mut proof_builder = Prover::new_proof_builder()?; proof_builder.add_common_attribute("master_secret")?; let non_credential_schema = build_non_credential_schema()?; @@ -1285,8 +1244,7 @@ mod tests { } #[test] - fn get_credential_values_for_attribute_works_for_cred_values_and_requested_attr_contains_spaces( - ) { + fn get_credential_values_for_attribute_works_for_cred_values_and_requested_attr_contains_spaces() { let cred_values = hashmap!(" name ".to_string() => _attr_values()); let res = @@ -1324,11 +1282,10 @@ mod tests { ISSUER_ID.try_into().unwrap(), ["a", "b", "c"][..].into(), ) - .unwrap() + .unwrap() } - fn _cred_def_and_key_correctness_proof( - ) -> (CredentialDefinition, CredentialKeyCorrectnessProof) { + fn _cred_def_and_key_correctness_proof() -> (CredentialDefinition, CredentialKeyCorrectnessProof) { let (cred_def, _, key_correctness_proof) = create_credential_definition( SCHEMA_ID.try_into().unwrap(), &_schema(), @@ -1339,7 +1296,7 @@ mod tests { support_revocation: false, }, ) - .unwrap(); + .unwrap(); (cred_def, key_correctness_proof) } @@ -1349,7 +1306,7 @@ mod tests { CRED_DEF_ID.try_into().unwrap(), &key_correctness_proof, ) - .unwrap() + .unwrap() } fn _legacy_schema() -> Schema { @@ -1359,11 +1316,10 @@ mod tests { LEGACY_DID_IDENTIFIER.try_into().unwrap(), ["a", "b", "c"][..].into(), ) - .unwrap() + .unwrap() } - fn _legacy_cred_def_and_key_correctness_proof( - ) -> (CredentialDefinition, CredentialKeyCorrectnessProof) { + fn _legacy_cred_def_and_key_correctness_proof() -> (CredentialDefinition, CredentialKeyCorrectnessProof) { let (cred_def, _, key_correctness_proof) = create_credential_definition( LEGACY_SCHEMA_IDENTIFIER.try_into().unwrap(), &_legacy_schema(), @@ -1374,7 +1330,7 @@ mod tests { support_revocation: false, }, ) - .unwrap(); + .unwrap(); (cred_def, key_correctness_proof) } @@ -1386,7 +1342,7 @@ mod tests { LEGACY_CRED_DEF_IDENTIFIER.try_into().unwrap(), &key_correctness_proof, ) - .unwrap() + .unwrap() } #[test] diff --git a/src/services/verifier.rs b/src/services/verifier.rs index e278a4b7..8e846b35 100644 --- a/src/services/verifier.rs +++ b/src/services/verifier.rs @@ -9,23 +9,21 @@ use crate::data_types::cred_def::CredentialDefinition; use crate::data_types::cred_def::CredentialDefinitionId; use crate::data_types::issuer_id::IssuerId; use crate::data_types::nonce::Nonce; -use crate::data_types::pres_request::AttributeInfo; use crate::data_types::pres_request::PresentationRequestPayload; -use crate::data_types::presentation::{ - AttributeValue, Identifier, RequestedProof, RevealedAttributeGroupInfo, RevealedAttributeInfo, - SubProofReferent, -}; +use crate::data_types::pres_request::{AttributeInfo, NonRevokedInterval, PredicateInfo}; +use crate::data_types::presentation::{Identifier, RequestedProof, RevealedAttributeInfo}; use crate::data_types::rev_reg_def::RevocationRegistryDefinitionId; use crate::data_types::schema::Schema; use crate::data_types::schema::SchemaId; use crate::data_types::w3c::presentation::W3CPresentation; use crate::error::Result; -use crate::services::helpers::build_non_credential_schema; +use crate::services::helpers::build_credential_schema; use crate::services::helpers::build_sub_proof_request; -use crate::services::helpers::{build_credential_schema, get_non_revoked_interval}; +use crate::services::helpers::{build_non_credential_schema, get_requested_non_revoked_interval}; use crate::utils::query::Query; use crate::utils::validation::LEGACY_DID_IDENTIFIER; +use crate::data_types::w3c::credential::W3CCredential; use anoncreds_clsignatures::{NonCredentialSchema, Proof, ProofVerifier}; use once_cell::sync::Lazy; use regex::Regex; @@ -59,42 +57,13 @@ pub fn verify_presentation( trace!("verify >>> presentation: {:?}, pres_req: {:?}, schemas: {:?}, cred_defs: {:?}, rev_reg_defs: {:?} rev_status_lists: {:?}", presentation, pres_req, schemas, cred_defs, rev_reg_defs, rev_status_lists); - // These values are from the prover and cannot be trusted - let received_revealed_attrs: HashMap = - received_revealed_attrs(presentation)?; - let received_unrevealed_attrs: HashMap = - received_unrevealed_attrs(presentation)?; - let received_predicates: HashMap = received_predicates(presentation)?; - let received_self_attested_attrs: HashSet = received_self_attested_attrs(presentation); - - let pres_req = pres_req.value(); + let presentation_request = pres_req.value(); - // Ensures that all attributes in the request is also in the presentation - compare_attr_from_proof_and_request( - pres_req, - &received_revealed_attrs, - &received_unrevealed_attrs, - &received_self_attested_attrs, - &received_predicates, - )?; - - // Ensures the encoded values are same as request - verify_revealed_attribute_values(pres_req, presentation)?; - - // Ensures the restrictions set out in the request is met - verify_requested_restrictions( - pres_req, - schemas, - cred_defs, - &presentation.requested_proof, - &received_revealed_attrs, - &received_unrevealed_attrs, - &received_predicates, - &received_self_attested_attrs, - )?; + // These values are from the prover and cannot be trusted + check_request_data_for_presentation(presentation_request, presentation, schemas, cred_defs)?; let mut proof_verifier = CLProofVerifier::init( - pres_req, + presentation_request, schemas, cred_defs, rev_reg_defs, @@ -111,6 +80,23 @@ pub fn verify_presentation( .requested_proof .predicate_referents(sub_proof_index as u32); + let (attributes, attrs_nonrevoked_interval) = + presentation_request.get_requested_attributes(&attributes)?; + let (predicates, pred_nonrevoked_interval) = + presentation_request.get_requested_predicates(&predicates)?; + + { + check_non_revoked_interval( + proof_verifier.get_credential_definition(&identifier.cred_def_id)?, + attrs_nonrevoked_interval, + pred_nonrevoked_interval, + presentation_request, + identifier.rev_reg_id.as_ref(), + nonrevoke_interval_override, + identifier.timestamp, + )?; + } + proof_verifier.add_sub_proof( &attributes, &predicates, @@ -118,7 +104,6 @@ pub fn verify_presentation( &identifier.cred_def_id, identifier.rev_reg_id.as_ref(), identifier.timestamp, - nonrevoke_interval_override, )?; } @@ -144,44 +129,26 @@ pub fn verify_w3c_presentation( trace!("verify >>> presentation: {:?}, pres_req: {:?}, schemas: {:?}, cred_defs: {:?}, rev_reg_defs: {:?} rev_status_lists: {:?}", presentation, pres_req, schemas, cred_defs, rev_reg_defs, rev_status_lists); - // These values are from the prover and cannot be trusted - let (received_revealed_attrs, received_unrevealed_attrs, received_predicates) = - collect_received_attrs_and_predicates_from_w3c_presentation(presentation)?; - // W3C presentation does not support self-attested attributes - let self_attested_attrs = HashSet::new(); - - let pres_req = pres_req.value(); - - // Ensures that all attributes in the request is also in the presentation - compare_attr_from_proof_and_request( - pres_req, - &received_revealed_attrs, - &received_unrevealed_attrs, - &self_attested_attrs, - &received_predicates, - )?; + let presentation_request = pres_req.value(); - // Ensures the restrictions set out in the request is met - let requested_proof = build_requested_proof_from_w3c_presentation(presentation, pres_req)?; - verify_requested_restrictions( - pres_req, + // These values are from the prover and cannot be trusted + // Check that all requested attributes and predicates included into the presentation + // Also check that all requested credential restriction are valid + check_request_data_for_w3c_presentation( + presentation_request, + presentation, schemas, cred_defs, - &requested_proof, - &received_revealed_attrs, - &received_unrevealed_attrs, - &received_predicates, - &self_attested_attrs, + nonrevoke_interval_override, )?; - let proof_data = presentation.proof.get_proof_value()?; let mut proof = Proof { proofs: Vec::new(), - aggregated_proof: proof_data.aggregated, + aggregated_proof: presentation.proof.get_proof_value()?.aggregated, }; let mut proof_verifier = CLProofVerifier::init( - pres_req, + presentation_request, schemas, cred_defs, rev_reg_defs, @@ -193,24 +160,23 @@ pub fn verify_w3c_presentation( let proof_data = credential_proof.get_proof_value()?; let schema_id = &verifiable_credential.credential_schema.schema; let cred_def_id = &verifiable_credential.credential_schema.definition; - let rev_reg_id = verifiable_credential + let rev_reg_def_id = verifiable_credential .credential_schema .revocation_registry .as_ref(); - let mut revealed_attribute: HashSet = - credential_proof.mapping.revealed_attributes.clone(); - revealed_attribute.extend(credential_proof.mapping.revealed_attribute_groups.clone()); + let attributes: Vec = verifiable_credential.attributes(); + let predicates: Vec = verifiable_credential.predicates()?; proof_verifier.add_sub_proof( - &revealed_attribute, - &credential_proof.mapping.predicates, + &attributes, + &predicates, schema_id, cred_def_id, - rev_reg_id, + rev_reg_def_id, credential_proof.timestamp, - nonrevoke_interval_override, )?; + proof.proofs.push(proof_data.sub_proof); } @@ -226,6 +192,49 @@ pub fn generate_nonce() -> Result { new_nonce() } +fn check_non_revoked_interval( + cred_def: &CredentialDefinition, + attrs_nonrevoked_interval: Option, + pred_nonrevoked_interval: Option, + pres_req: &PresentationRequestPayload, + rev_reg_id: Option<&RevocationRegistryDefinitionId>, + nonrevoke_interval_override: Option< + &HashMap>, + >, + timestamp: Option, +) -> Result<()> { + // Collapse to the most stringent local interval for the attributes / predicates, + // we can do this because there is only 1 revocation status list for this credential + // if it satisfies the most stringent interval, it will satisfy all intervals + let interval = match (attrs_nonrevoked_interval, pred_nonrevoked_interval) { + (Some(attr), None) => Some(attr), + (None, Some(pred)) => Some(pred), + (Some(mut attr), Some(pred)) => { + attr.compare_and_set(&pred); + Some(attr) + } + _ => None, + }; + + if let (Some(_), true) = (cred_def.value.revocation.as_ref(), interval.is_some()) { + let cred_nonrevoked_interval = get_requested_non_revoked_interval( + rev_reg_id, + interval.as_ref(), + pres_req.non_revoked.as_ref(), + nonrevoke_interval_override, + ); + + if let Some(cred_nonrevoked_interval) = cred_nonrevoked_interval { + let timestamp = timestamp + .ok_or_else(|| err_msg!("Identifier timestamp not found for revocation check"))?; + + cred_nonrevoked_interval.is_valid(timestamp)?; + } + } + + Ok(()) +} + fn compare_attr_from_proof_and_request( pres_req: &PresentationRequestPayload, received_revealed_attrs: &HashMap, @@ -506,7 +515,14 @@ fn verify_requested_restrictions( for (referent, info) in &requested_attrs { if let Some(ref query) = info.restrictions { - let filter = gather_filter_info(referent, &proof_attr_identifiers, schemas, cred_defs)?; + let identifier = proof_attr_identifiers.get(referent).ok_or_else(|| { + err_msg!( + InvalidState, + "Identifier not found for referent: {}", + referent + ) + })?; + let filter = gather_filter_info(identifier, schemas, cred_defs)?; let attr_value_map: HashMap> = if let Some(name) = info.name.as_ref() @@ -550,7 +566,14 @@ fn verify_requested_restrictions( for (referent, info) in &pres_req.requested_predicates { if let Some(ref query) = info.restrictions { - let filter = gather_filter_info(referent, received_predicates, schemas, cred_defs)?; + let identifier = received_predicates.get(referent).ok_or_else(|| { + err_msg!( + InvalidState, + "Identifier not found for referent: {}", + referent + ) + })?; + let filter = gather_filter_info(identifier, schemas, cred_defs)?; // start with the predicate requested attribute, which is un-revealed let mut attr_value_map = HashMap::new(); @@ -611,19 +634,10 @@ fn is_self_attested( } fn gather_filter_info( - referent: &str, - identifiers: &HashMap, + identifier: &Identifier, schemas: &HashMap, cred_defs: &HashMap, ) -> Result { - let identifier = identifiers.get(referent).ok_or_else(|| { - err_msg!( - InvalidState, - "Identifier not found for referent: {}", - referent - ) - })?; - let schema_id = &identifier.schema_id; let cred_def_id = &identifier.cred_def_id; @@ -810,117 +824,255 @@ fn is_attr_operator(key: &str) -> bool { key.starts_with("attr::") && key.ends_with("::marker") } -#[allow(clippy::type_complexity)] -fn collect_received_attrs_and_predicates_from_w3c_presentation( - proof: &W3CPresentation, -) -> Result<( - HashMap, - HashMap, - HashMap, -)> { - let mut revealed: HashMap = HashMap::new(); - let mut unrevealed: HashMap = HashMap::new(); - let mut predicates: HashMap = HashMap::new(); - - for verifiable_credential in proof.verifiable_credential.iter() { - let presentation_proof = verifiable_credential.get_presentation_proof()?; +fn check_restrictions_for_w3c_credential( + credential: &W3CCredential, + restrictions: Option<&Query>, + schemas: &HashMap, + cred_defs: &HashMap, +) -> Result<()> { + if let Some(restrictions) = restrictions { let identifier: Identifier = Identifier { - schema_id: verifiable_credential.credential_schema.schema.clone(), - cred_def_id: verifiable_credential.credential_schema.definition.clone(), - rev_reg_id: verifiable_credential - .credential_schema - .revocation_registry - .clone(), + schema_id: credential.credential_schema.schema.clone(), + cred_def_id: credential.credential_schema.definition.clone(), + rev_reg_id: credential.credential_schema.revocation_registry.clone(), timestamp: None, }; - for revealed_attribute in &presentation_proof.mapping.revealed_attributes { - revealed.insert(revealed_attribute.to_string(), identifier.clone()); - } - for revealed_attribute_group in &presentation_proof.mapping.revealed_attribute_groups { - revealed.insert(revealed_attribute_group.to_string(), identifier.clone()); + let filter = gather_filter_info(&identifier, schemas, cred_defs)?; + let mut attr_value_map: HashMap> = HashMap::new(); + for (attribute, value) in credential.credential_subject.attributes.0.iter() { + if let Some(value) = value.as_str() { + attr_value_map.insert(attribute.to_owned(), Some(value)); + } } + process_operator(&attr_value_map, restrictions, &filter).map_err(err_map!( + "Requested restriction validation failed for \"{:?}\" attributes", + &attr_value_map + ))?; + } + Ok(()) +} - for unrevealed_attribute in &presentation_proof.mapping.unrevealed_attributes { - unrevealed.insert(unrevealed_attribute.to_string(), identifier.clone()); - } +fn check_non_revoked_interval_for_w3c_credential( + credential: &W3CCredential, + presentation_request: &PresentationRequestPayload, + nonrevoke_interval: Option<&NonRevokedInterval>, + nonrevoke_interval_override: Option< + &HashMap>, + >, +) -> Result<()> { + if credential.credential_schema.revocation_registry.is_none() { + return Ok(()); + } - for predicate in &presentation_proof.mapping.predicates { - predicates.insert(predicate.to_string(), identifier.clone()); - } + let non_revoked_interval = get_requested_non_revoked_interval( + credential.credential_schema.revocation_registry.as_ref(), + nonrevoke_interval, + presentation_request.non_revoked.as_ref(), + nonrevoke_interval_override, + ); + + if let Some(non_revoked_interval) = non_revoked_interval { + // FIXME: This is BAD: multiple times decoding of proof value + let timestamp = credential + .get_presentation_proof()? + .timestamp + .ok_or_else(|| err_msg!("Credential timestamp not found for revocation check"))?; + non_revoked_interval.is_valid(timestamp)?; } + Ok(()) +} - Ok((revealed, unrevealed, predicates)) +#[allow(clippy::too_many_arguments)] +fn check_conditions_for_w3c_credential( + credential: &W3CCredential, + presentation_request: &PresentationRequestPayload, + restrictions: Option<&Query>, + schemas: &HashMap, + cred_defs: &HashMap, + nonrevoke_interval: Option<&NonRevokedInterval>, + nonrevoke_interval_override: Option< + &HashMap>, + >, +) -> Result<()> { + check_restrictions_for_w3c_credential(credential, restrictions, schemas, cred_defs)?; + check_non_revoked_interval_for_w3c_credential( + credential, + presentation_request, + nonrevoke_interval, + nonrevoke_interval_override, + )?; + Ok(()) } -fn build_requested_proof_from_w3c_presentation( - presentation: &W3CPresentation, +#[allow(clippy::too_many_arguments)] +fn check_requested_attribute_for_w3c_presentation<'a>( presentation_request: &PresentationRequestPayload, -) -> Result { - let mut requested_proof = RequestedProof::default(); - - for (index, credential) in presentation.verifiable_credential.iter().enumerate() { - let sub_proof_index = index as u32; - let proof = credential.get_presentation_proof()?; - for referent in proof.mapping.revealed_attributes.iter() { - let requested_attribute = presentation_request - .requested_attributes - .get(referent) - .cloned() - .ok_or_else(|| err_msg!("Requested Attribute {} not found in request", referent))?; - - let name = requested_attribute - .name - .ok_or_else(|| err_msg!("Requested Attribute expected to have a name attribute"))?; - - let (_, raw) = credential.get_attribute(&name)?; - requested_proof.revealed_attrs.insert( - referent.clone(), - RevealedAttributeInfo { - sub_proof_index, - raw: raw.to_string(), - encoded: "".to_string(), // encoded value not needed - }, - ); + presentation: &'a W3CPresentation, + attribute: &str, + restrictions: Option<&Query>, + nonrevoke_interval: Option<&NonRevokedInterval>, + schemas: &HashMap, + cred_defs: &HashMap, + nonrevoke_interval_override: Option< + &HashMap>, + >, +) -> Result<&'a W3CCredential> { + let mut found_credential: Option<&'a W3CCredential> = None; + for credential in presentation.verifiable_credential.iter() { + let valid_credential = credential.has_attribute(attribute) + && check_conditions_for_w3c_credential( + credential, + presentation_request, + restrictions, + schemas, + cred_defs, + nonrevoke_interval, + nonrevoke_interval_override, + ) + .is_ok(); + + if valid_credential { + found_credential = Some(credential); + break; } - for referent in proof.mapping.revealed_attribute_groups.iter() { - let requested_attribute = presentation_request - .requested_attributes - .get(referent) - .cloned() - .ok_or_else(|| err_msg!("Requested Attribute {} not found in request", referent))?; - let names = requested_attribute.names.ok_or_else(|| { - err_msg!("Requested Attribute expected to have a names attribute") + } + + if let Some(found_credential) = found_credential { + // credential for attribute is found in revealed data + return Ok(found_credential); + } + + // else consider attribute as unrevealed and try to find credential which schema includes requested attribute + for credential in presentation.verifiable_credential.iter() { + let schema = schemas + .get(&credential.credential_schema.schema) + .ok_or_else(|| { + err_msg!( + "Credential schema not found {}", + credential.credential_schema.schema + ) })?; - let mut group_info = RevealedAttributeGroupInfo { - sub_proof_index, - values: HashMap::new(), - }; - for name in names.iter() { - let (_, raw) = credential.get_attribute(name)?; - group_info.values.insert( - name.clone(), - AttributeValue { - raw: raw.to_string(), - encoded: "".to_string(), - }, - ); - } - requested_proof - .revealed_attr_groups - .insert(referent.to_string(), group_info); + + let valid_credential = schema.has_attribute(attribute) + && check_conditions_for_w3c_credential( + credential, + presentation_request, + restrictions, + schemas, + cred_defs, + nonrevoke_interval, + nonrevoke_interval_override, + ) + .is_ok(); + + if valid_credential { + found_credential = Some(credential); + break; } - for referent in proof.mapping.unrevealed_attributes.iter() { - requested_proof - .unrevealed_attrs - .insert(referent.to_string(), SubProofReferent { sub_proof_index }); + } + + if let Some(found_credential) = found_credential { + // credential for attribute is found in revealed data + return Ok(found_credential); + } + + Err(err_msg!( + "Presentation does not contain attribute {}", + attribute + )) +} + +fn check_request_data_for_presentation( + presentation_request: &PresentationRequestPayload, + presentation: &Presentation, + schemas: &HashMap, + cred_defs: &HashMap, +) -> Result<()> { + // These values are from the prover and cannot be trusted + let received_revealed_attrs: HashMap = + received_revealed_attrs(presentation)?; + let received_unrevealed_attrs: HashMap = + received_unrevealed_attrs(presentation)?; + let received_predicates: HashMap = received_predicates(presentation)?; + let received_self_attested_attrs: HashSet = received_self_attested_attrs(presentation); + + // Ensures that all attributes in the request is also in the presentation + compare_attr_from_proof_and_request( + presentation_request, + &received_revealed_attrs, + &received_unrevealed_attrs, + &received_self_attested_attrs, + &received_predicates, + )?; + + // Ensures the encoded values are same as request + verify_revealed_attribute_values(presentation_request, presentation)?; + + // Ensures the restrictions set out in the request is met + verify_requested_restrictions( + presentation_request, + schemas, + cred_defs, + &presentation.requested_proof, + &received_revealed_attrs, + &received_unrevealed_attrs, + &received_predicates, + &received_self_attested_attrs, + )?; + + Ok(()) +} + +fn check_request_data_for_w3c_presentation( + presentation_request: &PresentationRequestPayload, + presentation: &W3CPresentation, + schemas: &HashMap, + cred_defs: &HashMap, + nonrevoke_interval_override: Option< + &HashMap>, + >, +) -> Result<()> { + for (_, attribute) in presentation_request.requested_attributes.iter() { + if let Some(ref name) = attribute.name { + check_requested_attribute_for_w3c_presentation( + presentation_request, + presentation, + name, + attribute.restrictions.as_ref(), + attribute.non_revoked.as_ref(), + schemas, + cred_defs, + nonrevoke_interval_override, + )?; } - for referent in proof.mapping.predicates.iter() { - requested_proof - .predicates - .insert(referent.to_string(), SubProofReferent { sub_proof_index }); + if let Some(ref names) = attribute.names { + for name in names { + check_requested_attribute_for_w3c_presentation( + presentation_request, + presentation, + name, + attribute.restrictions.as_ref(), + attribute.non_revoked.as_ref(), + schemas, + cred_defs, + nonrevoke_interval_override, + )?; + } } } - Ok(requested_proof) + for (_, predicate) in presentation_request.requested_predicates.iter() { + check_requested_attribute_for_w3c_presentation( + presentation_request, + presentation, + &predicate.name, + predicate.restrictions.as_ref(), + predicate.non_revoked.as_ref(), + schemas, + cred_defs, + nonrevoke_interval_override, + )?; + } + Ok(()) } fn build_revocation_registry_map( @@ -963,7 +1115,7 @@ struct CLProofVerifier<'a> { cred_defs: &'a HashMap, rev_reg_defs: Option<&'a HashMap>, revocation_map: - Option>>, + Option>>, } impl<'a> CLProofVerifier<'a> { @@ -975,7 +1127,7 @@ impl<'a> CLProofVerifier<'a> { &'a HashMap, >, rev_status_lists: Option<&'a Vec>, - ) -> Result> { + ) -> Result { let proof_verifier = Verifier::new_proof_verifier()?; let non_credential_schema = build_non_credential_schema()?; let revocation_map = build_revocation_registry_map(rev_status_lists)?; @@ -993,15 +1145,12 @@ impl<'a> CLProofVerifier<'a> { #[allow(clippy::too_many_arguments)] fn add_sub_proof( &mut self, - attributes: &HashSet, - predicates: &HashSet, + attributes: &[AttributeInfo], + predicates: &[PredicateInfo], schema_id: &SchemaId, cred_def_id: &CredentialDefinitionId, rev_reg_def_id: Option<&RevocationRegistryDefinitionId>, timestamp: Option, - nonrevoke_interval_override: Option< - &HashMap>, - >, ) -> Result<()> { let schema = self.get_schema(schema_id)?; let cred_def = self.get_credential_definition(cred_def_id)?; @@ -1012,38 +1161,7 @@ impl<'a> CLProofVerifier<'a> { &cred_def.value.primary, cred_def.value.revocation.as_ref(), )?; - - let (attrs_for_credential, attrs_nonrevoked_interval) = self - .presentation_request - .get_requested_attributes(attributes)?; - let (predicates_for_credential, pred_nonrevoked_interval) = self - .presentation_request - .get_requested_predicates(predicates)?; - - let cred_nonrevoked_interval = get_non_revoked_interval( - attrs_nonrevoked_interval, - pred_nonrevoked_interval, - self.presentation_request, - rev_reg_def_id, - nonrevoke_interval_override, - ); - - if let (Some(_), true) = ( - cred_def.value.revocation.as_ref(), - cred_nonrevoked_interval.is_some(), - ) { - let timestamp = timestamp - .ok_or_else(|| err_msg!("Identifier timestamp not found for revocation check"))?; - - // Validate timestamp - cred_nonrevoked_interval - .map(|int| int.is_valid(timestamp)) - .transpose()?; - }; - - let sub_pres_request = - build_sub_proof_request(&attrs_for_credential, &predicates_for_credential)?; - + let sub_pres_request = build_sub_proof_request(attributes, predicates)?; let rev_key_pub = rev_reg_def.map(|d| &d.value.public_keys.accum_key); self.proof_verifier.add_sub_proof_request( &sub_pres_request, @@ -1090,41 +1208,46 @@ impl<'a> CLProofVerifier<'a> { Option<&'a RevocationRegistryDefinition>, Option<&'a RevocationRegistry>, )> { - let (rev_reg_def, rev_reg) = - if let (Some(rev_reg_id), Some(timestamp)) = (rev_reg_id, timestamp) { - let rev_reg_defs = self.rev_reg_defs.ok_or_else(|| { - err_msg!("Could not load the Revocation Registry Definitions mapping") - })?; - - let rev_reg_map = self - .revocation_map - .as_ref() - .ok_or_else(|| err_msg!("Could not load the Revocation Registry mapping"))?; - - let rev_reg_def = Some(rev_reg_defs.get(rev_reg_id).ok_or_else(|| { - err_msg!( + let (rev_reg_def, rev_reg) = if rev_reg_id.is_some() { + let timestamp = timestamp + .ok_or_else(|| err_msg!("Identifier timestamp not found for revocation check"))?; + + let rev_reg_defs = self.rev_reg_defs.ok_or_else(|| { + err_msg!("Could not load the Revocation Registry Definitions mapping") + })?; + + let rev_reg_map = self + .revocation_map + .as_ref() + .ok_or_else(|| err_msg!("Could not load the Revocation Registry mapping"))?; + + let rev_reg_id = rev_reg_id + .ok_or_else(|| err_msg!("Revocation Registry Id not found for revocation check"))?; + + let rev_reg_def = Some(rev_reg_defs.get(rev_reg_id).ok_or_else(|| { + err_msg!( "Revocation Registry Definition not provided for ID: {:?}", rev_reg_id ) - })?); - - let rev_reg = Some( - rev_reg_map - .get(rev_reg_id) - .and_then(|regs| regs.get(×tamp)) - .ok_or_else(|| { - err_msg!( + })?); + + let rev_reg = Some( + rev_reg_map + .get(rev_reg_id) + .and_then(|regs| regs.get(×tamp)) + .ok_or_else(|| { + err_msg!( "Revocation Registry not provided for ID and timestamp: {:?}, {:?}", rev_reg_id, timestamp ) - })?, - ); + })?, + ); - (rev_reg_def, rev_reg) - } else { - (None, None) - }; + (rev_reg_def, rev_reg) + } else { + (None, None) + }; Ok((rev_reg_def, rev_reg)) } diff --git a/tests/anoncreds_demos.rs b/tests/anoncreds_demos.rs index b3f6e589..a91cb609 100644 --- a/tests/anoncreds_demos.rs +++ b/tests/anoncreds_demos.rs @@ -3648,7 +3648,7 @@ fn anoncreds_demo_works_for_issue_w3c_credential_add_identity_proof_present_w3c_ } #[test] -fn anoncreds_demo_works_for_issue_w3c_credential_and_present_w3c_presentation_for_restrictions() { +fn noncredit_demo_works_for_issue_w3c_credential_and_present_w3c_presentation_for_case_incentive_attributes() { // Create Prover pseudo wallet and link secret let mut prover_wallet = ProverWallet::default();