Skip to content

Commit

Permalink
support stopping openId4vp; stop triggers TransferEvent.Disconnected …
Browse files Browse the repository at this point in the history
…event
  • Loading branch information
vkanellopoulos committed Feb 6, 2025
1 parent 6c9fd74 commit c5f122d
Show file tree
Hide file tree
Showing 10 changed files with 394 additions and 172 deletions.
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ mockito-inline = "5.2.0"
mockito-kotlin = "5.3.1"
mockk = "1.13.5"
nimbus-sdk = "11.20.1"
robolectric = "4.14"
sonarqube = "5.1.0.4882"
test-core = "1.4.0"
test-rules = "1.4.0"
Expand Down Expand Up @@ -73,6 +74,7 @@ mockito-inline = { module = "org.mockito:mockito-inline", version.ref = "mockito
mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version.ref = "mockito-kotlin" }
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
nimbus-oauth2-oidc-sdk = { module = "com.nimbusds:oauth2-oidc-sdk", version.ref = "nimbus-sdk" }
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
test-core = { module = "androidx.test:core", version.ref = "test-core" }
test-coreKtx = { module = "androidx.test:core-ktx", version.ref = "test-core" }
test-rules = { module = "androidx.test:rules", version.ref = "test-rules" }
Expand Down
9 changes: 8 additions & 1 deletion wallet-core/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 European Commission
* Copyright (c) 2024-2025 European Commission
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -82,6 +82,12 @@ android {
jvmTarget = libs.versions.java.get()
}

testOptions {
unitTests {
isIncludeAndroidResources = true
}
}

