Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend RequestParsing to SignatureRequests #143

Merged
merged 14 commits into from
Oct 16, 2024
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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`
*/
Expand Down Expand Up @@ -113,7 +110,7 @@ data class SignHashParameters(
@Serializable(with = Asn1EncodableBase64Serializer::class)
val signAlgoParams: Asn1Element? = null,

) : SignatureRequestParameters {
) : CSCSignatureRequestParameters {

@Transient
val signAlgorithm: SignatureAlgorithm? = getSignAlgorithm(signAlgoOid, signAlgoParams)
Expand Down Expand Up @@ -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)" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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")
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
@@ -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>(SignatureRequestParameters::class) {
object CSCSignatureRequestParameterSerializer :
JsonContentPolymorphicSerializer<CSCSignatureRequestParameters>(CSCSignatureRequestParameters::class) {
override fun selectDeserializer(element: JsonElement) = when {
"hashes" in element.jsonObject -> SignHashParameters.serializer()
else -> SignDocParameters.serializer()
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -174,10 +176,11 @@ class AuthenticationRequestParameterFromSerializerTest : FreeSpec({
cscTestVectorSignDoc2,
cscTestVectorSignDoc3
).forEachIndexed { i, vec ->
"Testvector ${i+1}" - {
"Testvector ${i + 1}" - {
val expected = jsonSerializer.decodeFromString<JsonObject>(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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -64,15 +64,15 @@ 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.
* 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? = 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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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`
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<JwsSigned> {

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())
}
}
Original file line number Diff line number Diff line change
@@ -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>(RequestParameters::class) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<RequestParameters> {
val parameters = element.jsonObject
return when {
"signatureQualifier" in parameters -> SignatureRequestParameters.serializer()
else -> AuthenticationRequestParameters.serializer()
}
}
}


Loading
Loading