From 912e885babe5094984ba1c4a2f81c44ec4b435f4 Mon Sep 17 00:00:00 2001 From: Simon Mueller Date: Fri, 4 Oct 2024 16:41:47 +0200 Subject: [PATCH 01/14] Refactor Requestparser --- ...rs.kt => CSCSignatureRequestParameters.kt} | 13 ++--- ...CSCSignatureRequestParameterSerializer.kt} | 6 +-- .../at/asitplus/dif/RequestDataClassTests.kt | 19 ++++---- .../at/asitplus/openid/JwsSignedSerializer.kt | 21 +++++++++ ...quest.kt => SignatureRequestParameters.kt} | 4 +- .../wallet/lib/oidc/AuthenticationRequest.kt | 28 ++++------- .../wallet/lib/oidc/OidcSiopWallet.kt | 21 ++++++--- .../wallet/lib/oidc/RequestParameterFrom.kt | 21 +++++++++ .../lib/oidc/SignatureRequestParameterFrom.kt | 47 +++++++++++++++++++ ...ationRequestParser.kt => RequestParser.kt} | 18 +++---- .../wallet/lib/oidvci/RqesWalletService.kt | 12 ++--- 11 files changed, 148 insertions(+), 62 deletions(-) rename dif-data-classes/src/commonMain/kotlin/at/asitplus/dif/rqes/{SignatureRequestParameters.kt => CSCSignatureRequestParameters.kt} (95%) rename dif-data-classes/src/commonMain/kotlin/at/asitplus/dif/rqes/serializers/{SignatureRequestParameterSerializer.kt => CSCSignatureRequestParameterSerializer.kt} (69%) create mode 100644 openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/JwsSignedSerializer.kt rename openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/rqes/{RqesRequest.kt => SignatureRequestParameters.kt} (97%) create mode 100644 vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/RequestParameterFrom.kt create mode 100644 vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/SignatureRequestParameterFrom.kt rename vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/{AuthenticationRequestParser.kt => RequestParser.kt} (86%) diff --git a/dif-data-classes/src/commonMain/kotlin/at/asitplus/dif/rqes/SignatureRequestParameters.kt b/dif-data-classes/src/commonMain/kotlin/at/asitplus/dif/rqes/CSCSignatureRequestParameters.kt similarity index 95% rename from dif-data-classes/src/commonMain/kotlin/at/asitplus/dif/rqes/SignatureRequestParameters.kt rename to dif-data-classes/src/commonMain/kotlin/at/asitplus/dif/rqes/CSCSignatureRequestParameters.kt index d932ef38..f47ae165 100644 --- a/dif-data-classes/src/commonMain/kotlin/at/asitplus/dif/rqes/SignatureRequestParameters.kt +++ b/dif-data-classes/src/commonMain/kotlin/at/asitplus/dif/rqes/CSCSignatureRequestParameters.kt @@ -1,9 +1,7 @@ -@file:UseSerializers(SignatureRequestParameterSerializer::class) - package at.asitplus.dif.rqes import at.asitplus.dif.rqes.serializers.Asn1EncodableBase64Serializer -import at.asitplus.dif.rqes.serializers.SignatureRequestParameterSerializer +import at.asitplus.dif.rqes.serializers.CSCSignatureRequestParameterSerializer import at.asitplus.dif.rqes.collection_entries.Document import at.asitplus.dif.rqes.collection_entries.DocumentDigestEntries.CscDocumentDigest import at.asitplus.dif.rqes.enums.OperationModeEnum @@ -15,10 +13,9 @@ import at.asitplus.signum.indispensable.asn1.ObjectIdentifier import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient -import kotlinx.serialization.UseSerializers -@Serializable(with = SignatureRequestParameterSerializer::class) -sealed interface SignatureRequestParameters { +@Serializable(with = CSCSignatureRequestParameterSerializer::class) +sealed interface CSCSignatureRequestParameters { /** * The credentialID as defined in the Input parameter table in `/credentials/info` */ @@ -113,7 +110,7 @@ data class SignHashParameters( @Serializable(with = Asn1EncodableBase64Serializer::class) val signAlgoParams: Asn1Element? = null, - ) : SignatureRequestParameters { + ) : CSCSignatureRequestParameters { @Transient val signAlgorithm: SignatureAlgorithm? = getSignAlgorithm(signAlgoOid, signAlgoParams) @@ -201,7 +198,7 @@ data class SignDocParameters( @SerialName("returnValidationInformation") val returnValidationInformation: Boolean = false, - ) : SignatureRequestParameters { +) : CSCSignatureRequestParameters { init { require(credentialId != null || signatureQualifier != null) { "Either credentialId or signatureQualifier must not be null (both can be present)" } require(documentDigests != null || documents != null) { "Either documentDigests or documents must not be null (both can be present)" } diff --git a/dif-data-classes/src/commonMain/kotlin/at/asitplus/dif/rqes/serializers/SignatureRequestParameterSerializer.kt b/dif-data-classes/src/commonMain/kotlin/at/asitplus/dif/rqes/serializers/CSCSignatureRequestParameterSerializer.kt similarity index 69% rename from dif-data-classes/src/commonMain/kotlin/at/asitplus/dif/rqes/serializers/SignatureRequestParameterSerializer.kt rename to dif-data-classes/src/commonMain/kotlin/at/asitplus/dif/rqes/serializers/CSCSignatureRequestParameterSerializer.kt index 22a055d8..5d68f91f 100644 --- a/dif-data-classes/src/commonMain/kotlin/at/asitplus/dif/rqes/serializers/SignatureRequestParameterSerializer.kt +++ b/dif-data-classes/src/commonMain/kotlin/at/asitplus/dif/rqes/serializers/CSCSignatureRequestParameterSerializer.kt @@ -1,14 +1,14 @@ package at.asitplus.dif.rqes.serializers +import at.asitplus.dif.rqes.CSCSignatureRequestParameters import at.asitplus.dif.rqes.SignDocParameters import at.asitplus.dif.rqes.SignHashParameters -import at.asitplus.dif.rqes.SignatureRequestParameters import kotlinx.serialization.json.JsonContentPolymorphicSerializer import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.jsonObject -object SignatureRequestParameterSerializer : - JsonContentPolymorphicSerializer(SignatureRequestParameters::class) { +object CSCSignatureRequestParameterSerializer : + JsonContentPolymorphicSerializer(CSCSignatureRequestParameters::class) { override fun selectDeserializer(element: JsonElement) = when { "hashes" in element.jsonObject -> SignHashParameters.serializer() else -> SignDocParameters.serializer() diff --git a/dif-data-classes/src/commonTest/kotlin/at/asitplus/dif/RequestDataClassTests.kt b/dif-data-classes/src/commonTest/kotlin/at/asitplus/dif/RequestDataClassTests.kt index 86ede77f..30638e28 100644 --- a/dif-data-classes/src/commonTest/kotlin/at/asitplus/dif/RequestDataClassTests.kt +++ b/dif-data-classes/src/commonTest/kotlin/at/asitplus/dif/RequestDataClassTests.kt @@ -1,9 +1,8 @@ package at.asitplus.dif +import at.asitplus.dif.rqes.CSCSignatureRequestParameters import at.asitplus.dif.rqes.SignDocParameters import at.asitplus.dif.rqes.SignHashParameters -import at.asitplus.dif.rqes.SignatureRequestParameters -import at.asitplus.signum.indispensable.X509SignatureAlgorithm import at.asitplus.signum.indispensable.io.Base64Strict import io.github.aakira.napier.Napier import io.kotest.core.spec.style.FreeSpec @@ -143,7 +142,6 @@ class AuthenticationRequestParameterFromSerializerTest : FreeSpec({ SignHashParameters( credentialId = "1234", hashes = listOf("abcd".decodeToByteArray(Base64Strict)), - signAlgoOid = X509SignatureAlgorithm.ES256.oid ), SignDocParameters( credentialId = "1234", @@ -155,16 +153,18 @@ class AuthenticationRequestParameterFromSerializerTest : FreeSpec({ ) ) dummyEntries.forEachIndexed { i, dummyEntry -> - "Entry ${i+1}" { - val serialized = jsonSerializer.encodeToString(SignatureRequestParameters.serializer(), dummyEntry) + "Entry ${i + 1}" { + val serialized = jsonSerializer.encodeToString(CSCSignatureRequestParameters.serializer(), dummyEntry) .also { Napier.d("serialized ${dummyEntry::class}: $it") } - val deserialized = jsonSerializer.decodeFromString(SignatureRequestParameters.serializer(), serialized) + val deserialized = + jsonSerializer.decodeFromString(CSCSignatureRequestParameters.serializer(), serialized) deserialized shouldBe dummyEntry } } } + //TODO fix asn1 parsing "CSC Test vectors".config(enabled = false) - { listOf( cscTestVectorSignHash1, @@ -174,10 +174,11 @@ class AuthenticationRequestParameterFromSerializerTest : FreeSpec({ cscTestVectorSignDoc2, cscTestVectorSignDoc3 ).forEachIndexed { i, vec -> - "Testvector ${i+1}" - { + "Testvector ${i + 1}" - { val expected = jsonSerializer.decodeFromString(vec) - val actual = jsonSerializer.decodeFromString(SignatureRequestParameters.serializer(), vec) - val sanitycheck = jsonSerializer.decodeFromJsonElement(SignatureRequestParameters.serializer(), expected) + val actual = jsonSerializer.decodeFromString(CSCSignatureRequestParameters.serializer(), vec) + val sanitycheck = + jsonSerializer.decodeFromJsonElement(CSCSignatureRequestParameters.serializer(), expected) "sanitycheck" { actual shouldBe sanitycheck } diff --git a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/JwsSignedSerializer.kt b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/JwsSignedSerializer.kt new file mode 100644 index 00000000..360755ac --- /dev/null +++ b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/JwsSignedSerializer.kt @@ -0,0 +1,21 @@ +package at.asitplus.openid + +import at.asitplus.signum.indispensable.josef.JwsSigned +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +object JwsSignedSerializer : KSerializer { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("JwsSignedSerializer", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): JwsSigned = JwsSigned.deserialize(decoder.decodeString()).getOrThrow() + + override fun serialize(encoder: Encoder, value: JwsSigned) { + encoder.encodeString(value.serialize()) + } +} \ No newline at end of file diff --git a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/rqes/RqesRequest.kt b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/rqes/SignatureRequestParameters.kt similarity index 97% rename from openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/rqes/RqesRequest.kt rename to openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/rqes/SignatureRequestParameters.kt index b66d5471..c03f5989 100644 --- a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/rqes/RqesRequest.kt +++ b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/rqes/SignatureRequestParameters.kt @@ -26,7 +26,7 @@ import kotlinx.serialization.json.JsonObject * the process */ @Serializable -data class RqesRequest( +data class SignatureRequestParameters( @SerialName("response_type") val responseType: String, @@ -85,7 +85,7 @@ data class RqesRequest( signAlgoParam: Asn1Element? = null, signedProps: List? = null, conformanceLevelEnum: ConformanceLevelEnum? = ConformanceLevelEnum.ADESBB, - signedEnvelopeProperty: SignedEnvelopeProperty? = SignedEnvelopeProperty.defaultProperty(signatureFormat) + signedEnvelopeProperty: SignedEnvelopeProperty? = SignedEnvelopeProperty.defaultProperty(signatureFormat), ): CscDocumentDigest = CscDocumentDigest( hashes = this.documentDigests.map { it.hash }, diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/AuthenticationRequest.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/AuthenticationRequest.kt index b8fdf2ee..6249e9fb 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/AuthenticationRequest.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/AuthenticationRequest.kt @@ -4,17 +4,17 @@ package at.asitplus.wallet.lib.oidc import at.asitplus.catching import at.asitplus.dif.rqes.serializers.UrlSerializer import at.asitplus.openid.AuthenticationRequestParameters -import at.asitplus.signum.indispensable.josef.JwsSigned +import at.asitplus.openid.JwsSignedSerializer +import at.asitplus.openid.rqes.SignatureRequestParameters import io.ktor.http.* -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString + + @Serializable -sealed class AuthenticationRequestParametersFrom { +sealed class AuthenticationRequestParametersFrom : RequestParametersFrom { fun serialize(): String = jsonSerializer.encodeToString(this) @@ -47,18 +47,6 @@ sealed class AuthenticationRequestParametersFrom { val jsonString: String, override val parameters: AuthenticationRequestParameters, ) : AuthenticationRequestParametersFrom() - } -internal object JwsSignedSerializer : KSerializer { - - override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("JwsSignedSerializer", PrimitiveKind.STRING) - - override fun deserialize(decoder: Decoder): JwsSigned = JwsSigned.deserialize(decoder.decodeString()).getOrThrow() - - override fun serialize(encoder: Encoder, value: JwsSigned) { - encoder.encodeString(value.serialize()) - } -} \ No newline at end of file diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWallet.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWallet.kt index d91639c9..7b1742a0 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWallet.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWallet.kt @@ -3,7 +3,11 @@ package at.asitplus.wallet.lib.oidc import at.asitplus.KmmResult import at.asitplus.catching import at.asitplus.dif.PresentationDefinition -import at.asitplus.openid.* +import at.asitplus.openid.AuthenticationRequestParameters +import at.asitplus.openid.AuthenticationResponseParameters +import at.asitplus.openid.IdTokenType +import at.asitplus.openid.OAuth2AuthorizationServerMetadata +import at.asitplus.openid.OpenIdConstants import at.asitplus.openid.OpenIdConstants.BINDING_METHOD_JWK import at.asitplus.openid.OpenIdConstants.Errors import at.asitplus.openid.OpenIdConstants.ID_TOKEN @@ -11,15 +15,21 @@ import at.asitplus.openid.OpenIdConstants.PREFIX_DID_KEY import at.asitplus.openid.OpenIdConstants.SCOPE_OPENID import at.asitplus.openid.OpenIdConstants.URN_TYPE_JWK_THUMBPRINT import at.asitplus.openid.OpenIdConstants.VP_TOKEN +import at.asitplus.openid.RelyingPartyMetadata import at.asitplus.signum.indispensable.CryptoPublicKey import at.asitplus.signum.indispensable.josef.JsonWebKey import at.asitplus.signum.indispensable.josef.JsonWebKeySet import at.asitplus.signum.indispensable.josef.JwsSigned import at.asitplus.signum.indispensable.josef.toJsonWebKey -import at.asitplus.wallet.lib.agent.* +import at.asitplus.wallet.lib.agent.CredentialSubmission +import at.asitplus.wallet.lib.agent.DefaultCryptoService +import at.asitplus.wallet.lib.agent.EphemeralKeyWithoutCert +import at.asitplus.wallet.lib.agent.Holder +import at.asitplus.wallet.lib.agent.HolderAgent +import at.asitplus.wallet.lib.agent.KeyMaterial import at.asitplus.wallet.lib.jws.DefaultJwsService import at.asitplus.wallet.lib.jws.JwsService -import at.asitplus.wallet.lib.oidc.helper.AuthenticationRequestParser +import at.asitplus.wallet.lib.oidc.helper.RequestParser import at.asitplus.wallet.lib.oidc.helper.AuthenticationResponseFactory import at.asitplus.wallet.lib.oidc.helper.AuthorizationRequestValidator import at.asitplus.wallet.lib.oidc.helper.PresentationFactory @@ -129,11 +139,10 @@ class OidcSiopWallet( * [AuthenticationResponseResult]. */ suspend fun parseAuthenticationRequestParameters(input: String): KmmResult = - AuthenticationRequestParser.createWithDefaults( + RequestParser.createWithDefaults( remoteResourceRetriever = remoteResourceRetriever, requestObjectJwsVerifier = requestObjectJwsVerifier, - ).parseAuthenticationRequestParameters(input) - + ).parseRequestParameters(input).transform { KmmResult(it as AuthenticationRequestParametersFrom) } /** * Pass in the deserialized [AuthenticationRequestParameters], which were either encoded as query params, * or JSON serialized as a JWT Request Object. diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/RequestParameterFrom.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/RequestParameterFrom.kt new file mode 100644 index 00000000..0b1faac5 --- /dev/null +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/RequestParameterFrom.kt @@ -0,0 +1,21 @@ +package at.asitplus.wallet.lib.oidc + +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.json.JsonContentPolymorphicSerializer +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.jsonObject + +sealed interface RequestParametersFrom + +object RequestParametersFromSerializer : + JsonContentPolymorphicSerializer(RequestParametersFrom::class) { + override fun selectDeserializer(element: JsonElement): DeserializationStrategy { + val parameters = element.jsonObject["parameters"]?.jsonObject + return parameters?.let { + when { + "signatureQualifier" in it -> SignatureRequestParametersFrom.serializer() + else -> AuthenticationRequestParametersFrom.serializer() + } + } ?: throw Exception("Invalid parameters") + } +} \ No newline at end of file diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/SignatureRequestParameterFrom.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/SignatureRequestParameterFrom.kt new file mode 100644 index 00000000..b5844bc2 --- /dev/null +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/SignatureRequestParameterFrom.kt @@ -0,0 +1,47 @@ +package at.asitplus.wallet.lib.oidc + +import at.asitplus.catching +import at.asitplus.dif.rqes.Serializer.UrlSerializer +import at.asitplus.openid.JwsSignedSerializer +import at.asitplus.openid.rqes.SignatureRequestParameters +import io.ktor.http.* +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString + + +@Serializable +sealed class SignatureRequestParametersFrom : RequestParametersFrom { + fun serialize(): String = jsonSerializer.encodeToString(this) + + companion object { + fun deserialize(input: String) = + catching { jsonSerializer.decodeFromString(input) } + } + + abstract val parameters: SignatureRequestParameters + + @Serializable + @SerialName("JwsSigned") + data class JwsSigned( + @Serializable(JwsSignedSerializer::class) + val jwsSigned: at.asitplus.signum.indispensable.josef.JwsSigned, + override val parameters: SignatureRequestParameters, + ) : SignatureRequestParametersFrom() + + @Serializable + @SerialName("Uri") + data class Uri( + @Serializable(UrlSerializer::class) + val url: Url, + override val parameters: SignatureRequestParameters, + ) : SignatureRequestParametersFrom() + + @Serializable + @SerialName("Json") + data class Json( + val jsonString: String, + override val parameters: SignatureRequestParameters, + ) : SignatureRequestParametersFrom() + +} \ No newline at end of file diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthenticationRequestParser.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/RequestParser.kt similarity index 86% rename from vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthenticationRequestParser.kt rename to vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/RequestParser.kt index 1715582c..538d671e 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthenticationRequestParser.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/RequestParser.kt @@ -12,13 +12,14 @@ import at.asitplus.wallet.lib.oidc.AuthenticationRequestParametersFrom import at.asitplus.wallet.lib.oidc.AuthenticationResponseResult import at.asitplus.wallet.lib.oidc.RemoteResourceRetrieverFunction import at.asitplus.wallet.lib.oidc.RequestObjectJwsVerifier +import at.asitplus.wallet.lib.oidc.RequestParametersFrom import at.asitplus.wallet.lib.oidvci.OAuth2Exception import at.asitplus.wallet.lib.oidvci.decodeFromUrlQuery import io.github.aakira.napier.Napier import io.ktor.http.* import io.ktor.util.* -internal class AuthenticationRequestParser( +internal class RequestParser( /** * Need to implement if resources are defined by reference, i.e. the URL for a [JsonWebKeySet], * or the authentication request itself as `request_uri`, or `presentation_definition_uri`. @@ -36,7 +37,7 @@ internal class AuthenticationRequestParser( fun createWithDefaults( remoteResourceRetriever: RemoteResourceRetrieverFunction? = null, requestObjectJwsVerifier: RequestObjectJwsVerifier? = null, - ) = AuthenticationRequestParser( + ) = RequestParser( remoteResourceRetriever = remoteResourceRetriever ?: { null }, requestObjectJwsVerifier = requestObjectJwsVerifier ?: RequestObjectJwsVerifier { _, _ -> true }, ) @@ -47,7 +48,7 @@ internal class AuthenticationRequestParser( * to create [AuthenticationResponseParameters] that can be sent back to the Verifier, see * [AuthenticationResponseResult]. */ - suspend fun parseAuthenticationRequestParameters(input: String): KmmResult = catching { + suspend fun parseRequestParameters(input: String): KmmResult = catching { // maybe it is a request JWS val parsedParams = kotlin.run { parseRequestObjectJws(input) } ?: kotlin.runCatching { // maybe it's in the URL parameters @@ -64,20 +65,21 @@ internal class AuthenticationRequestParser( ?: throw OAuth2Exception(Errors.INVALID_REQUEST) .also { Napier.w("Could not parse authentication request: $input") } - val extractedParams = parsedParams.let { extractRequestObject(it.parameters) ?: it } - .also { Napier.i("Parsed authentication request: $it") } + val extractedParams = + parsedParams.let { extractRequestObject((it as AuthenticationRequestParametersFrom).parameters) ?: it } + .also { Napier.i("Parsed authentication request: $it") } extractedParams } - private suspend fun extractRequestObject(params: AuthenticationRequestParameters): AuthenticationRequestParametersFrom? = + private suspend fun extractRequestObject(params: AuthenticationRequestParameters): RequestParametersFrom? = params.request?.let { requestObject -> parseRequestObjectJws(requestObject) } ?: params.requestUri?.let { uri -> remoteResourceRetriever.invoke(uri) - ?.let { parseAuthenticationRequestParameters(it).getOrNull() } + ?.let { parseRequestParameters(it).getOrNull() } } - private fun parseRequestObjectJws(requestObject: String): AuthenticationRequestParametersFrom.JwsSigned? { + private fun parseRequestObjectJws(requestObject: String): RequestParametersFrom? { return JwsSigned.deserialize(requestObject).getOrNull()?.let { jws -> val params = AuthenticationRequestParameters.deserialize(jws.payload.decodeToString()).getOrElse { return null diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidvci/RqesWalletService.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidvci/RqesWalletService.kt index 5979aa9f..3619b0de 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidvci/RqesWalletService.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidvci/RqesWalletService.kt @@ -1,12 +1,12 @@ package at.asitplus.wallet.lib.oidvci +import at.asitplus.dif.rqes.CSCSignatureRequestParameters import at.asitplus.dif.rqes.enums.SignatureFormat import at.asitplus.dif.rqes.RqesConstants import at.asitplus.dif.rqes.SignDocParameters import at.asitplus.dif.rqes.SignHashParameters -import at.asitplus.dif.rqes.SignatureRequestParameters import at.asitplus.openid.AuthenticationRequestParameters -import at.asitplus.openid.rqes.RqesRequest +import at.asitplus.openid.rqes.SignatureRequestParameters import at.asitplus.signum.indispensable.X509SignatureAlgorithm import at.asitplus.wallet.lib.oauth2.OAuth2Client import com.benasher44.uuid.uuid4 @@ -18,7 +18,7 @@ class RqesWalletService( ) { suspend fun createOAuth2AuthenticationRequest( - rqesRequest: RqesRequest, + rqesRequest: at.asitplus.openid.rqes.SignatureRequestParameters, credentialId: ByteArray, ): AuthenticationRequestParameters = oauth2Client.createAuthRequest( @@ -31,7 +31,7 @@ class RqesWalletService( /** * TODO: could also use [Document] instead of [CscDocumentDigest], also [credential_id] instead of [SAD] */ - suspend fun createSignDocRequestParameters(rqesRequest: RqesRequest, sad: String): SignatureRequestParameters = + suspend fun createSignDocRequestParameters(rqesRequest: SignatureRequestParameters, sad: String): CSCSignatureRequestParameters = SignDocParameters( sad = sad, signatureQualifier = rqesRequest.signatureQualifier, @@ -45,10 +45,10 @@ class RqesWalletService( ) suspend fun createSignHashRequestParameters( - rqesRequest: RqesRequest, + rqesRequest: at.asitplus.openid.rqes.SignatureRequestParameters, credentialId: String, sad: String, - ): SignatureRequestParameters = SignHashParameters( + ): CSCSignatureRequestParameters = SignHashParameters( credentialId = credentialId, sad = sad, hashes = rqesRequest.documentDigests.map { it.hash }, From 50db0ab1fb77fa9ada633dec4868af5f4c228c2c Mon Sep 17 00:00:00 2001 From: Simon Mueller Date: Fri, 4 Oct 2024 16:43:41 +0200 Subject: [PATCH 02/14] Make RequestParser public --- .../kotlin/at/asitplus/wallet/lib/oidc/helper/RequestParser.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/RequestParser.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/RequestParser.kt index 538d671e..d3048575 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/RequestParser.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/RequestParser.kt @@ -19,7 +19,7 @@ import io.github.aakira.napier.Napier import io.ktor.http.* import io.ktor.util.* -internal class RequestParser( +class RequestParser( /** * Need to implement if resources are defined by reference, i.e. the URL for a [JsonWebKeySet], * or the authentication request itself as `request_uri`, or `presentation_definition_uri`. From bac72d34ee14fd38ddd9a3a7abe7b11c57e1f7a8 Mon Sep 17 00:00:00 2001 From: Simon Mueller Date: Fri, 4 Oct 2024 17:09:44 +0200 Subject: [PATCH 03/14] Refactor parseRequestParameters --- .../openid/AuthenticationRequestParameters.kt | 2 +- .../at/asitplus/openid/RequestParameters.kt | 23 +++++++++++++++++++ .../{rqes => }/SignatureRequestParameters.kt | 6 ++--- .../wallet/lib/oidc/AuthenticationRequest.kt | 1 - .../lib/oidc/SignatureRequestParameterFrom.kt | 2 +- .../wallet/lib/oidc/helper/RequestParser.kt | 19 ++++++++++++--- .../wallet/lib/oidvci/RqesWalletService.kt | 6 ++--- 7 files changed, 46 insertions(+), 13 deletions(-) create mode 100644 openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/RequestParameters.kt rename openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/{rqes => }/SignatureRequestParameters.kt (96%) diff --git a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/AuthenticationRequestParameters.kt b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/AuthenticationRequestParameters.kt index 6ec82113..980d94bd 100644 --- a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/AuthenticationRequestParameters.kt +++ b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/AuthenticationRequestParameters.kt @@ -339,7 +339,7 @@ data class AuthenticationRequestParameters( */ @SerialName("clientData") val clientData: String? = null, -) { +): RequestParameters { override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/RequestParameters.kt b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/RequestParameters.kt new file mode 100644 index 00000000..a4074cb4 --- /dev/null +++ b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/RequestParameters.kt @@ -0,0 +1,23 @@ +package at.asitplus.openid + +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.json.JsonContentPolymorphicSerializer +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.jsonObject + +sealed interface RequestParameters + +object RequestParametersSerializer : + JsonContentPolymorphicSerializer(RequestParameters::class) { + override fun selectDeserializer(element: JsonElement): DeserializationStrategy { + val parameters = element.jsonObject["parameters"]?.jsonObject + return parameters?.let { + when { + "signatureQualifier" in it -> SignatureRequestParameters.serializer() + else -> AuthenticationRequestParameters.serializer() + } + } ?: throw Exception("Invalid parameters") + } +} + + diff --git a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/rqes/SignatureRequestParameters.kt b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/SignatureRequestParameters.kt similarity index 96% rename from openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/rqes/SignatureRequestParameters.kt rename to openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/SignatureRequestParameters.kt index c03f5989..1cbc30fa 100644 --- a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/rqes/SignatureRequestParameters.kt +++ b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/SignatureRequestParameters.kt @@ -1,4 +1,4 @@ -package at.asitplus.openid.rqes +package at.asitplus.openid import at.asitplus.dif.rqes.collection_entries.DocumentDigestEntries.CscDocumentDigest import at.asitplus.dif.rqes.collection_entries.DocumentDigestEntries.OAuthDocumentDigest @@ -7,8 +7,6 @@ import at.asitplus.dif.rqes.enums.ConformanceLevelEnum import at.asitplus.dif.rqes.enums.SignatureFormat import at.asitplus.dif.rqes.enums.SignatureQualifierEnum import at.asitplus.dif.rqes.enums.SignedEnvelopeProperty -import at.asitplus.openid.AuthorizationDetails -import at.asitplus.openid.OpenIdConstants import at.asitplus.signum.indispensable.Digest import at.asitplus.signum.indispensable.X509SignatureAlgorithm import at.asitplus.signum.indispensable.asn1.Asn1Element @@ -69,7 +67,7 @@ data class SignatureRequestParameters( @SerialName("clientData") val clientData: String?, -) { +) : RequestParameters { fun toAuthorizationDetails(): AuthorizationDetails = AuthorizationDetails.CSCCredential( credentialID = this.clientId, diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/AuthenticationRequest.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/AuthenticationRequest.kt index 6249e9fb..aaad7ca6 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/AuthenticationRequest.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/AuthenticationRequest.kt @@ -5,7 +5,6 @@ import at.asitplus.catching import at.asitplus.dif.rqes.serializers.UrlSerializer import at.asitplus.openid.AuthenticationRequestParameters import at.asitplus.openid.JwsSignedSerializer -import at.asitplus.openid.rqes.SignatureRequestParameters import io.ktor.http.* import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/SignatureRequestParameterFrom.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/SignatureRequestParameterFrom.kt index b5844bc2..5d3a214c 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/SignatureRequestParameterFrom.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/SignatureRequestParameterFrom.kt @@ -3,7 +3,7 @@ package at.asitplus.wallet.lib.oidc import at.asitplus.catching import at.asitplus.dif.rqes.Serializer.UrlSerializer import at.asitplus.openid.JwsSignedSerializer -import at.asitplus.openid.rqes.SignatureRequestParameters +import at.asitplus.openid.SignatureRequestParameters import io.ktor.http.* import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/RequestParser.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/RequestParser.kt index d3048575..de31e99d 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/RequestParser.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/RequestParser.kt @@ -6,18 +6,24 @@ import at.asitplus.openid.AuthenticationRequestParameters import at.asitplus.openid.AuthenticationResponseParameters import at.asitplus.openid.OpenIdConstants import at.asitplus.openid.OpenIdConstants.Errors +import at.asitplus.openid.RequestParametersSerializer +import at.asitplus.openid.SignatureRequestParameters import at.asitplus.signum.indispensable.josef.JsonWebKeySet import at.asitplus.signum.indispensable.josef.JwsSigned +import at.asitplus.wallet.lib.data.vckJsonSerializer import at.asitplus.wallet.lib.oidc.AuthenticationRequestParametersFrom import at.asitplus.wallet.lib.oidc.AuthenticationResponseResult import at.asitplus.wallet.lib.oidc.RemoteResourceRetrieverFunction import at.asitplus.wallet.lib.oidc.RequestObjectJwsVerifier import at.asitplus.wallet.lib.oidc.RequestParametersFrom +import at.asitplus.wallet.lib.oidc.RequestParametersFromSerializer +import at.asitplus.wallet.lib.oidc.SignatureRequestParametersFrom import at.asitplus.wallet.lib.oidvci.OAuth2Exception import at.asitplus.wallet.lib.oidvci.decodeFromUrlQuery import io.github.aakira.napier.Napier import io.ktor.http.* import io.ktor.util.* +import kotlinx.serialization.json.encodeToJsonElement class RequestParser( /** @@ -47,15 +53,22 @@ class RequestParser( * Pass in the URL sent by the Verifier (containing the [AuthenticationRequestParameters] as query parameters), * to create [AuthenticationResponseParameters] that can be sent back to the Verifier, see * [AuthenticationResponseResult]. + * TODO finish all cases as in URI */ suspend fun parseRequestParameters(input: String): KmmResult = catching { // maybe it is a request JWS val parsedParams = kotlin.run { parseRequestObjectJws(input) } ?: kotlin.runCatching { // maybe it's in the URL parameters Url(input).let { - val params = it.parameters.flattenEntries().toMap() - .decodeFromUrlQuery() - AuthenticationRequestParametersFrom.Uri(it, params) + val params = vckJsonSerializer.encodeToJsonElement(it.parameters) + vckJsonSerializer.decodeFromJsonElement(RequestParametersSerializer, params).let { result -> + when (result) { + is AuthenticationRequestParameters -> + AuthenticationRequestParametersFrom.Uri(it, result) + is SignatureRequestParameters -> + SignatureRequestParametersFrom.Uri(it, result) + } + } } }.onFailure { it.printStackTrace() }.getOrNull() ?: catching { // maybe it is already a JSON string diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidvci/RqesWalletService.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidvci/RqesWalletService.kt index 3619b0de..210c043a 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidvci/RqesWalletService.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidvci/RqesWalletService.kt @@ -6,7 +6,7 @@ import at.asitplus.dif.rqes.RqesConstants import at.asitplus.dif.rqes.SignDocParameters import at.asitplus.dif.rqes.SignHashParameters import at.asitplus.openid.AuthenticationRequestParameters -import at.asitplus.openid.rqes.SignatureRequestParameters +import at.asitplus.openid.SignatureRequestParameters import at.asitplus.signum.indispensable.X509SignatureAlgorithm import at.asitplus.wallet.lib.oauth2.OAuth2Client import com.benasher44.uuid.uuid4 @@ -18,7 +18,7 @@ class RqesWalletService( ) { suspend fun createOAuth2AuthenticationRequest( - rqesRequest: at.asitplus.openid.rqes.SignatureRequestParameters, + rqesRequest: SignatureRequestParameters, credentialId: ByteArray, ): AuthenticationRequestParameters = oauth2Client.createAuthRequest( @@ -45,7 +45,7 @@ class RqesWalletService( ) suspend fun createSignHashRequestParameters( - rqesRequest: at.asitplus.openid.rqes.SignatureRequestParameters, + rqesRequest: SignatureRequestParameters, credentialId: String, sad: String, ): CSCSignatureRequestParameters = SignHashParameters( From 6fe05e2b558924b3500fa3b4bf9733df964e4701 Mon Sep 17 00:00:00 2001 From: Simon Mueller Date: Mon, 7 Oct 2024 10:59:30 +0200 Subject: [PATCH 04/14] Refactor parseRequestParameters --- .../wallet/lib/oidc/AuthenticationRequest.kt | 3 +- .../wallet/lib/oidc/OidcSiopWallet.kt | 10 +++-- .../wallet/lib/oidc/RequestParameterFrom.kt | 5 ++- .../lib/oidc/SignatureRequestParameterFrom.kt | 2 +- .../wallet/lib/oidc/helper/RequestParser.kt | 44 ++++++++++++++----- .../wallet/lib/oidc/OidcSiopProtocolTest.kt | 3 +- 6 files changed, 47 insertions(+), 20 deletions(-) diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/AuthenticationRequest.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/AuthenticationRequest.kt index aaad7ca6..70bfded0 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/AuthenticationRequest.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/AuthenticationRequest.kt @@ -11,7 +11,6 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString - @Serializable sealed class AuthenticationRequestParametersFrom : RequestParametersFrom { @@ -22,7 +21,7 @@ sealed class AuthenticationRequestParametersFrom : RequestParametersFrom { catching { jsonSerializer.decodeFromString(input) } } - abstract val parameters: AuthenticationRequestParameters + abstract override val parameters: AuthenticationRequestParameters @Serializable @SerialName("JwsSigned") diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWallet.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWallet.kt index 7b1742a0..5a0022ca 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWallet.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWallet.kt @@ -16,6 +16,7 @@ import at.asitplus.openid.OpenIdConstants.SCOPE_OPENID import at.asitplus.openid.OpenIdConstants.URN_TYPE_JWK_THUMBPRINT import at.asitplus.openid.OpenIdConstants.VP_TOKEN import at.asitplus.openid.RelyingPartyMetadata +import at.asitplus.openid.RequestParameters import at.asitplus.signum.indispensable.CryptoPublicKey import at.asitplus.signum.indispensable.josef.JsonWebKey import at.asitplus.signum.indispensable.josef.JsonWebKeySet @@ -29,10 +30,10 @@ import at.asitplus.wallet.lib.agent.HolderAgent import at.asitplus.wallet.lib.agent.KeyMaterial import at.asitplus.wallet.lib.jws.DefaultJwsService import at.asitplus.wallet.lib.jws.JwsService -import at.asitplus.wallet.lib.oidc.helper.RequestParser import at.asitplus.wallet.lib.oidc.helper.AuthenticationResponseFactory import at.asitplus.wallet.lib.oidc.helper.AuthorizationRequestValidator import at.asitplus.wallet.lib.oidc.helper.PresentationFactory +import at.asitplus.wallet.lib.oidc.helper.RequestParser import at.asitplus.wallet.lib.oidc.helpers.AuthorizationResponsePreparationState import at.asitplus.wallet.lib.oidvci.OAuth2Exception import io.github.aakira.napier.Napier @@ -143,6 +144,7 @@ class OidcSiopWallet( remoteResourceRetriever = remoteResourceRetriever, requestObjectJwsVerifier = requestObjectJwsVerifier, ).parseRequestParameters(input).transform { KmmResult(it as AuthenticationRequestParametersFrom) } + /** * Pass in the deserialized [AuthenticationRequestParameters], which were either encoded as query params, * or JSON serialized as a JWT Request Object. @@ -158,7 +160,7 @@ class OidcSiopWallet( * Creates the authentication response from the RP's [params] */ suspend fun createAuthnResponseParams( - params: AuthenticationRequestParametersFrom + params: AuthenticationRequestParametersFrom, ): KmmResult = startAuthorizationResponsePreparation(params).map { finalizeAuthorizationResponseParameters( request = params, @@ -180,7 +182,7 @@ class OidcSiopWallet( * Starts the authorization response building process from the RP's authentication request in [params] */ suspend fun startAuthorizationResponsePreparation( - params: AuthenticationRequestParametersFrom + params: AuthenticationRequestParametersFrom, ): KmmResult = catching { val clientMetadata = params.parameters.loadClientMetadata() val presentationDefinition = params.parameters.loadPresentationDefinition() @@ -322,7 +324,7 @@ typealias ScopePresentationDefinitionRetriever = suspend (String) -> Presentatio * Implementations need to verify the passed [JwsSigned] and return its result */ fun interface RequestObjectJwsVerifier { - operator fun invoke(jws: JwsSigned, authnRequest: AuthenticationRequestParameters): Boolean + operator fun invoke(jws: JwsSigned, request: RequestParameters): Boolean } private fun Collection?.combine(certKey: JsonWebKey?): Collection { diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/RequestParameterFrom.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/RequestParameterFrom.kt index 0b1faac5..9fa712d7 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/RequestParameterFrom.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/RequestParameterFrom.kt @@ -1,11 +1,14 @@ package at.asitplus.wallet.lib.oidc +import at.asitplus.openid.RequestParameters import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.json.JsonContentPolymorphicSerializer import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.jsonObject -sealed interface RequestParametersFrom +sealed interface RequestParametersFrom { + val parameters: RequestParameters +} object RequestParametersFromSerializer : JsonContentPolymorphicSerializer(RequestParametersFrom::class) { diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/SignatureRequestParameterFrom.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/SignatureRequestParameterFrom.kt index 5d3a214c..a151f658 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/SignatureRequestParameterFrom.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/SignatureRequestParameterFrom.kt @@ -19,7 +19,7 @@ sealed class SignatureRequestParametersFrom : RequestParametersFrom { catching { jsonSerializer.decodeFromString(input) } } - abstract val parameters: SignatureRequestParameters + abstract override val parameters: SignatureRequestParameters @Serializable @SerialName("JwsSigned") diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/RequestParser.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/RequestParser.kt index de31e99d..fb9a80bb 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/RequestParser.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/RequestParser.kt @@ -16,13 +16,10 @@ import at.asitplus.wallet.lib.oidc.AuthenticationResponseResult import at.asitplus.wallet.lib.oidc.RemoteResourceRetrieverFunction import at.asitplus.wallet.lib.oidc.RequestObjectJwsVerifier import at.asitplus.wallet.lib.oidc.RequestParametersFrom -import at.asitplus.wallet.lib.oidc.RequestParametersFromSerializer import at.asitplus.wallet.lib.oidc.SignatureRequestParametersFrom import at.asitplus.wallet.lib.oidvci.OAuth2Exception -import at.asitplus.wallet.lib.oidvci.decodeFromUrlQuery import io.github.aakira.napier.Napier import io.ktor.http.* -import io.ktor.util.* import kotlinx.serialization.json.encodeToJsonElement class RequestParser( @@ -60,26 +57,39 @@ class RequestParser( val parsedParams = kotlin.run { parseRequestObjectJws(input) } ?: kotlin.runCatching { // maybe it's in the URL parameters Url(input).let { - val params = vckJsonSerializer.encodeToJsonElement(it.parameters) + val params = + vckJsonSerializer.encodeToJsonElement(it.parameters) //ToDo Double check if this in-between step is necessary vckJsonSerializer.decodeFromJsonElement(RequestParametersSerializer, params).let { result -> when (result) { is AuthenticationRequestParameters -> AuthenticationRequestParametersFrom.Uri(it, result) + is SignatureRequestParameters -> SignatureRequestParametersFrom.Uri(it, result) + .also { Napier.d { "It did make a difference for URI" } } } } } }.onFailure { it.printStackTrace() }.getOrNull() ?: catching { // maybe it is already a JSON string - val params = AuthenticationRequestParameters.deserialize(input).getOrThrow() - AuthenticationRequestParametersFrom.Json(input, params) +// val params = AuthenticationRequestParameters.deserialize(input).getOrThrow() +// AuthenticationRequestParametersFrom.Json(input, params) + val params = vckJsonSerializer.decodeFromString(RequestParametersSerializer, input) + when (params) { + is AuthenticationRequestParameters -> + AuthenticationRequestParametersFrom.Json(input, params) + + is SignatureRequestParameters -> + SignatureRequestParametersFrom.Json(input, params) + .also { Napier.d { "It did make a difference for Json" } } + } }.getOrNull() ?: throw OAuth2Exception(Errors.INVALID_REQUEST) .also { Napier.w("Could not parse authentication request: $input") } + Napier.d { "it is of type ${parsedParams::class}" } val extractedParams = - parsedParams.let { extractRequestObject((it as AuthenticationRequestParametersFrom).parameters) ?: it } + parsedParams.let { extractRequestObject(it.parameters as AuthenticationRequestParameters) ?: it } .also { Napier.i("Parsed authentication request: $it") } extractedParams } @@ -94,13 +104,25 @@ class RequestParser( private fun parseRequestObjectJws(requestObject: String): RequestParametersFrom? { return JwsSigned.deserialize(requestObject).getOrNull()?.let { jws -> - val params = AuthenticationRequestParameters.deserialize(jws.payload.decodeToString()).getOrElse { + val params = kotlin.runCatching { + vckJsonSerializer.decodeFromString( + RequestParametersSerializer, + jws.payload.decodeToString() + ) + }.getOrElse { return null .apply { Napier.w("parseRequestObjectJws: Deserialization failed", it) } } - if (requestObjectJwsVerifier.invoke(jws, params)) - AuthenticationRequestParametersFrom.JwsSigned(jws, params) - else null + if (requestObjectJwsVerifier.invoke(jws, params)) { + when (params) { + is AuthenticationRequestParameters -> + AuthenticationRequestParametersFrom.JwsSigned(jws, params) + + is SignatureRequestParameters -> + SignatureRequestParametersFrom.JwsSigned(jws, params) + .also { Napier.d { "It did make a difference for JwsSigned" } } + } + } else null .also { Napier.w("parseRequestObjectJws: Signature not verified for $jws") } } } diff --git a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopProtocolTest.kt b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopProtocolTest.kt index ac6d45c5..4f0b105d 100644 --- a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopProtocolTest.kt +++ b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopProtocolTest.kt @@ -5,6 +5,7 @@ import at.asitplus.openid.AuthenticationResponseParameters import at.asitplus.openid.OpenIdConstants import at.asitplus.openid.OpenIdConstants.ID_TOKEN import at.asitplus.openid.OpenIdConstants.VP_TOKEN +import at.asitplus.openid.RequestParameters import at.asitplus.signum.indispensable.josef.* import at.asitplus.wallet.lib.agent.* import at.asitplus.wallet.lib.data.AtomicAttribute2023 @@ -446,7 +447,7 @@ private suspend fun buildAttestationJwt( private fun verifierAttestationVerifier(trustedKey: JsonWebKey) = object : RequestObjectJwsVerifier { - override fun invoke(jws: JwsSigned, authnRequest: AuthenticationRequestParameters): Boolean { + override fun invoke(jws: JwsSigned, request: RequestParameters): Boolean { val attestationJwt = jws.header.attestationJwt?.let { JwsSigned.deserialize(it).getOrThrow() } ?: return false val verifierJwsService = DefaultVerifierJwsService() From ed9ef85655ce8676e1dad29f3bfb2b4fc70f3113 Mon Sep 17 00:00:00 2001 From: Simon Mueller Date: Mon, 7 Oct 2024 12:49:44 +0200 Subject: [PATCH 05/14] Refactor parseRequestParameters --- .../at/asitplus/openid/RequestParameters.kt | 17 +++++----- .../wallet/lib/oidc/RequestParameterFrom.kt | 2 ++ .../wallet/lib/oidc/helper/RequestParser.kt | 31 +++++++++---------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/RequestParameters.kt b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/RequestParameters.kt index a4074cb4..4318e174 100644 --- a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/RequestParameters.kt +++ b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/RequestParameters.kt @@ -1,22 +1,21 @@ package at.asitplus.openid import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonContentPolymorphicSerializer import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.jsonObject +@Serializable sealed interface RequestParameters -object RequestParametersSerializer : - JsonContentPolymorphicSerializer(RequestParameters::class) { +object RequestParametersSerializer : JsonContentPolymorphicSerializer(RequestParameters::class) { override fun selectDeserializer(element: JsonElement): DeserializationStrategy { - val parameters = element.jsonObject["parameters"]?.jsonObject - return parameters?.let { - when { - "signatureQualifier" in it -> SignatureRequestParameters.serializer() - else -> AuthenticationRequestParameters.serializer() - } - } ?: throw Exception("Invalid parameters") + val parameters = element.jsonObject + return when { + "signatureQualifier" in parameters -> SignatureRequestParameters.serializer() + else -> AuthenticationRequestParameters.serializer() + } } } diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/RequestParameterFrom.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/RequestParameterFrom.kt index 9fa712d7..81410611 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/RequestParameterFrom.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/RequestParameterFrom.kt @@ -2,10 +2,12 @@ package at.asitplus.wallet.lib.oidc import at.asitplus.openid.RequestParameters import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonContentPolymorphicSerializer import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.jsonObject +@Serializable sealed interface RequestParametersFrom { val parameters: RequestParameters } diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/RequestParser.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/RequestParser.kt index fb9a80bb..c74f5582 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/RequestParser.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/RequestParser.kt @@ -10,17 +10,20 @@ import at.asitplus.openid.RequestParametersSerializer import at.asitplus.openid.SignatureRequestParameters import at.asitplus.signum.indispensable.josef.JsonWebKeySet import at.asitplus.signum.indispensable.josef.JwsSigned -import at.asitplus.wallet.lib.data.vckJsonSerializer import at.asitplus.wallet.lib.oidc.AuthenticationRequestParametersFrom import at.asitplus.wallet.lib.oidc.AuthenticationResponseResult import at.asitplus.wallet.lib.oidc.RemoteResourceRetrieverFunction import at.asitplus.wallet.lib.oidc.RequestObjectJwsVerifier import at.asitplus.wallet.lib.oidc.RequestParametersFrom import at.asitplus.wallet.lib.oidc.SignatureRequestParametersFrom +import at.asitplus.wallet.lib.oidc.jsonSerializer import at.asitplus.wallet.lib.oidvci.OAuth2Exception +import at.asitplus.wallet.lib.oidvci.decodeFromUrlQuery +import at.asitplus.wallet.lib.oidvci.json import io.github.aakira.napier.Napier import io.ktor.http.* -import kotlinx.serialization.json.encodeToJsonElement +import io.ktor.util.* +import kotlinx.serialization.json.JsonObject class RequestParser( /** @@ -50,32 +53,28 @@ class RequestParser( * Pass in the URL sent by the Verifier (containing the [AuthenticationRequestParameters] as query parameters), * to create [AuthenticationResponseParameters] that can be sent back to the Verifier, see * [AuthenticationResponseResult]. - * TODO finish all cases as in URI */ suspend fun parseRequestParameters(input: String): KmmResult = catching { // maybe it is a request JWS val parsedParams = kotlin.run { parseRequestObjectJws(input) } ?: kotlin.runCatching { // maybe it's in the URL parameters Url(input).let { - val params = - vckJsonSerializer.encodeToJsonElement(it.parameters) //ToDo Double check if this in-between step is necessary - vckJsonSerializer.decodeFromJsonElement(RequestParametersSerializer, params).let { result -> - when (result) { - is AuthenticationRequestParameters -> - AuthenticationRequestParametersFrom.Uri(it, result) + val params = it.parameters.flattenEntries().toMap().decodeFromUrlQuery() + when (val result = json.decodeFromJsonElement(RequestParametersSerializer, params)) { + is AuthenticationRequestParameters -> + AuthenticationRequestParametersFrom.Uri(it, result) - is SignatureRequestParameters -> - SignatureRequestParametersFrom.Uri(it, result) - .also { Napier.d { "It did make a difference for URI" } } - } + is SignatureRequestParameters -> + SignatureRequestParametersFrom.Uri(it, result) + .also { Napier.d { "It did make a difference for URI" } } } } +// } }.onFailure { it.printStackTrace() }.getOrNull() ?: catching { // maybe it is already a JSON string // val params = AuthenticationRequestParameters.deserialize(input).getOrThrow() // AuthenticationRequestParametersFrom.Json(input, params) - val params = vckJsonSerializer.decodeFromString(RequestParametersSerializer, input) - when (params) { + when (val params = jsonSerializer.decodeFromString(RequestParametersSerializer, input)) { is AuthenticationRequestParameters -> AuthenticationRequestParametersFrom.Json(input, params) @@ -105,7 +104,7 @@ class RequestParser( private fun parseRequestObjectJws(requestObject: String): RequestParametersFrom? { return JwsSigned.deserialize(requestObject).getOrNull()?.let { jws -> val params = kotlin.runCatching { - vckJsonSerializer.decodeFromString( + jsonSerializer.decodeFromString( RequestParametersSerializer, jws.payload.decodeToString() ) From 8b001a83565ef5b03e333f166c69e8fd1ec32f48 Mon Sep 17 00:00:00 2001 From: Simon Mueller Date: Mon, 7 Oct 2024 13:42:51 +0200 Subject: [PATCH 06/14] Clean up --- .../wallet/lib/oidc/RequestParameterFrom.kt | 19 ++----------------- .../wallet/lib/oidc/helper/RequestParser.kt | 10 +++------- 2 files changed, 5 insertions(+), 24 deletions(-) diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/RequestParameterFrom.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/RequestParameterFrom.kt index 81410611..06b8104d 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/RequestParameterFrom.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/RequestParameterFrom.kt @@ -1,26 +1,11 @@ package at.asitplus.wallet.lib.oidc import at.asitplus.openid.RequestParameters -import kotlinx.serialization.DeserializationStrategy +import at.asitplus.openid.RequestParametersSerializer import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonContentPolymorphicSerializer -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.jsonObject @Serializable sealed interface RequestParametersFrom { + @Serializable(with = RequestParametersSerializer::class) val parameters: RequestParameters -} - -object RequestParametersFromSerializer : - JsonContentPolymorphicSerializer(RequestParametersFrom::class) { - override fun selectDeserializer(element: JsonElement): DeserializationStrategy { - val parameters = element.jsonObject["parameters"]?.jsonObject - return parameters?.let { - when { - "signatureQualifier" in it -> SignatureRequestParametersFrom.serializer() - else -> AuthenticationRequestParametersFrom.serializer() - } - } ?: throw Exception("Invalid parameters") - } } \ No newline at end of file diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/RequestParser.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/RequestParser.kt index c74f5582..3b947704 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/RequestParser.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/RequestParser.kt @@ -66,29 +66,26 @@ class RequestParser( is SignatureRequestParameters -> SignatureRequestParametersFrom.Uri(it, result) - .also { Napier.d { "It did make a difference for URI" } } } } // } }.onFailure { it.printStackTrace() }.getOrNull() ?: catching { // maybe it is already a JSON string -// val params = AuthenticationRequestParameters.deserialize(input).getOrThrow() -// AuthenticationRequestParametersFrom.Json(input, params) when (val params = jsonSerializer.decodeFromString(RequestParametersSerializer, input)) { is AuthenticationRequestParameters -> AuthenticationRequestParametersFrom.Json(input, params) is SignatureRequestParameters -> SignatureRequestParametersFrom.Json(input, params) - .also { Napier.d { "It did make a difference for Json" } } } }.getOrNull() ?: throw OAuth2Exception(Errors.INVALID_REQUEST) .also { Napier.w("Could not parse authentication request: $input") } - Napier.d { "it is of type ${parsedParams::class}" } val extractedParams = - parsedParams.let { extractRequestObject(it.parameters as AuthenticationRequestParameters) ?: it } + (parsedParams.parameters as? AuthenticationRequestParameters)?.let { + extractRequestObject(it) + } ?: parsedParams .also { Napier.i("Parsed authentication request: $it") } extractedParams } @@ -119,7 +116,6 @@ class RequestParser( is SignatureRequestParameters -> SignatureRequestParametersFrom.JwsSigned(jws, params) - .also { Napier.d { "It did make a difference for JwsSigned" } } } } else null .also { Napier.w("parseRequestObjectJws: Signature not verified for $jws") } From 6ea7246e494d788195bb9df6edfa719e6f7661a8 Mon Sep 17 00:00:00 2001 From: Simon Mueller Date: Mon, 7 Oct 2024 13:54:44 +0200 Subject: [PATCH 07/14] Update Readme --- CHANGELOG.md | 3 +++ .../wallet/lib/oidc/helper/RequestParser.kt | 14 ++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25d78be8..f3ca4255 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ Release 5.1.0: - tbd + - New Class `SignatureRequestFrom` to handle signature requests by reference + - Rename `AuthenticationRequestParser` to `RequestParser` + - `RequestParser` can now handle `SignatureRequestFrom` Release 5.0.0: - Remove `OidcSiopWallet.newDefaultInstance()` and replace it with a constructor diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/RequestParser.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/RequestParser.kt index 3b947704..ad496ac1 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/RequestParser.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/RequestParser.kt @@ -28,7 +28,7 @@ import kotlinx.serialization.json.JsonObject class RequestParser( /** * Need to implement if resources are defined by reference, i.e. the URL for a [JsonWebKeySet], - * or the authentication request itself as `request_uri`, or `presentation_definition_uri`. + * or the request itself as `request_uri`, or `presentation_definition_uri`. * Implementations need to fetch the url passed in, and return either the body, if there is one, * or the HTTP header `Location`, i.e. if the server sends the request object as a redirect. */ @@ -50,7 +50,7 @@ class RequestParser( } /** - * Pass in the URL sent by the Verifier (containing the [AuthenticationRequestParameters] as query parameters), + * Pass in the URL sent by the Verifier (containing the [RequestParameters] as query parameters), * to create [AuthenticationResponseParameters] that can be sent back to the Verifier, see * [AuthenticationResponseResult]. */ @@ -68,7 +68,6 @@ class RequestParser( SignatureRequestParametersFrom.Uri(it, result) } } -// } }.onFailure { it.printStackTrace() }.getOrNull() ?: catching { // maybe it is already a JSON string when (val params = jsonSerializer.decodeFromString(RequestParametersSerializer, input)) { @@ -83,11 +82,10 @@ class RequestParser( .also { Napier.w("Could not parse authentication request: $input") } val extractedParams = - (parsedParams.parameters as? AuthenticationRequestParameters)?.let { - extractRequestObject(it) - } ?: parsedParams - .also { Napier.i("Parsed authentication request: $it") } - extractedParams + (parsedParams.parameters as? AuthenticationRequestParameters)?.let { extractRequestObject(it) } + ?: parsedParams + + extractedParams.also { Napier.i("Parsed authentication request: $it") } } private suspend fun extractRequestObject(params: AuthenticationRequestParameters): RequestParametersFrom? = From c036b8b367bb1b19fd2bbc617bb02dc1064c2326 Mon Sep 17 00:00:00 2001 From: Simon Mueller Date: Mon, 7 Oct 2024 18:14:39 +0200 Subject: [PATCH 08/14] Clean up --- .../kotlin/at/asitplus/dif/rqes/collection_entries/Document.kt | 1 + .../commonTest/kotlin/at/asitplus/dif/RequestDataClassTests.kt | 2 ++ 2 files changed, 3 insertions(+) diff --git a/dif-data-classes/src/commonMain/kotlin/at/asitplus/dif/rqes/collection_entries/Document.kt b/dif-data-classes/src/commonMain/kotlin/at/asitplus/dif/rqes/collection_entries/Document.kt index 622dd219..dc919493 100644 --- a/dif-data-classes/src/commonMain/kotlin/at/asitplus/dif/rqes/collection_entries/Document.kt +++ b/dif-data-classes/src/commonMain/kotlin/at/asitplus/dif/rqes/collection_entries/Document.kt @@ -20,6 +20,7 @@ import kotlinx.serialization.json.JsonObject @Serializable data class Document( /** + * TODO * base64-encoded document content to be signed, testcases weird so for now string */ @SerialName("document") diff --git a/dif-data-classes/src/commonTest/kotlin/at/asitplus/dif/RequestDataClassTests.kt b/dif-data-classes/src/commonTest/kotlin/at/asitplus/dif/RequestDataClassTests.kt index 30638e28..23a0e375 100644 --- a/dif-data-classes/src/commonTest/kotlin/at/asitplus/dif/RequestDataClassTests.kt +++ b/dif-data-classes/src/commonTest/kotlin/at/asitplus/dif/RequestDataClassTests.kt @@ -3,6 +3,7 @@ package at.asitplus.dif import at.asitplus.dif.rqes.CSCSignatureRequestParameters import at.asitplus.dif.rqes.SignDocParameters import at.asitplus.dif.rqes.SignHashParameters +import at.asitplus.signum.indispensable.X509SignatureAlgorithm import at.asitplus.signum.indispensable.io.Base64Strict import io.github.aakira.napier.Napier import io.kotest.core.spec.style.FreeSpec @@ -142,6 +143,7 @@ class AuthenticationRequestParameterFromSerializerTest : FreeSpec({ SignHashParameters( credentialId = "1234", hashes = listOf("abcd".decodeToByteArray(Base64Strict)), + signAlgoOid = X509SignatureAlgorithm.ES256.oid ), SignDocParameters( credentialId = "1234", From 0dde2d6837e568f1bc5e672bc59f45c0ede58189 Mon Sep 17 00:00:00 2001 From: Simon Mueller Date: Mon, 7 Oct 2024 18:19:54 +0200 Subject: [PATCH 09/14] Add todos for other branch --- .../kotlin/at/asitplus/wallet/lib/oidvci/RqesWalletService.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidvci/RqesWalletService.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidvci/RqesWalletService.kt index 210c043a..0a02416a 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidvci/RqesWalletService.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidvci/RqesWalletService.kt @@ -30,6 +30,7 @@ class RqesWalletService( /** * TODO: could also use [Document] instead of [CscDocumentDigest], also [credential_id] instead of [SAD] + * TODO implement [CredentialInfo] dataclass + hand over here */ suspend fun createSignDocRequestParameters(rqesRequest: SignatureRequestParameters, sad: String): CSCSignatureRequestParameters = SignDocParameters( @@ -44,6 +45,8 @@ class RqesWalletService( responseUri = this.redirectUrl, //TODO double check ) + + //TODO implement [CredentialInfo] dataclass + hand over here suspend fun createSignHashRequestParameters( rqesRequest: SignatureRequestParameters, credentialId: String, From 01f693cda612a18f065757232958e0d293e1b18d Mon Sep 17 00:00:00 2001 From: Simon Mueller Date: Wed, 9 Oct 2024 16:42:29 +0200 Subject: [PATCH 10/14] Add documentation --- .../openid/AuthenticationRequestParameters.kt | 18 ++-- .../at/asitplus/openid/RequestParameters.kt | 10 +- .../openid/SignatureRequestParameters.kt | 91 +++++++++++++++++-- .../wallet/lib/oidc/OidcSiopVerifier.kt | 4 +- .../helper/AuthenticationResponseFactory.kt | 4 +- .../helper/AuthorizationRequestValidator.kt | 2 +- .../wallet/lib/oidc/OidcSiopInteropTest.kt | 8 +- 7 files changed, 110 insertions(+), 27 deletions(-) diff --git a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/AuthenticationRequestParameters.kt b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/AuthenticationRequestParameters.kt index 980d94bd..d30899ed 100644 --- a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/AuthenticationRequestParameters.kt +++ b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/AuthenticationRequestParameters.kt @@ -31,13 +31,13 @@ data class AuthenticationRequestParameters( * Optional when JAR (RFC9101) is used. */ @SerialName("response_type") - val responseType: String? = null, + override val responseType: String? = null, /** * OIDC: REQUIRED. OAuth 2.0 Client Identifier valid at the Authorization Server. */ @SerialName("client_id") - val clientId: String? = null, + override val clientId: String, /** * OIDC: REQUIRED. Redirection URI to which the response will be sent. This URI MUST exactly match one of the @@ -64,7 +64,7 @@ data class AuthenticationRequestParameters( * parameter with a browser cookie. */ @SerialName("state") - val state: String? = null, + override val state: String? = null, /** * OIDC: OPTIONAL. String value used to associate a Client session with an ID Token, and to mitigate replay attacks. @@ -72,7 +72,7 @@ data class AuthenticationRequestParameters( * be present in the nonce values used to prevent attackers from guessing values. */ @SerialName("nonce") - val nonce: String? = null, + override val nonce: String? = null, /** * OIDC: OPTIONAL. This parameter is used to request that specific Claims be returned. The value is a JSON object @@ -173,7 +173,7 @@ data class AuthenticationRequestParameters( * scheme. */ @SerialName("client_id_scheme") - val clientIdScheme: OpenIdConstants.ClientIdScheme? = null, + override val clientIdScheme: OpenIdConstants.ClientIdScheme? = null, /** * OID4VP: OPTIONAL. String containing the Wallet's identifier. The Credential Issuer can use the discovery process @@ -207,7 +207,7 @@ data class AuthenticationRequestParameters( * authentication process to a certain endpoint using the HTTP POST method. */ @SerialName("response_mode") - val responseMode: OpenIdConstants.ResponseMode? = null, + override val responseMode: OpenIdConstants.ResponseMode? = null, /** * OID4VP: OPTIONAL. The Response URI to which the Wallet MUST send the Authorization Response using an HTTPS POST @@ -218,7 +218,7 @@ data class AuthenticationRequestParameters( * `invalid_request` Authorization Response error. */ @SerialName("response_uri") - val responseUrl: String? = null, + override val responseUri: String? = null, /** * OAuth 2.0 JAR: If signed, the Authorization Request Object SHOULD contain the Claims `iss` (issuer) and `aud` @@ -368,7 +368,7 @@ data class AuthenticationRequestParameters( if (userHint != other.userHint) return false if (issuerState != other.issuerState) return false if (responseMode != other.responseMode) return false - if (responseUrl != other.responseUrl) return false + if (responseUri != other.responseUri) return false if (audience != other.audience) return false if (issuer != other.issuer) return false if (issuedAt != other.issuedAt) return false @@ -413,7 +413,7 @@ data class AuthenticationRequestParameters( result = 31 * result + (userHint?.hashCode() ?: 0) result = 31 * result + (issuerState?.hashCode() ?: 0) result = 31 * result + (responseMode?.hashCode() ?: 0) - result = 31 * result + (responseUrl?.hashCode() ?: 0) + result = 31 * result + (responseUri?.hashCode() ?: 0) result = 31 * result + (audience?.hashCode() ?: 0) result = 31 * result + (issuer?.hashCode() ?: 0) result = 31 * result + (issuedAt?.hashCode() ?: 0) diff --git a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/RequestParameters.kt b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/RequestParameters.kt index 4318e174..ac814a98 100644 --- a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/RequestParameters.kt +++ b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/RequestParameters.kt @@ -7,7 +7,15 @@ import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.jsonObject @Serializable -sealed interface RequestParameters +sealed interface RequestParameters { + val responseType: String? + val clientId: String + val clientIdScheme: OpenIdConstants.ClientIdScheme? + val responseMode: OpenIdConstants.ResponseMode? + val responseUri: String? + val nonce: String? + val state: String? +} object RequestParametersSerializer : JsonContentPolymorphicSerializer(RequestParameters::class) { override fun selectDeserializer(element: JsonElement): DeserializationStrategy { diff --git a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/SignatureRequestParameters.kt b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/SignatureRequestParameters.kt index 1cbc30fa..dd8a510b 100644 --- a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/SignatureRequestParameters.kt +++ b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/SignatureRequestParameters.kt @@ -13,6 +13,7 @@ import at.asitplus.signum.indispensable.asn1.Asn1Element import at.asitplus.signum.indispensable.asn1.ObjectIdentifier import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient import kotlinx.serialization.json.JsonObject /** @@ -22,52 +23,126 @@ import kotlinx.serialization.json.JsonObject * In the Wallet centric model this is the request * coming from the Driving application to the wallet which starts * the process + * + * This should not be confused with the CSC-related extensions to [AuthenticationRequestParameters] which are used + * by the wallet to communicate with the QTSP using OAuth2 */ @Serializable data class SignatureRequestParameters( + /** + * OIDC: REQUIRED. OAuth 2.0 Response Type value that determines the authorization processing flow to be used, + * including what parameters are returned from the endpoints used. When using the Authorization Code Flow, this + * value is `code`. + * + * For OIDC SIOPv2, this is typically `id_token`. For OID4VP, this is typically `vp_token`. + * + * Optional when JAR (RFC9101) is used. + */ @SerialName("response_type") - val responseType: String, + override val responseType: String, + /** + * OIDC: REQUIRED. OAuth 2.0 Client Identifier valid at the Authorization Server. + */ @SerialName("client_id") - val clientId: String, + override val clientId: String, + /** + * OID4VP: OPTIONAL. A string identifying the scheme of the value in the `client_id` Authorization Request parameter + * (Client Identifier scheme). The [clientIdScheme] parameter namespaces the respective Client Identifier. If an + * Authorization Request uses the [clientIdScheme] parameter, the Wallet MUST interpret the Client Identifier of + * the Verifier in the context of the Client Identifier scheme. If the parameter is not present, the Wallet MUST + * behave as specified in RFC6749. If the same Client Identifier is used with different Client Identifier schemes, + * those occurrences MUST be treated as different Verifiers. Note that the Verifier needs to determine which Client + * Identifier schemes the Wallet supports prior to sending the Authorization Request in order to choose a supported + * scheme. + */ @SerialName("client_id_scheme") - val clientIdScheme: String? = null, + override val clientIdScheme: OpenIdConstants.ClientIdScheme? = null, /** + * OAuth 2.0 Responses: OPTIONAL. Informs the Authorization Server of the mechanism to be used for returning + * Authorization Response parameters from the Authorization Endpoint. This use of this parameter is NOT RECOMMENDED + * with a value that specifies the same Response Mode as the default Response Mode for the Response Type used. * SHOULD be direct post */ @SerialName("response_mode") - val responseMode: OpenIdConstants.ResponseMode? = null, + override val responseMode: OpenIdConstants.ResponseMode? = null, /** - * MUST be present if direct post + * OID4VP: OPTIONAL. The Response URI to which the Wallet MUST send the Authorization Response using an HTTPS POST + * request as defined by the Response Mode `direct_post`. The Response URI receives all Authorization Response + * parameters as defined by the respective Response Type. When the `response_uri` parameter is present, + * the `redirect_uri` Authorization Request parameter MUST NOT be present. If the `redirect_uri` Authorization + * Request parameter is present when the Response Mode is `direct_post`, the Wallet MUST return an + * `invalid_request` Authorization Response error. */ @SerialName("response_uri") - val responseUri: String? = null, + override val responseUri: String? = null, + /** + * OIDC: OPTIONAL. String value used to associate a Client session with an ID Token, and to mitigate replay attacks. + * The value is passed through unmodified from the Authentication Request to the ID Token. Sufficient entropy MUST + * be present in the nonce values used to prevent attackers from guessing values. + */ @SerialName("nonce") - val nonce: String, + override val nonce: String, + /** + * OIDC: RECOMMENDED. Opaque value used to maintain state between the request and the callback. Typically, + * Cross-Site Request Forgery (CSRF, XSRF) mitigation is done by cryptographically binding the value of this + * parameter with a browser cookie. + */ @SerialName("state") - val state: String? = null, + override val state: String? = null, + /** + * UC5 Draft REQUIRED. + * This parameter contains the symbolic identifier determining the kind of + * signature to be created + */ @SerialName("signatureQualifier") val signatureQualifier: SignatureQualifierEnum = SignatureQualifierEnum.EU_EIDAS_QES, + /** + * UC5 Draft REQUIRED. + * An array composed of entries for every + * document to be signed + */ @SerialName("documentDigests") val documentDigests: List, + /** + * UC5 Draft REQUIRED. + * An array composed of entries for every + * document to be signed + */ @SerialName("documentLocations") val documentLocations: List, + /** + * UC5 Draft REQUIRED. + * String containing the OID of the hash + * algorithm used to generate the hashes listed + * in [documentDigests] + */ @SerialName("hashAlgorithmOID") val hashAlgorithmOid: ObjectIdentifier = Digest.SHA256.oid, + /** + * CSC: OPTIONAL + * Arbitrary data from the signature application. It can be used to handle a + * transaction identifier or other application-spe cific data that may be useful for + * debugging purposes + */ @SerialName("clientData") val clientData: String?, ) : RequestParameters { + + @Transient + val hashAlgorithm: Digest? = Digest.entries.find { digest -> digest.oid == hashAlgorithmOid } + fun toAuthorizationDetails(): AuthorizationDetails = AuthorizationDetails.CSCCredential( credentialID = this.clientId, diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopVerifier.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopVerifier.kt index 45061096..daece5bd 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopVerifier.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopVerifier.kt @@ -182,7 +182,7 @@ class OidcSiopVerifier private constructor( */ val responseMode: OpenIdConstants.ResponseMode = OpenIdConstants.ResponseMode.FRAGMENT, /** - * Response URL to set in the [AuthenticationRequestParameters.responseUrl], + * Response URL to set in the [AuthenticationRequestParameters.responseUri], * required if [responseMode] is set to [OpenIdConstants.ResponseMode.DIRECT_POST] or * [OpenIdConstants.ResponseMode.DIRECT_POST_JWT]. */ @@ -324,7 +324,7 @@ class OidcSiopVerifier private constructor( .also { stateToResponseTypeStore.put(requestOptions.state, it) }, clientId = clientId, redirectUrl = requestOptions.buildRedirectUrl(), - responseUrl = requestOptions.responseUrl, + responseUri = requestOptions.responseUrl, clientIdScheme = clientIdScheme.clientIdScheme, scope = requestOptions.buildScope(), nonce = nonceService.provideNonce() diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthenticationResponseFactory.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthenticationResponseFactory.kt index ab4df1a6..eb0a5425 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthenticationResponseFactory.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthenticationResponseFactory.kt @@ -40,7 +40,7 @@ internal class AuthenticationResponseFactory( request: AuthenticationRequestParametersFrom, response: AuthenticationResponse, ): AuthenticationResponseResult.Post { - val url = request.parameters.responseUrl + val url = request.parameters.responseUri ?: request.parameters.redirectUrl ?: throw OAuth2Exception(Errors.INVALID_REQUEST) val responseSerialized = buildJarm(request, response) @@ -55,7 +55,7 @@ internal class AuthenticationResponseFactory( request: AuthenticationRequestParametersFrom, response: AuthenticationResponse, ): AuthenticationResponseResult.Post { - val url = request.parameters.responseUrl + val url = request.parameters.responseUri ?: request.parameters.redirectUrl ?: throw OAuth2Exception(Errors.INVALID_REQUEST) return AuthenticationResponseResult.Post(url, response.params.encodeToParameters()) diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthorizationRequestValidator.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthorizationRequestValidator.kt index 7bbef220..ba52294c 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthorizationRequestValidator.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthorizationRequestValidator.kt @@ -128,7 +128,7 @@ internal class AuthorizationRequestValidator { Napier.w("response_mode is $responseMode, but redirect_url is set") throw OAuth2Exception(Errors.INVALID_REQUEST) } - if (responseUrl == null) { + if (responseUri == null) { Napier.w("response_mode is $responseMode, but response_url is not set") throw OAuth2Exception(Errors.INVALID_REQUEST) } diff --git a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopInteropTest.kt b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopInteropTest.kt index 4a96c6e7..ebc51baa 100644 --- a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopInteropTest.kt +++ b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopInteropTest.kt @@ -246,7 +246,7 @@ class OidcSiopInteropTest : FreeSpec({ val parsed = jsonSerializer.decodeFromString(input) parsed.shouldNotBeNull() - parsed.responseUrl shouldBe "https://verifier-backend.eudiw.dev/wallet/direct_post" + parsed.responseUri shouldBe "https://verifier-backend.eudiw.dev/wallet/direct_post" parsed.clientIdScheme shouldBe OpenIdConstants.ClientIdScheme.X509_SAN_DNS parsed.responseType shouldBe "vp_token" parsed.nonce shouldBe "nonce" @@ -287,7 +287,7 @@ class OidcSiopInteropTest : FreeSpec({ payload = AuthenticationRequestParameters( nonce = "RjEQKQeG8OUaKT4ij84E8mCvry6pVSgDyqRBMW5eBTPItP4DIfbKaT6M6v6q2Dvv8fN7Im7Ifa6GI2j6dHsJaQ==", state = "ef391e30-bacc-4441-af5d-7f42fb682e02", - responseUrl = "https://example.com/ef391e30-bacc-4441-af5d-7f42fb682e02", + responseUri = "https://example.com/ef391e30-bacc-4441-af5d-7f42fb682e02", clientId = "https://example.com/ef391e30-bacc-4441-af5d-7f42fb682e02", ).serialize().encodeToByteArray(), addX5c = false @@ -302,8 +302,8 @@ class OidcSiopInteropTest : FreeSpec({ val parsed = wallet.parseAuthenticationRequestParameters(input).getOrThrow() parsed.parameters.state shouldBe "ef391e30-bacc-4441-af5d-7f42fb682e02" - parsed.parameters.responseUrl shouldBe "https://example.com/ef391e30-bacc-4441-af5d-7f42fb682e02" - parsed.parameters.clientId shouldBe parsed.parameters.responseUrl + parsed.parameters.responseUri shouldBe "https://example.com/ef391e30-bacc-4441-af5d-7f42fb682e02" + parsed.parameters.clientId shouldBe parsed.parameters.responseUri } "empty client_id" { From 4fadb72df1c569e7e02b112353ed20833894253c Mon Sep 17 00:00:00 2001 From: Simon Mueller Date: Mon, 14 Oct 2024 12:49:56 +0200 Subject: [PATCH 11/14] Make clientId nullable --- .../at/asitplus/openid/AuthenticationRequestParameters.kt | 8 ++++---- .../kotlin/at/asitplus/openid/RequestParameters.kt | 4 ++-- .../at/asitplus/openid/SignatureRequestParameters.kt | 2 +- .../at/asitplus/wallet/lib/oidc/OidcSiopVerifier.kt | 4 ++-- .../lib/oidc/helper/AuthenticationResponseFactory.kt | 4 ++-- .../lib/oidc/helper/AuthorizationRequestValidator.kt | 2 +- .../at/asitplus/wallet/lib/oidc/OidcSiopInteropTest.kt | 8 ++++---- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/AuthenticationRequestParameters.kt b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/AuthenticationRequestParameters.kt index d30899ed..71a54fd5 100644 --- a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/AuthenticationRequestParameters.kt +++ b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/AuthenticationRequestParameters.kt @@ -37,7 +37,7 @@ data class AuthenticationRequestParameters( * OIDC: REQUIRED. OAuth 2.0 Client Identifier valid at the Authorization Server. */ @SerialName("client_id") - override val clientId: String, + override val clientId: String? = null, /** * OIDC: REQUIRED. Redirection URI to which the response will be sent. This URI MUST exactly match one of the @@ -218,7 +218,7 @@ data class AuthenticationRequestParameters( * `invalid_request` Authorization Response error. */ @SerialName("response_uri") - override val responseUri: String? = null, + override val responseUrl: String? = null, /** * OAuth 2.0 JAR: If signed, the Authorization Request Object SHOULD contain the Claims `iss` (issuer) and `aud` @@ -368,7 +368,7 @@ data class AuthenticationRequestParameters( if (userHint != other.userHint) return false if (issuerState != other.issuerState) return false if (responseMode != other.responseMode) return false - if (responseUri != other.responseUri) return false + if (responseUrl != other.responseUrl) return false if (audience != other.audience) return false if (issuer != other.issuer) return false if (issuedAt != other.issuedAt) return false @@ -413,7 +413,7 @@ data class AuthenticationRequestParameters( result = 31 * result + (userHint?.hashCode() ?: 0) result = 31 * result + (issuerState?.hashCode() ?: 0) result = 31 * result + (responseMode?.hashCode() ?: 0) - result = 31 * result + (responseUri?.hashCode() ?: 0) + result = 31 * result + (responseUrl?.hashCode() ?: 0) result = 31 * result + (audience?.hashCode() ?: 0) result = 31 * result + (issuer?.hashCode() ?: 0) result = 31 * result + (issuedAt?.hashCode() ?: 0) diff --git a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/RequestParameters.kt b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/RequestParameters.kt index ac814a98..dfd5a1a2 100644 --- a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/RequestParameters.kt +++ b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/RequestParameters.kt @@ -9,10 +9,10 @@ import kotlinx.serialization.json.jsonObject @Serializable sealed interface RequestParameters { val responseType: String? - val clientId: String + val clientId: String? val clientIdScheme: OpenIdConstants.ClientIdScheme? val responseMode: OpenIdConstants.ResponseMode? - val responseUri: String? + val responseUrl: String? val nonce: String? val state: String? } diff --git a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/SignatureRequestParameters.kt b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/SignatureRequestParameters.kt index dd8a510b..49509642 100644 --- a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/SignatureRequestParameters.kt +++ b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/SignatureRequestParameters.kt @@ -79,7 +79,7 @@ data class SignatureRequestParameters( * `invalid_request` Authorization Response error. */ @SerialName("response_uri") - override val responseUri: String? = null, + override val responseUrl: String? = null, /** * OIDC: OPTIONAL. String value used to associate a Client session with an ID Token, and to mitigate replay attacks. diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopVerifier.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopVerifier.kt index daece5bd..45061096 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopVerifier.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopVerifier.kt @@ -182,7 +182,7 @@ class OidcSiopVerifier private constructor( */ val responseMode: OpenIdConstants.ResponseMode = OpenIdConstants.ResponseMode.FRAGMENT, /** - * Response URL to set in the [AuthenticationRequestParameters.responseUri], + * Response URL to set in the [AuthenticationRequestParameters.responseUrl], * required if [responseMode] is set to [OpenIdConstants.ResponseMode.DIRECT_POST] or * [OpenIdConstants.ResponseMode.DIRECT_POST_JWT]. */ @@ -324,7 +324,7 @@ class OidcSiopVerifier private constructor( .also { stateToResponseTypeStore.put(requestOptions.state, it) }, clientId = clientId, redirectUrl = requestOptions.buildRedirectUrl(), - responseUri = requestOptions.responseUrl, + responseUrl = requestOptions.responseUrl, clientIdScheme = clientIdScheme.clientIdScheme, scope = requestOptions.buildScope(), nonce = nonceService.provideNonce() diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthenticationResponseFactory.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthenticationResponseFactory.kt index eb0a5425..ab4df1a6 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthenticationResponseFactory.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthenticationResponseFactory.kt @@ -40,7 +40,7 @@ internal class AuthenticationResponseFactory( request: AuthenticationRequestParametersFrom, response: AuthenticationResponse, ): AuthenticationResponseResult.Post { - val url = request.parameters.responseUri + val url = request.parameters.responseUrl ?: request.parameters.redirectUrl ?: throw OAuth2Exception(Errors.INVALID_REQUEST) val responseSerialized = buildJarm(request, response) @@ -55,7 +55,7 @@ internal class AuthenticationResponseFactory( request: AuthenticationRequestParametersFrom, response: AuthenticationResponse, ): AuthenticationResponseResult.Post { - val url = request.parameters.responseUri + val url = request.parameters.responseUrl ?: request.parameters.redirectUrl ?: throw OAuth2Exception(Errors.INVALID_REQUEST) return AuthenticationResponseResult.Post(url, response.params.encodeToParameters()) diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthorizationRequestValidator.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthorizationRequestValidator.kt index ba52294c..7bbef220 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthorizationRequestValidator.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthorizationRequestValidator.kt @@ -128,7 +128,7 @@ internal class AuthorizationRequestValidator { Napier.w("response_mode is $responseMode, but redirect_url is set") throw OAuth2Exception(Errors.INVALID_REQUEST) } - if (responseUri == null) { + if (responseUrl == null) { Napier.w("response_mode is $responseMode, but response_url is not set") throw OAuth2Exception(Errors.INVALID_REQUEST) } diff --git a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopInteropTest.kt b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopInteropTest.kt index ebc51baa..4a96c6e7 100644 --- a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopInteropTest.kt +++ b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopInteropTest.kt @@ -246,7 +246,7 @@ class OidcSiopInteropTest : FreeSpec({ val parsed = jsonSerializer.decodeFromString(input) parsed.shouldNotBeNull() - parsed.responseUri shouldBe "https://verifier-backend.eudiw.dev/wallet/direct_post" + parsed.responseUrl shouldBe "https://verifier-backend.eudiw.dev/wallet/direct_post" parsed.clientIdScheme shouldBe OpenIdConstants.ClientIdScheme.X509_SAN_DNS parsed.responseType shouldBe "vp_token" parsed.nonce shouldBe "nonce" @@ -287,7 +287,7 @@ class OidcSiopInteropTest : FreeSpec({ payload = AuthenticationRequestParameters( nonce = "RjEQKQeG8OUaKT4ij84E8mCvry6pVSgDyqRBMW5eBTPItP4DIfbKaT6M6v6q2Dvv8fN7Im7Ifa6GI2j6dHsJaQ==", state = "ef391e30-bacc-4441-af5d-7f42fb682e02", - responseUri = "https://example.com/ef391e30-bacc-4441-af5d-7f42fb682e02", + responseUrl = "https://example.com/ef391e30-bacc-4441-af5d-7f42fb682e02", clientId = "https://example.com/ef391e30-bacc-4441-af5d-7f42fb682e02", ).serialize().encodeToByteArray(), addX5c = false @@ -302,8 +302,8 @@ class OidcSiopInteropTest : FreeSpec({ val parsed = wallet.parseAuthenticationRequestParameters(input).getOrThrow() parsed.parameters.state shouldBe "ef391e30-bacc-4441-af5d-7f42fb682e02" - parsed.parameters.responseUri shouldBe "https://example.com/ef391e30-bacc-4441-af5d-7f42fb682e02" - parsed.parameters.clientId shouldBe parsed.parameters.responseUri + parsed.parameters.responseUrl shouldBe "https://example.com/ef391e30-bacc-4441-af5d-7f42fb682e02" + parsed.parameters.clientId shouldBe parsed.parameters.responseUrl } "empty client_id" { From 13c5ae42e0137c6618f104a7afe7818b051ce13c Mon Sep 17 00:00:00 2001 From: Simon Mueller Date: Mon, 14 Oct 2024 13:02:14 +0200 Subject: [PATCH 12/14] Fix import --- .../asitplus/wallet/lib/oidc/SignatureRequestParameterFrom.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/SignatureRequestParameterFrom.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/SignatureRequestParameterFrom.kt index a151f658..7fbf69c8 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/SignatureRequestParameterFrom.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/SignatureRequestParameterFrom.kt @@ -1,7 +1,7 @@ package at.asitplus.wallet.lib.oidc import at.asitplus.catching -import at.asitplus.dif.rqes.Serializer.UrlSerializer +import at.asitplus.dif.rqes.serializers.UrlSerializer import at.asitplus.openid.JwsSignedSerializer import at.asitplus.openid.SignatureRequestParameters import io.ktor.http.* From 0bb9de5e74f1b150e2cdc367e689ccd6d5633a7f Mon Sep 17 00:00:00 2001 From: Simon Mueller Date: Mon, 14 Oct 2024 13:54:13 +0200 Subject: [PATCH 13/14] Add simple test --- .../lib/rqes/SignatureRequestParsingTests.kt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/rqes/SignatureRequestParsingTests.kt diff --git a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/rqes/SignatureRequestParsingTests.kt b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/rqes/SignatureRequestParsingTests.kt new file mode 100644 index 00000000..811c640f --- /dev/null +++ b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/rqes/SignatureRequestParsingTests.kt @@ -0,0 +1,18 @@ +package at.asitplus.wallet.lib.rqes + +import at.asitplus.wallet.lib.oidc.SignatureRequestParametersFrom +import at.asitplus.wallet.lib.oidc.helper.RequestParser +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe + +class SignatureRequestParsingTests : FreeSpec({ + //TODO better tests + val jwt = + """eyJhbGciOiJSUzI1NiIsImtpZCI6ImRpZDpkcl9wb2M6c2lnIzEiLCJ0eXAiOiJvYXV0aC1hdXRoei1yZXErand0In0.eyJyZXNwb25zZV90eXBlIjoic2lnbl9yZXF1ZXN0IiwiY2xpZW50X2lkIjoiaHR0cHM6Ly9hcHBzLmVnaXouZ3YuYXQvZHJpdmluZ2FwcCIsImNsaWVudF9pZF9zY2hlbWUiOiJyZWRpcmVjdF91cmkiLCJyZXNwb25zZV9tb2RlIjoiZGlyZWN0X3Bvc3QiLCJyZXNwb25zZV91cmkiOiJodHRwczovL2FwcHMuZWdpei5ndi5hdC9kcml2aW5nYXBwL3dhbGxldC9zaWduUmVzcG9uc2UiLCJub25jZSI6ImQ5NWMwOGM4LTNhYmUtNDc5ZS05YzM1LTg3YmYyMTk2NzdhZCIsInN0YXRlIjoiMDFmY2EwMTEtZmU0Yi00NDQ2LTlmYWQtMDVhNTkwZjMzMTZlIiwic2lnbmF0dXJlUXVhbGlmaWVyIjoiZXVfZWlkYXNfcWVzIiwiZG9jdW1lbnREaWdlc3RzIjpbeyJoYXNoIjoiaGJlREZZUUowODNrMXJQb3JsM0hYeVJ3WkM0VG9LZUlVN2thR0dJYkUwWT0iLCJsYWJlbCI6InRlc3QudHh0In1dLCJkb2N1bWVudExvY2F0aW9ucyI6W3sidXJpIjoiaHR0cHM6Ly9hcHBzLmVnaXouZ3YuYXQvZHJpdmluZ2FwcC9kb2MvY2FsbGJhY2s_dXVpZD0zMDliNzg5ZS0xZTNlLTRjNzMtYmNhNi0zMzIyY2U0YjgxMTQiLCJtZXRob2QiOnsidHlwZSI6InB1YmxpYyIsIm9uZVRpbWVQYXNzd29yZDoiOm51bGx9fV0sImhhc2hBbGdvcml0aG1PSUQiOiIyLjE2Ljg0MC4xLjEwMS4zLjQuMi4xIiwiY2xpZW50RGF0YSI6bnVsbH0.FgD4CT_x-uzbOLMxqwuNB9dr8v6OieCgGsQJFlEUy0QUHnAITFkbQKm8p-mEqYgDClkUOnqih0q9j8ou-9V88ugU3c1BL3ZSilf2hLlmkfnEA3D1YPv3fsKDsGpd_DF1pWOZoKF4h10aUsF65076NycPBUn5xGBMLBaMUonVUcNzsZ_4e-MQZbQIqDybwr_d7giv0IU-HZzUIMfFB7aYwST8WMeB264Hl3T53nNr3o6zNQD5el-IfOYrRgz-gOwRkR9ewOquTkcFu1BPWSwH_BenEUlgECrf9Di2bGAcLrC4DLIc79dyPGKi3WZO4HAoZWIdN5wEeSf6Ke4Ua0GUFiZlu_a1wtAs5ZL6iClkxS91kB3E59yOH6lf41EGxI2TE7M3giGBswJS9vIeU6mQDmy42pkNS6PE5VUIau0wJcyu_ChK-Ms6svEQgQ_hC4aKYiYBf4rnRLW8hirG-hSH91qvkqmS89STalIfl1eZtxThhmhxhldNkqUuDGlgTyFv""" + + "can parse SignatureRequestParameter from signed JWT" { + val parser = RequestParser.createWithDefaults() + val res = parser.parseRequestParameters(jwt).getOrThrow() + res::class shouldBe SignatureRequestParametersFrom.JwsSigned::class + } +}) \ No newline at end of file From cc90a974a0c622f1bc8bf35c46d5796f8686f2fe Mon Sep 17 00:00:00 2001 From: Simon Mueller Date: Mon, 14 Oct 2024 14:07:22 +0200 Subject: [PATCH 14/14] Add simple test --- .../src/commonMain/kotlin/at/asitplus/dif/rqes/Misc.kt | 4 ++-- .../kotlin/at/asitplus/openid/SignatureRequestParameters.kt | 4 ++-- .../asitplus/wallet/lib/rqes/SignatureRequestParsingTests.kt | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/dif-data-classes/src/commonMain/kotlin/at/asitplus/dif/rqes/Misc.kt b/dif-data-classes/src/commonMain/kotlin/at/asitplus/dif/rqes/Misc.kt index c7c04055..2a3767dd 100644 --- a/dif-data-classes/src/commonMain/kotlin/at/asitplus/dif/rqes/Misc.kt +++ b/dif-data-classes/src/commonMain/kotlin/at/asitplus/dif/rqes/Misc.kt @@ -23,7 +23,7 @@ internal fun getSignAlgorithm(signAlgoOid: ObjectIdentifier, signAlgoParams: Asn } @Throws(Exception::class) -internal fun getHashAlgorithm(hashAlgorithmOid: ObjectIdentifier?, signatureAlgorithm: SignatureAlgorithm?) = +fun getHashAlgorithm(hashAlgorithmOid: ObjectIdentifier?, signatureAlgorithm: SignatureAlgorithm? = null) = hashAlgorithmOid?.let { Digest.entries.find { digest -> digest.oid == it } } ?: when(signatureAlgorithm) { @@ -32,4 +32,4 @@ internal fun getHashAlgorithm(hashAlgorithmOid: ObjectIdentifier?, signatureAlgo is SignatureAlgorithm.HMAC -> signatureAlgorithm.digest is SignatureAlgorithm.RSA -> signatureAlgorithm.digest null -> null - } ?: throw Exception("Unknown hashing algorithm") + } ?: throw Exception("Unknown hashing algorithm defined with oid $hashAlgorithmOid or signature algorithm $signatureAlgorithm") diff --git a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/SignatureRequestParameters.kt b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/SignatureRequestParameters.kt index 49509642..40497807 100644 --- a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/SignatureRequestParameters.kt +++ b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/SignatureRequestParameters.kt @@ -7,6 +7,7 @@ import at.asitplus.dif.rqes.enums.ConformanceLevelEnum import at.asitplus.dif.rqes.enums.SignatureFormat import at.asitplus.dif.rqes.enums.SignatureQualifierEnum import at.asitplus.dif.rqes.enums.SignedEnvelopeProperty +import at.asitplus.dif.rqes.getHashAlgorithm import at.asitplus.signum.indispensable.Digest import at.asitplus.signum.indispensable.X509SignatureAlgorithm import at.asitplus.signum.indispensable.asn1.Asn1Element @@ -18,7 +19,6 @@ import kotlinx.serialization.json.JsonObject /** * TODO: Find new home (different subfolder most likely) - * TODO: Describe vars * * In the Wallet centric model this is the request * coming from the Driving application to the wallet which starts @@ -141,7 +141,7 @@ data class SignatureRequestParameters( ) : RequestParameters { @Transient - val hashAlgorithm: Digest? = Digest.entries.find { digest -> digest.oid == hashAlgorithmOid } + val hashAlgorithm: Digest = getHashAlgorithm(hashAlgorithmOid) fun toAuthorizationDetails(): AuthorizationDetails = AuthorizationDetails.CSCCredential( diff --git a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/rqes/SignatureRequestParsingTests.kt b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/rqes/SignatureRequestParsingTests.kt index 811c640f..3a502a08 100644 --- a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/rqes/SignatureRequestParsingTests.kt +++ b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/rqes/SignatureRequestParsingTests.kt @@ -4,6 +4,7 @@ import at.asitplus.wallet.lib.oidc.SignatureRequestParametersFrom import at.asitplus.wallet.lib.oidc.helper.RequestParser import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe class SignatureRequestParsingTests : FreeSpec({ //TODO better tests