From 7dab12e408fdb0731b303bf1ccece4ce8a571d2d Mon Sep 17 00:00:00 2001 From: Daniel Kim Date: Thu, 17 Sep 2026 01:08:52 +0000 Subject: [PATCH] Support partial matching in DCQL and delegation for PNV matcher - Allow partial credential matching across credential sets in DCQL for PNV matcher - Define SetDelegationTypeForEntryInSet in credentialmanager.h - Forward delegation_type from candidate credentials through dcql matching - Set delegation (type 1) when wasm_version >= 7 and candidate delegation_type is FULL (1) without user verification - Suppress partial matches when user verification is requested, delegation is unsupported (wasm_version < 7), or candidate delegation_type is not FULL (1) - Match user_verification_hint claim path inside dcql_query claims to detect UV request - Modernize PNV test framework and add unit test coverage for hint matching, full match delegation, UV suppression, and partial match filtering - Update pnv.wasm asset used by CMWallet - Add pnv_test and test_pnv targets to matcher/Makefile --- app/src/main/assets/pnv.wasm | Bin 68787 -> 71660 bytes matcher/Makefile | 33 +- matcher/credentialmanager.h | 5 + matcher/pnv/dcql.c | 35 ++- matcher/pnv/openid4vp1_0.c | 199 +++++++++++-- matcher/pnv/test/common.cpp | 235 ++++++++++----- matcher/pnv/test/common.hpp | 43 +-- matcher/pnv/test/openid4vp1_0_test.cpp | 398 ++++++++++++++++++++++--- matcher/test_runner.cc | 1 + 9 files changed, 779 insertions(+), 170 deletions(-) diff --git a/app/src/main/assets/pnv.wasm b/app/src/main/assets/pnv.wasm index 7d52c746683338fd037ba535e2339652b21d97c7..4dab36ef71212848912b1ed01f4464707dc6e24e 100755 GIT binary patch delta 18290 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 E2M(8gLjV8( delta 15442 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 B6*~X` diff --git a/matcher/Makefile b/matcher/Makefile index 05d81ef..33fb888 100644 --- a/matcher/Makefile +++ b/matcher/Makefile @@ -1,7 +1,7 @@ -CXX = g++ -CC = gcc -CXXFLAGS = -std=c++17 -Wall -Wextra -g -I. -I/usr/include/doctest -I/usr/include/nlohmann -CFLAGS = -Wall -Wextra -g -I. -Wno-unused-variable -Wno-unused-but-set-variable -Wno-format-overflow +CXX ?= g++ +CC ?= gcc +CXXFLAGS ?= -std=c++17 -Wall -Wextra -g -I. -I/usr/include/doctest -I/usr/include/nlohmann +CFLAGS ?= -Wall -Wextra -g -I. -Wno-unused-variable -Wno-unused-but-set-variable -Wno-format-overflow # Common objects COMMON_OBJS = dcql.o openid4vp1_0.o base64.o cJSON/cJSON.o issuance/provision.o @@ -13,6 +13,27 @@ test_runner: test_runner.o dcql.o openid4vp1_0.o base64.o cJSON/cJSON.o test: test_runner ./test_runner +# PNV tests +PNV_OBJS = pnv/dcql.o pnv/openid4vp1_0.o base64.o cJSON/cJSON.o + +pnv_test: pnv/test/openid4vp1_0_test.o pnv/test/common.o $(PNV_OBJS) + $(CXX) $(CXXFLAGS) -o $@ $^ + +pnv/test/openid4vp1_0_test.o: pnv/test/openid4vp1_0_test.cpp + $(CXX) $(CXXFLAGS) -c $< -o $@ + +pnv/test/common.o: pnv/test/common.cpp + $(CXX) $(CXXFLAGS) -c $< -o $@ + +pnv/dcql.o: pnv/dcql.c + $(CC) $(CFLAGS) -c $< -o $@ + +pnv/openid4vp1_0.o: pnv/openid4vp1_0.c + $(CC) $(CFLAGS) -c $< -o $@ + +test_pnv: pnv_test + ./pnv_test + test_runner.o: test_runner.cc $(CXX) $(CXXFLAGS) -c $< -o $@ @@ -32,6 +53,6 @@ issuance/provision.o: issuance/provision.c $(CC) $(CFLAGS) -c $< -o $@ clean: - rm -f *.o cJSON/*.o issuance/*.o test_runner + rm -f *.o cJSON/*.o issuance/*.o pnv/*.o pnv/test/*.o test_runner pnv_test -.PHONY: test clean +.PHONY: test test_pnv clean diff --git a/matcher/credentialmanager.h b/matcher/credentialmanager.h index 9d34d58..06a2c2f 100644 --- a/matcher/credentialmanager.h +++ b/matcher/credentialmanager.h @@ -125,6 +125,11 @@ __attribute__((import_module("credman_v5"), import_name("AddMetadataDisplayTextT #endif void AddMetadataDisplayTextToEntrySet(const char *cred_id, const char *metadata_display_text, const char *set_id, int set_index); +#if defined(__wasm__) +__attribute__((import_module("credman_v7"), import_name("SetDelegationTypeForEntryInSet"))) +#endif +void SetDelegationTypeForEntryInSet(const char* cred_id, int delegation_type, const char* set_id, int set_index); + #ifdef __cplusplus } #endif diff --git a/matcher/pnv/dcql.c b/matcher/pnv/dcql.c index 22159e2..9249ae1 100644 --- a/matcher/pnv/dcql.c +++ b/matcher/pnv/dcql.c @@ -177,6 +177,7 @@ MatchCredential(cJSON *credential, cJSON *credential_store) cJSON_AddItemReferenceToObject(matched_credential, "aggregator_consent", aggregator_consent); cJSON_AddItemReferenceToObject(matched_credential, "aggregator_policy_text", aggregator_policy_text); cJSON_AddItemReferenceToObject(matched_credential, "aggregator_policy_url", aggregator_policy_url); + cJSON_AddItemReferenceToObject(matched_credential, "delegation_type", cJSON_GetObjectItemCaseSensitive(candidate, "delegation_type")); cJSON *matched_claim_names = cJSON_CreateArray(); // printf("candidate %s\n", cJSON_Print(candidate)); cJSON_AddItemReferenceToArray(matched_claim_names, cJSON_GetObjectItemCaseSensitive(candidate, "shared_attribute_display_name")); @@ -217,6 +218,7 @@ MatchCredential(cJSON *credential, cJSON *credential_store) cJSON_AddItemReferenceToObject(matched_credential, "aggregator_consent", aggregator_consent); cJSON_AddItemReferenceToObject(matched_credential, "aggregator_policy_text", aggregator_policy_text); cJSON_AddItemReferenceToObject(matched_credential, "aggregator_policy_url", aggregator_policy_url); + cJSON_AddItemReferenceToObject(matched_credential, "delegation_type", cJSON_GetObjectItemCaseSensitive(candidate, "delegation_type")); cJSON *matched_claim_names = cJSON_CreateArray(); cJSON_AddItemReferenceToArray(matched_claim_names, cJSON_GetObjectItemCaseSensitive(candidate, "shared_attribute_display_name")); @@ -411,7 +413,6 @@ cJSON *dcql_query(cJSON *query, cJSON *credential_store) cJSON_ArrayForEach(matched_credential, credentials) { cJSON_AddItemReferenceToArray(matched_cred_ids, cJSON_GetObjectItemCaseSensitive(matched_credential, "id")); } - char set_id_buffer[16]; cJSON_AddItemReferenceToObject(single_matched_credential_set, "matched_credential_ids", matched_cred_ids); cJSON* curr_matched_credential_sets = cJSON_CreateArray(); // For consistency with the credential_sets case cJSON_AddItemReferenceToArray(curr_matched_credential_sets, single_matched_credential_set); @@ -440,29 +441,37 @@ cJSON *dcql_query(cJSON *query, cJSON *credential_store) cJSON* curr_matched_credential_sets = cJSON_CreateArray(); cJSON* options = cJSON_GetObjectItemCaseSensitive(credential_set, "options"); cJSON* option; - int credential_set_matched = 0; int option_idx = 0; cJSON_ArrayForEach(option, options) { cJSON* matched_cred_ids = cJSON_CreateArray(); + cJSON* matched_indices = cJSON_CreateArray(); cJSON* cred_id; - credential_set_matched = 1; + int cred_idx = 0; cJSON_ArrayForEach(cred_id, option) { - if (cJSON_GetObjectItemCaseSensitive(candidate_matched_credentials, cJSON_GetStringValue(cred_id)) == NULL) { - credential_set_matched = 0; - break; - } // Remove for multi-provider support - cJSON_AddItemReferenceToArray(matched_cred_ids, cred_id); + if (cJSON_GetObjectItemCaseSensitive(candidate_matched_credentials, cJSON_GetStringValue(cred_id)) != NULL) { + cJSON_AddItemReferenceToArray(matched_cred_ids, cred_id); + cJSON_AddItemToArray(matched_indices, cJSON_CreateNumber(cred_idx)); + } + ++cred_idx; } - if (credential_set_matched != 0) { + int option_length = cJSON_GetArraySize(option); + int matched_count = cJSON_GetArraySize(matched_cred_ids); + if (matched_count > 0) { cJSON* cred_set_info = cJSON_CreateObject(); - char set_id_buffer[4]; - char option_id_buffer[4]; - int chars_written = sprintf(set_id_buffer, "%d", set_idx); - chars_written = sprintf(option_id_buffer, "%d", option_idx); + char set_id_buffer[16]; + char option_id_buffer[16]; + sprintf(set_id_buffer, "%d", set_idx); + sprintf(option_id_buffer, "%d", option_idx); cJSON_AddStringToObject(cred_set_info, "set_id", set_id_buffer); cJSON_AddStringToObject(cred_set_info, "option_id", option_id_buffer); cJSON_AddItemReferenceToObject(cred_set_info, "matched_credential_ids", matched_cred_ids); + cJSON_AddNumberToObject(cred_set_info, "option_length", option_length); + cJSON_AddItemToObject(cred_set_info, "matched_indices", matched_indices); + cJSON_AddBoolToObject(cred_set_info, "is_partial", matched_count < option_length); cJSON_AddItemReferenceToArray(curr_matched_credential_sets, cred_set_info); + } else { + cJSON_Delete(matched_cred_ids); + cJSON_Delete(matched_indices); } ++option_idx; } diff --git a/matcher/pnv/openid4vp1_0.c b/matcher/pnv/openid4vp1_0.c index 0b01f3a..c199465 100644 --- a/matcher/pnv/openid4vp1_0.c +++ b/matcher/pnv/openid4vp1_0.c @@ -38,8 +38,9 @@ void report_credential_set_length(char* set_id, int curr_length, int curr_set_id cJSON *matched_credential_set = cJSON_GetArrayItem(matched_credential_sets, curr_set_idx); cJSON *matched_option; cJSON_ArrayForEach(matched_option, matched_credential_set) { - cJSON *matched_credential_ids = cJSON_GetObjectItemCaseSensitive(matched_option, "matched_credential_ids"); - int option_size = cJSON_GetArraySize(matched_credential_ids); + int option_size = cJSON_HasObjectItem(matched_option, "option_length") ? + cJSON_GetObjectItem(matched_option, "option_length")->valueint : + cJSON_GetArraySize(cJSON_GetObjectItemCaseSensitive(matched_option, "matched_credential_ids")); report_credential_set_length(set_id, option_size + curr_length, curr_set_idx + 1, matched_credential_sets, credential_sets_length); } } else { @@ -47,7 +48,117 @@ void report_credential_set_length(char* set_id, int curr_length, int curr_set_id } } -void report_matched_credential(uint32_t wasm_version, cJSON* matched_doc, cJSON* matched_credential_id, int doc_idx, int request_id, char* set_id, char* dcql_set_idx, char* dcql_option_idx, char *creds_blob, cJSON* transaction_credential_ids, char* merchant_name, char* transaction_amount, char* additional_info) { +static int is_user_verification_requested(cJSON *data_json, cJSON *query, cJSON *matched_credential_id) { + if (data_json != NULL) { + cJSON *uv = cJSON_GetObjectItemCaseSensitive(data_json, "user_verification"); + if (uv != NULL) { + if (cJSON_IsString(uv) && uv->valuestring != NULL && strcmp(uv->valuestring, "discouraged") != 0) { + return 1; + } + if (cJSON_IsTrue(uv)) { + return 1; + } + } + } + + if (query != NULL) { + cJSON *credentials = cJSON_GetObjectItemCaseSensitive(query, "credentials"); + if (credentials != NULL && cJSON_IsArray(credentials)) { + cJSON *cred; + const char *matched_id_str = cJSON_IsString(matched_credential_id) ? matched_credential_id->valuestring : NULL; + cJSON_ArrayForEach(cred, credentials) { + cJSON *cred_id = cJSON_GetObjectItemCaseSensitive(cred, "id"); + if (matched_id_str != NULL && cred_id != NULL && cJSON_IsString(cred_id)) { + if (strcmp(cred_id->valuestring, matched_id_str) != 0) { + continue; + } + } + + cJSON *cred_uv = cJSON_GetObjectItemCaseSensitive(cred, "user_verification"); + if (cred_uv != NULL) { + if (cJSON_IsString(cred_uv) && cred_uv->valuestring != NULL && strcmp(cred_uv->valuestring, "discouraged") != 0) { + return 1; + } + if (cJSON_IsTrue(cred_uv)) { + return 1; + } + } + + cJSON *claims = cJSON_GetObjectItemCaseSensitive(cred, "claims"); + if (claims != NULL && cJSON_IsArray(claims)) { + cJSON *claim; + cJSON_ArrayForEach(claim, claims) { + cJSON *path = cJSON_GetObjectItemCaseSensitive(claim, "path"); + if (path != NULL) { + if (cJSON_IsArray(path)) { + cJSON *path_elem; + cJSON_ArrayForEach(path_elem, path) { + if (cJSON_IsString(path_elem) && path_elem->valuestring != NULL) { + if (strcmp(path_elem->valuestring, "user_verification_hint") == 0) { + return 1; + } + } + } + } else if (cJSON_IsString(path) && path->valuestring != NULL) { + if (strcmp(path->valuestring, "user_verification_hint") == 0) { + return 1; + } + } + } + } + } + } + } + } + + return 0; +} + +static int is_option_suppressed(cJSON *matched_option, cJSON *data_json, cJSON *query, uint32_t wasm_version, cJSON *matched_docs) { + int is_partial = cJSON_HasObjectItem(matched_option, "is_partial") && + cJSON_IsTrue(cJSON_GetObjectItem(matched_option, "is_partial")); + if (!is_partial) { + return 0; + } + // Partial matches must be suppressed if delegation is not supported (< v7), + // or if user verification is requested (delegation type would be NONE), + // or if any matched candidate's delegation type is not FULL (1). + if (wasm_version < 7) { + return 1; + } + cJSON *matched_cred_ids = cJSON_GetObjectItemCaseSensitive(matched_option, "matched_credential_ids"); + cJSON *matched_cred_id; + cJSON_ArrayForEach(matched_cred_id, matched_cred_ids) { + if (is_user_verification_requested(data_json, query, matched_cred_id)) { + return 1; + } + if (matched_docs != NULL) { + const char *id_str = cJSON_GetStringValue(matched_cred_id); + cJSON *matched_doc = cJSON_GetObjectItemCaseSensitive(matched_docs, id_str); + if (matched_doc == NULL) { + return 1; + } + cJSON *matched = cJSON_GetObjectItem(matched_doc, "matched"); + if (matched == NULL || cJSON_GetArraySize(matched) == 0) { + return 1; + } + cJSON *c; + cJSON_ArrayForEach(c, matched) { + cJSON *del_type = cJSON_GetObjectItemCaseSensitive(c, "delegation_type"); + int val = (del_type != NULL && cJSON_IsNumber(del_type)) ? del_type->valueint : 0; + if (val != 1) { + return 1; + } + } + } + } + return 0; +} + +void report_matched_credential(uint32_t wasm_version, cJSON* matched_doc, cJSON* matched_credential_id, int doc_idx, int request_id, char* set_id, char* dcql_set_idx, char* dcql_option_idx, char *creds_blob, cJSON* transaction_credential_ids, char* merchant_name, char* transaction_amount, char* additional_info, int is_partial, cJSON *data_json, cJSON *query) { + if (matched_doc == NULL) { + return; + } cJSON *matched_credential = cJSON_GetObjectItem(matched_doc, "matched"); cJSON *c; cJSON_ArrayForEach(c, matched_credential) @@ -79,12 +190,12 @@ void report_matched_credential(uint32_t wasm_version, cJSON* matched_doc, cJSON* // char *subtitle = cJSON_GetStringValue(cJSON_GetObjectItem(c_display, "subtitle")); // cJSON *icon = cJSON_GetObjectItem(c_display, "icon"); // printf("transaction cred ids %s\n", cJSON_Print(transaction_credential_ids)); - + // // double icon_start = (cJSON_GetNumberValue(cJSON_GetObjectItem(icon, "start"))); // int icon_start_int = icon_start; // printf("icon_start int %d, double %f\n", icon_start_int, icon_start); // int icon_len = (int)(cJSON_GetNumberValue(cJSON_GetObjectItem(icon, "length"))); - + // // if (wasm_version >= 3) // { // AddPaymentEntryToSetV2(matched_id, merchant_name, title, subtitle, creds_blob + icon_start_int, icon_len, transaction_amount, NULL, 0, NULL, 0, additional_info, metadata, set_id, doc_idx); @@ -131,6 +242,13 @@ void report_matched_credential(uint32_t wasm_version, cJSON* matched_doc, cJSON* } printf("Adding entry with id: %s\n", matched_id); AddEntryToSet(matched_id, creds_blob + icon_start_int, icon_len, title, subtitle, disclaimer, NULL, metadata, set_id, doc_idx); + cJSON *del_type_item = cJSON_GetObjectItemCaseSensitive(c, "delegation_type"); + int entry_del_type = (del_type_item != NULL && cJSON_IsNumber(del_type_item)) ? del_type_item->valueint : 0; + int uv_requested = is_user_verification_requested(data_json, query, matched_credential_id); + int effective_del_type = uv_requested ? 0 : entry_del_type; + if (wasm_version >= 7 && effective_del_type == 1) { + SetDelegationTypeForEntryInSet(matched_id, 1, set_id, doc_idx); + } if (aggregator_consent != NULL && verifier_terms_prefix != NULL) { @@ -157,7 +275,7 @@ void report_matched_credential(uint32_t wasm_version, cJSON* matched_doc, cJSON* } } -void report_matched_credential_set(char* set_id, int curr_set_idx, cJSON *matched_credential_sets, int curr_doc_idx, int credential_sets_length, uint32_t wasm_version, cJSON* matched_docs, int request_id, char *creds_blob, cJSON* transaction_credential_ids, char* merchant_name, char* transaction_amount, char* additional_info) { +void report_matched_credential_set(char* set_id, int curr_set_idx, cJSON *matched_credential_sets, int curr_doc_idx, int credential_sets_length, uint32_t wasm_version, cJSON* matched_docs, int request_id, char *creds_blob, cJSON* transaction_credential_ids, char* merchant_name, char* transaction_amount, char* additional_info, cJSON *data_json, cJSON *query) { if (curr_set_idx < credential_sets_length) { cJSON *matched_credential_set = cJSON_GetArrayItem(matched_credential_sets, curr_set_idx); cJSON *matched_option; @@ -165,19 +283,25 @@ void report_matched_credential_set(char* set_id, int curr_set_idx, cJSON *matche cJSON *curr_matched_credential_ids = cJSON_GetObjectItemCaseSensitive(matched_option, "matched_credential_ids"); char *dcql_set_idx = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(matched_option, "set_id")); // TODO char *dcql_option_idx = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(matched_option, "option_id")); + int is_partial = cJSON_HasObjectItem(matched_option, "is_partial") && + cJSON_IsTrue(cJSON_GetObjectItem(matched_option, "is_partial")); + cJSON *matched_indices = cJSON_GetObjectItem(matched_option, "matched_indices"); cJSON *matched_doc; cJSON *matched_credential_id; - int new_doc_idx = curr_doc_idx; + int k = 0; cJSON_ArrayForEach(matched_credential_id, curr_matched_credential_ids) { + int doc_idx = curr_doc_idx + (matched_indices ? cJSON_GetArrayItem(matched_indices, k)->valueint : k); printf("matched_credential_id %s\n", cJSON_GetStringValue(matched_credential_id)); matched_doc = cJSON_GetObjectItemCaseSensitive(matched_docs, cJSON_GetStringValue(matched_credential_id)); - report_matched_credential(wasm_version, matched_doc, matched_credential_id, new_doc_idx, request_id, set_id, dcql_set_idx, dcql_option_idx, creds_blob, transaction_credential_ids, merchant_name, transaction_amount, additional_info); - ++new_doc_idx; + report_matched_credential(wasm_version, matched_doc, matched_credential_id, doc_idx, request_id, set_id, dcql_set_idx, dcql_option_idx, creds_blob, transaction_credential_ids, merchant_name, transaction_amount, additional_info, is_partial, data_json, query); + ++k; } - ++curr_set_idx; - report_matched_credential_set(set_id, curr_set_idx, matched_credential_sets, new_doc_idx, credential_sets_length, wasm_version, matched_docs, request_id, creds_blob, transaction_credential_ids, merchant_name, transaction_amount, additional_info); + int option_length = cJSON_HasObjectItem(matched_option, "option_length") ? + cJSON_GetObjectItem(matched_option, "option_length")->valueint : cJSON_GetArraySize(curr_matched_credential_ids); + int new_doc_idx = curr_doc_idx + option_length; + report_matched_credential_set(set_id, curr_set_idx + 1, matched_credential_sets, new_doc_idx, credential_sets_length, wasm_version, matched_docs, request_id, creds_blob, transaction_credential_ids, merchant_name, transaction_amount, additional_info, data_json, query); } } } @@ -320,33 +444,65 @@ int openid_main() // printf("matched_creds %d\n", cJSON_GetArraySize(matched_creds)); // printf("matched_creds %s\n", cJSON_Print(cJSON_GetArrayItem(matched_creds,0))); - int matched_credential_sets_size = cJSON_GetArraySize(matched_credential_sets); + cJSON *filtered_credential_sets = NULL; + if (matched_credential_sets != NULL) { + filtered_credential_sets = cJSON_CreateArray(); + int all_sets_matched = 1; + cJSON *matched_credential_set; + cJSON_ArrayForEach(matched_credential_set, matched_credential_sets) { + cJSON *filtered_set = cJSON_CreateArray(); + cJSON *matched_option; + cJSON_ArrayForEach(matched_option, matched_credential_set) { + if (!is_option_suppressed(matched_option, data_json, query, wasm_version, matched_docs)) { + cJSON_AddItemReferenceToArray(filtered_set, matched_option); + } + } + if (cJSON_GetArraySize(filtered_set) == 0) { + all_sets_matched = 0; + cJSON_Delete(filtered_set); + break; + } + cJSON_AddItemToArray(filtered_credential_sets, filtered_set); + } + if (!all_sets_matched) { + cJSON_Delete(filtered_credential_sets); + filtered_credential_sets = NULL; + } + } + + int matched_credential_sets_size = cJSON_GetArraySize(filtered_credential_sets); if (matched_credential_sets_size > 0) { // Some credential(s) matched - cJSON *first_matched_credential_set = cJSON_GetArrayItem(matched_credential_sets, 0); + cJSON *first_matched_credential_set = cJSON_GetArrayItem(filtered_credential_sets, 0); cJSON *matched_option; cJSON_ArrayForEach(matched_option, first_matched_credential_set) { cJSON *matched_credential_ids = cJSON_GetObjectItemCaseSensitive(matched_option, "matched_credential_ids"); int credential_set_size = cJSON_GetArraySize(matched_credential_ids); - char set_id_buffer[26]; + int option_length = cJSON_HasObjectItem(matched_option, "option_length") ? + cJSON_GetObjectItem(matched_option, "option_length")->valueint : credential_set_size; + int is_partial = cJSON_HasObjectItem(matched_option, "is_partial") && + cJSON_IsTrue(cJSON_GetObjectItem(matched_option, "is_partial")); + cJSON *matched_indices = cJSON_GetObjectItem(matched_option, "matched_indices"); + char set_id_buffer[64]; if (cJSON_HasObjectItem(matched_option, "set_id")) { char *set_idx = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(matched_option, "set_id")); char *option_idx = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(matched_option, "option_id")); int chars_written = sprintf(set_id_buffer, "req:%d;set:%s;option:%s", i, set_idx, option_idx); if (wasm_version > 1) { // Report set length - report_credential_set_length(set_id_buffer, credential_set_size, 1, matched_credential_sets, matched_credential_sets_size); + report_credential_set_length(set_id_buffer, option_length, 1, filtered_credential_sets, matched_credential_sets_size); } cJSON *matched_doc; cJSON *matched_credential_id; - int doc_idx = 0; + int k = 0; cJSON_ArrayForEach(matched_credential_id, matched_credential_ids) { + int doc_idx = matched_indices ? cJSON_GetArrayItem(matched_indices, k)->valueint : k; printf("matched_credential_id %s\n", cJSON_GetStringValue(matched_credential_id)); matched_doc = cJSON_GetObjectItemCaseSensitive(matched_docs, cJSON_GetStringValue(matched_credential_id)); - report_matched_credential(wasm_version, matched_doc, matched_credential_id, doc_idx, i, set_id_buffer, set_idx, option_idx, creds_blob, transaction_credential_ids, merchant_name, transaction_amount, additional_info); - ++doc_idx; + report_matched_credential(wasm_version, matched_doc, matched_credential_id, doc_idx, i, set_id_buffer, set_idx, option_idx, creds_blob, transaction_credential_ids, merchant_name, transaction_amount, additional_info, is_partial, data_json, query); + ++k; } - report_matched_credential_set(set_id_buffer, 1, matched_credential_sets, doc_idx, matched_credential_sets_size, wasm_version, matched_docs, i, creds_blob, transaction_credential_ids, merchant_name, transaction_amount, additional_info); + report_matched_credential_set(set_id_buffer, 1, filtered_credential_sets, option_length, matched_credential_sets_size, wasm_version, matched_docs, i, creds_blob, transaction_credential_ids, merchant_name, transaction_amount, additional_info, data_json, query); } else { // No credential_sets present in dcql int chars_written = sprintf(set_id_buffer, "req:%d;null", i); if (wasm_version > 1) { // Report set length @@ -360,12 +516,15 @@ int openid_main() { printf("matched_credential_id %s\n", cJSON_GetStringValue(matched_credential_id)); matched_doc = cJSON_GetObjectItemCaseSensitive(matched_docs, cJSON_GetStringValue(matched_credential_id)); - report_matched_credential(wasm_version, matched_doc, matched_credential_id, doc_idx, i, set_id_buffer, NULL, NULL, creds_blob, transaction_credential_ids, merchant_name, transaction_amount, additional_info); + report_matched_credential(wasm_version, matched_doc, matched_credential_id, doc_idx, i, set_id_buffer, NULL, NULL, creds_blob, transaction_credential_ids, merchant_name, transaction_amount, additional_info, 0, data_json, query); ++doc_idx; } } } } + if (filtered_credential_sets != NULL) { + cJSON_Delete(filtered_credential_sets); + } } } return 0; diff --git a/matcher/pnv/test/common.cpp b/matcher/pnv/test/common.cpp index 5eb2f25..b5f8b3d 100644 --- a/matcher/pnv/test/common.cpp +++ b/matcher/pnv/test/common.cpp @@ -5,6 +5,9 @@ #include #include +#include +#include +#include using json = nlohmann::json; @@ -14,6 +17,51 @@ TestCredmanState &TestCredmanState::instance() return state; } +void TestCredmanState::reset() +{ + request_buffer.clear(); + credentials_buffer.clear(); + wasm_version = 7; + entry_sets.clear(); + entries.clear(); +} + +std::string getTestDataPath(const std::string &relative_path) +{ + std::filesystem::path source_path = __FILE__; + std::filesystem::path source_dir = source_path.parent_path(); + return (source_dir / relative_path).string(); +} + +std::string readFileToString(const std::string &file_path) +{ + std::ifstream input_file(file_path, std::ios::binary); + if (!input_file.is_open()) + { + return ""; + } + std::ostringstream ss; + ss << input_file.rdbuf(); + return ss.str(); +} + +std::string makeRegistryBlob(const nlohmann::json ®istry_json) +{ + std::string json_str = registry_json.dump(); + std::string blob; + int offset = 4; + blob.resize(4 + json_str.size()); + memcpy(blob.data(), &offset, 4); + memcpy(blob.data() + 4, json_str.data(), json_str.size()); + return blob; +} + +nlohmann::json loadDefaultRegistryJson() +{ + std::string content = readFileToString(getTestDataPath("data/pnv_registry.json")); + return json::parse(content); +} + RequestGenerator::RequestGenerator() { request_json_ = json::parse(R"({ @@ -87,11 +135,43 @@ RequestGenerator &RequestGenerator::with_vct_values(const std::vector 0) entry.icon = std::string(icon, icon_len); - if (title) - entry.title = title; - if (subtitle) - entry.subtitle = subtitle; - if (disclaimer) - entry.disclaimer = disclaimer; - if (warning) - entry.warning = warning; - TestCredmanState::instance().string_id_entries.push_back(entry); - } - - void SetAdditionalDisclaimerAndUrlForVerificationEntry(char *cred_id, char *secondary_disclaimer, char *url_display_text, char *url_value) - { - if (!cred_id) - return; - auto &entries = TestCredmanState::instance().string_id_entries; - auto it = std::find_if(entries.begin(), entries.end(), [&](const StringIdEntry &entry) - { return entry.id == cred_id; }); - if (it != entries.end()) + entry.title = title ? title : ""; + entry.subtitle = subtitle ? subtitle : ""; + entry.disclaimer = disclaimer ? disclaimer : ""; + entry.warning = warning ? warning : ""; + entry.metadata = metadata ? metadata : ""; + entry.set_id = set_id ? set_id : ""; + entry.set_index = set_index; + TestCredmanState::instance().entries.push_back(entry); + } + + void SetDelegationTypeForEntryInSet(const char *cred_id, int delegation_type, const char *set_id, int set_index) + { + auto &entries = TestCredmanState::instance().entries; + for (auto &e : entries) { - if (secondary_disclaimer) - it->secondary_disclaimer = secondary_disclaimer; - if (url_display_text) - it->url_display_text = url_display_text; - if (url_value) - it->url_value = url_value; + if (e.cred_id == (cred_id ? cred_id : "") && + e.set_id == (set_id ? set_id : "") && + e.set_index == set_index) + { + e.delegation_type = delegation_type; + } } } - void AddFieldForStringIdEntry(char *cred_id, char *field_display_name, char *field_display_value) + void AddFieldToEntrySet(const char *cred_id, const char *field_display_name, const char *field_display_value, const char *set_id, int set_index) { - if (!cred_id) - return; - auto &entries = TestCredmanState::instance().string_id_entries; - auto it = std::find_if(entries.begin(), entries.end(), [&](const StringIdEntry &entry) - { return entry.id == cred_id; }); - if (it != entries.end()) + auto &entries = TestCredmanState::instance().entries; + for (auto &e : entries) { - it->fields.emplace_back( - field_display_name ? field_display_name : "", - field_display_value ? field_display_value : ""); + if (e.cred_id == (cred_id ? cred_id : "") && + e.set_id == (set_id ? set_id : "") && + e.set_index == set_index) + { + e.fields.emplace_back(field_display_name ? field_display_name : "", field_display_value ? field_display_value : ""); + } } } - void AddPaymentEntry(char *cred_id, char *merchant_name, char *payment_method_name, char *payment_method_subtitle, char *payment_method_icon, size_t payment_method_icon_len, char *transaction_amount, char *bank_icon, size_t bank_icon_len, char *payment_provider_icon, size_t payment_provider_icon_len) + void SetAdditionalDisclaimerAndUrlForVerificationEntryInCredentialSet(const char *cred_id, const char *secondary_disclaimer, const char *url_display_text, const char *url_value, const char *set_id, int set_index) { - if (!cred_id) - return; - PaymentEntry entry; - entry.id = cred_id; - if (merchant_name) - entry.merchant_name = merchant_name; - if (payment_method_name) - entry.payment_method_name = payment_method_name; - if (payment_method_subtitle) - entry.payment_method_subtitle = payment_method_subtitle; - if (payment_method_icon) - entry.payment_method_icon = std::string(payment_method_icon, payment_method_icon_len); - if (transaction_amount) - entry.transaction_amount = transaction_amount; - if (bank_icon) - entry.bank_icon = std::string(bank_icon, bank_icon_len); - if (payment_provider_icon) - entry.payment_provider_icon = std::string(payment_provider_icon, payment_provider_icon_len); - TestCredmanState::instance().payment_entries.push_back(entry); + auto &entries = TestCredmanState::instance().entries; + for (auto &e : entries) + { + if (e.cred_id == (cred_id ? cred_id : "") && + e.set_id == (set_id ? set_id : "") && + e.set_index == set_index) + { + if (secondary_disclaimer) e.secondary_disclaimer = secondary_disclaimer; + if (url_display_text) e.url_display_text = url_display_text; + if (url_value) e.url_value = url_value; + } + } + } + + void AddMetadataDisplayTextToEntrySet(const char *cred_id, const char *metadata_display_text, const char *set_id, int set_index) + { + (void)cred_id; (void)metadata_display_text; (void)set_id; (void)set_index; } + + // Unused stubs required to satisfy credentialmanager.h declarations + void AddEntry(long long, const char *, size_t, const char *, const char *, const char *, const char *) {} + void AddField(long long, const char *, const char *) {} + void AddStringIdEntry(const char *, const char *, size_t, const char *, const char *, const char *, const char *) {} + void AddFieldForStringIdEntry(const char *, const char *, const char *) {} + void AddPaymentEntry(const char *, const char *, const char *, const char *, const char *, size_t, const char *, const char *, size_t, const char *, size_t) {} + void AddPaymentEntryToSet(const char *, const char *, const char *, const char *, const char *, size_t, const char *, const char *, size_t, const char *, size_t, const char *, const char *, int) {} + void AddPaymentEntryToSetV2(const char *, const char *, const char *, const char *, const char *, size_t, const char *, const char *, size_t, const char *, size_t, const char *, const char *, const char *, int) {} + void AddInlineIssuanceEntry(const char *, const char *, size_t, const char *, const char *) {} + void SetAdditionalDisclaimerAndUrlForVerificationEntry(const char *, const char *, const char *, const char *) {} + void GetCallingAppInfo(CallingAppInfo *) {} + void SelfDeclarePackageInfo(const char *, const char *, size_t) {} } doctest::String toString(const TestCredmanState &state) { doctest::String s; - s += "string_id_entries:\n"; - for (const auto &entry : state.string_id_entries) + s += "entries count: "; + s += std::to_string(state.entries.size()).c_str(); + s += "\n"; + for (const auto &entry : state.entries) { s += " id: "; - s += entry.id.c_str(); + s += entry.cred_id.c_str(); + s += " set_id: "; + s += entry.set_id.c_str(); + s += " set_idx: "; + s += std::to_string(entry.set_index).c_str(); + s += " del_type: "; + s += std::to_string(entry.delegation_type).c_str(); s += "\n"; } return s; -} \ No newline at end of file +} diff --git a/matcher/pnv/test/common.hpp b/matcher/pnv/test/common.hpp index f83446e..ac9bcf0 100644 --- a/matcher/pnv/test/common.hpp +++ b/matcher/pnv/test/common.hpp @@ -1,5 +1,4 @@ #pragma once -#pragma once #include #include #include @@ -7,39 +6,40 @@ #include #include -struct StringIdEntry +struct EntryInSet { - std::string id; + std::string cred_id; std::string icon; std::string title; std::string subtitle; std::string disclaimer; std::string warning; + std::string metadata; + std::string set_id; + int set_index = 0; + int delegation_type = 0; std::string secondary_disclaimer; std::string url_display_text; std::string url_value; std::vector> fields; }; -struct PaymentEntry +struct EntrySet { - std::string id; - std::string merchant_name; - std::string payment_method_name; - std::string payment_method_subtitle; - std::string payment_method_icon; - std::string transaction_amount; - std::string bank_icon; - std::string payment_provider_icon; + std::string set_id; + int set_length = 0; }; struct TestCredmanState { - std::string request_buffer, credentials_buffer; - std::vector string_id_entries; - std::vector payment_entries; + std::string request_buffer; + std::string credentials_buffer; + uint32_t wasm_version = 7; + std::vector entry_sets; + std::vector entries; static TestCredmanState &instance(); + void reset(); }; doctest::String toString(const TestCredmanState &state); @@ -48,8 +48,7 @@ struct TestCredmanStateGuard { ~TestCredmanStateGuard() { - TestCredmanState::instance().string_id_entries.clear(); - TestCredmanState::instance().payment_entries.clear(); + TestCredmanState::instance().reset(); } }; @@ -62,10 +61,20 @@ class RequestGenerator RequestGenerator &with_android_carrier_hint(const std::vector &hints); RequestGenerator &with_subscription_hint(const std::vector &hints); RequestGenerator &with_vct_values(const std::vector &values); + RequestGenerator &with_user_verification(const std::string &uv); + RequestGenerator &with_user_verification_hint_claim(); + RequestGenerator &with_credential_sets(const nlohmann::json &sets); + RequestGenerator &add_credential(const nlohmann::json &cred); std::string build(); + nlohmann::json &json_data(); private: nlohmann::json request_json_; }; +std::string getTestDataPath(const std::string &relative_path); +std::string readFileToString(const std::string &file_path); +std::string makeRegistryBlob(const nlohmann::json ®istry_json); +nlohmann::json loadDefaultRegistryJson(); + extern TestCredmanState testCredmanState; diff --git a/matcher/pnv/test/openid4vp1_0_test.cpp b/matcher/pnv/test/openid4vp1_0_test.cpp index 37190bc..32a3ae3 100644 --- a/matcher/pnv/test/openid4vp1_0_test.cpp +++ b/matcher/pnv/test/openid4vp1_0_test.cpp @@ -6,41 +6,19 @@ #include #include #include -#include // Required for std::ostringstream +#include extern "C" int openid_main(); -// Helper function to get the path to a test data file relative to the current source file -std::string getTestDataPath(const std::string &relative_path) -{ - std::filesystem::path source_path = __FILE__; - std::filesystem::path source_dir = source_path.parent_path(); - return (source_dir / relative_path).string(); -} - -// Helper function to read the entire content of a file into a std::string -std::string readFileToString(const std::string &file_path) -{ - std::ifstream input_file(file_path, std::ios::binary); - if (!input_file.is_open()) - { - return ""; // Return empty string if file cannot be opened - } - std::ostringstream ss; - ss << input_file.rdbuf(); - return ss.str(); -} - TEST_CASE("OpenID4VP") { using namespace std::string_literals; - TestCredmanState::instance().credentials_buffer = - std::string({'\4', '\0', '\0', '\0'}) + - readFileToString(getTestDataPath("data/pnv_registry.json")); SUBCASE("Only filter by phone number") { TestCredmanStateGuard guard; + TestCredmanState::instance().credentials_buffer = + makeRegistryBlob(loadDefaultRegistryJson()); TestCredmanState::instance().request_buffer = RequestGenerator() .with_phone_number_hint({"+16502154321", "+16502154322", "+16502154323"}) @@ -49,19 +27,21 @@ TEST_CASE("OpenID4VP") REQUIRE_EQ(0, openid_main()); CAPTURE(TestCredmanState::instance()); - REQUIRE(TestCredmanState::instance().string_id_entries.size() == 16); - REQUIRE(TestCredmanState::instance().string_id_entries[0].id == R"({"entry_id":"verify_1","dcql_cred_id":"aggregator1","req_idx":0})"s); - REQUIRE(TestCredmanState::instance().string_id_entries[1].id == R"({"entry_id":"verify_3","dcql_cred_id":"aggregator1","req_idx":0})"s); - REQUIRE(TestCredmanState::instance().string_id_entries[2].id == R"({"entry_id":"verify_5","dcql_cred_id":"aggregator1","req_idx":0})"s); + REQUIRE(TestCredmanState::instance().entries.size() == 16); + REQUIRE(TestCredmanState::instance().entries[0].cred_id == "verify_1"); + REQUIRE(TestCredmanState::instance().entries[1].cred_id == "verify_3"); + REQUIRE(TestCredmanState::instance().entries[2].cred_id == "verify_5"); - REQUIRE(TestCredmanState::instance().string_id_entries[8].id == R"({"entry_id":"verify_2","dcql_cred_id":"aggregator1","req_idx":0})"s); - REQUIRE(TestCredmanState::instance().string_id_entries[9].id == R"({"entry_id":"verify_4","dcql_cred_id":"aggregator1","req_idx":0})"s); - REQUIRE(TestCredmanState::instance().string_id_entries[10].id == R"({"entry_id":"verify_6","dcql_cred_id":"aggregator1","req_idx":0})"s); + REQUIRE(TestCredmanState::instance().entries[8].cred_id == "verify_2"); + REQUIRE(TestCredmanState::instance().entries[9].cred_id == "verify_4"); + REQUIRE(TestCredmanState::instance().entries[10].cred_id == "verify_6"); } SUBCASE("Filter by both carrier and android carrier hint requiring both matches") { TestCredmanStateGuard guard; + TestCredmanState::instance().credentials_buffer = + makeRegistryBlob(loadDefaultRegistryJson()); TestCredmanState::instance().request_buffer = RequestGenerator() .with_carrier_hint({"22222", "110999"}) @@ -71,15 +51,17 @@ TEST_CASE("OpenID4VP") REQUIRE_EQ(0, openid_main()); CAPTURE(TestCredmanState::instance()); - REQUIRE(TestCredmanState::instance().string_id_entries.size() == 16); - REQUIRE(TestCredmanState::instance().string_id_entries[0].id == R"({"entry_id":"verify_7","dcql_cred_id":"aggregator1","req_idx":0})"s); - REQUIRE(TestCredmanState::instance().string_id_entries[1].id == R"({"entry_id":"verify_8","dcql_cred_id":"aggregator1","req_idx":0})"s); - REQUIRE(TestCredmanState::instance().string_id_entries[2].id == R"({"entry_id":"verify_15","dcql_cred_id":"aggregator1","req_idx":0})"s); + REQUIRE(TestCredmanState::instance().entries.size() == 16); + REQUIRE(TestCredmanState::instance().entries[0].cred_id == "verify_7"); + REQUIRE(TestCredmanState::instance().entries[1].cred_id == "verify_8"); + REQUIRE(TestCredmanState::instance().entries[2].cred_id == "verify_15"); } SUBCASE("Filter by carrier only") { TestCredmanStateGuard guard; + TestCredmanState::instance().credentials_buffer = + makeRegistryBlob(loadDefaultRegistryJson()); TestCredmanState::instance().request_buffer = RequestGenerator() .with_carrier_hint({"22222", "110999"}) @@ -88,15 +70,17 @@ TEST_CASE("OpenID4VP") REQUIRE_EQ(0, openid_main()); CAPTURE(TestCredmanState::instance()); - REQUIRE(TestCredmanState::instance().string_id_entries.size() == 16); - REQUIRE(TestCredmanState::instance().string_id_entries[0].id == R"({"entry_id":"phone_number_5","dcql_cred_id":"aggregator1","req_idx":0})"s); - REQUIRE(TestCredmanState::instance().string_id_entries[1].id == R"({"entry_id":"phone_number_6","dcql_cred_id":"aggregator1","req_idx":0})"s); - REQUIRE(TestCredmanState::instance().string_id_entries[2].id == R"({"entry_id":"phone_number_7","dcql_cred_id":"aggregator1","req_idx":0})"s); + REQUIRE(TestCredmanState::instance().entries.size() == 16); + REQUIRE(TestCredmanState::instance().entries[0].cred_id == "phone_number_5"); + REQUIRE(TestCredmanState::instance().entries[1].cred_id == "phone_number_6"); + REQUIRE(TestCredmanState::instance().entries[2].cred_id == "phone_number_7"); } SUBCASE("Filter by both carrier and subscription hint ordering carrier matches first") { TestCredmanStateGuard guard; + TestCredmanState::instance().credentials_buffer = + makeRegistryBlob(loadDefaultRegistryJson()); TestCredmanState::instance().request_buffer = RequestGenerator() .with_carrier_hint({"22222", "110999"}) @@ -105,10 +89,332 @@ TEST_CASE("OpenID4VP") .build(); REQUIRE_EQ(0, openid_main()); CAPTURE(TestCredmanState::instance()); - REQUIRE(TestCredmanState::instance().string_id_entries.size() == 16); - REQUIRE(TestCredmanState::instance().string_id_entries[0].id == R"({"entry_id":"verify_13","dcql_cred_id":"aggregator1","req_idx":0})"s); - REQUIRE(TestCredmanState::instance().string_id_entries[1].id == R"({"entry_id":"verify_14","dcql_cred_id":"aggregator1","req_idx":0})"s); - REQUIRE(TestCredmanState::instance().string_id_entries[2].id == R"({"entry_id":"verify_15","dcql_cred_id":"aggregator1","req_idx":0})"s); - REQUIRE(TestCredmanState::instance().string_id_entries[4].id == R"({"entry_id":"verify_5","dcql_cred_id":"aggregator1","req_idx":0})"s); + REQUIRE(TestCredmanState::instance().entries.size() == 16); + REQUIRE(TestCredmanState::instance().entries[0].cred_id == "verify_13"); + REQUIRE(TestCredmanState::instance().entries[1].cred_id == "verify_14"); + REQUIRE(TestCredmanState::instance().entries[2].cred_id == "verify_15"); + REQUIRE(TestCredmanState::instance().entries[4].cred_id == "verify_5"); + } + + SUBCASE("Full match with delegation_type 1 and wasm_version >= 7 sets delegation") + { + TestCredmanStateGuard guard; + auto reg = loadDefaultRegistryJson(); + reg["credentials"]["dc-authorization+sd-jwt"]["number-verification/verify/ts43"][0]["delegation_type"] = 1; + TestCredmanState::instance().credentials_buffer = makeRegistryBlob(reg); + TestCredmanState::instance().wasm_version = 7; + TestCredmanState::instance().request_buffer = + RequestGenerator() + .with_phone_number_hint({"+16502154321"}) + .with_vct_values({"number-verification/verify/ts43"}) + .build(); + + REQUIRE_EQ(0, openid_main()); + CAPTURE(TestCredmanState::instance()); + REQUIRE(TestCredmanState::instance().entries.size() >= 1); + REQUIRE(TestCredmanState::instance().entries[0].cred_id == "verify_1"); + CHECK_EQ(TestCredmanState::instance().entries[0].delegation_type, 1); + // Verify an entry without delegation_type 1 does not have delegation set + CHECK_EQ(TestCredmanState::instance().entries[1].delegation_type, 0); + } + + SUBCASE("Full match with delegation_type 0 does not set delegation") + { + TestCredmanStateGuard guard; + auto reg = loadDefaultRegistryJson(); + reg["credentials"]["dc-authorization+sd-jwt"]["number-verification/verify/ts43"][0]["delegation_type"] = 0; + TestCredmanState::instance().credentials_buffer = makeRegistryBlob(reg); + TestCredmanState::instance().wasm_version = 7; + TestCredmanState::instance().request_buffer = + RequestGenerator() + .with_phone_number_hint({"+16502154321"}) + .with_vct_values({"number-verification/verify/ts43"}) + .build(); + + REQUIRE_EQ(0, openid_main()); + CAPTURE(TestCredmanState::instance()); + REQUIRE(TestCredmanState::instance().entries.size() >= 1); + REQUIRE(TestCredmanState::instance().entries[0].cred_id == "verify_1"); + CHECK_EQ(TestCredmanState::instance().entries[0].delegation_type, 0); + } + + SUBCASE("Full match with delegation_type 1 but user verification requested overrides delegation to 0") + { + TestCredmanStateGuard guard; + auto reg = loadDefaultRegistryJson(); + reg["credentials"]["dc-authorization+sd-jwt"]["number-verification/verify/ts43"][0]["delegation_type"] = 1; + TestCredmanState::instance().credentials_buffer = makeRegistryBlob(reg); + TestCredmanState::instance().wasm_version = 7; + TestCredmanState::instance().request_buffer = + RequestGenerator() + .with_phone_number_hint({"+16502154321"}) + .with_vct_values({"number-verification/verify/ts43"}) + .with_user_verification("required") + .build(); + + REQUIRE_EQ(0, openid_main()); + CAPTURE(TestCredmanState::instance()); + REQUIRE(TestCredmanState::instance().entries.size() >= 1); + REQUIRE(TestCredmanState::instance().entries[0].cred_id == "verify_1"); + CHECK_EQ(TestCredmanState::instance().entries[0].delegation_type, 0); + } + + SUBCASE("Full match with delegation_type 1 but user_verification_hint claim requested overrides delegation to 0") + { + TestCredmanStateGuard guard; + auto reg = loadDefaultRegistryJson(); + reg["credentials"]["dc-authorization+sd-jwt"]["number-verification/verify/ts43"][0]["delegation_type"] = 1; + TestCredmanState::instance().credentials_buffer = makeRegistryBlob(reg); + TestCredmanState::instance().wasm_version = 7; + TestCredmanState::instance().request_buffer = + RequestGenerator() + .with_phone_number_hint({"+16502154321"}) + .with_vct_values({"number-verification/verify/ts43"}) + .with_user_verification_hint_claim() + .build(); + + REQUIRE_EQ(0, openid_main()); + CAPTURE(TestCredmanState::instance()); + REQUIRE(TestCredmanState::instance().entries.size() >= 1); + REQUIRE(TestCredmanState::instance().entries[0].cred_id == "verify_1"); + CHECK_EQ(TestCredmanState::instance().entries[0].delegation_type, 0); + } + + SUBCASE("Full match with delegation_type 1 but wasm_version < 7 does not set delegation") + { + TestCredmanStateGuard guard; + auto reg = loadDefaultRegistryJson(); + reg["credentials"]["dc-authorization+sd-jwt"]["number-verification/verify/ts43"][0]["delegation_type"] = 1; + TestCredmanState::instance().credentials_buffer = makeRegistryBlob(reg); + TestCredmanState::instance().wasm_version = 6; + TestCredmanState::instance().request_buffer = + RequestGenerator() + .with_phone_number_hint({"+16502154321"}) + .with_vct_values({"number-verification/verify/ts43"}) + .build(); + + REQUIRE_EQ(0, openid_main()); + CAPTURE(TestCredmanState::instance()); + REQUIRE(TestCredmanState::instance().entries.size() >= 1); + REQUIRE(TestCredmanState::instance().entries[0].cred_id == "verify_1"); + CHECK_EQ(TestCredmanState::instance().entries[0].delegation_type, 0); + } + + SUBCASE("Partial match with delegation_type 1 is allowed and sets delegation") + { + TestCredmanStateGuard guard; + // Construct registry with one single entry that has delegation_type 1 + auto reg = loadDefaultRegistryJson(); + auto verify_1 = reg["credentials"]["dc-authorization+sd-jwt"]["number-verification/verify/ts43"][0]; + verify_1["delegation_type"] = 1; + nlohmann::json single_reg = { + {"credentials", { + {"dc-authorization+sd-jwt", { + {"number-verification/verify/ts43", nlohmann::json::array({verify_1})} + }} + }} + }; + TestCredmanState::instance().credentials_buffer = makeRegistryBlob(single_reg); + TestCredmanState::instance().wasm_version = 7; + + // Build request with a credential set requiring 2 credentials, but only 1 matches + RequestGenerator gen; + gen.with_phone_number_hint({"+16502154321"}); + gen.with_vct_values({"number-verification/verify/ts43"}); + gen.add_credential({ + {"id", "cred_unmatched"}, + {"format", "dc-authorization+sd-jwt"}, + {"meta", { + {"credential_authorization_jwt", "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJkY2FnZ3JlZ2F0b3IuZGV2In0.c2ln"}, + {"vct_values", nlohmann::json::array({"number-verification/unmatched"})} + }} + }); + gen.with_credential_sets(nlohmann::json::array({ + { + {"required", true}, + {"options", nlohmann::json::array({ + nlohmann::json::array({"aggregator1", "cred_unmatched"}) + })} + } + })); + TestCredmanState::instance().request_buffer = gen.build(); + + REQUIRE_EQ(0, openid_main()); + CAPTURE(TestCredmanState::instance()); + REQUIRE(TestCredmanState::instance().entries.size() == 1); + CHECK_EQ(TestCredmanState::instance().entries[0].cred_id, "verify_1"); + CHECK_EQ(TestCredmanState::instance().entries[0].delegation_type, 1); + } + + SUBCASE("Partial match with delegation_type 0 is suppressed") + { + TestCredmanStateGuard guard; + // Construct registry with one single entry that has delegation_type 0 + auto reg = loadDefaultRegistryJson(); + auto verify_1 = reg["credentials"]["dc-authorization+sd-jwt"]["number-verification/verify/ts43"][0]; + verify_1["delegation_type"] = 0; + nlohmann::json single_reg = { + {"credentials", { + {"dc-authorization+sd-jwt", { + {"number-verification/verify/ts43", nlohmann::json::array({verify_1})} + }} + }} + }; + TestCredmanState::instance().credentials_buffer = makeRegistryBlob(single_reg); + TestCredmanState::instance().wasm_version = 7; + + RequestGenerator gen; + gen.with_phone_number_hint({"+16502154321"}); + gen.with_vct_values({"number-verification/verify/ts43"}); + gen.add_credential({ + {"id", "cred_unmatched"}, + {"format", "dc-authorization+sd-jwt"}, + {"meta", { + {"credential_authorization_jwt", "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJkY2FnZ3JlZ2F0b3IuZGV2In0.c2ln"}, + {"vct_values", nlohmann::json::array({"number-verification/unmatched"})} + }} + }); + gen.with_credential_sets(nlohmann::json::array({ + { + {"required", true}, + {"options", nlohmann::json::array({ + nlohmann::json::array({"aggregator1", "cred_unmatched"}) + })} + } + })); + TestCredmanState::instance().request_buffer = gen.build(); + + REQUIRE_EQ(0, openid_main()); + CAPTURE(TestCredmanState::instance()); + // Option must be suppressed + CHECK(TestCredmanState::instance().entries.empty()); + } + + SUBCASE("Partial match with delegation_type 1 but user verification requested is suppressed") + { + TestCredmanStateGuard guard; + auto reg = loadDefaultRegistryJson(); + auto verify_1 = reg["credentials"]["dc-authorization+sd-jwt"]["number-verification/verify/ts43"][0]; + verify_1["delegation_type"] = 1; + nlohmann::json single_reg = { + {"credentials", { + {"dc-authorization+sd-jwt", { + {"number-verification/verify/ts43", nlohmann::json::array({verify_1})} + }} + }} + }; + TestCredmanState::instance().credentials_buffer = makeRegistryBlob(single_reg); + TestCredmanState::instance().wasm_version = 7; + + RequestGenerator gen; + gen.with_phone_number_hint({"+16502154321"}); + gen.with_vct_values({"number-verification/verify/ts43"}); + gen.with_user_verification("required"); + gen.add_credential({ + {"id", "cred_unmatched"}, + {"format", "dc-authorization+sd-jwt"}, + {"meta", { + {"credential_authorization_jwt", "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJkY2FnZ3JlZ2F0b3IuZGV2In0.c2ln"}, + {"vct_values", nlohmann::json::array({"number-verification/unmatched"})} + }} + }); + gen.with_credential_sets(nlohmann::json::array({ + { + {"required", true}, + {"options", nlohmann::json::array({ + nlohmann::json::array({"aggregator1", "cred_unmatched"}) + })} + } + })); + TestCredmanState::instance().request_buffer = gen.build(); + + REQUIRE_EQ(0, openid_main()); + CAPTURE(TestCredmanState::instance()); + CHECK(TestCredmanState::instance().entries.empty()); + } + + SUBCASE("Partial match with delegation_type 1 but user_verification_hint claim requested is suppressed") + { + TestCredmanStateGuard guard; + auto reg = loadDefaultRegistryJson(); + auto verify_1 = reg["credentials"]["dc-authorization+sd-jwt"]["number-verification/verify/ts43"][0]; + verify_1["delegation_type"] = 1; + nlohmann::json single_reg = { + {"credentials", { + {"dc-authorization+sd-jwt", { + {"number-verification/verify/ts43", nlohmann::json::array({verify_1})} + }} + }} + }; + TestCredmanState::instance().credentials_buffer = makeRegistryBlob(single_reg); + TestCredmanState::instance().wasm_version = 7; + + RequestGenerator gen; + gen.with_phone_number_hint({"+16502154321"}); + gen.with_vct_values({"number-verification/verify/ts43"}); + gen.with_user_verification_hint_claim(); + gen.add_credential({ + {"id", "cred_unmatched"}, + {"format", "dc-authorization+sd-jwt"}, + {"meta", { + {"credential_authorization_jwt", "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJkY2FnZ3JlZ2F0b3IuZGV2In0.c2ln"}, + {"vct_values", nlohmann::json::array({"number-verification/unmatched"})} + }} + }); + gen.with_credential_sets(nlohmann::json::array({ + { + {"required", true}, + {"options", nlohmann::json::array({ + nlohmann::json::array({"aggregator1", "cred_unmatched"}) + })} + } + })); + TestCredmanState::instance().request_buffer = gen.build(); + + REQUIRE_EQ(0, openid_main()); + CAPTURE(TestCredmanState::instance()); + CHECK(TestCredmanState::instance().entries.empty()); } -} \ No newline at end of file + + SUBCASE("Partial match with delegation_type 1 but wasm_version < 7 is suppressed") + { + TestCredmanStateGuard guard; + auto reg = loadDefaultRegistryJson(); + auto verify_1 = reg["credentials"]["dc-authorization+sd-jwt"]["number-verification/verify/ts43"][0]; + verify_1["delegation_type"] = 1; + nlohmann::json single_reg = { + {"credentials", { + {"dc-authorization+sd-jwt", { + {"number-verification/verify/ts43", nlohmann::json::array({verify_1})} + }} + }} + }; + TestCredmanState::instance().credentials_buffer = makeRegistryBlob(single_reg); + TestCredmanState::instance().wasm_version = 6; + + RequestGenerator gen; + gen.with_phone_number_hint({"+16502154321"}); + gen.with_vct_values({"number-verification/verify/ts43"}); + gen.add_credential({ + {"id", "cred_unmatched"}, + {"format", "dc-authorization+sd-jwt"}, + {"meta", { + {"credential_authorization_jwt", "eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJkY2FnZ3JlZ2F0b3IuZGV2In0.c2ln"}, + {"vct_values", nlohmann::json::array({"number-verification/unmatched"})} + }} + }); + gen.with_credential_sets(nlohmann::json::array({ + { + {"required", true}, + {"options", nlohmann::json::array({ + nlohmann::json::array({"aggregator1", "cred_unmatched"}) + })} + } + })); + TestCredmanState::instance().request_buffer = gen.build(); + + REQUIRE_EQ(0, openid_main()); + CAPTURE(TestCredmanState::instance()); + CHECK(TestCredmanState::instance().entries.empty()); + } +} diff --git a/matcher/test_runner.cc b/matcher/test_runner.cc index 6ec189b..03d408a 100644 --- a/matcher/test_runner.cc +++ b/matcher/test_runner.cc @@ -217,6 +217,7 @@ void AddFieldForStringIdEntry(const char*, const char*, const char*) {} void AddPaymentEntry(const char*, const char*, const char*, const char*, const char*, size_t, const char*, const char*, size_t, const char*, size_t) {} void SetAdditionalDisclaimerAndUrlForVerificationEntry(const char*, const char*, const char*, const char*) {} void SetAdditionalDisclaimerAndUrlForVerificationEntryInCredentialSet(const char*, const char*, const char*, const char*, const char*, int) {} +void SetDelegationTypeForEntryInSet(const char*, int, const char*, int) {} void GetCallingAppInfo(CallingAppInfo*) {} void SelfDeclarePackageInfo(const char*, const char*, size_t) {} }