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

Feature/signature services #127

Draft
wants to merge 51 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
d5f9077
OID4VCI: Support parsing multiple proofs and JWK key binding
nodh Sep 16, 2024
6b92654
Fix comment
n0900 Sep 9, 2024
01c0713
Add stumps for signature creation
n0900 Sep 9, 2024
6d6366f
Add relevant Enum classes
n0900 Sep 11, 2024
5894263
Restructure folders
n0900 Sep 11, 2024
9a69573
Add Test vectors
n0900 Sep 11, 2024
8c72bae
Add Test vectors
n0900 Sep 13, 2024
1120c9e
Add SignatureQualifier
n0900 Sep 13, 2024
33dd4e8
Add RqesRequest
n0900 Sep 13, 2024
a4557ce
Add RqesRequest
n0900 Sep 13, 2024
b9c6a59
Change default values
n0900 Sep 13, 2024
b637fde
Add SignDoc helper function
n0900 Sep 13, 2024
e285f85
Add SignDocParameter Init
n0900 Sep 13, 2024
fc04ea5
Change authorizationDetails type
n0900 Sep 16, 2024
5ce153e
Change authorizationDetails type
n0900 Sep 16, 2024
bc8cd10
Fix artifactVersion
n0900 Sep 16, 2024
62b803b
Add Serialization Annotation
n0900 Sep 16, 2024
5e4caae
Change url to string java compatability
n0900 Sep 16, 2024
daf05e4
Fix constructor
n0900 Sep 19, 2024
20731c8
Extend RqesWalletService
n0900 Sep 19, 2024
a779167
Extend RqesWalletService
n0900 Sep 19, 2024
c3c90a2
Refactor RqesWalletService
n0900 Sep 25, 2024
50fb66a
Change credentialID to bytearray
n0900 Sep 25, 2024
536046a
Fix variable names in test cases
n0900 Oct 2, 2024
c211cc1
Add custom serializers
n0900 Oct 3, 2024
1d86034
Integrate indispensable data classes
n0900 Oct 3, 2024
cd70d07
Integrate indispensable data classes
n0900 Oct 3, 2024
94030ce
Integrate indispensable data classes
n0900 Oct 7, 2024
d8752a3
Integrate indispensable data classes
n0900 Oct 7, 2024
ee73016
Integrate indispensable data classes
n0900 Oct 9, 2024
a84103f
Integrate indispensable data classes
n0900 Oct 9, 2024
48ffebe
Refactor signAlgorithm function
n0900 Oct 14, 2024
65820f0
Refactor hashAlgorithm function
n0900 Oct 14, 2024
49dd43b
Revert TokenResponse changes
n0900 Oct 14, 2024
70fe45a
Revert change
n0900 Oct 14, 2024
1be694e
serializer package to lower case
n0900 Oct 14, 2024
912e885
Refactor Requestparser
n0900 Oct 4, 2024
50db0ab
Make RequestParser public
n0900 Oct 4, 2024
bac72d3
Refactor parseRequestParameters
n0900 Oct 4, 2024
6fe05e2
Refactor parseRequestParameters
n0900 Oct 7, 2024
ed9ef85
Refactor parseRequestParameters
n0900 Oct 7, 2024
8b001a8
Clean up
n0900 Oct 7, 2024
6ea7246
Update Readme
n0900 Oct 7, 2024
c036b8b
Clean up
n0900 Oct 7, 2024
0dde2d6
Add todos for other branch
n0900 Oct 7, 2024
01f693c
Add documentation
n0900 Oct 9, 2024
4fadb72
Make clientId nullable
n0900 Oct 14, 2024
13c5ae4
Fix import
n0900 Oct 14, 2024
0bb9de5
Add simple test
n0900 Oct 14, 2024
cc90a97
Add simple test
n0900 Oct 14, 2024
d1c106f
Merge pull request #143 from a-sit-plus/feature/refactorRequestParser
n0900 Oct 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Expand Up @@ -2,15 +2,12 @@

package at.asitplus.dif

import at.asitplus.dif.rqes.Base64URLTransactionDataSerializer
import at.asitplus.dif.rqes.TransactionDataEntry
import at.asitplus.dif.rqes.serializers.Base64URLTransactionDataSerializer
import at.asitplus.dif.rqes.collection_entries.TransactionData
import com.benasher44.uuid.uuid4
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.jsonObject

