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/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/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/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/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..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 @@ -1,8 +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 @@ -155,16 +155,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 +176,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/AuthenticationRequestParameters.kt b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/AuthenticationRequestParameters.kt index 6ec82113..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 @@ -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? = null, /** * 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 responseUrl: String? = null, /** * OAuth 2.0 JAR: If signed, the Authorization Request Object SHOULD contain the Claims `iss` (issuer) and `aud` @@ -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/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/RequestParameters.kt b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/RequestParameters.kt new file mode 100644 index 00000000..dfd5a1a2 --- /dev/null +++ b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/RequestParameters.kt @@ -0,0 +1,30 @@ +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 { + val responseType: String? + val clientId: String? + val clientIdScheme: OpenIdConstants.ClientIdScheme? + val responseMode: OpenIdConstants.ResponseMode? + val responseUrl: String? + val nonce: String? + val state: String? +} + +object RequestParametersSerializer : JsonContentPolymorphicSerializer(RequestParameters::class) { + override fun selectDeserializer(element: JsonElement): DeserializationStrategy { + val parameters = element.jsonObject + return when { + "signatureQualifier" in parameters -> SignatureRequestParameters.serializer() + else -> AuthenticationRequestParameters.serializer() + } + } +} + + 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 new file mode 100644 index 00000000..40497807 --- /dev/null +++ b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/SignatureRequestParameters.kt @@ -0,0 +1,173 @@ +package at.asitplus.openid + +import at.asitplus.dif.rqes.collection_entries.DocumentDigestEntries.CscDocumentDigest +import at.asitplus.dif.rqes.collection_entries.DocumentDigestEntries.OAuthDocumentDigest +import at.asitplus.dif.rqes.collection_entries.DocumentLocation +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 +import at.asitplus.signum.indispensable.asn1.ObjectIdentifier +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import kotlinx.serialization.json.JsonObject + +/** + * TODO: Find new home (different subfolder most likely) + * + * 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") + override val responseType: String, + + /** + * OIDC: REQUIRED. OAuth 2.0 Client Identifier valid at the Authorization Server. + */ + @SerialName("client_id") + 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") + 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") + override val responseMode: OpenIdConstants.ResponseMode? = null, + + /** + * 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") + override val responseUrl: 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") + 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") + 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 = getHashAlgorithm(hashAlgorithmOid) + + fun toAuthorizationDetails(): AuthorizationDetails = + AuthorizationDetails.CSCCredential( + credentialID = this.clientId, + signatureQualifier = this.signatureQualifier, + hashAlgorithmOid = this.hashAlgorithmOid, + documentDigests = this.documentDigests, + documentLocations = this.documentLocations, + ) + + fun getCscDocumentDigests( + signatureFormat: SignatureFormat, + signAlgorithm: X509SignatureAlgorithm, + signAlgoParam: Asn1Element? = null, + signedProps: List? = null, + conformanceLevelEnum: ConformanceLevelEnum? = ConformanceLevelEnum.ADESBB, + signedEnvelopeProperty: SignedEnvelopeProperty? = SignedEnvelopeProperty.defaultProperty(signatureFormat), + ): CscDocumentDigest = + CscDocumentDigest( + hashes = this.documentDigests.map { it.hash }, + hashAlgorithmOid = this.hashAlgorithmOid, + signatureFormat = signatureFormat, + conformanceLevel = conformanceLevelEnum, + signAlgoOid = signAlgorithm.oid, + signAlgoParams = signAlgoParam, + signedProps = signedProps, + signedEnvelopeProperty = signedEnvelopeProperty + ) +} 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/RqesRequest.kt deleted file mode 100644 index b66d5471..00000000 --- a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/rqes/RqesRequest.kt +++ /dev/null @@ -1,100 +0,0 @@ -package at.asitplus.openid.rqes - -import at.asitplus.dif.rqes.collection_entries.DocumentDigestEntries.CscDocumentDigest -import at.asitplus.dif.rqes.collection_entries.DocumentDigestEntries.OAuthDocumentDigest -import at.asitplus.dif.rqes.collection_entries.DocumentLocation -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 -import at.asitplus.signum.indispensable.asn1.ObjectIdentifier -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -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 - * the process - */ -@Serializable -data class RqesRequest( - - @SerialName("response_type") - val responseType: String, - - @SerialName("client_id") - val clientId: String, - - @SerialName("client_id_scheme") - val clientIdScheme: String? = null, - - /** - * SHOULD be direct post - */ - @SerialName("response_mode") - val responseMode: OpenIdConstants.ResponseMode? = null, - - /** - * MUST be present if direct post - */ - @SerialName("response_uri") - val responseUri: String? = null, - - @SerialName("nonce") - val nonce: String, - - @SerialName("state") - val state: String? = null, - - @SerialName("signatureQualifier") - val signatureQualifier: SignatureQualifierEnum = SignatureQualifierEnum.EU_EIDAS_QES, - - @SerialName("documentDigests") - val documentDigests: List, - - @SerialName("documentLocations") - val documentLocations: List, - - @SerialName("hashAlgorithmOID") - val hashAlgorithmOid: ObjectIdentifier = Digest.SHA256.oid, - - @SerialName("clientData") - val clientData: String?, -) { - fun toAuthorizationDetails(): AuthorizationDetails = - AuthorizationDetails.CSCCredential( - credentialID = this.clientId, - signatureQualifier = this.signatureQualifier, - hashAlgorithmOid = this.hashAlgorithmOid, - documentDigests = this.documentDigests, - documentLocations = this.documentLocations, - ) - - fun getCscDocumentDigests( - signatureFormat: SignatureFormat, - signAlgorithm: X509SignatureAlgorithm, - signAlgoParam: Asn1Element? = null, - signedProps: List? = null, - conformanceLevelEnum: ConformanceLevelEnum? = ConformanceLevelEnum.ADESBB, - signedEnvelopeProperty: SignedEnvelopeProperty? = SignedEnvelopeProperty.defaultProperty(signatureFormat) - ): CscDocumentDigest = - CscDocumentDigest( - hashes = this.documentDigests.map { it.hash }, - hashAlgorithmOid = this.hashAlgorithmOid, - signatureFormat = signatureFormat, - conformanceLevel = conformanceLevelEnum, - signAlgoOid = signAlgorithm.oid, - signAlgoParams = signAlgoParam, - signedProps = signedProps, - signedEnvelopeProperty = signedEnvelopeProperty - ) -} 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..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 @@ -4,17 +4,15 @@ 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 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) @@ -23,7 +21,7 @@ sealed class AuthenticationRequestParametersFrom { catching { jsonSerializer.decodeFromString(input) } } - abstract val parameters: AuthenticationRequestParameters + abstract override val parameters: AuthenticationRequestParameters @Serializable @SerialName("JwsSigned") @@ -47,18 +45,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..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 @@ -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,18 +15,25 @@ 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.openid.RequestParameters 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.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 @@ -129,10 +140,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, @@ -149,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, @@ -171,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() @@ -313,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 new file mode 100644 index 00000000..06b8104d --- /dev/null +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/RequestParameterFrom.kt @@ -0,0 +1,11 @@ +package at.asitplus.wallet.lib.oidc + +import at.asitplus.openid.RequestParameters +import at.asitplus.openid.RequestParametersSerializer +import kotlinx.serialization.Serializable + +@Serializable +sealed interface RequestParametersFrom { + @Serializable(with = RequestParametersSerializer::class) + val parameters: RequestParameters +} \ 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..7fbf69c8 --- /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.serializers.UrlSerializer +import at.asitplus.openid.JwsSignedSerializer +import at.asitplus.openid.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 override 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 55% 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..ad496ac1 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 @@ -6,22 +6,29 @@ 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.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 io.ktor.util.* +import kotlinx.serialization.json.JsonObject -internal class AuthenticationRequestParser( +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. */ @@ -36,56 +43,79 @@ internal class AuthenticationRequestParser( fun createWithDefaults( remoteResourceRetriever: RemoteResourceRetrieverFunction? = null, requestObjectJwsVerifier: RequestObjectJwsVerifier? = null, - ) = AuthenticationRequestParser( + ) = RequestParser( remoteResourceRetriever = remoteResourceRetriever ?: { null }, requestObjectJwsVerifier = requestObjectJwsVerifier ?: RequestObjectJwsVerifier { _, _ -> true }, ) } /** - * 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]. */ - 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 Url(input).let { - val params = it.parameters.flattenEntries().toMap() - .decodeFromUrlQuery() - AuthenticationRequestParametersFrom.Uri(it, params) + 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) + } } }.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) + } }.getOrNull() ?: 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") } - extractedParams + val extractedParams = + (parsedParams.parameters as? AuthenticationRequestParameters)?.let { extractRequestObject(it) } + ?: parsedParams + + extractedParams.also { Napier.i("Parsed authentication request: $it") } } - 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 { + val params = kotlin.runCatching { + jsonSerializer.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) + } + } else null .also { Napier.w("parseRequestObjectJws: Signature not verified for $jws") } } } 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..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 @@ -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.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: SignatureRequestParameters, credentialId: ByteArray, ): AuthenticationRequestParameters = oauth2Client.createAuthRequest( @@ -30,8 +30,9 @@ 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: RqesRequest, sad: String): SignatureRequestParameters = + suspend fun createSignDocRequestParameters(rqesRequest: SignatureRequestParameters, sad: String): CSCSignatureRequestParameters = SignDocParameters( sad = sad, signatureQualifier = rqesRequest.signatureQualifier, @@ -44,11 +45,13 @@ class RqesWalletService( responseUri = this.redirectUrl, //TODO double check ) + + //TODO implement [CredentialInfo] dataclass + hand over here suspend fun createSignHashRequestParameters( - rqesRequest: RqesRequest, + rqesRequest: SignatureRequestParameters, credentialId: String, sad: String, - ): SignatureRequestParameters = SignHashParameters( + ): CSCSignatureRequestParameters = SignHashParameters( credentialId = credentialId, sad = sad, hashes = rqesRequest.documentDigests.map { it.hash }, 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() 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..3a502a08 --- /dev/null +++ b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/rqes/SignatureRequestParsingTests.kt @@ -0,0 +1,19 @@ +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 +import io.kotest.matchers.shouldNotBe + +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