Skip to content

Commit

Permalink
wip: oid4vp integration
Browse files Browse the repository at this point in the history
Signed-off-by: Ryan Tate <[email protected]>
  • Loading branch information
Ryanmtate committed Oct 5, 2024
1 parent 4a78334 commit de24ae8
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,18 @@ package com.spruceid.mobile.sdk.oid4vp

import com.spruceid.mobile.sdk.rs.Credential
import com.spruceid.mobile.sdk.rs.Holder
import com.spruceid.mobile.sdk.rs.HolderInterface
import com.spruceid.mobile.sdk.rs.RequestSignerInterface
import com.spruceid.mobile.sdk.rs.Url
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext

class OID4VPHolder(
keyId: String,
credentials: List<Credential>,
trustedDids: List<String>
private val keyId: String,
private val credentials: List<Credential>,
private val trustedDids: List<String>
) {
public val keyId = keyId
public val credentials = credentials
public val trustedDids = trustedDids

suspend fun handle_url(
url: Url,
credentialPresentation: CredentialPresentation
) : Url? {
return runBlocking {
Holder.newWithCredentials(
credentials,
RequestSigner(keyId),
trustedDids
).handleOid4vpRequest(url, credentialPresentation)
}
}
suspend fun handleUrl(url: Url, credentialPresentation: CredentialPresentation): Url? =
withContext(Dispatchers.Default) {
Holder.newWithCredentials(credentials, RequestSigner(keyId), trustedDids)
.handleOid4vpRequest(url, credentialPresentation)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.lifecycle.coroutineScope
import androidx.navigation.NavHostController
Expand All @@ -21,6 +22,7 @@ import com.spruceid.mobilesdkexample.db.RawCredentialsRepository
import com.spruceid.mobilesdkexample.navigation.SetupNavGraph
import com.spruceid.mobilesdkexample.ui.theme.Bg
import com.spruceid.mobilesdkexample.ui.theme.MobileSdkTheme
import com.spruceid.mobilesdkexample.utils.exampleSdJwt
import com.spruceid.mobilesdkexample.viewmodels.IRawCredentialsViewModel
import com.spruceid.mobilesdkexample.viewmodels.RawCredentialsViewModelFactory
import kotlinx.coroutines.launch
Expand All @@ -33,8 +35,9 @@ class MainActivity : ComponentActivity() {

val deepLinkUri: Uri? = intent.data
if (deepLinkUri != null) {
// Remove? TBD
if (deepLinkUri.scheme == "oid4vp://") {
// NOTE: See ScanOID4VPQR.kt for handling OID4VP QR code scanning,
// NOTE: See DispatchQRView.kt for handling OID4VP QR code scanning,
// and credential selection.
}
}
Expand All @@ -52,6 +55,22 @@ class MainActivity : ComponentActivity() {
val credentialsViewModel: IRawCredentialsViewModel by viewModels {
RawCredentialsViewModelFactory((application as MainApplication).rawCredentialsRepository)
}

// Insert a raw credential into the rawCredentialsRepository,
// using a suspend / async method.
LaunchedEffect(credentialsViewModel) {
lifecycle.coroutineScope.launch {
// Clear the raw credentials table.
// credentialsViewModel.deleteAllRawCredentials()
// // Load the exampleSdJwt into the raw credentials table.
// credentialsViewModel.saveRawCredential(
// com.spruceid.mobilesdkexample.db.RawCredentials(
// rawCredential = exampleSdJwt
// )
// )
}
}

SetupNavGraph(navController, credentialsViewModel)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ fun SetupNavGraph(
}
)
) {
DispatchQRView(navController)
DispatchQRView(navController, rawCredentialsViewModel)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,9 @@ val keyPEM = "-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHB


val keyBase64 =
"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgEAqKZdZQgPVtjlEBfz2ItHG8oXIONenOxRePtqOQ42yhRANCAATA43gI2Ib8+qKK4YEOfNCRiNOhyHaCLgAvKdhHS+y6wpG3oJ2xudXagzKKbcfvUda4x0j8zR1/oD56mpm85GbO"
"MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgEAqKZdZQgPVtjlEBfz2ItHG8oXIONenOxRePtqOQ42yhRANCAATA43gI2Ib8+qKK4YEOfNCRiNOhyHaCLgAvKdhHS+y6wpG3oJ2xudXagzKKbcfvUda4x0j8zR1/oD56mpm85GbO"

//const val exampleSdJwt = "eyJhbGciOiJFUzI1NiJ9.eyJfc2RfYWxnIjoic2hhMjU2IiwiY3JlZGVudGlhbFN1YmplY3QiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnL25zL2NyZWRlbnRpYWxzL3YyIiwiaHR0cHM6Ly9wdXJsLmltc2dsb2JhbC5vcmcvc3BlYy9vYi92M3AwL2NvbnRleHQtMy4wLjMuanNvbiJdLCJhd2FyZGVkRGF0ZSI6IjIwMjQtMDktMjNUMTg6MTI6MTIrMDAwMCIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkZW50aXR5IjpbeyJoYXNoZWQiOmZhbHNlLCJpZGVudGl0eUhhc2giOiJKb2huIFNtaXRoIiwiaWRlbnRpdHlUeXBlIjoibmFtZSIsInNhbHQiOiJub3QtdXNlZCIsInR5cGUiOiJJZGVudGl0eU9iamVjdCJ9LHsiaGFzaGVkIjpmYWxzZSwiaWRlbnRpdHlIYXNoIjoiam9obi5zbWl0aEBleGFtcGxlLmNvbSIsImlkZW50aXR5VHlwZSI6ImVtYWlsQWRkcmVzcyIsInNhbHQiOiJub3QtdXNlZCIsInR5cGUiOiJJZGVudGl0eU9iamVjdCJ9XSwiYWNoaWV2ZW1lbnQiOnsibmFtZSI6IkNvbG9yYWRvRldEIFRlYW0gTWVtYmVyc2hpcCIsInR5cGUiOiJBY2hpZXZlbWVudCJ9fSwiaXNzdWVyIjp7ImlkIjoiZGlkOmp3azpleUpoYkdjaU9pSkZVekkxTmlJc0ltTnlkaUk2SWxBdE1qVTJJaXdpYTNSNUlqb2lSVU1pTENKNElqb2liV0pVTTJkcU9XRnZPR051UzI4ME0wcHJjVlJQVW1OSlFWSTRNRmd3VFVGWFFXTkdZelp2UjFKTVl5SXNJbmtpT2lKaU9GVk9ZMGhETW1GSFEzSjFTVFowUWxSV1NWWTBkVzVaV0VWeVMwTTRaRFJuUlRGR1owczBRMDVKSW4wIzAiLCJuYW1lIjoiQ29sb3JhZG8gV29ya2ZvcmNlIERldmVsb3BtZW50IENvdW5jaWwiLCJ0eXBlIjoiUHJvZmlsZSJ9LCJuYW1lIjoiQ29sb3JhZG9GV0RUZWFtTWVtYmVyc2hpcCIsInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJPcGVuQmFkZ2VDcmVkZW50aWFsIl19fQ.Aj40fUgmPygWVULtPgVWbpIsgbZjBp-KE_ZudRYpZ90PPGSbRf3hLEHZ9nkCKhYX7xCmTOtdYHnjhEwIFN1Xng~";

const val exampleSdJwt = "eyJhbGciOiJFZERTQSJ9.eyJfc2RfYWxnIjoic2hhLTI1NiIsImlzcyI6Imh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwic3ViIjoiMTIzNDU2Nzg5MCIsIl9zZCI6WyJ4U1poOVJaU0RQOEhnNXllZ3Vlck9TbnZ2UXFDcjF4RmdQc1g4U1A4Qkk4Il19.5rxobR0a1hT8WUCN86p8rbQuUsBEcLLuE7p6UBSfAzCgbaF0ZxbXcnAPPwGnBQVcETx7QAmxCJNudfQ5n4tADg~WyIyVmpiY3FEd3RtdTNiWHlZaUJIbjZRIiwidmMiLHsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnL25zL2NyZWRlbnRpYWxzL3YyIiwiaHR0cHM6Ly9wdXJsLmltc2dsb2JhbC5vcmcvc3BlYy9vYi92M3AwL2NvbnRleHQtMy4wLjMuanNvbiJdLCJhd2FyZGVkRGF0ZSI6IjIwMjQtMDktMjNUMTg6MTI6MTIrMDAwMCIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkZW50aXR5IjpbeyJoYXNoZWQiOmZhbHNlLCJpZGVudGl0eUhhc2giOiJKb2huIFNtaXRoIiwiaWRlbnRpdHlUeXBlIjoibmFtZSIsInNhbHQiOiJub3QtdXNlZCIsInR5cGUiOiJJZGVudGl0eU9iamVjdCJ9LHsiaGFzaGVkIjpmYWxzZSwiaWRlbnRpdHlIYXNoIjoiam9obi5zbWl0aEBleGFtcGxlLmNvbSIsImlkZW50aXR5VHlwZSI6ImVtYWlsQWRkcmVzcyIsInNhbHQiOiJub3QtdXNlZCIsInR5cGUiOiJJZGVudGl0eU9iamVjdCJ9XSwiYWNoaWV2ZW1lbnQiOnsibmFtZSI6IkNvbG9yYWRvRldEIFRlYW0gTWVtYmVyc2hpcCIsInR5cGUiOiJBY2hpZXZlbWVudCJ9fSwiaXNzdWVyIjp7ImlkIjoiZGlkOmp3azpleUpoYkdjaU9pSkZVekkxTmlJc0ltTnlkaUk2SWxBdE1qVTJJaXdpYTNSNUlqb2lSVU1pTENKNElqb2liV0pVTTJkcU9XRnZPR051UzI4ME0wcHJjVlJQVW1OSlFWSTRNRmd3VFVGWFFXTkdZelp2UjFKTVl5SXNJbmtpT2lKaU9GVk9ZMGhETW1GSFEzSjFTVFowUWxSV1NWWTBkVzVaV0VWeVMwTTRaRFJuUlRGR1owczBRMDVKSW4wIzAiLCJuYW1lIjoiQ29sb3JhZG8gV29ya2ZvcmNlIERldmVsb3BtZW50IENvdW5jaWwiLCJ0eXBlIjoiUHJvZmlsZSJ9LCJuYW1lIjoiQ29sb3JhZG9GV0RUZWFtTWVtYmVyc2hpcCIsInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJPcGVuQmFkZ2VDcmVkZW50aWFsIl19XQ~"

Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class AchievementCredentialItem {

constructor(rawCredential: String, onDelete: (() -> Unit)? = null) {
val decodedSdJwt = decodeRevealSdJwt(rawCredential)

this.credential = JSONObject(decodedSdJwt)
this.onDelete = onDelete
}
Expand Down Expand Up @@ -244,12 +245,15 @@ class AchievementCredentialItem {

@Composable
fun detailsComponent() {
val awardedDate = keyPathFinder(credential, mutableListOf("awardedDate")).toString()
println("credential: $credential")
// NOTE: The credential contains a `vc` property with the verifiable credential payload.
val awardedDate = keyPathFinder(credential, mutableListOf("vc", "awardedDate")).toString()
println("awardedDate: $awardedDate")
val ISO8601DateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss[.SSS]Z")
val parsedDate = OffsetDateTime.parse(awardedDate, ISO8601DateFormat)
val dateTimeFormatter = DateTimeFormatter.ofPattern("MMM dd, yyyy 'at' h:mm a")

val identity = keyPathFinder(credential, mutableListOf("credentialSubject", "identity")) as JSONArray
val identity = keyPathFinder(credential, mutableListOf("vc", "credentialSubject", "identity")) as JSONArray
val details = MutableList(identity.length()) { i ->
val obj = identity.get(i) as JSONObject
Pair(obj["identityType"].toString(), obj["identityHash"].toString())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,50 @@ package com.spruceid.mobilesdkexample.wallet

import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.navigation.NavController
import com.spruceid.mobilesdkexample.ScanningComponent
import com.spruceid.mobilesdkexample.ScanningType
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.spruceid.mobile.sdk.oid4vp.CredentialPresentation
import com.spruceid.mobile.sdk.oid4vp.OID4VPHolder
import com.spruceid.mobile.sdk.oid4vp.RequestSigner
import com.spruceid.mobile.sdk.oid4vp.SelectCredentialsView
import com.spruceid.mobile.sdk.rs.Credential
import com.spruceid.mobile.sdk.rs.Holder
import com.spruceid.mobile.sdk.rs.ParsedCredential
import com.spruceid.mobile.sdk.rs.SdJwt
import com.spruceid.mobile.sdk.rs.Uuid
import com.spruceid.mobilesdkexample.viewmodels.IRawCredentialsViewModel
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch

@OptIn(ExperimentalMaterial3Api::class, ExperimentalPermissionsApi::class)
@Composable
fun DispatchQRView(
navController: NavController
navController: NavController,
rawCredentialsViewModel: IRawCredentialsViewModel,
) {
val scope = rememberCoroutineScope()

val rawCredentials by rawCredentialsViewModel.rawCredentials.collectAsState()

var credentials by remember { mutableStateOf(emptyList<Credential>()) }

// maintain of state of credentials that once loaded will be used
// to lazy load the component view.
var parsedCredentials by remember { mutableStateOf(emptyList<ParsedCredential>()) }

// Selected Credential ID to be used in the future.
var selectedCredentialId by remember { mutableStateOf<Uuid?>(null) }

var holder by remember { mutableStateOf<Holder?>(null) }

// A function that sets the parsed credentials based on the Rust callback.
fun onCredentials(credentialList: List<ParsedCredential>) {
parsedCredentials = credentialList
Expand All @@ -42,26 +57,56 @@ fun DispatchQRView(
}

fun onRead(url: String) {
println("Reading URL: $url")

if (credentials.isEmpty()) {
credentials = rawCredentials.map { rawCredential ->
ParsedCredential
.newSdJwt(SdJwt.newFromCompactSdJwt(rawCredential.rawCredential))
.intoGenericForm()
}
}

println("Credentials: $credentials")

// TODO: Should this be a different scope?
GlobalScope.launch {
// TODO: Need to find the key from the key manager.
val keyId = "INSERT KEY ID HERE"

// TODO: Change this to a VDC collection in the future.
// TODO: Use the credential datastore to retrieve credentials.
val credentials = emptyList<Credential>()

// TODO: Add an entry in utils file for trusted DIDs.
val trustedDids = emptyList<String>()

OID4VPHolder(
keyId,
credentials,
trustedDids
).handle_url(url, CredentialPresentation(
::onCredentials,
::onSelectedCredential
))
scope.launch {
// // Convert rawCredentials to a list of ParsedCredentials



//
// val keyId = "INSERT KEY ID HERE"
// val trustedDIDs = emptyList<String>()
//
//
// holder = Holder.newWithCredentials(
// credentials,
// RequestSigner(keyId),
// trustedDIDs
// )
//
//// // TODO: Need to find the key from the key manager.
//// val keyId = "INSERT KEY ID HERE"
////
//// // TODO: Change this to a VDC collection in the future.
//// // TODO: Use the credential datastore to retrieve credentials.
//// val credentials = emptyList<Credential>()
////
//// // TODO: Add an entry in utils file for trusted DIDs.
//// val trustedDids = emptyList<String>()
////
//// // TODO:
//// val response = OID4VPHolder(
//// keyId,
//// credentials,
//// trustedDids
//// ).handleUrl(url, CredentialPresentation(
//// ::onCredentials,
//// ::onSelectedCredential
//// ))
////
//// // TODO: dispatchUrl(response)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.spruceid.mobilesdkexample.R
import com.spruceid.mobilesdkexample.db.RawCredentials
import com.spruceid.mobilesdkexample.navigation.Screen
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.exampleSdJwt
import com.spruceid.mobilesdkexample.viewmodels.IRawCredentialsViewModel
import kotlinx.coroutines.launch

Expand Down

0 comments on commit de24ae8

Please sign in to comment.