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 7, 2024
1 parent 4a78334 commit e29296a
Show file tree
Hide file tree
Showing 10 changed files with 200 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class CredentialPresentation(
// This method will be called when the Rust code awaits the response.
override suspend fun permissionResponse(): PermissionResponse {
while (selectedCredential() == null) {
println("Waiting for credential selection")
kotlinx.coroutines.delay(100)
}
return PermissionResponse(selectedCredential()!!)
Expand Down
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 @@ -7,7 +7,8 @@ const val VERIFY_VC_PATH = "verify_vc"
const val VERIFIER_SETTINGS_HOME_PATH = "verifier_settings_home"
const val WALLET_SETTINGS_HOME_PATH = "wallet_settings_home"
const val ADD_TO_WALLET_PATH = "add_to_wallet/{rawCredential}"
const val OID4VP_PATH = "oid4vp/{params}"
const val SCAN_QR_PATH = "scan_qr"
const val HANDLE_OID4VP_PATH = "oid4vp/{url}"


sealed class Screen(val route: String) {
Expand All @@ -18,5 +19,6 @@ sealed class Screen(val route: String) {
object VerifierSettingsHomeScreen : Screen(VERIFIER_SETTINGS_HOME_PATH)
object WalletSettingsHomeScreen : Screen(WALLET_SETTINGS_HOME_PATH)
object AddToWalletScreen : Screen(ADD_TO_WALLET_PATH)
object ScanQRScreen : Screen(OID4VP_PATH)
object ScanQRScreen : Screen(SCAN_QR_PATH)
object HandleOID4VP : Screen(HANDLE_OID4VP_PATH)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.spruceid.mobilesdkexample.navigation

import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
Expand All @@ -15,6 +14,7 @@ import com.spruceid.mobilesdkexample.viewmodels.IRawCredentialsViewModel
import com.spruceid.mobilesdkexample.wallet.AddToWalletView
import com.spruceid.mobilesdkexample.walletsettings.WalletSettingsHomeView
import com.spruceid.mobilesdkexample.wallet.DispatchQRView
import com.spruceid.mobilesdkexample.wallet.HandleOID4VPView

@Composable
fun SetupNavGraph(
Expand Down Expand Up @@ -68,13 +68,19 @@ fun SetupNavGraph(
}
composable(
route = Screen.ScanQRScreen.route,
) {
DispatchQRView(navController)
}
composable(
route = Screen.HandleOID4VP.route,
deepLinks = listOf(
navDeepLink {
uriPattern = "oid4vp://{params}"
uriPattern = "oid4vp://{url}"
}
)
) {
DispatchQRView(navController)
) { backStackEntry ->
val url = backStackEntry.arguments?.getString("url")!!
HandleOID4VPView(navController, rawCredentialsViewModel, url)
}
}
}
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
@@ -1,81 +1,55 @@
package com.spruceid.mobilesdkexample.wallet

import android.os.Bundle
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.navigation.NavController
import androidx.navigation.navArgument
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.SelectCredentialsView
import com.spruceid.mobile.sdk.rs.Credential
import com.spruceid.mobile.sdk.rs.ParsedCredential
import com.spruceid.mobile.sdk.rs.Uuid
import kotlinx.coroutines.GlobalScope
import com.spruceid.mobilesdkexample.navigation.Screen
import com.spruceid.mobilesdkexample.viewmodels.IRawCredentialsViewModel
import kotlinx.coroutines.launch
import java.net.URLEncoder
import java.nio.charset.StandardCharsets

// The scheme for the OID4VP QR code.
const val OPEN_ID4VP_SCHEME = "openid4vp://"

@OptIn(ExperimentalMaterial3Api::class, ExperimentalPermissionsApi::class)
@Composable
fun DispatchQRView(
navController: NavController
navController: NavController,
) {
// maintain of state of credentials that once loaded will be used
// to lazy load the component view.
var parsedCredentials by remember { mutableStateOf(emptyList<ParsedCredential>()) }
val scope = rememberCoroutineScope()

// Selected Credential ID to be used in the future.
var selectedCredentialId by remember { mutableStateOf<Uuid?>(null) }
fun onRead(url: String) {
println("Reading URL: $url")

// A function that sets the parsed credentials based on the Rust callback.
fun onCredentials(credentialList: List<ParsedCredential>) {
parsedCredentials = credentialList
}
scope.launch {

// A function that returns the selectedCredentialId, called until a credential is selected.
fun onSelectedCredential(): Uuid? {
return selectedCredentialId
}
if (url.contains(OPEN_ID4VP_SCHEME)) {
val encodedUrl = URLEncoder.encode(
url,
// url.replace(OPEN_ID4VP_SCHEME, ""),
StandardCharsets.UTF_8.toString()
)

fun onRead(url: String) {
// 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>()
navController.navigate("oid4vp/$encodedUrl") {
launchSingleTop = true
restoreState = true
}
}

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

OID4VPHolder(
keyId,
credentials,
trustedDids
).handle_url(url, CredentialPresentation(
::onCredentials,
::onSelectedCredential
))
}
}

// Return the scanning component while the credentials are being loaded (i.e. empty list).
if (parsedCredentials.isEmpty()) {
ScanningComponent(
navController = navController,
scanningType = ScanningType.QRCODE,
onRead = ::onRead
)
} else {
// Load the Credential View
SelectCredentialsView(parsedCredentials, onSelectedCredential = { selectedCredential ->
selectedCredentialId = selectedCredential.id()
})
}
ScanningComponent(
navController = navController,
scanningType = ScanningType.QRCODE,
onRead = ::onRead
)
}
Loading

0 comments on commit e29296a

Please sign in to comment.