@Serializable(with = InputDescriptorSerializer::class)
sealed interface InputDescriptor {
Expand Down Expand Up @@ -63,13 +60,6 @@ data class QesInputDescriptor(
@SerialName("constraints")
override val constraints: Constraint? = null,
@SerialName("transaction_data")
val transactionData: List<@Serializable(Base64URLTransactionDataSerializer::class) TransactionDataEntry>,
val transactionData: List<@Serializable(Base64URLTransactionDataSerializer::class) TransactionData>,
) : InputDescriptor


object InputDescriptorSerializer : JsonContentPolymorphicSerializer<InputDescriptor>(InputDescriptor::class) {
override fun selectDeserializer(element: JsonElement) = when {
"transaction_data" in element.jsonObject -> QesInputDescriptor.serializer()
else -> DifInputDescriptor.serializer()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package at.asitplus.dif

import kotlinx.serialization.json.JsonContentPolymorphicSerializer
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.jsonObject


object InputDescriptorSerializer : JsonContentPolymorphicSerializer<InputDescriptor>(InputDescriptor::class) {
override fun selectDeserializer(element: JsonElement) = when {
"transaction_data" in element.jsonObject -> QesInputDescriptor.serializer()
else -> DifInputDescriptor.serializer()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
package at.asitplus.dif.rqes

import at.asitplus.dif.rqes.serializers.Asn1EncodableBase64Serializer
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
import at.asitplus.dif.rqes.enums.SignatureQualifierEnum
import at.asitplus.signum.indispensable.Digest
import at.asitplus.signum.indispensable.SignatureAlgorithm
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

@Serializable(with = CSCSignatureRequestParameterSerializer::class)
sealed interface CSCSignatureRequestParameters {
/**
* The credentialID as defined in the Input parameter table in `/credentials/info`
*/
val credentialId: String?

/**
* The Signature Activation Data returned by the Credential Authorization
* methods. Not needed if the signing application has passed an access token in
* the “Authorization” HTTP header with scope “credential”, which is also good for
* the credential identified by credentialID.
* Note: For backward compatibility, signing applications MAY pass access tokens
* with scope “credential” in this parameter.
*/
val sad: String?

/**
* The type of operation mode requested to the remote signing server
* The default value is “S”, so if the parameter is omitted then the remote signing
* server will manage the request in synchronous operation mode.
*/
val operationMode: OperationModeEnum?

/**
* Maximum period of time, expressed in milliseconds, until which the server
* SHALL keep the request outcome(s) available for the client application retrieval.
* This parameter MAY be specified only if the parameter operationMode is “A”.
*/
val validityPeriod: Int?

/**
* Value of one location where the server will notify the signature creation
* operation completion, as an URI value. This parameter MAY be specified only if
* the parameter operationMode is “A”.
*/
val responseUri: String?

/**
* 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
*/
val clientData: String?
}

@Serializable
data class SignHashParameters(

@SerialName("credentialID")
override val credentialId: String,

@SerialName("SAD")
override val sad: String? = null,

@SerialName("operationMode")
override val operationMode: OperationModeEnum = OperationModeEnum.SYNCHRONOUS,

@SerialName("validity_period")
override val validityPeriod: Int? = null,

@SerialName("response_uri")
override val responseUri: String? = null,

@SerialName("clientData")
override val clientData: String? = null,

/**
* Input-type is JsonArray - do not use HashesSerializer!
* One or more base64-encoded hash values to be signed
*/
@SerialName("hashes")
val hashes: Hashes,

/**
* String containing the OID of the hash algorithm used to generate the hashes
*/
@SerialName("hashAlgorithmOID")
val hashAlgorithmOid: ObjectIdentifier? = null,

/**
* The OID of the algorithm to use for signing. It SHALL be one of the values
* allowed by the credential as returned in keyAlgo as defined in `credentials/info` or as defined
* in `credentials/list`
*/
@SerialName("signAlgo")
val signAlgoOid: ObjectIdentifier,

/**
* The Base64-encoded DER-encoded ASN.1 signature algorithm parameters if required by
* the signature algorithm - Necessary for RSASSA-PSS for example
*/
@SerialName("signAlgoParams")
@Serializable(with = Asn1EncodableBase64Serializer::class)
val signAlgoParams: Asn1Element? = null,

) : CSCSignatureRequestParameters {

@Transient
val signAlgorithm: SignatureAlgorithm? = getSignAlgorithm(signAlgoOid, signAlgoParams)

@Transient
val hashAlgorithm: Digest = getHashAlgorithm(hashAlgorithmOid, signAlgorithm)

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false

other as SignHashParameters
if (!hashes.contentEquals(other.hashes)) return false
if (credentialId != other.credentialId) return false
if (sad != other.sad) return false
if (operationMode != other.operationMode) return false
if (validityPeriod != other.validityPeriod) return false
if (responseUri != other.responseUri) return false
if (clientData != other.clientData) return false
if (hashAlgorithmOid != other.hashAlgorithmOid) return false
if (signAlgoOid != other.signAlgoOid) return false
if (signAlgoParams != other.signAlgoParams) return false
if (signAlgorithm != other.signAlgorithm) return false
if (hashAlgorithm != other.hashAlgorithm) return false

return true
}

override fun hashCode(): Int {
var result = hashes.contentHashCode()
result = 31 * result + (sad?.hashCode() ?: 0)
result = 31 * result + operationMode.hashCode()
result = 31 * result + (validityPeriod ?: 0)
result = 31 * result + (responseUri?.hashCode() ?: 0)
result = 31 * result + (clientData?.hashCode() ?: 0)
result = 31 * result + credentialId.hashCode()
result = 31 * result + (hashAlgorithmOid?.hashCode() ?: 0)
result = 31 * result + signAlgoOid.hashCode()
result = 31 * result + (signAlgoParams?.hashCode() ?: 0)
result = 31 * result + (signAlgorithm?.hashCode() ?: 0)
result = 31 * result + hashAlgorithm.hashCode()
return result
}
}

@Serializable
data class SignDocParameters(

@SerialName("credentialID")
override val credentialId: String? = null,

@SerialName("SAD")
override val sad: String? = null,

@SerialName("operationMode")
override val operationMode: OperationModeEnum = OperationModeEnum.SYNCHRONOUS,

@SerialName("validity_period")
override val validityPeriod: Int? = null,

@SerialName("response_uri")
override val responseUri: String? = null,

@SerialName("clientData")
override val clientData: String? = null,

/**
* Identifier of the signature type to be created, e.g. “eu_eidas_qes” to denote
* a Qualified Electronic Signature according to eIDAS
*/
@SerialName("signatureQualifier")
val signatureQualifier: SignatureQualifierEnum? = null,

@SerialName("documentDigests")
val documentDigests: Collection<CscDocumentDigest>? = null,

@SerialName("documents")
val documents: Collection<Document>? = null,

/**
* This parameter SHALL be set to “true” to request the service to return the
* “validationInfo”. The default value is “false”, i.e. no
* “validationInfo” info is provided.
*/
@SerialName("returnValidationInformation")
val returnValidationInformation: Boolean = false,

) : 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)" }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package at.asitplus.dif.rqes

import at.asitplus.signum.indispensable.io.ByteArrayBase64Serializer
import kotlinx.serialization.Serializable

typealias Hashes = List<@Serializable(ByteArrayBase64Serializer::class) ByteArray>

fun Hashes.contentEquals(other: List<ByteArray>): Boolean {
if (size != other.size) return false
this.forEachIndexed {i, entry -> if (!entry.contentEquals(other[i])) return false }
return true
}

fun Hashes.contentHashCode(): Int = this.sumOf { 31 * it.contentHashCode() }
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package at.asitplus.dif.rqes

import at.asitplus.signum.indispensable.Digest
import at.asitplus.signum.indispensable.SignatureAlgorithm
import at.asitplus.signum.indispensable.X509SignatureAlgorithm
import at.asitplus.signum.indispensable.asn1.Asn1Element
import at.asitplus.signum.indispensable.asn1.ObjectIdentifier
import at.asitplus.signum.indispensable.asn1.encoding.Asn1
import io.github.aakira.napier.Napier


internal fun getSignAlgorithm(signAlgoOid: ObjectIdentifier, signAlgoParams: Asn1Element?): SignatureAlgorithm? =
kotlin.runCatching {
X509SignatureAlgorithm.doDecode(Asn1.Sequence {
+signAlgoOid
+(signAlgoParams ?: Asn1.Null())
}).also {
require(it.digest != Digest.SHA1)
}.algorithm
}.getOrElse {
Napier.w { "Could not resolve $signAlgoOid" }
null
}

@Throws(Exception::class)
fun getHashAlgorithm(hashAlgorithmOid: ObjectIdentifier?, signatureAlgorithm: SignatureAlgorithm? = null) =
hashAlgorithmOid?.let {
Digest.entries.find { digest -> digest.oid == it }
} ?: when(signatureAlgorithm) {
//TODO change as soon as digest is a member of the interface
is SignatureAlgorithm.ECDSA -> signatureAlgorithm.digest
is SignatureAlgorithm.HMAC -> signatureAlgorithm.digest
is SignatureAlgorithm.RSA -> signatureAlgorithm.digest
null -> null
} ?: throw Exception("Unknown hashing algorithm defined with oid $hashAlgorithmOid or signature algorithm $signatureAlgorithm")
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
package at.asitplus.dif.rqes

object RqesConstants {

const val SCOPE = "credential"
nodh marked this conversation as resolved.
Show resolved Hide resolved

const val SIGNATURE_QUALIFIER = "eu_eidas_qes"

}
Loading
Loading