diff --git a/CredentialProvider/wasm/matcher-rs/src/bindings.rs b/CredentialProvider/wasm/matcher-rs/src/bindings.rs index c45f023..b0f8e40 100644 --- a/CredentialProvider/wasm/matcher-rs/src/bindings.rs +++ b/CredentialProvider/wasm/matcher-rs/src/bindings.rs @@ -35,6 +35,12 @@ unsafe extern "C" { set_id: *const c_char, set_index: i32, ); + pub fn SetDelegationTypeForEntryInSet( + cred_id: *const c_char, + delegation_type: i32, + set_id: *const c_char, + set_index: i32, + ); pub fn AddFieldToEntrySet( cred_id: *const c_char, field_display_name: *const c_char, diff --git a/CredentialProvider/wasm/matcher-rs/src/credman.rs b/CredentialProvider/wasm/matcher-rs/src/credman.rs index cb259a0..12aad7c 100644 --- a/CredentialProvider/wasm/matcher-rs/src/credman.rs +++ b/CredentialProvider/wasm/matcher-rs/src/credman.rs @@ -5,6 +5,7 @@ use crate::bindings::{ AddMetadataDisplayTextToEntrySet, AddPaymentEntryToSetV2, AddStringIdEntry, GetCredentialsSize, GetRequestBuffer, GetRequestSize, GetWasmVersion, ReadCredentialsBuffer, SelfDeclarePackageInfo, }; +use crate::openid4vp_models::DelegationType; pub trait CredmanApi { fn get_request_buffer(&self) -> Vec; @@ -40,6 +41,7 @@ pub trait CredmanApi { metadata: &str, set_id: &str, set_index: i32, + delegation_type: DelegationType, ); fn add_field_to_entry_set( &mut self, @@ -63,6 +65,7 @@ pub trait CredmanApi { metadata: &str, set_id: &str, set_index: i32, + delegation_type: DelegationType, ); fn add_inline_issuance_entry( &mut self, @@ -238,6 +241,7 @@ impl CredmanApi for CredmanApiImpl { metadata: &str, set_id: &str, set_index: i32, + delegation_type: DelegationType, ) { let cred_id_c = CString::new(cred_id).unwrap(); let title_c = if title.is_empty() { @@ -289,6 +293,14 @@ impl CredmanApi for CredmanApiImpl { set_id_c.as_ptr(), set_index, ); + if self.get_wasm_version() >= 7 && delegation_type != DelegationType::None { + crate::bindings::SetDelegationTypeForEntryInSet( + cred_id_c.as_ptr(), + delegation_type as i32, + set_id_c.as_ptr(), + set_index, + ); + } } } fn add_field_to_entry_set( @@ -334,6 +346,7 @@ impl CredmanApi for CredmanApiImpl { metadata: &str, set_id: &str, set_index: i32, + delegation_type: DelegationType, ) { let cred_id_c = CString::new(cred_id).unwrap(); let merchant_name_c = if merchant_name.is_empty() { @@ -415,6 +428,14 @@ impl CredmanApi for CredmanApiImpl { set_id_c.as_ptr(), set_index, ); + if self.get_wasm_version() >= 7 && delegation_type != DelegationType::None { + crate::bindings::SetDelegationTypeForEntryInSet( + cred_id_c.as_ptr(), + delegation_type as i32, + set_id_c.as_ptr(), + set_index, + ); + } } } fn add_inline_issuance_entry( diff --git a/CredentialProvider/wasm/matcher-rs/src/dcql.rs b/CredentialProvider/wasm/matcher-rs/src/dcql.rs index 8ca7feb..925b034 100644 --- a/CredentialProvider/wasm/matcher-rs/src/dcql.rs +++ b/CredentialProvider/wasm/matcher-rs/src/dcql.rs @@ -164,6 +164,7 @@ fn match_candidate_claims<'a>( add_all_claims(&mut matched_claim_names, &candidate.paths); return Some(MatchedCredential { id: &candidate.id, + delegation_type: candidate.delegation_type, display: &candidate.display, matched_claim_names, matched_claim_metadata: Vec::new(), @@ -216,6 +217,7 @@ fn match_candidate_claims<'a>( ); Some(MatchedCredential { id: &candidate.id, + delegation_type: candidate.delegation_type, display: &candidate.display, matched_claim_names: current_set_names, matched_claim_metadata: current_set_metadata, @@ -256,6 +258,7 @@ fn match_candidate_claims<'a>( log::debug!("Candidate {}: all claims matched", candidate.id); Some(MatchedCredential { id: &candidate.id, + delegation_type: candidate.delegation_type, display: &candidate.display, matched_claim_names, matched_claim_metadata, @@ -587,6 +590,7 @@ mod tests { ); creds.push(RegistryCredential { id: "mdoc_cred_1".to_string(), + delegation_type: DelegationType::None, display: RegistryDisplay::default(), paths, }); @@ -629,6 +633,7 @@ mod tests { ); creds.push(RegistryCredential { id: "mdoc_cred_1".to_string(), + delegation_type: DelegationType::None, display: RegistryDisplay::default(), paths, }); @@ -674,6 +679,7 @@ mod tests { ); creds.push(RegistryCredential { id: "mdoc_cred_1".to_string(), + delegation_type: DelegationType::None, display: RegistryDisplay::default(), paths, }); @@ -709,6 +715,7 @@ mod tests { let mut creds = Vec::new(); creds.push(RegistryCredential { id: "mdoc_cred_1".to_string(), + delegation_type: DelegationType::None, display: RegistryDisplay::default(), paths: DeterministicMap::new(), }); @@ -863,6 +870,7 @@ mod tests { RegistryCredential { id: id.to_string(), + delegation_type: DelegationType::None, display: RegistryDisplay::default(), paths, } @@ -950,6 +958,7 @@ mod tests { let cred1 = RegistryCredential { id: "cred1".to_string(), + delegation_type: DelegationType::None, display: RegistryDisplay::default(), paths, }; diff --git a/CredentialProvider/wasm/matcher-rs/src/issuance.rs b/CredentialProvider/wasm/matcher-rs/src/issuance.rs index 46abe1c..0dbcc80 100644 --- a/CredentialProvider/wasm/matcher-rs/src/issuance.rs +++ b/CredentialProvider/wasm/matcher-rs/src/issuance.rs @@ -287,6 +287,7 @@ mod test { _metadata: &str, _set_id: &str, _set_index: i32, + _delegation_type: crate::openid4vp_models::DelegationType, ) { } fn add_field_to_entry_set( @@ -312,6 +313,7 @@ mod test { _metadata: &str, _set_id: &str, _set_index: i32, + _delegation_type: crate::openid4vp_models::DelegationType, ) { } fn add_inline_issuance_entry( @@ -429,7 +431,7 @@ mod test { } } ] -"#, +}"#, registered_json: r#" { "entry_id": "C", diff --git a/CredentialProvider/wasm/matcher-rs/src/openid4vp_models.rs b/CredentialProvider/wasm/matcher-rs/src/openid4vp_models.rs index e822854..68fb708 100644 --- a/CredentialProvider/wasm/matcher-rs/src/openid4vp_models.rs +++ b/CredentialProvider/wasm/matcher-rs/src/openid4vp_models.rs @@ -58,10 +58,38 @@ pub struct RegistryCredentials { pub issuance: Option, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +#[repr(i32)] +pub enum DelegationType { + #[default] + None = 0, + Full = 1, +} + +impl From for DelegationType { + fn from(val: i32) -> Self { + match val { + 1 => DelegationType::Full, + _ => DelegationType::None, + } + } +} + +impl nanoserde::DeJson for DelegationType { + fn de_json( + state: &mut nanoserde::DeJsonState, + input: &mut std::str::Chars, + ) -> Result { + let val = i32::de_json(state, input)?; + Ok(DelegationType::from(val)) + } +} + #[derive(DeJson, Debug, Clone, Default)] #[nserde(default)] pub struct RegistryCredential { pub id: String, + pub delegation_type: DelegationType, pub display: RegistryDisplay, pub paths: DeterministicMap, // Recursive structure } @@ -120,6 +148,7 @@ pub struct MatchedClaim<'a> { #[derive(Debug, Clone)] pub struct MatchedCredential<'a> { pub id: &'a str, + pub delegation_type: DelegationType, pub display: &'a RegistryDisplay, pub matched_claim_names: Vec<&'a JsonValue>, // RegistryClaimDisplay pub matched_claim_metadata: Vec<&'a [String]>, @@ -436,4 +465,19 @@ mod tests { // Validate that the string can be parsed into JsonValue let _: JsonValue = DeJson::deserialize_json(&json).expect("Serialized JSON is invalid"); } + + #[test] + fn test_parse_delegation_type() { + let json_none = r#"{"id":"c1","delegation_type":0,"display":{"verification":{"title":"","subtitle":"","explainer":"","warning":"","metadata_display_text":""}},"paths":{}}"#; + let cred_none: RegistryCredential = DeJson::deserialize_json(json_none).unwrap(); + assert_eq!(cred_none.delegation_type, DelegationType::None); + + let json_full = r#"{"id":"c2","delegation_type":1,"display":{"verification":{"title":"","subtitle":"","explainer":"","warning":"","metadata_display_text":""}},"paths":{}}"#; + let cred_full: RegistryCredential = DeJson::deserialize_json(json_full).unwrap(); + assert_eq!(cred_full.delegation_type, DelegationType::Full); + + let json_default = r#"{"id":"c3","display":{"verification":{"title":"","subtitle":"","explainer":"","warning":"","metadata_display_text":""}},"paths":{}}"#; + let cred_default: RegistryCredential = DeJson::deserialize_json(json_default).unwrap(); + assert_eq!(cred_default.delegation_type, DelegationType::None); + } } diff --git a/CredentialProvider/wasm/matcher-rs/src/reporter.rs b/CredentialProvider/wasm/matcher-rs/src/reporter.rs index 853dd43..333cc52 100644 --- a/CredentialProvider/wasm/matcher-rs/src/reporter.rs +++ b/CredentialProvider/wasm/matcher-rs/src/reporter.rs @@ -41,6 +41,7 @@ fn report_payment_transaction_entry( selection_metadata_json, credential_set_id, document_index, + matched_credential.delegation_type, ); Ok(()) } @@ -77,6 +78,7 @@ fn report_standard_verification_entry( selection_metadata_json, credential_set_id, document_index, + matched_credential.delegation_type, ); log::trace!( @@ -405,6 +407,7 @@ mod tests { struct MockCredman { wasm_version: u32, added_entries: Vec, + added_delegations: Vec<(String, DelegationType)>, added_fields: Vec<(String, String, String)>, payment_entries: Vec, inline_entries: Vec, @@ -455,8 +458,10 @@ mod tests { _metadata: &str, _set_id: &str, _set_index: i32, + delegation_type: DelegationType, ) { self.added_entries.push(cred_id.to_string()); + self.added_delegations.push((cred_id.to_string(), delegation_type)); } fn add_field_to_entry_set( &mut self, @@ -486,8 +491,10 @@ mod tests { _metadata: &str, _set_id: &str, _set_index: i32, + delegation_type: DelegationType, ) { self.payment_entries.push(cred_id.to_string()); + self.added_delegations.push((cred_id.to_string(), delegation_type)); } fn add_inline_issuance_entry( &mut self, @@ -519,6 +526,7 @@ mod tests { let mut mock = MockCredman { wasm_version: 1, added_entries: vec![], + added_delegations: vec![], added_fields: vec![], payment_entries: vec![], inline_entries: vec![], @@ -541,6 +549,7 @@ mod tests { let mut mock = MockCredman { wasm_version: 1, added_entries: vec![], + added_delegations: vec![], added_fields: vec![], payment_entries: vec![], inline_entries: vec![], @@ -565,6 +574,7 @@ mod tests { id: "cred1", matched: vec![MatchedCredential { id: "cred1", + delegation_type: DelegationType::None, display: &display, matched_claim_names: vec![], matched_claim_metadata: vec![], @@ -586,6 +596,128 @@ mod tests { report_match_result(&mut mock, &match_result, 0, &openid4vp_data, &[]).unwrap(); assert_eq!(mock.added_entries, vec!["cred1"]); + assert_eq!(mock.added_delegations, vec![("cred1".to_string(), DelegationType::None)]); + } + + #[test] + fn test_report_standard_entry_with_delegation_full() { + let mut mock = MockCredman { + wasm_version: 7, + added_entries: vec![], + added_delegations: vec![], + added_fields: vec![], + payment_entries: vec![], + inline_entries: vec![], + added_entry_sets: vec![], + }; + + let display = RegistryDisplay { + verification: RegistryVerification { + title: "Delegated Cred".to_string(), + subtitle: "".to_string(), + explainer: "".to_string(), + warning: "".to_string(), + metadata_display_text: "".to_string(), + icon: None, + }, + }; + + let mut matched_credentials = DeterministicMap::new(); + matched_credentials.insert( + "cred_full", + DcqlMatchedCredentialEntry { + id: "cred_full", + matched: vec![MatchedCredential { + id: "cred_full", + delegation_type: DelegationType::Full, + display: &display, + matched_claim_names: vec![], + matched_claim_metadata: vec![], + }], + }, + ); + + let match_result = DcqlMatchResult { + matched_credential_sets: vec![vec![MatchedCredentialSetInfo { + set_id: Cow::Borrowed("set1"), + option_id: Cow::Borrowed("opt1"), + matched_credential_ids: vec!["cred_full"], + }]], + matched_credentials, + inline_issuance: None, + }; + let openid4vp_data = OpenId4VpData::default(); + + report_match_result(&mut mock, &match_result, 0, &openid4vp_data, &[]).unwrap(); + + assert_eq!(mock.added_entries, vec!["cred_full"]); + assert_eq!( + mock.added_delegations, + vec![("cred_full".to_string(), DelegationType::Full)] + ); + } + + #[test] + fn test_report_payment_entry_with_delegation_full() { + let mut mock = MockCredman { + wasm_version: 7, + added_entries: vec![], + added_delegations: vec![], + added_fields: vec![], + payment_entries: vec![], + inline_entries: vec![], + added_entry_sets: vec![], + }; + + let display = RegistryDisplay { + verification: RegistryVerification { + title: "Payment Card".to_string(), + subtitle: "".to_string(), + explainer: "".to_string(), + warning: "".to_string(), + metadata_display_text: "".to_string(), + icon: None, + }, + }; + + let mut matched_credentials = DeterministicMap::new(); + matched_credentials.insert( + "cred_pay", + DcqlMatchedCredentialEntry { + id: "cred_pay", + matched: vec![MatchedCredential { + id: "cred_pay", + delegation_type: DelegationType::Full, + display: &display, + matched_claim_names: vec![], + matched_claim_metadata: vec![], + }], + }, + ); + + let match_result = DcqlMatchResult { + matched_credential_sets: vec![vec![MatchedCredentialSetInfo { + set_id: Cow::Borrowed("set1"), + option_id: Cow::Borrowed("opt1"), + matched_credential_ids: vec!["cred_pay"], + }]], + matched_credentials, + inline_issuance: None, + }; + + let td_b64 = "eyJ0eXBlIjoidXJuOmV1ZGk6c2NhOnBheW1lbnQ6MSIsInBheWxvYWQiOnsicGF5ZWUiOnsibmFtZSI6Ik1lcmNoYW50IFgifSwiYW1vdW50X2Rpc3BsYXkiOiJFVVIgNTAuMDAifSwiY3JlZGVudGlhbF9pZHMiOlsibWRsIiwiY3JlZF9wYXkiXX0"; + let openid4vp_data = OpenId4VpData { + transaction_data: vec![td_b64.to_string()], + ..Default::default() + }; + + report_match_result(&mut mock, &match_result, 0, &openid4vp_data, &[]).unwrap(); + + assert_eq!(mock.payment_entries, vec!["cred_pay"]); + assert_eq!( + mock.added_delegations, + vec![("cred_pay".to_string(), DelegationType::Full)] + ); } #[test] @@ -593,6 +725,7 @@ mod tests { let mut mock = MockCredman { wasm_version: 1, added_entries: vec![], + added_delegations: vec![], added_fields: vec![], payment_entries: vec![], inline_entries: vec![], @@ -624,6 +757,7 @@ mod tests { let mut mock = MockCredman { wasm_version: 2, added_entries: vec![], + added_delegations: vec![], added_fields: vec![], payment_entries: vec![], inline_entries: vec![], @@ -676,6 +810,7 @@ mod tests { id, matched: vec![MatchedCredential { id, + delegation_type: DelegationType::None, display: &display, matched_claim_names: vec![], matched_claim_metadata: vec![], diff --git a/CredentialProvider/wasm/matcher-rs/src/test_utils.rs b/CredentialProvider/wasm/matcher-rs/src/test_utils.rs index f7ab724..38258c1 100644 --- a/CredentialProvider/wasm/matcher-rs/src/test_utils.rs +++ b/CredentialProvider/wasm/matcher-rs/src/test_utils.rs @@ -164,6 +164,7 @@ impl CredmanApi for FakeCredman { _metadata: &str, set_id: &str, set_index: i32, + _delegation_type: crate::openid4vp_models::DelegationType, ) { let s_id = set_id.to_string(); let c_id = cred_id.to_string(); @@ -225,6 +226,7 @@ impl CredmanApi for FakeCredman { _metadata: &str, set_id: &str, set_index: i32, + _delegation_type: crate::openid4vp_models::DelegationType, ) { let s_id = set_id.to_string(); let c_id = cred_id.to_string();