diff --git a/CHANGELOG.md b/CHANGELOG.md
index 75ec14ec8..9eca2c6bf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,7 +12,10 @@ Release NEXT:
- Fix name shadowing of gradle plugins by renaming file `Plugin.kt` -> `VcLibConventions.kt`
- Fix: Add missing iOS exports
- Add switch to disable composite build (useful for publishing)
-
+ - Get rid of arrays in serializable types and use collections instead
+ - Improve interoperability with verifiers and issuers from
+ - `OidcSiopVerifier`: Move `credentialScheme` from constructor to `createAuthnRequest`
+ - `OidcSiopWallet`: Add constructor parameter to fetch JSON Web Key Sets
Release 3.4.0:
- Target Java 17
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 42b9d7781..7e9cc69a3 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -28,3 +28,11 @@ rootProject.name = "vclibrary"
include(":vclib")
include(":vclib-aries")
include(":vclib-openid")
+
+includeBuild("kmp-crypto") {
+ dependencySubstitution {
+ substitute(module("at.asitplus.crypto:datatypes")).using(project(":datatypes"))
+ substitute(module("at.asitplus.crypto:datatypes-jws")).using(project(":datatypes-jws"))
+ substitute(module("at.asitplus.crypto:datatypes-cose")).using(project(":datatypes-cose"))
+ }
+}
diff --git a/vclib-aries/src/commonMain/kotlin/at/asitplus/wallet/lib/aries/PresentProofProtocol.kt b/vclib-aries/src/commonMain/kotlin/at/asitplus/wallet/lib/aries/PresentProofProtocol.kt
index d921ba610..f5ad8acb4 100644
--- a/vclib-aries/src/commonMain/kotlin/at/asitplus/wallet/lib/aries/PresentProofProtocol.kt
+++ b/vclib-aries/src/commonMain/kotlin/at/asitplus/wallet/lib/aries/PresentProofProtocol.kt
@@ -208,17 +208,17 @@ class PresentProofProtocol(
val claimsConstraints = requestedClaims?.map(this::buildConstraintFieldForClaim) ?: listOf()
val typeConstraints = buildConstraintFieldForType(credentialScheme.vcType)
val presentationDefinition = PresentationDefinition(
- inputDescriptors = arrayOf(
+ inputDescriptors = listOf(
InputDescriptor(
name = credentialScheme.vcType,
schema = SchemaReference(uri = credentialScheme.schemaUri),
constraints = Constraint(
- fields = (claimsConstraints + typeConstraints).toTypedArray()
+ fields = claimsConstraints + typeConstraints
)
)
),
formats = FormatHolder(
- jwtVp = FormatContainerJwt(arrayOf(JwsAlgorithm.ES256.identifier))
+ jwtVp = FormatContainerJwt(listOf(JwsAlgorithm.ES256.identifier))
)
)
val requestPresentation = RequestPresentationAttachment(
@@ -245,12 +245,12 @@ class PresentProofProtocol(
}
private fun buildConstraintFieldForType(attributeType: String) = ConstraintField(
- path = arrayOf("\$.vc[*].type", "\$.type"),
+ path = listOf("\$.vc[*].type", "\$.type"),
filter = ConstraintFilter(type = "string", const = attributeType)
)
private fun buildConstraintFieldForClaim(claimName: String) = ConstraintField(
- path = arrayOf("\$.vc[*].name", "\$.type"),
+ path = listOf("\$.vc[*].name", "\$.type"),
filter = ConstraintFilter(type = "string", const = claimName)
)
diff --git a/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/AuthenticationRequestParameters.kt b/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/AuthenticationRequestParameters.kt
index 08f968a42..17b97f857 100644
--- a/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/AuthenticationRequestParameters.kt
+++ b/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/AuthenticationRequestParameters.kt
@@ -1,9 +1,13 @@
package at.asitplus.wallet.lib.oidc
+import at.asitplus.wallet.lib.data.InstantLongSerializer
import at.asitplus.wallet.lib.data.dif.PresentationDefinition
import at.asitplus.wallet.lib.oidvci.AuthorizationDetails
+import io.github.aakira.napier.Napier
+import kotlinx.datetime.Instant
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
+import kotlinx.serialization.encodeToString
/**
* Contents of an OIDC Authentication Request.
@@ -157,18 +161,25 @@ data class AuthenticationRequestParameters(
val clientIdScheme: String? = null,
/**
- * May contain the Wallet's OIDC issuer URL, for discovery.
- * Recommended in Dynamic Credential Request.
+ * OID4VP: OPTIONAL. String containing the Wallet's identifier. The Credential Issuer can use the discovery process
+ * defined in SIOPv2 to determine the Wallet's capabilities and endpoints, using the `wallet_issuer` value as the
+ * Issuer Identifier referred to in SIOPv2. This is RECOMMENDED in Dynamic Credential Requests.
*/
@SerialName("wallet_issuer")
val walletIssuer: String? = null,
/**
- * Recommended in Dynamic Credential Request
+ * OID4VP: OPTIONAL. String containing an opaque End-User hint that the Wallet MAY use in subsequent callbacks to
+ * optimize the End-User's experience. This is RECOMMENDED in Dynamic Credential Requests.
*/
@SerialName("user_hint")
val userHint: String? = null,
+ /**
+ * OID4VP: OPTIONAL. String value identifying a certain processing context at the Credential Issuer. A value for
+ * this parameter is typically passed in a Credential Offer from the Credential Issuer to the Wallet. This request
+ * parameter is used to pass the issuer_state value back to the Credential Issuer.
+ */
@SerialName("issuer_state")
val issuerState: String? = null,
@@ -209,4 +220,23 @@ data class AuthenticationRequestParameters(
*/
@SerialName("iss")
val issuer: String? = null,
-)
+
+ /**
+ * OPTIONAL. Time at which the request was issued.
+ */
+ @SerialName("iat")
+ @Serializable(with = InstantLongSerializer::class)
+ val issuedAt: Instant? = null,
+) {
+
+ fun serialize() = jsonSerializer.encodeToString(this)
+
+ companion object {
+ fun deserialize(it: String) = kotlin.runCatching {
+ jsonSerializer.decodeFromString(it)
+ }.getOrElse {
+ Napier.w("deserialize failed", it)
+ null
+ }
+ }
+}
diff --git a/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/AuthenticationResponseParameters.kt b/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/AuthenticationResponseParameters.kt
index e8deaee99..4e7c1e84e 100644
--- a/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/AuthenticationResponseParameters.kt
+++ b/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/AuthenticationResponseParameters.kt
@@ -1,8 +1,10 @@
package at.asitplus.wallet.lib.oidc
import at.asitplus.wallet.lib.data.dif.PresentationSubmission
+import io.github.aakira.napier.Napier
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
+import kotlinx.serialization.encodeToString
/**
* Contents of an OIDC Authentication Response.
@@ -61,4 +63,16 @@ data class AuthenticationResponseParameters(
*/
@SerialName("state")
val state: String? = null,
-)
\ No newline at end of file
+) {
+
+ fun serialize() = jsonSerializer.encodeToString(this)
+
+ companion object {
+ fun deserialize(it: String) = kotlin.runCatching {
+ jsonSerializer.decodeFromString(it)
+ }.getOrElse {
+ Napier.w("deserialize failed", it)
+ null
+ }
+ }
+}
diff --git a/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/JsonWebKeySet.kt b/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/JsonWebKeySet.kt
new file mode 100644
index 000000000..a8503c281
--- /dev/null
+++ b/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/JsonWebKeySet.kt
@@ -0,0 +1,26 @@
+package at.asitplus.wallet.lib.oidc
+
+import at.asitplus.crypto.datatypes.jws.JsonWebKey
+import io.github.aakira.napier.Napier
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.encodeToString
+
+@Serializable
+data class JsonWebKeySet(
+ @SerialName("keys")
+ val keys: Collection,
+) {
+
+ fun serialize() = jsonSerializer.encodeToString(this)
+
+ companion object {
+ fun deserialize(it: String) = kotlin.runCatching {
+ jsonSerializer.decodeFromString(it)
+ }.getOrElse {
+ Napier.w("deserialize failed", it)
+ null
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopVerifier.kt b/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopVerifier.kt
index 2c2c5ba07..073cf9a54 100644
--- a/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopVerifier.kt
+++ b/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopVerifier.kt
@@ -61,7 +61,6 @@ class OidcSiopVerifier(
private val verifierJwsService: VerifierJwsService,
timeLeewaySeconds: Long = 300L,
private val clock: Clock = Clock.System,
- private val credentialScheme: ConstantIndex.CredentialScheme? = null,
) {
private val timeLeeway = timeLeewaySeconds.toDuration(DurationUnit.SECONDS)
@@ -77,7 +76,6 @@ class OidcSiopVerifier(
jwsService: JwsService = DefaultJwsService(cryptoService),
timeLeewaySeconds: Long = 300L,
clock: Clock = Clock.System,
- credentialScheme: ConstantIndex.CredentialScheme? = null,
) = OidcSiopVerifier(
verifier = verifier,
relyingPartyUrl = relyingPartyUrl,
@@ -86,18 +84,17 @@ class OidcSiopVerifier(
verifierJwsService = verifierJwsService,
timeLeewaySeconds = timeLeewaySeconds,
clock = clock,
- credentialScheme = credentialScheme,
)
}
private val containerJwt =
- FormatContainerJwt(algorithms = verifierJwsService.supportedAlgorithms.map { it.identifier }.toTypedArray())
+ FormatContainerJwt(algorithms = verifierJwsService.supportedAlgorithms.map { it.identifier })
private val metadata by lazy {
RelyingPartyMetadata(
- redirectUris = arrayOf(relyingPartyUrl),
- jsonWebKeySet = JsonWebKeySet(arrayOf(agentPublicKey.toJsonWebKey())),
- subjectSyntaxTypesSupported = arrayOf(URN_TYPE_JWK_THUMBPRINT, PREFIX_DID_KEY),
+ redirectUris = listOf(relyingPartyUrl),
+ jsonWebKeySet = JsonWebKeySet(listOf(agentPublicKey.toJsonWebKey())),
+ subjectSyntaxTypesSupported = listOf(URN_TYPE_JWK_THUMBPRINT, PREFIX_DID_KEY),
vpFormats = FormatHolder(
msoMdoc = containerJwt,
jwtVp = containerJwt,
@@ -139,6 +136,7 @@ class OidcSiopVerifier(
*
* @param responseMode which response mode to request, see [OpenIdConstants.ResponseModes]
* @param representation specifies the required representation, see [ConstantIndex.CredentialRepresentation]
+ * @param credentialScheme which credential type to request, or `null` to make no restrictions
* @param requestedAttributes list of attributes that shall be requested explicitly (selective disclosure)
*/
suspend fun createAuthnRequestUrl(
@@ -146,6 +144,7 @@ class OidcSiopVerifier(
responseMode: String? = null,
representation: ConstantIndex.CredentialRepresentation = ConstantIndex.CredentialRepresentation.PLAIN_JWT,
state: String? = uuid4().toString(),
+ credentialScheme: ConstantIndex.CredentialScheme? = null,
requestedAttributes: List? = null,
): String {
val urlBuilder = URLBuilder(walletUrl)
@@ -153,6 +152,7 @@ class OidcSiopVerifier(
responseMode = responseMode,
representation = representation,
state = state,
+ credentialScheme = credentialScheme,
requestedAttributes = requestedAttributes,
).encodeToParameters()
.forEach { urlBuilder.parameters.append(it.key, it.value) }
@@ -173,6 +173,7 @@ class OidcSiopVerifier(
responseMode: String? = null,
representation: ConstantIndex.CredentialRepresentation = ConstantIndex.CredentialRepresentation.PLAIN_JWT,
state: String? = uuid4().toString(),
+ credentialScheme: ConstantIndex.CredentialScheme? = null,
requestedAttributes: List? = null,
): KmmResult {
val urlBuilder = URLBuilder(walletUrl)
@@ -180,6 +181,7 @@ class OidcSiopVerifier(
responseMode = responseMode,
representation = representation,
state = state,
+ credentialScheme = credentialScheme,
requestedAttributes = requestedAttributes,
).getOrElse {
return KmmResult.failure(it)
@@ -194,18 +196,21 @@ class OidcSiopVerifier(
* @param responseMode which response mode to request, see [OpenIdConstants.ResponseModes]
* @param representation specifies the required representation, see [ConstantIndex.CredentialRepresentation]
* @param state opaque value which will be returned by the OpenId Provider and also in [AuthnResponseResult]
+ * @param credentialScheme which credential type to request, or `null` to make no restrictions
* @param requestedAttributes list of attributes that shall be requested explicitly (selective disclosure)
*/
suspend fun createAuthnRequestAsRequestObject(
responseMode: String? = null,
representation: ConstantIndex.CredentialRepresentation = ConstantIndex.CredentialRepresentation.PLAIN_JWT,
state: String? = uuid4().toString(),
+ credentialScheme: ConstantIndex.CredentialScheme? = null,
requestedAttributes: List? = null,
): KmmResult {
val requestObject = createAuthnRequest(
responseMode = responseMode,
representation = representation,
state = state,
+ credentialScheme = credentialScheme,
requestedAttributes = requestedAttributes,
)
val requestObjectSerialized = jsonSerializer.encodeToString(
@@ -235,12 +240,14 @@ class OidcSiopVerifier(
* @param responseMode which response mode to request, see [OpenIdConstants.ResponseModes]
* @param representation specifies the required representation, see [ConstantIndex.CredentialRepresentation]
* @param state opaque value which will be returned by the OpenId Provider and also in [AuthnResponseResult]
+ * @param credentialScheme which credential type to request, or `null` to make no restrictions
* @param requestedAttributes list of attributes that shall be requested explicitly (selective disclosure)
*/
suspend fun createAuthnRequest(
responseMode: String? = null,
representation: ConstantIndex.CredentialRepresentation = ConstantIndex.CredentialRepresentation.PLAIN_JWT,
state: String? = uuid4().toString(),
+ credentialScheme: ConstantIndex.CredentialScheme? = null,
requestedAttributes: List? = null,
): AuthenticationRequestParameters {
val typeConstraint = credentialScheme?.let {
@@ -250,8 +257,8 @@ class OidcSiopVerifier(
ConstantIndex.CredentialRepresentation.ISO_MDOC -> it.isoConstraint()
}
}
- val attributeConstraint = requestedAttributes?.let { createConstraints(representation, it) } ?: arrayOf()
- val constraintFields = listOfNotNull(typeConstraint, *attributeConstraint).toTypedArray()
+ val attributeConstraint = requestedAttributes?.let { createConstraints(representation, it) } ?: listOf()
+ val constraintFields = attributeConstraint + typeConstraint
return AuthenticationRequestParameters(
responseType = "$ID_TOKEN $VP_TOKEN",
clientId = relyingPartyUrl,
@@ -266,11 +273,11 @@ class OidcSiopVerifier(
presentationDefinition = PresentationDefinition(
id = uuid4().toString(),
formats = representation.toFormatHolder(),
- inputDescriptors = arrayOf(
+ inputDescriptors = listOf(
InputDescriptor(
id = uuid4().toString(),
- schema = arrayOf(SchemaReference(credentialScheme?.schemaUri ?: "https://example.com")),
- constraints = Constraint(fields = constraintFields),
+ schema = listOf(SchemaReference(credentialScheme?.schemaUri ?: "https://example.com")),
+ constraints = Constraint(fields = constraintFields.filterNotNull()),
)
),
),
@@ -284,7 +291,7 @@ class OidcSiopVerifier(
}
private fun ConstantIndex.CredentialScheme.vcConstraint() = ConstraintField(
- path = arrayOf("$.type"),
+ path = listOf("$.type"),
filter = ConstraintFilter(
type = "string",
pattern = vcType,
@@ -292,7 +299,7 @@ class OidcSiopVerifier(
)
private fun ConstantIndex.CredentialScheme.isoConstraint() = ConstraintField(
- path = arrayOf("$.mdoc.doctype"),
+ path = listOf("$.mdoc.doctype"),
filter = ConstraintFilter(
type = "string",
pattern = isoDocType,
@@ -302,12 +309,12 @@ class OidcSiopVerifier(
private fun createConstraints(
credentialRepresentation: ConstantIndex.CredentialRepresentation,
attributeTypes: List,
- ): Array = attributeTypes.map {
+ ): Collection = attributeTypes.map {
if (credentialRepresentation == ConstantIndex.CredentialRepresentation.ISO_MDOC)
- ConstraintField(path = arrayOf("\$.mdoc.$it"), intentToRetain = false)
+ ConstraintField(path = listOf("\$.mdoc.$it"), intentToRetain = false)
else
- ConstraintField(path = arrayOf("\$.$it"))
- }.toTypedArray()
+ ConstraintField(path = listOf("\$.$it"))
+ }
sealed class AuthnResponseResult {
@@ -410,7 +417,7 @@ class OidcSiopVerifier(
val presentationSubmission = params.presentationSubmission
?: return AuthnResponseResult.ValidationError("presentation_submission", params.state)
.also { Napier.w("presentation_submission empty") }
- val descriptor = presentationSubmission.descriptorMap?.get(0)
+ val descriptor = presentationSubmission.descriptorMap?.firstOrNull()
?: return AuthnResponseResult.ValidationError("presentation_submission", params.state)
.also { Napier.w("presentation_submission contains no descriptors") }
val vp = params.vpToken
diff --git a/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWallet.kt b/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWallet.kt
index 4cbf081ce..d18c4e092 100644
--- a/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWallet.kt
+++ b/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWallet.kt
@@ -2,12 +2,12 @@ package at.asitplus.wallet.lib.oidc
import at.asitplus.KmmResult
import at.asitplus.crypto.datatypes.CryptoPublicKey
-import at.asitplus.crypto.datatypes.jws.JwsAlgorithm
import at.asitplus.crypto.datatypes.jws.JwsSigned
import at.asitplus.crypto.datatypes.jws.toJsonWebKey
import at.asitplus.wallet.lib.agent.CryptoService
import at.asitplus.wallet.lib.agent.Holder
import at.asitplus.wallet.lib.data.AttributeIndex
+import at.asitplus.wallet.lib.data.ConstantIndex
import at.asitplus.wallet.lib.data.dif.ClaimFormatEnum
import at.asitplus.wallet.lib.data.dif.PresentationSubmission
import at.asitplus.wallet.lib.data.dif.PresentationSubmissionDescriptor
@@ -53,7 +53,18 @@ class OidcSiopWallet(
private val jwsService: JwsService,
private val verifierJwsService: VerifierJwsService = DefaultVerifierJwsService(),
private val clock: Clock = Clock.System,
- private val clientId: String = "https://wallet.a-sit.at/"
+ private val clientId: String = "https://wallet.a-sit.at/",
+ /**
+ * Need to implement if JSON web keys are not specified directly as `jwks` in authn requests,
+ * but need to be retrieved from the `jwks_uri`. Implementations need to fetch the URL passed and return the
+ * content parsed as [JsonWebKeySet].
+ */
+ private val jwkSetRetriever: (String) -> JsonWebKeySet? = { null },
+ /**
+ * Need to implement if `request_uri` parameters are used, i.e. the actual authn request can be retrieved
+ * from that URL. Implementations need to fetch the URL and return the content.
+ */
+ private val requestRetriever: (String) -> String? = { null },
) {
companion object {
@@ -63,7 +74,9 @@ class OidcSiopWallet(
jwsService: JwsService = DefaultJwsService(cryptoService),
verifierJwsService: VerifierJwsService = DefaultVerifierJwsService(),
clock: Clock = Clock.System,
- clientId: String = "https://wallet.a-sit.at/"
+ clientId: String = "https://wallet.a-sit.at/",
+ jwkSetRetriever: (String) -> JsonWebKeySet? = { null },
+ requestRetriever: (String) -> String? = { null },
) = OidcSiopWallet(
holder = holder,
agentPublicKey = cryptoService.publicKey,
@@ -71,6 +84,8 @@ class OidcSiopWallet(
verifierJwsService = verifierJwsService,
clock = clock,
clientId = clientId,
+ jwkSetRetriever = jwkSetRetriever,
+ requestRetriever = requestRetriever,
)
}
@@ -126,6 +141,11 @@ class OidcSiopWallet(
params.request?.let { requestObject ->
return parseRequestObjectJws(requestObject)
}
+ params.requestUri?.let { uri ->
+ requestRetriever.invoke(uri)?.let { requestObject ->
+ return parseRequestObjectJws(requestObject)
+ }
+ }
return null
}
@@ -133,7 +153,7 @@ class OidcSiopWallet(
JwsSigned.parse(requestObject)?.let { jws ->
if (verifierJwsService.verifyJwsObject(jws)) {
return kotlin.runCatching {
- jsonSerializer.decodeFromString(jws.payload.decodeToString())
+ AuthenticationRequestParameters.deserialize(jws.payload.decodeToString())
}.getOrNull()
}
}
@@ -154,17 +174,17 @@ class OidcSiopWallet(
if (!request.responseType.contains(ID_TOKEN) && !request.responseType.contains(VP_TOKEN)) {
return KmmResult.failure(OAuth2Exception(Errors.INVALID_REQUEST))
}
- if (request.responseMode?.contains(POST) == true) {
+ if (request.responseMode?.startsWith(POST) == true) {
if (request.redirectUrl == null)
return KmmResult.failure(OAuth2Exception(Errors.INVALID_REQUEST))
val body = responseParams.encodeToParameters().formUrlEncode()
- KmmResult.success(AuthenticationResponseResult.Post(request.redirectUrl, body))
- } else if (request.responseMode?.contains(DIRECT_POST) == true) {
+ return KmmResult.success(AuthenticationResponseResult.Post(request.redirectUrl, body))
+ } else if (request.responseMode?.startsWith(DIRECT_POST) == true) {
if (request.responseUrl == null || request.redirectUrl != null)
return KmmResult.failure(OAuth2Exception(Errors.INVALID_REQUEST))
val body = responseParams.encodeToParameters().formUrlEncode()
- KmmResult.success(AuthenticationResponseResult.Post(request.responseUrl, body))
- } else if (request.responseMode?.contains(QUERY) == true) {
+ return KmmResult.success(AuthenticationResponseResult.Post(request.responseUrl, body))
+ } else if (request.responseMode?.startsWith(QUERY) == true) {
if (request.redirectUrl == null)
return KmmResult.failure(OAuth2Exception(Errors.INVALID_REQUEST))
val url = URLBuilder(request.redirectUrl)
@@ -174,7 +194,7 @@ class OidcSiopWallet(
}
}
.buildString()
- KmmResult.success(AuthenticationResponseResult.Redirect(url))
+ return KmmResult.success(AuthenticationResponseResult.Redirect(url))
} else {
// default for vp_token and id_token is fragment
if (request.redirectUrl == null)
@@ -182,7 +202,7 @@ class OidcSiopWallet(
val url = URLBuilder(request.redirectUrl)
.apply { encodedFragment = responseParams.encodeToParameters().formUrlEncode() }
.buildString()
- KmmResult.success(AuthenticationResponseResult.Redirect(url))
+ return KmmResult.success(AuthenticationResponseResult.Redirect(url))
}
}, {
return KmmResult.failure(it)
@@ -195,45 +215,55 @@ class OidcSiopWallet(
suspend fun createAuthnResponseParams(
params: AuthenticationRequestParameters
): KmmResult {
- val audience = params.clientMetadata?.jsonWebKeySet?.keys?.get(0)?.identifier
+ if (params.clientMetadata == null) {
+ return KmmResult.failure(OAuth2Exception(Errors.INVALID_REQUEST))
+ .also { Napier.w("client metadata is not specified") }
+ }
+ val audience = params.clientMetadata.jsonWebKeySet?.keys?.firstOrNull()?.identifier
+ ?: params.clientMetadata.jsonWebKeySetUrl?.let { jwkSetRetriever.invoke(it)?.keys?.firstOrNull()?.identifier }
?: return KmmResult.failure(OAuth2Exception(Errors.INVALID_REQUEST))
.also { Napier.w("Could not parse audience") }
if (URN_TYPE_JWK_THUMBPRINT !in params.clientMetadata.subjectSyntaxTypesSupported)
return KmmResult.failure(OAuth2Exception(Errors.SUBJECT_SYNTAX_TYPES_NOT_SUPPORTED))
.also { Napier.w("Incompatible subject syntax types algorithms") }
- if (params.clientId != params.redirectUrl)
+ if (params.clientIdScheme == OpenIdConstants.ClientIdSchemes.REDIRECT_URI && params.redirectUrl == null) {
return KmmResult.failure(OAuth2Exception(Errors.INVALID_REQUEST))
- .also { Napier.w("client_id does not match redirect_uri") }
- if (params.responseType?.contains(ID_TOKEN) != true)
+ .also { Napier.w("client_id_scheme is redirect_uri, but that is not set") }
+ }
+ if (params.redirectUrl != null) {
+ if (params.clientId != params.redirectUrl)
+ return KmmResult.failure(OAuth2Exception(Errors.INVALID_REQUEST))
+ .also { Napier.w("client_id does not match redirect_uri") }
+ }
+ if (params.responseType == null)
return KmmResult.failure(OAuth2Exception(Errors.INVALID_REQUEST))
- .also { Napier.w("response_type is not \"$ID_TOKEN\"") }
+ .also { Napier.w("response_type is not specified") }
if (!params.responseType.contains(VP_TOKEN) && params.presentationDefinition == null)
return KmmResult.failure(OAuth2Exception(Errors.INVALID_REQUEST))
.also { Napier.w("vp_token not requested") }
- // TODO Client shall send the client_id_scheme, which needs to be supported by the Wallet
- if (params.clientMetadata.vpFormats == null)
- return KmmResult.failure(OAuth2Exception(Errors.REGISTRATION_VALUE_NOT_SUPPORTED))
- .also { Napier.w("Incompatible subject syntax types algorithms") }
- if (params.clientMetadata.vpFormats.jwtVp?.algorithms?.contains(jwsService.algorithm.identifier) != true)
- return KmmResult.failure(OAuth2Exception(Errors.REGISTRATION_VALUE_NOT_SUPPORTED))
- .also { Napier.w("Incompatible JWT algorithms") }
- if (params.clientMetadata.vpFormats.jwtSd?.algorithms?.contains(jwsService.algorithm.identifier) != true)
- return KmmResult.failure(OAuth2Exception(Errors.REGISTRATION_VALUE_NOT_SUPPORTED))
- .also { Napier.w("Incompatible JWT algorithms") }
- if (params.clientMetadata.vpFormats.msoMdoc?.algorithms?.contains(jwsService.algorithm.identifier) != true)
- return KmmResult.failure(OAuth2Exception(Errors.REGISTRATION_VALUE_NOT_SUPPORTED))
- .also { Napier.w("Incompatible JWT algorithms") }
+ if (params.clientMetadata.vpFormats != null) {
+ if (params.clientMetadata.vpFormats.jwtVp?.algorithms?.contains(jwsService.algorithm.identifier) != true)
+ return KmmResult.failure(OAuth2Exception(Errors.REGISTRATION_VALUE_NOT_SUPPORTED))
+ .also { Napier.w("Incompatible JWT algorithms") }
+ if (params.clientMetadata.vpFormats.jwtSd?.algorithms?.contains(jwsService.algorithm.identifier) != true)
+ return KmmResult.failure(OAuth2Exception(Errors.REGISTRATION_VALUE_NOT_SUPPORTED))
+ .also { Napier.w("Incompatible JWT algorithms") }
+ if (params.clientMetadata.vpFormats.msoMdoc?.algorithms?.contains(jwsService.algorithm.identifier) != true)
+ return KmmResult.failure(OAuth2Exception(Errors.REGISTRATION_VALUE_NOT_SUPPORTED))
+ .also { Napier.w("Incompatible JWT algorithms") }
+ }
if (params.nonce == null)
return KmmResult.failure(OAuth2Exception(Errors.INVALID_REQUEST))
.also { Napier.w("nonce is null") }
val now = clock.now()
// we'll assume jwk-thumbprint
+ val agentJsonWebKey = agentPublicKey.toJsonWebKey()
val idToken = IdToken(
- issuer = agentPublicKey.toJsonWebKey().jwkThumbprint,
- subject = agentPublicKey.toJsonWebKey().jwkThumbprint,
- subjectJwk = agentPublicKey.toJsonWebKey(),
- audience = params.redirectUrl,
+ issuer = agentJsonWebKey.jwkThumbprint,
+ subject = agentJsonWebKey.jwkThumbprint,
+ subjectJwk = agentJsonWebKey,
+ audience = params.redirectUrl ?: params.clientId,
issuedAt = now,
expiration = now + 60.seconds,
nonce = params.nonce,
@@ -244,12 +274,28 @@ class OidcSiopWallet(
return KmmResult.failure(OAuth2Exception(Errors.USER_CANCELLED))
}
- val requestedScopes = (params.scope ?: "").split(" ")
+ val requestedAttributeTypes = (params.scope ?: "").split(" ")
.filterNot { it == SCOPE_OPENID }.filterNot { it == SCOPE_PROFILE }
- .mapNotNull { AttributeIndex.resolveAttributeType(it) }
- .toList()
+ .filter { it.isNotEmpty() }
+ val requestedNamespace = params.presentationDefinition?.inputDescriptors
+ ?.mapNotNull { it.constraints }
+ ?.flatMap { it.fields?.toList() ?: listOf() }
+ ?.firstOrNull { it.path.toList().contains("$.mdoc.namespace") }
+ ?.filter?.const
+ val requestedSchemes = mutableListOf()
+ if (requestedNamespace != null) {
+ requestedSchemes.add(AttributeIndex.resolveIsoNamespace(requestedNamespace)
+ ?: return KmmResult.failure(OAuth2Exception(Errors.USER_CANCELLED))
+ .also { Napier.w("Could not resolve requested namespace $requestedNamespace") })
+ requestedAttributeTypes.forEach { requestedAttributeTyp ->
+ requestedSchemes.add(AttributeIndex.resolveAttributeType(requestedAttributeTyp)
+ ?: return KmmResult.failure(OAuth2Exception(Errors.USER_CANCELLED))
+ .also { Napier.w("Could not resolve requested attribute type $it") })
+ }
+ }
val requestedClaims = params.presentationDefinition?.inputDescriptors
- ?.mapNotNull { it.constraints }?.flatMap { it.fields?.toList() ?: listOf() }
+ ?.mapNotNull { it.constraints }
+ ?.flatMap { it.fields?.toList() ?: listOf() }
?.flatMap { it.path.toList() }
?.filter { it != "$.type" }
?.filter { it != "$.mdoc.doctype" }
@@ -259,7 +305,7 @@ class OidcSiopWallet(
val vp = holder.createPresentation(
challenge = params.nonce,
audienceId = audience,
- credentialSchemes = requestedScopes.ifEmpty { null },
+ credentialSchemes = requestedSchemes.toList().ifEmpty { null },
requestedClaims = requestedClaims.ifEmpty { null }
) ?: return KmmResult.failure(OAuth2Exception(Errors.USER_CANCELLED))
.also { Napier.w("Could not create presentation") }
@@ -280,7 +326,7 @@ class OidcSiopWallet(
path = "\$.verifiableCredential[0]"
),
)
- }?.toTypedArray()
+ }
)
return KmmResult.success(
AuthenticationResponseParameters(
@@ -302,7 +348,7 @@ class OidcSiopWallet(
format = ClaimFormatEnum.JWT_SD,
path = "\$",
)
- }?.toTypedArray()
+ }
)
return KmmResult.success(
AuthenticationResponseParameters(
@@ -324,7 +370,7 @@ class OidcSiopWallet(
format = ClaimFormatEnum.MSO_MDOC,
path = "\$",
)
- }?.toTypedArray()
+ }
)
return KmmResult.success(
AuthenticationResponseParameters(
@@ -339,5 +385,4 @@ class OidcSiopWallet(
}
}
-
}
diff --git a/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/RelyingPartyMetadata.kt b/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/RelyingPartyMetadata.kt
index 9c739beee..27c8b2bb3 100644
--- a/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/RelyingPartyMetadata.kt
+++ b/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/RelyingPartyMetadata.kt
@@ -1,6 +1,7 @@
package at.asitplus.wallet.lib.oidc
-import at.asitplus.crypto.datatypes.jws.JsonWebKey
+import at.asitplus.crypto.datatypes.jws.JweAlgorithm
+import at.asitplus.crypto.datatypes.jws.JwsAlgorithm
import at.asitplus.wallet.lib.data.dif.FormatHolder
import io.github.aakira.napier.Napier
import kotlinx.serialization.SerialName
@@ -9,10 +10,92 @@ import kotlinx.serialization.encodeToString
@Serializable
data class RelyingPartyMetadata(
+ /**
+ * OIDC Registration: REQUIRED. Array of Redirection URI values used by the Client. One of these registered
+ * Redirection URI values MUST exactly match the `redirect_uri` parameter value used in each Authorization Request,
+ * with the matching performed as described in Section 6.2.1 of (RFC3986) (Simple String Comparison).
+ */
@SerialName("redirect_uris")
- val redirectUris: Array,
+ val redirectUris: List? = null,
+
+ /**
+ * OIDC Registration: OPTIONAL. Client's JWK Set document, passed by value. The semantics of the `jwks` parameter
+ * are the same as the [jsonWebKeySetUrl] parameter, other than that the JWK Set is passed by value, rather than by
+ * reference. This parameter is intended only to be used by Clients that, for some reason, are unable to use the
+ * [jsonWebKeySetUrl] parameter, for instance, by native applications that might not have a location to host the
+ * contents of the JWK Set. If a Client can use [jsonWebKeySetUrl], it MUST NOT use [jsonWebKeySet]. One significant
+ * downside of [jsonWebKeySet] is that it does not enable key rotation (which [jsonWebKeySetUrl] does, as described
+ * in Section 10 of OpenID Connect Core 1.0). The [jsonWebKeySetUrl] and [jsonWebKeySet] parameters MUST NOT be used
+ * together. The JWK Set MUST NOT contain private or symmetric key values.
+ */
@SerialName("jwks")
- val jsonWebKeySet: JsonWebKeySet,
+ val jsonWebKeySet: JsonWebKeySet? = null,
+
+ /**
+ * OIDC Registration: OPTIONAL. URL for the Client's JWK Set document, which MUST use the https scheme. If the
+ * Client signs requests to the Server, it contains the signing key(s) the Server uses to validate signatures from
+ * the Client. The JWK Set MAY also contain the Client's encryption keys(s), which are used by the Server to encrypt
+ * responses to the Client. When both signing and encryption keys are made available, a use (public key use)
+ * parameter value is REQUIRED for all keys in the referenced JWK Set to indicate each key's intended usage.
+ * Although some algorithms allow the same key to be used for both signatures and encryption, doing so is
+ * NOT RECOMMENDED, as it is less secure. The JWK `x5c` parameter MAY be used to provide X.509 representations of
+ * keys provided. When used, the bare key values MUST still be present and MUST match those in the certificate.
+ * The JWK Set MUST NOT contain private or symmetric key values.
+ */
+ @SerialName("jwks_uri")
+ val jsonWebKeySetUrl: String? = null,
+
+ /**
+ * OIDC Registration: OPTIONAL. JWS alg algorithm REQUIRED for signing the ID Token issued to this Client.
+ * The value none MUST NOT be used as the ID Token alg value unless the Client uses only Response Types that return
+ * no ID Token from the Authorization Endpoint (such as when only using the Authorization Code Flow).
+ * The default, if omitted, is RS256.
+ * The public key for validating the signature is provided by retrieving the JWK Set referenced by the `jwks_uri`
+ * element from OpenID Connect Discovery 1.0.
+ */
+ @SerialName("id_token_signed_response_alg")
+ val idTokenSignedResponseAlg: JwsAlgorithm? = null,
+
+ /**
+ * OID JARM: JWS (RFC7515) `alg` algorithm JWA (RFC7518). REQUIRED for signing authorization responses.
+ * If this is specified, the response will be signed using JWS and the configured algorithm.
+ * The algorithm `none` is not allowed. The default, if omitted, is RS256.
+ */
+ @SerialName("authorization_signed_response_alg")
+ val authorizationSignedResponseAlg: JwsAlgorithm? = null,
+
+ /**
+ * OID JARM: JWE (RFC7516) `alg` algorithm JWA (RFC7518). REQUIRED for encrypting authorization responses.
+ * If both signing and encryption are requested, the response will be signed then encrypted, with the result being
+ * a Nested JWT, as defined in JWT (RFC7519). The default, if omitted, is that no encryption is performed.
+ */
+ @SerialName("authorization_encrypted_response_alg")
+ val authorizationEncryptedResponseAlg: JweAlgorithm? = null,
+
+ /**
+ * OID JARM: JWE (RFC7516) `enc` algorithm JWA (RFC7518). REQUIRED for encrypting authorization responses.
+ * If [authorizationEncryptedResponseAlg] is specified, the default for this value is A128CBC-HS256.
+ * When [authorizationEncryptedResponseEncoding] is included, [authorizationEncryptedResponseAlg] MUST also be
+ * provided.
+ */
+ @SerialName("authorization_encrypted_response_enc")
+ val authorizationEncryptedResponseEncoding: String? = null,
+
+ /**
+ * OIDC Registration: OPTIONAL. JWE alg algorithm REQUIRED for encrypting the ID Token issued to this Client.
+ * If this is requested, the response will be signed then encrypted, with the result being a Nested JWT.
+ * The default, if omitted, is that no encryption is performed.
+ */
+ @SerialName("id_token_encrypted_response_alg")
+ val idTokenEncryptedResponseAlg: JweAlgorithm? = null,
+
+ /**
+ * OIDC Registration: OPTIONAL. JWE enc algorithm REQUIRED for encrypting the ID Token issued to this Client.
+ * If [idTokenEncryptedResponseAlg] is specified, the default value is A128CBC-HS256.
+ * When [idTokenEncryptedResponseEncoding] is included, [idTokenEncryptedResponseAlg] MUST also be provided.
+ */
+ @SerialName("id_token_encrypted_response_enc")
+ val idTokenEncryptedResponseEncoding: String? = null,
/**
* OIDC SIOPv2: REQUIRED. A JSON array of strings representing URI scheme identifiers and optionally method names of
@@ -20,7 +103,7 @@ data class RelyingPartyMetadata(
* Valid values include `urn:ietf:params:oauth:jwk-thumbprint`, `did:example` and others.
*/
@SerialName("subject_syntax_types_supported")
- val subjectSyntaxTypesSupported: Array,
+ val subjectSyntaxTypesSupported: List,
/**
* OID4VP: REQUIRED. An object defining the formats and proof types of Verifiable Presentations and Verifiable
@@ -37,34 +120,10 @@ data class RelyingPartyMetadata(
*/
@SerialName("client_id_scheme")
val clientIdScheme: String? = "pre-registered",
-
) {
fun serialize() = jsonSerializer.encodeToString(this)
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other == null || this::class != other::class) return false
-
- other as RelyingPartyMetadata
-
- if (!redirectUris.contentEquals(other.redirectUris)) return false
- if (jsonWebKeySet != other.jsonWebKeySet) return false
- if (!subjectSyntaxTypesSupported.contentEquals(other.subjectSyntaxTypesSupported)) return false
- if (vpFormats != other.vpFormats) return false
- return clientIdScheme == other.clientIdScheme
- }
-
- override fun hashCode(): Int {
- var result = redirectUris.contentHashCode()
- result = 31 * result + jsonWebKeySet.hashCode()
- result = 31 * result + subjectSyntaxTypesSupported.contentHashCode()
- result = 31 * result + (vpFormats?.hashCode() ?: 0)
- result = 31 * result + (clientIdScheme?.hashCode() ?: 0)
- return result
- }
-
-
companion object {
fun deserialize(it: String) = kotlin.runCatching {
jsonSerializer.decodeFromString(it)
@@ -73,40 +132,4 @@ data class RelyingPartyMetadata(
null
}
}
-
-}
-
-
-@Serializable
-data class JsonWebKeySet(
- @SerialName("keys")
- val keys: Array,
-) {
-
- fun serialize() = jsonSerializer.encodeToString(this)
-
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other == null || this::class != other::class) return false
-
- other as JsonWebKeySet
-
- if (!keys.contentEquals(other.keys)) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- return keys.contentHashCode()
- }
-
- companion object {
- fun deserialize(it: String) = kotlin.runCatching {
- jsonSerializer.decodeFromString(it)
- }.getOrElse {
- Napier.w("deserialize failed", it)
- null
- }
- }
-
}
diff --git a/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidvci/mdl/ClaimDisplayProperties.kt b/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidvci/mdl/ClaimDisplayProperties.kt
index 715090ce2..a766db37e 100644
--- a/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidvci/mdl/ClaimDisplayProperties.kt
+++ b/vclib-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidvci/mdl/ClaimDisplayProperties.kt
@@ -6,15 +6,14 @@ import kotlinx.serialization.Serializable
@Serializable
data class ClaimDisplayProperties(
/**
- * OID4VCI:
- * OPTIONAL. String value of a display name for the claim.
+ * OID4VCI: OPTIONAL. String value of a display name for the claim.
*/
@SerialName("name")
val name: String? = null,
/**
- * OID4VCI:
- * OPTIONAL. String value that identifies language of this object represented as language tag values defined in BCP47 [RFC5646].
+ * OID4VCI: OPTIONAL. String value that identifies language of this object represented as language tag values
+ * defined in BCP47 (RFC5646).
*/
@SerialName("locale")
val locale: String? = null,
diff --git a/vclib-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/DummyCredentialDataProvider.kt b/vclib-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/DummyCredentialDataProvider.kt
index c9715bcab..fea590b56 100644
--- a/vclib-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/DummyCredentialDataProvider.kt
+++ b/vclib-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/DummyCredentialDataProvider.kt
@@ -2,7 +2,6 @@ package at.asitplus.wallet.lib.oidc
import at.asitplus.KmmResult
import at.asitplus.crypto.datatypes.CryptoPublicKey
-import at.asitplus.crypto.datatypes.jws.toJsonWebKey
import at.asitplus.wallet.lib.agent.ClaimToBeIssued
import at.asitplus.wallet.lib.agent.CredentialToBeIssued
import at.asitplus.wallet.lib.agent.Issuer
@@ -106,6 +105,34 @@ class DummyCredentialDataProvider(
)
)
}
+
+ if (credentialScheme == EudiwPidCredentialScheme) {
+ val subjectId = subjectPublicKey.didEncoded
+ val claims = listOfNotNull(
+ optionalClaim(claimNames, "family_name", "someone"),
+ )
+ credentials += when (representation) {
+ ConstantIndex.CredentialRepresentation.SD_JWT -> listOf(
+ CredentialToBeIssued.VcSd(claims = claims, expiration = expiration)
+ )
+
+ ConstantIndex.CredentialRepresentation.PLAIN_JWT -> listOf(
+ CredentialToBeIssued.VcJwt(
+ subject = EudiwPid1(subjectId, "someone"),
+ expiration = expiration,
+ )
+ )
+
+ ConstantIndex.CredentialRepresentation.ISO_MDOC -> listOf(
+ CredentialToBeIssued.Iso(
+ issuerSignedItems = claims.mapIndexed { index, claim ->
+ issuerSignedItem(claim.name, claim.value, index.toUInt())
+ },
+ expiration = expiration,
+ )
+ )
+ }
+ }
return KmmResult.success(credentials)
}
diff --git a/vclib-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/EqualityTests.kt b/vclib-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/EqualityTests.kt
new file mode 100644
index 000000000..b330e3eff
--- /dev/null
+++ b/vclib-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/EqualityTests.kt
@@ -0,0 +1,73 @@
+package at.asitplus.wallet.lib.oidc
+
+import at.asitplus.crypto.datatypes.jws.JsonWebKey
+import io.kotest.core.spec.style.FreeSpec
+import io.kotest.matchers.booleans.shouldBeTrue
+import kotlinx.serialization.SerialName
+import kotlin.random.Random
+
+class EqualityTests : FreeSpec({
+
+ lateinit var jwk1: JsonWebKey
+ lateinit var jwk2: JsonWebKey
+
+ beforeEach {
+ jwk1 = JsonWebKey(x = Random.Default.nextBytes(32))
+ jwk2 = JsonWebKey(x = Random.Default.nextBytes(32))
+ }
+
+ "JsonWebKeySet new" {
+ val first = JsonWebKeySet(keys = listOf(jwk1, jwk2))
+ val second = JsonWebKeySet(keys = listOf(jwk1, jwk2))
+
+ val equals = first == second
+
+ equals.shouldBeTrue()
+ }
+
+ "JsonWebKeySet new unordered" {
+ val first = JsonWebKeySet(keys = setOf(jwk1, jwk2))
+ val second = JsonWebKeySet(keys = setOf(jwk2, jwk1))
+
+ val equals = first == second
+
+ equals.shouldBeTrue()
+ }
+
+ "JsonWebKeySet old" {
+ val first = OldJsonWebKeySet(keys = arrayOf(jwk1, jwk2))
+ val second = OldJsonWebKeySet(keys = arrayOf(jwk1, jwk2))
+
+ val equals = first == second
+
+ equals.shouldBeTrue()
+ }
+
+ "JsonWebKeySet old unordered" {
+ val first = OldJsonWebKeySet(keys = arrayOf(jwk1, jwk2))
+ val second = OldJsonWebKeySet(keys = arrayOf(jwk1, jwk2).reversedArray())
+
+ val equals = first == second
+
+ // this is false, because the order matters on arrays
+ //equals.shouldBeTrue()
+ }
+})
+
+data class OldJsonWebKeySet(
+ @SerialName("keys")
+ val keys: Array,
+) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null || this::class != other::class) return false
+
+ other as OldJsonWebKeySet
+
+ return keys.contentEquals(other.keys)
+ }
+
+ override fun hashCode(): Int {
+ return keys.contentHashCode()
+ }
+}
\ No newline at end of file
diff --git a/vclib-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/EudiwPid1.kt b/vclib-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/EudiwPid1.kt
new file mode 100644
index 000000000..1b4e501b2
--- /dev/null
+++ b/vclib-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/EudiwPid1.kt
@@ -0,0 +1,14 @@
+package at.asitplus.wallet.lib.oidc
+
+import at.asitplus.wallet.lib.data.CredentialSubject
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+@SerialName("EudiwPid1")
+data class EudiwPid1(
+ override val id: String,
+
+ @SerialName("family_name")
+ val familyName: String,
+) : CredentialSubject()
\ No newline at end of file
diff --git a/vclib-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/EudiwPidCredentialScheme.kt b/vclib-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/EudiwPidCredentialScheme.kt
new file mode 100644
index 000000000..85f7f29b4
--- /dev/null
+++ b/vclib-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/EudiwPidCredentialScheme.kt
@@ -0,0 +1,11 @@
+package at.asitplus.wallet.lib.oidc
+
+import at.asitplus.wallet.lib.data.ConstantIndex
+
+object EudiwPidCredentialScheme : ConstantIndex.CredentialScheme {
+ override val schemaUri: String = "https://wallet.a-sit.at/schemas/1.0.0/EudiwPid1.json"
+ override val vcType: String = "EudiwPid1"
+ override val isoNamespace: String = "eu.europa.ec.eudiw.pid.1"
+ override val isoDocType: String = "eu.europa.ec.eudiw.pid.1"
+ override val claimNames: Collection = listOf("family_name")
+}
\ No newline at end of file
diff --git a/vclib-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopInteropTest.kt b/vclib-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopInteropTest.kt
new file mode 100644
index 000000000..0dddfc7c9
--- /dev/null
+++ b/vclib-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopInteropTest.kt
@@ -0,0 +1,300 @@
+package at.asitplus.wallet.lib.oidc
+
+import at.asitplus.crypto.datatypes.jws.JweAlgorithm
+import at.asitplus.crypto.datatypes.jws.JwsAlgorithm
+import at.asitplus.wallet.lib.LibraryInitializer
+import at.asitplus.wallet.lib.agent.CryptoService
+import at.asitplus.wallet.lib.agent.DefaultCryptoService
+import at.asitplus.wallet.lib.agent.Holder
+import at.asitplus.wallet.lib.agent.HolderAgent
+import at.asitplus.wallet.lib.agent.IssuerAgent
+import at.asitplus.wallet.lib.data.ConstantIndex
+import at.asitplus.wallet.lib.data.CredentialSubject
+import at.asitplus.wallet.lib.oidvci.decodeFromPostBody
+import io.kotest.core.spec.style.FreeSpec
+import io.kotest.matchers.collections.shouldBeSingleton
+import io.kotest.matchers.collections.shouldHaveSingleElement
+import io.kotest.matchers.nulls.shouldNotBeNull
+import io.kotest.matchers.shouldBe
+import io.kotest.matchers.types.shouldBeInstanceOf
+import kotlinx.coroutines.runBlocking
+import kotlinx.datetime.Instant
+import kotlinx.serialization.modules.SerializersModule
+import kotlinx.serialization.modules.polymorphic
+import kotlinx.serialization.modules.subclass
+
+/**
+ * Tests our SIOP implementation against EUDI Ref Impl.,
+ * see [https://verifier.eudiw.dev/cbor-selectable/verifiable](https://verifier.eudiw.dev/cbor-selectable/verifiable)
+ */
+class OidcSiopInteropTest : FreeSpec({
+
+ lateinit var holderCryptoService: CryptoService
+ lateinit var holderAgent: Holder
+ lateinit var holderSiop: OidcSiopWallet
+
+ beforeSpec {
+ LibraryInitializer.registerExtensionLibrary(
+ LibraryInitializer.ExtensionLibraryInfo(
+ credentialScheme = EudiwPidCredentialScheme,
+ serializersModule = SerializersModule {
+ polymorphic(CredentialSubject::class) {
+ subclass(EudiwPid1::class)
+ }
+ },
+ )
+ )
+ }
+
+ beforeEach {
+ holderCryptoService = DefaultCryptoService()
+ holderAgent = HolderAgent.newDefaultInstance(holderCryptoService)
+ runBlocking {
+ holderAgent.storeCredentials(
+ IssuerAgent.newDefaultInstance(
+ DefaultCryptoService(),
+ dataProvider = DummyCredentialDataProvider(),
+ ).issueCredential(
+ subjectPublicKey = holderCryptoService.publicKey,
+ attributeTypes = listOf(EudiwPidCredentialScheme.vcType),
+ representation = ConstantIndex.CredentialRepresentation.ISO_MDOC,
+ ).toStoreCredentialInput()
+ )
+ }
+ }
+
+ "EUDI from URL" {
+ val url = """
+ eudi-openid4vp://verifier-backend.eudiw.dev?client_id=verifier-backend.eudiw.dev&request_uri=https%3A%2F%2F
+ verifier-backend.eudiw.dev%2Fwallet%2Frequest.jwt%2FWLFJEn9AGbJfAcEyaQTzzxueqmeRazmsHIkxMRTkGRL1zyI7un
+ -KJWaXtulrfiSS38LlU5ABDB9Zdsfq_11r8Q
+ """.trimIndent().replace("\n", "")
+
+ val requestObject = """
+ eyJ4NWMiOlsiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVUkxha05EUVhKRFowRjNTVUpCWjBsVlpuazVkVFpU
+ VEhSblRuVm1PVkJZV1dKb0wxRkVjWFZZZWpVd2QwTm5XVWxMYjFwSmVtb3dSVUYzU1hkWVJFVmxUVUozUjBFeFZVVkJkM2RXVlVWc1JVbEZi
+ SHBqTTFac1kybENSRkZUUVhSSlJsWlZTVVJCZUUxVE1IZExkMWxFVmxGUlMwUkRVa1pXVlZKS1NVWmthR0pIZUd4a1EwSlRXbGRhYkdOdFZu
+ VlpNbFZuVTFjeGQySkhWblJhVnpVd1dWaFNjR0l5TkhoRGVrRktRbWRPVmtKQldWUkJiRlpWVFVJMFdFUlVTVEJOUkVsNVRtcEJlVTE2V1hw
+ Tk1XOVlSRlJKTWsxRVNYbE9WRUY1VFhwWmVrMXNiM2RoVkVWa1RVSnpSMEV4VlVWQmQzZFZVbFpXUlZOVFFsTmFWekYyWkVkVloxWnRWbmxo
+ VjFwd1dsaEplRVJFUVV0Q1owNVdRa0ZWVkVGNlFYZE5WRVYwVFVOelIwRXhWVVZEWjNkclVsWldSVk5UUWxoWlYzaHpXbGhSWjFWdFZtMWFX
+ RXBzWW0xT2JFbEZiSFJqUjNoc1lsZFdkV1JIUmpCaFZ6bDFUVkZ6ZDBOUldVUldVVkZIUlhkS1ZsWkVRbHBOUWsxSFFubHhSMU5OTkRsQlow
+ VkhRME54UjFOTk5EbEJkMFZJUVRCSlFVSk5ZbGRDUVVNeFIyb3JSMFJQTDNsRFUySm5Za1ozY0dsMlVGbFhUSHBGZGtsTVRuUmtRM1kzVkhn
+ eFJYTjRVRU40UW5BelJGcENORVpKY2pSQ2JHMVdXWFJIWVZWaWIxWkphV2hTUW1sUlJHOHpUWEJYYVdwblowWkNUVWxKUWxCVVFVMUNaMDVX
+ U0ZKTlFrRm1PRVZCYWtGQlRVSTRSMEV4VldSSmQxRlpUVUpoUVVaTVRuTjFTa1ZZU0U1bGEwZHRXWGhvTUV4b2FUaENRWHBLVldKTlExVkhR
+ VEZWWkVWUlVXVk5RbmxEUjI1YWJHTnRiRzFoVjFaNVRGZEthRmt5ZEd4aWJWRjFXbGhXYTJGWVkzVmFSMVl5VFVKSlIwRXhWV1JLVVZGTVRV
+ RnJSMEo1YVVKcVJqQkdRVkZaZDFGM1dVUldVakJtUWtSM2QwOXFRVFJ2UkdGblRrbFplV0ZJVWpCalNFMDJUSGs1ZDJOdFZuZGpiVGxyVEc1
+ Q2NtRlROV3hrVjFKd1pIazFhMXBZV1haWk0wcHpURE5DY0ZwR09VUlJWamxXVmtZNGQwMVROV3BqYlhkM1NGRlpSRlpTTUU5Q1FsbEZSa1pu
+ YlVGbmRVSlRkbE51YlRZNFducHZOVWxUZEVsMk1tWk5NazFCTkVkQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQ1pFSm5UbFpJVWtsRlZtcENW
+ V2hzU205a1NGSjNZM3B2ZGt3eVpIQmtSMmd4V1drMWFtSXlNSFphV0ZWMFdrZHNibUZZVW1oaVF6RndXa2RXZFdSSGJEQmxVekV6V1ZkNGMx
+ cFlVWFpaV0VwcVlVZHNNRnBYVGpCa1dFcHNURmRHZFZwRE1YbGFWMXBzWTIxV2RWa3lWWFJhYmtwb1lsZFdNMkl6U25KTlFXOUhRME54UjFO
+ Tk5EbENRVTFEUVRKblFVMUhWVU5OVVVSSFptZE1TMjVpUzJocFQxWkdNM2hUVlRCaFpXcDFMMjVsUjFGVlZuVk9Zbk5SZHpCTVpVUkVkMGxY
+ SzNKTVlYUmxZbEpuYnpsb1RWaEVZek4zY214VlEwMUJTVnA1U2pkc1VsSldaWGxOY2pOM2FuRnJRa1l5YkRsWllqQjNUMUZ3YzI1YVFrRldW
+ VUZRZVVrMWVHaFhXREpUUVdGNmIyMHlTbXB6VGk5aFMwRnJVVDA5Q2kwdExTMHRSVTVFSUVORlVsUkpSa2xEUVZSRkxTMHRMUzA9IiwiTFMw
+ dExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVUklWRU5EUVhGUFowRjNTVUpCWjBsVlZuRnFaM1JLY1dZMGFGVlpTbXR4
+ WkZsNmFTc3dlSGRvZDBaWmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhkWVJFVmxUVUozUjBFeFZVVkJkM2RXVlVWc1JVbEZiSHBqTTFac1kybENS
+ RkZUUVhSSlJsWlZTVVJCZUUxVE1IZExkMWxFVmxGUlMwUkRVa1pXVlZKS1NVWmthR0pIZUd4a1EwSlRXbGRhYkdOdFZuVlpNbFZuVTFjeGQy
+ SkhWblJhVnpVd1dWaFNjR0l5TkhoRGVrRktRbWRPVmtKQldWUkJiRlpWVFVJMFdFUlVTWHBOUkd0M1RWUkZORTE2VVhoT01XOVlSRlJOZVUx
+ VVJYbE9la1UwVFhwUmVFNXNiM2RZUkVWbFRVSjNSMEV4VlVWQmQzZFdWVVZzUlVsRmJIcGpNMVpzWTJsQ1JGRlRRWFJKUmxaVlNVUkJlRTFU
+ TUhkTGQxbEVWbEZSUzBSRFVrWldWVkpLU1Vaa2FHSkhlR3hrUTBKVFdsZGFiR050Vm5WWk1sVm5VMWN4ZDJKSFZuUmFWelV3V1ZoU2NHSXlO
+ SGhEZWtGS1FtZE9Wa0pCV1ZSQmJGWlZUVWhaZDBWQldVaExiMXBKZW1vd1EwRlJXVVpMTkVWRlFVTkpSRmxuUVVWR1p6VlRhR1p6ZUhBMVVp
+ OVZSa2xGUzFNelRESTNaSGR1Um1odWFsTm5WV2d5WW5STFQxRkZibVppTTJSdmVXVnhUVUYyUW5SVlRXeERiR2h6UmpOMVpXWkxhVzVEZHpB
+ NFRrSXpNWEozUXl0a2RHbzJXQzlNUlROdU1rTTVhbEpQU1ZWT09GQnlibXhNVXpWUmN6UlNjelJhVlRWUFNXZDZkRzloVHpoSE9XODBTVUpL
+ UkVORFFWTkJkMFZuV1VSV1VqQlVRVkZJTDBKQlozZENaMFZDTDNkSlFrRkVRV1pDWjA1V1NGTk5SVWRFUVZkblFsTjZZa3hwVWtaNGVsaHdR
+ bkJ0VFZsa1F6Ulpka0ZSVFhsV1IzcEJWMEpuVGxaSVUxVkNRV1k0UlVSRVFVdENaMmR5WjFGSlEwRkJRVUpDZWtKRVFtZE9Wa2hTT0VWUVJF
+ RTJUVVJwWjA1eFFUQm9ha3B2WkVoU2QyTjZiM1pNTTBKNVdsaENlV0l5VVhWalIzUndURzFXTVZwSGJETk1iVkpzWkdrNWFtTnRkM1pqUjJ4
+ cldEQk9RbGd4VmxWWWVrRjRURzFPZVdKRVFXUkNaMDVXU0ZFMFJVWm5VVlZ6TW5rMGExSmpZekUyVVdGYWFrZElVWFZIVEhkRlJFMXNVbk4z
+ UkdkWlJGWlNNRkJCVVVndlFrRlJSRUZuUlVkTlJqQkhRVEZWWkVWblVsZE5SbE5IVlcxb01HUklRbnBQYVRoMldqSnNNR0ZJVm1sTWJVNTJZ
+ bE01YkdSVE1XdGhWMlJ3WkVkR2MweFhiR3RhVnpVd1lWaFNOVXhZWkdoaVIzaHNaRU01YUdOdFRtOWhXRkpzV1ROU01XTnRWWFJaVnpWclRG
+ aEtiRnB0Vm5sYVZ6VnFXbE14YldOdFJuUmFXR1IyWTIxemQwTm5XVWxMYjFwSmVtb3dSVUYzVFVSaFFVRjNXbEZKZDJGWVZVRXphaXNyZUd3
+ dmRHUkVOelowV0VWWFEybHJaazB4UTJGU2VqUjJla0pETjA1VE1IZERaRWwwUzJsNk5raGFaVlk0UlZCMFRrTnVjMlpMY0U1QmFrVkJjWEpr
+ WlV0RWJuSTFTM2RtT0VKQk4zUkJWR1ZvZUU1c1QxWTBTRzVqTVRCWVR6RllWVXgwYVdkRGQySTBPVkp3YTNGc1V6SklkV3dyUkhCeFQySlZj
+ d290TFMwdExVVk9SQ0JEUlZKVVNVWkpRMEZVUlMwdExTMHQiXSwidHlwIjoib2F1dGgtYXV0aHotcmVxK2p3dCIsImFsZyI6IkVTMjU2In0.
+ eyJyZXNwb25zZV91cmkiOiJodHRwczovL3ZlcmlmaWVyLWJhY2tlbmQuZXVkaXcuZGV2L3dhbGxldC9kaXJlY3RfcG9zdCIsImNsaWVudF9p
+ ZF9zY2hlbWUiOiJ4NTA5X3Nhbl9kbnMiLCJyZXNwb25zZV90eXBlIjoidnBfdG9rZW4iLCJub25jZSI6Im5vbmNlIiwiY2xpZW50X2lkIjoi
+ dmVyaWZpZXItYmFja2VuZC5ldWRpdy5kZXYiLCJyZXNwb25zZV9tb2RlIjoiZGlyZWN0X3Bvc3Quand0IiwiYXVkIjoiaHR0cHM6Ly9zZWxm
+ LWlzc3VlZC5tZS92MiIsInNjb3BlIjoiIiwicHJlc2VudGF0aW9uX2RlZmluaXRpb24iOnsiaWQiOiIzMmY1NDE2My03MTY2LTQ4ZjEtOTNk
+ OC1mZjIxN2JkYjA2NTMiLCJpbnB1dF9kZXNjcmlwdG9ycyI6W3siaWQiOiJldWRpX3BpZCIsIm5hbWUiOiJFVURJIFBJRCIsInB1cnBvc2Ui
+ OiJXZSBuZWVkIHRvIHZlcmlmeSB5b3VyIGlkZW50aXR5IiwiY29uc3RyYWludHMiOnsiZmllbGRzIjpbeyJwYXRoIjpbIiQubWRvYy5kb2N0
+ eXBlIl0sImZpbHRlciI6eyJ0eXBlIjoic3RyaW5nIiwiY29uc3QiOiJldS5ldXJvcGEuZWMuZXVkaXcucGlkLjEifX0seyJwYXRoIjpbIiQu
+ bWRvYy5uYW1lc3BhY2UiXSwiZmlsdGVyIjp7InR5cGUiOiJzdHJpbmciLCJjb25zdCI6ImV1LmV1cm9wYS5lYy5ldWRpdy5waWQuMSJ9fSx7
+ InBhdGgiOlsiJC5tZG9jLmZhbWlseV9uYW1lIl0sImludGVudF90b19yZXRhaW4iOmZhbHNlfSx7InBhdGgiOlsiJC5tZG9jLmdpdmVuX25h
+ bWUiXSwiaW50ZW50X3RvX3JldGFpbiI6ZmFsc2V9LHsicGF0aCI6WyIkLm1kb2MuYmlydGhfZGF0ZSJdLCJpbnRlbnRfdG9fcmV0YWluIjpm
+ YWxzZX0seyJwYXRoIjpbIiQubWRvYy5hZ2Vfb3Zlcl8xOCJdLCJpbnRlbnRfdG9fcmV0YWluIjpmYWxzZX0seyJwYXRoIjpbIiQubWRvYy5h
+ Z2VfaW5feWVhcnMiXSwiaW50ZW50X3RvX3JldGFpbiI6ZmFsc2V9LHsicGF0aCI6WyIkLm1kb2MuYWdlX2JpcnRoX3llYXIiXSwiaW50ZW50
+ X3RvX3JldGFpbiI6ZmFsc2V9LHsicGF0aCI6WyIkLm1kb2MuZmFtaWx5X25hbWVfYmlydGgiXSwiaW50ZW50X3RvX3JldGFpbiI6ZmFsc2V9
+ LHsicGF0aCI6WyIkLm1kb2MuZ2l2ZW5fbmFtZV9iaXJ0aCJdLCJpbnRlbnRfdG9fcmV0YWluIjpmYWxzZX0seyJwYXRoIjpbIiQubWRvYy5i
+ aXJ0aF9wbGFjZSJdLCJpbnRlbnRfdG9fcmV0YWluIjpmYWxzZX0seyJwYXRoIjpbIiQubWRvYy5iaXJ0aF9jb3VudHJ5Il0sImludGVudF90
+ b19yZXRhaW4iOmZhbHNlfSx7InBhdGgiOlsiJC5tZG9jLmJpcnRoX3N0YXRlIl0sImludGVudF90b19yZXRhaW4iOmZhbHNlfSx7InBhdGgi
+ OlsiJC5tZG9jLmJpcnRoX2NpdHkiXSwiaW50ZW50X3RvX3JldGFpbiI6ZmFsc2V9LHsicGF0aCI6WyIkLm1kb2MucmVzaWRlbnRfYWRkcmVz
+ cyJdLCJpbnRlbnRfdG9fcmV0YWluIjpmYWxzZX0seyJwYXRoIjpbIiQubWRvYy5yZXNpZGVudF9jb3VudHJ5Il0sImludGVudF90b19yZXRh
+ aW4iOmZhbHNlfSx7InBhdGgiOlsiJC5tZG9jLnJlc2lkZW50X3N0YXRlIl0sImludGVudF90b19yZXRhaW4iOmZhbHNlfSx7InBhdGgiOlsi
+ JC5tZG9jLnJlc2lkZW50X2NpdHkiXSwiaW50ZW50X3RvX3JldGFpbiI6ZmFsc2V9LHsicGF0aCI6WyIkLm1kb2MucmVzaWRlbnRfcG9zdGFs
+ X2NvZGUiXSwiaW50ZW50X3RvX3JldGFpbiI6ZmFsc2V9LHsicGF0aCI6WyIkLm1kb2MucmVzaWRlbnRfc3RyZWV0Il0sImludGVudF90b19y
+ ZXRhaW4iOmZhbHNlfSx7InBhdGgiOlsiJC5tZG9jLnJlc2lkZW50X2hvdXNlX251bWJlciJdLCJpbnRlbnRfdG9fcmV0YWluIjpmYWxzZX0s
+ eyJwYXRoIjpbIiQubWRvYy5nZW5kZXIiXSwiaW50ZW50X3RvX3JldGFpbiI6ZmFsc2V9LHsicGF0aCI6WyIkLm1kb2MubmF0aW9uYWxpdHki
+ XSwiaW50ZW50X3RvX3JldGFpbiI6ZmFsc2V9LHsicGF0aCI6WyIkLm1kb2MuaXNzdWFuY2VfZGF0ZSJdLCJpbnRlbnRfdG9fcmV0YWluIjpm
+ YWxzZX0seyJwYXRoIjpbIiQubWRvYy5leHBpcnlfZGF0ZSJdLCJpbnRlbnRfdG9fcmV0YWluIjpmYWxzZX0seyJwYXRoIjpbIiQubWRvYy5p
+ c3N1aW5nX2F1dGhvcml0eSJdLCJpbnRlbnRfdG9fcmV0YWluIjpmYWxzZX0seyJwYXRoIjpbIiQubWRvYy5kb2N1bWVudF9udW1iZXIiXSwi
+ aW50ZW50X3RvX3JldGFpbiI6ZmFsc2V9LHsicGF0aCI6WyIkLm1kb2MuYWRtaW5pc3RyYXRpdmVfbnVtYmVyIl0sImludGVudF90b19yZXRh
+ aW4iOmZhbHNlfSx7InBhdGgiOlsiJC5tZG9jLmlzc3VpbmdfY291bnRyeSJdLCJpbnRlbnRfdG9fcmV0YWluIjpmYWxzZX0seyJwYXRoIjpb
+ IiQubWRvYy5pc3N1aW5nX2p1cmlzZGljdGlvbiJdLCJpbnRlbnRfdG9fcmV0YWluIjpmYWxzZX1dfX1dfSwic3RhdGUiOiJXTEZKRW45QUdi
+ SmZBY0V5YVFUenp4dWVxbWVSYXptc0hJa3hNUlRrR1JMMXp5STd1bi1LSldhWHR1bHJmaVNTMzhMbFU1QUJEQjlaZHNmcV8xMXI4USIsImlh
+ dCI6MTcxMDc2NjI5NCwiY2xpZW50X21ldGFkYXRhIjp7ImF1dGhvcml6YXRpb25fZW5jcnlwdGVkX3Jlc3BvbnNlX2FsZyI6IkVDREgtRVMi
+ LCJhdXRob3JpemF0aW9uX2VuY3J5cHRlZF9yZXNwb25zZV9lbmMiOiJBMTI4Q0JDLUhTMjU2IiwiaWRfdG9rZW5fZW5jcnlwdGVkX3Jlc3Bv
+ bnNlX2FsZyI6IlJTQS1PQUVQLTI1NiIsImlkX3Rva2VuX2VuY3J5cHRlZF9yZXNwb25zZV9lbmMiOiJBMTI4Q0JDLUhTMjU2Iiwiandrc191
+ cmkiOiJodHRwczovL3ZlcmlmaWVyLWJhY2tlbmQuZXVkaXcuZGV2L3dhbGxldC9qYXJtL1dMRkpFbjlBR2JKZkFjRXlhUVR6enh1ZXFtZVJh
+ em1zSElreE1SVGtHUkwxenlJN3VuLUtKV2FYdHVscmZpU1MzOExsVTVBQkRCOVpkc2ZxXzExcjhRL2p3a3MuanNvbiIsInN1YmplY3Rfc3lu
+ dGF4X3R5cGVzX3N1cHBvcnRlZCI6WyJ1cm46aWV0ZjpwYXJhbXM6b2F1dGg6andrLXRodW1icHJpbnQiXSwiaWRfdG9rZW5fc2lnbmVkX3Jl
+ c3BvbnNlX2FsZyI6IlJTMjU2In19.a5UzXIoRZzNQFAWFblAhkYocrR05hB-GIO7nRdqRnFrqxjvBVP6HfFhPyASRmhSgE0vUe0TPN0-TbQk
+ Yh0-LeA
+ """.trimIndent()
+
+ val jwkset = """
+ {
+ "keys": [
+ {
+ "alg": "ECDH-ES",
+ "crv": "P-256",
+ "kid": "1835c633-bd3f-429e-8dfa-64596b83aa0c",
+ "kty": "EC",
+ "use": "enc",
+ "x": "lBeONku60ShqCvndUdFVubOCCuvMjWTmElaxgHWbuMo",
+ "y": "NHYLE--QpTqc9vGrTLoq1dm2c86AC6af6xiHiLpKjdk"
+ }
+ ]
+ }
+
+ """.trimIndent()
+
+ holderSiop = OidcSiopWallet.newInstance(
+ holder = holderAgent,
+ cryptoService = holderCryptoService,
+ jwkSetRetriever = { it ->
+ if (it == "https://verifier-backend.eudiw.dev/wallet/jarm/" +
+ "WLFJEn9AGbJfAcEyaQTzzxueqmeRazmsHIkxMRTkGRL1zyI7un-KJWaXtulrfiSS38LlU5ABDB9Zdsfq_11r8Q/jwks.json"
+ )
+ JsonWebKeySet.deserialize(jwkset) else null
+ },
+ requestRetriever = { it ->
+ if (it == "https://verifier-backend.eudiw.dev/wallet/request.jwt/" +
+ "WLFJEn9AGbJfAcEyaQTzzxueqmeRazmsHIkxMRTkGRL1zyI7un-KJWaXtulrfiSS38LlU5ABDB9Zdsfq_11r8Q"
+ )
+ requestObject else null
+ }
+ )
+
+ val response = holderSiop.createAuthnResponse(url).getOrThrow()
+
+ response.shouldBeInstanceOf()
+ val params = response.content.decodeFromPostBody()
+ params.presentationSubmission.shouldNotBeNull()
+ params.vpToken.shouldNotBeNull()
+ params.idToken.shouldNotBeNull()
+ }
+
+ "EUDI AuthnRequest can be parsed" {
+ val input = """
+ {
+ "response_uri": "https://verifier-backend.eudiw.dev/wallet/direct_post",
+ "client_id_scheme": "x509_san_dns",
+ "response_type": "vp_token",
+ "nonce": "nonce",
+ "client_id": "verifier-backend.eudiw.dev",
+ "response_mode": "direct_post.jwt",
+ "aud": "https://self-issued.me/v2",
+ "scope": "",
+ "presentation_definition": {
+ "id": "32f54163-7166-48f1-93d8-ff217bdb0653",
+ "input_descriptors": [
+ {
+ "id": "eudi_pid",
+ "name": "EUDI PID",
+ "purpose": "We need to verify your identity",
+ "constraints": {
+ "fields": [
+ {
+ "path": [
+ "${'$'}.mdoc.doctype"
+ ],
+ "filter": {
+ "type": "string",
+ "const": "eu.europa.ec.eudiw.pid.1"
+ }
+ },
+ {
+ "path": [
+ "${'$'}.mdoc.namespace"
+ ],
+ "filter": {
+ "type": "string",
+ "const": "eu.europa.ec.eudiw.pid.1"
+ }
+ },
+ {
+ "path": [
+ "${'$'}.mdoc.given_name"
+ ],
+ "intent_to_retain": false
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "state": "xgagB1vsIrWhMLixoJTCVZZvOHsZ8QrulEFxc0bjJdMRyzqO6j2-UB00gmOZraocfoknlxXY-kaoLlX8kygqxw",
+ "iat": 1710313534,
+ "client_metadata": {
+ "authorization_encrypted_response_alg": "ECDH-ES",
+ "authorization_encrypted_response_enc": "A128CBC-HS256",
+ "id_token_encrypted_response_alg": "RSA-OAEP-256",
+ "id_token_encrypted_response_enc": "A128CBC-HS256",
+ "jwks_uri": "https://verifier-backend.eudiw.dev/wallet/jarm/xgagB1vsIrWhMLixoJTCVZZvOHsZ8QrulEFxc0bjJdMRyzqO6j2-UB00gmOZraocfoknlxXY-kaoLlX8kygqxw/jwks.json",
+ "subject_syntax_types_supported": [
+ "urn:ietf:params:oauth:jwk-thumbprint"
+ ],
+ "id_token_signed_response_alg": "RS256"
+ }
+ }
+ """.trimIndent()
+
+ val parsed = jsonSerializer.decodeFromString(input)
+ parsed.shouldNotBeNull()
+
+ parsed.responseUrl shouldBe "https://verifier-backend.eudiw.dev/wallet/direct_post"
+ parsed.clientIdScheme shouldBe "x509_san_dns"
+ parsed.responseType shouldBe "vp_token"
+ parsed.nonce shouldBe "nonce"
+ parsed.clientId shouldBe "verifier-backend.eudiw.dev"
+ parsed.responseMode shouldBe "direct_post.jwt"
+ parsed.audience shouldBe "https://self-issued.me/v2"
+ parsed.scope shouldBe ""
+ val pd = parsed.presentationDefinition
+ pd.shouldNotBeNull()
+ pd.id shouldBe "32f54163-7166-48f1-93d8-ff217bdb0653"
+ val id = pd.inputDescriptors.firstOrNull()
+ id.shouldNotBeNull()
+ id.id shouldBe "eudi_pid"
+ id.name shouldBe "EUDI PID"
+ id.purpose shouldBe "We need to verify your identity"
+ val fields = id.constraints?.fields
+ fields.shouldNotBeNull()
+ fields.filter { it.path.contains("$.mdoc.doctype") }.shouldBeSingleton()
+ fields.filter { it.path.contains("$.mdoc.namespace") }.shouldBeSingleton()
+ fields.filter { it.path.contains("$.mdoc.given_name") }.shouldBeSingleton()
+ parsed.state shouldBe "xgagB1vsIrWhMLixoJTCVZZvOHsZ8QrulEFxc0bjJdMRyzqO6j2-UB00gmOZraocfoknlxXY-kaoLlX8kygqxw"
+ parsed.issuedAt shouldBe Instant.fromEpochSeconds(1710313534)
+ val cm = parsed.clientMetadata
+ cm.shouldNotBeNull()
+ cm.subjectSyntaxTypesSupported shouldHaveSingleElement "urn:ietf:params:oauth:jwk-thumbprint"
+ cm.authorizationEncryptedResponseAlg shouldBe JweAlgorithm.ECDH_ES
+ cm.authorizationEncryptedResponseEncoding shouldBe "A128CBC-HS256"
+ cm.idTokenEncryptedResponseAlg shouldBe JweAlgorithm.RSA_OAEP_256
+ cm.idTokenEncryptedResponseEncoding shouldBe "A128CBC-HS256"
+ cm.idTokenSignedResponseAlg shouldBe JwsAlgorithm.RS256
+ cm.jsonWebKeySetUrl shouldBe "https://verifier-backend.eudiw.dev/wallet/jarm/" +
+ "xgagB1vsIrWhMLixoJTCVZZvOHsZ8QrulEFxc0bjJdMRyzqO6j2-UB00gmOZraocfoknlxXY-kaoLlX8kygqxw/jwks.json"
+ }
+
+})
+
+
diff --git a/vclib-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopIsoProtocolTest.kt b/vclib-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopIsoProtocolTest.kt
index 3309a00e8..b721fcfff 100644
--- a/vclib-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopIsoProtocolTest.kt
+++ b/vclib-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopIsoProtocolTest.kt
@@ -72,9 +72,14 @@ class OidcSiopIsoProtocolTest : FreeSpec({
verifier = verifierAgent,
cryptoService = verifierCryptoService,
relyingPartyUrl = relyingPartyUrl,
- credentialScheme = ConstantIndex.MobileDrivingLicence2023,
)
- val document = runProcess(verifierSiop, walletUrl, ConstantIndex.CredentialRepresentation.ISO_MDOC, holderSiop)
+ val document = runProcess(
+ verifierSiop,
+ walletUrl,
+ ConstantIndex.CredentialRepresentation.ISO_MDOC,
+ ConstantIndex.MobileDrivingLicence2023,
+ holderSiop
+ )
document.validItems.shouldNotBeEmpty()
document.invalidItems.shouldBeEmpty()
@@ -85,9 +90,14 @@ class OidcSiopIsoProtocolTest : FreeSpec({
verifier = verifierAgent,
cryptoService = verifierCryptoService,
relyingPartyUrl = relyingPartyUrl,
- credentialScheme = ConstantIndex.AtomicAttribute2023,
)
- val document = runProcess(verifierSiop, walletUrl, ConstantIndex.CredentialRepresentation.ISO_MDOC, holderSiop)
+ val document = runProcess(
+ verifierSiop,
+ walletUrl,
+ ConstantIndex.CredentialRepresentation.ISO_MDOC,
+ ConstantIndex.AtomicAttribute2023,
+ holderSiop
+ )
document.validItems.shouldNotBeEmpty()
document.invalidItems.shouldBeEmpty()
@@ -99,12 +109,12 @@ class OidcSiopIsoProtocolTest : FreeSpec({
verifier = verifierAgent,
cryptoService = verifierCryptoService,
relyingPartyUrl = relyingPartyUrl,
- credentialScheme = ConstantIndex.MobileDrivingLicence2023,
)
val document = runProcess(
verifierSiop,
walletUrl,
ConstantIndex.CredentialRepresentation.ISO_MDOC,
+ ConstantIndex.MobileDrivingLicence2023,
holderSiop,
listOf(requestedClaim)
)
@@ -121,13 +131,15 @@ private suspend fun runProcess(
verifierSiop: OidcSiopVerifier,
walletUrl: String,
credentialRepresentation: ConstantIndex.CredentialRepresentation,
+ credentialScheme: ConstantIndex.CredentialScheme,
holderSiop: OidcSiopWallet,
requestedAttributes: List? = null,
): IsoDocumentParsed {
val authnRequest = verifierSiop.createAuthnRequestUrl(
walletUrl = walletUrl,
representation = credentialRepresentation,
- requestedAttributes = requestedAttributes
+ credentialScheme = credentialScheme,
+ requestedAttributes = requestedAttributes,
).also { println(it) }
val authnResponse = holderSiop.createAuthnResponse(authnRequest).getOrThrow()
diff --git a/vclib-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopProtocolTest.kt b/vclib-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopProtocolTest.kt
index 8af43ad71..ac4150cde 100644
--- a/vclib-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopProtocolTest.kt
+++ b/vclib-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopProtocolTest.kt
@@ -171,10 +171,12 @@ class OidcSiopProtocolTest : FreeSpec({
verifier = verifierAgent,
cryptoService = verifierCryptoService,
relyingPartyUrl = relyingPartyUrl,
- credentialScheme = ConstantIndex.AtomicAttribute2023,
)
- val authnRequest = verifierSiop.createAuthnRequestUrl(walletUrl = walletUrl).also { println(it) }
+ val authnRequest = verifierSiop.createAuthnRequestUrl(
+ walletUrl = walletUrl,
+ credentialScheme = ConstantIndex.AtomicAttribute2023
+ ).also { println(it) }
val authnResponse = holderSiop.createAuthnResponse(authnRequest).getOrThrow()
authnResponse.shouldBeInstanceOf().also { println(it) }
diff --git a/vclib-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopSdJwtProtocolTest.kt b/vclib-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopSdJwtProtocolTest.kt
index 2a4af1bdd..844253121 100644
--- a/vclib-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopSdJwtProtocolTest.kt
+++ b/vclib-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopSdJwtProtocolTest.kt
@@ -85,11 +85,11 @@ class OidcSiopSdJwtProtocolTest : FreeSpec({
verifier = verifierAgent,
cryptoService = verifierCryptoService,
relyingPartyUrl = relyingPartyUrl,
- credentialScheme = ConstantIndex.AtomicAttribute2023,
)
val authnRequest = verifierSiop.createAuthnRequestUrl(
walletUrl = walletUrl,
representation = ConstantIndex.CredentialRepresentation.SD_JWT,
+ credentialScheme = ConstantIndex.AtomicAttribute2023,
requestedAttributes = listOf(requestedClaim),
).also { println(it) }
authnRequest shouldContain "jwt_sd"
diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/HolderAgent.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/HolderAgent.kt
index 734f43670..820c8888a 100644
--- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/HolderAgent.kt
+++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/HolderAgent.kt
@@ -283,7 +283,7 @@ class HolderAgent(
challenge: String,
audienceId: String,
): Holder.CreatePresentationResult? {
- val vp = VerifiablePresentation(validCredentials.toTypedArray())
+ val vp = VerifiablePresentation(validCredentials)
val vpSerialized = vp.toJws(challenge, identifier, audienceId).serialize()
val jwsPayload = vpSerialized.encodeToByteArray()
val jws = jwsService.createSignedJwt(JwsContentTypeConstants.JWT, jwsPayload).getOrElse {
diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/IssuerAgent.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/IssuerAgent.kt
index 41911abc8..75c09dac3 100644
--- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/IssuerAgent.kt
+++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/IssuerAgent.kt
@@ -277,7 +277,7 @@ class IssuerAgent(
expiration = expirationDate,
jwtId = vcId,
disclosureDigests = disclosureDigests,
- type = arrayOf(VcDataModelConstants.VERIFIABLE_CREDENTIAL, scheme.vcType),
+ type = listOf(VcDataModelConstants.VERIFIABLE_CREDENTIAL, scheme.vcType),
selectiveDisclosureAlgorithm = "sha-256",
confirmationKey = subjectPublicKey.toJsonWebKey(),
credentialStatus = credentialStatus,
diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/Validator.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/Validator.kt
index ceb7b1391..dfc5fef17 100644
--- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/Validator.kt
+++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/Validator.kt
@@ -183,9 +183,9 @@ class Validator(
val vp = VerifiablePresentationParsed(
id = parsedVp.jws.vp.id,
type = parsedVp.jws.vp.type,
- verifiableCredentials = validVcList.toTypedArray(),
- revokedVerifiableCredentials = revokedVcList.toTypedArray(),
- invalidVerifiableCredentials = invalidVcList.toTypedArray(),
+ verifiableCredentials = validVcList,
+ revokedVerifiableCredentials = revokedVcList,
+ invalidVerifiableCredentials = invalidVcList,
)
Napier.d("VP: Valid")
return Verifier.VerifyPresentationResult.Success(vp)
diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/AttributeIndex.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/AttributeIndex.kt
index e50503063..b33e174fd 100644
--- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/AttributeIndex.kt
+++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/AttributeIndex.kt
@@ -27,4 +27,12 @@ object AttributeIndex {
return schemeSet.firstOrNull { it.vcType == type }
}
+ /**
+ * Matches the passed [namespace] against all known namespace from [ConstantIndex.CredentialScheme.isoNamespace]
+ */
+ fun resolveIsoNamespace(namespace: String): ConstantIndex.CredentialScheme? {
+ // allow for extension to the namespace by appending ".countryname" or anything else, according to spec
+ return schemeSet.firstOrNull { it.isoNamespace.startsWith(namespace) || namespace.startsWith(it.isoNamespace) }
+ }
+
}
\ No newline at end of file
diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/VerifiableCredential.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/VerifiableCredential.kt
index f44840dfd..d7436824d 100644
--- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/VerifiableCredential.kt
+++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/VerifiableCredential.kt
@@ -17,7 +17,7 @@ data class VerifiableCredential(
@SerialName("id")
val id: String,
@SerialName("type")
- val type: Array,
+ val type: Collection,
@SerialName("issuer")
val issuer: String,
@Serializable(with = InstantStringSerializer::class)
@@ -43,7 +43,7 @@ data class VerifiableCredential(
expirationDate: Instant? = Clock.System.now() + lifetime,
) : this(
id = id,
- type = arrayOf(VERIFIABLE_CREDENTIAL, credentialType),
+ type = listOf(VERIFIABLE_CREDENTIAL, credentialType),
issuer = issuer,
issuanceDate = issuanceDate,
expirationDate = expirationDate,
@@ -61,7 +61,7 @@ data class VerifiableCredential(
credentialType: String,
) : this(
id = id,
- type = arrayOf(VERIFIABLE_CREDENTIAL, credentialType),
+ type = listOf(VERIFIABLE_CREDENTIAL, credentialType),
issuer = issuer,
issuanceDate = issuanceDate,
expirationDate = expirationDate,
@@ -77,37 +77,11 @@ data class VerifiableCredential(
credentialSubject: RevocationListSubject,
) : this(
id = id,
- type = arrayOf(VERIFIABLE_CREDENTIAL, REVOCATION_LIST_2020),
+ type = listOf(VERIFIABLE_CREDENTIAL, REVOCATION_LIST_2020),
issuer = issuer,
issuanceDate = issuanceDate,
expirationDate = issuanceDate + lifetime,
credentialStatus = null,
credentialSubject = credentialSubject,
)
-
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other == null || this::class != other::class) return false
-
- other as VerifiableCredential
-
- if (id != other.id) return false
- if (!type.contentEquals(other.type)) return false
- if (issuer != other.issuer) return false
- if (issuanceDate != other.issuanceDate) return false
- if (expirationDate != other.expirationDate) return false
- if (credentialStatus != other.credentialStatus) return false
- return credentialSubject == other.credentialSubject
- }
-
- override fun hashCode(): Int {
- var result = id.hashCode()
- result = 31 * result + type.contentHashCode()
- result = 31 * result + issuer.hashCode()
- result = 31 * result + issuanceDate.hashCode()
- result = 31 * result + (expirationDate?.hashCode() ?: 0)
- result = 31 * result + (credentialStatus?.hashCode() ?: 0)
- result = 31 * result + credentialSubject.hashCode()
- return result
- }
-}
+}
\ No newline at end of file
diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/VerifiableCredentialSdJwt.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/VerifiableCredentialSdJwt.kt
index 5720a3c85..d53458ed8 100644
--- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/VerifiableCredentialSdJwt.kt
+++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/VerifiableCredentialSdJwt.kt
@@ -27,7 +27,7 @@ data class VerifiableCredentialSdJwt(
@SerialName("_sd")
val disclosureDigests: List,
@SerialName("type")
- val type: Array,
+ val type: Collection,
@SerialName("credentialStatus")
val credentialStatus: CredentialStatus? = null,
@SerialName("_sd_alg")
@@ -38,40 +38,6 @@ data class VerifiableCredentialSdJwt(
fun serialize() = jsonSerializer.encodeToString(this)
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other == null || this::class != other::class) return false
-
- other as VerifiableCredentialSdJwt
-
- if (subject != other.subject) return false
- if (notBefore != other.notBefore) return false
- if (issuer != other.issuer) return false
- if (expiration != other.expiration) return false
- if (jwtId != other.jwtId) return false
- if (disclosureDigests != other.disclosureDigests) return false
- if (!type.contentEquals(other.type)) return false
- if (credentialStatus != other.credentialStatus) return false
- if (selectiveDisclosureAlgorithm != other.selectiveDisclosureAlgorithm) return false
- if (confirmationKey != other.confirmationKey) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = subject.hashCode()
- result = 31 * result + notBefore.hashCode()
- result = 31 * result + issuer.hashCode()
- result = 31 * result + (expiration?.hashCode() ?: 0)
- result = 31 * result + jwtId.hashCode()
- result = 31 * result + disclosureDigests.hashCode()
- result = 31 * result + type.contentHashCode()
- result = 31 * result + (credentialStatus?.hashCode() ?: 0)
- result = 31 * result + selectiveDisclosureAlgorithm.hashCode()
- result = 31 * result + (confirmationKey?.hashCode() ?: 0)
- return result
- }
-
companion object {
fun deserialize(it: String) = kotlin.runCatching {
jsonSerializer.decodeFromString(it)
diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/VerifiablePresentation.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/VerifiablePresentation.kt
index 95de1cb88..6e9d61a52 100644
--- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/VerifiablePresentation.kt
+++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/VerifiablePresentation.kt
@@ -14,10 +14,10 @@ data class VerifiablePresentation(
@SerialName("type")
val type: String,
@SerialName("verifiableCredential")
- val verifiableCredential: Array,
+ val verifiableCredential: Collection,
) {
- constructor(verifiableCredential: Array) : this(
+ constructor(verifiableCredential: Collection) : this(
id = "urn:uuid:${uuid4()}",
type = "VerifiablePresentation",
verifiableCredential = verifiableCredential
@@ -30,25 +30,4 @@ data class VerifiablePresentation(
audience = audienceId,
jwtId = id
)
-
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other == null || this::class != other::class) return false
-
- other as VerifiablePresentation
-
- if (id != other.id) return false
- if (type != other.type) return false
- if (!verifiableCredential.contentEquals(other.verifiableCredential)) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = id.hashCode()
- result = 31 * result + type.hashCode()
- result = 31 * result + verifiableCredential.contentHashCode()
- return result
- }
-
}
\ No newline at end of file
diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/VerifiablePresentationParsed.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/VerifiablePresentationParsed.kt
index e059e04a0..56e33de52 100644
--- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/VerifiablePresentationParsed.kt
+++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/VerifiablePresentationParsed.kt
@@ -7,31 +7,7 @@ package at.asitplus.wallet.lib.data
data class VerifiablePresentationParsed(
val id: String,
val type: String,
- val verifiableCredentials: Array = arrayOf(),
- val revokedVerifiableCredentials: Array = arrayOf(),
- val invalidVerifiableCredentials: Array = arrayOf(),
-) {
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other == null || this::class != other::class) return false
-
- other as VerifiablePresentationParsed
-
- if (id != other.id) return false
- if (type != other.type) return false
- if (!verifiableCredentials.contentEquals(other.verifiableCredentials)) return false
- if (!revokedVerifiableCredentials.contentEquals(other.revokedVerifiableCredentials)) return false
- if (!invalidVerifiableCredentials.contentEquals(other.invalidVerifiableCredentials)) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = id.hashCode()
- result = 31 * result + type.hashCode()
- result = 31 * result + verifiableCredentials.contentHashCode()
- result = 31 * result + revokedVerifiableCredentials.contentHashCode()
- result = 31 * result + invalidVerifiableCredentials.contentHashCode()
- return result
- }
-}
\ No newline at end of file
+ val verifiableCredentials: Collection = listOf(),
+ val revokedVerifiableCredentials: Collection = listOf(),
+ val invalidVerifiableCredentials: Collection = listOf(),
+)
\ No newline at end of file
diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/VerifiablePresentationSdJwtParsed.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/VerifiablePresentationSdJwtParsed.kt
index 2963443da..7ceadf6ca 100644
--- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/VerifiablePresentationSdJwtParsed.kt
+++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/VerifiablePresentationSdJwtParsed.kt
@@ -7,31 +7,7 @@ package at.asitplus.wallet.lib.data
data class VerifiablePresentationSdJwtParsed(
val id: String,
val type: String,
- val verifiableCredentials: Array = arrayOf(),
- val revokedVerifiableCredentials: Array = arrayOf(),
- val invalidVerifiableCredentials: Array = arrayOf(),
-) {
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other == null || this::class != other::class) return false
-
- other as VerifiablePresentationSdJwtParsed
-
- if (id != other.id) return false
- if (type != other.type) return false
- if (!verifiableCredentials.contentEquals(other.verifiableCredentials)) return false
- if (!revokedVerifiableCredentials.contentEquals(other.revokedVerifiableCredentials)) return false
- if (!invalidVerifiableCredentials.contentEquals(other.invalidVerifiableCredentials)) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = id.hashCode()
- result = 31 * result + type.hashCode()
- result = 31 * result + verifiableCredentials.contentHashCode()
- result = 31 * result + revokedVerifiableCredentials.contentHashCode()
- result = 31 * result + invalidVerifiableCredentials.contentHashCode()
- return result
- }
-}
\ No newline at end of file
+ val verifiableCredentials: Collection = listOf(),
+ val revokedVerifiableCredentials: Collection = listOf(),
+ val invalidVerifiableCredentials: Collection = listOf(),
+)
\ No newline at end of file
diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/Constraint.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/Constraint.kt
index a2d19bb49..3213763fc 100644
--- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/Constraint.kt
+++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/Constraint.kt
@@ -10,7 +10,7 @@ import kotlinx.serialization.Serializable
@Serializable
data class Constraint(
@SerialName("fields")
- val fields: Array? = null,
+ val fields: Collection? = null,
@SerialName("limit_disclosure")
val limitDisclosure: RequirementEnum? = null,
@SerialName("statuses")
@@ -18,42 +18,7 @@ data class Constraint(
@SerialName("subject_is_issuer")
val subjectIsIssuer: RequirementEnum? = null,
@SerialName("is_holder")
- val isHolder: Array? = null,
+ val isHolder: Collection? = null,
@SerialName("same_subject")
- val sameSubject: Array? = null,
-) {
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other == null || this::class != other::class) return false
-
- other as Constraint
-
- if (fields != null) {
- if (other.fields == null) return false
- if (!fields.contentEquals(other.fields)) return false
- } else if (other.fields != null) return false
- if (limitDisclosure != other.limitDisclosure) return false
- if (statuses != other.statuses) return false
- if (subjectIsIssuer != other.subjectIsIssuer) return false
- if (isHolder != null) {
- if (other.isHolder == null) return false
- if (!isHolder.contentEquals(other.isHolder)) return false
- } else if (other.isHolder != null) return false
- if (sameSubject != null) {
- if (other.sameSubject == null) return false
- if (!sameSubject.contentEquals(other.sameSubject)) return false
- } else if (other.sameSubject != null) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = fields?.contentHashCode() ?: 0
- result = 31 * result + (limitDisclosure?.hashCode() ?: 0)
- result = 31 * result + (statuses?.hashCode() ?: 0)
- result = 31 * result + (subjectIsIssuer?.hashCode() ?: 0)
- result = 31 * result + (isHolder?.contentHashCode() ?: 0)
- result = 31 * result + (sameSubject?.contentHashCode() ?: 0)
- return result
- }
-}
\ No newline at end of file
+ val sameSubject: Collection? = null,
+)
\ No newline at end of file
diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/ConstraintField.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/ConstraintField.kt
index 766add87c..d05b928ea 100644
--- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/ConstraintField.kt
+++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/ConstraintField.kt
@@ -17,33 +17,9 @@ data class ConstraintField(
val predicate: RequirementEnum? = null,
@SerialName("path")
// should be JSONPath
- val path: Array,
+ val path: List,
@SerialName("filter")
val filter: ConstraintFilter? = null,
@SerialName("intent_to_retain")
val intentToRetain: Boolean? = null,
-) {
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other == null || this::class != other::class) return false
-
- other as ConstraintField
-
- if (id != other.id) return false
- if (purpose != other.purpose) return false
- if (predicate != other.predicate) return false
- if (!path.contentEquals(other.path)) return false
- if (filter != other.filter) return false
- return intentToRetain == other.intentToRetain
- }
-
- override fun hashCode(): Int {
- var result = id?.hashCode() ?: 0
- result = 31 * result + (purpose?.hashCode() ?: 0)
- result = 31 * result + (predicate?.hashCode() ?: 0)
- result = 31 * result + path.contentHashCode()
- result = 31 * result + (filter?.hashCode() ?: 0)
- result = 31 * result + (intentToRetain?.hashCode() ?: 0)
- return result
- }
-}
\ No newline at end of file
+)
\ No newline at end of file
diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/ConstraintFilter.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/ConstraintFilter.kt
index e29df72a7..e6efdf6cb 100644
--- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/ConstraintFilter.kt
+++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/ConstraintFilter.kt
@@ -30,49 +30,7 @@ data class ConstraintFilter(
@SerialName("maxLength")
val maxLength: Int? = null,
@SerialName("enum")
- val enum: Array? = null,
+ val enum: Collection? = null,
@SerialName("not")
val not: ConstraintNotFilter? = null,
-) {
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other == null || this::class != other::class) return false
-
- other as ConstraintFilter
-
- if (type != other.type) return false
- if (format != other.format) return false
- if (const != other.const) return false
- if (pattern != other.pattern) return false
- if (exclusiveMinimum != other.exclusiveMinimum) return false
- if (exclusiveMaximum != other.exclusiveMaximum) return false
- if (minimum != other.minimum) return false
- if (maximum != other.maximum) return false
- if (minLength != other.minLength) return false
- if (maxLength != other.maxLength) return false
- if (enum != null) {
- if (other.enum == null) return false
- if (!enum.contentEquals(other.enum)) return false
- } else if (other.enum != null) return false
- if (not != other.not) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = type.hashCode()
- result = 31 * result + (format?.hashCode() ?: 0)
- result = 31 * result + (const?.hashCode() ?: 0)
- result = 31 * result + (pattern?.hashCode() ?: 0)
- result = 31 * result + (exclusiveMinimum ?: 0)
- result = 31 * result + (exclusiveMaximum ?: 0)
- result = 31 * result + (minimum ?: 0)
- result = 31 * result + (maximum ?: 0)
- result = 31 * result + (minLength ?: 0)
- result = 31 * result + (maxLength ?: 0)
- result = 31 * result + (enum?.contentHashCode() ?: 0)
- result = 31 * result + (not?.hashCode() ?: 0)
- return result
- }
-}
-
+)
\ No newline at end of file
diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/ConstraintHolder.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/ConstraintHolder.kt
index f5439c669..a3f6f6149 100644
--- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/ConstraintHolder.kt
+++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/ConstraintHolder.kt
@@ -10,25 +10,7 @@ import kotlinx.serialization.Serializable
@Serializable
data class ConstraintHolder(
@SerialName("field_id")
- val fieldIds: Array,
+ val fieldIds: Collection,
@SerialName("directive")
val directive: RequirementEnum,
-) {
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other == null || this::class != other::class) return false
-
- other as ConstraintHolder
-
- if (!fieldIds.contentEquals(other.fieldIds)) return false
- if (directive != other.directive) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = fieldIds.contentHashCode()
- result = 31 * result + directive.hashCode()
- return result
- }
-}
\ No newline at end of file
+)
\ No newline at end of file
diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/ConstraintNotFilter.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/ConstraintNotFilter.kt
index d26641509..0b662bab9 100644
--- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/ConstraintNotFilter.kt
+++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/ConstraintNotFilter.kt
@@ -12,26 +12,5 @@ data class ConstraintNotFilter(
@SerialName("const")
val const: String? = null,
@SerialName("enum")
- val enum: Array? = null,
-) {
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other == null || this::class != other::class) return false
-
- other as ConstraintNotFilter
-
- if (const != other.const) return false
- if (enum != null) {
- if (other.enum == null) return false
- if (!enum.contentEquals(other.enum)) return false
- } else if (other.enum != null) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = const?.hashCode() ?: 0
- result = 31 * result + (enum?.contentHashCode() ?: 0)
- return result
- }
-}
\ No newline at end of file
+ val enum: Collection? = null,
+)
\ No newline at end of file
diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/FormatContainerJwt.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/FormatContainerJwt.kt
index aaaa8c22d..158730b87 100644
--- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/FormatContainerJwt.kt
+++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/FormatContainerJwt.kt
@@ -10,20 +10,5 @@ import kotlinx.serialization.Serializable
@Serializable
data class FormatContainerJwt(
@SerialName("alg")
- val algorithms: Array,
-) {
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other == null || this::class != other::class) return false
-
- other as FormatContainerJwt
-
- if (!algorithms.contentEquals(other.algorithms)) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- return algorithms.contentHashCode()
- }
-}
\ No newline at end of file
+ val algorithms: Collection,
+)
\ No newline at end of file
diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/FormatContainerLdp.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/FormatContainerLdp.kt
index 490c8cd01..b50acf36b 100644
--- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/FormatContainerLdp.kt
+++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/FormatContainerLdp.kt
@@ -10,20 +10,5 @@ import kotlinx.serialization.Serializable
@Serializable
data class FormatContainerLdp(
@SerialName("proof_type")
- val proofType: Array,
-) {
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other == null || this::class != other::class) return false
-
- other as FormatContainerLdp
-
- if (!proofType.contentEquals(other.proofType)) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- return proofType.contentHashCode()
- }
-}
\ No newline at end of file
+ val proofType: Collection,
+)
\ No newline at end of file
diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/InputDescriptor.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/InputDescriptor.kt
index cb352151c..8ae6f96b2 100644
--- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/InputDescriptor.kt
+++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/InputDescriptor.kt
@@ -21,42 +21,14 @@ data class InputDescriptor(
@SerialName("format")
val format: FormatHolder? = null,
@SerialName("schema")
- val schema: Array,
+ val schema: Collection? = null,
@SerialName("constraints")
val constraints: Constraint? = null,
) {
constructor(name: String, schema: SchemaReference, constraints: Constraint? = null) : this(
id = uuid4().toString(),
name = name,
- schema = arrayOf(schema),
+ schema = listOf(schema),
constraints = constraints,
)
-
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other == null || this::class != other::class) return false
-
- other as InputDescriptor
-
- if (id != other.id) return false
- if (group != other.group) return false
- if (name != other.name) return false
- if (purpose != other.purpose) return false
- if (format != other.format) return false
- if (!schema.contentEquals(other.schema)) return false
- if (constraints != other.constraints) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = id.hashCode()
- result = 31 * result + (group?.hashCode() ?: 0)
- result = 31 * result + (name?.hashCode() ?: 0)
- result = 31 * result + (purpose?.hashCode() ?: 0)
- result = 31 * result + (format?.hashCode() ?: 0)
- result = 31 * result + schema.contentHashCode()
- result = 31 * result + (constraints?.hashCode() ?: 0)
- return result
- }
}
\ No newline at end of file
diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/PresentationDefinition.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/PresentationDefinition.kt
index 17abfb3cf..28e311246 100644
--- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/PresentationDefinition.kt
+++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/PresentationDefinition.kt
@@ -20,48 +20,19 @@ data class PresentationDefinition(
@SerialName("purpose")
val purpose: String? = null,
@SerialName("input_descriptors")
- val inputDescriptors: Array,
+ val inputDescriptors: Collection,
@SerialName("format")
val formats: FormatHolder? = null,
@SerialName("submission_requirements")
- val submissionRequirements: Array? = null,
+ val submissionRequirements: Collection? = null,
) {
constructor(
- inputDescriptors: Array,
+ inputDescriptors: Collection,
formats: FormatHolder
) : this(id = uuid4().toString(), inputDescriptors = inputDescriptors, formats = formats)
fun serialize() = jsonSerializer.encodeToString(this)
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other == null || this::class != other::class) return false
-
- other as PresentationDefinition
-
- if (id != other.id) return false
- if (name != other.name) return false
- if (purpose != other.purpose) return false
- if (!inputDescriptors.contentEquals(other.inputDescriptors)) return false
- if (formats != other.formats) return false
- if (submissionRequirements != null) {
- if (other.submissionRequirements == null) return false
- if (!submissionRequirements.contentEquals(other.submissionRequirements)) return false
- } else if (other.submissionRequirements != null) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = id.hashCode()
- result = 31 * result + (name?.hashCode() ?: 0)
- result = 31 * result + (purpose?.hashCode() ?: 0)
- result = 31 * result + inputDescriptors.contentHashCode()
- result = 31 * result + (formats?.hashCode() ?: 0)
- result = 31 * result + (submissionRequirements?.contentHashCode() ?: 0)
- return result
- }
-
companion object {
fun deserialize(it: String) = kotlin.runCatching {
jsonSerializer.decodeFromString(it)
diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/PresentationSubmission.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/PresentationSubmission.kt
index 2ddfde4bf..fbc354a33 100644
--- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/PresentationSubmission.kt
+++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/PresentationSubmission.kt
@@ -14,28 +14,5 @@ data class PresentationSubmission(
@SerialName("definition_id")
val definitionId: String,
@SerialName("descriptor_map")
- val descriptorMap: Array? = null,
-) {
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other == null || this::class != other::class) return false
-
- other as PresentationSubmission
-
- if (id != other.id) return false
- if (definitionId != other.definitionId) return false
- if (descriptorMap != null) {
- if (other.descriptorMap == null) return false
- if (!descriptorMap.contentEquals(other.descriptorMap)) return false
- } else if (other.descriptorMap != null) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = id.hashCode()
- result = 31 * result + definitionId.hashCode()
- result = 31 * result + (descriptorMap?.contentHashCode() ?: 0)
- return result
- }
-}
\ No newline at end of file
+ val descriptorMap: Collection? = null,
+)
\ No newline at end of file
diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/SubmissionRequirement.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/SubmissionRequirement.kt
index 01d58b8d4..bff934538 100644
--- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/SubmissionRequirement.kt
+++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/dif/SubmissionRequirement.kt
@@ -24,38 +24,5 @@ data class SubmissionRequirement(
@SerialName("from")
val from: String? = null,
@SerialName("from_nested")
- val fromNested: Array? = null,
-) {
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other == null || this::class != other::class) return false
-
- other as SubmissionRequirement
-
- if (name != other.name) return false
- if (purpose != other.purpose) return false
- if (rule != other.rule) return false
- if (count != other.count) return false
- if (min != other.min) return false
- if (max != other.max) return false
- if (from != other.from) return false
- if (fromNested != null) {
- if (other.fromNested == null) return false
- if (!fromNested.contentEquals(other.fromNested)) return false
- } else if (other.fromNested != null) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = name?.hashCode() ?: 0
- result = 31 * result + (purpose?.hashCode() ?: 0)
- result = 31 * result + (rule?.hashCode() ?: 0)
- result = 31 * result + (count ?: 0)
- result = 31 * result + (min ?: 0)
- result = 31 * result + (max ?: 0)
- result = 31 * result + (from?.hashCode() ?: 0)
- result = 31 * result + (fromNested?.contentHashCode() ?: 0)
- return result
- }
-}
\ No newline at end of file
+ val fromNested: Collection? = null,
+)
\ No newline at end of file
diff --git a/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/agent/ValidatorVcTest.kt b/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/agent/ValidatorVcTest.kt
index d9768ad12..b5d2235f0 100644
--- a/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/agent/ValidatorVcTest.kt
+++ b/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/agent/ValidatorVcTest.kt
@@ -201,26 +201,6 @@ class ValidatorVcTest : FreeSpec() {
}
}
- "Invalid type in credential is not valid" - {
- withData(
- nameFn = ::credentialNameFn,
- dataProvider.getCredential(
- verifierCryptoService.publicKey,
- ConstantIndex.AtomicAttribute2023,
- ConstantIndex.CredentialRepresentation.PLAIN_JWT
- ).getOrThrow()
- ) {
- issueCredential(it)
- .also { it.type[0] = "fakeCredential" }
- .let { wrapVcInJws(it) }
- .let { signJws(it) }
- ?.let {
- verifier.verifyVcJws(it)
- .shouldBeInstanceOf()
- }
- }
- }
-
"Invalid expiration in credential is not valid" - {
withData(
nameFn = ::credentialNameFn,
@@ -373,7 +353,8 @@ class ValidatorVcTest : FreeSpec() {
private fun issueCredential(
credential: CredentialToBeIssued,
issuanceDate: Instant = Clock.System.now(),
- expirationDate: Instant? = Clock.System.now() + 60.seconds
+ expirationDate: Instant? = Clock.System.now() + 60.seconds,
+ type: String = ConstantIndex.AtomicAttribute2023.vcType,
): VerifiableCredential {
credential.shouldBeInstanceOf()
val sub = credential.subject
@@ -393,7 +374,7 @@ class ValidatorVcTest : FreeSpec() {
issuer = issuer.identifier,
credentialStatus = credentialStatus,
credentialSubject = sub,
- credentialType = ConstantIndex.AtomicAttribute2023.vcType,
+ credentialType = type,
issuanceDate = issuanceDate,
expirationDate = expirationDate,
)
diff --git a/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/agent/ValidatorVpTest.kt b/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/agent/ValidatorVpTest.kt
index 1d2dae765..bdfd05b27 100644
--- a/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/agent/ValidatorVpTest.kt
+++ b/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/agent/ValidatorVpTest.kt
@@ -129,7 +129,7 @@ class ValidatorVpTest : FreeSpec({
.map { it.vcSerialized }
(validCredentials.isEmpty()) shouldBe false
- val vp = VerifiablePresentation(validCredentials.toTypedArray())
+ val vp = VerifiablePresentation(validCredentials)
val vpSerialized = vp.toJws(
challenge = challenge,
issuerId = holder.identifier,
@@ -148,7 +148,7 @@ class ValidatorVpTest : FreeSpec({
.filterIsInstance()
.map { it.vcSerialized }
- val vp = VerifiablePresentation(credentials.toTypedArray())
+ val vp = VerifiablePresentation(credentials)
val vpSerialized = VerifiablePresentationJws(
vp = vp,
challenge = challenge,
@@ -168,7 +168,7 @@ class ValidatorVpTest : FreeSpec({
val credentials = holderCredentialStore.getCredentials().getOrThrow()
.filterIsInstance()
.map { it.vcSerialized }
- val vp = VerifiablePresentation(credentials.toTypedArray())
+ val vp = VerifiablePresentation(credentials)
val vpSerialized = VerifiablePresentationJws(
vp = vp,
challenge = challenge,
@@ -191,7 +191,7 @@ class ValidatorVpTest : FreeSpec({
val vp = VerifiablePresentation(
id = "urn:uuid:${uuid4()}",
type = "wrong_type",
- verifiableCredential = credentials.toTypedArray()
+ verifiableCredential = credentials
)
val vpSerialized = vp.toJws(
challenge = challenge,