Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ espresso-core = "3.6.1"
eudi-document-manager = "0.13.0"
eudi-iso18013-data-transfer = "0.10.0"
eudi-lib-jvm-openid4vci-kt = "0.9.1"
eudi-lib-jvm-siop-openid4vp-kt = "0.11.1"
eudi-lib-jvm-siop-openid4vp-kt = "0.12.0-SNAPSHOT"
eudi-lib-jvm-sdjwt-kt = "0.10.0"
eudi-lib-kmp-statium = "0.4.0"
gradle-plugin = "8.13.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@ import eu.europa.ec.eudi.iso18013.transfer.response.device.ProcessedDeviceReques
import eu.europa.ec.eudi.openid4vp.CoseAlgorithm
import eu.europa.ec.eudi.openid4vp.JarConfiguration
import eu.europa.ec.eudi.openid4vp.JwkSetSource.ByReference
import eu.europa.ec.eudi.openid4vp.OpenId4VPConfig
import eu.europa.ec.eudi.openid4vp.PreregisteredClient
import eu.europa.ec.eudi.openid4vp.ResolvedRequestObject
import eu.europa.ec.eudi.openid4vp.ResponseEncryptionConfiguration
import eu.europa.ec.eudi.openid4vp.ResponseMode
import eu.europa.ec.eudi.openid4vp.SiopOpenId4VPConfig
import eu.europa.ec.eudi.openid4vp.SupportedClientIdPrefix
import eu.europa.ec.eudi.openid4vp.VPConfiguration
import eu.europa.ec.eudi.openid4vp.VerifiablePresentation
Expand Down Expand Up @@ -187,10 +187,10 @@ internal fun generateMdocGeneratedNonce(): String {
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes)
}

