Skip to content

Commit 647426d

Browse files
committed
Update the registry call to use the latest Android Jetpack API
1 parent d17ac46 commit 647426d

File tree

9 files changed

+256
-217
lines changed

9 files changed

+256
-217
lines changed

app/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,11 @@ dependencies {
4848
implementation(libs.ktor.serialization.kotlinx.json)
4949
implementation(libs.androidx.lifecycle.viewmodel.compose)
5050
implementation(libs.androidx.registry.digitalcredentials.mdoc)
51+
implementation(libs.androidx.registry.digitalcredentials.sdjwt)
52+
implementation(libs.androidx.registry.digitalcredentials.openid)
5153
implementation(libs.androidx.registry.provider)
5254
implementation(libs.androidx.credentials)
55+
implementation(libs.androidx.credentials.play)
5356
implementation(libs.play.services.identity.credentials)
5457
implementation(libs.androidx.registry.provider.play.services)
5558
implementation(libs.lifecycle.runtime.ktx)

app/src/main/java/com/credman/cmwallet/CmWalletApplication.kt

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import androidx.core.graphics.drawable.toBitmap
77
import androidx.credentials.DigitalCredential
88
import androidx.credentials.ExperimentalDigitalCredentialApi
99
import androidx.credentials.provider.CallingAppInfo
10+
import androidx.credentials.registry.digitalcredentials.openid4vp.OpenId4VpRegistry
1011
import androidx.credentials.registry.provider.RegisterCredentialsRequest
1112
import androidx.credentials.registry.provider.RegistryManager
13+
import androidx.credentials.registry.provider.digitalcredentials.DigitalCredentialRegistry
1214
import androidx.room.Room
1315
import com.credman.cmwallet.data.repository.CredentialRepository
1416
import com.credman.cmwallet.data.repository.CredentialRepository.Companion.ICON
@@ -39,6 +41,8 @@ class CmWalletApplication : Application() {
3941
lateinit var database: CredentialDatabase
4042
lateinit var credentialRepo: CredentialRepository
4143

44+
lateinit var walletIcon: Bitmap
45+
4246
fun computeClientId(callingAppInfo: CallingAppInfo): String {
4347
val origin = callingAppInfo.getOrigin(credentialRepo.privAppsJson)
4448
return if (origin == null) {
@@ -61,6 +65,8 @@ class CmWalletApplication : Application() {
6165
override fun onCreate() {
6266
super.onCreate()
6367

68+
walletIcon = resources.getDrawable(R.mipmap.ic_launcher, theme).toBitmap()
69+
6470
val testIssuerSignedString =
6571
"ompuYW1lU3BhY2VzoXFvcmcuaXNvLjE4MDEzLjUuMYPYGFhUpGhkaWdlc3RJRABmcmFuZG9tUKRsGD3aPLpwu_wGZyvuvdxxZWxlbWVudElkZW50aWZpZXJrZmFtaWx5X25hbWVsZWxlbWVudFZhbHVlZVNtaXRo2BhYUaRoZGlnZXN0SUQBZnJhbmRvbVAQwZXPLt5ybFSqRvFVCnPocWVsZW1lbnRJZGVudGlmaWVyamdpdmVuX25hbWVsZWxlbWVudFZhbHVlY0pvbtgYWE-kaGRpZ2VzdElEAmZyYW5kb21QPNysOvdkUbmuOPhvyXsrAHFlbGVtZW50SWRlbnRpZmllcmthZ2Vfb3Zlcl8yMWxlbGVtZW50VmFsdWX1amlzc3VlckF1dGiEQ6EBJqEYIVkCSzCCAkcwggHtoAMCAQICFHStD_3VcEOVnxRIW57aoGfaMp7FMAoGCCqGSM49BAMCMHkxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRwwGgYDVQQKDBNEaWdpdGFsIENyZWRlbnRpYWxzMR8wHQYDVQQDDBZkaWdpdGFsY3JlZGVudGlhbHMuZGV2MB4XDTI0MTExMDAxMDgwM1oXDTM0MTAyOTAxMDgwM1oweTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxHDAaBgNVBAoME0RpZ2l0YWwgQ3JlZGVudGlhbHMxHzAdBgNVBAMMFmRpZ2l0YWxjcmVkZW50aWFscy5kZXYwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATrQ6h60nar2xgrGpTMbRRYLBtWyfkHw2k4QzZc40EsBJNeDp-WXKz85dJjNloCsC7Ckb1spirxQdKVPWy2eRBpo1MwUTAdBgNVHQ4EFgQUCyxw_AMcbG8Lp1EwUuOaRBk527AwHwYDVR0jBBgwFoAUCyxw_AMcbG8Lp1EwUuOaRBk527AwDwYDVR0TAQH_BAUwAwEB_zAKBggqhkjOPQQDAgNIADBFAiEA_JW68hhRYz9l2scu8yW55xi7yyq7ycHg6arTH4b75zMCIG5DADVEbdGnoh6rzTKUdXEh2EnsgjERk6vH6u25Y4fLWQG62BhZAbWmZ3ZlcnNpb25jMS4wb2RpZ2VzdEFsZ29yaXRobWdTSEEtMjU2Z2RvY1R5cGV1b3JnLmlzby4xODAxMy41LjEubURMbHZhbHVlRGlnZXN0c6Fxb3JnLmlzby4xODAxMy41LjGjAFgg-TGk78sfX6xxEfdjckEmDSfiVWzOGIIwTqm0oQetoR8BWCAcX3iJNwCyYOy1Bfl9sAjv1lEuD7iXI5dJbkwPUB6-RwJYIGFOQ5HGtkmhrJWuJ6eTdM2PC_lAIDR5_9pWUiRogpWwbWRldmljZUtleUluZm-haWRldmljZUtleaQBAiABIVggdO8Xw9vvSFlJ9WC7Jd69A_jZ8fbaDi54X92jIbkJmxoiWCAMTMw-ipf52P1MpCfqncpCKgmnEXVhBruNhKLUYs3VhWx2YWxpZGl0eUluZm-jZnNpZ25lZMB4GzIwMjQtMTEtMTdUMjA6NTI6MjIuOTE5NzgyWml2YWxpZEZyb23AeBsyMDI0LTExLTE3VDIwOjUyOjIyLjkxOTc4OVpqdmFsaWRVbnRpbMB4GzIwMzQtMTEtMDVUMjA6NTI6MjIuOTE5Nzg5WlhAl1Lt2d0SSsbuMizlTkVeLR7wucamVyUhyHm6PdG1W0YWXIxfLGwP0rG7Zhpuomh5kpItM7lRdR_FdkJHXO81MQ"
6672
val testIssuerSigned = testIssuerSignedString.decodeBase64UrlNoPadding()
@@ -85,24 +91,14 @@ class CmWalletApplication : Application() {
8591

8692
// Listen for new credentials and update the registry.
8793
applicationScope.launch {
88-
credentialRepo.credentialRegistryDatabase.collect { credentialDatabase ->
89-
Log.i(TAG, "Credentials changed $credentialDatabase")
90-
registryManager.registerCredentials(
91-
request = object : RegisterCredentialsRequest(
92-
DigitalCredential.TYPE_DIGITAL_CREDENTIAL,
93-
"openid4vp",
94-
credentialDatabase,
95-
openId4VPDraft24Matcher
96-
) {}
97-
)
98-
registryManager.registerCredentials(
99-
request = object : RegisterCredentialsRequest(
100-
DigitalCredential.TYPE_DIGITAL_CREDENTIAL,
101-
"openid4vp1.0",
102-
credentialDatabase,
103-
openId4VP1_0Matcher
104-
) {}
105-
)
94+
credentialRepo.credentialRegistryDatabase.collect { openid4vpRegistry ->
95+
Log.i(TAG, "Credentials changed $openid4vpRegistry")
96+
// registryManager.registerCredentials(openid4vpRegistry)
97+
registryManager.registerCredentials(object : DigitalCredentialRegistry(
98+
id = openid4vpRegistry.id,
99+
credentials = openid4vpRegistry.credentials,
100+
matcher = openId4VP1_0Matcher
101+
) {})
106102

107103
// Phone number verification demo
108104
credentialRepo.registerPhoneNumberVerification(registryManager, loadPhoneNumberMatcher())

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

Lines changed: 109 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
11
package com.credman.cmwallet.data.repository
22

3-
import android.graphics.Bitmap
4-
import android.os.Build
3+
import android.graphics.BitmapFactory
54
import android.util.Log
6-
import androidx.core.graphics.drawable.toBitmap
75
import androidx.credentials.DigitalCredential
86
import androidx.credentials.ExperimentalDigitalCredentialApi
7+
import androidx.credentials.registry.digitalcredentials.mdoc.MdocEntry
8+
import androidx.credentials.registry.digitalcredentials.mdoc.MdocField
9+
import androidx.credentials.registry.digitalcredentials.mdoc.MdocInlineIssuanceEntry
10+
import androidx.credentials.registry.digitalcredentials.openid4vp.OpenId4VpRegistry
11+
import androidx.credentials.registry.digitalcredentials.sdjwt.SdJwtClaim
12+
import androidx.credentials.registry.digitalcredentials.sdjwt.SdJwtEntry
13+
import androidx.credentials.registry.digitalcredentials.sdjwt.SdJwtInlineIssuanceEntry
914
import androidx.credentials.registry.provider.RegisterCredentialsRequest
1015
import androidx.credentials.registry.provider.RegistryManager
11-
import com.credman.cmwallet.R
16+
import androidx.credentials.registry.provider.digitalcredentials.DigitalCredentialEntry
17+
import androidx.credentials.registry.provider.digitalcredentials.InlineIssuanceEntry
18+
import androidx.credentials.registry.provider.digitalcredentials.VerificationEntryDisplayProperties
19+
import androidx.credentials.registry.provider.digitalcredentials.VerificationFieldDisplayProperties
20+
import com.credman.cmwallet.CmWalletApplication
1221
import com.credman.cmwallet.data.model.CredentialDisplayData
1322
import com.credman.cmwallet.data.model.CredentialItem
1423
import com.credman.cmwallet.data.model.CredentialKeySoftware
@@ -58,10 +67,10 @@ class CredentialRepository {
5867

5968
val credentials: Flow<List<CredentialItem>> = combinedCredentials()
6069

61-
val credentialRegistryDatabase: Flow<ByteArray> = flow {
70+
val credentialRegistryDatabase: Flow<OpenId4VpRegistry> = flow {
6271
emitAll(combinedCredentials().map { credentials ->
6372
Log.i("CredentialRepository", "Updating flow with ${credentials.size}")
64-
createRegistryDatabase(credentials)
73+
createRegistry(credentials)
6574
})
6675
}
6776

@@ -163,151 +172,137 @@ class CredentialRepository {
163172
put(ICON, iconJson)
164173
}
165174

166-
private fun constructJwtForRegistry(
175+
private fun constructJwtClaims(
167176
rawJwt: JSONObject,
168177
displayConfig: CredentialConfigurationSdJwtVc?,
169-
path: JSONArray,
170-
): JSONObject {
171-
val result = JSONObject()
178+
claims: MutableList<SdJwtClaim>,
179+
path: List<String>
180+
) {
172181
for (key in rawJwt.keys()) {
173182
val v = rawJwt[key]
174-
val currPath = JSONArray(path.toString()) // Make a copy
175-
currPath.put(key)
183+
val currPath = path.toMutableList() // Make a copy
184+
currPath.add(key)
176185
if (v is JSONObject) {
177-
result.put(
178-
key,
179-
constructJwtForRegistry(v, displayConfig, currPath)
186+
constructJwtClaims(
187+
v,
188+
displayConfig,
189+
claims,
190+
currPath
180191
)
181192
} else {
182-
result.put(
183-
key,
184-
JSONObject().apply {
185-
val displayName = displayConfig?.claims?.firstOrNull{
186-
JSONArray(it.path) == currPath
187-
}?.display?.first()?.name ?: key
188-
putOpt(DISPLAY, displayName)
189-
putOpt(VALUE, v)
190-
}
193+
val displayName = displayConfig?.claims?.firstOrNull{
194+
JSONArray(it.path) == currPath
195+
}?.display?.first()?.name ?: currPath.joinToString(separator = ".")
196+
claims.add(
197+
SdJwtClaim(
198+
path = currPath,
199+
value = v,
200+
fieldDisplayPropertySet = setOf(VerificationFieldDisplayProperties(
201+
displayName = displayName,
202+
)),
203+
// isSelectivelyDisclosable = TODO()
204+
)
191205
)
192206
}
193207
}
194-
return result
195208
}
196209

197-
/**
198-
* Credential Registry has the following format:
199-
*
200-
* |---------------------------------------|
201-
* |--- (Int) offset of credential json ---|
202-
* |--------- (Byte Array) Icon 1 ---------|
203-
* |--------- (Byte Array) Icon 2 ---------|
204-
* |------------- More Icons... -----------|
205-
* |----------- Credential Json -----------| // See assets/paymentcreds.json as an example
206-
* |---------------------------------------|
207-
*/
208210
@OptIn(ExperimentalEncodingApi::class)
209-
private fun createRegistryDatabase(items: List<CredentialItem>): ByteArray {
210-
val out = ByteArrayOutputStream()
211-
212-
val iconMap: Map<String, RegistryIcon> = items.associate {
213-
Pair(
214-
it.id,
215-
RegistryIcon(it.displayData.icon?.decodeBase64() ?: ByteArray(0))
216-
)
217-
}
218-
// Write the offset to the json
219-
val jsonOffset = 4 + iconMap.values.sumOf { it.iconValue.size }
220-
val buffer = ByteBuffer.allocate(4)
221-
buffer.order(ByteOrder.LITTLE_ENDIAN)
222-
buffer.putInt(jsonOffset)
223-
out.write(buffer.array())
211+
private fun createRegistry(items: List<CredentialItem>): OpenId4VpRegistry {
212+
val credentialEntries: MutableList<DigitalCredentialEntry> = mutableListOf()
224213

225-
// Write the icons
226-
var currIconOffset = 4
227-
iconMap.values.forEach {
228-
it.iconOffset = currIconOffset
229-
out.write(it.iconValue)
230-
currIconOffset += it.iconValue.size
231-
}
232-
233-
val mdocCredentials = JSONObject()
234-
val sdJwtCredentials = JSONObject()
235214
items.forEach { item ->
236215
when (item.config) {
237216
is CredentialConfigurationSdJwtVc -> {
238-
val credJson = JSONObject()
239-
credJson.putCommon(item.id, item.displayData, iconMap)
240217
val sdJwtVc = SdJwt(item.credentials.first().credential, (item.credentials.first().key as CredentialKeySoftware).privateKey)
241218
val rawJwt = sdJwtVc.verifiedResult.processedJwt
242-
val jwtWithDisplay = constructJwtForRegistry(rawJwt, item.config, JSONArray())
243-
// TODO: what do we do with non-user-friendly claims such as iss, aud?
244-
credJson.put(PATHS, jwtWithDisplay)
245-
val vctType = rawJwt["vct"] as String
246-
when (val current = sdJwtCredentials.opt(vctType) ?: JSONArray()) {
247-
is JSONArray -> sdJwtCredentials.put(vctType, current.put(credJson))
248-
else -> throw IllegalStateException("Unexpected type ${current::class.java}")
249-
}
250-
219+
val claims = mutableListOf<SdJwtClaim>()
220+
constructJwtClaims(rawJwt, item.config, claims, emptyList())
221+
credentialEntries.add(SdJwtEntry(
222+
verifiableCredentialType = rawJwt["vct"] as String,
223+
claims = claims,
224+
entryDisplayPropertySet = setOf(VerificationEntryDisplayProperties(
225+
title = item.displayData.title,
226+
subtitle = item.displayData.subtitle,
227+
icon = item.displayData.icon?.decodeBase64()?.let {
228+
BitmapFactory.decodeByteArray(it, 0, it.size)
229+
} ?: CmWalletApplication.walletIcon
230+
)),
231+
id = item.id,
232+
))
251233
}
252234
is CredentialConfigurationMDoc -> {
253-
val credJson = JSONObject()
254-
credJson.putCommon(item.id, item.displayData, iconMap)
255235
val mdoc = MDoc(item.credentials.first().credential.decodeBase64UrlNoPadding())
236+
val mdocFields = mutableListOf<MdocField>()
256237
if (mdoc.issuerSignedNamespaces.isNotEmpty()) {
257-
val pathJson = JSONObject()
258238
mdoc.issuerSignedNamespaces.forEach { (namespace, elements) ->
259-
val namespaceJson = JSONObject()
260239
elements.forEach { (element, value) ->
261-
val namespaceDataJson = JSONObject()
262-
namespaceDataJson.putOpt(VALUE, value)
263240
val displayName = item.config.claims?.firstOrNull{
264241
it.path[0] == namespace && it.path[1] == element
265242
}?.display?.first()?.name!!
266-
namespaceDataJson.put(DISPLAY, displayName)
267-
// namespaceDataJson.putOpt(
268-
// DISPLAY_VALUE,
269-
// namespaceData.value.displayValue
270-
// )
271-
namespaceJson.put(element, namespaceDataJson)
272-
}
273-
pathJson.put(namespace, namespaceJson)
274-
}
275-
credJson.put(PATHS, pathJson)
276-
}
277-
if (Build.VERSION.SDK_INT >= 33) {
278-
mdocCredentials.append(item.config.doctype, credJson)
279-
} else {
280-
when (val current = mdocCredentials.opt(item.config.doctype)) {
281-
is JSONArray -> {
282-
mdocCredentials.put(item.config.doctype, current.put(credJson))
283-
}
284-
285-
null -> {
286-
mdocCredentials.put(
287-
item.config.doctype,
288-
JSONArray().put(credJson)
243+
mdocFields.add(
244+
MdocField(
245+
namespace = namespace,
246+
identifier = element,
247+
fieldValue = value,
248+
fieldDisplayPropertySet = setOf(
249+
VerificationFieldDisplayProperties(
250+
displayName = displayName,
251+
// displayValue = namespaceData.value.displayValue
252+
)
253+
)
254+
)
289255
)
290256
}
291-
292-
else -> throw IllegalStateException(
293-
"Unexpected namespaced data that's" +
294-
" not a JSONArray. Instead it is ${current::class.java}"
295-
)
296257
}
297258
}
259+
credentialEntries.add(MdocEntry(
260+
docType = item.config.doctype,
261+
fields = mdocFields,
262+
entryDisplayPropertySet = setOf(VerificationEntryDisplayProperties(
263+
title = item.displayData.title,
264+
subtitle = item.displayData.subtitle,
265+
icon = item.displayData.icon?.decodeBase64()?.let {
266+
BitmapFactory.decodeByteArray(it, 0, it.size)
267+
} ?: CmWalletApplication.walletIcon
268+
)),
269+
id = item.id,
270+
))
298271
}
299272

300273
is CredentialConfigurationUnknownFormat -> TODO()
301274
}
302275
}
303-
val registryCredentials = JSONObject()
304-
registryCredentials.put("mso_mdoc", mdocCredentials)
305-
registryCredentials.put("dc+sd-jwt", sdJwtCredentials)
306-
val registryJson = JSONObject()
307-
registryJson.put(CREDENTIALS, registryCredentials)
308-
Log.d(TAG, "Credential to be registered: ${registryJson.toString(2)}")
309-
out.write(registryJson.toString().toByteArray())
310-
return out.toByteArray()
276+
return OpenId4VpRegistry(
277+
credentialEntries = credentialEntries,
278+
inlineIssuanceEntries = emptyList(),
279+
// listOf(
280+
// MdocInlineIssuanceEntry(
281+
// id = "Issuance",
282+
// display = InlineIssuanceEntry.InlineIssuanceDisplayProperties(
283+
// subtitle = "Mobile Drivers License, State Id, and Others",
284+
// ),
285+
// supportedMdocs = setOf(
286+
// MdocInlineIssuanceEntry.SupportedMdoc(
287+
// "eu.europa.ec.eudi.pid.1"
288+
// ),
289+
// MdocInlineIssuanceEntry.SupportedMdoc(
290+
// "org.iso.18013.5.1.mDL1"
291+
// ),
292+
// )
293+
// ),
294+
// SdJwtInlineIssuanceEntry(
295+
// id = "sd-jwt-issuance",
296+
// display = InlineIssuanceEntry.InlineIssuanceDisplayProperties(
297+
// subtitle = "Mobile Drivers License, State Id, and Others",
298+
// ),
299+
// supportedSdJwts = setOf(
300+
// SdJwtInlineIssuanceEntry.SupportedSdJwt("urn:openid:interop:id:1")
301+
// )
302+
// )
303+
// ),
304+
id = "openid4vp1.0",
305+
)
311306
}
312307

313308
companion object {

0 commit comments

Comments
 (0)