Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion sample-application/sample-application/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ android {
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
isCoreLibraryDesugaringEnabled = true
}
kotlinOptions {
jvmTarget = "11"
Expand All @@ -55,6 +56,13 @@ android {
}
}

configurations.configureEach {
exclude(group = "org.bouncycastle", module = "bcprov-jdk15on")
exclude(group = "org.bouncycastle", module = "bcprov-jdk15to18")
exclude(group = "org.bouncycastle", module = "bcpkix-jdk15on")
exclude(group = "org.bouncycastle", module = "bcpkix-jdk15to18")
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

dependencies {


Expand Down Expand Up @@ -82,6 +90,7 @@ dependencies {
exclude(group = "com.google.crypto.tink", module = "tink")
exclude(group = "io.mosip", module = "vcverifier-jar")
exclude(group = "com.google.protobuf", module = "protobuf-java")
exclude(group = "com.apicatalog", module = "titanium-json-ld")
exclude(group = "com.apicatalog", module = "titanium-json-ld-jre8")
exclude(group = "org.bouncycastle")
}
Expand All @@ -90,9 +99,25 @@ dependencies {
// Exclude transitive dependencies to prevent conflicts and use explicitly declared versions
exclude(group = "org.bouncycastle")
exclude(group = "org.springframework")
exclude(group = "com.apicatalog", module = "titanium-json-ld")
exclude(group = "com.apicatalog", module = "titanium-json-ld-jre8")
}

implementation("io.inji:inji-openid4vp-aar:0.7.0-SNAPSHOT") {
Comment thread
Kaushikgupta469 marked this conversation as resolved.
Outdated
Comment thread
Kaushikgupta469 marked this conversation as resolved.
Outdated
exclude(group = "org.bouncycastle", module = "bcpkix-jdk15on")
exclude(group = "org.bouncycastle", module = "bcpkix-jdk18on")
exclude(group = "com.google.crypto.tink", module = "tink")
exclude(group = "com.augustcellars.cose", module = "cose-java")
exclude(group = "io.mosip", module = "vcverifier-jar")
exclude(group = "com.apicatalog", module = "titanium-json-ld")
exclude(group = "com.apicatalog", module = "titanium-json-ld-jre8")
}

implementation("com.google.code.gson:gson:2.11.0")
implementation("com.jayway.jsonpath:json-path:2.9.0")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.17.0")
implementation("com.google.crypto.tink:tink-android:1.6.1")

implementation("androidx.navigation:navigation-compose:2.8.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.5")
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.5")
Expand All @@ -118,7 +143,7 @@ dependencies {
exclude(group = "com.apicatalog", module = "titanium-json-ld-jre8")
}

implementation("com.apicatalog:titanium-json-ld:1.3.2")
implementation("com.apicatalog:titanium-json-ld-jre8:1.3.2")

implementation("org.bouncycastle:bcprov-jdk18on:1.74")

Expand All @@ -130,6 +155,8 @@ dependencies {
// ML Kit Barcode Scanning
implementation("com.google.mlkit:barcode-scanning:17.2.0")

coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")

testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.lifecycle.lifecycleScope
import androidx.navigation.compose.rememberNavController
import com.example.samplecredentialwallet.navigation.AppNavHost
import com.example.samplecredentialwallet.ovp.utils.OpenID4VPManager
import com.example.samplecredentialwallet.utils.SecureKeystoreManager
import com.example.samplecredentialwallet.utils.AuthCodeHolder
import kotlinx.coroutines.launch
Expand All @@ -26,6 +27,8 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

OpenID4VPManager.init("sample-credential-wallet")

// Initialize keystore manager
keystoreManager = SecureKeystoreManager.getInstance(this)

Expand Down Expand Up @@ -89,16 +92,30 @@ class MainActivity : ComponentActivity() {

override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
Log.d(TAG, "onNewIntent received: data=${intent.data}")
setIntent(intent)
handleDeeplink(intent)
}

private fun handleDeeplink(intent: Intent?) {
intent?.data?.let { uri: Uri ->
Log.d(TAG, "handleDeeplink called with uri=$uri")
if (uri.toString().startsWith("io.mosip.residentapp.inji://oauthredirect")) {
val code = uri.getQueryParameter("code")
Log.d(TAG, "⚡ handleDeeplink triggered with code=$code")
val error = uri.getQueryParameter("error")
val queryParams = mutableMapOf<String, String>()
uri.queryParameterNames.forEach { name ->
queryParams[name] = uri.getQueryParameter(name) ?: ""
}

Log.d(
TAG,
"OAuth redirect received. codePresent=${code != null}, error=$error, params=${queryParams.keys}"
)

// Complete both holders because different flows wait on different deferred types.
AuthCodeHolder.complete(code)
AuthCodeHolder.completeV2(queryParams)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,19 @@ import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.samplecredentialwallet.ovp.ui.MatchingCredentialsScreen
import com.example.samplecredentialwallet.ovp.ui.VPShareScreen
import com.example.samplecredentialwallet.ovp.ui.VPSuccessScreen
import com.example.samplecredentialwallet.ovp.viewmodel.OVPViewModel
import com.example.samplecredentialwallet.ui.credential.CredentialDownloadScreen
import com.example.samplecredentialwallet.ui.credential.CredentialListScreen
import com.example.samplecredentialwallet.ui.home.HomeScreen
import com.example.samplecredentialwallet.ui.issuer.IssuerListScreen
import com.example.samplecredentialwallet.ui.issuer.IssuerDetailScreen
import androidx.compose.material3.*
import com.example.samplecredentialwallet.ui.auth.AuthWebViewScreen
import com.example.samplecredentialwallet.ui.splash.SplashScreen
import com.example.samplecredentialwallet.utils.Constants
import com.example.samplecredentialwallet.utils.IssuerRepository
import com.example.samplecredentialwallet.utils.IssuerRepositoryV2
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
import com.google.mlkit.vision.barcode.BarcodeScanning
Expand Down Expand Up @@ -87,10 +90,15 @@ sealed class Screen(val route: String) {
}

object QrScanner : Screen("qr_scanner")
object VPShare : Screen("vp_share")
object MatchingCredentials : Screen("matching_credentials")
object VPSuccess : Screen("vp_success")
}

@Composable
fun AppNavHost(navController: NavHostController) {
val ovpViewModel: OVPViewModel = viewModel()

NavHost(navController = navController, startDestination = Screen.Splash.route) {
composable(Screen.Splash.route) {
SplashScreen {
Expand All @@ -104,6 +112,9 @@ fun AppNavHost(navController: NavHostController) {
onNavigate = { navController.navigate(Screen.IssuerList.route) },
onViewCredential = { index ->
navController.navigate(Screen.CredentialList.createRoute(index))
},
onShareCredentials = {
navController.navigate(Screen.VPShare.route)
}
)
}
Expand Down Expand Up @@ -140,9 +151,17 @@ fun AppNavHost(navController: NavHostController) {
composable(Screen.AuthWebView.route) { backStackEntry ->
val encodedUrl = backStackEntry.arguments?.getString("authUrl") ?: ""
val authUrl = Uri.decode(encodedUrl) // decode back
// Use Constants.redirectUri if set (trusted issuer flow).
// Fall back to the credential-offer redirect URI so that
// AuthWebViewScreen's startsWith check never fires on every URL
// (empty string always matches startsWith in Kotlin, which was
// causing the auth flow to fail immediately for credential offer).
val redirectUri = Constants.redirectUri
?.takeIf { it.isNotEmpty() }
?: "io.mosip.residentapp.inji://oauthredirect"
AuthWebViewScreen(
authorizationUrl = authUrl,
redirectUri = Constants.redirectUri ?: "",
redirectUri = redirectUri,
navController = navController
)
}
Expand Down Expand Up @@ -174,6 +193,38 @@ fun AppNavHost(navController: NavHostController) {
Log.d("AppNavHost", "Navigating to credential list with index: $credentialIndex")
CredentialListScreen(navController, credentialIndex)
}

composable(Screen.VPShare.route) {
VPShareScreen(
ovpViewModel = ovpViewModel,
onNavigateToMatching = {
navController.navigate(Screen.MatchingCredentials.route)
}
)
}

composable(Screen.MatchingCredentials.route) {
MatchingCredentialsScreen(
ovpViewModel = ovpViewModel,
navController = navController,
onSuccess = {
navController.navigate(Screen.VPSuccess.route)
},
onBackToShare = {
navController.popBackStack(Screen.VPShare.route, inclusive = false)
}
)
}

composable(Screen.VPSuccess.route) {
VPSuccessScreen(
onGoHome = {
navController.navigate(Screen.Home.route) {
popUpTo(Screen.Home.route) { inclusive = true }
}
}
)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.example.samplecredentialwallet.ovp.data

import com.fasterxml.jackson.databind.PropertyNamingStrategies
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import io.mosip.openID4VP.authorizationRequest.VPFormatSupported
import io.mosip.openID4VP.authorizationRequest.Verifier
import io.mosip.openID4VP.authorizationRequest.WalletMetadata
import io.mosip.openID4VP.constants.ClientIdScheme
import io.mosip.openID4VP.constants.ContentEncryptionAlgorithm
import io.mosip.openID4VP.constants.KeyManagementAlgorithm
import io.mosip.openID4VP.constants.RequestSigningAlgorithm
import io.mosip.openID4VP.constants.VPFormatType

object OVPData {
fun getWalletMetadata(): WalletMetadata {
return WalletMetadata(
presentationDefinitionURISupported = true,
vpFormatsSupported = mapOf(
VPFormatType.LDP_VC to VPFormatSupported(
algValuesSupported = listOf("Ed25519Signature2018", "Ed25519Signature2020", "RSASignature2018")
),
VPFormatType.MSO_MDOC to VPFormatSupported(
algValuesSupported = listOf("ES256")
)
),
clientIdSchemesSupported = listOf(
ClientIdScheme.REDIRECT_URI,
ClientIdScheme.DID,
ClientIdScheme.PRE_REGISTERED
),
requestObjectSigningAlgValuesSupported = listOf(RequestSigningAlgorithm.EdDSA),
authorizationEncryptionAlgValuesSupported = listOf(KeyManagementAlgorithm.ECDH_ES),
authorizationEncryptionEncValuesSupported = listOf(ContentEncryptionAlgorithm.A256GCM)
)
}

fun getTrustedVerifiers(): List<Verifier> {
// Replace with your verifier configuration for non-demo usage.
val verifierJson = """
[
{
"client_id": "mock-client",
"response_uris": [
"https://localhost:3000/verifier/vp-response"
],
"jwks_uri": "https://localhost:3000/.well-known/jwks.json"
}
]
""".trimIndent()

val objectMapper = jacksonObjectMapper()
.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)

return objectMapper.readValue(verifierJson)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.example.samplecredentialwallet.ovp.data

import com.google.gson.JsonObject

data class VCMetadata(
val format: String,
val vc: JsonObject,
val rawCBORData: String? = null,
val deviceKeyAlias: String? = null
)
Loading
Loading