Skip to content

Commit a2094a7

Browse files
committed
[PNV Demo] Update test data
1 parent 4dcb7e0 commit a2094a7

File tree

6 files changed

+116
-31
lines changed

6 files changed

+116
-31
lines changed

app/src/main/assets/pnv.wasm

766 Bytes
Binary file not shown.

app/src/main/java/com/credman/cmwallet/data/repository/CredentialRepository.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,19 @@ class CredentialRepository {
8484
val testPhoneNumberTokens = listOf(
8585
PnvTokenRegistry.TEST_PNV_1_GET_PHONE_NUMBER,
8686
PnvTokenRegistry.TEST_PNV_1_VERIFY_PHONE_NUMBER,
87-
PnvTokenRegistry.TEST_PNV_2_GET_PHONE_NUMBER
87+
PnvTokenRegistry.TEST_PNV_2_VERIFY_PHONE_NUMBER
8888
)
8989

90+
// For chrome < 138. Should be removed soon
91+
registryManager.registerCredentials(
92+
request = object : RegisterCredentialsRequest(
93+
"com.credman.IdentityCredential",
94+
"openid4vp1.0-pnv",
95+
PnvTokenRegistry.buildRegistryDatabase(testPhoneNumberTokens),
96+
pnvMatcher
97+
) {}
98+
)
99+
// For native apps and chrome 138+
90100
registryManager.registerCredentials(
91101
request = object : RegisterCredentialsRequest(
92102
DigitalCredential.TYPE_DIGITAL_CREDENTIAL,

app/src/main/java/com/credman/cmwallet/pnv/PnvTokenRegistry.kt

Lines changed: 61 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,25 @@ package com.credman.cmwallet.pnv
33
import android.util.Base64
44
import android.util.Log
55
import androidx.credentials.provider.CallingAppInfo
6-
import com.credman.cmwallet.CmWalletApplication.Companion.TAG
76
import com.credman.cmwallet.CmWalletApplication.Companion.computeClientId
87
import com.credman.cmwallet.createJWTES256
8+
import com.credman.cmwallet.data.repository.CredentialRepository.Companion.ICON
9+
import com.credman.cmwallet.data.repository.CredentialRepository.Companion.LENGTH
10+
import com.credman.cmwallet.data.repository.CredentialRepository.Companion.START
11+
import com.credman.cmwallet.data.repository.CredentialRepository.RegistryIcon
12+
import com.credman.cmwallet.decodeBase64
913
import com.credman.cmwallet.getcred.GetCredentialActivity.DigitalCredentialRequestOptions
1014
import com.credman.cmwallet.getcred.GetCredentialActivity.DigitalCredentialResult
1115
import com.credman.cmwallet.jweSerialization
1216
import com.credman.cmwallet.jwsDeserialization
1317
import com.credman.cmwallet.loadECPrivateKey
1418
import com.credman.cmwallet.openid4vp.OpenId4VP
1519
import com.credman.cmwallet.openid4vp.OpenId4VP.Companion.IDENTIFIERS_1_0
16-
import com.credman.cmwallet.openid4vp.OpenId4VPMatchedCredential
17-
import com.credman.cmwallet.openid4vp.OpenId4VPMatchedMDocClaims
18-
import com.credman.cmwallet.openid4vp.OpenId4VPMatchedSdJwtClaims
1920
import com.credman.cmwallet.pnv.PnvTokenRegistry.Companion.TEST_PNV_1_GET_PHONE_NUMBER
2021
import com.credman.cmwallet.pnv.PnvTokenRegistry.Companion.TEST_PNV_1_VERIFY_PHONE_NUMBER
21-
import com.credman.cmwallet.pnv.PnvTokenRegistry.Companion.TEST_PNV_2_GET_PHONE_NUMBER
22+
import com.credman.cmwallet.pnv.PnvTokenRegistry.Companion.TEST_PNV_2_VERIFY_PHONE_NUMBER
2223
import com.credman.cmwallet.pnv.PnvTokenRegistry.Companion.VCT_GET_PHONE_NUMBER
2324
import com.credman.cmwallet.pnv.PnvTokenRegistry.Companion.VCT_VERIFY_PHONE_NUMBER
24-
import com.credman.cmwallet.toJWK
2525
import kotlinx.serialization.json.buildJsonObject
2626
import kotlinx.serialization.json.put
2727
import org.json.JSONArray
@@ -30,7 +30,6 @@ import java.io.ByteArrayOutputStream
3030
import java.nio.ByteBuffer
3131
import java.nio.ByteOrder
3232
import java.security.interfaces.ECPrivateKey
33-
import java.time.Instant
3433

3534
/**
3635
* A phone number verification entry to be registered with the Credential Manager.
@@ -48,11 +47,14 @@ data class PnvTokenRegistry(
4847
val tokenId: String,
4948
val vct: String,
5049
val title: String,
50+
val subtitle: String? = null,
5151
val providerConsent: String?,
5252
val subscriptionHint: Int,
5353
val carrierHint: String,
5454
val phoneNumberHint: String?,
5555
val iss: String,
56+
val icon: String? = null,
57+
val phoneNumberAttributeDisplayName: String, // Should be localized
5658
) {
5759
/** Converts this TS43 entry to the more generic SD-JWT registry item(s). */
5860
private fun toSdJwtRegistryItems(): SdJwtRegistryItem {
@@ -65,7 +67,7 @@ data class PnvTokenRegistry(
6567
RegistryClaim("carrier_hint", null, carrierHint),
6668
RegistryClaim("phone_number_hint", null, phoneNumberHint),
6769
),
68-
displayData = ItemDisplayData(title = title, subtitle = null, description = providerConsent),
70+
displayData = ItemDisplayData(title = title, subtitle = subtitle, description = providerConsent),
6971
)
7072
}
7173

@@ -82,51 +84,79 @@ data class PnvTokenRegistry(
8284
internal const val PATHS = "paths"
8385
internal const val VALUE = "value"
8486
internal const val DISPLAY = "display"
87+
internal const val SHARED_ATTRIBUTE_DISPLAY_NAME = "shared_attribute_display_name"
8588

8689
val TEST_PNV_1_GET_PHONE_NUMBER = PnvTokenRegistry(
8790
tokenId = "pnv_1",
8891
vct = VCT_GET_PHONE_NUMBER,
89-
title = "Phone Number",
90-
providerConsent = "CMWallet will enable your carrier {carrier name} to share your phone number iwth {app/domain name}",
92+
title = "Terrific Telecom",
93+
subtitle = "+16502154321",
94+
providerConsent = "CMWallet will enable your carrier (Terrific Telecom) to share your phone number",
9195
subscriptionHint = 1,
9296
carrierHint = "310250",
9397
phoneNumberHint = "+16502154321",
94-
iss = "https://example-carrier2.com",
98+
iss = "https://example.terrific-telecom.dev",
99+
icon = "iVBORw0KGgoAAAANSUhEUgAAAA4AAAAWCAYAAADwza0nAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAACBSURBVHgB7ZTBDUVQEEXvff8VoITfC0YZatABHWiH6IUOaICHBCuReXbEWd1kcmZmMRmGIinhSpABFBDoJpqccSKtA/7wY7C71FQ1NUaUyKIg4Ba8MbiJ3YPnqvcnfuI7xAfdqr0qXjU9FTXTGUnca//NIYGdoflla1BbDsPIqZgBHcEomi+uUHMAAAAASUVORK5CYII=",
100+
phoneNumberAttributeDisplayName = "Phone number",
95101
)
96102
val TEST_PNV_1_VERIFY_PHONE_NUMBER = TEST_PNV_1_GET_PHONE_NUMBER.copy(
97103
vct = VCT_VERIFY_PHONE_NUMBER,
98104
)
99-
val TEST_PNV_2_GET_PHONE_NUMBER = PnvTokenRegistry(
105+
val TEST_PNV_2_VERIFY_PHONE_NUMBER = PnvTokenRegistry(
100106
tokenId = "pnv_2",
101-
vct = VCT_GET_PHONE_NUMBER,
102-
title = "Phone Number",
103-
providerConsent = "CMWallet will enable your carrier MOCK-CARRIER-2 to share your phone number",
107+
vct = VCT_VERIFY_PHONE_NUMBER,
108+
title = "Work Number",
109+
subtitle = "Timely Telecom",
110+
providerConsent = "CMWallet will enable your carrier (Timely Telecom) to share your phone number",
104111
subscriptionHint = 2,
105112
carrierHint = "380250",
106113
phoneNumberHint = "+16502157890",
107-
iss = "https://example-carrier2.com"
114+
iss = "https://example.timely-telecom.dev",
115+
icon = "iVBORw0KGgoAAAANSUhEUgAAAA4AAAAWCAYAAADwza0nAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAACBSURBVHgB7ZTBDUVQEEXvff8VoITfC0YZatABHWiH6IUOaICHBCuReXbEWd1kcmZmMRmGIinhSpABFBDoJpqccSKtA/7wY7C71FQ1NUaUyKIg4Ba8MbiJ3YPnqvcnfuI7xAfdqr0qXjU9FTXTGUnca//NIYGdoflla1BbDsPIqZgBHcEomi+uUHMAAAAASUVORK5CYII=",
116+
phoneNumberAttributeDisplayName = "Phone number",
108117
)
109118

110119
fun buildRegistryDatabase(items: List<PnvTokenRegistry>): ByteArray {
111120
val out = ByteArrayOutputStream()
112121

113-
// We don't support icon for phone number tokens, yet
122+
val iconMap: Map<String, RegistryIcon> = items.associate {
123+
Pair(
124+
it.tokenId,
125+
RegistryIcon(it.icon?.decodeBase64() ?: ByteArray(0))
126+
)
127+
}
128+
114129
// Write the offset to the json
115-
val jsonOffset = 4
130+
val jsonOffset = 4 + iconMap.values.sumOf { it.iconValue.size }
116131
val buffer = ByteBuffer.allocate(4)
117132
buffer.order(ByteOrder.LITTLE_ENDIAN)
118133
buffer.putInt(jsonOffset)
119134
out.write(buffer.array())
120135

136+
// Write the icons
137+
var currIconOffset = 4
138+
iconMap.values.forEach {
139+
it.iconOffset = currIconOffset
140+
out.write(it.iconValue)
141+
currIconOffset += it.iconValue.size
142+
}
143+
121144
val sdJwtCredentials = JSONObject()
122-
for (item in items.map { it.toSdJwtRegistryItems() }) {
145+
for (item in items) {
146+
val sdJwtRegistryItem = item.toSdJwtRegistryItems()
123147
val credJson = JSONObject()
124-
credJson.put(ID, item.id)
125-
credJson.put(TITLE, item.displayData.title)
126-
credJson.putOpt(SUBTITLE, item.displayData.subtitle)
127-
credJson.putOpt(DISCLAIMER, item.displayData.description)
148+
credJson.put(SHARED_ATTRIBUTE_DISPLAY_NAME, item.phoneNumberAttributeDisplayName)
149+
credJson.put(ID, sdJwtRegistryItem.id)
150+
credJson.put(TITLE, sdJwtRegistryItem.displayData.title)
151+
credJson.putOpt(SUBTITLE, sdJwtRegistryItem.displayData.subtitle)
152+
credJson.putOpt(DISCLAIMER, sdJwtRegistryItem.displayData.description)
153+
val iconJson = JSONObject().apply {
154+
put(START, iconMap[sdJwtRegistryItem.id]!!.iconOffset)
155+
put(LENGTH, iconMap[sdJwtRegistryItem.id]!!.iconValue.size)
156+
}
157+
credJson.putOpt(ICON, iconJson)
128158
val paths = JSONObject()
129-
for (claim in item.claims) {
159+
for (claim in sdJwtRegistryItem.claims) {
130160
paths.put(claim.path, JSONObject().putOpt(DISPLAY, claim.display).putOpt(VALUE, claim.value))
131161
}
132162

@@ -153,7 +183,11 @@ private class RegistryClaim(
153183
val value: Any?,
154184
)
155185

156-
private class ItemDisplayData(val title: String, val subtitle: String?, val description: String?)
186+
private class ItemDisplayData(
187+
val title: String,
188+
val subtitle: String?,
189+
val description: String?,
190+
)
157191

158192
private class SdJwtRegistryItem(
159193
val id: String,
@@ -170,7 +204,7 @@ fun maybeHandlePnv(
170204
origin: String, // Either the web origin or the calling app sha
171205
callingAppInfo: CallingAppInfo
172206
): DigitalCredentialResult? {
173-
if (selectedID != TEST_PNV_1_GET_PHONE_NUMBER.tokenId && selectedID != TEST_PNV_2_GET_PHONE_NUMBER.tokenId) {
207+
if (selectedID != TEST_PNV_1_GET_PHONE_NUMBER.tokenId && selectedID != TEST_PNV_2_VERIFY_PHONE_NUMBER.tokenId) {
174208
return null
175209
}
176210
val digitalCredentialOptions = DigitalCredentialRequestOptions.createFrom(requestJson)
@@ -199,7 +233,7 @@ fun maybeHandlePnv(
199233

200234
val selectedCred = when (selectedID) {
201235
TEST_PNV_1_GET_PHONE_NUMBER.tokenId -> if (vct == TEST_PNV_1_GET_PHONE_NUMBER.vct) TEST_PNV_1_GET_PHONE_NUMBER else TEST_PNV_1_VERIFY_PHONE_NUMBER
202-
TEST_PNV_2_GET_PHONE_NUMBER.tokenId -> TEST_PNV_2_GET_PHONE_NUMBER
236+
TEST_PNV_2_VERIFY_PHONE_NUMBER.tokenId -> TEST_PNV_2_VERIFY_PHONE_NUMBER
203237
else -> return null
204238
}
205239

matcher/credentialmanager.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ __attribute__((import_module("credman"), import_name("AddPaymentEntry")))
5151
#endif
5252
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);
5353

54+
#if defined(__wasm__)
55+
__attribute__((import_module("credman"), import_name("SetAdditionalDisclaimerAndUrlForVerificationEntry")))
56+
#endif
57+
void SetAdditionalDisclaimerAndUrlForVerificationEntry(char *cred_id, char *secondary_disclaimer, char *url_display_text, char *url_value);
5458

5559
typedef struct CallingAppInfo {
5660
char package_name[256];

matcher/pnv/dcql.c

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include <stdio.h>
22
#include <string.h>
33

4+
#include "../base64.h"
45
#include "../dcql.h"
56

67
#include "../cJSON/cJSON.h"
@@ -34,6 +35,9 @@ cJSON* MatchCredential(cJSON* credential, cJSON* credential_store) {
3435
}
3536

3637
// Filter by meta
38+
cJSON* aggregator_consent = NULL;
39+
cJSON* aggregator_policy_url = NULL;
40+
cJSON* aggregator_policy_text = NULL;
3741
if (meta != NULL) {
3842
if (strcmp(format, "dc+sd-jwt-pnv") == 0) {
3943
cJSON* vct_values_obj = cJSON_GetObjectItemCaseSensitive(meta, "vct_values");
@@ -47,6 +51,26 @@ cJSON* MatchCredential(cJSON* credential, cJSON* credential_store) {
4751
cJSON_AddItemReferenceToArray(candidates, curr_candidate);
4852
}
4953
}
54+
if (cJSON_HasObjectItem(meta, "credential_authorization_jwt")) {
55+
char* cred_auth_jwt = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(meta, "credential_authorization_jwt"));
56+
int delimiter = '.';
57+
char* payload_start = strchr(cred_auth_jwt, delimiter);
58+
payload_start++;
59+
char* payload_end = strchr(payload_start, delimiter);
60+
*payload_end = '\0';
61+
char* decoded_cred_auth_json;
62+
int decoded_cred_auth_json_len = B64DecodeURL(payload_start, &decoded_cred_auth_json);
63+
cJSON* cred_auth_json = cJSON_Parse(decoded_cred_auth_json);
64+
if (cJSON_HasObjectItem(cred_auth_json, "consent_data")) {
65+
char* consent_data = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(cred_auth_json, "consent_data"));
66+
char* decoded_consent_data_json;
67+
int decoded_consent_data_json_len = B64DecodeURL(consent_data, &decoded_consent_data_json);
68+
cJSON* consent_data_json = cJSON_Parse(decoded_consent_data_json);
69+
aggregator_consent = cJSON_GetObjectItemCaseSensitive(consent_data_json, "consent_text");
70+
aggregator_policy_url = cJSON_GetObjectItemCaseSensitive(consent_data_json, "policy_link");
71+
aggregator_policy_text = cJSON_GetObjectItemCaseSensitive(consent_data_json, "policy_text");
72+
}
73+
}
5074
} else {
5175
return matched_credentials;
5276
}
@@ -69,9 +93,12 @@ cJSON* MatchCredential(cJSON* credential, cJSON* credential_store) {
6993
cJSON_AddItemReferenceToObject(matched_credential, "subtitle", cJSON_GetObjectItemCaseSensitive(candidate, "subtitle"));
7094
cJSON_AddItemReferenceToObject(matched_credential, "disclaimer", cJSON_GetObjectItemCaseSensitive(candidate, "disclaimer"));
7195
cJSON_AddItemReferenceToObject(matched_credential, "icon", cJSON_GetObjectItemCaseSensitive(candidate, "icon"));
96+
cJSON_AddItemReferenceToObject(matched_credential, "aggregator_consent", aggregator_consent);
97+
cJSON_AddItemReferenceToObject(matched_credential, "aggregator_policy_text", aggregator_policy_text);
98+
cJSON_AddItemReferenceToObject(matched_credential, "aggregator_policy_url", aggregator_policy_url);
7299
cJSON* matched_claim_names = cJSON_CreateArray();
73100
//printf("candidate %s\n", cJSON_Print(candidate));
74-
cJSON_AddItemReferenceToArray(matched_claim_names, cJSON_GetObjectItemCaseSensitive(candidate, "title"));
101+
cJSON_AddItemReferenceToArray(matched_claim_names, cJSON_GetObjectItemCaseSensitive(candidate, "shared_attribute_display_name"));
75102
cJSON_AddItemReferenceToObject(matched_credential, "matched_claim_names", matched_claim_names);
76103
cJSON_AddItemReferenceToArray(matched_credentials, matched_credential);
77104
}
@@ -85,8 +112,11 @@ cJSON* MatchCredential(cJSON* credential, cJSON* credential_store) {
85112
cJSON_AddItemReferenceToObject(matched_credential, "subtitle", cJSON_GetObjectItemCaseSensitive(candidate, "subtitle"));
86113
cJSON_AddItemReferenceToObject(matched_credential, "disclaimer", cJSON_GetObjectItemCaseSensitive(candidate, "disclaimer"));
87114
cJSON_AddItemReferenceToObject(matched_credential, "icon", cJSON_GetObjectItemCaseSensitive(candidate, "icon"));
115+
cJSON_AddItemReferenceToObject(matched_credential, "aggregator_consent", aggregator_consent);
116+
cJSON_AddItemReferenceToObject(matched_credential, "aggregator_policy_text", aggregator_policy_text);
117+
cJSON_AddItemReferenceToObject(matched_credential, "aggregator_policy_url", aggregator_policy_url);
88118
cJSON* matched_claim_names = cJSON_CreateArray();
89-
cJSON_AddItemReferenceToArray(matched_claim_names, cJSON_GetObjectItemCaseSensitive(candidate, "title"));
119+
cJSON_AddItemReferenceToArray(matched_claim_names, cJSON_GetObjectItemCaseSensitive(candidate, "shared_attribute_display_name"));
90120

91121
cJSON* claim;
92122
cJSON* candidate_claims = cJSON_GetObjectItemCaseSensitive(candidate, "paths");
@@ -134,6 +164,9 @@ cJSON* MatchCredential(cJSON* credential, cJSON* credential_store) {
134164
cJSON_AddItemReferenceToObject(matched_credential, "subtitle", cJSON_GetObjectItemCaseSensitive(candidate, "subtitle"));
135165
cJSON_AddItemReferenceToObject(matched_credential, "disclaimer", cJSON_GetObjectItemCaseSensitive(candidate, "disclaimer"));
136166
cJSON_AddItemReferenceToObject(matched_credential, "icon", cJSON_GetObjectItemCaseSensitive(candidate, "icon"));
167+
cJSON_AddItemReferenceToObject(matched_credential, "aggregator_consent", aggregator_consent);
168+
cJSON_AddItemReferenceToObject(matched_credential, "aggregator_policy_text", aggregator_policy_text);
169+
cJSON_AddItemReferenceToObject(matched_credential, "aggregator_policy_url", aggregator_policy_url);
137170
cJSON* matched_claim_ids = cJSON_CreateObject();
138171

139172
cJSON* claim;
@@ -179,7 +212,7 @@ cJSON* MatchCredential(cJSON* credential, cJSON* credential_store) {
179212
}
180213
}
181214
if (matched_claim_count == cJSON_GetArraySize(claim_set)) {
182-
cJSON_AddItemReferenceToArray(matched_claim_names, cJSON_GetObjectItemCaseSensitive(candidate, "title"));
215+
cJSON_AddItemReferenceToArray(matched_claim_names, cJSON_GetObjectItemCaseSensitive(candidate, "shared_attribute_display_name"));
183216
cJSON_AddItemReferenceToObject(matched_credential, "matched_claim_names", matched_claim_names);
184217
cJSON_AddItemReferenceToArray(matched_credentials, matched_credential);
185218
break;

matcher/pnv/openid4vp1_0.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ int main() {
163163
char *title = cJSON_GetStringValue(cJSON_GetObjectItem(c, "title"));
164164
char *subtitle = cJSON_GetStringValue(cJSON_GetObjectItem(c, "subtitle"));
165165
char *disclaimer = cJSON_GetStringValue(cJSON_GetObjectItem(c, "disclaimer"));
166+
char *aggregator_consent = cJSON_GetStringValue(cJSON_GetObjectItem(c, "aggregator_consent"));
167+
char *aggregator_policy_url = cJSON_GetStringValue(cJSON_GetObjectItem(c, "aggregator_policy_url"));
168+
char *aggregator_policy_text = cJSON_GetStringValue(cJSON_GetObjectItem(c, "aggregator_policy_text"));
166169
cJSON* icon = cJSON_GetObjectItem(c, "icon");
167170
int icon_start_int = 0;
168171
int icon_len = 0;
@@ -177,6 +180,7 @@ int main() {
177180
}
178181
matched = 1;
179182
AddStringIdEntry(id, creds_blob + icon_start_int, icon_len, title, subtitle, disclaimer, NULL);
183+
SetAdditionalDisclaimerAndUrlForVerificationEntry(id, aggregator_consent, aggregator_policy_text, aggregator_policy_url);
180184
cJSON *matched_claim_names = cJSON_GetObjectItem(c, "matched_claim_names");
181185
cJSON *claim;
182186
cJSON_ArrayForEach(claim, matched_claim_names) {

0 commit comments

Comments
 (0)