Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions matcher/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
test_runner
*.o
37 changes: 37 additions & 0 deletions matcher/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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

# Tests
test_runner: test_runner.o dcql.o openid4vp1_0.o base64.o cJSON/cJSON.o
$(CXX) $(CXXFLAGS) -o $@ $^

test: test_runner
./test_runner

test_runner.o: test_runner.cc
$(CXX) $(CXXFLAGS) -c $< -o $@

dcql.o: dcql.c
$(CC) $(CFLAGS) -c $< -o $@

openid4vp1_0.o: openid4vp1_0.c
$(CC) $(CFLAGS) -c $< -o $@

base64.o: base64.c
$(CC) $(CFLAGS) -c $< -o $@

cJSON/cJSON.o: cJSON/cJSON.c
$(CC) $(CFLAGS) -c $< -o $@

issuance/provision.o: issuance/provision.c
$(CC) $(CFLAGS) -c $< -o $@

clean:
rm -f *.o cJSON/*.o issuance/*.o test_runner

.PHONY: test clean
1 change: 1 addition & 0 deletions matcher/base64.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ int B64DecodeURL(char* input, char** output) {
output_len--;
}

buffer[output_len] = '\0';

return output_len;
}
117 changes: 117 additions & 0 deletions matcher/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# OpenID4VP Matcher: Comprehensive Code Index

## 1. Registry Binary Format
The registry blob is a custom binary format:
- **Header**: 4 bytes (Little-endian `int`) representing the offset from the start of the blob to the beginning of the JSON metadata.
- **Icon Section**: Raw PNG bytes located between the header and the JSON metadata.
- **Metadata Section**: UTF-8 encoded JSON string starting at the specified offset.

## 2. Registry JSON Schema
Root structure: `{"credentials": { ... }}`

### 2.1 mso_mdoc (credentials.mso_mdoc)
- **Key**: Document Type (e.g., "org.iso.18013.5.1.mDL")
- **Value**: Array of Credential Objects:
- `id`: String.
- `display`:
- `verification`:
- `title`: String.
- `subtitle`: String (optional).
- `explainer`: String (optional).
- `warning`: String (optional).
- `metadata_display_text`: String (optional).
- `icon`: `{"start": <int>, "length": <int>}`.
- `paths`: Map of Namespace -> Map of ClaimName -> Claim object:
- `value`: Any (raw value).
- `display`:
- `verification`:
- `display`: String (localized name).
- `display_value`: String (optional localized value).

### 2.2 dc+sd-jwt (credentials.dc+sd-jwt)
- **Key**: Verifiable Credential Type (VCT) string.
- **Value**: Array of Credential Objects.
- **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.
- **Example**: A claim with `path = ["user", "name", "first"]` becomes:
```json
"paths": {
"user": {
"name": {
"first": {
"value": "John",
"display": { "verification": { "display": "First Name", ... } }
}
}
}
}
```
- **Structure**:
- `id`: String.
- `display`: (same as mso_mdoc).
- `paths`: Recursive nested object. Leaf nodes are Claim objects (same structure as mso_mdoc).

### 2.3 Issuance (credentials.issuance)
- **mso_mdoc**: Array of MdocInlineIssuanceEntry objects:
- `id`: String.
- `subtitle`: String (optional).
- `title`: String (optional hint).
- `icon`: `{"start": <int>, "length": <int>}` (optional).
- `supported`: Array of DocType Strings.
- **dc+sd-jwt**: Array of SdJwtInlineIssuanceEntry objects:
- `id`: String.
- `subtitle`: String (optional).
- `title`: String (optional hint).
- `icon`: `{"start": <int>, "length": <int>}` (optional).
- `supported`: Array of VCT Strings.

---

## 3. C Function Index

### base64.c / base64.h
- `static int B64Lookup(char x)`: Helper function that maps Base64-URL characters to their corresponding 6-bit integer values.
- `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 ('=').

### credentialmanager.c / credentialmanager.h
- Provides ACM (Android Credential Manager) environment bindings to interface with the Android OS.
- `void* GetRequest()`: Fetches the buffer containing the ACM request JSON.
- `void* GetCredentials()`: Fetches the buffer containing the Registry binary blob.
- Also exposes WASM imports for ACM reporting: `AddEntrySet`, `AddEntryToSet`, `AddFieldToEntrySet`, `AddPaymentEntryToSetV2`, `GetWasmVersion`, `AddInlineIssuanceEntry`, etc.

### dcql.c / dcql.h
- `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.
- `cJSON* MatchCredential(cJSON* credential, cJSON* credential_store)`: Evaluates a single DCQL `credential` requirement against the registry `credential_store`.
- Determines if the requested format is `mso_mdoc` or `dc+sd-jwt`.
- Checks if the credential candidate matches the `meta` criteria (e.g., matching `doctype_value` for `mso_mdoc`, or `vct_values` for `dc+sd-jwt`).
- Iterates over candidates that matched the `meta` criteria.
- 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.
- If `claim_sets` are specified, it verifies that at least one logical group of claims is fully satisfied by the matched claims.
- Identifies matching inline issuance options by comparing `supported` DocTypes or VCTs against the `meta` requirements.
- 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.
- `cJSON* dcql_query(cJSON* query, cJSON* credential_store)`: High-level function that orchestrates DCQL evaluation.
- Iterates over all `credentials` in the DCQL query and calls `MatchCredential` for each.
- 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).
- If no `credential_sets` are defined, it requires all requested credentials to match.
- Returns a final `match_result` JSON containing `matched_credential_sets` (valid combinations of credentials) and `matched_credentials` (the actual credential details).

### openid4vp1_0.c
- `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`.
- `void report_matched_credential(...)`: Reports a specific matched credential to the ACM.
- Constructs a JSON metadata string containing the matched claims, request index, and DCQL IDs.
- 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).
- Otherwise, it reports it as a standard entry via `AddEntryToSet`, providing title, subtitle, explainer, and icon offsets.
- Iterates through `matched_claim_names` to report each individual matched claim via `AddFieldToEntrySet`.
- Reports `metadata_display_text` via `AddMetadataDisplayTextToEntrySet` if present.
- `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.
- `int main()`: The global entry point for the WASM module.
- Fetches the credentials binary blob and finds the JSON metadata offset.
- Parses the registry JSON and the DCQL request JSON.
- Determines if the request is OpenID4VP (signed or unsigned) and decodes the payload via `B64DecodeURL` if it's signed (JWS).
- Handles transaction data extraction for payments (merchant name, amount, additional info).
- Calls `dcql_query` to perform the actual matching.
- Extracts the matched results and uses the `report_*` functions to format and send the results back to the Android OS via the ACM API.
- Handles inline issuance fallback by calling `AddInlineIssuanceEntry` if no regular credentials match but inline issuance is supported.

### testharness.c
- Provides mock implementations for ACM APIs (`GetRequestSize`, `GetRequestBuffer`, `GetCredentialsSize`, `ReadCredentialsBuffer`, etc.) to allow for local execution of the `main()` function.
- Reads test inputs from local files like `request.json` and `testcreds.json`.
10 changes: 5 additions & 5 deletions matcher/openid4vp1_0.c
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ void report_matched_credential(uint32_t wasm_version, cJSON* matched_doc, cJSON*
AddFieldForStringIdEntry(id, claim_display, claim_value);
}
}
if (wasm_version >= 5) {
if (wasm_version >= 5 && metadata_display_text != NULL) {
AddMetadataDisplayTextToEntrySet(matched_id, metadata_display_text, set_id, doc_idx);
}
}
Expand Down Expand Up @@ -195,7 +195,7 @@ void report_matched_credential_set(char* set_id, int curr_set_idx, cJSON *matche
}
}

int main()
int openid4vp_main()
{
uint32_t credentials_size;
GetCredentialsSize(&credentials_size);
Expand Down Expand Up @@ -298,7 +298,7 @@ int main()
transaction_data = cJSON_Parse(transaction_data_json);
transaction_credential_ids = cJSON_GetObjectItem(transaction_data, "credential_ids");
char *transaction_data_type = cJSON_GetStringValue(cJSON_GetObjectItem(transaction_data, "type"));
if (strcmp(transaction_data_type, "urn:eudi:sca:payment:1") == 0) {
if (transaction_data_type != NULL && strcmp(transaction_data_type, "urn:eudi:sca:payment:1") == 0) {
cJSON *payload = cJSON_GetObjectItem(transaction_data, "payload");
merchant_name = cJSON_GetStringValue(cJSON_GetObjectItem(cJSON_GetObjectItem(payload, "payee"), "name"));

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

additional_info = cJSON_GetStringValue(cJSON_GetObjectItem(transaction_data, "additional_info"));
} else if (strcmp(transaction_data_type, "payment_details") == 0) {
} else if (transaction_data_type != NULL && strcmp(transaction_data_type, "payment_details") == 0) {
merchant_name = cJSON_GetStringValue(cJSON_GetObjectItem(transaction_data, "payee_name"));

char *amount = cJSON_GetStringValue(cJSON_GetObjectItem(transaction_data, "payment_amount"));
Expand Down Expand Up @@ -347,7 +347,7 @@ int main()
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];
char set_id_buffer[64];

if (cJSON_HasObjectItem(matched_option, "set_id")) {
char *set_idx = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(matched_option, "set_id"));
Expand Down
Loading
Loading