diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index a563245..040567e 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -4,14 +4,6 @@
-
-
-
-
-
-
-
-
diff --git a/MobileSdk/src/main/java/com/spruceid/mobile/sdk/BaseCredential.kt b/MobileSdk/src/main/java/com/spruceid/mobile/sdk/BaseCredential.kt
deleted file mode 100644
index 79e8657..0000000
--- a/MobileSdk/src/main/java/com/spruceid/mobile/sdk/BaseCredential.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.spruceid.mobile.sdk
-
-open class BaseCredential {
- private var id: String?
-
- constructor() {
- this.id = null
- }
-
- constructor(id: String) {
- this.id = id
- }
-
- fun getId(): String? {
- return this.id
- }
-
- fun setId(id: String) {
- this.id = id
- }
-
- override fun toString(): String {
- return "Credential($id)"
- }
-
- open fun get(keys: List): Map {
- return if (keys.contains("id")) {
- mapOf("id" to this.id!!)
- } else {
- emptyMap()
- }
- }
-}
\ No newline at end of file
diff --git a/MobileSdk/src/main/java/com/spruceid/mobile/sdk/Credential.kt b/MobileSdk/src/main/java/com/spruceid/mobile/sdk/Credential.kt
new file mode 100644
index 0000000..f22db64
--- /dev/null
+++ b/MobileSdk/src/main/java/com/spruceid/mobile/sdk/Credential.kt
@@ -0,0 +1,112 @@
+package com.spruceid.mobile.sdk
+
+import com.spruceid.mobile.sdk.rs.JsonVc
+import com.spruceid.mobile.sdk.rs.JwtVc
+import com.spruceid.mobile.sdk.rs.Mdoc
+import org.json.JSONException
+import org.json.JSONObject
+import org.json.JSONTokener
+
+/**
+ * Access all of the elements in the mdoc, ignoring namespaces and missing elements that cannot be encoded as JSON.
+ */
+fun Mdoc.jsonEncodedDetailsAll(): JSONObject = this.jsonEncodedDetailsInternal(null)
+
+/**
+ * Access the specified elements in the mdoc, ignoring namespaces and missing elements that cannot be encoded as JSON.
+ */
+fun Mdoc.jsonEncodedDetailsFiltered(elementIdentifiers: List): JSONObject = this.jsonEncodedDetailsInternal(elementIdentifiers)
+
+
+private fun Mdoc.jsonEncodedDetailsInternal(elementIdentifiers: List?): JSONObject =
+ JSONObject(
+ // Ignore the namespaces.
+ this.details().values.flatMap { elements ->
+ elements.map { element ->
+ val id = element.identifier
+ val jsonString = element.value
+
+ // If a filter is provided, filter out non-specified ids.
+ if (elementIdentifiers != null) {
+ if (!elementIdentifiers.contains(id)) {
+ return@map null
+ }
+ }
+
+ if (jsonString != null) {
+ try {
+ val jsonElement = JSONTokener(jsonString).nextValue()
+ return@map Pair(id, jsonElement)
+ } catch (e: JSONException) {
+ print("failed to decode '$id' as JSON: $e")
+ }
+ }
+
+ return@map null
+ }
+ }.filterNotNull().toMap()
+ )
+
+/**
+ * Access the W3C VCDM credential (not including the JWT envelope).
+ */
+fun JwtVc.credentialClaims(): JSONObject {
+ try {
+ return JSONObject(this.credentialAsJsonEncodedUtf8String())
+ } catch (e: Error) {
+ print("failed to decode VCDM data from UTF-8-encoded JSON")
+ return JSONObject()
+ }
+}
+
+/**
+ * Access the specified claims from the W3C VCDM credential (not including the JWT envelope).
+ */
+fun JwtVc.credentialClaimsFiltered(claimNames: List): JSONObject {
+ val old = this.credentialClaims()
+ val new = JSONObject()
+ for (name in claimNames) {
+ if (old.has(name)) {
+ new.put(name, keyPathFinder(old, name.split(".").toMutableList()))
+ }
+ }
+ return new
+}
+
+/**
+ * Access the W3C VCDM credential.
+ */
+fun JsonVc.credentialClaims(): JSONObject {
+ try {
+ return JSONObject(this.credentialAsJsonEncodedUtf8String())
+ } catch (e: Error) {
+ print("failed to decode VCDM data from UTF-8-encoded JSON")
+ return JSONObject()
+ }
+}
+
+/**
+ * Access the specified claims from the W3C VCDM credential.
+ */
+fun JsonVc.credentialClaimsFiltered(claimNames: List): JSONObject {
+ val old = this.credentialClaims()
+ val new = JSONObject()
+ for (name in claimNames) {
+ new.put(name, keyPathFinder(old, name.split(".").toMutableList()))
+ }
+ return new
+}
+
+private fun keyPathFinder(json: Any, path: MutableList): Any {
+ try {
+ val firstKey = path.first()
+ val element = (json as JSONObject)[firstKey]
+ path.removeAt(0)
+ if (path.isNotEmpty()) {
+ return keyPathFinder(element, path)
+ }
+ return element
+ } catch (e: Exception) {
+ return ""
+ }
+}
\ No newline at end of file
diff --git a/MobileSdk/src/main/java/com/spruceid/mobile/sdk/CredentialPack.kt b/MobileSdk/src/main/java/com/spruceid/mobile/sdk/CredentialPack.kt
index a1c02e7..acb52e2 100644
--- a/MobileSdk/src/main/java/com/spruceid/mobile/sdk/CredentialPack.kt
+++ b/MobileSdk/src/main/java/com/spruceid/mobile/sdk/CredentialPack.kt
@@ -1,108 +1,98 @@
package com.spruceid.mobile.sdk
-import java.security.KeyFactory
-import java.security.KeyStore
-import java.security.cert.Certificate
-import java.security.cert.CertificateFactory
-import java.security.spec.PKCS8EncodedKeySpec
-import java.util.Base64
+import com.spruceid.mobile.sdk.rs.JsonVc
+import com.spruceid.mobile.sdk.rs.JwtVc
+import com.spruceid.mobile.sdk.rs.Mdoc
+import com.spruceid.mobile.sdk.rs.ParsedCredential
+import org.json.JSONObject
/**
* Collection of BaseCredentials with methods to interact with all instances
*/
class CredentialPack {
- private val credentials: MutableList
+ private val credentials: MutableList
constructor() {
credentials = mutableListOf()
}
- constructor(credentialsArray: MutableList) {
+ constructor(credentialsArray: MutableList) {
this.credentials = credentialsArray
}
- fun addW3CVC(credentialString: String): List {
- val vc = W3CVC(credentialString = credentialString)
- credentials.add(vc)
+ /**
+ * Add a JwtVc to the CredentialPack.
+ */
+ fun addJwtVc(jwtVc: JwtVc): List {
+ credentials.add(ParsedCredential.newJwtVcJson(jwtVc))
return credentials
}
- fun addMDoc(
- id: String,
- mdocBase64: String,
- keyPEM: String,
- keyBase64: String
- ): List {
- try {
- val decodedKey = Base64.getDecoder().decode(
- keyBase64
- )
-
- val privateKey = KeyFactory.getInstance(
- "EC"
- ).generatePrivate(
- PKCS8EncodedKeySpec(
- decodedKey
- )
- )
-
- val cert: Array = arrayOf(
- CertificateFactory.getInstance(
- "X.509"
- ).generateCertificate(
- keyPEM.byteInputStream()
- )
- )
-
- val ks: KeyStore = KeyStore.getInstance(
- "AndroidKeyStore"
- )
-
- ks.load(
- null
- )
-
- ks.setKeyEntry(
- "someAlias",
- privateKey,
- null,
- cert
- )
-
- credentials.add(
- MDoc(
- id,
- Base64.getDecoder().decode(mdocBase64),
- "someAlias"
- )
- )
- } catch (e: Throwable) {
- print(
- e
- )
- throw e
- }
+ /**
+ * Add a JsonVc to the CredentialPack.
+ */
+ fun addJsonVc(jsonVc: JsonVc): List {
+ credentials.add(ParsedCredential.newLdpVc(jsonVc))
return credentials
}
- fun get(keys: List): Map> {
- val values = emptyMap>().toMutableMap()
-
- for (credential in credentials) {
- values[credential.getId()!!] = credential.get(keys)
- }
- return values
- }
-
- fun getCredentialsByIds(credentialsIds: List): List {
- return credentials.filter { credential -> credentialsIds.contains(credential.getId()) }
- }
-
- fun getCredentials(): List {
+ /**
+ * Add an Mdoc to the CredentialPack.
+ */
+ fun addMdoc(mdoc: Mdoc): List {
+ credentials.add(ParsedCredential.newMsoMdoc(mdoc))
return credentials
}
- fun getCredentialById(credentialId: String): BaseCredential? {
- return credentials.find { credential -> credential.getId().equals(credentialId) }
- }
+ /**
+ * Find claims from all credentials in this CredentialPack.
+ */
+ fun findCredentialClaims(claimNames: List): Map =
+ this.list()
+ .map { credential ->
+ var claims: JSONObject
+ val mdoc = credential.asMsoMdoc()
+ val jwtVc = credential.asJwtVc()
+ val jsonVc = credential.asJsonVc()
+
+ if (mdoc != null) {
+ claims = mdoc.jsonEncodedDetailsFiltered(claimNames)
+ } else if (jwtVc != null) {
+ claims = jwtVc.credentialClaimsFiltered(claimNames)
+ } else if (jsonVc != null) {
+ claims = jsonVc.credentialClaimsFiltered(claimNames)
+ } else {
+ var type: String
+ try {
+ type = credential.intoGenericForm().type
+ } catch (e: Error) {
+ type = "unknown"
+ }
+ print("unsupported credential type: $type")
+ claims = JSONObject()
+ }
+
+ return@map Pair(credential.id(), claims)
+ }
+ .toMap()
+
+
+ /**
+ * Get credentials by id.
+ */
+ fun getCredentialsByIds(credentialsIds: List): List =
+ this.list().filter { credential -> credentialsIds.contains(credential.id()) }
+
+
+ /**
+ * Get a credential by id.
+ */
+ fun getCredentialById(credentialId: String): ParsedCredential? =
+ this.list().find { credential -> credential.id() == credentialId }
+
+
+ /**
+ * List all of the credentials in the CredentialPack.
+ */
+ fun list(): List = this.credentials
}
\ No newline at end of file
diff --git a/MobileSdk/src/main/java/com/spruceid/mobile/sdk/CredentialsViewModel.kt b/MobileSdk/src/main/java/com/spruceid/mobile/sdk/CredentialsViewModel.kt
index 8bc1840..ff8a52d 100644
--- a/MobileSdk/src/main/java/com/spruceid/mobile/sdk/CredentialsViewModel.kt
+++ b/MobileSdk/src/main/java/com/spruceid/mobile/sdk/CredentialsViewModel.kt
@@ -5,16 +5,19 @@ import android.util.Log
import androidx.lifecycle.ViewModel
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
class CredentialsViewModel : ViewModel() {
- private val _credentials = MutableStateFlow>(arrayListOf())
+ private val _credentials = MutableStateFlow>(arrayListOf())
val credentials = _credentials.asStateFlow()
private val _currState = MutableStateFlow(PresentmentState.UNINITIALIZED)
@@ -37,10 +40,20 @@ class CredentialsViewModel : ViewModel() {
private val _transport = MutableStateFlow(null)
- fun storeCredential(credential: BaseCredential) {
+ fun storeCredential(credential: ParsedCredential) {
_credentials.value.add(credential)
}
+ private fun firstMdoc(): Mdoc {
+ val mdoc = _credentials.value
+ .map { credential -> credential.asMsoMdoc() }
+ .firstOrNull()
+ if (mdoc == null) {
+ throw Exception("no mdoc found")
+ }
+ return mdoc
+ }
+
fun toggleAllowedNamespace(docType: String, specName: String, fieldName: String) {
if (_allowedNamespaces.value.isEmpty()) {
_allowedNamespaces.value = mapOf(Pair(docType, mapOf(Pair(specName, listOf()))))
@@ -78,8 +91,8 @@ class CredentialsViewModel : ViewModel() {
suspend fun present(bluetoothManager: BluetoothManager) {
Log.d("CredentialsViewModel.present", "Credentials: ${_credentials.value}")
_uuid.value = UUID.randomUUID()
- val first: MDoc = _credentials.value.first() as MDoc
- _session.value = initializeMdlPresentationFromBytes(first.inner, _uuid.value.toString())
+ val mdoc = this.firstMdoc()
+ _session.value = initializeMdlPresentationFromBytes(mdoc, _uuid.value.toString())
_currState.value = PresentmentState.ENGAGING_QR_CODE
_transport.value = Transport(bluetoothManager)
_transport.value!!
@@ -102,7 +115,7 @@ class CredentialsViewModel : ViewModel() {
}
fun submitNamespaces(allowedNamespaces: Map>>) {
- val firstMDoc: MDoc = _credentials.value.first() as MDoc
+ val mdoc = this.firstMdoc()
if(allowedNamespaces.isEmpty()) {
val e = Error("Select at least one namespace")
Log.e("CredentialsViewModel.submitNamespaces", e.toString())
@@ -122,9 +135,9 @@ class CredentialsViewModel : ViewModel() {
null
)
- val entry = ks.getEntry(firstMDoc.keyAlias, null)
+ val entry = ks.getEntry(mdoc.keyAlias(), null)
if (entry !is KeyStore.PrivateKeyEntry) {
- throw IllegalStateException("No such private key under the alias <${firstMDoc.keyAlias}>")
+ throw IllegalStateException("No such private key under the alias <${mdoc.keyAlias()}>")
}
try {
diff --git a/MobileSdk/src/main/java/com/spruceid/mobile/sdk/IsoMdlPresentation.kt b/MobileSdk/src/main/java/com/spruceid/mobile/sdk/IsoMdlPresentation.kt
index 2ff3970..2151915 100644
--- a/MobileSdk/src/main/java/com/spruceid/mobile/sdk/IsoMdlPresentation.kt
+++ b/MobileSdk/src/main/java/com/spruceid/mobile/sdk/IsoMdlPresentation.kt
@@ -4,6 +4,7 @@ import android.bluetooth.BluetoothManager
import android.util.Log
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.RequestException
import com.spruceid.mobile.sdk.rs.initializeMdlPresentationFromBytes
import java.security.KeyStore
@@ -16,7 +17,7 @@ abstract class BLESessionStateDelegate {
}
class IsoMdlPresentation(
- val mdoc: MDoc,
+ val mdoc: Mdoc,
val keyAlias: String,
val bluetoothManager: BluetoothManager,
val callback: BLESessionStateDelegate
@@ -28,7 +29,7 @@ class IsoMdlPresentation(
suspend fun initialize() {
try {
- session = initializeMdlPresentationFromBytes(this.mdoc.inner, uuid.toString())
+ session = initializeMdlPresentationFromBytes(this.mdoc, uuid.toString())
this.bleManager = Transport(this.bluetoothManager)
this.bleManager!!
.initialize(
diff --git a/MobileSdk/src/main/java/com/spruceid/mobile/sdk/MDoc.kt b/MobileSdk/src/main/java/com/spruceid/mobile/sdk/MDoc.kt
deleted file mode 100644
index 7e2529e..0000000
--- a/MobileSdk/src/main/java/com/spruceid/mobile/sdk/MDoc.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.spruceid.mobile.sdk
-
-import android.util.Log
-import com.spruceid.mobile.sdk.rs.Mdoc as InnerMDoc
-
-class MDoc(id: String, issuerAuth: ByteArray, val keyAlias: String) : BaseCredential(id) {
- val inner: InnerMDoc
-
- init {
- try {
- inner = InnerMDoc.fromCborEncodedDocument(issuerAuth, keyAlias)
- } catch (e: Throwable) {
- Log.e("MDoc.init", e.toString())
- throw e
- }
- }
-}
\ No newline at end of file
diff --git a/MobileSdk/src/main/java/com/spruceid/mobile/sdk/W3CVC.kt b/MobileSdk/src/main/java/com/spruceid/mobile/sdk/W3CVC.kt
deleted file mode 100644
index 19cc97e..0000000
--- a/MobileSdk/src/main/java/com/spruceid/mobile/sdk/W3CVC.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-package com.spruceid.mobile.sdk
-
-import org.json.JSONObject
-
-class W3CVC(credentialString: String): BaseCredential() {
- private var credential: JSONObject = JSONObject(credentialString)
-
- init {
- super.setId(credential.getString("id"))
- }
-
- override fun get(keys: List): Map {
- val res = mutableMapOf()
-
- for (key in keys) {
- res[key] = keyPathFinder(credential, key.split(".").toMutableList())
- }
- return res
- }
-
- private fun keyPathFinder(json: Any, path: MutableList): Any {
- try {
- val firstKey = path.first()
- val element = (json as JSONObject)[firstKey]
- path.removeAt(0)
- if (path.isNotEmpty()) {
- return keyPathFinder(element, path)
- }
- return element
- } catch (e: Exception) {
- return ""
- }
- }
-}
diff --git a/MobileSdk/src/main/java/com/spruceid/mobile/sdk/ui/BaseCard.kt b/MobileSdk/src/main/java/com/spruceid/mobile/sdk/ui/BaseCard.kt
index e2f4fc9..7457b76 100644
--- a/MobileSdk/src/main/java/com/spruceid/mobile/sdk/ui/BaseCard.kt
+++ b/MobileSdk/src/main/java/com/spruceid/mobile/sdk/ui/BaseCard.kt
@@ -9,6 +9,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.spruceid.mobile.sdk.CredentialPack
+import org.json.JSONObject
/**
* Data class with the specification to display the credential pack in a list view
@@ -23,13 +24,13 @@ import com.spruceid.mobile.sdk.CredentialPack
*/
data class CardRenderingListView(
val titleKeys: List,
- val titleFormatter: @Composable ((values: Map>) -> Unit)? = null,
+ val titleFormatter: @Composable ((values: Map) -> Unit)? = null,
val descriptionKeys: List? = null,
- val descriptionFormatter: @Composable ((values: Map>) -> Unit)? = null,
+ val descriptionFormatter: @Composable ((values: Map) -> Unit)? = null,
val leadingIconKeys: List? = null,
- val leadingIconFormatter: @Composable ((values: Map>) -> Unit)? = null,
+ val leadingIconFormatter: @Composable ((values: Map) -> Unit)? = null,
val trailingActionKeys: List? = null,
- val trailingActionButton: @Composable ((values: Map>) -> Unit)? = null
+ val trailingActionButton: @Composable ((values: Map) -> Unit)? = null
)
/**
@@ -39,7 +40,7 @@ data class CardRenderingListView(
*/
data class CardRenderingDetailsField(
val keys: List,
- val formatter: @Composable ((values: Map>) -> Unit)? = null
+ val formatter: @Composable ((values: Map) -> Unit)? = null
)
/**
@@ -99,8 +100,8 @@ fun CardListView(
credentialPack: CredentialPack,
rendering: CardRenderingListView
) {
- val titleValues = credentialPack.get(rendering.titleKeys)
- val descriptionValues = credentialPack.get(rendering.descriptionKeys ?: emptyList())
+ val titleValues = credentialPack.findCredentialClaims(rendering.titleKeys)
+ val descriptionValues = credentialPack.findCredentialClaims(rendering.descriptionKeys ?: emptyList())
Row(
Modifier.height(intrinsicSize = IntrinsicSize.Max)
@@ -108,7 +109,7 @@ fun CardListView(
// Leading icon
if(rendering.leadingIconFormatter != null) {
rendering.leadingIconFormatter.invoke(
- credentialPack.get(rendering.leadingIconKeys ?: emptyList())
+ credentialPack.findCredentialClaims(rendering.leadingIconKeys ?: emptyList())
)
}
@@ -118,8 +119,11 @@ fun CardListView(
rendering.titleFormatter.invoke(titleValues)
} else {
Text(text = titleValues.values
- .fold(emptyList()) { acc, next -> acc + next.values
- .joinToString(" ") { value -> value.toString() }
+ .fold(emptyList()) { acc, next -> acc +
+ next.keys()
+ .asSequence()
+ .map { key -> next.get(key)}
+ .joinToString(" ") { value -> value.toString() }
}.joinToString("").trim())
}
@@ -128,8 +132,11 @@ fun CardListView(
rendering.descriptionFormatter.invoke(descriptionValues)
} else {
Text(text = descriptionValues.values
- .fold(emptyList()) { acc, next -> acc + next.values
- .joinToString(" ") { value -> value.toString() }
+ .fold(emptyList()) { acc, next -> acc +
+ next.keys()
+ .asSequence()
+ .map { key -> next.get(key)}
+ .joinToString(" ") { value -> value.toString() }
}.joinToString("").trim())
}
}
@@ -139,7 +146,7 @@ fun CardListView(
// Trailing action button
if(rendering.trailingActionButton != null) {
rendering.trailingActionButton.invoke(
- credentialPack.get(rendering.trailingActionKeys ?: emptyList())
+ credentialPack.findCredentialClaims(rendering.trailingActionKeys ?: emptyList())
)
}
}
@@ -157,14 +164,17 @@ fun CardDetailsView(
) {
Column {
rendering.fields.forEach {
- val values = credentialPack.get(it.keys)
+ val values = credentialPack.findCredentialClaims(it.keys)
if(it.formatter != null) {
it.formatter.invoke(values)
} else {
Text(text = values.values
- .fold(emptyList()) { acc, next -> acc + next.values
- .joinToString(" ") { value -> value.toString() }
+ .fold(emptyList()) { acc, next -> acc +
+ next.keys()
+ .asSequence()
+ .map { key -> next.get(key)}
+ .joinToString(" ") { value -> value.toString() }
}.joinToString("").trim())
}
}
diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/wallet/GenericCredentialListItem.kt b/example/src/main/java/com/spruceid/mobilesdkexample/wallet/GenericCredentialListItem.kt
index 38abb2a..6039802 100644
--- a/example/src/main/java/com/spruceid/mobilesdkexample/wallet/GenericCredentialListItem.kt
+++ b/example/src/main/java/com/spruceid/mobilesdkexample/wallet/GenericCredentialListItem.kt
@@ -41,7 +41,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.spruceid.mobile.sdk.CredentialPack
-import com.spruceid.mobile.sdk.W3CVC
+import com.spruceid.mobile.sdk.rs.JsonVc
import com.spruceid.mobile.sdk.rs.vcToSignedVp
import com.spruceid.mobile.sdk.ui.BaseCard
import com.spruceid.mobile.sdk.ui.CardRenderingDetailsField
@@ -64,10 +64,10 @@ import com.spruceid.mobilesdkexample.utils.small_vc
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun GenericCredentialListItems(
- vc: String
+ vc_json: String
) {
val credentialPack = CredentialPack()
- credentialPack.addW3CVC(credentialString = vc)
+ credentialPack.addJsonVc(JsonVc.newFromJson(vc_json))
var sheetOpen by remember {
mutableStateOf(false)
@@ -149,6 +149,7 @@ fun GenericCredentialDetailsItem(credentialPack: CredentialPack) {
CardRenderingDetailsField(
// it's also possible just request the credentialSubject and cast it to JSONObject
keys = listOf(
+ "issuer.name",
"credentialSubject.image",
"credentialSubject.givenName",
"credentialSubject.familyName",
@@ -159,15 +160,14 @@ fun GenericCredentialDetailsItem(credentialPack: CredentialPack) {
"credentialSubject.driversLicense.family_name",
"credentialSubject.driversLicense.birth_date",
),
- formatter = {values ->
- val w3cvc = values.toList()
- .first { credentialPack.getCredentialById(it.first)!! is W3CVC }.second
+ formatter = { values ->
+ val w3cvc = values.toList().first().second
var portrait = ""
var firstName = ""
var lastName = ""
var birthDate = ""
- if(w3cvc["credentialSubject.driversLicense"].toString().isNotEmpty()) {
+ if (w3cvc["credentialSubject.driversLicense"].toString().isNotEmpty()) {
portrait = w3cvc["credentialSubject.driversLicense.portrait"].toString()
firstName = w3cvc["credentialSubject.driversLicense.given_name"].toString()
lastName = w3cvc["credentialSubject.driversLicense.family_name"].toString()
@@ -201,7 +201,7 @@ fun GenericCredentialDetailsItem(credentialPack: CredentialPack) {
)
BitmapImage(
byteArray,
- contentDescription = w3cvc["issuer.name"].toString(),
+ contentDescription = w3cvc["issuer.name"].toString(),
modifier = Modifier
.width(90.dp)
.padding(end = 12.dp)
@@ -252,8 +252,7 @@ fun GenericCredentialDetailsItem(credentialPack: CredentialPack) {
CardRenderingDetailsField(
keys = listOf("issuanceDate"),
formatter = { values ->
- val w3cvc = values.toList()
- .first { credentialPack.getCredentialById(it.first)!! is W3CVC }.second
+ val w3cvc = values.toList().first().second
Row {
Column {
@@ -291,9 +290,8 @@ fun GenericCredentialDetailsItem(credentialPack: CredentialPack) {
fun GenericCredentialListItem(credentialPack: CredentialPack) {
val listRendering = CardRenderingListView(
titleKeys = listOf("name"),
- titleFormatter = {values ->
- val w3cvc = values.toList()
- .first { credentialPack.getCredentialById(it.first)!! is W3CVC }.second
+ titleFormatter = { values ->
+ val w3cvc = values.toList().first().second
Text(
text = w3cvc["name"].toString(),
@@ -305,9 +303,8 @@ fun GenericCredentialListItem(credentialPack: CredentialPack) {
)
},
descriptionKeys = listOf("description", "valid"),
- descriptionFormatter = {values ->
- val w3cvc = values.toList()
- .first { credentialPack.getCredentialById(it.first)!! is W3CVC }.second
+ descriptionFormatter = { values ->
+ val w3cvc = values.toList().first().second
Column {
Text(
@@ -318,7 +315,7 @@ fun GenericCredentialListItem(credentialPack: CredentialPack) {
color = TextBody
)
Spacer(modifier = Modifier.height(16.dp))
- if(w3cvc["valid"].toString() == "true") {
+ if (w3cvc["valid"].toString() == "true") {
Row(verticalAlignment = Alignment.CenterVertically) {
Image(
painter = painterResource(id = R.drawable.valid),
@@ -338,8 +335,7 @@ fun GenericCredentialListItem(credentialPack: CredentialPack) {
},
leadingIconKeys = listOf("issuer.image", "issuer.name"),
leadingIconFormatter = { values ->
- val w3cvc = values.toList()
- .first { credentialPack.getCredentialById(it.first)!! is W3CVC }.second
+ val w3cvc = values.toList().first().second
val byteArray = Base64.decode(
w3cvc["issuer.image"]
.toString()
@@ -355,7 +351,7 @@ fun GenericCredentialListItem(credentialPack: CredentialPack) {
) {
BitmapImage(
byteArray,
- contentDescription = w3cvc["issuer.name"].toString(),
+ contentDescription = w3cvc["issuer.name"].toString(),
modifier = Modifier
.width(50.dp)
.padding(end = 12.dp)
@@ -424,7 +420,7 @@ fun GenericCredentialListItemQRCode() {
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
- if(vc != null) {
+ if (vc != null) {
Image(
painter = rememberQrBitmapPainter(vc!!, size = 300.dp),
contentDescription = stringResource(id = R.string.vp_qr_code),
diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/wallet/ShareableCredentialListItem.kt b/example/src/main/java/com/spruceid/mobilesdkexample/wallet/ShareableCredentialListItem.kt
index b5c068e..176880d 100644
--- a/example/src/main/java/com/spruceid/mobilesdkexample/wallet/ShareableCredentialListItem.kt
+++ b/example/src/main/java/com/spruceid/mobilesdkexample/wallet/ShareableCredentialListItem.kt
@@ -34,9 +34,10 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import com.spruceid.mobile.sdk.BaseCredential
import com.spruceid.mobile.sdk.CredentialPack
import com.spruceid.mobile.sdk.CredentialsViewModel
+import com.spruceid.mobile.sdk.rs.Mdoc
+import com.spruceid.mobile.sdk.rs.ParsedCredential
import com.spruceid.mobilesdkexample.R
import com.spruceid.mobilesdkexample.ui.theme.Bg
import com.spruceid.mobilesdkexample.ui.theme.CredentialBorder
@@ -46,7 +47,12 @@ import com.spruceid.mobilesdkexample.ui.theme.TextHeader
import com.spruceid.mobilesdkexample.ui.theme.TextOnPrimary
import com.spruceid.mobilesdkexample.utils.keyBase64
import com.spruceid.mobilesdkexample.utils.keyPEM
-import java.util.UUID
+import java.security.KeyFactory
+import java.security.KeyStore
+import java.security.cert.Certificate
+import java.security.cert.CertificateFactory
+import java.security.spec.PKCS8EncodedKeySpec
+import java.util.Base64
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -57,12 +63,50 @@ fun ShareableCredentialListItems(
CredentialPack()
}
val credentials = remember {
- credentialPack.addMDoc(
- id = UUID.randomUUID().toString(),
- mdocBase64 = mdocBase64,
- keyPEM = keyPEM,
- keyBase64 = keyBase64
+ val keyAlias = "someAlias"
+
+ val decodedKey = Base64.getDecoder().decode(
+ keyBase64
+ )
+
+ val privateKey = KeyFactory.getInstance(
+ "EC"
+ ).generatePrivate(
+ PKCS8EncodedKeySpec(
+ decodedKey
+ )
+ )
+
+ val cert: Array = arrayOf(
+ CertificateFactory.getInstance(
+ "X.509"
+ ).generateCertificate(
+ keyPEM.byteInputStream()
+ )
+ )
+
+ val ks: KeyStore = KeyStore.getInstance(
+ "AndroidKeyStore"
)
+
+ ks.load(
+ null
+ )
+
+ ks.setKeyEntry(
+ keyAlias,
+ privateKey,
+ null,
+ cert
+ )
+
+
+ val mdoc = Mdoc.fromCborEncodedDocument(
+ Base64.getDecoder().decode(mdocBase64),
+ keyAlias
+ )
+
+ credentialPack.addMdoc(mdoc)
}
var sheetOpen by remember {
@@ -127,23 +171,23 @@ fun ShareableCredentialListItems(
}
@Composable
-fun ShareableCredentialDetailsItem(credential: BaseCredential) {
+fun ShareableCredentialDetailsItem(credential: ParsedCredential) {
Box(
Modifier
.fillMaxWidth()
.padding(24.dp)
) {
- Text(credential.getId().toString())
+ Text(credential.id())
}
}
@Composable
-fun ShareableCredentialListItem(credential: BaseCredential) {
- Text(credential.getId().toString())
+fun ShareableCredentialListItem(credential: ParsedCredential) {
+ Text(credential.id())
}
@Composable
-fun ShareableCredentialListItemQRCode(credential: BaseCredential) {
+fun ShareableCredentialListItemQRCode(credential: ParsedCredential) {
var showQRCode by remember {
mutableStateOf(false)
}
@@ -177,7 +221,7 @@ fun ShareableCredentialListItemQRCode(credential: BaseCredential) {
.fillMaxWidth()
.clickable {
showQRCode = !showQRCode
- if(!showQRCode) {
+ if (!showQRCode) {
credentialViewModel.cancel()
}
}
@@ -198,7 +242,7 @@ fun ShareableCredentialListItemQRCode(credential: BaseCredential) {
}
AnimatedVisibility(visible = showQRCode) {
- if(showQRCode) {
+ if (showQRCode) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
diff --git a/example/src/main/java/com/spruceid/mobilesdkexample/wallet/WalletHomeView.kt b/example/src/main/java/com/spruceid/mobilesdkexample/wallet/WalletHomeView.kt
index 5b8db1a..118d430 100644
--- a/example/src/main/java/com/spruceid/mobilesdkexample/wallet/WalletHomeView.kt
+++ b/example/src/main/java/com/spruceid/mobilesdkexample/wallet/WalletHomeView.kt
@@ -39,7 +39,6 @@ import com.spruceid.mobilesdkexample.ui.theme.CTAButtonBlue
import com.spruceid.mobilesdkexample.ui.theme.Inter
import com.spruceid.mobilesdkexample.ui.theme.TextHeader
import com.spruceid.mobilesdkexample.ui.theme.Primary
-import com.spruceid.mobilesdkexample.utils.mdocBase64
import com.spruceid.mobilesdkexample.viewmodels.IRawCredentialsViewModel
import kotlinx.coroutines.launch