sourceSets.getByName("test").apply {
res.setSrcDirs(files("resources"))
}
Expand Down Expand Up @@ -145,6 +151,7 @@ dependencies {
testImplementation(libs.json)
testImplementation(libs.kotlin.coroutines.test)
testImplementation(libs.biometric.ktx)
testImplementation(libs.robolectric)

androidTestImplementation(libs.android.junit)
androidTestImplementation(libs.mockito.android)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import java.security.SecureRandom
import java.util.Base64

/**
* Utility class to generate the session transcript for the OpenID4VP protocol.
* Utility to generate the session transcript for the OpenID4VP protocol.
*
* SessionTranscript = [
* DeviceEngagementBytes,
Expand Down Expand Up @@ -76,136 +76,128 @@ import java.util.Base64
* nonce = tstr
*
*/

internal object OpenId4VpUtils {

@JvmStatic
internal fun generateSessionTranscript(
clientId: String,
responseUri: String,
nonce: String,
mdocGeneratedNonce: String,
): SessionTranscriptBytes {

val openID4VPHandover =
generateOpenId4VpHandover(clientId, responseUri, nonce, mdocGeneratedNonce)

val sessionTranscriptBytes =
CBORObject.NewArray().apply {
Add(CBORObject.Null)
Add(CBORObject.Null)
Add(openID4VPHandover)
}.EncodeToBytes()

return sessionTranscriptBytes
}

@JvmStatic
internal fun generateOpenId4VpHandover(
clientId: String,
responseUri: String,
nonce: String,
mdocGeneratedNonce: String,
): com.upokecenter.cbor.CBORObject {
val clientIdToHash = CBORObject.NewArray().apply {
Add(clientId)
Add(mdocGeneratedNonce)
}.EncodeToBytes()

val responseUriToHash = CBORObject.NewArray().apply {
Add(responseUri)
Add(mdocGeneratedNonce)
internal fun generateSessionTranscript(
clientId: String,
responseUri: String,
nonce: String,
mdocGeneratedNonce: String,
): SessionTranscriptBytes {

val openID4VPHandover =
generateOpenId4VpHandover(clientId, responseUri, nonce, mdocGeneratedNonce)

val sessionTranscriptBytes =
CBORObject.NewArray().apply {
Add(CBORObject.Null)
Add(CBORObject.Null)
Add(openID4VPHandover)
}.EncodeToBytes()

val clientIdHash = MessageDigest.getInstance("SHA-256").digest(clientIdToHash)
val responseUriHash = MessageDigest.getInstance("SHA-256").digest(responseUriToHash)

val openID4VPHandover = CBORObject.NewArray().apply {
Add(clientIdHash)
Add(responseUriHash)
Add(nonce)
}
return openID4VPHandover
}

@JvmStatic
internal fun generateMdocGeneratedNonce(): String {
val secureRandom = SecureRandom()
val bytes = ByteArray(16)
secureRandom.nextBytes(bytes)
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes)
return sessionTranscriptBytes
}

internal fun generateOpenId4VpHandover(
clientId: String,
responseUri: String,
nonce: String,
mdocGeneratedNonce: String,
): CBORObject {
val clientIdToHash = CBORObject.NewArray().apply {
Add(clientId)
Add(mdocGeneratedNonce)
}.EncodeToBytes()

val responseUriToHash = CBORObject.NewArray().apply {
Add(responseUri)
Add(mdocGeneratedNonce)
}.EncodeToBytes()

val clientIdHash = MessageDigest.getInstance("SHA-256").digest(clientIdToHash)
val responseUriHash = MessageDigest.getInstance("SHA-256").digest(responseUriToHash)

val openID4VPHandover = CBORObject.NewArray().apply {
Add(clientIdHash)
Add(responseUriHash)
Add(nonce)
}

@JvmStatic
internal fun OpenId4VpConfig.toSiopOpenId4VPConfig(trust: Openid4VpX509CertificateTrust): SiopOpenId4VPConfig {
return SiopOpenId4VPConfig(
jarmConfiguration = JarmConfiguration.Encryption(
supportedAlgorithms = encryptionAlgorithms.map {
JWEAlgorithm.parse(it.name)
},
supportedMethods = encryptionMethods.map {
EncryptionMethod.parse(it.name)
},
),
supportedClientIdSchemes = clientIdSchemes.map { clientIdScheme ->
when (clientIdScheme) {
is ClientIdScheme.Preregistered -> Preregistered(
clientIdScheme.preregisteredVerifiers.associate { verifier ->
verifier.clientId to PreregisteredClient(
verifier.clientId,
verifier.legalName,
JWSAlgorithm.RS256 to ByReference(
URI("${verifier.verifierApi}/wallet/public-keys.json")
)
return openID4VPHandover
}

internal fun generateMdocGeneratedNonce(): String {
val secureRandom = SecureRandom()
val bytes = ByteArray(16)
secureRandom.nextBytes(bytes)
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes)
}

internal fun OpenId4VpConfig.toSiopOpenId4VPConfig(trust: Openid4VpX509CertificateTrust): SiopOpenId4VPConfig {
return SiopOpenId4VPConfig(
jarmConfiguration = JarmConfiguration.Encryption(
supportedAlgorithms = encryptionAlgorithms.map {
JWEAlgorithm.parse(it.name)
},
supportedMethods = encryptionMethods.map {
EncryptionMethod.parse(it.name)
},
),
supportedClientIdSchemes = clientIdSchemes.map { clientIdScheme ->
when (clientIdScheme) {
is ClientIdScheme.Preregistered -> Preregistered(
clientIdScheme.preregisteredVerifiers.associate { verifier ->
verifier.clientId to PreregisteredClient(
verifier.clientId,
verifier.legalName,
JWSAlgorithm.RS256 to ByReference(
URI("${verifier.verifierApi}/wallet/public-keys.json")
)
)
}
)

ClientIdScheme.X509SanDns -> X509SanDns(trust)

ClientIdScheme.X509SanUri -> X509SanUri(trust)
}
},
vpConfiguration = VPConfiguration(
vpFormats = VpFormats(
formats.map { format ->
when (format) {
is Format.MsoMdoc -> {
VpFormat.MsoMdoc
}
)

ClientIdScheme.X509SanDns -> X509SanDns(trust)

ClientIdScheme.X509SanUri -> X509SanUri(trust)
}
},
vpConfiguration = VPConfiguration(
vpFormats = VpFormats(
formats.map { format ->
when (format) {
is Format.MsoMdoc -> {
VpFormat.MsoMdoc
}

is Format.SdJwtVc -> {
VpFormat.SdJwtVc(
format.sdJwtAlgorithms.map { alg ->
JWSAlgorithm.parse(alg.jwseAlgorithmIdentifier)
},
format.kbJwtAlgorithms.map { alg ->
JWSAlgorithm.parse(alg.jwseAlgorithmIdentifier)
}
)
}
is Format.SdJwtVc -> {
VpFormat.SdJwtVc(
format.sdJwtAlgorithms.map { alg ->
JWSAlgorithm.parse(alg.jwseAlgorithmIdentifier)
},
format.kbJwtAlgorithms.map { alg ->
JWSAlgorithm.parse(alg.jwseAlgorithmIdentifier)
}
)
}
}.toList()
))
}
}.toList()
)
)
}

@JvmStatic
internal fun ResolvedRequestObject.OpenId4VPAuthorization.getSessionTranscriptBytes(
mdocGeneratedNonce: String,
): SessionTranscriptBytes {
val clientId = this.client.id
val responseUri =
(this.responseMode as ResponseMode.DirectPostJwt?)?.responseURI?.toString()
?: ""
val nonce = this.nonce

val sessionTranscriptBytes = generateSessionTranscript(
clientId,
responseUri,
nonce,
mdocGeneratedNonce
)
return sessionTranscriptBytes
}
}
)
}

internal fun ResolvedRequestObject.OpenId4VPAuthorization.getSessionTranscriptBytes(
mdocGeneratedNonce: String,
): SessionTranscriptBytes {
val clientId = this.client.id
val responseUri =
(this.responseMode as ResponseMode.DirectPostJwt?)?.responseURI?.toString()
?: ""
val nonce = this.nonce

val sessionTranscriptBytes = generateSessionTranscript(
clientId,
responseUri,
nonce,
mdocGeneratedNonce
)
return sessionTranscriptBytes
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 European Commission
* Copyright (c) 2024-2025 European Commission
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -76,6 +76,11 @@ interface PresentationManager : TransferEvent.Listenable, ReaderTrustStoreAware
*/
fun startRemotePresentation(uri: Uri)

/**
* Stops any ongoing remote presentation
*/
fun stopRemotePresentation()

/**
* Send a response to verifier
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 European Commission
* Copyright (c) 2024-2025 European Commission
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -115,6 +115,10 @@ class PresentationManagerImpl @JvmOverloads constructor(
)
}

override fun stopRemotePresentation() {
openId4vpManager?.stop()
}


companion object {
const val MDOC_SCHEME = "mdoc"
Expand Down
Loading

0 comments on commit c5f122d

Please sign in to comment.