Skip to content

Commit 5195165

Browse files
netheril96QZHelen
authored andcommitted
Add golden tests to the current OpenID4VP matcher
TAG=agy CONV=a1593f60-9757-49cc-9ce0-d04dcaed1d5a
1 parent 8b24b15 commit 5195165

70 files changed

Lines changed: 4896 additions & 5 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

matcher/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
test_runner
2+
*.o

matcher/Makefile

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
CXX = g++
2+
CC = gcc
3+
CXXFLAGS = -std=c++17 -Wall -Wextra -g -I. -I/usr/include/doctest -I/usr/include/nlohmann
4+
CFLAGS = -Wall -Wextra -g -I. -Wno-unused-variable -Wno-unused-but-set-variable -Wno-format-overflow
5+
6+
# Common objects
7+
COMMON_OBJS = dcql.o openid4vp1_0.o base64.o cJSON/cJSON.o issuance/provision.o
8+
9+
# Tests
10+
test_runner: test_runner.o dcql.o openid4vp1_0.o base64.o cJSON/cJSON.o
11+
$(CXX) $(CXXFLAGS) -o $@ $^
12+
13+
test: test_runner
14+
./test_runner
15+
16+
test_runner.o: test_runner.cc
17+
$(CXX) $(CXXFLAGS) -c $< -o $@
18+
19+
dcql.o: dcql.c
20+
$(CC) $(CFLAGS) -c $< -o $@
21+
22+
openid4vp1_0.o: openid4vp1_0.c
23+
$(CC) $(CFLAGS) -c $< -o $@
24+
25+
base64.o: base64.c
26+
$(CC) $(CFLAGS) -c $< -o $@
27+
28+
cJSON/cJSON.o: cJSON/cJSON.c
29+
$(CC) $(CFLAGS) -c $< -o $@
30+
31+
issuance/provision.o: issuance/provision.c
32+
$(CC) $(CFLAGS) -c $< -o $@
33+
34+
clean:
35+
rm -f *.o cJSON/*.o issuance/*.o test_runner
36+
37+
.PHONY: test clean

matcher/base64.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ int B64DecodeURL(char* input, char** output) {
4343
output_len--;
4444
}
4545

46+
buffer[output_len] = '\0';
4647

4748
return output_len;
4849
}

matcher/index.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# OpenID4VP Matcher: Comprehensive Code Index
2+
3+
## 1. Registry Binary Format
4+
The registry blob is a custom binary format:
5+
- **Header**: 4 bytes (Little-endian `int`) representing the offset from the start of the blob to the beginning of the JSON metadata.
6+
- **Icon Section**: Raw PNG bytes located between the header and the JSON metadata.
7+
- **Metadata Section**: UTF-8 encoded JSON string starting at the specified offset.
8+
9+
## 2. Registry JSON Schema
10+
Root structure: `{"credentials": { ... }}`
11+
12+
### 2.1 mso_mdoc (credentials.mso_mdoc)
13+
- **Key**: Document Type (e.g., "org.iso.18013.5.1.mDL")
14+
- **Value**: Array of Credential Objects:
15+
- `id`: String.
16+
- `display`:
17+
- `verification`:
18+
- `title`: String.
19+
- `subtitle`: String (optional).
20+
- `explainer`: String (optional).
21+
- `warning`: String (optional).
22+
- `metadata_display_text`: String (optional).
23+
- `icon`: `{"start": <int>, "length": <int>}`.
24+
- `paths`: Map of Namespace -> Map of ClaimName -> Claim object:
25+
- `value`: Any (raw value).
26+
- `display`:
27+
- `verification`:
28+
- `display`: String (localized name).
29+
- `display_value`: String (optional localized value).
30+
31+
### 2.2 dc+sd-jwt (credentials.dc+sd-jwt)
32+
- **Key**: Verifiable Credential Type (VCT) string.
33+
- **Value**: Array of Credential Objects.
34+
- **Paths Construction**: The Kotlin `SdJwtEntry.claims` (a list) is flattened into a nested `paths` object. Each `SdJwtClaim.path` (an array of strings) defines the nesting.
35+
- **Example**: A claim with `path = ["user", "name", "first"]` becomes:
36+
```json
37+
"paths": {
38+
"user": {
39+
"name": {
40+
"first": {
41+
"value": "John",
42+
"display": { "verification": { "display": "First Name", ... } }
43+
}
44+
}
45+
}
46+
}
47+
```
48+
- **Structure**:
49+
- `id`: String.
50+
- `display`: (same as mso_mdoc).
51+
- `paths`: Recursive nested object. Leaf nodes are Claim objects (same structure as mso_mdoc).
52+
53+
### 2.3 Issuance (credentials.issuance)
54+
- **mso_mdoc**: Array of MdocInlineIssuanceEntry objects:
55+
- `id`: String.
56+
- `subtitle`: String (optional).
57+
- `title`: String (optional hint).
58+
- `icon`: `{"start": <int>, "length": <int>}` (optional).
59+
- `supported`: Array of DocType Strings.
60+
- **dc+sd-jwt**: Array of SdJwtInlineIssuanceEntry objects:
61+
- `id`: String.
62+
- `subtitle`: String (optional).
63+
- `title`: String (optional hint).
64+
- `icon`: `{"start": <int>, "length": <int>}` (optional).
65+
- `supported`: Array of VCT Strings.
66+
67+
---
68+
69+
## 3. C Function Index
70+
71+
### base64.c / base64.h
72+
- `static int B64Lookup(char x)`: Helper function that maps Base64-URL characters to their corresponding 6-bit integer values.
73+
- `int B64DecodeURL(char* input, char** output)`: Takes a Base64-URL encoded string and decodes it. It allocates a new buffer for the output using `malloc` and returns the decoded length. It correctly handles base64url specific characters ('-' and '_') as well as standard padding ('=').
74+
75+
### credentialmanager.c / credentialmanager.h
76+
- Provides ACM (Android Credential Manager) environment bindings to interface with the Android OS.
77+
- `void* GetRequest()`: Fetches the buffer containing the ACM request JSON.
78+
- `void* GetCredentials()`: Fetches the buffer containing the Registry binary blob.
79+
- Also exposes WASM imports for ACM reporting: `AddEntrySet`, `AddEntryToSet`, `AddFieldToEntrySet`, `AddPaymentEntryToSetV2`, `GetWasmVersion`, `AddInlineIssuanceEntry`, etc.
80+
81+
### dcql.c / dcql.h
82+
- `int AddAllClaims(cJSON* matched_claim_names, cJSON* candidate_paths)`: Recursively traverses a JSON object representing candidate claim paths. For every object containing a "display" key, it adds that "display" object to the `matched_claim_names` array. This is used when a request doesn't specify specific claims, so the matcher must collect all available claims from the credential to show to the user.
83+
- `cJSON* MatchCredential(cJSON* credential, cJSON* credential_store)`: Evaluates a single DCQL `credential` requirement against the registry `credential_store`.
84+
- Determines if the requested format is `mso_mdoc` or `dc+sd-jwt`.
85+
- Checks if the credential candidate matches the `meta` criteria (e.g., matching `doctype_value` for `mso_mdoc`, or `vct_values` for `dc+sd-jwt`).
86+
- Iterates over candidates that matched the `meta` criteria.
87+
- If specific `claims` are requested, it traverses the `paths` of the candidate using the requested JSON paths array. It matches claim values if `values` are specified in the request.
88+
- If `claim_sets` are specified, it verifies that at least one logical group of claims is fully satisfied by the matched claims.
89+
- Identifies matching inline issuance options by comparing `supported` DocTypes or VCTs against the `meta` requirements.
90+
- Returns a JSON object with `matched_creds` (list of matched credentials containing `id`, `display`, `matched_claim_names`, and `matched_claim_metadata`) and an `inline_issuance` entry if applicable.
91+
- `cJSON* dcql_query(cJSON* query, cJSON* credential_store)`: High-level function that orchestrates DCQL evaluation.
92+
- Iterates over all `credentials` in the DCQL query and calls `MatchCredential` for each.
93+
- If `credential_sets` are defined in the query, it iterates over these sets and their options to find valid combinations of matched credentials that satisfy the query logic (handling `required` flags).
94+
- If no `credential_sets` are defined, it requires all requested credentials to match.
95+
- Returns a final `match_result` JSON containing `matched_credential_sets` (valid combinations of credentials) and `matched_credentials` (the actual credential details).
96+
97+
### openid4vp1_0.c
98+
- `void report_credential_set_length(...)`: Recursively calculates the total number of credentials across all options in a matched credential set and reports this total length to the ACM using `AddEntrySet`.
99+
- `void report_matched_credential(...)`: Reports a specific matched credential to the ACM.
100+
- Constructs a JSON metadata string containing the matched claims, request index, and DCQL IDs.
101+
- Checks if the credential is a payment transaction (if `transaction_credential_ids` match). If so, it reports it as a payment entry via `AddPaymentEntryToSetV2` or `AddPaymentEntryToSet` (depending on the WASM version).
102+
- Otherwise, it reports it as a standard entry via `AddEntryToSet`, providing title, subtitle, explainer, and icon offsets.
103+
- Iterates through `matched_claim_names` to report each individual matched claim via `AddFieldToEntrySet`.
104+
- Reports `metadata_display_text` via `AddMetadataDisplayTextToEntrySet` if present.
105+
- `void report_matched_credential_set(...)`: Recursively iterates through the complex `matched_credential_sets` structure returned by `dcql_query` and calls `report_matched_credential` for each valid credential in each option.
106+
- `int main()`: The global entry point for the WASM module.
107+
- Fetches the credentials binary blob and finds the JSON metadata offset.
108+
- Parses the registry JSON and the DCQL request JSON.
109+
- Determines if the request is OpenID4VP (signed or unsigned) and decodes the payload via `B64DecodeURL` if it's signed (JWS).
110+
- Handles transaction data extraction for payments (merchant name, amount, additional info).
111+
- Calls `dcql_query` to perform the actual matching.
112+
- Extracts the matched results and uses the `report_*` functions to format and send the results back to the Android OS via the ACM API.
113+
- Handles inline issuance fallback by calling `AddInlineIssuanceEntry` if no regular credentials match but inline issuance is supported.
114+
115+
### testharness.c
116+
- Provides mock implementations for ACM APIs (`GetRequestSize`, `GetRequestBuffer`, `GetCredentialsSize`, `ReadCredentialsBuffer`, etc.) to allow for local execution of the `main()` function.
117+
- Reads test inputs from local files like `request.json` and `testcreds.json`.

matcher/openid4vp1_0.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ void report_matched_credential(uint32_t wasm_version, cJSON* matched_doc, cJSON*
163163
AddFieldForStringIdEntry(id, claim_display, claim_value);
164164
}
165165
}
166-
if (wasm_version >= 5) {
166+
if (wasm_version >= 5 && metadata_display_text != NULL) {
167167
AddMetadataDisplayTextToEntrySet(matched_id, metadata_display_text, set_id, doc_idx);
168168
}
169169
}
@@ -195,7 +195,7 @@ void report_matched_credential_set(char* set_id, int curr_set_idx, cJSON *matche
195195
}
196196
}
197197

198-
int main()
198+
int openid4vp_main()
199199
{
200200
uint32_t credentials_size;
201201
GetCredentialsSize(&credentials_size);
@@ -298,7 +298,7 @@ int main()
298298
transaction_data = cJSON_Parse(transaction_data_json);
299299
transaction_credential_ids = cJSON_GetObjectItem(transaction_data, "credential_ids");
300300
char *transaction_data_type = cJSON_GetStringValue(cJSON_GetObjectItem(transaction_data, "type"));
301-
if (strcmp(transaction_data_type, "urn:eudi:sca:payment:1") == 0) {
301+
if (transaction_data_type != NULL && strcmp(transaction_data_type, "urn:eudi:sca:payment:1") == 0) {
302302
cJSON *payload = cJSON_GetObjectItem(transaction_data, "payload");
303303
merchant_name = cJSON_GetStringValue(cJSON_GetObjectItem(cJSON_GetObjectItem(payload, "payee"), "name"));
304304

@@ -316,7 +316,7 @@ int main()
316316
printf("transaction amount %s\n", transaction_amount);
317317

318318
additional_info = cJSON_GetStringValue(cJSON_GetObjectItem(transaction_data, "additional_info"));
319-
} else if (strcmp(transaction_data_type, "payment_details") == 0) {
319+
} else if (transaction_data_type != NULL && strcmp(transaction_data_type, "payment_details") == 0) {
320320
merchant_name = cJSON_GetStringValue(cJSON_GetObjectItem(transaction_data, "payee_name"));
321321

322322
char *amount = cJSON_GetStringValue(cJSON_GetObjectItem(transaction_data, "payment_amount"));
@@ -347,7 +347,7 @@ int main()
347347
cJSON_ArrayForEach(matched_option, first_matched_credential_set) {
348348
cJSON *matched_credential_ids = cJSON_GetObjectItemCaseSensitive(matched_option, "matched_credential_ids");
349349
int credential_set_size = cJSON_GetArraySize(matched_credential_ids);
350-
char set_id_buffer[26];
350+
char set_id_buffer[64];
351351

352352
if (cJSON_HasObjectItem(matched_option, "set_id")) {
353353
char *set_idx = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(matched_option, "set_id"));

0 commit comments

Comments
 (0)