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" {