Skip to content
This repository was archived by the owner on Mar 5, 2025. It is now read-only.

Commit

Permalink
[Android] In person share and verify mDLs (#113)
Browse files Browse the repository at this point in the history
This updates the app to share and verify mDLs.
  • Loading branch information
Juliano1612 authored Feb 17, 2025
1 parent bf841b1 commit 6b170dc
Show file tree
Hide file tree
Showing 24 changed files with 1,150 additions and 1,062 deletions.
2 changes: 1 addition & 1 deletion MobileSdk/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ android {
}

dependencies {
api("com.spruceid.mobile.sdk.rs:mobilesdkrs:0.8.3")
api("com.spruceid.mobile.sdk.rs:mobilesdkrs:0.8.5")
//noinspection GradleCompatible
implementation("com.android.support:appcompat-v7:28.0.0")
/* Begin UI dependencies */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ import android.app.Application
import android.bluetooth.BluetoothManager
import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
import com.spruceid.mobile.sdk.rs.CryptoCurveUtils
import com.spruceid.mobile.sdk.rs.ItemsRequest
import com.spruceid.mobile.sdk.rs.MdlPresentationSession
import com.spruceid.mobile.sdk.rs.Mdoc
import com.spruceid.mobile.sdk.rs.ParsedCredential
import com.spruceid.mobile.sdk.rs.initializeMdlPresentationFromBytes
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
import java.security.KeyStore
import java.security.Signature
import java.util.UUID
Expand All @@ -35,7 +34,17 @@ class CredentialsViewModel(application: Application) : AndroidViewModel(applicat
val itemsRequest = _itemsRequests.asStateFlow()

private val _allowedNamespaces =
MutableStateFlow<Map<String, Map<String, List<String>>>>(mapOf())
MutableStateFlow<Map<String, Map<String, List<String>>>>(
mapOf(
Pair(
"org.iso.18013.5.1.mDL",
mapOf(
Pair("org.iso.18013.5.1", listOf()),
Pair("org.iso.18013.5.1.aamva", listOf())
)
)
)
)
val allowedNamespaces = _allowedNamespaces.asStateFlow()

private val _uuid = MutableStateFlow<UUID>(UUID.randomUUID())
Expand All @@ -57,25 +66,39 @@ class CredentialsViewModel(application: Application) : AndroidViewModel(applicat
}

fun toggleAllowedNamespace(docType: String, specName: String, fieldName: String) {
if (_allowedNamespaces.value.isEmpty()) {
_allowedNamespaces.value = mapOf(Pair(docType, mapOf(Pair(specName, listOf()))))
}
val allowedForSpec = _allowedNamespaces.value[docType]!![specName]

if (!allowedForSpec!!.contains(fieldName)) {
_allowedNamespaces.value = mapOf(
Pair(
docType,
mapOf(Pair(specName, allowedForSpec.plus(fieldName)))
)
)
_allowedNamespaces.value = _allowedNamespaces.value.toMutableMap().apply {
this[docType] = this[docType]?.toMutableMap()?.apply {
this[specName] = (this[specName] ?: emptyList()) + fieldName
} ?: mapOf(specName to listOf(fieldName))
}
} else {
_allowedNamespaces.value = mapOf(
Pair(
docType,
mapOf(Pair(specName, allowedForSpec.minus(fieldName)))
)
)
_allowedNamespaces.value = _allowedNamespaces.value.toMutableMap().apply {
this[docType] = this[docType]?.toMutableMap()?.apply {
this[specName] = this[specName]?.filter { it != fieldName } ?: emptyList()
} ?: mapOf(specName to listOf())
}
}
}

fun addAllAllowedNamespaces(
docType: String,
namespace: Map<String, Map<String, Boolean>>
) {
_allowedNamespaces.value = _allowedNamespaces.value.toMutableMap().apply {
val existingSpecs = this[docType]?.toMutableMap() ?: mutableMapOf()

namespace.forEach { (specName, fields) ->
val existingFields = existingSpecs[specName]?.toMutableList() ?: mutableListOf()

// Add to the list ignoring the boolean value
existingFields.addAll(fields.keys.filter { it !in existingFields })

existingSpecs[specName] = existingFields
}

this[docType] = existingSpecs
}
}

Expand Down Expand Up @@ -144,10 +167,13 @@ class CredentialsViewModel(application: Application) : AndroidViewModel(applicat
}

try {
val signer = Signature.getInstance("SHA256withECDSA")
signer.initSign(entry.privateKey)
signer.update(payload)
val signature = signer.sign()
val derSigner = Signature.getInstance("SHA256withECDSA")
derSigner.initSign(entry.privateKey)
derSigner.update(payload)
val derSignature = derSigner.sign()
val signature =
CryptoCurveUtils.secp256r1()
.ensureRawFixedWidthSignatureEncoding(bytes = derSignature)!!
val response = _session.value!!.submitResponse(signature)
_transport.value!!.send(response)
_currState.value = PresentmentState.SUCCESS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.bluetooth.BluetoothManager
import android.content.Context
import android.util.Log
import com.spruceid.mobile.sdk.rs.MDocItem
import com.spruceid.mobile.sdk.rs.MdlReaderResponseData
import com.spruceid.mobile.sdk.rs.MdlReaderResponseException
import com.spruceid.mobile.sdk.rs.MdlSessionManager
import com.spruceid.mobile.sdk.rs.establishSession
Expand Down Expand Up @@ -51,4 +52,12 @@ class IsoMdlReader(
throw e
}
}

fun handleMdlReaderResponseData(response: ByteArray): MdlReaderResponseData {
try {
return com.spruceid.mobile.sdk.rs.handleResponse(session, response)
} catch (e: MdlReaderResponseException) {
throw e
}
}
}
33 changes: 33 additions & 0 deletions MobileSdk/src/main/java/com/spruceid/mobile/sdk/Utils.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package com.spruceid.mobile.sdk

import com.spruceid.mobile.sdk.rs.MDocItem
import org.json.JSONArray
import org.json.JSONObject


fun hexToByteArray(value: String): ByteArray {
val stripped = value.substring(2)
Expand Down Expand Up @@ -28,3 +32,32 @@ enum class PresentmentState {
/// App should display a success message and offer to close the page
SUCCESS,
}

// Recursive function to convert MDocItem to JSONObject
fun mDocItemToJson(item: MDocItem): Any {
return when (item) {
is MDocItem.Text -> item.v1
is MDocItem.Bool -> item.v1
is MDocItem.Integer -> item.v1
is MDocItem.ItemMap -> mapToJson(item.v1)
is MDocItem.Array -> JSONArray(item.v1.map { mDocItemToJson(it) })
}
}

// Convert Map<String, MDocItem> to JSONObject
fun mapToJson(map: Map<String, MDocItem>): JSONObject {
val jsonObject = JSONObject()
for ((key, value) in map) {
jsonObject.put(key, mDocItemToJson(value))
}
return jsonObject
}

// Convert Map<String, Map<String, MDocItem>> to JSONObject
fun convertToJson(map: Map<String, Map<String, MDocItem>>): JSONObject {
val jsonObject = JSONObject()
for ((key, value) in map) {
jsonObject.put(key, mapToJson(value))
}
return jsonObject
}
4 changes: 2 additions & 2 deletions example/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ android {
applicationId = "com.spruceid.mobilesdkexample"
minSdk = 26
targetSdk = 34
versionCode = 28
versionName = "1.5.4"
versionCode = 29
versionName = "1.7.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ import com.spruceid.mobilesdkexample.ui.theme.ColorBlue900
import com.spruceid.mobilesdkexample.ui.theme.Switzer
import com.spruceid.mobilesdkexample.verifier.VerifierHomeView
import com.spruceid.mobilesdkexample.viewmodels.CredentialPacksViewModel
import com.spruceid.mobilesdkexample.viewmodels.StatusListViewModel
import com.spruceid.mobilesdkexample.viewmodels.HelpersViewModel
import com.spruceid.mobilesdkexample.viewmodels.StatusListViewModel
import com.spruceid.mobilesdkexample.viewmodels.VerificationMethodsViewModel
import com.spruceid.mobilesdkexample.viewmodels.WalletActivityLogsViewModel
import com.spruceid.mobilesdkexample.wallet.WalletHomeView
Expand Down Expand Up @@ -173,5 +173,4 @@ fun HomeBottomTabs(
}
}
}

}
Loading

0 comments on commit 6b170dc

Please sign in to comment.