internal fun SiopOpenId4VPConfig.Companion.make(
internal fun makeOpenId4VPConfig(
config: OpenId4VpConfig,
trust: OpenId4VpReaderTrust,
): SiopOpenId4VPConfig {
): OpenId4VPConfig {
val supportedClientIdPrefixes = config.clientIdSchemes.map { clientIdScheme ->
when (clientIdScheme) {
is ClientIdScheme.Preregistered -> SupportedClientIdPrefix.Preregistered(
Expand All @@ -211,8 +211,8 @@ internal fun SiopOpenId4VPConfig.Companion.make(
ClientIdScheme.X509Hash -> SupportedClientIdPrefix.X509Hash(trust = trust)
}
}
return SiopOpenId4VPConfig(
issuer = SelfIssued,
return OpenId4VPConfig(
issuer = OpenId4VPConfig.SelfIssued,
jarConfiguration = JarConfiguration.Default,
responseEncryptionConfiguration = ResponseEncryptionConfiguration.Supported(
supportedAlgorithms = config.encryptionAlgorithms.map { it.nimbus },
Expand All @@ -225,23 +225,13 @@ internal fun SiopOpenId4VPConfig.Companion.make(
)
}

/**
* Converts an [OpenId4VpConfig] to a [SiopOpenId4VPConfig] using the provided trust anchor.
*
* @param trust The trust anchor for reader verification.
* @return The corresponding [SiopOpenId4VPConfig].
*/
internal fun OpenId4VpConfig.toSiopOpenId4VPConfig(trust: OpenId4VpReaderTrust): SiopOpenId4VPConfig {
return SiopOpenId4VPConfig.make(this, trust)
}

/**
* Extension function to get the session transcript bytes from a resolved OpenID4VP authorization request.
*
* @param mdocGeneratedNonce The generated nonce for mdoc.
* @return The session transcript as a byte array.
*/
internal fun ResolvedRequestObject.OpenId4VPAuthorization.getSessionTranscriptBytes(
internal fun ResolvedRequestObject.getSessionTranscriptBytes(
mdocGeneratedNonce: String,
): SessionTranscriptBytes {
val clientId = this.client.id.clientId
Expand Down Expand Up @@ -375,7 +365,7 @@ internal suspend fun SdJwt<JwtAndClaims>.serializeWithKeyBinding(
* @throws IllegalArgumentException if no claims are disclosed or presentation creation fails.
*/
internal suspend fun verifiablePresentationForSdJwtVc(
resolvedRequestObject: ResolvedRequestObject.OpenId4VPAuthorization,
resolvedRequestObject: ResolvedRequestObject,
document: IssuedDocument,
disclosedDocument: DisclosedDocument,
signatureAlgorithm: Algorithm,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
* JSON serialization/deserialization capabilities for:
* - VP Token consensus data
* - Verifiable presentations collections
*
* - Individual verifiable presentation objects
*/
package eu.europa.ec.eudi.wallet.transactionLogging.presentation
Expand Down Expand Up @@ -52,7 +53,7 @@ import kotlinx.serialization.modules.SerializersModule


val module = SerializersModule {
contextual(Consensus.PositiveConsensus.VPTokenConsensus::class, VPTokenConsensusSerializer)
contextual(Consensus.PositiveConsensus::class, VPTokenConsensusSerializer)
}

val VPTokenConsensusJson = Json {
Expand All @@ -61,16 +62,16 @@ val VPTokenConsensusJson = Json {
}

/**
* Custom serializer for [Consensus.PositiveConsensus.VPTokenConsensus] objects.
* Custom serializer for [Consensus.PositiveConsensus] objects.
*
* This serializer handles the serialization and deserialization of VP Token consensus data,
* which contains verifiable presentations that have been agreed upon during the consensus process.
* The serializer delegates the actual presentations serialization to [VerifiablePresentationsSerializer].
*
* @see Consensus.PositiveConsensus.VPTokenConsensus
* @see Consensus.PositiveConsensus
* @see VerifiablePresentationsSerializer
*/
object VPTokenConsensusSerializer : KSerializer<Consensus.PositiveConsensus.VPTokenConsensus> {
object VPTokenConsensusSerializer : KSerializer<Consensus.PositiveConsensus> {

/**
* Serial descriptor for the VPTokenConsensus structure.
Expand All @@ -81,12 +82,12 @@ object VPTokenConsensusSerializer : KSerializer<Consensus.PositiveConsensus.VPTo
}

/**
* Serializes a [Consensus.PositiveConsensus.VPTokenConsensus] object to the encoder.
* Serializes a [Consensus.PositiveConsensus] object to the encoder.
*
* @param encoder The encoder to write the serialized data to
* @param value The VPTokenConsensus object to serialize
* @param value The PositiveConsensus object to serialize
*/
override fun serialize(encoder: Encoder, value: Consensus.PositiveConsensus.VPTokenConsensus) {
override fun serialize(encoder: Encoder, value: Consensus.PositiveConsensus) {
encoder.encodeStructure(descriptor) {
encodeSerializableElement(
descriptor,
Expand All @@ -98,13 +99,13 @@ object VPTokenConsensusSerializer : KSerializer<Consensus.PositiveConsensus.VPTo
}

/**
* Deserializes a [Consensus.PositiveConsensus.VPTokenConsensus] object from the decoder.
* Deserializes a [Consensus.PositiveConsensus] object from the decoder.
*
* @param decoder The decoder to read the serialized data from
* @return The deserialized VPTokenConsensus object
* @return The deserialized PositiveConsensus object
* @throws SerializationException if the required verifiablePresentations field is missing
*/
override fun deserialize(decoder: Decoder): Consensus.PositiveConsensus.VPTokenConsensus {
override fun deserialize(decoder: Decoder): Consensus.PositiveConsensus {
return decoder.decodeStructure(descriptor) {
var verifiablePresentations: VerifiablePresentations? = null
while (true) {
Expand All @@ -118,7 +119,7 @@ object VPTokenConsensusSerializer : KSerializer<Consensus.PositiveConsensus.VPTo
else -> error("Unexpected index: $index")
}
}
Consensus.PositiveConsensus.VPTokenConsensus(
Consensus.PositiveConsensus(
verifiablePresentations = verifiablePresentations
?: throw SerializationException("Missing verifiablePresentations")
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ class TransactionLogBuilder(
* It handles different types of requests:
* - [DeviceRequest]: Stores the raw request bytes.
* - [OpenId4VpRequest]: Extracts and stores the presentation definition or digital credentials query from the resolved request object.
* Requires the resolved request to be [ResolvedRequestObject.OpenId4VPAuthorization] and
* Requires the resolved request to be [ResolvedRequestObject] and
* - Other request types: Marks the log status as [TransactionLog.Status.Error].
*
* The timestamp of the log is updated to the current time.
Expand All @@ -93,9 +93,6 @@ class TransactionLogBuilder(

is OpenId4VpRequest -> {
val resolvedRequestObject = request.resolvedRequestObject
require(resolvedRequestObject is ResolvedRequestObject.OpenId4VPAuthorization) {
"Only OpenId4VPAuthorization is supported"
}
val rawRequest = Json.encodeToString(resolvedRequestObject.query).toByteArray()

log.copy(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ fun parseVp(
): List<PresentedDocument> {
val parsedMetadata = metadata.map { TransactionLog.Metadata.fromJson(it) }
val vpToken =
VPTokenConsensusJson.decodeFromString<Consensus.PositiveConsensus.VPTokenConsensus>(
VPTokenConsensusJson.decodeFromString<Consensus.PositiveConsensus>(
String(rawResponse)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ import eu.europa.ec.eudi.iso18013.transfer.readerauth.ReaderTrustStore
import eu.europa.ec.eudi.iso18013.transfer.readerauth.ReaderTrustStoreAware
import eu.europa.ec.eudi.iso18013.transfer.response.Response
import eu.europa.ec.eudi.openid4vp.DispatchOutcome
import eu.europa.ec.eudi.openid4vp.OpenId4Vp
import eu.europa.ec.eudi.openid4vp.Resolution
import eu.europa.ec.eudi.openid4vp.ResolvedRequestObject
import eu.europa.ec.eudi.openid4vp.SiopOpenId4Vp
import eu.europa.ec.eudi.openid4vp.asException
import eu.europa.ec.eudi.wallet.internal.d
import eu.europa.ec.eudi.wallet.internal.e
import eu.europa.ec.eudi.wallet.internal.i
import eu.europa.ec.eudi.wallet.internal.toSiopOpenId4VPConfig
import eu.europa.ec.eudi.wallet.internal.makeOpenId4VPConfig
import eu.europa.ec.eudi.wallet.internal.wrappedWithContentNegotiation
import eu.europa.ec.eudi.wallet.internal.wrappedWithLogging
import eu.europa.ec.eudi.wallet.logging.Logger
Expand Down Expand Up @@ -79,13 +79,14 @@ class OpenId4VpManager(
}

/**
* Lazy initialization of the SIOP OpenID4VP protocol handler with logging and content negotiation.
* Lazy initialization of the OpenID4VP protocol handler with logging and content negotiation.
* Uses the configuration and trust anchor from the request processor.
*/
private val siopOpenId4Vp by lazy {
SiopOpenId4Vp(
siopOpenId4VPConfig = config.toSiopOpenId4VPConfig(
trust = requestProcessor.openid4VpX509CertificateTrust
private val openId4Vp by lazy {
OpenId4Vp(
openId4VPConfig = makeOpenId4VPConfig(
config,
requestProcessor.openid4VpX509CertificateTrust
),
httpClient = (ktorHttpClientFactory ?: DefaultHttpClientFactory)
.wrappedWithLogging(logger)
Expand Down Expand Up @@ -149,7 +150,7 @@ class OpenId4VpManager(
require(config.schemes.contains(Uri.parse(uri).scheme)) {
"Not supported scheme for OpenId4Vp"
}
when (val resolution = siopOpenId4Vp.resolveRequestUri(uri)) {
when (val resolution = openId4Vp.resolveRequestUri(uri)) {
is Resolution.Invalid -> {

// TODO dispatch error to verifier/RP
Expand All @@ -163,25 +164,13 @@ class OpenId4VpManager(

is Resolution.Success -> {
logger?.d(TAG, "Resolution.Success")
when (val resolvedRequest = resolution.requestObject) {
is ResolvedRequestObject.OpenId4VPAuthorization -> {
logger?.i(TAG, "${resolvedRequest::class.simpleName} received")
val request = OpenId4VpRequest(resolvedRequest)
val processedRequest = requestProcessor.process(request)
transferEventListeners.onTransferEvent(
TransferEvent.RequestReceived(processedRequest, request)
)
}

is ResolvedRequestObject.SiopAuthentication,
is ResolvedRequestObject.SiopOpenId4VPAuthentication,
-> {
logger?.i(TAG, "${resolvedRequest::class.simpleName} received")
transferEventListeners.onTransferEvent(
TransferEvent.Error(IllegalArgumentException("Unsupported request type"))
)
}
}
val resolvedRequest = resolution.requestObject
logger?.i(TAG, "${resolvedRequest::class.simpleName} received")
val request = OpenId4VpRequest(resolvedRequest)
val processedRequest = requestProcessor.process(request)
transferEventListeners.onTransferEvent(
TransferEvent.RequestReceived(processedRequest, request)
)
}
}
} catch (e: Throwable) {
Expand Down Expand Up @@ -212,13 +201,10 @@ class OpenId4VpManager(
require(response is OpenId4VpResponse) {
"Response must be an OpenId4VpResponse"
}
require(response.resolvedRequestObject is ResolvedRequestObject.OpenId4VPAuthorization) {
"Resolved request object must be OpenId4VPAuthorization"
}

logger?.let { response.debugLog(it, TAG) }

when (val outcome = siopOpenId4Vp.dispatch(
when (val outcome = openId4Vp.dispatch(
request = response.resolvedRequestObject,
consensus = response.vpToken,
encryptionParameters = response.encryptionParameters,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import kotlinx.serialization.Serializable
*/
class OpenId4VpResponse(
val resolvedRequestObject: ResolvedRequestObject,
val vpToken: Consensus.PositiveConsensus.VPTokenConsensus,
val vpToken: Consensus.PositiveConsensus,
val msoMdocNonce: String,
val respondedDocuments: Map<QueryId, List<RespondedDocument>>,
) : Response {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,6 @@ class DcqlRequestProcessor(
try {
// Validate request type and structure
require(request is OpenId4VpRequest) { "Request must be an OpenId4VpRequest" }
require(request.resolvedRequestObject is ResolvedRequestObject.OpenId4VPAuthorization) {
"Request must have be a OpenId4VPAuthorization"
}

val dcql = request.resolvedRequestObject.query
val credentials = dcql.credentials
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ import org.multipaz.crypto.Algorithm
* @property msoMdocNonce Random nonce used for MSO mdoc format presentations for security
*/
class ProcessedDcqlRequest(
val resolvedRequestObject: ResolvedRequestObject.OpenId4VPAuthorization,
val resolvedRequestObject: ResolvedRequestObject,
private val documentManager: DocumentManager,
private val queryMap: RequestedDocumentsByQueryId,
val msoMdocNonce: String,
Expand Down Expand Up @@ -116,7 +116,7 @@ class ProcessedDcqlRequest(
}
val verifiablePresentations = VerifiablePresentations(verifiablePresentationsMap)

val vpToken = Consensus.PositiveConsensus.VPTokenConsensus(verifiablePresentations)
val vpToken = Consensus.PositiveConsensus(verifiablePresentations)

// Construct the complete response object
val response = OpenId4VpResponse(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class Openid4VpUtilsTest {
}

@Test
fun testToSiopOpenId4VPConfig() {
fun testToOpenId4VPConfig() {
val trust = OpenId4VpReaderTrustImpl(null) // Assuming a default constructor or mock

// val openId4VpConfig = OpenId4VpConfig.Builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class SerializersTest {
)
)

private val vpTokenConsensus = Consensus.PositiveConsensus.VPTokenConsensus(
private val vpTokenConsensus = Consensus.PositiveConsensus(
verifiablePresentations = verifiablePresentations
)

Expand Down Expand Up @@ -264,7 +264,7 @@ class SerializersTest {
}

@Test
fun `Integration test - full VPTokenConsensus round trip with multiple presentation types`() {
fun `Integration test - full PositiveConsensus round trip with multiple presentation types`() {
val mixedPresentations = VerifiablePresentations(
mapOf(
QueryId("mixed") to listOf(
Expand All @@ -281,7 +281,7 @@ class SerializersTest {
)
)

val consensus = Consensus.PositiveConsensus.VPTokenConsensus(
val consensus = Consensus.PositiveConsensus(
verifiablePresentations = mixedPresentations
)

Expand Down Expand Up @@ -324,7 +324,7 @@ class SerializersTest {

// Deserialize using the contextual serializers
val deserialized =
VPTokenConsensusJson.decodeFromString<Consensus.PositiveConsensus.VPTokenConsensus>(
VPTokenConsensusJson.decodeFromString<Consensus.PositiveConsensus>(
serialized
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ class TransactionLogBuilderTest {
),
)

val resolvedRequestObject = ResolvedRequestObject.OpenId4VPAuthorization(
val resolvedRequestObject = ResolvedRequestObject(
client = mockk(),
responseMode = ResponseMode.DirectPostJwt(responseURI = mockk()),
query = dcql,
Expand Down Expand Up @@ -176,18 +176,6 @@ class TransactionLogBuilderTest {
assertEquals(nonPresentationLog, result)
}

@Test(expected = IllegalArgumentException::class)
fun `withRequest with OpenId4VpRequest but unsupported RequestObject throws exception`() {
val initialLog = builder.createEmptyPresentationLog()
val unsupportedResolvedRequestObject = mockk<ResolvedRequestObject.SiopAuthentication>()

val openId4VpRequest = mockk<OpenId4VpRequest> {
every { resolvedRequestObject } returns unsupportedResolvedRequestObject
}

builder.withRequest(initialLog, openId4VpRequest)
}

@Test
fun `withRelyingParty with valid processed request updates log correctly`() {
val initialLog = builder.createEmptyPresentationLog()
Expand Down Expand Up @@ -364,7 +352,7 @@ class TransactionLogBuilderTest {
)
val queryId = QueryId("query1")

val vpTokenMock = Consensus.PositiveConsensus.VPTokenConsensus(
val vpTokenMock = Consensus.PositiveConsensus(
verifiablePresentations = VerifiablePresentations(
mapOf(
queryId to listOf(
Expand Down
Loading
Loading