diff --git a/app/src/main/assets/pnv.wasm b/app/src/main/assets/pnv.wasm index 7d52c74..4dab36e 100755 Binary files a/app/src/main/assets/pnv.wasm and b/app/src/main/assets/pnv.wasm differ 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) {} }