From 81f335193e4d0c7e2cf854ef3c1e48e00460fee3 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Tue, 12 Nov 2024 18:06:14 +0200 Subject: [PATCH 01/53] refactor: refactor metadata to support d10 and d13 --- .../kotlin/id/walt/oid4vc/OpenID4VC.kt | 10 ++ .../kotlin/id/walt/oid4vc/OpenID4VCI.kt | 100 +++++++++---- .../oid4vc/data/OpenIDProviderMetadata.kt | 131 +++++++++++++++--- .../providers/OpenIDCredentialWallet.kt | 13 +- .../walt/oid4vc/providers/OpenIDProvider.kt | 5 +- .../kotlin/id/walt/oid4vc/CI_JVM_Test.kt | 18 +-- .../kotlin/id/walt/oid4vc/EBSITestWallet.kt | 6 +- .../kotlin/id/walt/oid4vc/OpenID4VCI_Test.kt | 2 +- .../id/walt/oid4vc/TestCredentialWallet.kt | 6 +- .../kotlin/id/walt/oid4vc/wallettest.kt | 2 +- .../src/test/kotlin/LspPotentialIssuance.kt | 10 +- .../src/test/kotlin/LspPotentialWallet.kt | 4 +- .../kotlin/id/walt/issuer/NewIssuanceStub.kt | 2 +- .../id/walt/issuer/NewTestOidcMetadata.kt | 2 +- .../exchange/CredentialOfferProcessor.kt | 6 + .../service/exchange/IssuanceService.kt | 8 +- .../IssuanceServiceExternalSignatures.kt | 3 +- .../service/oidc4vc/TestCredentialWallet.kt | 6 +- 18 files changed, 243 insertions(+), 91 deletions(-) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt index cff710602..e8b104eab 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt @@ -146,6 +146,9 @@ object OpenID4VC { isJar: Boolean? = true, presentationDefinition: PresentationDefinition? = null, ): AuthorizationCodeWithAuthorizationRequestResponse { + + providerMetadata as OpenIDProviderMetadataD13 + if (!authorizationRequest.responseType.contains(ResponseType.Code)) throw AuthorizationError( authorizationRequest, @@ -210,12 +213,17 @@ object OpenID4VC { AuthorizationErrorCode.invalid_request, message = "Invalid response type ${authorizationRequest.responseType}, for authorization code flow." ) + + providerMetadata as OpenIDProviderMetadataD13 + val issuer = providerMetadata.issuer ?: throw AuthorizationError(authorizationRequest, AuthorizationErrorCode.server_error,"No issuer configured in given provider metadata") val code = generateAuthorizationCodeFor(sessionId, issuer, tokenKey) return AuthorizationCodeResponse.success(code, mapOf("state" to listOf(authorizationRequest.state ?: randomUUID()))) } suspend fun processImplicitFlowAuthorization(authorizationRequest: AuthorizationRequest, sessionId: String, providerMetadata: OpenIDProviderMetadata, tokenKey: Key): TokenResponse { + providerMetadata as OpenIDProviderMetadataD13 + log.debug { "> processImplicitFlowAuthorization for $authorizationRequest" } if (!authorizationRequest.responseType.contains(ResponseType.Token) && !authorizationRequest.responseType.contains(ResponseType.VpToken) && !authorizationRequest.responseType.contains(ResponseType.IdToken) @@ -235,6 +243,8 @@ object OpenID4VC { } suspend fun processDirectPost(authorizationRequest: AuthorizationRequest, sessionId: String, providerMetadata: OpenIDProviderMetadata, tokenKey: Key): AuthorizationCodeResponse { + providerMetadata as OpenIDProviderMetadataD13 + // Verify nonce - need to add Id token nonce session // if (payload[JWTClaims.Payload.nonce] != session.) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt index 181c9a1bd..f8b1a2a0d 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt @@ -133,8 +133,11 @@ object OpenID4VCI { } fun resolveOfferedCredentials(credentialOffer: CredentialOffer, providerMetadata: OpenIDProviderMetadata): List { - val supportedCredentials = - providerMetadata.credentialConfigurationsSupported ?: mapOf() + val supportedCredentials = when (providerMetadata) { + is OpenIDProviderMetadataD10 -> providerMetadata.credentialSupported ?: mapOf() + is OpenIDProviderMetadataD13 -> providerMetadata.credentialConfigurationsSupported ?: mapOf() + } + return credentialOffer.credentialConfigurationIds.mapNotNull { c -> supportedCredentials[c]?.let { OfferedCredential.fromProviderMetadata(it) @@ -304,28 +307,58 @@ object OpenID4VCI { return payload } - fun createDefaultProviderMetadata(baseUrl: String) = OpenIDProviderMetadata( - issuer = baseUrl, - authorizationEndpoint = "$baseUrl/authorize", - pushedAuthorizationRequestEndpoint = "$baseUrl/par", - tokenEndpoint = "$baseUrl/token", - credentialEndpoint = "$baseUrl/credential", - batchCredentialEndpoint = "$baseUrl/batch_credential", - deferredCredentialEndpoint = "$baseUrl/credential_deferred", - jwksUri = "$baseUrl/jwks", - grantTypesSupported = setOf(GrantType.authorization_code, GrantType.pre_authorized_code), - requestUriParameterSupported = true, - subjectTypesSupported = setOf(SubjectType.public), - authorizationServer = baseUrl, - credentialIssuer = baseUrl, // (EBSI) this should be just "$baseUrl" https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#section-11.2.1 - responseTypesSupported = setOf( - "code", - "vp_token", - "id_token" - ), // (EBSI) this is required one https://www.rfc-editor.org/rfc/rfc8414.html#section-2 - idTokenSigningAlgValuesSupported = setOf("ES256"), // (EBSI) https://openid.net/specs/openid-connect-self-issued-v2-1_0.html#name-self-issued-openid-provider- - codeChallengeMethodsSupported = listOf("S256") - ) + fun createDefaultProviderMetadata(baseUrl: String, credentialSupported: Map, version: OpenID4VCIVersion) : OpenIDProviderMetadata { + + return when (version) { + OpenID4VCIVersion.D13 -> OpenIDProviderMetadataD13( + issuer = baseUrl, + authorizationEndpoint = "$baseUrl/authorize", + pushedAuthorizationRequestEndpoint = "$baseUrl/par", + tokenEndpoint = "$baseUrl/token", + credentialEndpoint = "$baseUrl/credential", + batchCredentialEndpoint = "$baseUrl/batch_credential", + deferredCredentialEndpoint = "$baseUrl/credential_deferred", + jwksUri = "$baseUrl/jwks", + grantTypesSupported = setOf(GrantType.authorization_code, GrantType.pre_authorized_code), + requestUriParameterSupported = true, + subjectTypesSupported = setOf(SubjectType.public), + authorizationServer = baseUrl, + credentialIssuer = baseUrl, // (EBSI) this should be just "$baseUrl" https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#section-11.2.1 + responseTypesSupported = setOf( + "code", + "vp_token", + "id_token" + ), // (EBSI) this is required one https://www.rfc-editor.org/rfc/rfc8414.html#section-2 + idTokenSigningAlgValuesSupported = setOf("ES256"), // (EBSI) https://openid.net/specs/openid-connect-self-issued-v2-1_0.html#name-self-issued-openid-provider- + codeChallengeMethodsSupported = listOf("S256"), + credentialConfigurationsSupported = credentialSupported + ) + + OpenID4VCIVersion.D10 -> OpenIDProviderMetadataD10( + issuer = baseUrl, + authorizationEndpoint = "$baseUrl/authorize", + pushedAuthorizationRequestEndpoint = "$baseUrl/par", + tokenEndpoint = "$baseUrl/token", + credentialEndpoint = "$baseUrl/credential", + batchCredentialEndpoint = "$baseUrl/batch_credential", + deferredCredentialEndpoint = "$baseUrl/credential_deferred", + jwksUri = "$baseUrl/jwks", + grantTypesSupported = setOf(GrantType.authorization_code, GrantType.pre_authorized_code), + requestUriParameterSupported = true, + subjectTypesSupported = setOf(SubjectType.public), + authorizationServer = baseUrl, + credentialIssuer = baseUrl, // (EBSI) this should be just "$baseUrl" https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#section-11.2.1 + responseTypesSupported = setOf( + "code", + "vp_token", + "id_token" + ), // (EBSI) this is required one https://www.rfc-editor.org/rfc/rfc8414.html#section-2 + idTokenSigningAlgValuesSupported = setOf("ES256"), // (EBSI) https://openid.net/specs/openid-connect-self-issued-v2-1_0.html#name-self-issued-openid-provider- + codeChallengeMethodsSupported = listOf("S256"), + credentialSupported = credentialSupported.mapValues { (_, credential) -> credential.copy(types = credential.credentialDefinition?.type, credentialDefinition = null) } + ) + } + } fun getNonceFromProof(proofOfPossession: ProofOfPossession) = when (proofOfPossession.proofType) { ProofType.jwt -> JwtUtils.parseJWTPayload(proofOfPossession.jwt!!)[JWTClaims.Payload.nonce]?.jsonPrimitive?.content @@ -368,7 +401,11 @@ object OpenID4VCI { "Invalid proof of possession" ) } - val supportedCredentialFormats = openIDProviderMetadata.credentialConfigurationsSupported?.values?.map { it.format }?.toSet() ?: setOf() + val supportedCredentialFormats = when (openIDProviderMetadata) { + is OpenIDProviderMetadataD13 -> openIDProviderMetadata.credentialConfigurationsSupported?.values?.map { it.format }?.toSet() ?: setOf() + is OpenIDProviderMetadataD10 -> openIDProviderMetadata.credentialSupported?.values?.map { it.format }?.toSet() ?: setOf() + } + if (!supportedCredentialFormats.contains(credentialRequest.format)) return CredentialRequestValidationResult( false, @@ -470,3 +507,16 @@ object OpenID4VCI { }} } } + + +enum class OpenID4VCIVersion(val versionString: String) { + D10("d10"), + D13("d13"); + + companion object { + fun from(version: String): OpenID4VCIVersion { + return values().find { it.versionString == version } + ?: throw IllegalArgumentException("Unsupported version: $version. Supported Versions are: Draft13 -> d13 and Draft10 -> d10") + } + } +} \ No newline at end of file diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt index ddcda332d..3657d694a 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt @@ -2,14 +2,12 @@ package id.walt.oid4vc.data import id.walt.oid4vc.* import id.walt.oid4vc.definitions.* -import kotlinx.serialization.EncodeDefault -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.* /** * OpenID Provider metadata object, according to @@ -60,7 +58,80 @@ import kotlinx.serialization.json.jsonObject * @param opTosUri OPTIONAL. URL that the OpenID Provider provides to the person registering the Client to read about OpenID Provider's terms of service. The registration process SHOULD display this URL to the person registering the Client if it is given. */ @Serializable -data class OpenIDProviderMetadata @OptIn(ExperimentalSerializationApi::class) constructor( +sealed class OpenIDProviderMetadata : JsonDataObject() { + override fun toJSON(): JsonObject = Json.encodeToJsonElement(OpenIDProviderMetadataSerializer, this).jsonObject + + companion object : JsonDataObjectFactory() { + override fun fromJSON(jsonObject: JsonObject): OpenIDProviderMetadata = + Json.decodeFromJsonElement(OpenIDProviderMetadataSerializer, jsonObject) + } +} + + @Serializable +data class OpenIDProviderMetadataD10( + @SerialName("issuer") val issuer: String? = null, + @SerialName("authorization_endpoint") val authorizationEndpoint: String? = null, + @SerialName("pushed_authorization_request_endpoint") val pushedAuthorizationRequestEndpoint: String? = null, + @SerialName("token_endpoint") val tokenEndpoint: String? = null, + @SerialName("userinfo_endpoint") val userinfoEndpoint: String? = null, + @SerialName("jwks_uri") val jwksUri: String? = null, + @SerialName("registration_endpoint") val registrationEndpoint: String? = null, + @EncodeDefault @SerialName("scopes_supported") val scopesSupported: Set = setOf("openid"), + @SerialName("response_types_supported") val responseTypesSupported: Set? = null, + @EncodeDefault @SerialName("response_modes_supported") val responseModesSupported: Set = setOf( + ResponseMode.query, + ResponseMode.fragment + ), + @EncodeDefault @SerialName("grant_types_supported") @Serializable(GrantTypeSetSerializer::class) val grantTypesSupported: Set = setOf( + GrantType.authorization_code, + GrantType.pre_authorized_code + ), + @SerialName("acr_values_supported") val acrValuesSupported: Set? = null, + @SerialName("subject_types_supported") val subjectTypesSupported: Set? = null, + @SerialName("id_token_signing_alg_values_supported") val idTokenSigningAlgValuesSupported: Set? = null, + @SerialName("id_token_encryption_alg_values_supported") val idTokenEncryptionAlgValuesSupported: Set? = null, + @SerialName("id_token_encryption_enc_values_supported") val idTokenEncryptionEncValuesSupported: Set? = null, + @SerialName("userinfo_signing_alg_values_supported") val userinfoSigningAlgValuesSupported: Set? = null, + @SerialName("userinfo_encryption_alg_values_supported") val userinfoEncryptionAlgValuesSupported: Set? = null, + @SerialName("userinfo_encryption_enc_values_supported") val userinfoEncryptionEncValuesSupported: Set? = null, + @SerialName("request_object_signing_alg_values_supported") val requestObjectSigningAlgValuesSupported: Set? = null, + @SerialName("request_object_encryption_alg_values_supported") val requestObjectEncryptionAlgValuesSupported: Set? = null, + @SerialName("request_object_encryption_enc_values_supported") val requestObjectEncryptionEncValuesSupported: Set? = null, + @SerialName("token_endpoint_auth_methods_supported") val tokenEndpointAuthMethodsSupported: Set? = null, + @SerialName("token_endpoint_auth_signing_alg_values_supported") val tokenEndpointAuthSigningAlgValuesSupported: Set? = null, + @SerialName("display_values_supported") val displayValuesSupported: Set? = null, + @SerialName("claim_types_supported") val claimTypesSupported: Set? = null, + @SerialName("claims_supported") val claimsSupported: Set? = null, + @SerialName("service_documentation") val serviceDocumentation: String? = null, + @SerialName("claims_locales_supported") val claimsLocalesSupported: Set? = null, + @SerialName("ui_locales_supported") val uiLocalesSupported: Set? = null, + @SerialName("claims_parameter_supported") val claimsParameterSupported: Boolean = false, + @SerialName("request_parameter_supported") val requestParameterSupported: Boolean = false, + @SerialName("request_uri_parameter_supported") val requestUriParameterSupported: Boolean = true, + @SerialName("require_request_uri_registration") val requireRequestUriRegistration: Boolean = false, + @SerialName("op_policy_uri") val opPolicyUri: String? = null, + @SerialName("op_tos_uri") val opTosUri: String? = null, + // OID4VCI properties + @SerialName("credential_issuer") val credentialIssuer: String? = null, + @SerialName("credential_endpoint") val credentialEndpoint: String? = null, + @SerialName("credentials_supported") @Serializable(CredentialSupportedMapSerializer::class) val credentialSupported: Map? = null, + @SerialName("batch_credential_endpoint") val batchCredentialEndpoint: String? = null, + @SerialName("deferred_credential_endpoint") val deferredCredentialEndpoint: String? = null, + @SerialName("authorization_servers") val authorizationServers: Set? = null, + @SerialName("display") @Serializable(DisplayPropertiesListSerializer::class) val display: List? = null, + @SerialName("presentation_definition_uri_supported") val presentationDefinitionUriSupported: Boolean? = null, + //@SerialName("vp_formats_supported") @Serializable(SupportedVPFormatMapSerializer::class) val vpFormatsSupported: Map? = null, + @SerialName("client_id_schemes_supported") val clientIdSchemesSupported: List? = null, + @SerialName("authorization_server") val authorizationServer: String? = authorizationServers?.firstOrNull(), // Move here since if we have a null value for this parameter, the discovery fails + @SerialName("code_challenge_methods_supported") val codeChallengeMethodsSupported: List? = null, + @SerialName("require_pushed_authorization_requests") val requirePushedAuthorizationRequests: Boolean? = null, + @SerialName("dpop_signing_alg_values_supported") val dpopSigningAlgValuesSupported: Set? = null, + override val customParameters: Map = mapOf() +) : OpenIDProviderMetadata() + + +@Serializable +data class OpenIDProviderMetadataD13( @SerialName("issuer") val issuer: String? = null, @SerialName("authorization_endpoint") val authorizationEndpoint: String? = null, @SerialName("pushed_authorization_request_endpoint") val pushedAuthorizationRequestEndpoint: String? = null, @@ -119,14 +190,9 @@ data class OpenIDProviderMetadata @OptIn(ExperimentalSerializationApi::class) co @SerialName("require_pushed_authorization_requests") val requirePushedAuthorizationRequests: Boolean? = null, @SerialName("dpop_signing_alg_values_supported") val dpopSigningAlgValuesSupported: Set? = null, override val customParameters: Map = mapOf() -) : JsonDataObject() { - override fun toJSON(): JsonObject = Json.encodeToJsonElement(OpenIDProviderMetadataSerializer, this).jsonObject - - companion object : JsonDataObjectFactory() { - override fun fromJSON(jsonObject: JsonObject): OpenIDProviderMetadata = - Json.decodeFromJsonElement(OpenIDProviderMetadataSerializer, jsonObject) - } - + ) : OpenIDProviderMetadata() +{ + // TODO: make them abstract in the sealed class fun getVctByCredentialConfigurationId(credentialConfigurationId: String) = credentialConfigurationsSupported?.get(credentialConfigurationId)?.vct fun getVctBySupportedCredentialConfiguration( @@ -145,5 +211,32 @@ data class OpenIDProviderMetadata @OptIn(ExperimentalSerializationApi::class) co } } -object OpenIDProviderMetadataSerializer : - JsonDataObjectSerializer(OpenIDProviderMetadata.serializer()) +object OpenIDProviderMetadataSerializer : KSerializer { + + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("OpenIDProviderMetadata") + + override fun deserialize(decoder: Decoder): OpenIDProviderMetadata { + val jsonDecoder = decoder as? JsonDecoder + ?: throw IllegalStateException("This class can only be deserialized with JSON") + + val jsonObject = jsonDecoder.decodeJsonElement().jsonObject + + return when { + "credentials_supported" in jsonObject -> Json.decodeFromJsonElement(OpenIDProviderMetadataD10.serializer(), jsonObject) + "credential_configurations_supported" in jsonObject -> Json.decodeFromJsonElement(OpenIDProviderMetadataD13.serializer(), jsonObject) + else -> throw IllegalArgumentException("Unknown OpenIDProviderMetadata version: missing expected fields") + } + } + + override fun serialize(encoder: Encoder, value: OpenIDProviderMetadata) { + val jsonEncoder = encoder as? JsonEncoder + ?: throw IllegalStateException("This class can only be serialized with JSON") + + val jsonElement: JsonElement = when (value) { + is OpenIDProviderMetadataD10 -> Json.encodeToJsonElement(OpenIDProviderMetadataD10.serializer(), value) + is OpenIDProviderMetadataD13 -> Json.encodeToJsonElement(OpenIDProviderMetadataD13.serializer(), value) + } + + jsonEncoder.encodeJsonElement(jsonElement) + } +} diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDCredentialWallet.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDCredentialWallet.kt index 231e2c480..81f75b7e2 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDCredentialWallet.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDCredentialWallet.kt @@ -245,7 +245,7 @@ abstract class OpenIDCredentialWallet( ) val issuerMetadataUrl = getCIProviderMetadataUrl(credentialOffer.credentialIssuer) val issuerMetadata = - httpGetAsJson(Url(issuerMetadataUrl))?.jsonObject?.let { OpenIDProviderMetadata.fromJSON(it) } ?: throw CredentialOfferError( + httpGetAsJson(Url(issuerMetadataUrl))?.jsonObject?.let { OpenIDProviderMetadata.fromJSON(it) as OpenIDProviderMetadataD13 } ?: throw CredentialOfferError( null, credentialOffer, CredentialOfferErrorCode.invalid_issuer, @@ -276,15 +276,15 @@ abstract class OpenIDCredentialWallet( ) val issuerMetadataUrl = getCIProviderMetadataUrl(credentialOffer.credentialIssuer) val issuerMetadata = - httpGetAsJson(Url(issuerMetadataUrl))?.jsonObject?.let { OpenIDProviderMetadata.fromJSON(it) } ?: throw CredentialOfferError( + httpGetAsJson(Url(issuerMetadataUrl))?.jsonObject?.let { OpenIDProviderMetadata.fromJSON(it) as OpenIDProviderMetadataD13} ?: throw CredentialOfferError( null, credentialOffer, CredentialOfferErrorCode.invalid_issuer, "Could not resolve issuer provider metadata from $issuerMetadataUrl" ) val authorizationServerMetadata = issuerMetadata.authorizationServer?.let { authServer -> - httpGetAsJson(Url(getCommonProviderMetadataUrl(authServer)))?.jsonObject?.let { OpenIDProviderMetadata.fromJSON(it) } - } ?: issuerMetadata + httpGetAsJson(Url(getCommonProviderMetadataUrl(authServer)))?.jsonObject?.let { OpenIDProviderMetadata.fromJSON(it) as OpenIDProviderMetadataD13} + } ?: issuerMetadata as OpenIDProviderMetadataD13 val offeredCredentials = OpenID4VCI.resolveOfferedCredentials(credentialOffer, issuerMetadata) val codeVerifier = if (client.useCodeChallenge) randomUUID() else null @@ -370,7 +370,7 @@ abstract class OpenIDCredentialWallet( ) val issuerMetadataUrl = getCIProviderMetadataUrl(credentialOffer.credentialIssuer) val issuerMetadata = - httpGetAsJson(Url(issuerMetadataUrl))?.jsonObject?.let { OpenIDProviderMetadata.fromJSON(it) } ?: throw CredentialOfferError( + httpGetAsJson(Url(issuerMetadataUrl))?.jsonObject?.let { OpenIDProviderMetadata.fromJSON(it) as OpenIDProviderMetadataD13 } ?: throw CredentialOfferError( null, credentialOffer, CredentialOfferErrorCode.invalid_issuer, @@ -408,6 +408,9 @@ abstract class OpenIDCredentialWallet( userPIN, codeVerifier ) + authorizationServerMetadata as OpenIDProviderMetadataD13 + issuerMetadata as OpenIDProviderMetadataD13 + val tokenHttpResp = httpSubmitForm(Url(authorizationServerMetadata.tokenEndpoint!!), parametersOf(tokenReq.toHttpParameters())) if (!tokenHttpResp.status.isSuccess() || tokenHttpResp.body == null) throw TokenError( tokenReq, diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDProvider.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDProvider.kt index ce6f71354..9626ea479 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDProvider.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDProvider.kt @@ -3,6 +3,7 @@ package id.walt.oid4vc.providers import id.walt.crypto.keys.Key import id.walt.oid4vc.OpenID4VC import id.walt.oid4vc.OpenID4VCI +import id.walt.oid4vc.OpenID4VCIVersion import id.walt.oid4vc.data.* import id.walt.oid4vc.data.ResponseType.Companion.getResponseTypeString import id.walt.oid4vc.data.dif.PresentationDefinition @@ -30,10 +31,10 @@ import kotlin.uuid.Uuid abstract class OpenIDProvider( val baseUrl: String, ) : ISessionCache, ITokenProvider { - abstract val metadata: OpenIDProviderMetadata + abstract val metadata: OpenIDProviderMetadataD13 abstract val config: OpenIDProviderConfig - protected open fun createDefaultProviderMetadata() = OpenID4VCI.createDefaultProviderMetadata(baseUrl) + protected open fun createDefaultProviderMetadata() = OpenID4VCI.createDefaultProviderMetadata(baseUrl, emptyMap(), OpenID4VCIVersion.D13) fun getCommonProviderMetadataUrl(): String { return URLBuilder(baseUrl).apply { diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/CI_JVM_Test.kt b/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/CI_JVM_Test.kt index d341c740e..d9becf42e 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/CI_JVM_Test.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/CI_JVM_Test.kt @@ -5,7 +5,6 @@ import com.nimbusds.jose.crypto.ECDSASigner import com.nimbusds.jose.crypto.ECDSAVerifier import com.nimbusds.jose.crypto.bc.BouncyCastleProviderSingleton import com.nimbusds.jose.jwk.ECKey -import id.walt.policies.policies.JwtSignaturePolicy import id.walt.crypto.keys.KeyType import id.walt.crypto.keys.jwk.JWKKey import id.walt.did.dids.DidService @@ -14,15 +13,11 @@ import id.walt.did.dids.registrar.dids.DidJwkCreateOptions import id.walt.did.dids.registrar.dids.DidKeyCreateOptions import id.walt.did.dids.registrar.dids.DidWebCreateOptions import id.walt.oid4vc.data.* -import id.walt.oid4vc.data.dif.PresentationDefinition -import id.walt.oid4vc.definitions.JWTClaims import id.walt.oid4vc.definitions.OPENID_CREDENTIAL_AUTHORIZATION_TYPE import id.walt.oid4vc.providers.CredentialWalletConfig import id.walt.oid4vc.providers.OpenIDClientConfig -import id.walt.oid4vc.providers.TokenTarget import id.walt.oid4vc.requests.* import id.walt.oid4vc.responses.* -import id.walt.oid4vc.util.JwtUtils import id.walt.sdjwt.SDJwt import id.walt.sdjwt.SDMap import id.walt.sdjwt.SDPayload @@ -42,18 +37,15 @@ import io.ktor.server.netty.* import io.ktor.server.request.* import io.ktor.server.routing.* import io.ktor.util.* -import io.ktor.util.reflect.* import kotlinx.coroutines.test.runTest -import kotlinx.datetime.Clock import kotlinx.serialization.json.* import org.junit.jupiter.api.BeforeAll import java.io.File import kotlin.test.* -import kotlin.time.Duration.Companion.minutes class CI_JVM_Test { - var testMetadata = OpenIDProviderMetadata( + var testMetadata = OpenIDProviderMetadataD13( authorizationEndpoint = "https://localhost/oidc", credentialConfigurationsSupported = mapOf( "UniversityDegreeCredential_jwt_vc_json" to CredentialSupported( @@ -256,7 +248,7 @@ class CI_JVM_Test { println("// get issuer metadata") val providerMetadataUri = credentialWallet.getCIProviderMetadataUrl(credOfferReq.credentialOffer!!.credentialIssuer) - val providerMetadata = ktorClient.get(providerMetadataUri).call.body() + val providerMetadata = ktorClient.get(providerMetadataUri).call.body() as OpenIDProviderMetadataD13 println("providerMetadata: $providerMetadata") assertNotNull(actual = providerMetadata.authorizationEndpoint) println("// resolve offered credentials") @@ -311,7 +303,7 @@ class CI_JVM_Test { val providerMetadataUri = credentialWallet.getCIProviderMetadataUrl(credOfferReq.credentialOffer!!.credentialIssuer) val providerMetadata = - ktorClient.get(providerMetadataUri).call.body().let { OpenIDProviderMetadata.fromJSON(it) } + ktorClient.get(providerMetadataUri).call.body().let { OpenIDProviderMetadata.fromJSON(it) } as OpenIDProviderMetadataD13 println("providerMetadata: $providerMetadata") assertNotNull(actual = providerMetadata.authorizationEndpoint) println("// resolve offered credentials") @@ -367,7 +359,7 @@ class CI_JVM_Test { val providerMetadataUri = credentialWallet.getCIProviderMetadataUrl(credOfferReq.credentialOffer!!.credentialIssuer) val providerMetadata = - ktorClient.get(providerMetadataUri).call.body().let { OpenIDProviderMetadata.fromJSON(it) } + ktorClient.get(providerMetadataUri).call.body().let { OpenIDProviderMetadata.fromJSON(it) } as OpenIDProviderMetadataD13 println("providerMetadata: $providerMetadata") assertNotNull(actual = providerMetadata.tokenEndpoint) assertNotNull(actual = providerMetadata.credentialEndpoint) @@ -693,7 +685,7 @@ fun testIsolatedFunctionsCreateCredentialOffer(baseUrl: String, issuerState: Str suspend fun testIsolatedFunctionsResolveCredentialOffer(credOfferUrl: String): OfferedCredential { val parsedCredOffer = OpenID4VCI.parseAndResolveCredentialOfferRequestUrl(credOfferUrl) - val providerMetadata = OpenID4VCI.resolveCIProviderMetadata(parsedCredOffer) + val providerMetadata = OpenID4VCI.resolveCIProviderMetadata(parsedCredOffer) as OpenIDProviderMetadataD13 assertEquals(expected = parsedCredOffer.credentialIssuer, actual = providerMetadata.credentialIssuer) println("// resolve offered credentials") diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/EBSITestWallet.kt b/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/EBSITestWallet.kt index aff83738d..75b315867 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/EBSITestWallet.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/EBSITestWallet.kt @@ -13,7 +13,7 @@ import id.walt.crypto.keys.jwk.JWKKey import id.walt.crypto.utils.JwsUtils.decodeJws import id.walt.did.dids.DidService import id.walt.mdoc.dataelement.MapElement -import id.walt.oid4vc.data.OpenIDProviderMetadata +import id.walt.oid4vc.data.OpenIDProviderMetadataD13 import id.walt.oid4vc.data.dif.DescriptorMapping import id.walt.oid4vc.data.dif.PresentationDefinition import id.walt.oid4vc.data.dif.PresentationSubmission @@ -90,8 +90,8 @@ class EBSITestWallet( expirationTimestamp: Instant, ) = SIOPSession(id, authorizationRequest, expirationTimestamp) - override val metadata: OpenIDProviderMetadata - get() = createDefaultProviderMetadata() + override val metadata + get() = createDefaultProviderMetadata() as OpenIDProviderMetadataD13 override fun getSession(id: String): SIOPSession? = sessionCache[id] override fun getSessionByAuthServerState(authServerState: String): SIOPSession? { diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/OpenID4VCI_Test.kt b/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/OpenID4VCI_Test.kt index d86d42159..7c2cdfc6d 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/OpenID4VCI_Test.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/OpenID4VCI_Test.kt @@ -33,7 +33,7 @@ import kotlin.test.* class OpenID4VCI_Test { val ISSUER_BASE_URL = "https://test" val CREDENTIAL_OFFER_BASE_URL = "openid-credential-offer://test" - val ISSUER_METADATA = OpenID4VCI.createDefaultProviderMetadata(ISSUER_BASE_URL).copy( + val ISSUER_METADATA = (OpenID4VCI.createDefaultProviderMetadata(ISSUER_BASE_URL, emptyMap(), OpenID4VCIVersion.D13) as OpenIDProviderMetadataD13).copy( credentialConfigurationsSupported = mapOf( "VerifiableId" to CredentialSupported( CredentialFormat.jwt_vc_json, diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/TestCredentialWallet.kt b/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/TestCredentialWallet.kt index 8e43698c5..523fd4d34 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/TestCredentialWallet.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/TestCredentialWallet.kt @@ -12,7 +12,7 @@ import id.walt.crypto.keys.jwk.JWKKey import id.walt.crypto.utils.JwsUtils.decodeJws import id.walt.did.dids.DidService import id.walt.mdoc.dataelement.MapElement -import id.walt.oid4vc.data.OpenIDProviderMetadata +import id.walt.oid4vc.data.OpenIDProviderMetadataD13 import id.walt.oid4vc.data.ResponseMode import id.walt.oid4vc.data.ResponseType import id.walt.oid4vc.data.dif.DescriptorMapping @@ -233,8 +233,8 @@ class TestCredentialWallet( return true } - override val metadata: OpenIDProviderMetadata - get() = createDefaultProviderMetadata() + override val metadata + get() = createDefaultProviderMetadata() as OpenIDProviderMetadataD13 override fun getSession(id: String) = sessionCache[id] override fun getSessionByAuthServerState(authServerState: String): SIOPSession? { diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/wallettest.kt b/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/wallettest.kt index 138dc97ba..2ff75e1a2 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/wallettest.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/wallettest.kt @@ -106,7 +106,7 @@ class wallettest { println("// get issuer metadata") val providerMetadataUri = credentialWallet.getCIProviderMetadataUrl(parsedOfferReq.credentialOffer!!.credentialIssuer) - val providerMetadata = ktorClient.get(providerMetadataUri).call.body() + val providerMetadata = ktorClient.get(providerMetadataUri).call.body() as OpenIDProviderMetadataD13 println("providerMetadata: $providerMetadata") assertNotNull(actual = providerMetadata.credentialConfigurationsSupported) diff --git a/waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialIssuance.kt b/waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialIssuance.kt index 56eb7929a..ad35b7e3e 100644 --- a/waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialIssuance.kt +++ b/waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialIssuance.kt @@ -67,8 +67,8 @@ class LspPotentialIssuance(val client: HttpClient) { // ### get issuer metadata, steps 7-10 val providerMetadataUri = OpenID4VCI.getCIProviderMetadataUrl(parsedOffer.credentialIssuer) val oauthMetadataUri = OpenID4VCI.getOAuthProviderMetadataUrl(parsedOffer.credentialIssuer) - val providerMetadata = client.get(providerMetadataUri).bodyAsText().let { OpenIDProviderMetadata.fromJSONString(it) } - val oauthMetadata = client.get(oauthMetadataUri).body() + val providerMetadata = client.get(providerMetadataUri).bodyAsText().let { OpenIDProviderMetadata.fromJSONString(it) } as OpenIDProviderMetadataD13 + val oauthMetadata = client.get(oauthMetadataUri).body() assertNotNull(providerMetadata.credentialConfigurationsSupported) assertNotNull(providerMetadata.credentialEndpoint) assertNotNull(oauthMetadata.authorizationEndpoint) @@ -250,9 +250,9 @@ class LspPotentialIssuance(val client: HttpClient) { val providerMetadataUri = OpenID4VCI.getCIProviderMetadataUrl(parsedOffer.credentialIssuer) val jwtIssuerMetadataUri = OpenID4VCI.getJWTIssuerProviderMetadataUrl(parsedOffer.credentialIssuer) val oAuthMetadataUri = OpenID4VCI.getOAuthProviderMetadataUrl(parsedOffer.credentialIssuer) - val providerMetadata = client.get(providerMetadataUri).bodyAsText().let { OpenIDProviderMetadata.fromJSONString(it) } - val oauthMetadata = client.get(oAuthMetadataUri).body() - val jwtIssuerMetadata = client.get(jwtIssuerMetadataUri).body() + val providerMetadata = client.get(providerMetadataUri).bodyAsText().let { OpenIDProviderMetadata.fromJSONString(it) } as OpenIDProviderMetadataD13 + val oauthMetadata = client.get(oAuthMetadataUri).body() + val jwtIssuerMetadata = client.get(jwtIssuerMetadataUri).body() assertNotNull(providerMetadata.credentialConfigurationsSupported) assertNotNull(providerMetadata.credentialEndpoint) assertNotNull(jwtIssuerMetadata.issuer) diff --git a/waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialWallet.kt b/waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialWallet.kt index b6fee4e33..5800b12c6 100644 --- a/waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialWallet.kt +++ b/waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialWallet.kt @@ -76,7 +76,7 @@ class LspPotentialWallet(val client: HttpClient, val walletId: String) { // === resolve issuer metadata === val issuerMetadata = client.get("${resolvedOffer.credentialIssuer}/.well-known/openid-credential-issuer").expectSuccess() - .body() + .body() assertEquals(issuerMetadata.issuer, resolvedOffer.credentialIssuer) assertContains( issuerMetadata.credentialConfigurationsSupported!!.keys, @@ -222,7 +222,7 @@ class LspPotentialWallet(val client: HttpClient, val walletId: String) { // === resolve issuer metadata === val issuerMetadata = client.get("${resolvedOffer.credentialIssuer}/.well-known/openid-credential-issuer").expectSuccess() - .body() + .body() assertEquals(issuerMetadata.issuer, resolvedOffer.credentialIssuer) assertContains( issuerMetadata.credentialConfigurationsSupported!!.keys, diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/NewIssuanceStub.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/NewIssuanceStub.kt index 3a71086b7..b9c8dd6ac 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/NewIssuanceStub.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/NewIssuanceStub.kt @@ -114,7 +114,7 @@ suspend fun main() { check(credOffer.toJSONString() == parsedCredOffer.toJSONString()) println("Resolve metadata from ${parsedCredOffer.credentialIssuer}") - val providerMetadata = runBlocking { OpenID4VCI.resolveCIProviderMetadata(parsedCredOffer) } + val providerMetadata = runBlocking { OpenID4VCI.resolveCIProviderMetadata(parsedCredOffer) as OpenIDProviderMetadataD13 } check(parsedCredOffer.credentialIssuer == providerMetadata.credentialIssuer) diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/NewTestOidcMetadata.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/NewTestOidcMetadata.kt index 8e16fcaa1..f69754246 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/NewTestOidcMetadata.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/NewTestOidcMetadata.kt @@ -29,7 +29,7 @@ fun main() { get("/.well-known/openid-credential-issuer") { - val providerMetadata = OpenIDProviderMetadata( + val providerMetadata = OpenIDProviderMetadataD13( issuer = url, authorizationEndpoint = "$url/authorize", pushedAuthorizationRequestEndpoint = "$url/par", diff --git a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/CredentialOfferProcessor.kt b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/CredentialOfferProcessor.kt index 35ac55a4d..04fe634e3 100644 --- a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/CredentialOfferProcessor.kt +++ b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/CredentialOfferProcessor.kt @@ -1,6 +1,7 @@ package id.walt.webwallet.service.exchange import id.walt.oid4vc.data.OpenIDProviderMetadata +import id.walt.oid4vc.data.OpenIDProviderMetadataD13 import id.walt.oid4vc.requests.BatchCredentialRequest import id.walt.oid4vc.requests.CredentialRequest import id.walt.oid4vc.responses.BatchCredentialResponse @@ -29,6 +30,9 @@ object CredentialOfferProcessor { providerMetadata: OpenIDProviderMetadata, accessToken: String, ): List { + + providerMetadata as OpenIDProviderMetadataD13 + val batchCredentialRequest = BatchCredentialRequest(credReqs) val batchResponse = http.post(providerMetadata.batchCredentialEndpoint!!) { @@ -52,6 +56,8 @@ object CredentialOfferProcessor { providerMetadata: OpenIDProviderMetadata, accessToken: String, ): List { + providerMetadata as OpenIDProviderMetadataD13 + val credReq = credReqs.first() val credentialResponse = http.post(providerMetadata.credentialEndpoint!!) { diff --git a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/IssuanceService.kt b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/IssuanceService.kt index a34144138..0d7c0378f 100644 --- a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/IssuanceService.kt +++ b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/IssuanceService.kt @@ -12,12 +12,7 @@ import io.klogging.logger import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* -import io.ktor.util.* -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.Serializable -import kotlinx.serialization.decodeFromByteArray import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive @@ -70,7 +65,8 @@ object IssuanceService: IssuanceServiceBase() { val providerMetadata = getCredentialIssuerOpenIDMetadata( credentialOffer.credentialIssuer, credentialWallet, - ) + ) as OpenIDProviderMetadataD13 + logger.debug { "providerMetadata: $providerMetadata" } logger.debug { "// resolve offered credentials" } diff --git a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/IssuanceServiceExternalSignatures.kt b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/IssuanceServiceExternalSignatures.kt index a0d0e87b2..4ba6c9716 100644 --- a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/IssuanceServiceExternalSignatures.kt +++ b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/IssuanceServiceExternalSignatures.kt @@ -53,7 +53,8 @@ object IssuanceServiceExternalSignatures : IssuanceServiceBase() { val providerMetadata = getCredentialIssuerOpenIDMetadata( credentialOffer.credentialIssuer, credentialWallet, - ) + ) as OpenIDProviderMetadataD13 + logger.debug { "providerMetadata: $providerMetadata" } logger.debug { "// resolve offered credentials" } diff --git a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/oidc4vc/TestCredentialWallet.kt b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/oidc4vc/TestCredentialWallet.kt index f36881652..05599078f 100644 --- a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/oidc4vc/TestCredentialWallet.kt +++ b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/oidc4vc/TestCredentialWallet.kt @@ -26,7 +26,7 @@ import id.walt.mdoc.docrequest.MDocRequestBuilder import id.walt.mdoc.mdocauth.DeviceAuthentication import id.walt.oid4vc.OpenID4VP import id.walt.oid4vc.data.CredentialFormat -import id.walt.oid4vc.data.OpenIDProviderMetadata +import id.walt.oid4vc.data.OpenIDProviderMetadataD13 import id.walt.oid4vc.data.dif.DescriptorMapping import id.walt.oid4vc.data.dif.PresentationDefinition import id.walt.oid4vc.data.dif.PresentationSubmission @@ -332,8 +332,8 @@ class TestCredentialWallet( return true } - override val metadata: OpenIDProviderMetadata - get() = createDefaultProviderMetadata() + override val metadata + get() = createDefaultProviderMetadata() as OpenIDProviderMetadataD13 override fun createSIOPSession( id: String, From 19650b1a80105e0f9c4107b8b55f97e547a57088 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Tue, 12 Nov 2024 18:15:13 +0200 Subject: [PATCH 02/53] fix: authorize endpoint error in test cases --- .../src/test/kotlin/AuthorizationCodeFlow.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/waltid-services/waltid-e2e-tests/src/test/kotlin/AuthorizationCodeFlow.kt b/waltid-services/waltid-e2e-tests/src/test/kotlin/AuthorizationCodeFlow.kt index 9b2be97ff..afeeda8b9 100644 --- a/waltid-services/waltid-e2e-tests/src/test/kotlin/AuthorizationCodeFlow.kt +++ b/waltid-services/waltid-e2e-tests/src/test/kotlin/AuthorizationCodeFlow.kt @@ -23,6 +23,7 @@ class AuthorizationCodeFlow(private val client: HttpClient) { lateinit var offerUrl: String lateinit var issuerState: String val issuerApi = IssuerApi(client) + val authorizeEndpoint = "d13/authorize" // // Issue credential with Authorized Code Flow and Id Token request @@ -63,7 +64,7 @@ class AuthorizationCodeFlow(private val client: HttpClient) { ) ) - client.get("/authorize?${authorizationRequest.toHttpQueryString()}") {} + client.get("$authorizeEndpoint?${authorizationRequest.toHttpQueryString()}") {} .expectRedirect().apply { val idTokenRequest = AuthorizationRequest.fromHttpQueryString(headers["location"]!!) assert(idTokenRequest.responseType == setOf(ResponseType.IdToken)) { "response type should be id_token" } @@ -97,7 +98,7 @@ class AuthorizationCodeFlow(private val client: HttpClient) { ) ) - client.get("/authorize?${authorizationRequest.toHttpQueryString()}") { + client.get("$authorizeEndpoint?${authorizationRequest.toHttpQueryString()}") { }.expectRedirect().apply { val vpTokenRequest = AuthorizationRequest.fromHttpQueryString(headers["location"]!!) assert(vpTokenRequest.responseType == setOf(ResponseType.VpToken)) { "response type should be vp_token" } @@ -132,7 +133,7 @@ class AuthorizationCodeFlow(private val client: HttpClient) { ) ) - client.get("/authorize?${authorizationRequest.toHttpQueryString()}") { + client.get("$authorizeEndpoint?${authorizationRequest.toHttpQueryString()}") { }.expectRedirect().apply { assertEquals(true, headers["location"]!!.toString().contains("external_login")) } From 9d8ffc866b78ddf64ac805fb428cfbd20f05d3a6 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Tue, 12 Nov 2024 18:16:46 +0200 Subject: [PATCH 03/53] fix: add missing values in CredentialSupported --- .../kotlin/id/walt/oid4vc/data/CredentialSupported.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/CredentialSupported.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/CredentialSupported.kt index 7a44ae8ae..80e6274e7 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/CredentialSupported.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/CredentialSupported.kt @@ -70,12 +70,14 @@ data class CredentialSupported( val scope: String? = null, @SerialName("vct") val vct: String? = null, @SerialName("cryptographic_binding_methods_supported") val cryptographicBindingMethodsSupported: Set? = null, + @SerialName("id") val id: String? = null, // for draft 10 + @SerialName("cryptographic_suites_supported") val cryptographicSuitesSupported: Set? = null, // for draft 10 + val types: List? = null, // for draft 10 @SerialName("credential_signing_alg_values_supported") val credentialSigningAlgValuesSupported: Set? = null, @SerialName("proof_types_supported") val proofTypesSupported: Map? = null, @Serializable(DisplayPropertiesListSerializer::class) val display: List? = null, @SerialName("@context") val context: List? = null, @SerialName("credential_definition") val credentialDefinition: CredentialDefinition? = null, - val types: List? = null, // for draft 11 @SerialName("doctype") val docType: String? = null, @Serializable(ClaimDescriptorMapSerializer::class) val credentialSubject: Map? = null, @Serializable(ClaimDescriptorNamespacedMapSerializer::class) val claims: Map>? = null, From 52ebbf5a186dbad9948d415458786dbd4aa5c5cd Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Tue, 12 Nov 2024 18:21:33 +0200 Subject: [PATCH 04/53] refactor: update issuer api metadata and endpoints to support d10 and d13 --- .../id/walt/issuer/issuance/CIProvider.kt | 13 ++++--- .../id/walt/issuer/issuance/IssuerApi.kt | 1 - .../kotlin/id/walt/issuer/issuance/OidcApi.kt | 36 ++++++++++++------- 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt index 05bfb08aa..35f4dc58f 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt @@ -28,6 +28,7 @@ import id.walt.mdoc.mso.DeviceKeyInfo import id.walt.mdoc.mso.ValidityInfo import id.walt.oid4vc.OpenID4VC import id.walt.oid4vc.OpenID4VCI +import id.walt.oid4vc.OpenID4VCIVersion import id.walt.oid4vc.data.* import id.walt.oid4vc.definitions.JWTClaims import id.walt.oid4vc.definitions.OPENID_CREDENTIAL_AUTHORIZATION_TYPE @@ -64,14 +65,18 @@ import kotlin.uuid.ExperimentalUuidApi */ @OptIn(ExperimentalUuidApi::class) open class CIProvider( - val baseUrl: String = let { ConfigManager.getConfig().baseUrl }, + val baseUrl: String = let { ConfigManager.getConfig().baseUrl + "/${OpenID4VCIVersion.D13.versionString}"}, + val baseUrlD10: String = let { ConfigManager.getConfig().baseUrl + "/${OpenID4VCIVersion.D10.versionString}"}, + val config: CredentialIssuerConfig = CredentialIssuerConfig(credentialConfigurationsSupported = ConfigManager.getConfig().parse()) ) { private val log = KotlinLogging.logger { } + val metadata - get() = OpenID4VCI.createDefaultProviderMetadata(baseUrl).copy( - credentialConfigurationsSupported = config.credentialConfigurationsSupported - ) + get() = (OpenID4VCI.createDefaultProviderMetadata(baseUrl, config.credentialConfigurationsSupported, OpenID4VCIVersion.D13) as OpenIDProviderMetadataD13) + + val metadataD10 + get() = (OpenID4VCI.createDefaultProviderMetadata(baseUrlD10, config.credentialConfigurationsSupported, OpenID4VCIVersion.D10) as OpenIDProviderMetadataD10) companion object { diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuerApi.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuerApi.kt index ce6891754..6e54d99c4 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuerApi.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuerApi.kt @@ -12,7 +12,6 @@ import id.walt.oid4vc.definitions.CROSS_DEVICE_CREDENTIAL_OFFER_URL import id.walt.oid4vc.requests.CredentialOfferRequest import io.github.oshai.kotlinlogging.KotlinLogging import io.github.smiley4.ktorswaggerui.dsl.routes.OpenApiRequest -import io.github.smiley4.ktorswaggerui.dsl.routing.get import io.github.smiley4.ktorswaggerui.dsl.routing.post import io.github.smiley4.ktorswaggerui.dsl.routing.route import io.ktor.http.* diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt index 8014e4f00..7ec8d28ee 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt @@ -4,6 +4,7 @@ package id.walt.issuer.issuance import id.walt.policies.Verifier import id.walt.policies.models.PolicyRequest.Companion.parsePolicyRequests import id.walt.oid4vc.OpenID4VC +import id.walt.oid4vc.OpenID4VCIVersion import id.walt.oid4vc.data.* import id.walt.oid4vc.data.dif.PresentationDefinition import id.walt.oid4vc.data.dif.PresentationSubmission @@ -25,6 +26,7 @@ import io.github.smiley4.ktorswaggerui.dsl.routing.route import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.auth.* +import io.ktor.server.plugins.* import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* @@ -63,21 +65,29 @@ object OidcApi : CIProvider() { route("", { tags = listOf("oidc") }) { - get("/.well-known/openid-configuration") { + get("{standardVersion}/.well-known/openid-configuration") { + val standardVersion = call.parameters["standardVersion"] ?: throw IllegalArgumentException("standardVersion parameter is required") + val version = OpenID4VCIVersion.from(standardVersion) + + val metadata = when (version) { + OpenID4VCIVersion.D10 -> metadataD10 + OpenID4VCIVersion.D13 -> metadata + } + call.respond(metadata.toJSON()) } - get("/.well-known/openid-credential-issuer") { + get("{standardVersion}/.well-known/openid-credential-issuer") { call.respond(metadata.toJSON()) } - get("/.well-known/oauth-authorization-server") { + get("{standardVersion}/.well-known/oauth-authorization-server") { call.respond(metadata.toJSON()) } - get("/.well-known/jwt-vc-issuer") { + get("{standardVersion}/.well-known/jwt-vc-issuer") { call.respond(HttpStatusCode.OK, JWTVCIssuerMetadata(issuer = metadata.issuer, jwksUri = metadata.jwksUri)) } - get("/.well-known/vct/{type}") { + get("{standardVersion}/.well-known/vct/{type}") { val credType = call.parameters["type"] ?: throw IllegalArgumentException("Type required") // issuer api is the @@ -96,7 +106,7 @@ object OidcApi : CIProvider() { tags = listOf("oidc") }) { - post("/par") { + post("{standardVersion}/par") { val authReq = AuthorizationRequest.fromHttpParameters(call.receiveParameters().toMap()) try { val session = initializeIssuanceSession(authReq, 5.minutes, null) @@ -111,11 +121,11 @@ object OidcApi : CIProvider() { } } - get("/jwks") { + get("{standardVersion}/jwks") { call.respond(HttpStatusCode.OK, getJwksSessions()) } - get("/authorize") { + get("{standardVersion}/authorize") { val authReq = runBlocking { AuthorizationRequest.fromHttpParametersAuto(call.parameters.toMap()) } try { val issuanceSession = authReq.issuerState?.let { getSession(it) } ?: error("No issuance session found for given issuer state, or issuer state was empty: ${authReq.issuerState}") @@ -241,7 +251,7 @@ object OidcApi : CIProvider() { } } - post("/direct_post") { + post("{standardVersion}/direct_post") { val params = call.receiveParameters().toMap() logger.info { "/direct_post params: $params" } @@ -296,7 +306,7 @@ object OidcApi : CIProvider() { } } - post("/token") { + post("{standardVersion}/token") { val params = call.receiveParameters().toMap() logger.info { "/token params: $params" } @@ -330,7 +340,8 @@ object OidcApi : CIProvider() { } } } - post("/credential_deferred") { + + post("{standardVersion}/credential_deferred") { val accessToken = call.request.header(HttpHeaders.Authorization)?.substringAfter(" ") if (accessToken.isNullOrEmpty() || !OpenID4VC.verifyTokenSignature( TokenTarget.DEFERRED_CREDENTIAL, @@ -347,7 +358,8 @@ object OidcApi : CIProvider() { } } } - post("/batch_credential") { + + post("{standardVersion}/batch_credential") { val accessToken = call.request.header(HttpHeaders.Authorization)?.substringAfter(" ") val parsedToken = accessToken?.let { OpenID4VC.verifyAndParseToken(it, metadata.issuer!!, TokenTarget.ACCESS, CI_TOKEN_KEY) } if (parsedToken == null) { From 5337c888015433c142632fed7f7e572ab9f32156 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Tue, 12 Nov 2024 18:23:06 +0200 Subject: [PATCH 05/53] refactor: move credentialOffer endpoint to OidcApi --- .../id/walt/issuer/issuance/IssuerApi.kt | 22 +---------------- .../kotlin/id/walt/issuer/issuance/OidcApi.kt | 24 ++++++++++++++++++- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuerApi.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuerApi.kt index 6e54d99c4..398eebc44 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuerApi.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuerApi.kt @@ -54,7 +54,7 @@ suspend fun createCredentialOfferUri( logger.debug { "issuanceSession: $issuanceSession" } val offerRequest = - CredentialOfferRequest(null, "${OidcApi.baseUrl}/openid4vc/credentialOffer?id=${issuanceSession.id}") + CredentialOfferRequest(null, "${OidcApi.baseUrl}/credentialOffer?id=${issuanceSession.id}") logger.debug { "offerRequest: $offerRequest" } val offerUri = OpenID4VCI.getCredentialOfferRequestUrl(offerRequest, @@ -502,26 +502,6 @@ fun Application.issuerApi() { } } - get("credentialOffer", { - summary = "Gets a credential offer based on the session id" - request { - queryParameter("id") { required = true } - } - }) { - val sessionId = call.parameters["id"] ?: throw BadRequestException("Missing parameter \"id\"") - val issuanceSession = OidcApi.getSession(sessionId) - ?: throw NotFoundException("No active issuance session found by the given id") - val credentialOffer = issuanceSession.credentialOffer - ?: throw BadRequestException("Session has no credential offer set") - - issuanceSession.callbackUrl?.let { - CIProvider.sendCallback( - sessionId, "resolved_credential_offer", credentialOffer.toJSON(), it - ) - } - - context.respond(credentialOffer.toJSON()) - } } } } diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt index 7ec8d28ee..1eb6089f5 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt @@ -323,7 +323,29 @@ object OidcApi : CIProvider() { call.respond(HttpStatusCode.BadRequest, exc.toAuthorizationErrorResponse().toJSON()) } } - post("/credential") { + + get("{standardVersion}/credentialOffer", { + summary = "Gets a credential offer based on the session id" + request { + queryParameter("id") { required = true } + } + }) { + val sessionId = call.parameters["id"] ?: throw BadRequestException("Missing parameter \"id\"") + val issuanceSession = getSession(sessionId) + ?: throw NotFoundException("No active issuance session found by the given id") + val credentialOffer = issuanceSession.credentialOffer + ?: throw BadRequestException("Session has no credential offer set") + + issuanceSession.callbackUrl?.let { + sendCallback( + sessionId, "resolved_credential_offer", credentialOffer.toJSON(), it + ) + } + + context.respond(credentialOffer.toJSON()) + } + + post("{standardVersion}/credential") { val accessToken = call.request.header(HttpHeaders.Authorization)?.substringAfter(" ") val parsedToken = accessToken?.let { OpenID4VC.verifyAndParseToken(it, metadata.issuer!!, TokenTarget.ACCESS, CI_TOKEN_KEY) } if (parsedToken == null) { From aa95f696ae6c136307842326ffa32ee32a8e5dc1 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Tue, 12 Nov 2024 18:25:16 +0200 Subject: [PATCH 06/53] fix: fix unnecessary non-null assertions --- .../main/kotlin/id/walt/issuer/issuance/OidcApi.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt index 1eb6089f5..62683eb0a 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt @@ -434,11 +434,11 @@ object OidcApi : CIProvider() { val session = getSessionByAuthServerState(call.request.rawQueryParameters.toMap()["state"]!![0]) val authResp = OpenID4VC.processCodeFlowAuthorization(session?.authorizationRequest!!, session.id, metadata, CI_TOKEN_KEY) - val redirectUri = when (session.authorizationRequest!!.isReferenceToPAR) { - true -> getPushedAuthorizationSession(session.authorizationRequest!!).authorizationRequest?.redirectUri - false -> session.authorizationRequest!!.redirectUri + val redirectUri = when (session.authorizationRequest.isReferenceToPAR) { + true -> getPushedAuthorizationSession(session.authorizationRequest).authorizationRequest?.redirectUri + false -> session.authorizationRequest.redirectUri } ?: throw AuthorizationError( - session.authorizationRequest!!, + session.authorizationRequest, AuthorizationErrorCode.invalid_request, "No redirect_uri found for this authorization request" ) @@ -448,12 +448,12 @@ object OidcApi : CIProvider() { call.response.apply { status(HttpStatusCode.Found) val defaultResponseMode = - if (session.authorizationRequest!!.responseType.contains(ResponseType.Code)) ResponseMode.query else ResponseMode.fragment + if (session.authorizationRequest.responseType.contains(ResponseType.Code)) ResponseMode.query else ResponseMode.fragment header( HttpHeaders.Location, authResp.toRedirectUri( redirectUri, - session.authorizationRequest!!.responseMode ?: defaultResponseMode + session.authorizationRequest.responseMode ?: defaultResponseMode ) ) } From cc3c86a4f641fb5e932d11b0b2045339d5dc53f4 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Tue, 12 Nov 2024 18:37:47 +0200 Subject: [PATCH 07/53] fix: fix error in credential offer endpoint in test cases --- .../src/test/kotlin/id/walt/LocalIssuerApiTest.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/waltid-services/waltid-issuer-api/src/test/kotlin/id/walt/LocalIssuerApiTest.kt b/waltid-services/waltid-issuer-api/src/test/kotlin/id/walt/LocalIssuerApiTest.kt index 1f9b5b870..564b6ffb3 100644 --- a/waltid-services/waltid-issuer-api/src/test/kotlin/id/walt/LocalIssuerApiTest.kt +++ b/waltid-services/waltid-issuer-api/src/test/kotlin/id/walt/LocalIssuerApiTest.kt @@ -144,7 +144,7 @@ companion object { ConfigManager.testWithConfigs(testConfigs) val offerUri = createCredentialOfferUri(listOf(issueRequest), CredentialFormat.jwt_vc_json) - assertEquals(true, offerUri.contains("//localhost:7002/?credential_offer")) + assertEquals(true, offerUri.contains("//localhost:7002/d13/?credential_offer")) } @@ -167,7 +167,7 @@ companion object { ConfigManager.testWithConfigs(testConfigs) val offerUri = createCredentialOfferUri(listOf(issueRequest), CredentialFormat.jwt_vc_json) - assertEquals(true, offerUri.contains("//localhost:7002/?credential_offer")) + assertEquals(true, offerUri.contains("//localhost:7002/d13/?credential_offer")) } @Test @@ -213,7 +213,7 @@ companion object { ConfigManager.loadConfigs(emptyArray()) val offerUri = createCredentialOfferUri(issuanceRequests, CredentialFormat.jwt_vc_json) - assertEquals(true, offerUri.contains("//localhost:7002/?credential_offer")) + assertEquals(true, offerUri.contains("//localhost:7002/d13/?credential_offer")) } From d80bf0da0ed3772bdad66f9314b786f7c4676a97 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Wed, 13 Nov 2024 12:31:27 +0200 Subject: [PATCH 08/53] fix: initial addition of metadata in well-known endpoints --- .../kotlin/id/walt/issuer/issuance/OidcApi.kt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt index 62683eb0a..4c7e782f1 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt @@ -76,10 +76,28 @@ object OidcApi : CIProvider() { call.respond(metadata.toJSON()) } + get("{standardVersion}/.well-known/openid-credential-issuer") { + val standardVersion = call.parameters["standardVersion"] ?: throw IllegalArgumentException("standardVersion parameter is required") + val version = OpenID4VCIVersion.from(standardVersion) + + val metadata = when (version) { + OpenID4VCIVersion.D10 -> metadataD10 + OpenID4VCIVersion.D13 -> metadata + } + call.respond(metadata.toJSON()) } + get("{standardVersion}/.well-known/oauth-authorization-server") { + val standardVersion = call.parameters["standardVersion"] ?: throw IllegalArgumentException("standardVersion parameter is required") + val version = OpenID4VCIVersion.from(standardVersion) + + val metadata = when (version) { + OpenID4VCIVersion.D10 -> metadataD10 + OpenID4VCIVersion.D13 -> metadata + } + call.respond(metadata.toJSON()) } From a8780e07aa86c0aa6498e6182360cb09bacd91ce Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Wed, 13 Nov 2024 12:33:05 +0200 Subject: [PATCH 09/53] fix: supported credentials in d10 must be an array --- .../kotlin/id/walt/oid4vc/OpenID4VCI.kt | 8 +++++++- .../oid4vc/data/OpenIDProviderMetadata.kt | 19 +++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt index f8b1a2a0d..180cbd188 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt @@ -355,7 +355,13 @@ object OpenID4VCI { ), // (EBSI) this is required one https://www.rfc-editor.org/rfc/rfc8414.html#section-2 idTokenSigningAlgValuesSupported = setOf("ES256"), // (EBSI) https://openid.net/specs/openid-connect-self-issued-v2-1_0.html#name-self-issued-openid-provider- codeChallengeMethodsSupported = listOf("S256"), - credentialSupported = credentialSupported.mapValues { (_, credential) -> credential.copy(types = credential.credentialDefinition?.type, credentialDefinition = null) } + credentialSupported = credentialSupported. + filterValues { credential -> + credential.format == CredentialFormat.jwt_vc || credential.format == CredentialFormat.jwt_vc_json + }. + mapValues { + (_, credential) -> credential.copy(types = credential.credentialDefinition?.type, credentialDefinition = null) + } ) } } diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt index 3657d694a..58d3ec449 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt @@ -67,7 +67,7 @@ sealed class OpenIDProviderMetadata : JsonDataObject() { } } - @Serializable +@Serializable data class OpenIDProviderMetadataD10( @SerialName("issuer") val issuer: String? = null, @SerialName("authorization_endpoint") val authorizationEndpoint: String? = null, @@ -127,8 +127,23 @@ data class OpenIDProviderMetadataD10( @SerialName("require_pushed_authorization_requests") val requirePushedAuthorizationRequests: Boolean? = null, @SerialName("dpop_signing_alg_values_supported") val dpopSigningAlgValuesSupported: Set? = null, override val customParameters: Map = mapOf() -) : OpenIDProviderMetadata() +) : OpenIDProviderMetadata() { + + override fun toJSON(): JsonObject { + val originalJson = super.toJSON().toMutableMap() + credentialSupported?.let { credentials -> + val jsonArray = credentials.map { (id, credential) -> + val jsonObject = credential.toJSON().toMutableMap() + jsonObject["id"] = JsonPrimitive(id) + JsonObject(jsonObject) + } + originalJson["credentials_supported"] = JsonArray(jsonArray) + } + + return JsonObject(originalJson) + } +} @Serializable data class OpenIDProviderMetadataD13( From 0417c066463859fd77fd5b13caa232ba09d05f44 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Wed, 13 Nov 2024 12:37:30 +0200 Subject: [PATCH 10/53] fix: update enum values to enum entries --- .../src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt index 180cbd188..791a03e1d 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt @@ -521,7 +521,7 @@ enum class OpenID4VCIVersion(val versionString: String) { companion object { fun from(version: String): OpenID4VCIVersion { - return values().find { it.versionString == version } + return entries.find { it.versionString == version } ?: throw IllegalArgumentException("Unsupported version: $version. Supported Versions are: Draft13 -> d13 and Draft10 -> d10") } } From 129be9cd325b7ed8967fcd0dfa761d1569c9ea84 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Wed, 13 Nov 2024 13:09:13 +0200 Subject: [PATCH 11/53] fix: vct resolution and well-known endpoints --- .../src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt | 4 ++-- .../src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt index 791a03e1d..a2d722cc6 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt @@ -124,8 +124,8 @@ object OpenID4VCI { appendPathSegments(".well-known", "oauth-authorization-server") }.buildString() - fun getJWTIssuerProviderMetadataUrl(baseUrl: String) = URLBuilder(baseUrl).apply { - appendPathSegments(".well-known", "jwt-vc-issuer") + fun getJWTIssuerProviderMetadataUrl(baseUrl: String) = URLBuilder(Url(baseUrl).protocolWithAuthority).apply { + appendPathSegments(".well-known", "jwt-vc-issuer") }.buildString() suspend fun resolveCIProviderMetadata(credOffer: CredentialOffer) = http.get(getCIProviderMetadataUrl(credOffer)).bodyAsText().let { diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt index 4c7e782f1..da1583d91 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt @@ -101,11 +101,11 @@ object OidcApi : CIProvider() { call.respond(metadata.toJSON()) } - get("{standardVersion}/.well-known/jwt-vc-issuer") { + get("/.well-known/jwt-vc-issuer") { call.respond(HttpStatusCode.OK, JWTVCIssuerMetadata(issuer = metadata.issuer, jwksUri = metadata.jwksUri)) } - get("{standardVersion}/.well-known/vct/{type}") { + get("/.well-known/vct/{type}") { val credType = call.parameters["type"] ?: throw IllegalArgumentException("Type required") // issuer api is the From 7c9dbcf1b81ab514825e450f11b2b6d0f6cefc24 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Wed, 13 Nov 2024 13:22:04 +0200 Subject: [PATCH 12/53] fix: cleanup IssuanceRequest and CIProvider --- .../id/walt/issuer/issuance/CIProvider.kt | 14 +++-- .../walt/issuer/issuance/IssuanceRequests.kt | 58 +++++++++++++------ .../id/walt/issuer/issuance/IssuerApi.kt | 16 +++-- 3 files changed, 61 insertions(+), 27 deletions(-) diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt index 35f4dc58f..c8b620812 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt @@ -481,17 +481,21 @@ open class CIProvider( expiresIn: Duration, allowPreAuthorized: Boolean, callbackUrl: String? = null, - txCode: TxCode? = null, txCodeValue: String? = null, + txCode: TxCode? = null, + txCodeValue: String? = null, ): IssuanceSession = runBlocking { val sessionId = randomUUID() - val credentialOfferBuilder = - OidcIssuance.issuanceRequestsToCredentialOfferBuilder(issuanceRequests) + + val credentialOfferBuilder = OidcIssuance.issuanceRequestsToCredentialOfferBuilder(issuanceRequests) + credentialOfferBuilder.addAuthorizationCodeGrant(sessionId) + if (allowPreAuthorized) credentialOfferBuilder.addPreAuthorizedCodeGrant( - OpenID4VC.generateAuthorizationCodeFor(sessionId, metadata.issuer!!, CI_TOKEN_KEY), - txCode + preAuthCode = OpenID4VC.generateAuthorizationCodeFor(sessionId, metadata.issuer!!, CI_TOKEN_KEY), + txCode = txCode ) + return@runBlocking IssuanceSession( id = sessionId, authorizationRequest = null, diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuanceRequests.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuanceRequests.kt index db7c69f14..354e471ba 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuanceRequests.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuanceRequests.kt @@ -1,6 +1,7 @@ package id.walt.issuer.issuance import id.walt.credentials.vc.vcs.W3CVC +import id.walt.oid4vc.OpenID4VCIVersion import id.walt.oid4vc.data.* import id.walt.sdjwt.SDMap import io.ktor.server.plugins.BadRequestException @@ -91,35 +92,58 @@ data class IssuanceRequest( val issuerDid: String? = null, val x5Chain: List? = null, val trustedRootCAs: List? = null, - - var credentialFormat: CredentialFormat? = null -) { - constructor( - issuerKey: JsonObject, credentialConfigurationId: String, credentialData: JsonObject, - vct: String? = null, - mapping: JsonObject? = null, selectiveDisclosure: SDMap? = null, - authenticationMethod: AuthenticationMethod? = AuthenticationMethod.PRE_AUTHORIZED, // "PWD" OR "ID_TOKEN" OR "VP_TOKEN" OR "PRE_AUTHORIZED" OR "NONE" - vpRequestValue: String? = null, vpProfile: OpenId4VPProfile? = null, useJar: Boolean? = null, issuerDid: String, - x5Chain: List? = null, trustedRootCAs: List? = null, credentialFormat: CredentialFormat? = null - ) : this(issuerKey, credentialConfigurationId, - credentialData, vct, null, - mapping, selectiveDisclosure, authenticationMethod, vpRequestValue, vpProfile, useJar, issuerDid, x5Chain, - trustedRootCAs, credentialFormat - ) + var credentialFormat: CredentialFormat? = null, + val standardVersion: OpenID4VCIVersion? = OpenID4VCIVersion.D13, + ) { + constructor( + issuerKey: JsonObject, + credentialConfigurationId: String, + credentialData: JsonObject, + vct: String? = null, + mapping: JsonObject? = null, + selectiveDisclosure: SDMap? = null, + authenticationMethod: AuthenticationMethod? = AuthenticationMethod.PRE_AUTHORIZED, // "PWD" OR "ID_TOKEN" OR "VP_TOKEN" OR "PRE_AUTHORIZED" OR "NONE" + vpRequestValue: String? = null, + vpProfile: OpenId4VPProfile? = null, + useJar: Boolean? = null, + issuerDid: String, + x5Chain: List? = null, + trustedRootCAs: List? = null, + credentialFormat: CredentialFormat? = null, + standardVersion: OpenID4VCIVersion? = OpenID4VCIVersion.D13, + ) : + this( + issuerKey, + credentialConfigurationId, + credentialData, + vct, + null, + mapping, + selectiveDisclosure, + authenticationMethod, + vpRequestValue, + vpProfile, + useJar, + issuerDid, + x5Chain, + trustedRootCAs, + credentialFormat, + standardVersion + ) init { - credentialData?.let { require(it.isNotEmpty()) { throw BadRequestException("CredentialData in the request body cannot be empty") } } + require(credentialConfigurationId.isNotEmpty()) { throw BadRequestException("Credential configuration ID in the request body cannot be empty") } + require(issuerKey.isNotEmpty()) { throw BadRequestException("Issuer key in the request body cannot be empty") } - } } diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuerApi.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuerApi.kt index 398eebc44..3019eb454 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuerApi.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuerApi.kt @@ -43,7 +43,7 @@ suspend fun createCredentialOfferUri( val issuanceSession = OidcApi.initializeCredentialOffer( issuanceRequests = overwrittenIssuanceRequests, - expiresIn, + expiresIn = expiresIn, allowPreAuthorized = when (overwrittenIssuanceRequests[0].authenticationMethod) { AuthenticationMethod.PRE_AUTHORIZED -> true else -> false @@ -53,14 +53,20 @@ suspend fun createCredentialOfferUri( logger.debug { "issuanceSession: $issuanceSession" } - val offerRequest = - CredentialOfferRequest(null, "${OidcApi.baseUrl}/credentialOffer?id=${issuanceSession.id}") + val offerRequest = CredentialOfferRequest( + credentialOffer = null, + credentialOfferUri = "${OidcApi.baseUrl}/credentialOffer?id=${issuanceSession.id}" + ) + logger.debug { "offerRequest: $offerRequest" } - val offerUri = OpenID4VCI.getCredentialOfferRequestUrl(offerRequest, - CROSS_DEVICE_CREDENTIAL_OFFER_URL + OidcApi.baseUrl.removePrefix("https://").removePrefix("http://") + "/" + val offerUri = OpenID4VCI.getCredentialOfferRequestUrl( + credOfferReq = offerRequest, + credentialOfferEndpoint = CROSS_DEVICE_CREDENTIAL_OFFER_URL + OidcApi.baseUrl.removePrefix("https://").removePrefix("http://") + "/" ) + logger.debug { "Offer URI: $offerUri" } + return offerUri } From 288eaead420d8d8eef3306e2d759770fb1d903c9 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Wed, 13 Nov 2024 19:30:46 +0200 Subject: [PATCH 13/53] refactor: metadata class --- .../kotlin/id/walt/oid4vc/OpenID4VC.kt | 8 +- .../kotlin/id/walt/oid4vc/OpenID4VCI.kt | 13 +- .../oid4vc/data/OpenIDProviderMetadata.kt | 195 +++++++++--------- .../providers/OpenIDCredentialWallet.kt | 14 +- .../walt/oid4vc/providers/OpenIDProvider.kt | 2 +- .../kotlin/id/walt/oid4vc/CI_JVM_Test.kt | 10 +- .../kotlin/id/walt/oid4vc/EBSITestWallet.kt | 4 +- .../kotlin/id/walt/oid4vc/OpenID4VCI_Test.kt | 2 +- .../id/walt/oid4vc/TestCredentialWallet.kt | 4 +- .../kotlin/id/walt/oid4vc/wallettest.kt | 2 +- .../src/test/kotlin/LspPotentialIssuance.kt | 10 +- .../src/test/kotlin/LspPotentialWallet.kt | 4 +- .../kotlin/id/walt/issuer/NewIssuanceStub.kt | 2 +- .../id/walt/issuer/NewTestOidcMetadata.kt | 2 +- .../id/walt/issuer/issuance/CIProvider.kt | 4 +- .../exchange/CredentialOfferProcessor.kt | 5 +- .../service/exchange/IssuanceService.kt | 2 +- .../IssuanceServiceExternalSignatures.kt | 2 +- .../service/oidc4vc/TestCredentialWallet.kt | 4 +- 19 files changed, 144 insertions(+), 145 deletions(-) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt index e8b104eab..cfee185e3 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt @@ -147,7 +147,7 @@ object OpenID4VC { presentationDefinition: PresentationDefinition? = null, ): AuthorizationCodeWithAuthorizationRequestResponse { - providerMetadata as OpenIDProviderMetadataD13 + providerMetadata as OpenIDProviderMetadata.Draft13 if (!authorizationRequest.responseType.contains(ResponseType.Code)) throw AuthorizationError( @@ -214,7 +214,7 @@ object OpenID4VC { message = "Invalid response type ${authorizationRequest.responseType}, for authorization code flow." ) - providerMetadata as OpenIDProviderMetadataD13 + providerMetadata as OpenIDProviderMetadata.Draft13 val issuer = providerMetadata.issuer ?: throw AuthorizationError(authorizationRequest, AuthorizationErrorCode.server_error,"No issuer configured in given provider metadata") val code = generateAuthorizationCodeFor(sessionId, issuer, tokenKey) @@ -222,7 +222,7 @@ object OpenID4VC { } suspend fun processImplicitFlowAuthorization(authorizationRequest: AuthorizationRequest, sessionId: String, providerMetadata: OpenIDProviderMetadata, tokenKey: Key): TokenResponse { - providerMetadata as OpenIDProviderMetadataD13 + providerMetadata as OpenIDProviderMetadata.Draft13 log.debug { "> processImplicitFlowAuthorization for $authorizationRequest" } if (!authorizationRequest.responseType.contains(ResponseType.Token) && !authorizationRequest.responseType.contains(ResponseType.VpToken) @@ -243,7 +243,7 @@ object OpenID4VC { } suspend fun processDirectPost(authorizationRequest: AuthorizationRequest, sessionId: String, providerMetadata: OpenIDProviderMetadata, tokenKey: Key): AuthorizationCodeResponse { - providerMetadata as OpenIDProviderMetadataD13 + providerMetadata as OpenIDProviderMetadata.Draft13 // Verify nonce - need to add Id token nonce session // if (payload[JWTClaims.Payload.nonce] != session.) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt index a2d722cc6..84bc49b4a 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt @@ -134,8 +134,8 @@ object OpenID4VCI { fun resolveOfferedCredentials(credentialOffer: CredentialOffer, providerMetadata: OpenIDProviderMetadata): List { val supportedCredentials = when (providerMetadata) { - is OpenIDProviderMetadataD10 -> providerMetadata.credentialSupported ?: mapOf() - is OpenIDProviderMetadataD13 -> providerMetadata.credentialConfigurationsSupported ?: mapOf() + is OpenIDProviderMetadata.Draft10 -> providerMetadata.credentialSupported ?: mapOf() + is OpenIDProviderMetadata.Draft13 -> providerMetadata.credentialConfigurationsSupported ?: mapOf() } return credentialOffer.credentialConfigurationIds.mapNotNull { c -> @@ -309,8 +309,9 @@ object OpenID4VCI { fun createDefaultProviderMetadata(baseUrl: String, credentialSupported: Map, version: OpenID4VCIVersion) : OpenIDProviderMetadata { + return when (version) { - OpenID4VCIVersion.D13 -> OpenIDProviderMetadataD13( + OpenID4VCIVersion.D13 -> OpenIDProviderMetadata.Draft13( issuer = baseUrl, authorizationEndpoint = "$baseUrl/authorize", pushedAuthorizationRequestEndpoint = "$baseUrl/par", @@ -334,7 +335,7 @@ object OpenID4VCI { credentialConfigurationsSupported = credentialSupported ) - OpenID4VCIVersion.D10 -> OpenIDProviderMetadataD10( + OpenID4VCIVersion.D10 -> OpenIDProviderMetadata.Draft10( issuer = baseUrl, authorizationEndpoint = "$baseUrl/authorize", pushedAuthorizationRequestEndpoint = "$baseUrl/par", @@ -408,8 +409,8 @@ object OpenID4VCI { ) } val supportedCredentialFormats = when (openIDProviderMetadata) { - is OpenIDProviderMetadataD13 -> openIDProviderMetadata.credentialConfigurationsSupported?.values?.map { it.format }?.toSet() ?: setOf() - is OpenIDProviderMetadataD10 -> openIDProviderMetadata.credentialSupported?.values?.map { it.format }?.toSet() ?: setOf() + is OpenIDProviderMetadata.Draft13 -> openIDProviderMetadata.credentialConfigurationsSupported?.values?.map { it.format }?.toSet() ?: setOf() + is OpenIDProviderMetadata.Draft10 -> openIDProviderMetadata.credentialSupported?.values?.map { it.format }?.toSet() ?: setOf() } if (!supportedCredentialFormats.contains(credentialRequest.format)) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt index 58d3ec449..abb850cc5 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt @@ -59,16 +59,9 @@ import kotlinx.serialization.json.* */ @Serializable sealed class OpenIDProviderMetadata : JsonDataObject() { - override fun toJSON(): JsonObject = Json.encodeToJsonElement(OpenIDProviderMetadataSerializer, this).jsonObject - - companion object : JsonDataObjectFactory() { - override fun fromJSON(jsonObject: JsonObject): OpenIDProviderMetadata = - Json.decodeFromJsonElement(OpenIDProviderMetadataSerializer, jsonObject) - } -} -@Serializable -data class OpenIDProviderMetadataD10( + @Serializable + data class Draft10( @SerialName("issuer") val issuer: String? = null, @SerialName("authorization_endpoint") val authorizationEndpoint: String? = null, @SerialName("pushed_authorization_request_endpoint") val pushedAuthorizationRequestEndpoint: String? = null, @@ -120,109 +113,115 @@ data class OpenIDProviderMetadataD10( @SerialName("authorization_servers") val authorizationServers: Set? = null, @SerialName("display") @Serializable(DisplayPropertiesListSerializer::class) val display: List? = null, @SerialName("presentation_definition_uri_supported") val presentationDefinitionUriSupported: Boolean? = null, - //@SerialName("vp_formats_supported") @Serializable(SupportedVPFormatMapSerializer::class) val vpFormatsSupported: Map? = null, @SerialName("client_id_schemes_supported") val clientIdSchemesSupported: List? = null, @SerialName("authorization_server") val authorizationServer: String? = authorizationServers?.firstOrNull(), // Move here since if we have a null value for this parameter, the discovery fails @SerialName("code_challenge_methods_supported") val codeChallengeMethodsSupported: List? = null, @SerialName("require_pushed_authorization_requests") val requirePushedAuthorizationRequests: Boolean? = null, @SerialName("dpop_signing_alg_values_supported") val dpopSigningAlgValuesSupported: Set? = null, override val customParameters: Map = mapOf() -) : OpenIDProviderMetadata() { + ) : OpenIDProviderMetadata() { - override fun toJSON(): JsonObject { - val originalJson = super.toJSON().toMutableMap() + override fun toJSON(): JsonObject { + val originalJson = super.toJSON().toMutableMap() - credentialSupported?.let { credentials -> - val jsonArray = credentials.map { (id, credential) -> - val jsonObject = credential.toJSON().toMutableMap() - jsonObject["id"] = JsonPrimitive(id) - JsonObject(jsonObject) + credentialSupported?.let { credentials -> + val jsonArray = credentials.map { (id, credential) -> + val jsonObject = credential.toJSON().toMutableMap() + jsonObject["id"] = JsonPrimitive(id) + JsonObject(jsonObject) + } + originalJson["credentials_supported"] = JsonArray(jsonArray) } - originalJson["credentials_supported"] = JsonArray(jsonArray) - } - return JsonObject(originalJson) + return JsonObject(originalJson) + } } -} -@Serializable -data class OpenIDProviderMetadataD13( - @SerialName("issuer") val issuer: String? = null, - @SerialName("authorization_endpoint") val authorizationEndpoint: String? = null, - @SerialName("pushed_authorization_request_endpoint") val pushedAuthorizationRequestEndpoint: String? = null, - @SerialName("token_endpoint") val tokenEndpoint: String? = null, - @SerialName("userinfo_endpoint") val userinfoEndpoint: String? = null, - @SerialName("jwks_uri") val jwksUri: String? = null, - @SerialName("registration_endpoint") val registrationEndpoint: String? = null, - @EncodeDefault @SerialName("scopes_supported") val scopesSupported: Set = setOf("openid"), - @SerialName("response_types_supported") val responseTypesSupported: Set? = null, - @EncodeDefault @SerialName("response_modes_supported") val responseModesSupported: Set = setOf( - ResponseMode.query, - ResponseMode.fragment - ), - @EncodeDefault @SerialName("grant_types_supported") @Serializable(GrantTypeSetSerializer::class) val grantTypesSupported: Set = setOf( - GrantType.authorization_code, - GrantType.pre_authorized_code - ), - @SerialName("acr_values_supported") val acrValuesSupported: Set? = null, - @SerialName("subject_types_supported") val subjectTypesSupported: Set? = null, - @SerialName("id_token_signing_alg_values_supported") val idTokenSigningAlgValuesSupported: Set? = null, - @SerialName("id_token_encryption_alg_values_supported") val idTokenEncryptionAlgValuesSupported: Set? = null, - @SerialName("id_token_encryption_enc_values_supported") val idTokenEncryptionEncValuesSupported: Set? = null, - @SerialName("userinfo_signing_alg_values_supported") val userinfoSigningAlgValuesSupported: Set? = null, - @SerialName("userinfo_encryption_alg_values_supported") val userinfoEncryptionAlgValuesSupported: Set? = null, - @SerialName("userinfo_encryption_enc_values_supported") val userinfoEncryptionEncValuesSupported: Set? = null, - @SerialName("request_object_signing_alg_values_supported") val requestObjectSigningAlgValuesSupported: Set? = null, - @SerialName("request_object_encryption_alg_values_supported") val requestObjectEncryptionAlgValuesSupported: Set? = null, - @SerialName("request_object_encryption_enc_values_supported") val requestObjectEncryptionEncValuesSupported: Set? = null, - @SerialName("token_endpoint_auth_methods_supported") val tokenEndpointAuthMethodsSupported: Set? = null, - @SerialName("token_endpoint_auth_signing_alg_values_supported") val tokenEndpointAuthSigningAlgValuesSupported: Set? = null, - @SerialName("display_values_supported") val displayValuesSupported: Set? = null, - @SerialName("claim_types_supported") val claimTypesSupported: Set? = null, - @SerialName("claims_supported") val claimsSupported: Set? = null, - @SerialName("service_documentation") val serviceDocumentation: String? = null, - @SerialName("claims_locales_supported") val claimsLocalesSupported: Set? = null, - @SerialName("ui_locales_supported") val uiLocalesSupported: Set? = null, - @SerialName("claims_parameter_supported") val claimsParameterSupported: Boolean = false, - @SerialName("request_parameter_supported") val requestParameterSupported: Boolean = false, - @SerialName("request_uri_parameter_supported") val requestUriParameterSupported: Boolean = true, - @SerialName("require_request_uri_registration") val requireRequestUriRegistration: Boolean = false, - @SerialName("op_policy_uri") val opPolicyUri: String? = null, - @SerialName("op_tos_uri") val opTosUri: String? = null, - // OID4VCI properties - @SerialName("credential_issuer") val credentialIssuer: String? = null, - @SerialName("credential_endpoint") val credentialEndpoint: String? = null, - @SerialName("credential_configurations_supported") @Serializable(CredentialSupportedMapSerializer::class) val credentialConfigurationsSupported: Map? = null, - @SerialName("batch_credential_endpoint") val batchCredentialEndpoint: String? = null, - @SerialName("deferred_credential_endpoint") val deferredCredentialEndpoint: String? = null, - @SerialName("authorization_servers") val authorizationServers: Set? = null, - @SerialName("display") @Serializable(DisplayPropertiesListSerializer::class) val display: List? = null, - @SerialName("presentation_definition_uri_supported") val presentationDefinitionUriSupported: Boolean? = null, - //@SerialName("vp_formats_supported") @Serializable(SupportedVPFormatMapSerializer::class) val vpFormatsSupported: Map? = null, - @SerialName("client_id_schemes_supported") val clientIdSchemesSupported: List? = null, - @SerialName("authorization_server") val authorizationServer: String? = authorizationServers?.firstOrNull(), // Move here since if we have a null value for this parameter, the discovery fails - @SerialName("code_challenge_methods_supported") val codeChallengeMethodsSupported: List? = null, - @SerialName("require_pushed_authorization_requests") val requirePushedAuthorizationRequests: Boolean? = null, - @SerialName("dpop_signing_alg_values_supported") val dpopSigningAlgValuesSupported: Set? = null, - override val customParameters: Map = mapOf() + @Serializable + data class Draft13( + @SerialName("issuer") val issuer: String? = null, + @SerialName("authorization_endpoint") val authorizationEndpoint: String? = null, + @SerialName("pushed_authorization_request_endpoint") val pushedAuthorizationRequestEndpoint: String? = null, + @SerialName("token_endpoint") val tokenEndpoint: String? = null, + @SerialName("userinfo_endpoint") val userinfoEndpoint: String? = null, + @SerialName("jwks_uri") val jwksUri: String? = null, + @SerialName("registration_endpoint") val registrationEndpoint: String? = null, + @EncodeDefault @SerialName("scopes_supported") val scopesSupported: Set = setOf("openid"), + @SerialName("response_types_supported") val responseTypesSupported: Set? = null, + @EncodeDefault @SerialName("response_modes_supported") val responseModesSupported: Set = setOf( + ResponseMode.query, + ResponseMode.fragment + ), + @EncodeDefault @SerialName("grant_types_supported") @Serializable(GrantTypeSetSerializer::class) val grantTypesSupported: Set = setOf( + GrantType.authorization_code, + GrantType.pre_authorized_code + ), + @SerialName("acr_values_supported") val acrValuesSupported: Set? = null, + @SerialName("subject_types_supported") val subjectTypesSupported: Set? = null, + @SerialName("id_token_signing_alg_values_supported") val idTokenSigningAlgValuesSupported: Set? = null, + @SerialName("id_token_encryption_alg_values_supported") val idTokenEncryptionAlgValuesSupported: Set? = null, + @SerialName("id_token_encryption_enc_values_supported") val idTokenEncryptionEncValuesSupported: Set? = null, + @SerialName("userinfo_signing_alg_values_supported") val userinfoSigningAlgValuesSupported: Set? = null, + @SerialName("userinfo_encryption_alg_values_supported") val userinfoEncryptionAlgValuesSupported: Set? = null, + @SerialName("userinfo_encryption_enc_values_supported") val userinfoEncryptionEncValuesSupported: Set? = null, + @SerialName("request_object_signing_alg_values_supported") val requestObjectSigningAlgValuesSupported: Set? = null, + @SerialName("request_object_encryption_alg_values_supported") val requestObjectEncryptionAlgValuesSupported: Set? = null, + @SerialName("request_object_encryption_enc_values_supported") val requestObjectEncryptionEncValuesSupported: Set? = null, + @SerialName("token_endpoint_auth_methods_supported") val tokenEndpointAuthMethodsSupported: Set? = null, + @SerialName("token_endpoint_auth_signing_alg_values_supported") val tokenEndpointAuthSigningAlgValuesSupported: Set? = null, + @SerialName("display_values_supported") val displayValuesSupported: Set? = null, + @SerialName("claim_types_supported") val claimTypesSupported: Set? = null, + @SerialName("claims_supported") val claimsSupported: Set? = null, + @SerialName("service_documentation") val serviceDocumentation: String? = null, + @SerialName("claims_locales_supported") val claimsLocalesSupported: Set? = null, + @SerialName("ui_locales_supported") val uiLocalesSupported: Set? = null, + @SerialName("claims_parameter_supported") val claimsParameterSupported: Boolean = false, + @SerialName("request_parameter_supported") val requestParameterSupported: Boolean = false, + @SerialName("request_uri_parameter_supported") val requestUriParameterSupported: Boolean = true, + @SerialName("require_request_uri_registration") val requireRequestUriRegistration: Boolean = false, + @SerialName("op_policy_uri") val opPolicyUri: String? = null, + @SerialName("op_tos_uri") val opTosUri: String? = null, + // OID4VCI properties + @SerialName("credential_issuer") val credentialIssuer: String? = null, + @SerialName("credential_endpoint") val credentialEndpoint: String? = null, + @SerialName("credential_configurations_supported") @Serializable(CredentialSupportedMapSerializer::class) val credentialConfigurationsSupported: Map? = null, + @SerialName("batch_credential_endpoint") val batchCredentialEndpoint: String? = null, + @SerialName("deferred_credential_endpoint") val deferredCredentialEndpoint: String? = null, + @SerialName("authorization_servers") val authorizationServers: Set? = null, + @SerialName("display") @Serializable(DisplayPropertiesListSerializer::class) val display: List? = null, + @SerialName("presentation_definition_uri_supported") val presentationDefinitionUriSupported: Boolean? = null, + @SerialName("client_id_schemes_supported") val clientIdSchemesSupported: List? = null, + @SerialName("authorization_server") val authorizationServer: String? = authorizationServers?.firstOrNull(), // Move here since if we have a null value for this parameter, the discovery fails + @SerialName("code_challenge_methods_supported") val codeChallengeMethodsSupported: List? = null, + @SerialName("require_pushed_authorization_requests") val requirePushedAuthorizationRequests: Boolean? = null, + @SerialName("dpop_signing_alg_values_supported") val dpopSigningAlgValuesSupported: Set? = null, + override val customParameters: Map = mapOf() ) : OpenIDProviderMetadata() -{ - // TODO: make them abstract in the sealed class - fun getVctByCredentialConfigurationId(credentialConfigurationId: String) = credentialConfigurationsSupported?.get(credentialConfigurationId)?.vct + { + // TODO: make them abstract in the sealed class + fun getVctByCredentialConfigurationId(credentialConfigurationId: String) = credentialConfigurationsSupported?.get(credentialConfigurationId)?.vct - fun getVctBySupportedCredentialConfiguration( - baseUrl: String, - credType: String - ): CredentialSupported { - val expectedVct = "$baseUrl/$credType" + fun getVctBySupportedCredentialConfiguration( + baseUrl: String, + credType: String + ): CredentialSupported { + val expectedVct = "$baseUrl/$credType" - credentialConfigurationsSupported?.entries?.forEach { entry -> - if (getVctByCredentialConfigurationId(entry.key) == expectedVct) { - return entry.value + credentialConfigurationsSupported?.entries?.forEach { entry -> + if (getVctByCredentialConfigurationId(entry.key) == expectedVct) { + return entry.value + } } + + throw IllegalArgumentException("Invalid type value: $credType. The $credType type is not supported") } + } - throw IllegalArgumentException("Invalid type value: $credType. The $credType type is not supported") + override fun toJSON(): JsonObject = Json.encodeToJsonElement(OpenIDProviderMetadataSerializer, this).jsonObject + + companion object : JsonDataObjectFactory() { + override fun fromJSON(jsonObject: JsonObject): OpenIDProviderMetadata = + Json.decodeFromJsonElement(OpenIDProviderMetadataSerializer, jsonObject) } } @@ -237,8 +236,8 @@ object OpenIDProviderMetadataSerializer : KSerializer { val jsonObject = jsonDecoder.decodeJsonElement().jsonObject return when { - "credentials_supported" in jsonObject -> Json.decodeFromJsonElement(OpenIDProviderMetadataD10.serializer(), jsonObject) - "credential_configurations_supported" in jsonObject -> Json.decodeFromJsonElement(OpenIDProviderMetadataD13.serializer(), jsonObject) + "credentials_supported" in jsonObject -> Json.decodeFromJsonElement(OpenIDProviderMetadata.Draft10.serializer(), jsonObject) + "credential_configurations_supported" in jsonObject -> Json.decodeFromJsonElement(OpenIDProviderMetadata.Draft13.serializer(), jsonObject) else -> throw IllegalArgumentException("Unknown OpenIDProviderMetadata version: missing expected fields") } } @@ -248,8 +247,8 @@ object OpenIDProviderMetadataSerializer : KSerializer { ?: throw IllegalStateException("This class can only be serialized with JSON") val jsonElement: JsonElement = when (value) { - is OpenIDProviderMetadataD10 -> Json.encodeToJsonElement(OpenIDProviderMetadataD10.serializer(), value) - is OpenIDProviderMetadataD13 -> Json.encodeToJsonElement(OpenIDProviderMetadataD13.serializer(), value) + is OpenIDProviderMetadata.Draft10 -> Json.encodeToJsonElement(OpenIDProviderMetadata.Draft10.serializer(), value) + is OpenIDProviderMetadata.Draft13 -> Json.encodeToJsonElement(OpenIDProviderMetadata.Draft13.serializer(), value) } jsonEncoder.encodeJsonElement(jsonElement) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDCredentialWallet.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDCredentialWallet.kt index 81f75b7e2..6f0c9d9ee 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDCredentialWallet.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDCredentialWallet.kt @@ -245,7 +245,7 @@ abstract class OpenIDCredentialWallet( ) val issuerMetadataUrl = getCIProviderMetadataUrl(credentialOffer.credentialIssuer) val issuerMetadata = - httpGetAsJson(Url(issuerMetadataUrl))?.jsonObject?.let { OpenIDProviderMetadata.fromJSON(it) as OpenIDProviderMetadataD13 } ?: throw CredentialOfferError( + httpGetAsJson(Url(issuerMetadataUrl))?.jsonObject?.let { OpenIDProviderMetadata.fromJSON(it) as OpenIDProviderMetadata.Draft13 } ?: throw CredentialOfferError( null, credentialOffer, CredentialOfferErrorCode.invalid_issuer, @@ -276,15 +276,15 @@ abstract class OpenIDCredentialWallet( ) val issuerMetadataUrl = getCIProviderMetadataUrl(credentialOffer.credentialIssuer) val issuerMetadata = - httpGetAsJson(Url(issuerMetadataUrl))?.jsonObject?.let { OpenIDProviderMetadata.fromJSON(it) as OpenIDProviderMetadataD13} ?: throw CredentialOfferError( + httpGetAsJson(Url(issuerMetadataUrl))?.jsonObject?.let { OpenIDProviderMetadata.fromJSON(it) as OpenIDProviderMetadata.Draft13} ?: throw CredentialOfferError( null, credentialOffer, CredentialOfferErrorCode.invalid_issuer, "Could not resolve issuer provider metadata from $issuerMetadataUrl" ) val authorizationServerMetadata = issuerMetadata.authorizationServer?.let { authServer -> - httpGetAsJson(Url(getCommonProviderMetadataUrl(authServer)))?.jsonObject?.let { OpenIDProviderMetadata.fromJSON(it) as OpenIDProviderMetadataD13} - } ?: issuerMetadata as OpenIDProviderMetadataD13 + httpGetAsJson(Url(getCommonProviderMetadataUrl(authServer)))?.jsonObject?.let { OpenIDProviderMetadata.fromJSON(it) as OpenIDProviderMetadata.Draft13} + } ?: issuerMetadata as OpenIDProviderMetadata.Draft13 val offeredCredentials = OpenID4VCI.resolveOfferedCredentials(credentialOffer, issuerMetadata) val codeVerifier = if (client.useCodeChallenge) randomUUID() else null @@ -370,7 +370,7 @@ abstract class OpenIDCredentialWallet( ) val issuerMetadataUrl = getCIProviderMetadataUrl(credentialOffer.credentialIssuer) val issuerMetadata = - httpGetAsJson(Url(issuerMetadataUrl))?.jsonObject?.let { OpenIDProviderMetadata.fromJSON(it) as OpenIDProviderMetadataD13 } ?: throw CredentialOfferError( + httpGetAsJson(Url(issuerMetadataUrl))?.jsonObject?.let { OpenIDProviderMetadata.fromJSON(it) as OpenIDProviderMetadata.Draft13 } ?: throw CredentialOfferError( null, credentialOffer, CredentialOfferErrorCode.invalid_issuer, @@ -408,8 +408,8 @@ abstract class OpenIDCredentialWallet( userPIN, codeVerifier ) - authorizationServerMetadata as OpenIDProviderMetadataD13 - issuerMetadata as OpenIDProviderMetadataD13 + authorizationServerMetadata as OpenIDProviderMetadata.Draft13 + issuerMetadata as OpenIDProviderMetadata.Draft13 val tokenHttpResp = httpSubmitForm(Url(authorizationServerMetadata.tokenEndpoint!!), parametersOf(tokenReq.toHttpParameters())) if (!tokenHttpResp.status.isSuccess() || tokenHttpResp.body == null) throw TokenError( diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDProvider.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDProvider.kt index 9626ea479..9c3170751 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDProvider.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDProvider.kt @@ -31,7 +31,7 @@ import kotlin.uuid.Uuid abstract class OpenIDProvider( val baseUrl: String, ) : ISessionCache, ITokenProvider { - abstract val metadata: OpenIDProviderMetadataD13 + abstract val metadata: OpenIDProviderMetadata.Draft13 abstract val config: OpenIDProviderConfig protected open fun createDefaultProviderMetadata() = OpenID4VCI.createDefaultProviderMetadata(baseUrl, emptyMap(), OpenID4VCIVersion.D13) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/CI_JVM_Test.kt b/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/CI_JVM_Test.kt index d9becf42e..1a4959279 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/CI_JVM_Test.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/CI_JVM_Test.kt @@ -45,7 +45,7 @@ import kotlin.test.* class CI_JVM_Test { - var testMetadata = OpenIDProviderMetadataD13( + var testMetadata = OpenIDProviderMetadata.Draft13( authorizationEndpoint = "https://localhost/oidc", credentialConfigurationsSupported = mapOf( "UniversityDegreeCredential_jwt_vc_json" to CredentialSupported( @@ -248,7 +248,7 @@ class CI_JVM_Test { println("// get issuer metadata") val providerMetadataUri = credentialWallet.getCIProviderMetadataUrl(credOfferReq.credentialOffer!!.credentialIssuer) - val providerMetadata = ktorClient.get(providerMetadataUri).call.body() as OpenIDProviderMetadataD13 + val providerMetadata = ktorClient.get(providerMetadataUri).call.body() as OpenIDProviderMetadata.Draft13 println("providerMetadata: $providerMetadata") assertNotNull(actual = providerMetadata.authorizationEndpoint) println("// resolve offered credentials") @@ -303,7 +303,7 @@ class CI_JVM_Test { val providerMetadataUri = credentialWallet.getCIProviderMetadataUrl(credOfferReq.credentialOffer!!.credentialIssuer) val providerMetadata = - ktorClient.get(providerMetadataUri).call.body().let { OpenIDProviderMetadata.fromJSON(it) } as OpenIDProviderMetadataD13 + ktorClient.get(providerMetadataUri).call.body().let { OpenIDProviderMetadata.fromJSON(it) } as OpenIDProviderMetadata.Draft13 println("providerMetadata: $providerMetadata") assertNotNull(actual = providerMetadata.authorizationEndpoint) println("// resolve offered credentials") @@ -359,7 +359,7 @@ class CI_JVM_Test { val providerMetadataUri = credentialWallet.getCIProviderMetadataUrl(credOfferReq.credentialOffer!!.credentialIssuer) val providerMetadata = - ktorClient.get(providerMetadataUri).call.body().let { OpenIDProviderMetadata.fromJSON(it) } as OpenIDProviderMetadataD13 + ktorClient.get(providerMetadataUri).call.body().let { OpenIDProviderMetadata.fromJSON(it) } as OpenIDProviderMetadata.Draft13 println("providerMetadata: $providerMetadata") assertNotNull(actual = providerMetadata.tokenEndpoint) assertNotNull(actual = providerMetadata.credentialEndpoint) @@ -685,7 +685,7 @@ fun testIsolatedFunctionsCreateCredentialOffer(baseUrl: String, issuerState: Str suspend fun testIsolatedFunctionsResolveCredentialOffer(credOfferUrl: String): OfferedCredential { val parsedCredOffer = OpenID4VCI.parseAndResolveCredentialOfferRequestUrl(credOfferUrl) - val providerMetadata = OpenID4VCI.resolveCIProviderMetadata(parsedCredOffer) as OpenIDProviderMetadataD13 + val providerMetadata = OpenID4VCI.resolveCIProviderMetadata(parsedCredOffer) as OpenIDProviderMetadata.Draft13 assertEquals(expected = parsedCredOffer.credentialIssuer, actual = providerMetadata.credentialIssuer) println("// resolve offered credentials") diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/EBSITestWallet.kt b/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/EBSITestWallet.kt index 75b315867..85bad19b8 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/EBSITestWallet.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/EBSITestWallet.kt @@ -13,7 +13,7 @@ import id.walt.crypto.keys.jwk.JWKKey import id.walt.crypto.utils.JwsUtils.decodeJws import id.walt.did.dids.DidService import id.walt.mdoc.dataelement.MapElement -import id.walt.oid4vc.data.OpenIDProviderMetadataD13 +import id.walt.oid4vc.data.OpenIDProviderMetadata import id.walt.oid4vc.data.dif.DescriptorMapping import id.walt.oid4vc.data.dif.PresentationDefinition import id.walt.oid4vc.data.dif.PresentationSubmission @@ -91,7 +91,7 @@ class EBSITestWallet( ) = SIOPSession(id, authorizationRequest, expirationTimestamp) override val metadata - get() = createDefaultProviderMetadata() as OpenIDProviderMetadataD13 + get() = createDefaultProviderMetadata() as OpenIDProviderMetadata.Draft13 override fun getSession(id: String): SIOPSession? = sessionCache[id] override fun getSessionByAuthServerState(authServerState: String): SIOPSession? { diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/OpenID4VCI_Test.kt b/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/OpenID4VCI_Test.kt index 7c2cdfc6d..06195278f 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/OpenID4VCI_Test.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/OpenID4VCI_Test.kt @@ -33,7 +33,7 @@ import kotlin.test.* class OpenID4VCI_Test { val ISSUER_BASE_URL = "https://test" val CREDENTIAL_OFFER_BASE_URL = "openid-credential-offer://test" - val ISSUER_METADATA = (OpenID4VCI.createDefaultProviderMetadata(ISSUER_BASE_URL, emptyMap(), OpenID4VCIVersion.D13) as OpenIDProviderMetadataD13).copy( + val ISSUER_METADATA = (OpenID4VCI.createDefaultProviderMetadata(ISSUER_BASE_URL, emptyMap(), OpenID4VCIVersion.D13) as OpenIDProviderMetadata.Draft13).copy( credentialConfigurationsSupported = mapOf( "VerifiableId" to CredentialSupported( CredentialFormat.jwt_vc_json, diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/TestCredentialWallet.kt b/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/TestCredentialWallet.kt index 523fd4d34..376bf9d2b 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/TestCredentialWallet.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/TestCredentialWallet.kt @@ -12,7 +12,7 @@ import id.walt.crypto.keys.jwk.JWKKey import id.walt.crypto.utils.JwsUtils.decodeJws import id.walt.did.dids.DidService import id.walt.mdoc.dataelement.MapElement -import id.walt.oid4vc.data.OpenIDProviderMetadataD13 +import id.walt.oid4vc.data.OpenIDProviderMetadata import id.walt.oid4vc.data.ResponseMode import id.walt.oid4vc.data.ResponseType import id.walt.oid4vc.data.dif.DescriptorMapping @@ -234,7 +234,7 @@ class TestCredentialWallet( } override val metadata - get() = createDefaultProviderMetadata() as OpenIDProviderMetadataD13 + get() = createDefaultProviderMetadata() as OpenIDProviderMetadata.Draft13 override fun getSession(id: String) = sessionCache[id] override fun getSessionByAuthServerState(authServerState: String): SIOPSession? { diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/wallettest.kt b/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/wallettest.kt index 2ff75e1a2..9bbdc15a4 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/wallettest.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/wallettest.kt @@ -106,7 +106,7 @@ class wallettest { println("// get issuer metadata") val providerMetadataUri = credentialWallet.getCIProviderMetadataUrl(parsedOfferReq.credentialOffer!!.credentialIssuer) - val providerMetadata = ktorClient.get(providerMetadataUri).call.body() as OpenIDProviderMetadataD13 + val providerMetadata = ktorClient.get(providerMetadataUri).call.body() as OpenIDProviderMetadata.Draft13 println("providerMetadata: $providerMetadata") assertNotNull(actual = providerMetadata.credentialConfigurationsSupported) diff --git a/waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialIssuance.kt b/waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialIssuance.kt index ad35b7e3e..d4f5d1474 100644 --- a/waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialIssuance.kt +++ b/waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialIssuance.kt @@ -67,8 +67,8 @@ class LspPotentialIssuance(val client: HttpClient) { // ### get issuer metadata, steps 7-10 val providerMetadataUri = OpenID4VCI.getCIProviderMetadataUrl(parsedOffer.credentialIssuer) val oauthMetadataUri = OpenID4VCI.getOAuthProviderMetadataUrl(parsedOffer.credentialIssuer) - val providerMetadata = client.get(providerMetadataUri).bodyAsText().let { OpenIDProviderMetadata.fromJSONString(it) } as OpenIDProviderMetadataD13 - val oauthMetadata = client.get(oauthMetadataUri).body() + val providerMetadata = client.get(providerMetadataUri).bodyAsText().let { OpenIDProviderMetadata.fromJSONString(it) } as OpenIDProviderMetadata.Draft13 + val oauthMetadata = client.get(oauthMetadataUri).body() assertNotNull(providerMetadata.credentialConfigurationsSupported) assertNotNull(providerMetadata.credentialEndpoint) assertNotNull(oauthMetadata.authorizationEndpoint) @@ -250,9 +250,9 @@ class LspPotentialIssuance(val client: HttpClient) { val providerMetadataUri = OpenID4VCI.getCIProviderMetadataUrl(parsedOffer.credentialIssuer) val jwtIssuerMetadataUri = OpenID4VCI.getJWTIssuerProviderMetadataUrl(parsedOffer.credentialIssuer) val oAuthMetadataUri = OpenID4VCI.getOAuthProviderMetadataUrl(parsedOffer.credentialIssuer) - val providerMetadata = client.get(providerMetadataUri).bodyAsText().let { OpenIDProviderMetadata.fromJSONString(it) } as OpenIDProviderMetadataD13 - val oauthMetadata = client.get(oAuthMetadataUri).body() - val jwtIssuerMetadata = client.get(jwtIssuerMetadataUri).body() + val providerMetadata = client.get(providerMetadataUri).bodyAsText().let { OpenIDProviderMetadata.fromJSONString(it) } as OpenIDProviderMetadata.Draft13 + val oauthMetadata = client.get(oAuthMetadataUri).body() + val jwtIssuerMetadata = client.get(jwtIssuerMetadataUri).body() assertNotNull(providerMetadata.credentialConfigurationsSupported) assertNotNull(providerMetadata.credentialEndpoint) assertNotNull(jwtIssuerMetadata.issuer) diff --git a/waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialWallet.kt b/waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialWallet.kt index 5800b12c6..c781de4c7 100644 --- a/waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialWallet.kt +++ b/waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialWallet.kt @@ -76,7 +76,7 @@ class LspPotentialWallet(val client: HttpClient, val walletId: String) { // === resolve issuer metadata === val issuerMetadata = client.get("${resolvedOffer.credentialIssuer}/.well-known/openid-credential-issuer").expectSuccess() - .body() + .body() assertEquals(issuerMetadata.issuer, resolvedOffer.credentialIssuer) assertContains( issuerMetadata.credentialConfigurationsSupported!!.keys, @@ -222,7 +222,7 @@ class LspPotentialWallet(val client: HttpClient, val walletId: String) { // === resolve issuer metadata === val issuerMetadata = client.get("${resolvedOffer.credentialIssuer}/.well-known/openid-credential-issuer").expectSuccess() - .body() + .body() assertEquals(issuerMetadata.issuer, resolvedOffer.credentialIssuer) assertContains( issuerMetadata.credentialConfigurationsSupported!!.keys, diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/NewIssuanceStub.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/NewIssuanceStub.kt index b9c8dd6ac..04dfff12a 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/NewIssuanceStub.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/NewIssuanceStub.kt @@ -114,7 +114,7 @@ suspend fun main() { check(credOffer.toJSONString() == parsedCredOffer.toJSONString()) println("Resolve metadata from ${parsedCredOffer.credentialIssuer}") - val providerMetadata = runBlocking { OpenID4VCI.resolveCIProviderMetadata(parsedCredOffer) as OpenIDProviderMetadataD13 } + val providerMetadata = runBlocking { OpenID4VCI.resolveCIProviderMetadata(parsedCredOffer) as OpenIDProviderMetadata.Draft13 } check(parsedCredOffer.credentialIssuer == providerMetadata.credentialIssuer) diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/NewTestOidcMetadata.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/NewTestOidcMetadata.kt index f69754246..411a081c7 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/NewTestOidcMetadata.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/NewTestOidcMetadata.kt @@ -29,7 +29,7 @@ fun main() { get("/.well-known/openid-credential-issuer") { - val providerMetadata = OpenIDProviderMetadataD13( + val providerMetadata = OpenIDProviderMetadata.Draft13( issuer = url, authorizationEndpoint = "$url/authorize", pushedAuthorizationRequestEndpoint = "$url/par", diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt index c8b620812..4bdd3fe65 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt @@ -73,10 +73,10 @@ open class CIProvider( private val log = KotlinLogging.logger { } val metadata - get() = (OpenID4VCI.createDefaultProviderMetadata(baseUrl, config.credentialConfigurationsSupported, OpenID4VCIVersion.D13) as OpenIDProviderMetadataD13) + get() = (OpenID4VCI.createDefaultProviderMetadata(baseUrl, config.credentialConfigurationsSupported, OpenID4VCIVersion.D13) as OpenIDProviderMetadata.Draft13) val metadataD10 - get() = (OpenID4VCI.createDefaultProviderMetadata(baseUrlD10, config.credentialConfigurationsSupported, OpenID4VCIVersion.D10) as OpenIDProviderMetadataD10) + get() = (OpenID4VCI.createDefaultProviderMetadata(baseUrlD10, config.credentialConfigurationsSupported, OpenID4VCIVersion.D10) as OpenIDProviderMetadata.Draft10) companion object { diff --git a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/CredentialOfferProcessor.kt b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/CredentialOfferProcessor.kt index 04fe634e3..5dea4aa2a 100644 --- a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/CredentialOfferProcessor.kt +++ b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/CredentialOfferProcessor.kt @@ -1,7 +1,6 @@ package id.walt.webwallet.service.exchange import id.walt.oid4vc.data.OpenIDProviderMetadata -import id.walt.oid4vc.data.OpenIDProviderMetadataD13 import id.walt.oid4vc.requests.BatchCredentialRequest import id.walt.oid4vc.requests.CredentialRequest import id.walt.oid4vc.responses.BatchCredentialResponse @@ -31,7 +30,7 @@ object CredentialOfferProcessor { accessToken: String, ): List { - providerMetadata as OpenIDProviderMetadataD13 + providerMetadata as OpenIDProviderMetadata.Draft13 val batchCredentialRequest = BatchCredentialRequest(credReqs) @@ -56,7 +55,7 @@ object CredentialOfferProcessor { providerMetadata: OpenIDProviderMetadata, accessToken: String, ): List { - providerMetadata as OpenIDProviderMetadataD13 + providerMetadata as OpenIDProviderMetadata.Draft13 val credReq = credReqs.first() diff --git a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/IssuanceService.kt b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/IssuanceService.kt index 0d7c0378f..f644c322b 100644 --- a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/IssuanceService.kt +++ b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/IssuanceService.kt @@ -65,7 +65,7 @@ object IssuanceService: IssuanceServiceBase() { val providerMetadata = getCredentialIssuerOpenIDMetadata( credentialOffer.credentialIssuer, credentialWallet, - ) as OpenIDProviderMetadataD13 + ) as OpenIDProviderMetadata.Draft13 logger.debug { "providerMetadata: $providerMetadata" } diff --git a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/IssuanceServiceExternalSignatures.kt b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/IssuanceServiceExternalSignatures.kt index 4ba6c9716..639b76c77 100644 --- a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/IssuanceServiceExternalSignatures.kt +++ b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/IssuanceServiceExternalSignatures.kt @@ -53,7 +53,7 @@ object IssuanceServiceExternalSignatures : IssuanceServiceBase() { val providerMetadata = getCredentialIssuerOpenIDMetadata( credentialOffer.credentialIssuer, credentialWallet, - ) as OpenIDProviderMetadataD13 + ) as OpenIDProviderMetadata.Draft13 logger.debug { "providerMetadata: $providerMetadata" } diff --git a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/oidc4vc/TestCredentialWallet.kt b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/oidc4vc/TestCredentialWallet.kt index 05599078f..d06ad4d6e 100644 --- a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/oidc4vc/TestCredentialWallet.kt +++ b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/oidc4vc/TestCredentialWallet.kt @@ -26,7 +26,7 @@ import id.walt.mdoc.docrequest.MDocRequestBuilder import id.walt.mdoc.mdocauth.DeviceAuthentication import id.walt.oid4vc.OpenID4VP import id.walt.oid4vc.data.CredentialFormat -import id.walt.oid4vc.data.OpenIDProviderMetadataD13 +import id.walt.oid4vc.data.OpenIDProviderMetadata import id.walt.oid4vc.data.dif.DescriptorMapping import id.walt.oid4vc.data.dif.PresentationDefinition import id.walt.oid4vc.data.dif.PresentationSubmission @@ -333,7 +333,7 @@ class TestCredentialWallet( } override val metadata - get() = createDefaultProviderMetadata() as OpenIDProviderMetadataD13 + get() = createDefaultProviderMetadata() as OpenIDProviderMetadata.Draft13 override fun createSIOPSession( id: String, From aab9b18964e78862ad087de42f6da994aaa4c494 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Thu, 14 Nov 2024 17:16:36 +0200 Subject: [PATCH 14/53] fix: issuer metadata error in test cases --- .../waltid-e2e-tests/src/test/kotlin/LspPotentialIssuance.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialIssuance.kt b/waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialIssuance.kt index d4f5d1474..d45a53ffa 100644 --- a/waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialIssuance.kt +++ b/waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialIssuance.kt @@ -30,6 +30,7 @@ import id.walt.oid4vc.requests.TokenRequest import id.walt.oid4vc.responses.CredentialResponse import id.walt.oid4vc.responses.TokenResponse import id.walt.oid4vc.util.randomUUID +import id.walt.sdjwt.JWTVCIssuerMetadata import id.walt.sdjwt.SDJwtVC import id.walt.verifier.lspPotential.LspPotentialVerificationInterop import io.ktor.client.* @@ -252,7 +253,7 @@ class LspPotentialIssuance(val client: HttpClient) { val oAuthMetadataUri = OpenID4VCI.getOAuthProviderMetadataUrl(parsedOffer.credentialIssuer) val providerMetadata = client.get(providerMetadataUri).bodyAsText().let { OpenIDProviderMetadata.fromJSONString(it) } as OpenIDProviderMetadata.Draft13 val oauthMetadata = client.get(oAuthMetadataUri).body() - val jwtIssuerMetadata = client.get(jwtIssuerMetadataUri).body() + val jwtIssuerMetadata = client.get(jwtIssuerMetadataUri).body() assertNotNull(providerMetadata.credentialConfigurationsSupported) assertNotNull(providerMetadata.credentialEndpoint) assertNotNull(jwtIssuerMetadata.issuer) From b6d959d755efe0a72472747f5a4935b76a40459e Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Fri, 15 Nov 2024 20:43:17 +0200 Subject: [PATCH 15/53] refactor: issuer metadata class, serialize polymorphic function --- .../oid4vc/data/OpenIDProviderMetadata.kt | 288 +++++++++++------- 1 file changed, 180 insertions(+), 108 deletions(-) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt index abb850cc5..3173bf8d1 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt @@ -7,7 +7,13 @@ import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.buildClassSerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.polymorphic import kotlinx.serialization.json.* +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonDecoder +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.ClassDiscriminatorMode /** * OpenID Provider metadata object, according to @@ -58,68 +64,125 @@ import kotlinx.serialization.json.* * @param opTosUri OPTIONAL. URL that the OpenID Provider provides to the person registering the Client to read about OpenID Provider's terms of service. The registration process SHOULD display this URL to the person registering the Client if it is given. */ @Serializable -sealed class OpenIDProviderMetadata : JsonDataObject() { + +sealed class OpenIDProviderMetadata() : JsonDataObject() { + abstract val issuer: String? + abstract val authorizationEndpoint: String? + abstract val pushedAuthorizationRequestEndpoint: String? + abstract val tokenEndpoint: String? + abstract val userinfoEndpoint: String? + abstract val jwksUri: String? + abstract val registrationEndpoint: String? + abstract val scopesSupported: Set + abstract val responseTypesSupported: Set? + abstract val responseModesSupported: Set + abstract val grantTypesSupported: Set + abstract val acrValuesSupported: Set? + abstract val subjectTypesSupported: Set? + abstract val idTokenSigningAlgValuesSupported: Set? + abstract val idTokenEncryptionAlgValuesSupported: Set? + abstract val idTokenEncryptionEncValuesSupported: Set? + abstract val userinfoSigningAlgValuesSupported: Set? + abstract val userinfoEncryptionAlgValuesSupported: Set? + abstract val userinfoEncryptionEncValuesSupported: Set? + abstract val requestObjectSigningAlgValuesSupported: Set? + abstract val requestObjectEncryptionAlgValuesSupported: Set? + abstract val requestObjectEncryptionEncValuesSupported: Set? + abstract val tokenEndpointAuthMethodsSupported: Set? + abstract val tokenEndpointAuthSigningAlgValuesSupported: Set? + abstract val displayValuesSupported: Set? + abstract val claimTypesSupported: Set? + abstract val claimsSupported: Set? + abstract val serviceDocumentation: String? + abstract val claimsLocalesSupported: Set? + abstract val uiLocalesSupported: Set? + abstract val claimsParameterSupported: Boolean + abstract val requestParameterSupported: Boolean + abstract val requestUriParameterSupported: Boolean + abstract val requireRequestUriRegistration: Boolean + abstract val opPolicyUri: String? + abstract val opTosUri: String? + + // OID4VCI properties + abstract val credentialIssuer: String? + abstract val credentialEndpoint: String? + abstract val batchCredentialEndpoint: String? + abstract val deferredCredentialEndpoint: String? + abstract val authorizationServers: Set? + abstract val display: List? + abstract val presentationDefinitionUriSupported: Boolean? + abstract val clientIdSchemesSupported: List? + abstract val authorizationServer: String? + abstract val codeChallengeMethodsSupported: List? + abstract val requirePushedAuthorizationRequests: Boolean? + abstract val dpopSigningAlgValuesSupported: Set? + @Serializable data class Draft10( - @SerialName("issuer") val issuer: String? = null, - @SerialName("authorization_endpoint") val authorizationEndpoint: String? = null, - @SerialName("pushed_authorization_request_endpoint") val pushedAuthorizationRequestEndpoint: String? = null, - @SerialName("token_endpoint") val tokenEndpoint: String? = null, - @SerialName("userinfo_endpoint") val userinfoEndpoint: String? = null, - @SerialName("jwks_uri") val jwksUri: String? = null, - @SerialName("registration_endpoint") val registrationEndpoint: String? = null, - @EncodeDefault @SerialName("scopes_supported") val scopesSupported: Set = setOf("openid"), - @SerialName("response_types_supported") val responseTypesSupported: Set? = null, - @EncodeDefault @SerialName("response_modes_supported") val responseModesSupported: Set = setOf( + @SerialName("issuer") override val issuer: String? = null, + @SerialName("authorization_endpoint") override val authorizationEndpoint: String? = null, + @SerialName("pushed_authorization_request_endpoint") override val pushedAuthorizationRequestEndpoint: String? = null, + @SerialName("token_endpoint") override val tokenEndpoint: String? = null, + @SerialName("userinfo_endpoint") override val userinfoEndpoint: String? = null, + @SerialName("jwks_uri") override val jwksUri: String? = null, + @SerialName("registration_endpoint") override val registrationEndpoint: String? = null, + @EncodeDefault @SerialName("scopes_supported") override val scopesSupported: Set = setOf("openid"), + @SerialName("response_types_supported") override val responseTypesSupported: Set? = null, + @EncodeDefault @SerialName("response_modes_supported") override val responseModesSupported: Set = setOf( ResponseMode.query, ResponseMode.fragment ), - @EncodeDefault @SerialName("grant_types_supported") @Serializable(GrantTypeSetSerializer::class) val grantTypesSupported: Set = setOf( + @EncodeDefault @SerialName("grant_types_supported") @Serializable(GrantTypeSetSerializer::class) override val grantTypesSupported: Set = setOf( GrantType.authorization_code, GrantType.pre_authorized_code ), - @SerialName("acr_values_supported") val acrValuesSupported: Set? = null, - @SerialName("subject_types_supported") val subjectTypesSupported: Set? = null, - @SerialName("id_token_signing_alg_values_supported") val idTokenSigningAlgValuesSupported: Set? = null, - @SerialName("id_token_encryption_alg_values_supported") val idTokenEncryptionAlgValuesSupported: Set? = null, - @SerialName("id_token_encryption_enc_values_supported") val idTokenEncryptionEncValuesSupported: Set? = null, - @SerialName("userinfo_signing_alg_values_supported") val userinfoSigningAlgValuesSupported: Set? = null, - @SerialName("userinfo_encryption_alg_values_supported") val userinfoEncryptionAlgValuesSupported: Set? = null, - @SerialName("userinfo_encryption_enc_values_supported") val userinfoEncryptionEncValuesSupported: Set? = null, - @SerialName("request_object_signing_alg_values_supported") val requestObjectSigningAlgValuesSupported: Set? = null, - @SerialName("request_object_encryption_alg_values_supported") val requestObjectEncryptionAlgValuesSupported: Set? = null, - @SerialName("request_object_encryption_enc_values_supported") val requestObjectEncryptionEncValuesSupported: Set? = null, - @SerialName("token_endpoint_auth_methods_supported") val tokenEndpointAuthMethodsSupported: Set? = null, - @SerialName("token_endpoint_auth_signing_alg_values_supported") val tokenEndpointAuthSigningAlgValuesSupported: Set? = null, - @SerialName("display_values_supported") val displayValuesSupported: Set? = null, - @SerialName("claim_types_supported") val claimTypesSupported: Set? = null, - @SerialName("claims_supported") val claimsSupported: Set? = null, - @SerialName("service_documentation") val serviceDocumentation: String? = null, - @SerialName("claims_locales_supported") val claimsLocalesSupported: Set? = null, - @SerialName("ui_locales_supported") val uiLocalesSupported: Set? = null, - @SerialName("claims_parameter_supported") val claimsParameterSupported: Boolean = false, - @SerialName("request_parameter_supported") val requestParameterSupported: Boolean = false, - @SerialName("request_uri_parameter_supported") val requestUriParameterSupported: Boolean = true, - @SerialName("require_request_uri_registration") val requireRequestUriRegistration: Boolean = false, - @SerialName("op_policy_uri") val opPolicyUri: String? = null, - @SerialName("op_tos_uri") val opTosUri: String? = null, + @SerialName("acr_values_supported") override val acrValuesSupported: Set? = null, + @SerialName("subject_types_supported") override val subjectTypesSupported: Set? = null, + @SerialName("id_token_signing_alg_values_supported") override val idTokenSigningAlgValuesSupported: Set? = null, + @SerialName("id_token_encryption_alg_values_supported") override val idTokenEncryptionAlgValuesSupported: Set? = null, + @SerialName("id_token_encryption_enc_values_supported") override val idTokenEncryptionEncValuesSupported: Set? = null, + @SerialName("userinfo_signing_alg_values_supported") override val userinfoSigningAlgValuesSupported: Set? = null, + @SerialName("userinfo_encryption_alg_values_supported") override val userinfoEncryptionAlgValuesSupported: Set? = null, + @SerialName("userinfo_encryption_enc_values_supported") override val userinfoEncryptionEncValuesSupported: Set? = null, + @SerialName("request_object_signing_alg_values_supported") override val requestObjectSigningAlgValuesSupported: Set? = null, + @SerialName("request_object_encryption_alg_values_supported") override val requestObjectEncryptionAlgValuesSupported: Set? = null, + @SerialName("request_object_encryption_enc_values_supported") override val requestObjectEncryptionEncValuesSupported: Set? = null, + @SerialName("token_endpoint_auth_methods_supported") override val tokenEndpointAuthMethodsSupported: Set? = null, + @SerialName("token_endpoint_auth_signing_alg_values_supported") override val tokenEndpointAuthSigningAlgValuesSupported: Set? = null, + @SerialName("display_values_supported") override val displayValuesSupported: Set? = null, + @SerialName("claim_types_supported")override val claimTypesSupported: Set? = null, + @SerialName("claims_supported")override val claimsSupported: Set? = null, + @SerialName("service_documentation") override val serviceDocumentation: String? = null, + @SerialName("claims_locales_supported") override val claimsLocalesSupported: Set? = null, + @SerialName("ui_locales_supported") override val uiLocalesSupported: Set? = null, + @SerialName("claims_parameter_supported") override val claimsParameterSupported: Boolean = false, + @SerialName("request_parameter_supported") override val requestParameterSupported: Boolean = false, + @SerialName("request_uri_parameter_supported") override val requestUriParameterSupported: Boolean = true, + @SerialName("require_request_uri_registration") override val requireRequestUriRegistration: Boolean = false, + @SerialName("op_policy_uri") override val opPolicyUri: String? = null, + @SerialName("op_tos_uri") override val opTosUri: String? = null, + // OID4VCI properties - @SerialName("credential_issuer") val credentialIssuer: String? = null, - @SerialName("credential_endpoint") val credentialEndpoint: String? = null, + @SerialName("credential_issuer") override val credentialIssuer: String? = null, + @SerialName("credential_endpoint") override val credentialEndpoint: String? = null, + @SerialName("batch_credential_endpoint") override val batchCredentialEndpoint: String? = null, + @SerialName("deferred_credential_endpoint") override val deferredCredentialEndpoint: String? = null, + @SerialName("authorization_servers") override val authorizationServers: Set? = null, + @SerialName("display") @Serializable(DisplayPropertiesListSerializer::class) override val display: List? = null, + @SerialName("presentation_definition_uri_supported") override val presentationDefinitionUriSupported: Boolean? = null, + @SerialName("client_id_schemes_supported") override val clientIdSchemesSupported: List? = null, + @SerialName("authorization_server") override val authorizationServer: String? = authorizationServers?.firstOrNull(), + @SerialName("code_challenge_methods_supported") override val codeChallengeMethodsSupported: List? = null, + @SerialName("require_pushed_authorization_requests") override val requirePushedAuthorizationRequests: Boolean? = null, + @SerialName("dpop_signing_alg_values_supported") override val dpopSigningAlgValuesSupported: Set? = null, + + // OID4VCI 10 @SerialName("credentials_supported") @Serializable(CredentialSupportedMapSerializer::class) val credentialSupported: Map? = null, - @SerialName("batch_credential_endpoint") val batchCredentialEndpoint: String? = null, - @SerialName("deferred_credential_endpoint") val deferredCredentialEndpoint: String? = null, - @SerialName("authorization_servers") val authorizationServers: Set? = null, - @SerialName("display") @Serializable(DisplayPropertiesListSerializer::class) val display: List? = null, - @SerialName("presentation_definition_uri_supported") val presentationDefinitionUriSupported: Boolean? = null, - @SerialName("client_id_schemes_supported") val clientIdSchemesSupported: List? = null, - @SerialName("authorization_server") val authorizationServer: String? = authorizationServers?.firstOrNull(), // Move here since if we have a null value for this parameter, the discovery fails - @SerialName("code_challenge_methods_supported") val codeChallengeMethodsSupported: List? = null, - @SerialName("require_pushed_authorization_requests") val requirePushedAuthorizationRequests: Boolean? = null, - @SerialName("dpop_signing_alg_values_supported") val dpopSigningAlgValuesSupported: Set? = null, + override val customParameters: Map = mapOf() - ) : OpenIDProviderMetadata() { + + ) : OpenIDProviderMetadata() { override fun toJSON(): JsonObject { val originalJson = super.toJSON().toMutableMap() @@ -139,63 +202,68 @@ sealed class OpenIDProviderMetadata : JsonDataObject() { @Serializable data class Draft13( - @SerialName("issuer") val issuer: String? = null, - @SerialName("authorization_endpoint") val authorizationEndpoint: String? = null, - @SerialName("pushed_authorization_request_endpoint") val pushedAuthorizationRequestEndpoint: String? = null, - @SerialName("token_endpoint") val tokenEndpoint: String? = null, - @SerialName("userinfo_endpoint") val userinfoEndpoint: String? = null, - @SerialName("jwks_uri") val jwksUri: String? = null, - @SerialName("registration_endpoint") val registrationEndpoint: String? = null, - @EncodeDefault @SerialName("scopes_supported") val scopesSupported: Set = setOf("openid"), - @SerialName("response_types_supported") val responseTypesSupported: Set? = null, - @EncodeDefault @SerialName("response_modes_supported") val responseModesSupported: Set = setOf( + @SerialName("issuer") override val issuer: String? = null, + @SerialName("authorization_endpoint") override val authorizationEndpoint: String? = null, + @SerialName("pushed_authorization_request_endpoint") override val pushedAuthorizationRequestEndpoint: String? = null, + @SerialName("token_endpoint") override val tokenEndpoint: String? = null, + @SerialName("userinfo_endpoint") override val userinfoEndpoint: String? = null, + @SerialName("jwks_uri") override val jwksUri: String? = null, + @SerialName("registration_endpoint") override val registrationEndpoint: String? = null, + @EncodeDefault @SerialName("scopes_supported") override val scopesSupported: Set = setOf("openid"), + @SerialName("response_types_supported") override val responseTypesSupported: Set? = null, + @EncodeDefault @SerialName("response_modes_supported") override val responseModesSupported: Set = setOf( ResponseMode.query, ResponseMode.fragment ), - @EncodeDefault @SerialName("grant_types_supported") @Serializable(GrantTypeSetSerializer::class) val grantTypesSupported: Set = setOf( + @EncodeDefault @SerialName("grant_types_supported") @Serializable(GrantTypeSetSerializer::class) override val grantTypesSupported: Set = setOf( GrantType.authorization_code, GrantType.pre_authorized_code ), - @SerialName("acr_values_supported") val acrValuesSupported: Set? = null, - @SerialName("subject_types_supported") val subjectTypesSupported: Set? = null, - @SerialName("id_token_signing_alg_values_supported") val idTokenSigningAlgValuesSupported: Set? = null, - @SerialName("id_token_encryption_alg_values_supported") val idTokenEncryptionAlgValuesSupported: Set? = null, - @SerialName("id_token_encryption_enc_values_supported") val idTokenEncryptionEncValuesSupported: Set? = null, - @SerialName("userinfo_signing_alg_values_supported") val userinfoSigningAlgValuesSupported: Set? = null, - @SerialName("userinfo_encryption_alg_values_supported") val userinfoEncryptionAlgValuesSupported: Set? = null, - @SerialName("userinfo_encryption_enc_values_supported") val userinfoEncryptionEncValuesSupported: Set? = null, - @SerialName("request_object_signing_alg_values_supported") val requestObjectSigningAlgValuesSupported: Set? = null, - @SerialName("request_object_encryption_alg_values_supported") val requestObjectEncryptionAlgValuesSupported: Set? = null, - @SerialName("request_object_encryption_enc_values_supported") val requestObjectEncryptionEncValuesSupported: Set? = null, - @SerialName("token_endpoint_auth_methods_supported") val tokenEndpointAuthMethodsSupported: Set? = null, - @SerialName("token_endpoint_auth_signing_alg_values_supported") val tokenEndpointAuthSigningAlgValuesSupported: Set? = null, - @SerialName("display_values_supported") val displayValuesSupported: Set? = null, - @SerialName("claim_types_supported") val claimTypesSupported: Set? = null, - @SerialName("claims_supported") val claimsSupported: Set? = null, - @SerialName("service_documentation") val serviceDocumentation: String? = null, - @SerialName("claims_locales_supported") val claimsLocalesSupported: Set? = null, - @SerialName("ui_locales_supported") val uiLocalesSupported: Set? = null, - @SerialName("claims_parameter_supported") val claimsParameterSupported: Boolean = false, - @SerialName("request_parameter_supported") val requestParameterSupported: Boolean = false, - @SerialName("request_uri_parameter_supported") val requestUriParameterSupported: Boolean = true, - @SerialName("require_request_uri_registration") val requireRequestUriRegistration: Boolean = false, - @SerialName("op_policy_uri") val opPolicyUri: String? = null, - @SerialName("op_tos_uri") val opTosUri: String? = null, + @SerialName("acr_values_supported") override val acrValuesSupported: Set? = null, + @SerialName("subject_types_supported") override val subjectTypesSupported: Set? = null, + @SerialName("id_token_signing_alg_values_supported") override val idTokenSigningAlgValuesSupported: Set? = null, + @SerialName("id_token_encryption_alg_values_supported") override val idTokenEncryptionAlgValuesSupported: Set? = null, + @SerialName("id_token_encryption_enc_values_supported") override val idTokenEncryptionEncValuesSupported: Set? = null, + @SerialName("userinfo_signing_alg_values_supported") override val userinfoSigningAlgValuesSupported: Set? = null, + @SerialName("userinfo_encryption_alg_values_supported") override val userinfoEncryptionAlgValuesSupported: Set? = null, + @SerialName("userinfo_encryption_enc_values_supported") override val userinfoEncryptionEncValuesSupported: Set? = null, + @SerialName("request_object_signing_alg_values_supported") override val requestObjectSigningAlgValuesSupported: Set? = null, + @SerialName("request_object_encryption_alg_values_supported") override val requestObjectEncryptionAlgValuesSupported: Set? = null, + @SerialName("request_object_encryption_enc_values_supported") override val requestObjectEncryptionEncValuesSupported: Set? = null, + @SerialName("token_endpoint_auth_methods_supported") override val tokenEndpointAuthMethodsSupported: Set? = null, + @SerialName("token_endpoint_auth_signing_alg_values_supported") override val tokenEndpointAuthSigningAlgValuesSupported: Set? = null, + @SerialName("display_values_supported") override val displayValuesSupported: Set? = null, + @SerialName("claim_types_supported")override val claimTypesSupported: Set? = null, + @SerialName("claims_supported")override val claimsSupported: Set? = null, + @SerialName("service_documentation") override val serviceDocumentation: String? = null, + @SerialName("claims_locales_supported") override val claimsLocalesSupported: Set? = null, + @SerialName("ui_locales_supported") override val uiLocalesSupported: Set? = null, + @SerialName("claims_parameter_supported") override val claimsParameterSupported: Boolean = false, + @SerialName("request_parameter_supported") override val requestParameterSupported: Boolean = false, + @SerialName("request_uri_parameter_supported") override val requestUriParameterSupported: Boolean = true, + @SerialName("require_request_uri_registration") override val requireRequestUriRegistration: Boolean = false, + @SerialName("op_policy_uri") override val opPolicyUri: String? = null, + @SerialName("op_tos_uri") override val opTosUri: String? = null, + // OID4VCI properties - @SerialName("credential_issuer") val credentialIssuer: String? = null, - @SerialName("credential_endpoint") val credentialEndpoint: String? = null, + @SerialName("credential_issuer") override val credentialIssuer: String? = null, + @SerialName("credential_endpoint") override val credentialEndpoint: String? = null, + @SerialName("batch_credential_endpoint") override val batchCredentialEndpoint: String? = null, + @SerialName("deferred_credential_endpoint") override val deferredCredentialEndpoint: String? = null, + @SerialName("authorization_servers") override val authorizationServers: Set? = null, + @SerialName("display") @Serializable(DisplayPropertiesListSerializer::class) override val display: List? = null, + @SerialName("presentation_definition_uri_supported") override val presentationDefinitionUriSupported: Boolean? = null, + @SerialName("client_id_schemes_supported") override val clientIdSchemesSupported: List? = null, + @SerialName("authorization_server") override val authorizationServer: String? = authorizationServers?.firstOrNull(), + @SerialName("code_challenge_methods_supported") override val codeChallengeMethodsSupported: List? = null, + @SerialName("require_pushed_authorization_requests") override val requirePushedAuthorizationRequests: Boolean? = null, + @SerialName("dpop_signing_alg_values_supported") override val dpopSigningAlgValuesSupported: Set? = null, + + // OID4VCI 13 @SerialName("credential_configurations_supported") @Serializable(CredentialSupportedMapSerializer::class) val credentialConfigurationsSupported: Map? = null, - @SerialName("batch_credential_endpoint") val batchCredentialEndpoint: String? = null, - @SerialName("deferred_credential_endpoint") val deferredCredentialEndpoint: String? = null, - @SerialName("authorization_servers") val authorizationServers: Set? = null, - @SerialName("display") @Serializable(DisplayPropertiesListSerializer::class) val display: List? = null, - @SerialName("presentation_definition_uri_supported") val presentationDefinitionUriSupported: Boolean? = null, - @SerialName("client_id_schemes_supported") val clientIdSchemesSupported: List? = null, - @SerialName("authorization_server") val authorizationServer: String? = authorizationServers?.firstOrNull(), // Move here since if we have a null value for this parameter, the discovery fails - @SerialName("code_challenge_methods_supported") val codeChallengeMethodsSupported: List? = null, - @SerialName("require_pushed_authorization_requests") val requirePushedAuthorizationRequests: Boolean? = null, - @SerialName("dpop_signing_alg_values_supported") val dpopSigningAlgValuesSupported: Set? = null, + override val customParameters: Map = mapOf() + ) : OpenIDProviderMetadata() { // TODO: make them abstract in the sealed class @@ -230,11 +298,11 @@ object OpenIDProviderMetadataSerializer : KSerializer { override val descriptor: SerialDescriptor = buildClassSerialDescriptor("OpenIDProviderMetadata") override fun deserialize(decoder: Decoder): OpenIDProviderMetadata { - val jsonDecoder = decoder as? JsonDecoder - ?: throw IllegalStateException("This class can only be deserialized with JSON") + val jsonDecoder = decoder as? JsonDecoder ?: throw IllegalStateException("Invalid Decoder") val jsonObject = jsonDecoder.decodeJsonElement().jsonObject + // TODO: () return when { "credentials_supported" in jsonObject -> Json.decodeFromJsonElement(OpenIDProviderMetadata.Draft10.serializer(), jsonObject) "credential_configurations_supported" in jsonObject -> Json.decodeFromJsonElement(OpenIDProviderMetadata.Draft13.serializer(), jsonObject) @@ -242,15 +310,19 @@ object OpenIDProviderMetadataSerializer : KSerializer { } } - override fun serialize(encoder: Encoder, value: OpenIDProviderMetadata) { - val jsonEncoder = encoder as? JsonEncoder - ?: throw IllegalStateException("This class can only be serialized with JSON") - - val jsonElement: JsonElement = when (value) { - is OpenIDProviderMetadata.Draft10 -> Json.encodeToJsonElement(OpenIDProviderMetadata.Draft10.serializer(), value) - is OpenIDProviderMetadata.Draft13 -> Json.encodeToJsonElement(OpenIDProviderMetadata.Draft13.serializer(), value) + private val OpenIDProviderMetadataSerializersModule = SerializersModule { + polymorphic(OpenIDProviderMetadata::class) { + subclass(OpenIDProviderMetadata.Draft10::class, OpenIDProviderMetadata.Draft10.serializer()) + subclass(OpenIDProviderMetadata.Draft13::class, OpenIDProviderMetadata.Draft13.serializer()) } + } - jsonEncoder.encodeJsonElement(jsonElement) + override fun serialize(encoder: Encoder, value: OpenIDProviderMetadata) { + val json by lazy { Json { serializersModule = OpenIDProviderMetadataSerializersModule; classDiscriminatorMode = ClassDiscriminatorMode.NONE } } + val jsonElement = json.encodeToJsonElement(OpenIDProviderMetadata.serializer(), value) + encoder as? JsonEncoder ?: throw IllegalStateException("Invalid Encoder") + encoder.encodeJsonElement(jsonElement) } + } + From 1741fb7b9021086e8e8c34456bc1c58945ddf436 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Wed, 20 Nov 2024 13:28:00 +0200 Subject: [PATCH 16/53] feat: support for VCI Draft10 and Draft13 credential Offers --- .../kotlin/id/walt/oid4vc/OpenID4VCI.kt | 23 ++-- .../id/walt/oid4vc/data/CredentialOffer.kt | 116 +++++++++++++++--- .../kotlin/id/walt/issuer/NewIssuanceStub.kt | 2 +- .../id/walt/issuer/issuance/CIProvider.kt | 3 +- .../id/walt/issuer/issuance/IssuerApi.kt | 3 +- .../kotlin/id/walt/issuer/issuance/OidcApi.kt | 9 +- .../id/walt/issuer/issuance/OidcIssuance.kt | 16 ++- .../issuer/issuance2/IssuanceOfferManager.kt | 4 +- .../exchange/ExchangeController.kt | 2 +- 9 files changed, 141 insertions(+), 37 deletions(-) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt index 84bc49b4a..d6582ea6e 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt @@ -99,13 +99,19 @@ object OpenID4VCI { suspend fun parseAndResolveCredentialOfferRequestUrl(credOfferReqUrl: String): CredentialOffer { val offerReq = parseCredentialOfferRequestUrl(credOfferReqUrl) - return offerReq.credentialOffer - ?: if (!offerReq.credentialOfferUri.isNullOrEmpty()) { + return when { + + offerReq.credentialOfferUri != null -> { http.get(offerReq.credentialOfferUri).bodyAsText().let { CredentialOffer.fromJSONString(it) } - } else throw Exception("Credential offer request has no credential offer object set by value or reference.") + } + + offerReq.credentialOffer != null -> offerReq.credentialOffer + + else -> throw IllegalStateException("Credential Offer does not contain a Credential Offer Object nor a Credential Offer URI") + } } fun getCIProviderMetadataUrl(credOffer: CredentialOffer): String { @@ -138,10 +144,13 @@ object OpenID4VCI { is OpenIDProviderMetadata.Draft13 -> providerMetadata.credentialConfigurationsSupported ?: mapOf() } - return credentialOffer.credentialConfigurationIds.mapNotNull { c -> - supportedCredentials[c]?.let { - OfferedCredential.fromProviderMetadata(it) - } + val credentialIds = when (credentialOffer) { + is CredentialOffer.Draft13 -> credentialOffer.credentialConfigurationIds + is CredentialOffer.Draft10 -> credentialOffer.credentials + } + + return credentialIds.mapNotNull { id -> + supportedCredentials[id]?.let { OfferedCredential.fromProviderMetadata(it) } } } diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/CredentialOffer.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/CredentialOffer.kt index b83404590..521626b32 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/CredentialOffer.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/CredentialOffer.kt @@ -1,24 +1,28 @@ package id.walt.oid4vc.data +import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.SerializationException +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.* +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.polymorphic @Serializable -data class CredentialOffer private constructor( - @SerialName("credential_issuer") val credentialIssuer: String, - @SerialName("credential_configuration_ids") val credentialConfigurationIds: Set, - val grants: Map = mapOf(), - override val customParameters: Map = mapOf() -) : JsonDataObject() { - override fun toJSON() = Json.encodeToJsonElement(CredentialOfferSerializer, this).jsonObject +sealed class CredentialOffer() : JsonDataObject() { + abstract val grants: Map + abstract val credentialIssuer: String + + abstract class Builder( + protected val credentialIssuer: String + ) { + protected val supportedCredentialIds = mutableSetOf() + protected val grants = mutableMapOf() - class Builder(private val credentialIssuer: String) { - private val supportedCredentialIds = mutableSetOf() - private val grants = mutableMapOf() fun addOfferedCredential(supportedCredentialId: String) = this.also { supportedCredentialIds.add(supportedCredentialId) } @@ -32,13 +36,93 @@ data class CredentialOffer private constructor( GrantDetails(preAuthorizedCode = preAuthCode, txCode = txCode, interval = interval, authorizationServer = authorizationServer) } - fun build() = CredentialOffer(credentialIssuer, supportedCredentialIds, grants) + protected abstract fun buildInternal(): T + + fun build(): T = buildInternal() } + override fun toJSON() = Json.encodeToJsonElement(CredentialOfferSerializer, this).jsonObject + companion object : JsonDataObjectFactory() { override fun fromJSON(jsonObject: JsonObject) = Json.decodeFromJsonElement(CredentialOfferSerializer, jsonObject) } + + @Serializable + data class Draft13 ( + @SerialName("credential_issuer") override val credentialIssuer: String, + @SerialName("grants") override val grants: Map = mapOf(), + + @SerialName("credential_configuration_ids") val credentialConfigurationIds: Set, + + override val customParameters: Map = mapOf() + ) : CredentialOffer() { + + class Builder(credentialIssuer: String) : CredentialOffer.Builder(credentialIssuer) { + + override fun buildInternal() = Draft13( + credentialIssuer = credentialIssuer, + grants = grants, + credentialConfigurationIds = supportedCredentialIds + ) + + } + + } + + @Serializable + data class Draft10 ( + @SerialName("credential_issuer") override val credentialIssuer: String, + @SerialName("grants") override val grants: Map = mapOf(), + + @SerialName("credentials") val credentials: Set, + + override val customParameters: Map = mapOf() + ) : CredentialOffer() { + + class Builder(credentialIssuer: String) : CredentialOffer.Builder(credentialIssuer) { + + override fun buildInternal() = Draft10( + credentialIssuer = credentialIssuer, + grants = grants, + credentials = supportedCredentialIds + ) + + } + + } + } -object CredentialOfferSerializer : JsonDataObjectSerializer(CredentialOffer.serializer()) + +object CredentialOfferSerializer : KSerializer { + + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("CredentialOffer") + + override fun deserialize(decoder: Decoder): CredentialOffer { + val jsonDecoder = decoder as? JsonDecoder ?: throw SerializationException("Invalid Decoder") + + val jsonObject = jsonDecoder.decodeJsonElement().jsonObject + + // TODO: () + return when { + "credential_configuration_ids" in jsonObject -> Json.decodeFromJsonElement(CredentialOffer.Draft13.serializer(), jsonObject) + "credentials" in jsonObject -> Json.decodeFromJsonElement(CredentialOffer.Draft10.serializer(), jsonObject) + else -> throw IllegalArgumentException("Unknown CredentialOffer type: missing expected fields") + } + } + + private val CredentialOfferSerializersModule = SerializersModule { + polymorphic(CredentialOffer::class) { + subclass(CredentialOffer.Draft10::class, CredentialOffer.Draft10.serializer()) + subclass(CredentialOffer.Draft13::class, CredentialOffer.Draft13.serializer()) + } + } + + override fun serialize(encoder: Encoder, value: CredentialOffer) { + val json by lazy { Json { serializersModule = CredentialOfferSerializersModule; classDiscriminatorMode = ClassDiscriminatorMode.NONE } } + val jsonElement = json.encodeToJsonElement(CredentialOffer.serializer(), value) + encoder as? JsonEncoder ?: throw IllegalStateException("Invalid Encoder") + encoder.encodeJsonElement(jsonElement) + } +} \ No newline at end of file diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/NewIssuanceStub.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/NewIssuanceStub.kt index 04dfff12a..6accc6d9e 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/NewIssuanceStub.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/NewIssuanceStub.kt @@ -97,7 +97,7 @@ suspend fun main() { - val credOffer = CredentialOffer.Builder(url) + val credOffer = CredentialOffer.Draft13.Builder(url) .addOfferedCredential("VerifiableId") .addAuthorizationCodeGrant("test-state") .build() diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt index 4bdd3fe65..7e99d231e 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt @@ -483,10 +483,11 @@ open class CIProvider( callbackUrl: String? = null, txCode: TxCode? = null, txCodeValue: String? = null, + standardVersion: OpenID4VCIVersion = OpenID4VCIVersion.D13 ): IssuanceSession = runBlocking { val sessionId = randomUUID() - val credentialOfferBuilder = OidcIssuance.issuanceRequestsToCredentialOfferBuilder(issuanceRequests) + val credentialOfferBuilder = OidcIssuance.issuanceRequestsToCredentialOfferBuilder(issuanceRequests, standardVersion ) credentialOfferBuilder.addAuthorizationCodeGrant(sessionId) diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuerApi.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuerApi.kt index 3019eb454..bb38bdf32 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuerApi.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuerApi.kt @@ -48,7 +48,8 @@ suspend fun createCredentialOfferUri( AuthenticationMethod.PRE_AUTHORIZED -> true else -> false }, - callbackUrl = callbackUrl + callbackUrl = callbackUrl, + standardVersion = overwrittenIssuanceRequests.first().standardVersion!! ) logger.debug { "issuanceSession: $issuanceSession" } diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt index da1583d91..7ea3cc34b 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt @@ -164,10 +164,11 @@ object OidcApi : CIProvider() { AuthenticationMethod.ID_TOKEN -> { OpenID4VC.processCodeFlowAuthorizationWithAuthorizationRequest( - authReq, - ResponseType.IdToken, - metadata, CI_TOKEN_KEY, - issuanceSession.issuanceRequests.first().useJar + authorizationRequest = authReq, + responseType = ResponseType.IdToken, + providerMetadata = metadata, + tokenKey = CI_TOKEN_KEY, + isJar = issuanceSession.issuanceRequests.first().useJar ) } diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcIssuance.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcIssuance.kt index 54d7a78a8..7fb06cfda 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcIssuance.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcIssuance.kt @@ -1,6 +1,7 @@ package id.walt.issuer.issuance import id.walt.credentials.vc.vcs.W3CVC +import id.walt.oid4vc.OpenID4VCIVersion import id.walt.oid4vc.data.CredentialOffer import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonPrimitive @@ -19,11 +20,18 @@ object OidcIssuance { } - fun issuanceRequestsToCredentialOfferBuilder(issuanceRequests: List) = - issuanceRequestsToCredentialOfferBuilder(*issuanceRequests.toTypedArray()) + fun issuanceRequestsToCredentialOfferBuilder(issuanceRequests: List, standardVersion: OpenID4VCIVersion) = + issuanceRequestsToCredentialOfferBuilder( + issuanceRequests = *issuanceRequests.toTypedArray(), + standardVersion = standardVersion + ) + + fun issuanceRequestsToCredentialOfferBuilder(vararg issuanceRequests: IssuanceRequest, standardVersion: OpenID4VCIVersion): CredentialOffer.Builder<*> { + val builder = when (standardVersion) { + OpenID4VCIVersion.D13 -> CredentialOffer.Draft13.Builder(OidcApi.baseUrl) + OpenID4VCIVersion.D10 -> CredentialOffer.Draft10.Builder(OidcApi.baseUrl) + } - fun issuanceRequestsToCredentialOfferBuilder(vararg issuanceRequests: IssuanceRequest): CredentialOffer.Builder { - val builder = CredentialOffer.Builder(OidcApi.baseUrl) issuanceRequests.forEach { issuanceRequest -> builder.addOfferedCredential(issuanceRequest.credentialConfigurationId) } diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance2/IssuanceOfferManager.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance2/IssuanceOfferManager.kt index 6d09d5127..864dc9d2e 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance2/IssuanceOfferManager.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance2/IssuanceOfferManager.kt @@ -56,10 +56,10 @@ object IssuanceOfferManager { issuanceRequest.issuer - var offerBuilder = CredentialOffer.Builder(baseUrl) + var offerBuilder = CredentialOffer.Draft13.Builder(baseUrl) issuanceRequest.credential.map { it.credentialData.getType().last() }.forEach { - offerBuilder = offerBuilder.addOfferedCredential(it) + offerBuilder = offerBuilder.addOfferedCredential(it) as CredentialOffer.Draft13.Builder } val sessionId = Uuid.random().toString() diff --git a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/web/controllers/exchange/ExchangeController.kt b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/web/controllers/exchange/ExchangeController.kt index b877dc435..19614d7d3 100644 --- a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/web/controllers/exchange/ExchangeController.kt +++ b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/web/controllers/exchange/ExchangeController.kt @@ -232,7 +232,7 @@ fun Application.exchange() = walletRoute { val request = call.receiveText() val reqParams = Url(request).parameters.toMap() val parsedOffer = wallet.resolveCredentialOffer(CredentialOfferRequest.fromHttpParameters(reqParams)) - context.respond(parsedOffer) + context.respond(parsedOffer as CredentialOffer.Draft13) } get("resolveVctUrl", { summary = "Receive an verifiable credential type (VCT) URL and return resolved vct object as described in IETF SD-JWT VC" From 721f46a520cce501b202c887dd9eef19f0e7b76d Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Wed, 20 Nov 2024 13:36:33 +0200 Subject: [PATCH 17/53] fix: test cases to support for VCI Draft10 and Draft13 credential Offers --- .../src/jvmTest/kotlin/id/walt/oid4vc/CI_JVM_Test.kt | 2 +- .../jvmTest/kotlin/id/walt/oid4vc/OpenID4VCI_Test.kt | 12 ++++++------ .../src/test/kotlin/LspPotentialWallet.kt | 10 ++++++++-- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/CI_JVM_Test.kt b/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/CI_JVM_Test.kt index 1a4959279..336bf9bde 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/CI_JVM_Test.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/CI_JVM_Test.kt @@ -675,7 +675,7 @@ fun testCredentialIssuanceIsolatedFunctionsAuthCodeFlowRedirectWithCode(authCode } fun testIsolatedFunctionsCreateCredentialOffer(baseUrl: String, issuerState: String, issuedCredentialId: String): String { - val credOffer = CredentialOffer.Builder(baseUrl) + val credOffer = CredentialOffer.Draft13.Builder(baseUrl) .addOfferedCredential(issuedCredentialId) .addAuthorizationCodeGrant(issuerState) .build() diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/OpenID4VCI_Test.kt b/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/OpenID4VCI_Test.kt index 06195278f..6fd16af3e 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/OpenID4VCI_Test.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/OpenID4VCI_Test.kt @@ -72,7 +72,7 @@ class OpenID4VCI_Test { fun testCredentialIssuanceIsolatedFunctions() = runTest { println("// -------- CREDENTIAL ISSUER ----------") // init credential offer for full authorization code flow - val credOffer = CredentialOffer.Builder(ISSUER_BASE_URL) + val credOffer = CredentialOffer.Draft13.Builder(ISSUER_BASE_URL) .addOfferedCredential("VerifiableId") .addAuthorizationCodeGrant("test-state") .build() @@ -206,7 +206,7 @@ class OpenID4VCI_Test { // Available authentication methods are: NONE, ID_TOKEN, VP_TOKEN, PWD(Handled by third party authorization server), PRE_AUTHORIZED. The response for each method is a redirect to the proper location. println("// --Authentication method is NONE--") var issuerState = "test-state-none-auth" - var credOffer = CredentialOffer.Builder(ISSUER_BASE_URL) + var credOffer = CredentialOffer.Draft13.Builder(ISSUER_BASE_URL) .addOfferedCredential(issuedCredentialId) .addAuthorizationCodeGrant(issuerState) .build() @@ -322,7 +322,7 @@ class OpenID4VCI_Test { println("// --Authentication method is ID_TOKEN--") issuerState = "test-state-idtoken-auth" val credOfferUrl = testIsolatedFunctionsCreateCredentialOffer(ISSUER_BASE_URL, issuerState, issuedCredentialId) - credOffer = CredentialOfferRequest.fromHttpQueryString(Url(credOfferUrl).encodedQuery).credentialOffer!! + credOffer = CredentialOfferRequest.fromHttpQueryString(Url(credOfferUrl).encodedQuery).credentialOffer!! as CredentialOffer.Draft13 // Issuer Client shows credential offer request as QR code println(OpenID4VCI.getCredentialOfferRequestUrl(credOffer)) @@ -465,7 +465,7 @@ class OpenID4VCI_Test { // ---------------------------------- println("// --Authentication method is VP_TOKEN--") issuerState = "test-state-vptoken-auth" - credOffer = CredentialOffer.Builder(ISSUER_BASE_URL) + credOffer = CredentialOffer.Draft13.Builder(ISSUER_BASE_URL) .addOfferedCredential(issuedCredentialId) .addAuthorizationCodeGrant(issuerState) .build() @@ -618,7 +618,7 @@ class OpenID4VCI_Test { // ---------------------------------- println("// --Authentication method is PRE_AUTHORIZED--") val preAuthCode = randomUUID() - credOffer = CredentialOffer.Builder(ISSUER_BASE_URL) + credOffer = CredentialOffer.Draft13.Builder(ISSUER_BASE_URL) .addOfferedCredential(issuedCredentialId) .addPreAuthorizedCodeGrant(preAuthCode) .build() @@ -628,7 +628,7 @@ class OpenID4VCI_Test { println(issueReqUrl) println("// -------- WALLET ----------") - credOffer = OpenID4VCI.parseAndResolveCredentialOfferRequestUrl(issueReqUrl) + credOffer = OpenID4VCI.parseAndResolveCredentialOfferRequestUrl(issueReqUrl) as CredentialOffer.Draft13 //providerMetadata = OpenID4VCI.resolveCIProviderMetadata(parsedCredOffer) assertEquals(expected = credOffer.credentialIssuer, actual = providerMetadata.credentialIssuer) diff --git a/waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialWallet.kt b/waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialWallet.kt index c781de4c7..2a055703a 100644 --- a/waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialWallet.kt +++ b/waltid-services/waltid-e2e-tests/src/test/kotlin/LspPotentialWallet.kt @@ -66,9 +66,15 @@ class LspPotentialWallet(val client: HttpClient, val walletId: String) { val offerUri = offerResp.bodyAsText() // === resolve credential offer === + val resolvedOffer11 = client.post("/wallet-api/wallet/$walletId/exchange/resolveCredentialOffer") { + setBody(offerUri) + }.expectSuccess().body() + + println(resolvedOffer11) + val resolvedOffer = client.post("/wallet-api/wallet/$walletId/exchange/resolveCredentialOffer") { setBody(offerUri) - }.expectSuccess().body() + }.expectSuccess().body() assertEquals(1, resolvedOffer.credentialConfigurationIds.size) assertEquals("org.iso.18013.5.1.mDL", resolvedOffer.credentialConfigurationIds.first()) @@ -215,7 +221,7 @@ class LspPotentialWallet(val client: HttpClient, val walletId: String) { // === resolve credential offer === val resolvedOffer = client.post("/wallet-api/wallet/$walletId/exchange/resolveCredentialOffer") { setBody(offerUri) - }.expectSuccess().body() + }.expectSuccess().body() assertEquals(1, resolvedOffer.credentialConfigurationIds.size) assertEquals("identity_credential_vc+sd-jwt", resolvedOffer.credentialConfigurationIds.first()) From a7950afbe8e3872ddecc2ce5a572582c85b89869 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Wed, 20 Nov 2024 13:47:35 +0200 Subject: [PATCH 18/53] fix: update ciTokenKey to secp256r1 curve for EBSI CT v3 compliance --- .../kotlin/id/walt/issuer/config/OIDCIssuerServiceConfig.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/config/OIDCIssuerServiceConfig.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/config/OIDCIssuerServiceConfig.kt index e03d8d055..4dc47775f 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/config/OIDCIssuerServiceConfig.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/config/OIDCIssuerServiceConfig.kt @@ -8,5 +8,5 @@ import kotlinx.coroutines.runBlocking data class OIDCIssuerServiceConfig( val baseUrl: String, - val ciTokenKey: String = runBlocking { KeySerialization.serializeKey(JWKKey.generate(KeyType.Ed25519)) }, + val ciTokenKey: String = runBlocking { KeySerialization.serializeKey(JWKKey.generate(KeyType.secp256r1)) }, ) : WaltConfig() From b5ab931885e2ec4b9cdfc41f50754503a9f4ac9d Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Wed, 20 Nov 2024 22:32:41 +0200 Subject: [PATCH 19/53] fix: two different imports for generating uuid --- .../src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt index cfee185e3..173c7d39e 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt @@ -26,8 +26,6 @@ import io.ktor.utils.io.core.* import kotlinx.datetime.Clock import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.* -import kotlin.uuid.ExperimentalUuidApi -import kotlin.uuid.Uuid object OpenID4VC { private val log = KotlinLogging.logger { } @@ -137,7 +135,6 @@ object OpenID4VC { } // Create an ID or VP Token request using JAR OAuth2.0 specification https://www.rfc-editor.org/rfc/rfc9101.html - @OptIn(ExperimentalUuidApi::class) suspend fun processCodeFlowAuthorizationWithAuthorizationRequest( authorizationRequest: AuthorizationRequest, responseType: ResponseType, @@ -157,8 +154,8 @@ object OpenID4VC { ) // Bind authentication request with state - val authorizationRequestServerState = Uuid.random().toString() - val authorizationRequestServerNonce = Uuid.random().toString() + val authorizationRequestServerState = randomUUID() + val authorizationRequestServerNonce = randomUUID() val authorizationResponseServerMode = ResponseMode.direct_post val clientId = providerMetadata.issuer!! From c2e127b56a6842fa7da2777eb8adeb587dab4386 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Wed, 20 Nov 2024 23:04:39 +0200 Subject: [PATCH 20/53] fix: initializeIssuanceSession in authorize endpoint --- .../kotlin/id/walt/oid4vc/OpenID4VC.kt | 19 ++++++++----------- .../kotlin/id/walt/issuer/issuance/OidcApi.kt | 6 ++++++ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt index 173c7d39e..05410f00d 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt @@ -137,6 +137,7 @@ object OpenID4VC { // Create an ID or VP Token request using JAR OAuth2.0 specification https://www.rfc-editor.org/rfc/rfc9101.html suspend fun processCodeFlowAuthorizationWithAuthorizationRequest( authorizationRequest: AuthorizationRequest, + authServerState: String, responseType: ResponseType, providerMetadata: OpenIDProviderMetadata, tokenKey: Key, @@ -153,8 +154,6 @@ object OpenID4VC { message = "Invalid response type ${authorizationRequest.responseType}, for authorization code flow." ) - // Bind authentication request with state - val authorizationRequestServerState = randomUUID() val authorizationRequestServerNonce = randomUUID() val authorizationResponseServerMode = ResponseMode.direct_post @@ -162,11 +161,8 @@ object OpenID4VC { val redirectUri = providerMetadata.issuer + "/direct_post" val scope = setOf("openid") - // Create a session with the state of the ID Token request since it is needed in the direct_post endpoint - //initializeAuthorization(authorizationRequest, 5.minutes, authorizationRequestServerState) - return AuthorizationCodeWithAuthorizationRequestResponse.success( - state = authorizationRequestServerState, + state = authServerState, clientId = clientId, redirectUri = redirectUri, responseType = getResponseTypeString(responseType), @@ -175,14 +171,13 @@ object OpenID4VC { nonce = authorizationRequestServerNonce, requestUri = null, request = when (isJar) { - // Create a jwt as request object as defined in JAR OAuth2.0 specification true -> signToken( - TokenTarget.TOKEN, - buildJsonObject { + target = TokenTarget.TOKEN, + payload = buildJsonObject { put(JWTClaims.Payload.issuer, providerMetadata.issuer) put(JWTClaims.Payload.audience, authorizationRequest.clientId) put(JWTClaims.Payload.nonce, authorizationRequestServerNonce) - put("state", authorizationRequestServerState) + put("state", authServerState) put("client_id", clientId) put("redirect_uri", redirectUri) put("response_type", getResponseTypeString(responseType)) @@ -192,7 +187,9 @@ object OpenID4VC { ResponseType.VpToken -> put("presentation_definition", presentationDefinition!!.toJSON()) else -> null } - }, tokenKey) + }, + privKey = tokenKey + ) else -> null }, diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt index 7ea3cc34b..69d5d4dd3 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt @@ -18,6 +18,7 @@ import id.walt.oid4vc.requests.TokenRequest import id.walt.oid4vc.responses.AuthorizationErrorCode import id.walt.oid4vc.responses.CredentialErrorCode import id.walt.oid4vc.responses.PushedAuthorizationResponse +import id.walt.oid4vc.util.randomUUID import id.walt.sdjwt.JWTVCIssuerMetadata import id.walt.sdjwt.SDJWTVCTypeMetadata import io.github.oshai.kotlinlogging.KotlinLogging @@ -163,8 +164,13 @@ object OidcApi : CIProvider() { } AuthenticationMethod.ID_TOKEN -> { + val authServerState = randomUUID() + + initializeIssuanceSession(authReq, 5.minutes, authServerState) + OpenID4VC.processCodeFlowAuthorizationWithAuthorizationRequest( authorizationRequest = authReq, + authServerState = authServerState, responseType = ResponseType.IdToken, providerMetadata = metadata, tokenKey = CI_TOKEN_KEY, From 57a354a7b8606b5a38b6a943128b56dcdfa2bf0a Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Wed, 20 Nov 2024 23:06:49 +0200 Subject: [PATCH 21/53] chore: remove unused variables and redundant decorators --- .../src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt index 69d5d4dd3..016027544 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt @@ -35,20 +35,13 @@ import io.ktor.util.* import io.ktor.util.pipeline.* import kotlinx.coroutines.runBlocking import kotlinx.datetime.Clock -import kotlinx.serialization.Serializable import kotlinx.serialization.json.* -import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.time.Duration.Companion.minutes - -@Serializable -data class UserData(val email: String, val password: String, val id: String? = null) - object OidcApi : CIProvider() { private val logger = KotlinLogging.logger { } - private fun Application.oidcRoute(build: Route.() -> Unit) { routing { // authenticate("authenticated") { @@ -61,7 +54,6 @@ object OidcApi : CIProvider() { //} } - @OptIn(ExperimentalEncodingApi::class) fun Application.oidcApi() = oidcRoute { route("", { tags = listOf("oidc") From 736f324b4b7ba96a1892c1d3a6b9373450e98009 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Wed, 20 Nov 2024 23:15:16 +0200 Subject: [PATCH 22/53] fix: timestamp format in EBSI compliant credentials --- .../kotlin/id/walt/credentials/issuance/DataFunctions.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/waltid-libraries/credentials/waltid-verifiable-credentials/src/commonMain/kotlin/id/walt/credentials/issuance/DataFunctions.kt b/waltid-libraries/credentials/waltid-verifiable-credentials/src/commonMain/kotlin/id/walt/credentials/issuance/DataFunctions.kt index 0537fe3b6..ec33b029a 100644 --- a/waltid-libraries/credentials/waltid-verifiable-credentials/src/commonMain/kotlin/id/walt/credentials/issuance/DataFunctions.kt +++ b/waltid-libraries/credentials/waltid-verifiable-credentials/src/commonMain/kotlin/id/walt/credentials/issuance/DataFunctions.kt @@ -5,6 +5,7 @@ import io.ktor.client.* import io.ktor.client.request.* import io.ktor.client.statement.* import kotlinx.datetime.Clock +import kotlinx.datetime.Instant import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonPrimitive @@ -22,9 +23,8 @@ val dataFunctions = mapOf Date: Fri, 22 Nov 2024 18:57:07 +0200 Subject: [PATCH 23/53] fix: initialize authorization in vp token authentication --- .../kotlin/id/walt/issuer/issuance/OidcApi.kt | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt index 016027544..a19e2a048 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt @@ -171,13 +171,20 @@ object OidcApi : CIProvider() { } AuthenticationMethod.VP_TOKEN -> { + val authServerState = randomUUID() + + initializeIssuanceSession(authReq, 5.minutes, authServerState) + val vpProfile = issuanceSession.issuanceRequests.first().vpProfile ?: OpenId4VPProfile.DEFAULT - val credFormat = issuanceSession.issuanceRequests.first().credentialFormat ?: when(vpProfile) { - OpenId4VPProfile.HAIP -> CredentialFormat.sd_jwt_vc - OpenId4VPProfile.ISO_18013_7_MDOC -> CredentialFormat.mso_mdoc - OpenId4VPProfile.EBSIV3 -> CredentialFormat.jwt_vc - else -> CredentialFormat.jwt_vc_json - } + + val credFormat = issuanceSession.issuanceRequests.first().credentialFormat + ?: when(vpProfile) { + OpenId4VPProfile.HAIP -> CredentialFormat.sd_jwt_vc + OpenId4VPProfile.ISO_18013_7_MDOC -> CredentialFormat.mso_mdoc + OpenId4VPProfile.EBSIV3 -> CredentialFormat.jwt_vc + else -> CredentialFormat.jwt_vc_json + } + val vpRequestValue = issuanceSession.issuanceRequests.first().vpRequestValue ?: throw IllegalArgumentException("missing vpRequestValue parameter") @@ -190,14 +197,20 @@ object OidcApi : CIProvider() { else -> throw IllegalArgumentException("Invalid JSON type for requested credential: $it") } ?: throw IllegalArgumentException("Invalid VC type for requested credential: $it") } - val presentationDefinition = - PresentationDefinition.defaultGenerationFromVcTypesForCredentialFormat(requestedTypes, credFormat) + + val presentationDefinition = PresentationDefinition.defaultGenerationFromVcTypesForCredentialFormat( + types = requestedTypes, + format = credFormat + ) OpenID4VC.processCodeFlowAuthorizationWithAuthorizationRequest( - authReq, - ResponseType.VpToken, metadata, CI_TOKEN_KEY, - issuanceSession.issuanceRequests.first().useJar, - presentationDefinition + authorizationRequest = authReq, + authServerState = authServerState, + responseType = ResponseType.VpToken, + providerMetadata = metadata, + tokenKey = CI_TOKEN_KEY, + isJar = issuanceSession.issuanceRequests.first().useJar, + presentationDefinition = presentationDefinition ) } From d5b6f6d51c78700116df72d7d0c9517c01473773 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Sun, 24 Nov 2024 18:44:48 +0200 Subject: [PATCH 24/53] fix: credential offer and credential offer uri --- .../id/walt/issuer/issuance/CIProvider.kt | 27 +++++++++++++++++++ .../id/walt/issuer/issuance/IssuerApi.kt | 11 +++----- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt index 7e99d231e..7543c1fce 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt @@ -30,6 +30,7 @@ import id.walt.oid4vc.OpenID4VC import id.walt.oid4vc.OpenID4VCI import id.walt.oid4vc.OpenID4VCIVersion import id.walt.oid4vc.data.* +import id.walt.oid4vc.definitions.CROSS_DEVICE_CREDENTIAL_OFFER_URL import id.walt.oid4vc.definitions.JWTClaims import id.walt.oid4vc.definitions.OPENID_CREDENTIAL_AUTHORIZATION_TYPE import id.walt.oid4vc.errors.AuthorizationError @@ -584,4 +585,30 @@ open class CIProvider( state = session.authorizationRequest?.state ) } + + private fun resolveBaseUrl(version: OpenID4VCIVersion): String { + return when (version) { + OpenID4VCIVersion.D13 -> baseUrl + OpenID4VCIVersion.D10 -> baseUrlD10 + else -> throw IllegalArgumentException("Unsupported version: $version") + } + } + + fun buildCredentialOfferUri(standardVersion: OpenID4VCIVersion, issuanceSessionId: String): String { + val baseUrl = resolveBaseUrl(standardVersion) + return "$baseUrl/credentialOffer?id=$issuanceSessionId" + } + + fun buildOfferUri( + standardVersion: OpenID4VCIVersion, + offerRequest: CredentialOfferRequest + ): String { + val baseUrl = resolveBaseUrl(standardVersion) + val sanitizedBaseUrl = baseUrl.removePrefix("https://").removePrefix("http://") + return OpenID4VCI.getCredentialOfferRequestUrl( + credOfferReq = offerRequest, + credentialOfferEndpoint = "$CROSS_DEVICE_CREDENTIAL_OFFER_URL$sanitizedBaseUrl/" + ) + } + } diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuerApi.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuerApi.kt index bb38bdf32..e4d8f8da3 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuerApi.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuerApi.kt @@ -4,11 +4,11 @@ import id.walt.credentials.vc.vcs.W3CVC import id.walt.crypto.keys.KeyManager import id.walt.crypto.keys.KeySerialization import id.walt.did.dids.DidService +import id.walt.issuer.issuance.OidcApi.buildCredentialOfferUri +import id.walt.issuer.issuance.OidcApi.buildOfferUri import id.walt.issuer.issuance.OidcApi.getFormatByCredentialConfigurationId -import id.walt.oid4vc.OpenID4VCI import id.walt.oid4vc.data.AuthenticationMethod import id.walt.oid4vc.data.CredentialFormat -import id.walt.oid4vc.definitions.CROSS_DEVICE_CREDENTIAL_OFFER_URL import id.walt.oid4vc.requests.CredentialOfferRequest import io.github.oshai.kotlinlogging.KotlinLogging import io.github.smiley4.ktorswaggerui.dsl.routes.OpenApiRequest @@ -56,15 +56,12 @@ suspend fun createCredentialOfferUri( val offerRequest = CredentialOfferRequest( credentialOffer = null, - credentialOfferUri = "${OidcApi.baseUrl}/credentialOffer?id=${issuanceSession.id}" + credentialOfferUri = buildCredentialOfferUri(overwrittenIssuanceRequests.first().standardVersion!!, issuanceSession.id) ) logger.debug { "offerRequest: $offerRequest" } - val offerUri = OpenID4VCI.getCredentialOfferRequestUrl( - credOfferReq = offerRequest, - credentialOfferEndpoint = CROSS_DEVICE_CREDENTIAL_OFFER_URL + OidcApi.baseUrl.removePrefix("https://").removePrefix("http://") + "/" - ) + val offerUri = buildOfferUri(overwrittenIssuanceRequests.first().standardVersion!!, offerRequest) logger.debug { "Offer URI: $offerUri" } From 184066f3377445215b306d0d1c97b990bdf6aee7 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Sun, 24 Nov 2024 19:33:12 +0200 Subject: [PATCH 25/53] fix: vct and issuer metadata for sd-jwts --- .../src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt | 7 ++++++- .../kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt | 3 ++- .../src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt index d6582ea6e..42eff42bc 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt @@ -130,9 +130,14 @@ object OpenID4VCI { appendPathSegments(".well-known", "oauth-authorization-server") }.buildString() - fun getJWTIssuerProviderMetadataUrl(baseUrl: String) = URLBuilder(Url(baseUrl).protocolWithAuthority).apply { + fun getJWTIssuerProviderMetadataUrl(baseUrl: String) = Url(baseUrl).let { URLBuilder(it.protocolWithAuthority).apply { appendPathSegments(".well-known", "jwt-vc-issuer") + + if(it.fullPath.isNotEmpty()) + appendPathSegments(it.fullPath.trim('/')) + }.buildString() + } suspend fun resolveCIProviderMetadata(credOffer: CredentialOffer) = http.get(getCIProviderMetadataUrl(credOffer)).bodyAsText().let { OpenIDProviderMetadata.fromJSONString(it) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt index 3173bf8d1..fe075bacd 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt @@ -2,6 +2,7 @@ package id.walt.oid4vc.data import id.walt.oid4vc.* import id.walt.oid4vc.definitions.* +import io.ktor.http.* import kotlinx.serialization.* import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.buildClassSerialDescriptor @@ -273,7 +274,7 @@ sealed class OpenIDProviderMetadata() : JsonDataObject() { baseUrl: String, credType: String ): CredentialSupported { - val expectedVct = "$baseUrl/$credType" + val expectedVct = "${URLBuilder(Url(baseUrl).protocolWithAuthority)}/$credType" credentialConfigurationsSupported?.entries?.forEach { entry -> if (getVctByCredentialConfigurationId(entry.key) == expectedVct) { diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt index a19e2a048..71fbd610e 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt @@ -94,7 +94,7 @@ object OidcApi : CIProvider() { call.respond(metadata.toJSON()) } - get("/.well-known/jwt-vc-issuer") { + get("/.well-known/jwt-vc-issuer/{standardVersion}") { call.respond(HttpStatusCode.OK, JWTVCIssuerMetadata(issuer = metadata.issuer, jwksUri = metadata.jwksUri)) } From b8feff8c3c3a507a3c43e4e98db6a5857f07a8c2 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Sun, 24 Nov 2024 19:47:55 +0200 Subject: [PATCH 26/53] fix: authorization server bug in draft13 and draft 10 compliance --- .../src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt | 2 -- .../id/walt/oid4vc/data/OpenIDProviderMetadata.kt | 10 +++------- .../id/walt/oid4vc/providers/OpenIDCredentialWallet.kt | 10 +++++----- .../main/kotlin/id/walt/issuer/NewTestOidcMetadata.kt | 1 - 4 files changed, 8 insertions(+), 15 deletions(-) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt index 42eff42bc..c1a6a52e7 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt @@ -337,7 +337,6 @@ object OpenID4VCI { grantTypesSupported = setOf(GrantType.authorization_code, GrantType.pre_authorized_code), requestUriParameterSupported = true, subjectTypesSupported = setOf(SubjectType.public), - authorizationServer = baseUrl, credentialIssuer = baseUrl, // (EBSI) this should be just "$baseUrl" https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#section-11.2.1 responseTypesSupported = setOf( "code", @@ -361,7 +360,6 @@ object OpenID4VCI { grantTypesSupported = setOf(GrantType.authorization_code, GrantType.pre_authorized_code), requestUriParameterSupported = true, subjectTypesSupported = setOf(SubjectType.public), - authorizationServer = baseUrl, credentialIssuer = baseUrl, // (EBSI) this should be just "$baseUrl" https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#section-11.2.1 responseTypesSupported = setOf( "code", diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt index fe075bacd..dbdb8c4e1 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt @@ -104,16 +104,14 @@ sealed class OpenIDProviderMetadata() : JsonDataObject() { abstract val opPolicyUri: String? abstract val opTosUri: String? - // OID4VCI properties + // OID4VCI Draft 10 and Draft 13 properties abstract val credentialIssuer: String? abstract val credentialEndpoint: String? abstract val batchCredentialEndpoint: String? abstract val deferredCredentialEndpoint: String? - abstract val authorizationServers: Set? abstract val display: List? abstract val presentationDefinitionUriSupported: Boolean? abstract val clientIdSchemesSupported: List? - abstract val authorizationServer: String? abstract val codeChallengeMethodsSupported: List? abstract val requirePushedAuthorizationRequests: Boolean? abstract val dpopSigningAlgValuesSupported: Set? @@ -169,17 +167,16 @@ sealed class OpenIDProviderMetadata() : JsonDataObject() { @SerialName("credential_endpoint") override val credentialEndpoint: String? = null, @SerialName("batch_credential_endpoint") override val batchCredentialEndpoint: String? = null, @SerialName("deferred_credential_endpoint") override val deferredCredentialEndpoint: String? = null, - @SerialName("authorization_servers") override val authorizationServers: Set? = null, @SerialName("display") @Serializable(DisplayPropertiesListSerializer::class) override val display: List? = null, @SerialName("presentation_definition_uri_supported") override val presentationDefinitionUriSupported: Boolean? = null, @SerialName("client_id_schemes_supported") override val clientIdSchemesSupported: List? = null, - @SerialName("authorization_server") override val authorizationServer: String? = authorizationServers?.firstOrNull(), @SerialName("code_challenge_methods_supported") override val codeChallengeMethodsSupported: List? = null, @SerialName("require_pushed_authorization_requests") override val requirePushedAuthorizationRequests: Boolean? = null, @SerialName("dpop_signing_alg_values_supported") override val dpopSigningAlgValuesSupported: Set? = null, // OID4VCI 10 @SerialName("credentials_supported") @Serializable(CredentialSupportedMapSerializer::class) val credentialSupported: Map? = null, + @SerialName("authorization_server") val authorizationServer: String? = null, override val customParameters: Map = mapOf() @@ -251,17 +248,16 @@ sealed class OpenIDProviderMetadata() : JsonDataObject() { @SerialName("credential_endpoint") override val credentialEndpoint: String? = null, @SerialName("batch_credential_endpoint") override val batchCredentialEndpoint: String? = null, @SerialName("deferred_credential_endpoint") override val deferredCredentialEndpoint: String? = null, - @SerialName("authorization_servers") override val authorizationServers: Set? = null, @SerialName("display") @Serializable(DisplayPropertiesListSerializer::class) override val display: List? = null, @SerialName("presentation_definition_uri_supported") override val presentationDefinitionUriSupported: Boolean? = null, @SerialName("client_id_schemes_supported") override val clientIdSchemesSupported: List? = null, - @SerialName("authorization_server") override val authorizationServer: String? = authorizationServers?.firstOrNull(), @SerialName("code_challenge_methods_supported") override val codeChallengeMethodsSupported: List? = null, @SerialName("require_pushed_authorization_requests") override val requirePushedAuthorizationRequests: Boolean? = null, @SerialName("dpop_signing_alg_values_supported") override val dpopSigningAlgValuesSupported: Set? = null, // OID4VCI 13 @SerialName("credential_configurations_supported") @Serializable(CredentialSupportedMapSerializer::class) val credentialConfigurationsSupported: Map? = null, + @SerialName("authorization_servers") val authorizationServers: Set? = null, override val customParameters: Map = mapOf() diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDCredentialWallet.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDCredentialWallet.kt index 6f0c9d9ee..0f1f2c64d 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDCredentialWallet.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDCredentialWallet.kt @@ -251,8 +251,8 @@ abstract class OpenIDCredentialWallet( CredentialOfferErrorCode.invalid_issuer, "Could not resolve issuer provider metadata from $issuerMetadataUrl" ) - val authorizationServerMetadata = issuerMetadata.authorizationServer?.let { authServer -> - httpGetAsJson(Url(getCommonProviderMetadataUrl(authServer)))?.jsonObject?.let { OpenIDProviderMetadata.fromJSON(it) } + val authorizationServerMetadata = issuerMetadata.authorizationServers?.let { authServer -> + httpGetAsJson(Url(getCommonProviderMetadataUrl(authServer.first())))?.jsonObject?.let { OpenIDProviderMetadata.fromJSON(it) } } ?: issuerMetadata val offeredCredentials = OpenID4VCI.resolveOfferedCredentials(credentialOffer, issuerMetadata) @@ -282,8 +282,8 @@ abstract class OpenIDCredentialWallet( CredentialOfferErrorCode.invalid_issuer, "Could not resolve issuer provider metadata from $issuerMetadataUrl" ) - val authorizationServerMetadata = issuerMetadata.authorizationServer?.let { authServer -> - httpGetAsJson(Url(getCommonProviderMetadataUrl(authServer)))?.jsonObject?.let { OpenIDProviderMetadata.fromJSON(it) as OpenIDProviderMetadata.Draft13} + val authorizationServerMetadata = issuerMetadata.authorizationServers?.let { authServer -> + httpGetAsJson(Url(getCommonProviderMetadataUrl(authServer.first())))?.jsonObject?.let { OpenIDProviderMetadata.fromJSON(it) as OpenIDProviderMetadata.Draft13} } ?: issuerMetadata as OpenIDProviderMetadata.Draft13 val offeredCredentials = OpenID4VCI.resolveOfferedCredentials(credentialOffer, issuerMetadata) val codeVerifier = if (client.useCodeChallenge) randomUUID() else null @@ -441,7 +441,7 @@ abstract class OpenIDCredentialWallet( } } else { // execute batch credential request - executeBatchCredentialRequest(issuerMetadata.batchCredentialEndpoint, tokenResp.accessToken, offeredCredentials.map { + executeBatchCredentialRequest(issuerMetadata.batchCredentialEndpoint!!, tokenResp.accessToken, offeredCredentials.map { CredentialRequest.forOfferedCredential(it, generateDidProof(holderDid, credentialOffer.credentialIssuer, nonce, client)) }) } diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/NewTestOidcMetadata.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/NewTestOidcMetadata.kt index 411a081c7..a06b2d74e 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/NewTestOidcMetadata.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/NewTestOidcMetadata.kt @@ -41,7 +41,6 @@ fun main() { grantTypesSupported = setOf(GrantType.authorization_code, GrantType.pre_authorized_code), requestUriParameterSupported = true, subjectTypesSupported = setOf(SubjectType.public), - authorizationServer = url, credentialIssuer = url, // (EBSI) this should be just "$baseUrl" https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#section-11.2.1 responseTypesSupported = setOf( "code", From 24bc3d5cb06387c213095c109aab08c67f0ac451 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Sun, 24 Nov 2024 21:15:04 +0200 Subject: [PATCH 27/53] fix: error in baseUrl in issuanceRequestsToCredentialOfferBuilder --- .../src/main/kotlin/id/walt/issuer/issuance/OidcIssuance.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcIssuance.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcIssuance.kt index 7fb06cfda..2e39bf9cc 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcIssuance.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcIssuance.kt @@ -29,7 +29,7 @@ object OidcIssuance { fun issuanceRequestsToCredentialOfferBuilder(vararg issuanceRequests: IssuanceRequest, standardVersion: OpenID4VCIVersion): CredentialOffer.Builder<*> { val builder = when (standardVersion) { OpenID4VCIVersion.D13 -> CredentialOffer.Draft13.Builder(OidcApi.baseUrl) - OpenID4VCIVersion.D10 -> CredentialOffer.Draft10.Builder(OidcApi.baseUrl) + OpenID4VCIVersion.D10 -> CredentialOffer.Draft10.Builder(OidcApi.baseUrlD10) } issuanceRequests.forEach { issuanceRequest -> From 64769b645043bc16084929914e4d9a1692a978b5 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Sun, 24 Nov 2024 21:37:06 +0200 Subject: [PATCH 28/53] fix: add specialized serializer for credentials_supported field in Draft10 --- .../walt/oid4vc/data/CredentialSupported.kt | 41 +++++++++++++++++-- .../oid4vc/data/OpenIDProviderMetadata.kt | 2 +- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/CredentialSupported.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/CredentialSupported.kt index 80e6274e7..8697b7e25 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/CredentialSupported.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/CredentialSupported.kt @@ -6,12 +6,10 @@ import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.MapSerializer import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.* /* @context: [...], // json-ld only @@ -110,3 +108,38 @@ object CredentialSupportedMapSerializer : KSerializer) = internalSerializer.serialize(encoder, value) } + +/** + * The JsonDecoder attempts to deserialize the `credentials_supported` field. + * For Draft10, the `credentials_supported` field is represented as a JsonArray. + * However, the expected structure for a `Map` is a JsonObject + */ + +object CredentialSupportedArraySerializer : KSerializer> { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Array") + + override fun deserialize(decoder: Decoder): Map { + val jsonDecoder = decoder as? JsonDecoder + ?: throw IllegalStateException("Invalid Decoder") + + val jsonArray = jsonDecoder.decodeJsonElement() as? JsonArray + ?: throw IllegalStateException("Invalid Decoder") + + return jsonArray.mapIndexed { index, element -> + val key = index.toString() + val value = Json.decodeFromJsonElement(CredentialSupported.serializer(), element) + key to value + }.toMap() + } + + override fun serialize(encoder: Encoder, value: Map) { + encoder as? JsonEncoder + ?: throw IllegalStateException("Invalid Decoder") + + val jsonArray = value.values.map { credential -> + JsonObject(credential.toJSON().toMutableMap()) + } + + encoder.encodeJsonElement(JsonArray(jsonArray)) + } +} diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt index dbdb8c4e1..5a4298368 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt @@ -175,7 +175,7 @@ sealed class OpenIDProviderMetadata() : JsonDataObject() { @SerialName("dpop_signing_alg_values_supported") override val dpopSigningAlgValuesSupported: Set? = null, // OID4VCI 10 - @SerialName("credentials_supported") @Serializable(CredentialSupportedMapSerializer::class) val credentialSupported: Map? = null, + @SerialName("credentials_supported") @Serializable(CredentialSupportedArraySerializer::class) val credentialSupported: Map? = null, @SerialName("authorization_server") val authorizationServer: String? = null, override val customParameters: Map = mapOf() From 7c48b797dae898f13e410a7a2cebd30cec9d1fa3 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Mon, 25 Nov 2024 10:24:38 +0200 Subject: [PATCH 29/53] fix: rename getCommonProviderMetadataUrl to getOpenIdProviderMetadataUrl --- .../src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt index c1a6a52e7..ced03790e 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt @@ -122,7 +122,7 @@ object OpenID4VCI { appendPathSegments(".well-known", "openid-credential-issuer") }.buildString() - fun getCommonProviderMetadataUrl(baseUrl: String) = URLBuilder(baseUrl).apply { + fun getOpenIdProviderMetadataUrl(baseUrl: String) = URLBuilder(baseUrl).apply { appendPathSegments(".well-known", "openid-configuration") }.buildString() From 9cd0bea99b712f816f2f4062aef1affd7929cfa5 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Mon, 25 Nov 2024 12:43:55 +0200 Subject: [PATCH 30/53] fix: add request parameter in AuthorizationRequest --- .../kotlin/id/walt/oid4vc/requests/AuthorizationRequest.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/requests/AuthorizationRequest.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/requests/AuthorizationRequest.kt index 6027c8dca..bfd5cd313 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/requests/AuthorizationRequest.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/requests/AuthorizationRequest.kt @@ -40,6 +40,7 @@ data class AuthorizationRequest( val userHint: String? = null, val issuerState: String? = null, val requestUri: String? = null, + val request: String? = null, val presentationDefinition: PresentationDefinition? = null, val presentationDefinitionUri: String? = null, val clientIdScheme: ClientIdScheme? = null, @@ -73,6 +74,7 @@ data class AuthorizationRequest( userHint?.let { put("user_hint", listOf(it)) } issuerState?.let { put("issuer_state", listOf(it)) } requestUri?.let { put("request_uri", listOf(it)) } + request?.let { put("request", listOf(it)) } presentationDefinition?.let { put("presentation_definition", listOf(it.toJSONString())) } presentationDefinitionUri?.let { put("presentation_definition_uri", listOf(it)) } clientIdScheme?.let { put("client_id_scheme", listOf(it.value)) } @@ -136,6 +138,7 @@ data class AuthorizationRequest( userHint?.let { put("user_hint", JsonPrimitive(it)) } issuerState?.let { put("issuer_state", JsonPrimitive(it)) } requestUri?.let { put("request_uri", JsonPrimitive(it)) } + request?.let { put("request", JsonPrimitive(it)) } presentationDefinition?.let { put("presentation_definition", it.toJSON()) } presentationDefinitionUri?.let { put("presentation_definition_uri", JsonPrimitive(it)) } clientIdScheme?.let { put("client_id_scheme", JsonPrimitive(it.value)) } @@ -267,6 +270,7 @@ data class AuthorizationRequest( parameters["user_hint"]?.firstOrNull(), parameters["issuer_state"]?.firstOrNull(), parameters["request_uri"]?.firstOrNull(), + parameters["request"]?.firstOrNull(), parameters["presentation_definition"]?.firstOrNull()?.let { PresentationDefinition.fromJSONString(it) }, parameters["presentation_definition_uri"]?.firstOrNull(), parameters["client_id_scheme"]?.firstOrNull()?.let { ClientIdScheme.fromValue(it) }, From 0e6c93495e20d2c2d84ba5293dd4bc54126007a0 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Mon, 25 Nov 2024 12:50:59 +0200 Subject: [PATCH 31/53] feat: add d10 tests --- .../src/test/kotlin/IssuerD10.kt | 133 ++++++++++++++++++ .../src/test/kotlin/WaltidServicesE2ETests.kt | 4 + 2 files changed, 137 insertions(+) create mode 100644 waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD10.kt diff --git a/waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD10.kt b/waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD10.kt new file mode 100644 index 000000000..1bba8a486 --- /dev/null +++ b/waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD10.kt @@ -0,0 +1,133 @@ +import id.walt.commons.testing.utils.ServiceTestUtils.loadResource +import id.walt.crypto.utils.JsonUtils.toJsonElement +import id.walt.issuer.issuance.IssuanceRequest +import id.walt.oid4vc.OpenID4VCI.getCIProviderMetadataUrl +import id.walt.oid4vc.OpenID4VCIVersion +import id.walt.oid4vc.data.* +import id.walt.oid4vc.requests.AuthorizationRequest +import id.walt.oid4vc.requests.CredentialOfferRequest +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import io.ktor.util.* +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.json.* +import kotlin.test.* + +class IssuerDraft10(private val client: HttpClient) { + + fun testIssuerAPIDraft10() = runBlocking { + lateinit var offerUrl: String + lateinit var issuerState: String + + val issuerApi = IssuerApi(client) + + val issuanceReq = Json.decodeFromString(loadResource("issuance/openbadgecredential-issuance-request-with-authorization-code-flow-and-id-token.json")).copy( + credentialConfigurationId = "OpenBadgeCredential_jwt_vc", + standardVersion = OpenID4VCIVersion.D10, + useJar = true + ) + + issuerApi.jwt(issuanceReq) { + offerUrl = it + } + + val offerUrlParams = Url(offerUrl).parameters.toMap() + val offerObj = CredentialOfferRequest.fromHttpParameters(offerUrlParams) + val credOffer = client.get(offerObj.credentialOfferUri!!).body() + + assertNotNull(credOffer.credentialIssuer) + assertNotNull(credOffer.credentials) + assertNotNull(credOffer.grants) + + val issuerMetadataUrl = getCIProviderMetadataUrl(credOffer.credentialIssuer) + + val rawJsonMetadata = client.get(issuerMetadataUrl).bodyAsText() + val jsonElementMetadata = Json.parseToJsonElement(rawJsonMetadata) + assertTrue(jsonElementMetadata.jsonObject["credentials_supported"] is JsonArray, "Expected credentials_supported to be a JsonArray") + + val issuerMetadata = OpenIDProviderMetadata.fromJSONString(rawJsonMetadata) as OpenIDProviderMetadata.Draft10 + + assertTrue(issuerMetadata.credentialSupported!!.keys.all { it.toIntOrNull() != null }, "Expected credentials_supported keys to be array indices (e.g., '0', '1')") + + assertEquals(issuerMetadata.issuer, credOffer.credentialIssuer) + assertEquals(issuerMetadata.credentialIssuer, credOffer.credentialIssuer) + assertEquals(issuerMetadata.credentialIssuer, credOffer.credentialIssuer) + + val matchingCredential = issuerMetadata.credentialSupported + ?.values + ?.find { it.id == issuanceReq.credentialConfigurationId } + ?: throw AssertionError("No matching credential found for the credentialConfigurationId '${issuanceReq.credentialConfigurationId}'.") + + assertEquals( + matchingCredential.id, + issuanceReq.credentialConfigurationId + ) + + assertEquals( + CredentialFormat.jwt_vc, + matchingCredential.format + ) + + assertNull(issuerMetadata.authorizationServer) + + assertContains(issuerMetadata.grantTypesSupported, GrantType.authorization_code) + assertContains(issuerMetadata.grantTypesSupported, GrantType.pre_authorized_code) + + assertNotNull(issuerMetadata.jwksUri) + + val rawJsonJwks = client.get(issuerMetadata.jwksUri!!).bodyAsText() + + val keysArray = Json.parseToJsonElement(rawJsonJwks).jsonObject["keys"]?.jsonArray + ?: throw AssertionError("JWKS response must contain a 'keys' array") + + assertTrue( + keysArray.any { key -> + key.jsonObject.run { + this["kty"]?.jsonPrimitive?.content == "EC" && + this["crv"]?.jsonPrimitive?.content == "P-256" + } + }, + "JWKS must contain at least one key with 'kty': 'EC' and 'crv': 'P-256'" + ) + + issuerState = credOffer.grants["authorization_code"]!!.issuerState!! + + println(issuerState) + val authorizationRequest = AuthorizationRequest( + issuerState = issuerState, + clientId = "did:key:xzy", + scope = setOf("openid"), + clientMetadata = OpenIDClientMetadata( + requestUris = listOf("openid://redirect"), + jwksUri = "myuri.com/jwks", + customParameters = mapOf("authorization_endpoint" to "openid://".toJsonElement()), + ), + requestUri = "openid://redirect", + responseType = setOf(ResponseType.Code), + state = "UPD4Qjo2gzBNv641YQf19BamZets1xQpkY8jYTxvqq8", + codeChallenge = "UPD4Qjo2gzBNv641YQf19BamZets1xQpkY8jYTxvqq8", + codeChallengeMethod = "S256", + authorizationDetails = listOf( + AuthorizationDetails( + type = "openid_credential", + locations = listOf(credOffer.credentialIssuer), + format = CredentialFormat.jwt_vc, + credentialDefinition = CredentialDefinition(type = listOf("VerifiableCredential", "OpenBadgeCredential")) + ) + ) + ) + + client.get("${issuerMetadata.authorizationEndpoint}?${authorizationRequest.toHttpQueryString()}") {} + .expectRedirect().apply { + val idTokenRequest = AuthorizationRequest.fromHttpQueryString(headers["location"]!!) + assert(idTokenRequest.responseType == setOf(ResponseType.IdToken)) { "response type should be id_token" } + assert(idTokenRequest.responseMode == ResponseMode.direct_post) { "response mode should be direct post" } + assertNotNull(idTokenRequest.request) + } + + } + +} \ No newline at end of file diff --git a/waltid-services/waltid-e2e-tests/src/test/kotlin/WaltidServicesE2ETests.kt b/waltid-services/waltid-e2e-tests/src/test/kotlin/WaltidServicesE2ETests.kt index ed1377d21..c347d3db3 100644 --- a/waltid-services/waltid-e2e-tests/src/test/kotlin/WaltidServicesE2ETests.kt +++ b/waltid-services/waltid-e2e-tests/src/test/kotlin/WaltidServicesE2ETests.kt @@ -332,6 +332,10 @@ class WaltidServicesE2ETests { val authorizationCodeFlow = AuthorizationCodeFlow(testHttpClient(doFollowRedirects = false)) authorizationCodeFlow.testIssuerAPI() + // Test Issuer Draft 10 + val issuerDraft10 = IssuerDraft10(testHttpClient(doFollowRedirects = false)) + issuerDraft10.testIssuerAPIDraft10() + // Test External Signature API Endpoints //In the context of these test cases, a new wallet is created and initialized //accordingly, i.e., the default wallet that is employed by all the other test From 7f2b8377dcf795cc7e5eef04f2bb6892e6938156 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Mon, 25 Nov 2024 13:30:03 +0200 Subject: [PATCH 32/53] refactor: rename D to Draft to improve readability --- .../kotlin/id/walt/oid4vc/OpenID4VCI.kt | 8 ++++---- .../id/walt/oid4vc/providers/OpenIDProvider.kt | 2 +- .../kotlin/id/walt/oid4vc/OpenID4VCI_Test.kt | 2 +- .../src/test/kotlin/AuthorizationCodeFlow.kt | 2 +- .../src/test/kotlin/IssuerD10.kt | 2 +- .../kotlin/id/walt/issuer/issuance/CIProvider.kt | 16 ++++++++-------- .../id/walt/issuer/issuance/IssuanceRequests.kt | 4 ++-- .../kotlin/id/walt/issuer/issuance/OidcApi.kt | 12 ++++++------ .../id/walt/issuer/issuance/OidcIssuance.kt | 4 ++-- .../test/kotlin/id/walt/LocalIssuerApiTest.kt | 6 +++--- 10 files changed, 29 insertions(+), 29 deletions(-) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt index ced03790e..dca63af2b 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt @@ -325,7 +325,7 @@ object OpenID4VCI { return when (version) { - OpenID4VCIVersion.D13 -> OpenIDProviderMetadata.Draft13( + OpenID4VCIVersion.Draft13 -> OpenIDProviderMetadata.Draft13( issuer = baseUrl, authorizationEndpoint = "$baseUrl/authorize", pushedAuthorizationRequestEndpoint = "$baseUrl/par", @@ -348,7 +348,7 @@ object OpenID4VCI { credentialConfigurationsSupported = credentialSupported ) - OpenID4VCIVersion.D10 -> OpenIDProviderMetadata.Draft10( + OpenID4VCIVersion.Draft10 -> OpenIDProviderMetadata.Draft10( issuer = baseUrl, authorizationEndpoint = "$baseUrl/authorize", pushedAuthorizationRequestEndpoint = "$baseUrl/par", @@ -529,8 +529,8 @@ object OpenID4VCI { enum class OpenID4VCIVersion(val versionString: String) { - D10("d10"), - D13("d13"); + Draft10("draft10"), + Draft13("draft13"); companion object { fun from(version: String): OpenID4VCIVersion { diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDProvider.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDProvider.kt index 9c3170751..2d6e900d2 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDProvider.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDProvider.kt @@ -34,7 +34,7 @@ abstract class OpenIDProvider( abstract val metadata: OpenIDProviderMetadata.Draft13 abstract val config: OpenIDProviderConfig - protected open fun createDefaultProviderMetadata() = OpenID4VCI.createDefaultProviderMetadata(baseUrl, emptyMap(), OpenID4VCIVersion.D13) + protected open fun createDefaultProviderMetadata() = OpenID4VCI.createDefaultProviderMetadata(baseUrl, emptyMap(), OpenID4VCIVersion.Draft13) fun getCommonProviderMetadataUrl(): String { return URLBuilder(baseUrl).apply { diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/OpenID4VCI_Test.kt b/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/OpenID4VCI_Test.kt index 6fd16af3e..1c6fb47d3 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/OpenID4VCI_Test.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/OpenID4VCI_Test.kt @@ -33,7 +33,7 @@ import kotlin.test.* class OpenID4VCI_Test { val ISSUER_BASE_URL = "https://test" val CREDENTIAL_OFFER_BASE_URL = "openid-credential-offer://test" - val ISSUER_METADATA = (OpenID4VCI.createDefaultProviderMetadata(ISSUER_BASE_URL, emptyMap(), OpenID4VCIVersion.D13) as OpenIDProviderMetadata.Draft13).copy( + val ISSUER_METADATA = (OpenID4VCI.createDefaultProviderMetadata(ISSUER_BASE_URL, emptyMap(), OpenID4VCIVersion.Draft13) as OpenIDProviderMetadata.Draft13).copy( credentialConfigurationsSupported = mapOf( "VerifiableId" to CredentialSupported( CredentialFormat.jwt_vc_json, diff --git a/waltid-services/waltid-e2e-tests/src/test/kotlin/AuthorizationCodeFlow.kt b/waltid-services/waltid-e2e-tests/src/test/kotlin/AuthorizationCodeFlow.kt index afeeda8b9..b00cc7512 100644 --- a/waltid-services/waltid-e2e-tests/src/test/kotlin/AuthorizationCodeFlow.kt +++ b/waltid-services/waltid-e2e-tests/src/test/kotlin/AuthorizationCodeFlow.kt @@ -23,7 +23,7 @@ class AuthorizationCodeFlow(private val client: HttpClient) { lateinit var offerUrl: String lateinit var issuerState: String val issuerApi = IssuerApi(client) - val authorizeEndpoint = "d13/authorize" + val authorizeEndpoint = "draft13/authorize" // // Issue credential with Authorized Code Flow and Id Token request diff --git a/waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD10.kt b/waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD10.kt index 1bba8a486..971d63f68 100644 --- a/waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD10.kt +++ b/waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD10.kt @@ -26,7 +26,7 @@ class IssuerDraft10(private val client: HttpClient) { val issuanceReq = Json.decodeFromString(loadResource("issuance/openbadgecredential-issuance-request-with-authorization-code-flow-and-id-token.json")).copy( credentialConfigurationId = "OpenBadgeCredential_jwt_vc", - standardVersion = OpenID4VCIVersion.D10, + standardVersion = OpenID4VCIVersion.Draft10, useJar = true ) diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt index 7543c1fce..2be3d0e09 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt @@ -66,18 +66,18 @@ import kotlin.uuid.ExperimentalUuidApi */ @OptIn(ExperimentalUuidApi::class) open class CIProvider( - val baseUrl: String = let { ConfigManager.getConfig().baseUrl + "/${OpenID4VCIVersion.D13.versionString}"}, - val baseUrlD10: String = let { ConfigManager.getConfig().baseUrl + "/${OpenID4VCIVersion.D10.versionString}"}, + val baseUrl: String = let { ConfigManager.getConfig().baseUrl + "/${OpenID4VCIVersion.Draft13.versionString}"}, + val baseUrlDraft10: String = let { ConfigManager.getConfig().baseUrl + "/${OpenID4VCIVersion.Draft10.versionString}"}, val config: CredentialIssuerConfig = CredentialIssuerConfig(credentialConfigurationsSupported = ConfigManager.getConfig().parse()) ) { private val log = KotlinLogging.logger { } val metadata - get() = (OpenID4VCI.createDefaultProviderMetadata(baseUrl, config.credentialConfigurationsSupported, OpenID4VCIVersion.D13) as OpenIDProviderMetadata.Draft13) + get() = (OpenID4VCI.createDefaultProviderMetadata(baseUrl, config.credentialConfigurationsSupported, OpenID4VCIVersion.Draft13) as OpenIDProviderMetadata.Draft13) - val metadataD10 - get() = (OpenID4VCI.createDefaultProviderMetadata(baseUrlD10, config.credentialConfigurationsSupported, OpenID4VCIVersion.D10) as OpenIDProviderMetadata.Draft10) + val metadataDraft10 + get() = (OpenID4VCI.createDefaultProviderMetadata(baseUrlDraft10, config.credentialConfigurationsSupported, OpenID4VCIVersion.Draft10) as OpenIDProviderMetadata.Draft10) companion object { @@ -484,7 +484,7 @@ open class CIProvider( callbackUrl: String? = null, txCode: TxCode? = null, txCodeValue: String? = null, - standardVersion: OpenID4VCIVersion = OpenID4VCIVersion.D13 + standardVersion: OpenID4VCIVersion = OpenID4VCIVersion.Draft13 ): IssuanceSession = runBlocking { val sessionId = randomUUID() @@ -588,8 +588,8 @@ open class CIProvider( private fun resolveBaseUrl(version: OpenID4VCIVersion): String { return when (version) { - OpenID4VCIVersion.D13 -> baseUrl - OpenID4VCIVersion.D10 -> baseUrlD10 + OpenID4VCIVersion.Draft13 -> baseUrl + OpenID4VCIVersion.Draft10 -> baseUrlDraft10 else -> throw IllegalArgumentException("Unsupported version: $version") } } diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuanceRequests.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuanceRequests.kt index 354e471ba..fdfacea69 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuanceRequests.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuanceRequests.kt @@ -93,7 +93,7 @@ data class IssuanceRequest( val x5Chain: List? = null, val trustedRootCAs: List? = null, var credentialFormat: CredentialFormat? = null, - val standardVersion: OpenID4VCIVersion? = OpenID4VCIVersion.D13, + val standardVersion: OpenID4VCIVersion? = OpenID4VCIVersion.Draft13, ) { constructor( issuerKey: JsonObject, @@ -110,7 +110,7 @@ data class IssuanceRequest( x5Chain: List? = null, trustedRootCAs: List? = null, credentialFormat: CredentialFormat? = null, - standardVersion: OpenID4VCIVersion? = OpenID4VCIVersion.D13, + standardVersion: OpenID4VCIVersion? = OpenID4VCIVersion.Draft13, ) : this( issuerKey, diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt index 71fbd610e..5411aa728 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt @@ -63,8 +63,8 @@ object OidcApi : CIProvider() { val version = OpenID4VCIVersion.from(standardVersion) val metadata = when (version) { - OpenID4VCIVersion.D10 -> metadataD10 - OpenID4VCIVersion.D13 -> metadata + OpenID4VCIVersion.Draft10 -> metadataDraft10 + OpenID4VCIVersion.Draft13 -> metadata } call.respond(metadata.toJSON()) @@ -75,8 +75,8 @@ object OidcApi : CIProvider() { val version = OpenID4VCIVersion.from(standardVersion) val metadata = when (version) { - OpenID4VCIVersion.D10 -> metadataD10 - OpenID4VCIVersion.D13 -> metadata + OpenID4VCIVersion.Draft10 -> metadataDraft10 + OpenID4VCIVersion.Draft13 -> metadata } call.respond(metadata.toJSON()) @@ -87,8 +87,8 @@ object OidcApi : CIProvider() { val version = OpenID4VCIVersion.from(standardVersion) val metadata = when (version) { - OpenID4VCIVersion.D10 -> metadataD10 - OpenID4VCIVersion.D13 -> metadata + OpenID4VCIVersion.Draft10 -> metadataDraft10 + OpenID4VCIVersion.Draft13 -> metadata } call.respond(metadata.toJSON()) diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcIssuance.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcIssuance.kt index 2e39bf9cc..2bb5d91bc 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcIssuance.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcIssuance.kt @@ -28,8 +28,8 @@ object OidcIssuance { fun issuanceRequestsToCredentialOfferBuilder(vararg issuanceRequests: IssuanceRequest, standardVersion: OpenID4VCIVersion): CredentialOffer.Builder<*> { val builder = when (standardVersion) { - OpenID4VCIVersion.D13 -> CredentialOffer.Draft13.Builder(OidcApi.baseUrl) - OpenID4VCIVersion.D10 -> CredentialOffer.Draft10.Builder(OidcApi.baseUrlD10) + OpenID4VCIVersion.Draft13 -> CredentialOffer.Draft13.Builder(OidcApi.baseUrl) + OpenID4VCIVersion.Draft10 -> CredentialOffer.Draft10.Builder(OidcApi.baseUrlDraft10) } issuanceRequests.forEach { issuanceRequest -> diff --git a/waltid-services/waltid-issuer-api/src/test/kotlin/id/walt/LocalIssuerApiTest.kt b/waltid-services/waltid-issuer-api/src/test/kotlin/id/walt/LocalIssuerApiTest.kt index 564b6ffb3..ae547ea9f 100644 --- a/waltid-services/waltid-issuer-api/src/test/kotlin/id/walt/LocalIssuerApiTest.kt +++ b/waltid-services/waltid-issuer-api/src/test/kotlin/id/walt/LocalIssuerApiTest.kt @@ -144,7 +144,7 @@ companion object { ConfigManager.testWithConfigs(testConfigs) val offerUri = createCredentialOfferUri(listOf(issueRequest), CredentialFormat.jwt_vc_json) - assertEquals(true, offerUri.contains("//localhost:7002/d13/?credential_offer")) + assertEquals(true, offerUri.contains("//localhost:7002/draft13/?credential_offer")) } @@ -167,7 +167,7 @@ companion object { ConfigManager.testWithConfigs(testConfigs) val offerUri = createCredentialOfferUri(listOf(issueRequest), CredentialFormat.jwt_vc_json) - assertEquals(true, offerUri.contains("//localhost:7002/d13/?credential_offer")) + assertEquals(true, offerUri.contains("//localhost:7002/draft13/?credential_offer")) } @Test @@ -213,7 +213,7 @@ companion object { ConfigManager.loadConfigs(emptyArray()) val offerUri = createCredentialOfferUri(issuanceRequests, CredentialFormat.jwt_vc_json) - assertEquals(true, offerUri.contains("//localhost:7002/d13/?credential_offer")) + assertEquals(true, offerUri.contains("//localhost:7002/draft13/?credential_offer")) } From 0afbcd15e3a6484034a62fbeecb19f2af59292bb Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Mon, 25 Nov 2024 13:45:09 +0200 Subject: [PATCH 33/53] refactor: metadata resolution in issuer api --- .../id/walt/issuer/issuance/CIProvider.kt | 11 +++++++ .../kotlin/id/walt/issuer/issuance/OidcApi.kt | 30 ++++++------------- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt index 2be3d0e09..4d174adcc 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt @@ -611,4 +611,15 @@ open class CIProvider( ) } + fun getMetadataForVersion( + standardVersion: String?, + ): OpenIDProviderMetadata { + val version = OpenID4VCIVersion.from(standardVersion ?: throw IllegalArgumentException("standardVersion parameter is required")) + + return when (version) { + OpenID4VCIVersion.Draft10 -> metadataDraft10 + OpenID4VCIVersion.Draft13 -> metadata + } + } + } diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt index 5411aa728..c363c29bb 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt @@ -59,37 +59,25 @@ object OidcApi : CIProvider() { tags = listOf("oidc") }) { get("{standardVersion}/.well-known/openid-configuration") { - val standardVersion = call.parameters["standardVersion"] ?: throw IllegalArgumentException("standardVersion parameter is required") - val version = OpenID4VCIVersion.from(standardVersion) - - val metadata = when (version) { - OpenID4VCIVersion.Draft10 -> metadataDraft10 - OpenID4VCIVersion.Draft13 -> metadata - } + val metadata = getMetadataForVersion( + standardVersion = call.parameters["standardVersion"] ?: throw IllegalArgumentException("standardVersion parameter is required"), + ) call.respond(metadata.toJSON()) } get("{standardVersion}/.well-known/openid-credential-issuer") { - val standardVersion = call.parameters["standardVersion"] ?: throw IllegalArgumentException("standardVersion parameter is required") - val version = OpenID4VCIVersion.from(standardVersion) - - val metadata = when (version) { - OpenID4VCIVersion.Draft10 -> metadataDraft10 - OpenID4VCIVersion.Draft13 -> metadata - } + val metadata = getMetadataForVersion( + standardVersion = call.parameters["standardVersion"] ?: throw IllegalArgumentException("standardVersion parameter is required"), + ) call.respond(metadata.toJSON()) } get("{standardVersion}/.well-known/oauth-authorization-server") { - val standardVersion = call.parameters["standardVersion"] ?: throw IllegalArgumentException("standardVersion parameter is required") - val version = OpenID4VCIVersion.from(standardVersion) - - val metadata = when (version) { - OpenID4VCIVersion.Draft10 -> metadataDraft10 - OpenID4VCIVersion.Draft13 -> metadata - } + val metadata = getMetadataForVersion( + standardVersion = call.parameters["standardVersion"] ?: throw IllegalArgumentException("standardVersion parameter is required"), + ) call.respond(metadata.toJSON()) } From 3caabc43b6dd003044fe225e02de44f66d0c97dd Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Mon, 25 Nov 2024 18:45:52 +0200 Subject: [PATCH 34/53] fix: add Draft13 metadata in test Credential Wallet --- .../walt/wallet/core/service/oidc4vc/TestCredentialWallet.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/waltid-libraries/waltid-core-wallet/src/jvmMain/java/id/walt/wallet/core/service/oidc4vc/TestCredentialWallet.kt b/waltid-libraries/waltid-core-wallet/src/jvmMain/java/id/walt/wallet/core/service/oidc4vc/TestCredentialWallet.kt index ba9df5b13..cdc926c99 100644 --- a/waltid-libraries/waltid-core-wallet/src/jvmMain/java/id/walt/wallet/core/service/oidc4vc/TestCredentialWallet.kt +++ b/waltid-libraries/waltid-core-wallet/src/jvmMain/java/id/walt/wallet/core/service/oidc4vc/TestCredentialWallet.kt @@ -362,8 +362,8 @@ class TestCredentialWallet( return true } - override val metadata: OpenIDProviderMetadata - get() = createDefaultProviderMetadata() + override val metadata: OpenIDProviderMetadata.Draft13 + get() = createDefaultProviderMetadata() as OpenIDProviderMetadata.Draft13 override fun createSIOPSession( id: String, From c80b00bd56c7c8a3bdcff534e4713e1cf6381a2c Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Mon, 25 Nov 2024 20:34:23 +0200 Subject: [PATCH 35/53] fix: vp_token authentication method --- .../kotlin/id/walt/issuer/issuance/OidcApi.kt | 66 +++++++++++++------ 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt index c363c29bb..6c946b198 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt @@ -4,9 +4,9 @@ package id.walt.issuer.issuance import id.walt.policies.Verifier import id.walt.policies.models.PolicyRequest.Companion.parsePolicyRequests import id.walt.oid4vc.OpenID4VC -import id.walt.oid4vc.OpenID4VCIVersion import id.walt.oid4vc.data.* import id.walt.oid4vc.data.dif.PresentationDefinition +import id.walt.oid4vc.data.dif.PresentationDefinition.Companion.generateDefaultEBSIV3InputDescriptor import id.walt.oid4vc.data.dif.PresentationSubmission import id.walt.oid4vc.definitions.JWTClaims import id.walt.oid4vc.errors.* @@ -136,8 +136,8 @@ object OidcApi : CIProvider() { call.response.apply { status(HttpStatusCode.Found) header( - HttpHeaders.Location, - "${metadata.issuer}/external_login/${authReq.toHttpQueryString()}" + name = HttpHeaders.Location, + value = "${metadata.issuer}/external_login/${authReq.toHttpQueryString()}" ) } return@get @@ -161,7 +161,11 @@ object OidcApi : CIProvider() { AuthenticationMethod.VP_TOKEN -> { val authServerState = randomUUID() - initializeIssuanceSession(authReq, 5.minutes, authServerState) + initializeIssuanceSession( + authorizationRequest = authReq, + expiresIn = 5.minutes, + authServerState = authServerState + ) val vpProfile = issuanceSession.issuanceRequests.first().vpProfile ?: OpenId4VPProfile.DEFAULT @@ -186,7 +190,19 @@ object OidcApi : CIProvider() { } ?: throw IllegalArgumentException("Invalid VC type for requested credential: $it") } - val presentationDefinition = PresentationDefinition.defaultGenerationFromVcTypesForCredentialFormat( + val presentationDefinition = when(vpProfile) { + OpenId4VPProfile.EBSIV3 -> PresentationDefinition( + inputDescriptors = requestedTypes.map { type -> + generateDefaultEBSIV3InputDescriptor(type) + } + ) + else -> PresentationDefinition.defaultGenerationFromVcTypesForCredentialFormat( + types = requestedTypes, + format = credFormat + ) + } + + PresentationDefinition.defaultGenerationFromVcTypesForCredentialFormat( types = requestedTypes, format = credFormat ) @@ -203,12 +219,16 @@ object OidcApi : CIProvider() { } AuthenticationMethod.NONE -> OpenID4VC.processCodeFlowAuthorization( - authReq, issuanceSession.id, metadata, CI_TOKEN_KEY) + authorizationRequest = authReq, + sessionId = issuanceSession.id, + providerMetadata = metadata, + tokenKey = CI_TOKEN_KEY) + else -> { throw AuthorizationError( - authReq, - AuthorizationErrorCode.invalid_request, - "Request Authentication Method is invalid" + authorizationRequest = authReq, + errorCode = AuthorizationErrorCode.invalid_request, + message = "Request Authentication Method is invalid" ) } } @@ -245,26 +265,34 @@ object OidcApi : CIProvider() { logger.info { "Redirect Uri is: $redirectUri" } call.response.apply { + status(HttpStatusCode.Found) - val defaultResponseMode = - if (authReq.responseType.contains(ResponseType.Code)) ResponseMode.query else ResponseMode.fragment + + val defaultResponseMode = if (authReq.responseType.contains(ResponseType.Code)) ResponseMode.query else ResponseMode.fragment + authResp as IHTTPDataObject + header( - HttpHeaders.Location, - authResp.toRedirectUri(redirectUri, authReq.responseMode ?: defaultResponseMode) + name = HttpHeaders.Location, + value = authResp.toRedirectUri(redirectUri, authReq.responseMode ?: defaultResponseMode) ) } } catch (authExc: AuthorizationError) { logger.error(authExc) { "Authorization error: " } + call.response.apply { + status(HttpStatusCode.Found) - header(HttpHeaders.Location, URLBuilder(authExc.authorizationRequest.redirectUri!!).apply { - parameters.appendAll( - parametersOf( - authExc.toAuthorizationErrorResponse().toHttpParameters() + + header( + name = HttpHeaders.Location, + value = URLBuilder(authExc.authorizationRequest.redirectUri!!).apply { + parameters.appendAll( + parametersOf( + authExc.toAuthorizationErrorResponse().toHttpParameters() + ) ) - ) - }.buildString()) + }.buildString()) } } } From e70955ee3131377788f12c6215998f3a3d2c007f Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Tue, 26 Nov 2024 17:26:14 +0200 Subject: [PATCH 36/53] feat: add test for vp_token authentication method --- .../src/test/kotlin/IssuerD10.kt | 73 +++++++++++++++---- .../src/test/kotlin/WaltidServicesE2ETests.kt | 19 ++++- 2 files changed, 78 insertions(+), 14 deletions(-) diff --git a/waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD10.kt b/waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD10.kt index 971d63f68..d5087a32d 100644 --- a/waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD10.kt +++ b/waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD10.kt @@ -1,9 +1,11 @@ -import id.walt.commons.testing.utils.ServiceTestUtils.loadResource +import id.walt.credentials.utils.VCFormat +import id.walt.crypto.keys.jwk.JWKKey import id.walt.crypto.utils.JsonUtils.toJsonElement +import id.walt.crypto.utils.JwsUtils.decodeJws import id.walt.issuer.issuance.IssuanceRequest import id.walt.oid4vc.OpenID4VCI.getCIProviderMetadataUrl -import id.walt.oid4vc.OpenID4VCIVersion import id.walt.oid4vc.data.* +import id.walt.oid4vc.data.dif.PresentationDefinition import id.walt.oid4vc.requests.AuthorizationRequest import id.walt.oid4vc.requests.CredentialOfferRequest import io.ktor.client.* @@ -18,18 +20,13 @@ import kotlin.test.* class IssuerDraft10(private val client: HttpClient) { - fun testIssuerAPIDraft10() = runBlocking { + fun testIssuerAPIDraft10AuthFlowWithJar(issuanceReq: IssuanceRequest) = runBlocking { lateinit var offerUrl: String lateinit var issuerState: String + lateinit var authJarTokenRequest: AuthorizationRequest val issuerApi = IssuerApi(client) - val issuanceReq = Json.decodeFromString(loadResource("issuance/openbadgecredential-issuance-request-with-authorization-code-flow-and-id-token.json")).copy( - credentialConfigurationId = "OpenBadgeCredential_jwt_vc", - standardVersion = OpenID4VCIVersion.Draft10, - useJar = true - ) - issuerApi.jwt(issuanceReq) { offerUrl = it } @@ -122,12 +119,62 @@ class IssuerDraft10(private val client: HttpClient) { client.get("${issuerMetadata.authorizationEndpoint}?${authorizationRequest.toHttpQueryString()}") {} .expectRedirect().apply { - val idTokenRequest = AuthorizationRequest.fromHttpQueryString(headers["location"]!!) - assert(idTokenRequest.responseType == setOf(ResponseType.IdToken)) { "response type should be id_token" } - assert(idTokenRequest.responseMode == ResponseMode.direct_post) { "response mode should be direct post" } - assertNotNull(idTokenRequest.request) + authJarTokenRequest = AuthorizationRequest.fromHttpQueryString(headers["location"]!!) } + assert(authJarTokenRequest.responseMode == ResponseMode.direct_post) { "response mode should be direct post" } + assertNotNull(authJarTokenRequest.request) + + // Verify Token + val requestJwt = authJarTokenRequest.request!!.decodeJws() + + println(requestJwt) + + val keyId = requestJwt.header["kid"]!!.jsonPrimitive.content + assertNotNull(keyId) + + val jwksResponse = client.get(issuerMetadata.jwksUri!!).bodyAsText() + + val jwks = Json.parseToJsonElement(jwksResponse).jsonObject + + val matchingKey = jwks["keys"]?.jsonArray?.firstOrNull { key -> + key.jsonObject["kid"]?.jsonPrimitive?.content == keyId + } + + assertNotNull(matchingKey) + + val signingKey = JWKKey.importJWK(matchingKey.toString()).getOrThrow() + + assertTrue {signingKey.verifyJws(authJarTokenRequest.request!!).isSuccess} + + when (issuanceReq.authenticationMethod) { + AuthenticationMethod.ID_TOKEN -> { + assert(authJarTokenRequest.responseType == setOf(ResponseType.IdToken)) { "response type should be id_token" } + } + + AuthenticationMethod.VP_TOKEN -> { + assert(authJarTokenRequest.responseType == setOf(ResponseType.VpToken)) { "response type should be vp_token" } + + val presentationDefinitionJ = requestJwt.payload["presentation_definition"] + assertNotNull(presentationDefinitionJ) + + val presentationDefinition = PresentationDefinition.fromJSON( + presentationDefinitionJ.jsonObject + ) + + val inputDescriptors = presentationDefinition.inputDescriptors + + assertEquals(1, inputDescriptors.size) + + val theInputDescriptor = inputDescriptors.first() + + assertNotNull(theInputDescriptor.format, "theInputDescriptor format should not be null") + assertTrue(theInputDescriptor.format!!.containsKey(VCFormat.jwt_vc), "theInputDescriptor should be jwt_vc") + assertTrue(theInputDescriptor.format!![VCFormat.jwt_vc]?.alg?.contains("ES256") == true, "theInputDescriptor alg should be ES256") + } + + else -> throw AssertionError("Unexpected authentication method ${issuanceReq.authenticationMethod}") + } } } \ No newline at end of file diff --git a/waltid-services/waltid-e2e-tests/src/test/kotlin/WaltidServicesE2ETests.kt b/waltid-services/waltid-e2e-tests/src/test/kotlin/WaltidServicesE2ETests.kt index 2ad07231d..c74da6d2e 100644 --- a/waltid-services/waltid-e2e-tests/src/test/kotlin/WaltidServicesE2ETests.kt +++ b/waltid-services/waltid-e2e-tests/src/test/kotlin/WaltidServicesE2ETests.kt @@ -11,6 +11,7 @@ import id.walt.crypto.keys.KeyType import id.walt.issuer.issuance.IssuanceRequest import id.walt.issuer.issuerModule import id.walt.issuer.lspPotential.lspPotentialIssuanceTestApi +import id.walt.oid4vc.OpenID4VCIVersion import id.walt.oid4vc.data.OpenId4VPProfile import id.walt.oid4vc.data.dif.PresentationDefinition import id.walt.verifier.lspPotential.lspPotentialVerificationTestApi @@ -334,7 +335,23 @@ class WaltidServicesE2ETests { // Test Issuer Draft 10 val issuerDraft10 = IssuerDraft10(testHttpClient(doFollowRedirects = false)) - issuerDraft10.testIssuerAPIDraft10() + + val idTokenIssuanceReq = Json.decodeFromString(loadResource("issuance/openbadgecredential-issuance-request-with-authorization-code-flow-and-id-token.json")).copy( + credentialConfigurationId = "OpenBadgeCredential_jwt_vc", + standardVersion = OpenID4VCIVersion.Draft10, + useJar = true + ) + + issuerDraft10.testIssuerAPIDraft10AuthFlowWithJar(idTokenIssuanceReq) + + val vpTokenIssuanceReq = Json.decodeFromString(loadResource("issuance/openbadgecredential-issuance-request-with-authorization-code-flow-and-vp-token.json")).copy( + credentialConfigurationId = "OpenBadgeCredential_jwt_vc", + standardVersion = OpenID4VCIVersion.Draft10, + useJar = true + ) + + issuerDraft10.testIssuerAPIDraft10AuthFlowWithJar(vpTokenIssuanceReq) + // Test External Signature API Endpoints //In the context of these test cases, a new wallet is created and initialized From 776c6bf8f60325280b4876218b72d0b4c1e6b4c4 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Tue, 26 Nov 2024 18:12:06 +0200 Subject: [PATCH 37/53] fix: standard versions in /authorize endpoint --- .../src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt index 6c946b198..c4f12dc55 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt @@ -125,7 +125,9 @@ object OidcApi : CIProvider() { } get("{standardVersion}/authorize") { + val standardVersion = call.parameters["standardVersion"] ?: throw IllegalArgumentException("standardVersion parameter is required") val authReq = runBlocking { AuthorizationRequest.fromHttpParametersAuto(call.parameters.toMap()) } + try { val issuanceSession = authReq.issuerState?.let { getSession(it) } ?: error("No issuance session found for given issuer state, or issuer state was empty: ${authReq.issuerState}") val authMethod = issuanceSession.issuanceRequests.firstOrNull()?.authenticationMethod ?: AuthenticationMethod.NONE @@ -152,7 +154,7 @@ object OidcApi : CIProvider() { authorizationRequest = authReq, authServerState = authServerState, responseType = ResponseType.IdToken, - providerMetadata = metadata, + providerMetadata = getMetadataForVersion(standardVersion), tokenKey = CI_TOKEN_KEY, isJar = issuanceSession.issuanceRequests.first().useJar ) @@ -211,7 +213,7 @@ object OidcApi : CIProvider() { authorizationRequest = authReq, authServerState = authServerState, responseType = ResponseType.VpToken, - providerMetadata = metadata, + providerMetadata = getMetadataForVersion(standardVersion), tokenKey = CI_TOKEN_KEY, isJar = issuanceSession.issuanceRequests.first().useJar, presentationDefinition = presentationDefinition From b15f9cda0cf4b1f6b9aa485096f1584d680272cc Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Fri, 29 Nov 2024 14:39:53 +0200 Subject: [PATCH 38/53] fix: metadata casting error in processCodeFlowAuthorizationWithAuthorizationRequest --- .../src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt | 4 +++- .../kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt index 05410f00d..f438b5c6a 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt @@ -145,7 +145,9 @@ object OpenID4VC { presentationDefinition: PresentationDefinition? = null, ): AuthorizationCodeWithAuthorizationRequestResponse { - providerMetadata as OpenIDProviderMetadata.Draft13 + providerMetadata.castOrNull() + ?: providerMetadata.castOrNull() + ?: error("Unknown metadata type: $providerMetadata") if (!authorizationRequest.responseType.contains(ResponseType.Code)) throw AuthorizationError( diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt index 5a4298368..a4de0b464 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt @@ -116,6 +116,9 @@ sealed class OpenIDProviderMetadata() : JsonDataObject() { abstract val requirePushedAuthorizationRequests: Boolean? abstract val dpopSigningAlgValuesSupported: Set? + inline fun castOrNull(): T? { + return this as? T + } @Serializable data class Draft10( From 52719f9a708c5554eb1f2bd709633b425c06128a Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Fri, 29 Nov 2024 17:19:51 +0200 Subject: [PATCH 39/53] feat: add authorization request and JAR tests --- .../src/test/kotlin/IssuerD10.kt | 68 +++++++++++++++++-- 1 file changed, 62 insertions(+), 6 deletions(-) diff --git a/waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD10.kt b/waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD10.kt index d5087a32d..f77cccff9 100644 --- a/waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD10.kt +++ b/waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD10.kt @@ -1,3 +1,4 @@ +import com.nimbusds.jose.JWSAlgorithm import id.walt.credentials.utils.VCFormat import id.walt.crypto.keys.jwk.JWKKey import id.walt.crypto.utils.JsonUtils.toJsonElement @@ -6,6 +7,7 @@ import id.walt.issuer.issuance.IssuanceRequest import id.walt.oid4vc.OpenID4VCI.getCIProviderMetadataUrl import id.walt.oid4vc.data.* import id.walt.oid4vc.data.dif.PresentationDefinition +import id.walt.oid4vc.definitions.JWTClaims import id.walt.oid4vc.requests.AuthorizationRequest import id.walt.oid4vc.requests.CredentialOfferRequest import io.ktor.client.* @@ -122,15 +124,13 @@ class IssuerDraft10(private val client: HttpClient) { authJarTokenRequest = AuthorizationRequest.fromHttpQueryString(headers["location"]!!) } - assert(authJarTokenRequest.responseMode == ResponseMode.direct_post) { "response mode should be direct post" } + // Verify Authorization Request and JAR Token assertNotNull(authJarTokenRequest.request) - - // Verify Token val requestJwt = authJarTokenRequest.request!!.decodeJws() println(requestJwt) - val keyId = requestJwt.header["kid"]!!.jsonPrimitive.content + val keyId = requestJwt.header[JWTClaims.Header.keyID]!!.jsonPrimitive.content assertNotNull(keyId) val jwksResponse = client.get(issuerMetadata.jwksUri!!).bodyAsText() @@ -138,7 +138,7 @@ class IssuerDraft10(private val client: HttpClient) { val jwks = Json.parseToJsonElement(jwksResponse).jsonObject val matchingKey = jwks["keys"]?.jsonArray?.firstOrNull { key -> - key.jsonObject["kid"]?.jsonPrimitive?.content == keyId + key.jsonObject[JWTClaims.Header.keyID]?.jsonPrimitive?.content == keyId } assertNotNull(matchingKey) @@ -147,6 +147,16 @@ class IssuerDraft10(private val client: HttpClient) { assertTrue {signingKey.verifyJws(authJarTokenRequest.request!!).isSuccess} + val jarPayload = requestJwt.payload + assertNotNull(jarPayload) + + validateAuthorizationData( + issuerMetadata = issuerMetadata, + holderAuthorizationRequest = authorizationRequest, + jarPayload = jarPayload, + issuerAuthorizationRequest = authJarTokenRequest + ) + when (issuanceReq.authenticationMethod) { AuthenticationMethod.ID_TOKEN -> { assert(authJarTokenRequest.responseType == setOf(ResponseType.IdToken)) { "response type should be id_token" } @@ -170,11 +180,57 @@ class IssuerDraft10(private val client: HttpClient) { assertNotNull(theInputDescriptor.format, "theInputDescriptor format should not be null") assertTrue(theInputDescriptor.format!!.containsKey(VCFormat.jwt_vc), "theInputDescriptor should be jwt_vc") - assertTrue(theInputDescriptor.format!![VCFormat.jwt_vc]?.alg?.contains("ES256") == true, "theInputDescriptor alg should be ES256") + + assertTrue(theInputDescriptor.format!![VCFormat.jwt_vc]?.alg?.contains(JWSAlgorithm.ES256.name) == true, "theInputDescriptor alg should be ES256") } else -> throw AssertionError("Unexpected authentication method ${issuanceReq.authenticationMethod}") } } + private fun validateAuthorizationData( + issuerMetadata: OpenIDProviderMetadata. Draft10, + holderAuthorizationRequest: AuthorizationRequest, + jarPayload: Map? = null, + issuerAuthorizationRequest: AuthorizationRequest? = null + ) { + + val commonData = mapOf( + "client_id" to issuerMetadata.issuer, + "redirect_uri" to "${issuerMetadata.issuer}/direct_post", + "response_mode" to ResponseMode.direct_post.name + ) + + val jarSpecificData = mapOf( + JWTClaims.Payload.issuer to issuerMetadata.issuer, + JWTClaims.Payload.audience to holderAuthorizationRequest.clientId + ) + + jarPayload?.let { + validateData( + expectedData = commonData + jarSpecificData, + actualData = it + ) + } + + issuerAuthorizationRequest?.let { + validateData( + expectedData =commonData, + actualData = mapOf( + "client_id" to it.clientId, + "redirect_uri" to it.redirectUri, + "response_mode" to it.responseMode + ), + + ) + } + } + + private fun validateData(expectedData: Map, actualData: Map) { + expectedData.forEach { (key, expectedValue) -> + val actualValue = actualData[key]?.toJsonElement()?.jsonPrimitive?.content + assertEquals(expectedValue, actualValue, "Validation failed for key: $key") + } + } + } \ No newline at end of file From 51809dff1be3a669910ac729a0c382f9217990fb Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Fri, 29 Nov 2024 17:31:38 +0200 Subject: [PATCH 40/53] chore: minor structural improvements in test cases --- .../src/test/kotlin/IssuerD10.kt | 51 +++++++++---------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD10.kt b/waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD10.kt index f77cccff9..77b78c930 100644 --- a/waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD10.kt +++ b/waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD10.kt @@ -41,20 +41,39 @@ class IssuerDraft10(private val client: HttpClient) { assertNotNull(credOffer.credentials) assertNotNull(credOffer.grants) - val issuerMetadataUrl = getCIProviderMetadataUrl(credOffer.credentialIssuer) + val issuerMetadataUrl = getCIProviderMetadataUrl(credOffer.credentialIssuer) val rawJsonMetadata = client.get(issuerMetadataUrl).bodyAsText() val jsonElementMetadata = Json.parseToJsonElement(rawJsonMetadata) - assertTrue(jsonElementMetadata.jsonObject["credentials_supported"] is JsonArray, "Expected credentials_supported to be a JsonArray") + assertTrue(jsonElementMetadata.jsonObject["credentials_supported"] is JsonArray, "Expected credentials_supported in Open ID Provider Metadata to be a JsonArray") val issuerMetadata = OpenIDProviderMetadata.fromJSONString(rawJsonMetadata) as OpenIDProviderMetadata.Draft10 - + assertNull(issuerMetadata.authorizationServer) + assertContains(issuerMetadata.grantTypesSupported, GrantType.authorization_code) + assertContains(issuerMetadata.grantTypesSupported, GrantType.pre_authorized_code) + assertNotNull(issuerMetadata.jwksUri) assertTrue(issuerMetadata.credentialSupported!!.keys.all { it.toIntOrNull() != null }, "Expected credentials_supported keys to be array indices (e.g., '0', '1')") assertEquals(issuerMetadata.issuer, credOffer.credentialIssuer) assertEquals(issuerMetadata.credentialIssuer, credOffer.credentialIssuer) assertEquals(issuerMetadata.credentialIssuer, credOffer.credentialIssuer) + val rawJsonJwks = client.get(issuerMetadata.jwksUri!!).bodyAsText() + + val keysArray = Json.parseToJsonElement(rawJsonJwks).jsonObject["keys"]?.jsonArray + ?: throw AssertionError("JWKS response must contain a 'keys' array") + + assertTrue( + keysArray.any { key -> + key.jsonObject.run { + this["kty"]?.jsonPrimitive?.content == "EC" && + this["crv"]?.jsonPrimitive?.content == "P-256" + } + }, + "JWKS must contain at least one key with 'kty': 'EC' and 'crv': 'P-256'" + ) + + val matchingCredential = issuerMetadata.credentialSupported ?.values ?.find { it.id == issuanceReq.credentialConfigurationId } @@ -70,31 +89,9 @@ class IssuerDraft10(private val client: HttpClient) { matchingCredential.format ) - assertNull(issuerMetadata.authorizationServer) - - assertContains(issuerMetadata.grantTypesSupported, GrantType.authorization_code) - assertContains(issuerMetadata.grantTypesSupported, GrantType.pre_authorized_code) - - assertNotNull(issuerMetadata.jwksUri) - - val rawJsonJwks = client.get(issuerMetadata.jwksUri!!).bodyAsText() - - val keysArray = Json.parseToJsonElement(rawJsonJwks).jsonObject["keys"]?.jsonArray - ?: throw AssertionError("JWKS response must contain a 'keys' array") - assertTrue( - keysArray.any { key -> - key.jsonObject.run { - this["kty"]?.jsonPrimitive?.content == "EC" && - this["crv"]?.jsonPrimitive?.content == "P-256" - } - }, - "JWKS must contain at least one key with 'kty': 'EC' and 'crv': 'P-256'" - ) + issuerState = credOffer.grants[GrantType.authorization_code.name]!!.issuerState!! - issuerState = credOffer.grants["authorization_code"]!!.issuerState!! - - println(issuerState) val authorizationRequest = AuthorizationRequest( issuerState = issuerState, clientId = "did:key:xzy", @@ -128,8 +125,6 @@ class IssuerDraft10(private val client: HttpClient) { assertNotNull(authJarTokenRequest.request) val requestJwt = authJarTokenRequest.request!!.decodeJws() - println(requestJwt) - val keyId = requestJwt.header[JWTClaims.Header.keyID]!!.jsonPrimitive.content assertNotNull(keyId) From 3a579456254714d7a299a6346e9d36e782820ac2 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Fri, 29 Nov 2024 18:09:46 +0200 Subject: [PATCH 41/53] style: function invocations --- .../kotlin/id/walt/oid4vc/OpenID4VC.kt | 16 +++-- .../kotlin/id/walt/issuer/issuance/OidcApi.kt | 65 ++++++++++++------- 2 files changed, 54 insertions(+), 27 deletions(-) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt index f438b5c6a..b6ea81aba 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt @@ -151,8 +151,8 @@ object OpenID4VC { if (!authorizationRequest.responseType.contains(ResponseType.Code)) throw AuthorizationError( - authorizationRequest, - AuthorizationErrorCode.invalid_request, + authorizationRequest = authorizationRequest, + errorCode = AuthorizationErrorCode.invalid_request, message = "Invalid response type ${authorizationRequest.responseType}, for authorization code flow." ) @@ -247,9 +247,17 @@ object OpenID4VC { // Generate code and proceed as regular authorization request val mappedState = mapOf("state" to listOf(authorizationRequest.state!!)) val issuer = providerMetadata.issuer ?: throw AuthorizationError(authorizationRequest, AuthorizationErrorCode.server_error,"No issuer configured in given provider metadata") - val code = generateAuthorizationCodeFor(sessionId, issuer, tokenKey) - return AuthorizationCodeResponse.success(code, mappedState) + val code = generateAuthorizationCodeFor( + sessionId = sessionId, + issuer = issuer, + tokenKey = tokenKey + ) + + return AuthorizationCodeResponse.success( + code = code, + customParameters = mappedState + ) } const val PUSHED_AUTHORIZATION_REQUEST_URI_PREFIX = "urn:ietf:params:oauth:request_uri:" diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt index c4f12dc55..976b93a8c 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt @@ -148,7 +148,11 @@ object OidcApi : CIProvider() { AuthenticationMethod.ID_TOKEN -> { val authServerState = randomUUID() - initializeIssuanceSession(authReq, 5.minutes, authServerState) + initializeIssuanceSession( + authorizationRequest = authReq, + expiresIn = 5.minutes, + authServerState = authServerState + ) OpenID4VC.processCodeFlowAuthorizationWithAuthorizationRequest( authorizationRequest = authReq, @@ -237,31 +241,33 @@ object OidcApi : CIProvider() { } ResponseType.Token in authReq.responseType -> OpenID4VC.processImplicitFlowAuthorization( - authReq, issuanceSession.id, metadata, CI_TOKEN_KEY) + authorizationRequest = authReq, + sessionId = issuanceSession.id, + providerMetadata = metadata, + tokenKey = CI_TOKEN_KEY + ) else -> { throw AuthorizationError( - authReq, - AuthorizationErrorCode.unsupported_response_type, - "Response type not supported" + authorizationRequest = authReq, + errorCode = AuthorizationErrorCode.unsupported_response_type, + message = "Response type not supported" ) } } val redirectUri = when (authMethod) { - AuthenticationMethod.VP_TOKEN, AuthenticationMethod.ID_TOKEN -> authReq.clientMetadata!!.customParameters["authorization_endpoint"]?.jsonPrimitive?.content - ?: "openid://" - + AuthenticationMethod.VP_TOKEN, AuthenticationMethod.ID_TOKEN -> authReq.clientMetadata!!.customParameters["authorization_endpoint"]?.jsonPrimitive?.content ?: "openid://" else -> if (authReq.isReferenceToPAR) { - val pushedSession = getPushedAuthorizationSession(authReq) - pushedSession.authorizationRequest?.redirectUri - } else { - authReq.redirectUri - } ?: throw AuthorizationError( - authReq, - AuthorizationErrorCode.invalid_request, - "No redirect_uri found for this authorization request" - ) + val pushedSession = getPushedAuthorizationSession(authReq) + pushedSession.authorizationRequest?.redirectUri + } else { + authReq.redirectUri + } ?: throw AuthorizationError( + authorizationRequest = authReq, + errorCode = AuthorizationErrorCode.invalid_request, + message = "No redirect_uri found for this authorization request" + ) } logger.info { "Redirect Uri is: $redirectUri" } @@ -276,9 +282,13 @@ object OidcApi : CIProvider() { header( name = HttpHeaders.Location, - value = authResp.toRedirectUri(redirectUri, authReq.responseMode ?: defaultResponseMode) + value = authResp.toRedirectUri( + redirectUri = redirectUri, + responseMode = authReq.responseMode ?: defaultResponseMode + ) ) } + } catch (authExc: AuthorizationError) { logger.error(authExc) { "Authorization error: " } @@ -325,27 +335,36 @@ object OidcApi : CIProvider() { val policies = Json.parseToJsonElement("""["signature", "expired", "not-before"]""").jsonArray.parsePolicyRequests() Verifier.verifyPresentation( - presentationFormat, + format = presentationFormat, vpToken = vpToken, vpPolicies = policies, globalVcPolicies = policies, specificCredentialPolicies = emptyMap(), - mapOf("presentationSubmission" to presSub) + presentationContext = mapOf("presentationSubmission" to presSub) ) } // Process response val session = getSessionByAuthServerState(state) ?: throw IllegalStateException("No session found for given state parameter") val resp = OpenID4VC.processDirectPost( - session.authorizationRequest ?: throw IllegalStateException("Session for given state has no authorization request"), - session.id, metadata, CI_TOKEN_KEY) + authorizationRequest = session.authorizationRequest ?: throw IllegalStateException("Session for given state has no authorization request"), + sessionId = session.id, + providerMetadata = metadata, + tokenKey = CI_TOKEN_KEY + ) // Get the authorization_endpoint parameter which is the redirect_uri from the Authorization Request Parameter val redirectUri = getSessionByAuthServerState(state)!!.authorizationRequest!!.redirectUri!! call.response.apply { status(HttpStatusCode.Found) - header(HttpHeaders.Location, resp.toRedirectUri(redirectUri, ResponseMode.query)) + header( + name = HttpHeaders.Location, + value = resp.toRedirectUri( + redirectUri = redirectUri, + responseMode = ResponseMode.query + ) + ) } } catch (exc: TokenError) { From bc215f60b2220a45dce8eebb213832ac4e6f6cf7 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Fri, 29 Nov 2024 18:36:50 +0200 Subject: [PATCH 42/53] fix: cast metadata in processCodeFlowAuthorization/DirectPost/ImplicitFlow --- .../commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt index b6ea81aba..3ec9fb04a 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt @@ -210,7 +210,9 @@ object OpenID4VC { message = "Invalid response type ${authorizationRequest.responseType}, for authorization code flow." ) - providerMetadata as OpenIDProviderMetadata.Draft13 + providerMetadata.castOrNull() + ?: providerMetadata.castOrNull() + ?: error("Unknown metadata type: $providerMetadata") val issuer = providerMetadata.issuer ?: throw AuthorizationError(authorizationRequest, AuthorizationErrorCode.server_error,"No issuer configured in given provider metadata") val code = generateAuthorizationCodeFor(sessionId, issuer, tokenKey) @@ -218,7 +220,9 @@ object OpenID4VC { } suspend fun processImplicitFlowAuthorization(authorizationRequest: AuthorizationRequest, sessionId: String, providerMetadata: OpenIDProviderMetadata, tokenKey: Key): TokenResponse { - providerMetadata as OpenIDProviderMetadata.Draft13 + providerMetadata.castOrNull() + ?: providerMetadata.castOrNull() + ?: error("Unknown metadata type: $providerMetadata") log.debug { "> processImplicitFlowAuthorization for $authorizationRequest" } if (!authorizationRequest.responseType.contains(ResponseType.Token) && !authorizationRequest.responseType.contains(ResponseType.VpToken) @@ -239,7 +243,9 @@ object OpenID4VC { } suspend fun processDirectPost(authorizationRequest: AuthorizationRequest, sessionId: String, providerMetadata: OpenIDProviderMetadata, tokenKey: Key): AuthorizationCodeResponse { - providerMetadata as OpenIDProviderMetadata.Draft13 + providerMetadata.castOrNull() + ?: providerMetadata.castOrNull() + ?: error("Unknown metadata type: $providerMetadata") // Verify nonce - need to add Id token nonce session // if (payload[JWTClaims.Payload.nonce] != session.) From fd181ecfdd2518a4052cf5b4dfb5efb07d58f8e9 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Fri, 29 Nov 2024 18:41:40 +0200 Subject: [PATCH 43/53] style: function invocations --- .../src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt index 3ec9fb04a..e999fb689 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt @@ -127,7 +127,13 @@ object OpenID4VC { else -> throw TokenError(tokenRequest, TokenErrorCode.unsupported_grant_type, "Grant type not supported") } - return verifyAndParseToken(code, issuer, TokenTarget.TOKEN, tokenKey) ?: throw TokenError( + + return verifyAndParseToken( + token = code, + issuer = issuer, + target = TokenTarget.TOKEN, + tokenKey = tokenKey + ) ?: throw TokenError( tokenRequest = tokenRequest, errorCode = TokenErrorCode.invalid_grant, message = "Authorization code could not be verified" From ef78d6b41613132db1402149f86f80d529402544 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Fri, 29 Nov 2024 19:04:54 +0200 Subject: [PATCH 44/53] fix: remove unused null parameter --- .../src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt index e999fb689..b38c206f8 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt @@ -193,7 +193,7 @@ object OpenID4VC { put("scope", "openid") when (responseType) { ResponseType.VpToken -> put("presentation_definition", presentationDefinition!!.toJSON()) - else -> null + else -> {} } }, privKey = tokenKey From 2cbf84d22f43d641092cc01302443771079e7dbb Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Mon, 16 Dec 2024 16:09:41 +0200 Subject: [PATCH 45/53] fix: rename 10 to 11 --- .../commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt | 8 ++++---- .../commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt | 12 ++++++------ .../kotlin/id/walt/oid4vc/data/CredentialOffer.kt | 10 +++++----- .../id/walt/oid4vc/data/CredentialSupported.kt | 8 ++++---- .../id/walt/oid4vc/data/OpenIDProviderMetadata.kt | 10 +++++----- .../src/test/kotlin/{IssuerD10.kt => IssuerD11.kt} | 10 +++++----- .../src/test/kotlin/WaltidServicesE2ETests.kt | 12 ++++++------ .../kotlin/id/walt/issuer/issuance/CIProvider.kt | 12 +++++------- .../kotlin/id/walt/issuer/issuance/OidcIssuance.kt | 2 +- 9 files changed, 41 insertions(+), 43 deletions(-) rename waltid-services/waltid-e2e-tests/src/test/kotlin/{IssuerD10.kt => IssuerD11.kt} (97%) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt index b38c206f8..81d970db8 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VC.kt @@ -151,7 +151,7 @@ object OpenID4VC { presentationDefinition: PresentationDefinition? = null, ): AuthorizationCodeWithAuthorizationRequestResponse { - providerMetadata.castOrNull() + providerMetadata.castOrNull() ?: providerMetadata.castOrNull() ?: error("Unknown metadata type: $providerMetadata") @@ -216,7 +216,7 @@ object OpenID4VC { message = "Invalid response type ${authorizationRequest.responseType}, for authorization code flow." ) - providerMetadata.castOrNull() + providerMetadata.castOrNull() ?: providerMetadata.castOrNull() ?: error("Unknown metadata type: $providerMetadata") @@ -226,7 +226,7 @@ object OpenID4VC { } suspend fun processImplicitFlowAuthorization(authorizationRequest: AuthorizationRequest, sessionId: String, providerMetadata: OpenIDProviderMetadata, tokenKey: Key): TokenResponse { - providerMetadata.castOrNull() + providerMetadata.castOrNull() ?: providerMetadata.castOrNull() ?: error("Unknown metadata type: $providerMetadata") @@ -249,7 +249,7 @@ object OpenID4VC { } suspend fun processDirectPost(authorizationRequest: AuthorizationRequest, sessionId: String, providerMetadata: OpenIDProviderMetadata, tokenKey: Key): AuthorizationCodeResponse { - providerMetadata.castOrNull() + providerMetadata.castOrNull() ?: providerMetadata.castOrNull() ?: error("Unknown metadata type: $providerMetadata") diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt index f7b88c30f..63c9708fb 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt @@ -149,13 +149,13 @@ object OpenID4VCI { fun resolveOfferedCredentials(credentialOffer: CredentialOffer, providerMetadata: OpenIDProviderMetadata): List { val supportedCredentials = when (providerMetadata) { - is OpenIDProviderMetadata.Draft10 -> providerMetadata.credentialSupported ?: mapOf() + is OpenIDProviderMetadata.Draft11 -> providerMetadata.credentialSupported ?: mapOf() is OpenIDProviderMetadata.Draft13 -> providerMetadata.credentialConfigurationsSupported ?: mapOf() } val credentialIds = when (credentialOffer) { is CredentialOffer.Draft13 -> credentialOffer.credentialConfigurationIds - is CredentialOffer.Draft10 -> credentialOffer.credentials + is CredentialOffer.Draft11 -> credentialOffer.credentials } return credentialIds.mapNotNull { id -> @@ -351,7 +351,7 @@ object OpenID4VCI { credentialConfigurationsSupported = credentialSupported ) - OpenID4VCIVersion.Draft10 -> OpenIDProviderMetadata.Draft10( + OpenID4VCIVersion.Draft11 -> OpenIDProviderMetadata.Draft11( issuer = baseUrl, authorizationEndpoint = "$baseUrl/authorize", pushedAuthorizationRequestEndpoint = "$baseUrl/par", @@ -425,7 +425,7 @@ object OpenID4VCI { } val supportedCredentialFormats = when (openIDProviderMetadata) { is OpenIDProviderMetadata.Draft13 -> openIDProviderMetadata.credentialConfigurationsSupported?.values?.map { it.format }?.toSet() ?: setOf() - is OpenIDProviderMetadata.Draft10 -> openIDProviderMetadata.credentialSupported?.values?.map { it.format }?.toSet() ?: setOf() + is OpenIDProviderMetadata.Draft11 -> openIDProviderMetadata.credentialSupported?.values?.map { it.format }?.toSet() ?: setOf() } if (!supportedCredentialFormats.contains(credentialRequest.format)) @@ -531,13 +531,13 @@ object OpenID4VCI { enum class OpenID4VCIVersion(val versionString: String) { - Draft10("draft10"), + Draft11("draft11"), Draft13("draft13"); companion object { fun from(version: String): OpenID4VCIVersion { return entries.find { it.versionString == version } - ?: throw IllegalArgumentException("Unsupported version: $version. Supported Versions are: Draft13 -> d13 and Draft10 -> d10") + ?: throw IllegalArgumentException("Unsupported version: $version. Supported Versions are: Draft13 -> draft13 and Draft11 -> draft11") } } } \ No newline at end of file diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/CredentialOffer.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/CredentialOffer.kt index 521626b32..62aeabe33 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/CredentialOffer.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/CredentialOffer.kt @@ -71,7 +71,7 @@ sealed class CredentialOffer() : JsonDataObject() { } @Serializable - data class Draft10 ( + data class Draft11 ( @SerialName("credential_issuer") override val credentialIssuer: String, @SerialName("grants") override val grants: Map = mapOf(), @@ -80,9 +80,9 @@ sealed class CredentialOffer() : JsonDataObject() { override val customParameters: Map = mapOf() ) : CredentialOffer() { - class Builder(credentialIssuer: String) : CredentialOffer.Builder(credentialIssuer) { + class Builder(credentialIssuer: String) : CredentialOffer.Builder(credentialIssuer) { - override fun buildInternal() = Draft10( + override fun buildInternal() = Draft11( credentialIssuer = credentialIssuer, grants = grants, credentials = supportedCredentialIds @@ -107,14 +107,14 @@ object CredentialOfferSerializer : KSerializer { // TODO: () return when { "credential_configuration_ids" in jsonObject -> Json.decodeFromJsonElement(CredentialOffer.Draft13.serializer(), jsonObject) - "credentials" in jsonObject -> Json.decodeFromJsonElement(CredentialOffer.Draft10.serializer(), jsonObject) + "credentials" in jsonObject -> Json.decodeFromJsonElement(CredentialOffer.Draft11.serializer(), jsonObject) else -> throw IllegalArgumentException("Unknown CredentialOffer type: missing expected fields") } } private val CredentialOfferSerializersModule = SerializersModule { polymorphic(CredentialOffer::class) { - subclass(CredentialOffer.Draft10::class, CredentialOffer.Draft10.serializer()) + subclass(CredentialOffer.Draft11::class, CredentialOffer.Draft11.serializer()) subclass(CredentialOffer.Draft13::class, CredentialOffer.Draft13.serializer()) } } diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/CredentialSupported.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/CredentialSupported.kt index 8697b7e25..6ba9b613a 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/CredentialSupported.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/CredentialSupported.kt @@ -68,9 +68,9 @@ data class CredentialSupported( val scope: String? = null, @SerialName("vct") val vct: String? = null, @SerialName("cryptographic_binding_methods_supported") val cryptographicBindingMethodsSupported: Set? = null, - @SerialName("id") val id: String? = null, // for draft 10 - @SerialName("cryptographic_suites_supported") val cryptographicSuitesSupported: Set? = null, // for draft 10 - val types: List? = null, // for draft 10 + @SerialName("id") val id: String? = null, // for draft 11 + @SerialName("cryptographic_suites_supported") val cryptographicSuitesSupported: Set? = null, // for draft 11 + val types: List? = null, // for draft 11 @SerialName("credential_signing_alg_values_supported") val credentialSigningAlgValuesSupported: Set? = null, @SerialName("proof_types_supported") val proofTypesSupported: Map? = null, @Serializable(DisplayPropertiesListSerializer::class) val display: List? = null, @@ -111,7 +111,7 @@ object CredentialSupportedMapSerializer : KSerializer` is a JsonObject */ diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt index a4de0b464..cf46b401b 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/data/OpenIDProviderMetadata.kt @@ -104,7 +104,7 @@ sealed class OpenIDProviderMetadata() : JsonDataObject() { abstract val opPolicyUri: String? abstract val opTosUri: String? - // OID4VCI Draft 10 and Draft 13 properties + // OID4VCI Draft 11 and Draft 13 properties abstract val credentialIssuer: String? abstract val credentialEndpoint: String? abstract val batchCredentialEndpoint: String? @@ -121,7 +121,7 @@ sealed class OpenIDProviderMetadata() : JsonDataObject() { } @Serializable - data class Draft10( + data class Draft11( @SerialName("issuer") override val issuer: String? = null, @SerialName("authorization_endpoint") override val authorizationEndpoint: String? = null, @SerialName("pushed_authorization_request_endpoint") override val pushedAuthorizationRequestEndpoint: String? = null, @@ -177,7 +177,7 @@ sealed class OpenIDProviderMetadata() : JsonDataObject() { @SerialName("require_pushed_authorization_requests") override val requirePushedAuthorizationRequests: Boolean? = null, @SerialName("dpop_signing_alg_values_supported") override val dpopSigningAlgValuesSupported: Set? = null, - // OID4VCI 10 + // OID4VCI 11 @SerialName("credentials_supported") @Serializable(CredentialSupportedArraySerializer::class) val credentialSupported: Map? = null, @SerialName("authorization_server") val authorizationServer: String? = null, @@ -304,7 +304,7 @@ object OpenIDProviderMetadataSerializer : KSerializer { // TODO: () return when { - "credentials_supported" in jsonObject -> Json.decodeFromJsonElement(OpenIDProviderMetadata.Draft10.serializer(), jsonObject) + "credentials_supported" in jsonObject -> Json.decodeFromJsonElement(OpenIDProviderMetadata.Draft11.serializer(), jsonObject) "credential_configurations_supported" in jsonObject -> Json.decodeFromJsonElement(OpenIDProviderMetadata.Draft13.serializer(), jsonObject) else -> throw IllegalArgumentException("Unknown OpenIDProviderMetadata version: missing expected fields") } @@ -312,7 +312,7 @@ object OpenIDProviderMetadataSerializer : KSerializer { private val OpenIDProviderMetadataSerializersModule = SerializersModule { polymorphic(OpenIDProviderMetadata::class) { - subclass(OpenIDProviderMetadata.Draft10::class, OpenIDProviderMetadata.Draft10.serializer()) + subclass(OpenIDProviderMetadata.Draft11::class, OpenIDProviderMetadata.Draft11.serializer()) subclass(OpenIDProviderMetadata.Draft13::class, OpenIDProviderMetadata.Draft13.serializer()) } } diff --git a/waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD10.kt b/waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD11.kt similarity index 97% rename from waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD10.kt rename to waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD11.kt index 77b78c930..5c2d49fc9 100644 --- a/waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD10.kt +++ b/waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD11.kt @@ -20,9 +20,9 @@ import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.* import kotlin.test.* -class IssuerDraft10(private val client: HttpClient) { +class IssuerDraft11(private val client: HttpClient) { - fun testIssuerAPIDraft10AuthFlowWithJar(issuanceReq: IssuanceRequest) = runBlocking { + fun testIssuerAPIDraft11AuthFlowWithJar(issuanceReq: IssuanceRequest) = runBlocking { lateinit var offerUrl: String lateinit var issuerState: String lateinit var authJarTokenRequest: AuthorizationRequest @@ -35,7 +35,7 @@ class IssuerDraft10(private val client: HttpClient) { val offerUrlParams = Url(offerUrl).parameters.toMap() val offerObj = CredentialOfferRequest.fromHttpParameters(offerUrlParams) - val credOffer = client.get(offerObj.credentialOfferUri!!).body() + val credOffer = client.get(offerObj.credentialOfferUri!!).body() assertNotNull(credOffer.credentialIssuer) assertNotNull(credOffer.credentials) @@ -47,7 +47,7 @@ class IssuerDraft10(private val client: HttpClient) { val jsonElementMetadata = Json.parseToJsonElement(rawJsonMetadata) assertTrue(jsonElementMetadata.jsonObject["credentials_supported"] is JsonArray, "Expected credentials_supported in Open ID Provider Metadata to be a JsonArray") - val issuerMetadata = OpenIDProviderMetadata.fromJSONString(rawJsonMetadata) as OpenIDProviderMetadata.Draft10 + val issuerMetadata = OpenIDProviderMetadata.fromJSONString(rawJsonMetadata) as OpenIDProviderMetadata.Draft11 assertNull(issuerMetadata.authorizationServer) assertContains(issuerMetadata.grantTypesSupported, GrantType.authorization_code) assertContains(issuerMetadata.grantTypesSupported, GrantType.pre_authorized_code) @@ -184,7 +184,7 @@ class IssuerDraft10(private val client: HttpClient) { } private fun validateAuthorizationData( - issuerMetadata: OpenIDProviderMetadata. Draft10, + issuerMetadata: OpenIDProviderMetadata. Draft11, holderAuthorizationRequest: AuthorizationRequest, jarPayload: Map? = null, issuerAuthorizationRequest: AuthorizationRequest? = null diff --git a/waltid-services/waltid-e2e-tests/src/test/kotlin/WaltidServicesE2ETests.kt b/waltid-services/waltid-e2e-tests/src/test/kotlin/WaltidServicesE2ETests.kt index 748701a25..dab9e3fde 100644 --- a/waltid-services/waltid-e2e-tests/src/test/kotlin/WaltidServicesE2ETests.kt +++ b/waltid-services/waltid-e2e-tests/src/test/kotlin/WaltidServicesE2ETests.kt @@ -340,24 +340,24 @@ class WaltidServicesE2ETests { val authorizationCodeFlow = AuthorizationCodeFlow(testHttpClient(doFollowRedirects = false)) authorizationCodeFlow.testIssuerAPI() - // Test Issuer Draft 10 - val issuerDraft10 = IssuerDraft10(testHttpClient(doFollowRedirects = false)) + // Test Issuer Draft 11 + val issuerDraft11 = IssuerDraft11(testHttpClient(doFollowRedirects = false)) val idTokenIssuanceReq = Json.decodeFromString(loadResource("issuance/openbadgecredential-issuance-request-with-authorization-code-flow-and-id-token.json")).copy( credentialConfigurationId = "OpenBadgeCredential_jwt_vc", - standardVersion = OpenID4VCIVersion.Draft10, + standardVersion = OpenID4VCIVersion.Draft11, useJar = true ) - issuerDraft10.testIssuerAPIDraft10AuthFlowWithJar(idTokenIssuanceReq) + issuerDraft11.testIssuerAPIDraft11AuthFlowWithJar(idTokenIssuanceReq) val vpTokenIssuanceReq = Json.decodeFromString(loadResource("issuance/openbadgecredential-issuance-request-with-authorization-code-flow-and-vp-token.json")).copy( credentialConfigurationId = "OpenBadgeCredential_jwt_vc", - standardVersion = OpenID4VCIVersion.Draft10, + standardVersion = OpenID4VCIVersion.Draft11, useJar = true ) - issuerDraft10.testIssuerAPIDraft10AuthFlowWithJar(vpTokenIssuanceReq) + issuerDraft11.testIssuerAPIDraft11AuthFlowWithJar(vpTokenIssuanceReq) // Test External Signature API Endpoints diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt index 09ce7c1d4..519c81e65 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt @@ -7,9 +7,7 @@ import COSE.OneKey import cbor.Cbor import com.nimbusds.jose.jwk.JWK import com.nimbusds.jose.util.X509CertUtils -import com.sksamuel.hoplite.ConfigException import id.walt.commons.config.ConfigManager -import id.walt.commons.config.ConfigurationException import id.walt.commons.persistence.ConfiguredPersistence import id.walt.crypto.keys.* import id.walt.crypto.keys.jwk.JWKKey @@ -68,7 +66,7 @@ import kotlin.uuid.ExperimentalUuidApi @OptIn(ExperimentalUuidApi::class) open class CIProvider( val baseUrl: String = let { ConfigManager.getConfig().baseUrl + "/${OpenID4VCIVersion.Draft13.versionString}"}, - val baseUrlDraft10: String = let { ConfigManager.getConfig().baseUrl + "/${OpenID4VCIVersion.Draft10.versionString}"}, + val baseUrlDraft11: String = let { ConfigManager.getConfig().baseUrl + "/${OpenID4VCIVersion.Draft11.versionString}"}, val config: CredentialIssuerConfig = CredentialIssuerConfig(credentialConfigurationsSupported = ConfigManager.getConfig().parse()) ) { @@ -76,8 +74,8 @@ open class CIProvider( val metadata get() = (OpenID4VCI.createDefaultProviderMetadata(baseUrl, config.credentialConfigurationsSupported, OpenID4VCIVersion.Draft13) as OpenIDProviderMetadata.Draft13) - val metadataDraft10 - get() = (OpenID4VCI.createDefaultProviderMetadata(baseUrlDraft10, config.credentialConfigurationsSupported, OpenID4VCIVersion.Draft10) as OpenIDProviderMetadata.Draft10) + val metadataDraft11 + get() = (OpenID4VCI.createDefaultProviderMetadata(baseUrlDraft11, config.credentialConfigurationsSupported, OpenID4VCIVersion.Draft11) as OpenIDProviderMetadata.Draft11) companion object { private val log = KotlinLogging.logger { } @@ -605,7 +603,7 @@ open class CIProvider( private fun resolveBaseUrl(version: OpenID4VCIVersion): String { return when (version) { OpenID4VCIVersion.Draft13 -> baseUrl - OpenID4VCIVersion.Draft10 -> baseUrlDraft10 + OpenID4VCIVersion.Draft11 -> baseUrlDraft11 else -> throw IllegalArgumentException("Unsupported version: $version") } } @@ -633,7 +631,7 @@ open class CIProvider( val version = OpenID4VCIVersion.from(standardVersion ?: throw IllegalArgumentException("standardVersion parameter is required")) return when (version) { - OpenID4VCIVersion.Draft10 -> metadataDraft10 + OpenID4VCIVersion.Draft11 -> metadataDraft11 OpenID4VCIVersion.Draft13 -> metadata } } diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcIssuance.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcIssuance.kt index 2bb5d91bc..391a4553c 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcIssuance.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcIssuance.kt @@ -29,7 +29,7 @@ object OidcIssuance { fun issuanceRequestsToCredentialOfferBuilder(vararg issuanceRequests: IssuanceRequest, standardVersion: OpenID4VCIVersion): CredentialOffer.Builder<*> { val builder = when (standardVersion) { OpenID4VCIVersion.Draft13 -> CredentialOffer.Draft13.Builder(OidcApi.baseUrl) - OpenID4VCIVersion.Draft10 -> CredentialOffer.Draft10.Builder(OidcApi.baseUrlDraft10) + OpenID4VCIVersion.Draft11 -> CredentialOffer.Draft11.Builder(OidcApi.baseUrlDraft11) } issuanceRequests.forEach { issuanceRequest -> From 5f9ec8ea93b87b905f36fb841612383cde57cdad Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Mon, 16 Dec 2024 17:11:42 +0200 Subject: [PATCH 46/53] feat: add draft13 to issuer portal --- waltid-applications/waltid-web-portal/utils/getOfferUrl.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/waltid-applications/waltid-web-portal/utils/getOfferUrl.tsx b/waltid-applications/waltid-web-portal/utils/getOfferUrl.tsx index 85bb40caa..f87a95622 100644 --- a/waltid-applications/waltid-web-portal/utils/getOfferUrl.tsx +++ b/waltid-applications/waltid-web-portal/utils/getOfferUrl.tsx @@ -16,7 +16,7 @@ const getOfferUrl = async ( vpProfile?: string ) => { const data = await fetch( - `${NEXT_PUBLIC_ISSUER}/.well-known/openid-credential-issuer` + `${NEXT_PUBLIC_ISSUER}/draft13/.well-known/openid-credential-issuer` ).then((data) => { return data.json(); }); From 356599b393d2458699d29ac977b72cdb4c8fbb3f Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Tue, 17 Dec 2024 12:36:24 +0200 Subject: [PATCH 47/53] refactor: rename constants from lowercase to uppercase --- .../kotlin/id/walt/oid4vc/OpenID4VCI.kt | 10 +++++----- .../id/walt/oid4vc/providers/OpenIDProvider.kt | 2 +- .../kotlin/id/walt/oid4vc/OpenID4VCI_Test.kt | 2 +- .../src/test/kotlin/WaltidServicesE2ETests.kt | 4 ++-- .../id/walt/issuer/issuance/CIProvider.kt | 18 +++++++++--------- .../walt/issuer/issuance/IssuanceRequests.kt | 4 ++-- .../id/walt/issuer/issuance/OidcIssuance.kt | 4 ++-- 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt index 63c9708fb..eef76d47d 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt @@ -328,7 +328,7 @@ object OpenID4VCI { fun createDefaultProviderMetadata(baseUrl: String, credentialSupported: Map, version: OpenID4VCIVersion) : OpenIDProviderMetadata { return when (version) { - OpenID4VCIVersion.Draft13 -> OpenIDProviderMetadata.Draft13( + OpenID4VCIVersion.DRAFT13 -> OpenIDProviderMetadata.Draft13( issuer = baseUrl, authorizationEndpoint = "$baseUrl/authorize", pushedAuthorizationRequestEndpoint = "$baseUrl/par", @@ -351,7 +351,7 @@ object OpenID4VCI { credentialConfigurationsSupported = credentialSupported ) - OpenID4VCIVersion.Draft11 -> OpenIDProviderMetadata.Draft11( + OpenID4VCIVersion.DRAFT11 -> OpenIDProviderMetadata.Draft11( issuer = baseUrl, authorizationEndpoint = "$baseUrl/authorize", pushedAuthorizationRequestEndpoint = "$baseUrl/par", @@ -531,13 +531,13 @@ object OpenID4VCI { enum class OpenID4VCIVersion(val versionString: String) { - Draft11("draft11"), - Draft13("draft13"); + DRAFT11("draft11"), + DRAFT13("draft13"); companion object { fun from(version: String): OpenID4VCIVersion { return entries.find { it.versionString == version } - ?: throw IllegalArgumentException("Unsupported version: $version. Supported Versions are: Draft13 -> draft13 and Draft11 -> draft11") + ?: throw IllegalArgumentException("Unsupported version: $version. Supported Versions are: DRAFT13 -> draft13 and DRAFT11 -> draft11") } } } \ No newline at end of file diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDProvider.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDProvider.kt index 2d6e900d2..d48f1102b 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDProvider.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/providers/OpenIDProvider.kt @@ -34,7 +34,7 @@ abstract class OpenIDProvider( abstract val metadata: OpenIDProviderMetadata.Draft13 abstract val config: OpenIDProviderConfig - protected open fun createDefaultProviderMetadata() = OpenID4VCI.createDefaultProviderMetadata(baseUrl, emptyMap(), OpenID4VCIVersion.Draft13) + protected open fun createDefaultProviderMetadata() = OpenID4VCI.createDefaultProviderMetadata(baseUrl, emptyMap(), OpenID4VCIVersion.DRAFT13) fun getCommonProviderMetadataUrl(): String { return URLBuilder(baseUrl).apply { diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/OpenID4VCI_Test.kt b/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/OpenID4VCI_Test.kt index 335582f2b..43ec341d6 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/OpenID4VCI_Test.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/jvmTest/kotlin/id/walt/oid4vc/OpenID4VCI_Test.kt @@ -33,7 +33,7 @@ import kotlin.test.* class OpenID4VCI_Test { val ISSUER_BASE_URL = "https://test" val CREDENTIAL_OFFER_BASE_URL = "openid-credential-offer://test" - val ISSUER_METADATA = (OpenID4VCI.createDefaultProviderMetadata(ISSUER_BASE_URL, emptyMap(), OpenID4VCIVersion.Draft13) as OpenIDProviderMetadata.Draft13).copy( + val ISSUER_METADATA = (OpenID4VCI.createDefaultProviderMetadata(ISSUER_BASE_URL, emptyMap(), OpenID4VCIVersion.DRAFT13) as OpenIDProviderMetadata.Draft13).copy( credentialConfigurationsSupported = mapOf( "VerifiableId" to CredentialSupported( CredentialFormat.jwt_vc_json, diff --git a/waltid-services/waltid-e2e-tests/src/test/kotlin/WaltidServicesE2ETests.kt b/waltid-services/waltid-e2e-tests/src/test/kotlin/WaltidServicesE2ETests.kt index dab9e3fde..97f3bbe97 100644 --- a/waltid-services/waltid-e2e-tests/src/test/kotlin/WaltidServicesE2ETests.kt +++ b/waltid-services/waltid-e2e-tests/src/test/kotlin/WaltidServicesE2ETests.kt @@ -345,7 +345,7 @@ class WaltidServicesE2ETests { val idTokenIssuanceReq = Json.decodeFromString(loadResource("issuance/openbadgecredential-issuance-request-with-authorization-code-flow-and-id-token.json")).copy( credentialConfigurationId = "OpenBadgeCredential_jwt_vc", - standardVersion = OpenID4VCIVersion.Draft11, + standardVersion = OpenID4VCIVersion.DRAFT11, useJar = true ) @@ -353,7 +353,7 @@ class WaltidServicesE2ETests { val vpTokenIssuanceReq = Json.decodeFromString(loadResource("issuance/openbadgecredential-issuance-request-with-authorization-code-flow-and-vp-token.json")).copy( credentialConfigurationId = "OpenBadgeCredential_jwt_vc", - standardVersion = OpenID4VCIVersion.Draft11, + standardVersion = OpenID4VCIVersion.DRAFT11, useJar = true ) diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt index 519c81e65..180f90cf0 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/CIProvider.kt @@ -65,17 +65,17 @@ import kotlin.uuid.ExperimentalUuidApi */ @OptIn(ExperimentalUuidApi::class) open class CIProvider( - val baseUrl: String = let { ConfigManager.getConfig().baseUrl + "/${OpenID4VCIVersion.Draft13.versionString}"}, - val baseUrlDraft11: String = let { ConfigManager.getConfig().baseUrl + "/${OpenID4VCIVersion.Draft11.versionString}"}, + val baseUrl: String = let { ConfigManager.getConfig().baseUrl + "/${OpenID4VCIVersion.DRAFT13.versionString}"}, + val baseUrlDraft11: String = let { ConfigManager.getConfig().baseUrl + "/${OpenID4VCIVersion.DRAFT11.versionString}"}, val config: CredentialIssuerConfig = CredentialIssuerConfig(credentialConfigurationsSupported = ConfigManager.getConfig().parse()) ) { val metadata - get() = (OpenID4VCI.createDefaultProviderMetadata(baseUrl, config.credentialConfigurationsSupported, OpenID4VCIVersion.Draft13) as OpenIDProviderMetadata.Draft13) + get() = (OpenID4VCI.createDefaultProviderMetadata(baseUrl, config.credentialConfigurationsSupported, OpenID4VCIVersion.DRAFT13) as OpenIDProviderMetadata.Draft13) val metadataDraft11 - get() = (OpenID4VCI.createDefaultProviderMetadata(baseUrlDraft11, config.credentialConfigurationsSupported, OpenID4VCIVersion.Draft11) as OpenIDProviderMetadata.Draft11) + get() = (OpenID4VCI.createDefaultProviderMetadata(baseUrlDraft11, config.credentialConfigurationsSupported, OpenID4VCIVersion.DRAFT11) as OpenIDProviderMetadata.Draft11) companion object { private val log = KotlinLogging.logger { } @@ -493,7 +493,7 @@ open class CIProvider( callbackUrl: String? = null, txCode: TxCode? = null, txCodeValue: String? = null, - standardVersion: OpenID4VCIVersion = OpenID4VCIVersion.Draft13 + standardVersion: OpenID4VCIVersion = OpenID4VCIVersion.DRAFT13 ): IssuanceSession = runBlocking { val sessionId = randomUUID() @@ -602,8 +602,8 @@ open class CIProvider( private fun resolveBaseUrl(version: OpenID4VCIVersion): String { return when (version) { - OpenID4VCIVersion.Draft13 -> baseUrl - OpenID4VCIVersion.Draft11 -> baseUrlDraft11 + OpenID4VCIVersion.DRAFT13 -> baseUrl + OpenID4VCIVersion.DRAFT11 -> baseUrlDraft11 else -> throw IllegalArgumentException("Unsupported version: $version") } } @@ -631,8 +631,8 @@ open class CIProvider( val version = OpenID4VCIVersion.from(standardVersion ?: throw IllegalArgumentException("standardVersion parameter is required")) return when (version) { - OpenID4VCIVersion.Draft11 -> metadataDraft11 - OpenID4VCIVersion.Draft13 -> metadata + OpenID4VCIVersion.DRAFT11 -> metadataDraft11 + OpenID4VCIVersion.DRAFT13 -> metadata } } diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuanceRequests.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuanceRequests.kt index fdfacea69..c1df45742 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuanceRequests.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuanceRequests.kt @@ -93,7 +93,7 @@ data class IssuanceRequest( val x5Chain: List? = null, val trustedRootCAs: List? = null, var credentialFormat: CredentialFormat? = null, - val standardVersion: OpenID4VCIVersion? = OpenID4VCIVersion.Draft13, + val standardVersion: OpenID4VCIVersion? = OpenID4VCIVersion.DRAFT13, ) { constructor( issuerKey: JsonObject, @@ -110,7 +110,7 @@ data class IssuanceRequest( x5Chain: List? = null, trustedRootCAs: List? = null, credentialFormat: CredentialFormat? = null, - standardVersion: OpenID4VCIVersion? = OpenID4VCIVersion.Draft13, + standardVersion: OpenID4VCIVersion? = OpenID4VCIVersion.DRAFT13, ) : this( issuerKey, diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcIssuance.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcIssuance.kt index 391a4553c..eba51fb8d 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcIssuance.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcIssuance.kt @@ -28,8 +28,8 @@ object OidcIssuance { fun issuanceRequestsToCredentialOfferBuilder(vararg issuanceRequests: IssuanceRequest, standardVersion: OpenID4VCIVersion): CredentialOffer.Builder<*> { val builder = when (standardVersion) { - OpenID4VCIVersion.Draft13 -> CredentialOffer.Draft13.Builder(OidcApi.baseUrl) - OpenID4VCIVersion.Draft11 -> CredentialOffer.Draft11.Builder(OidcApi.baseUrlDraft11) + OpenID4VCIVersion.DRAFT13 -> CredentialOffer.Draft13.Builder(OidcApi.baseUrl) + OpenID4VCIVersion.DRAFT11 -> CredentialOffer.Draft11.Builder(OidcApi.baseUrlDraft11) } issuanceRequests.forEach { issuanceRequest -> From 6e46d74c3a056d68db30fa857bbd302891584c2f Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Thu, 19 Dec 2024 11:10:23 +0200 Subject: [PATCH 48/53] fix: add OptIn for uuid in test exchange api --- .../waltid-e2e-tests/src/test/kotlin/ExchangeApi.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/waltid-services/waltid-e2e-tests/src/test/kotlin/ExchangeApi.kt b/waltid-services/waltid-e2e-tests/src/test/kotlin/ExchangeApi.kt index 1d5162eae..81e6178e0 100644 --- a/waltid-services/waltid-e2e-tests/src/test/kotlin/ExchangeApi.kt +++ b/waltid-services/waltid-e2e-tests/src/test/kotlin/ExchangeApi.kt @@ -14,6 +14,7 @@ import kotlin.uuid.Uuid class ExchangeApi(private val client: HttpClient) { + @OptIn(ExperimentalUuidApi::class) suspend fun resolveCredentialOffer(wallet: Uuid, offerUrl: String, output: ((String) -> Unit)? = null) = test("/wallet-api/wallet/{wallet}/exchange/resolveCredentialOffer - resolve credential offer") { client.post("/wallet-api/wallet/$wallet/exchange/resolveCredentialOffer") { @@ -23,6 +24,7 @@ class ExchangeApi(private val client: HttpClient) { } } + @OptIn(ExperimentalUuidApi::class) suspend fun useOfferRequest( wallet: Uuid, offerUrl: String, @@ -39,6 +41,7 @@ class ExchangeApi(private val client: HttpClient) { } } + @OptIn(ExperimentalUuidApi::class) suspend fun resolvePresentationRequest( wallet: Uuid, presentationRequestUrl: String, @@ -54,6 +57,7 @@ class ExchangeApi(private val client: HttpClient) { } } + @OptIn(ExperimentalUuidApi::class) suspend fun matchCredentialsForPresentationDefinition( wallet: Uuid, presentationDefinition: String, @@ -72,6 +76,7 @@ class ExchangeApi(private val client: HttpClient) { } } + @OptIn(ExperimentalUuidApi::class) suspend fun unmatchedCredentialsForPresentationDefinition( wallet: Uuid, presentationDefinition: String, @@ -88,6 +93,7 @@ class ExchangeApi(private val client: HttpClient) { } } + @OptIn(ExperimentalUuidApi::class) suspend fun usePresentationRequest( wallet: Uuid, request: UsePresentationRequest, From 717931b4376e131c7597cced72a14e89c2972f97 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Thu, 19 Dec 2024 11:13:35 +0200 Subject: [PATCH 49/53] feat: add support for d11 in wallet-api --- .../commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt | 2 +- .../service/exchange/CredentialOfferProcessor.kt | 1 - .../webwallet/service/exchange/IssuanceService.kt | 2 +- .../web/controllers/exchange/ExchangeController.kt | 11 +++++++++-- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt index eef76d47d..158887d19 100644 --- a/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt +++ b/waltid-libraries/protocols/waltid-openid4vc/src/commonMain/kotlin/id/walt/oid4vc/OpenID4VCI.kt @@ -149,8 +149,8 @@ object OpenID4VCI { fun resolveOfferedCredentials(credentialOffer: CredentialOffer, providerMetadata: OpenIDProviderMetadata): List { val supportedCredentials = when (providerMetadata) { - is OpenIDProviderMetadata.Draft11 -> providerMetadata.credentialSupported ?: mapOf() is OpenIDProviderMetadata.Draft13 -> providerMetadata.credentialConfigurationsSupported ?: mapOf() + is OpenIDProviderMetadata.Draft11 -> providerMetadata.credentialSupported?.values?.associateBy { it.id } ?: mapOf() } val credentialIds = when (credentialOffer) { diff --git a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/CredentialOfferProcessor.kt b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/CredentialOfferProcessor.kt index 5dea4aa2a..64d87b741 100644 --- a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/CredentialOfferProcessor.kt +++ b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/CredentialOfferProcessor.kt @@ -55,7 +55,6 @@ object CredentialOfferProcessor { providerMetadata: OpenIDProviderMetadata, accessToken: String, ): List { - providerMetadata as OpenIDProviderMetadata.Draft13 val credReq = credReqs.first() diff --git a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/IssuanceService.kt b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/IssuanceService.kt index f644c322b..7c6fd6266 100644 --- a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/IssuanceService.kt +++ b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/service/exchange/IssuanceService.kt @@ -65,7 +65,7 @@ object IssuanceService: IssuanceServiceBase() { val providerMetadata = getCredentialIssuerOpenIDMetadata( credentialOffer.credentialIssuer, credentialWallet, - ) as OpenIDProviderMetadata.Draft13 + ) logger.debug { "providerMetadata: $providerMetadata" } diff --git a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/web/controllers/exchange/ExchangeController.kt b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/web/controllers/exchange/ExchangeController.kt index 6d5f2d9da..b99453720 100644 --- a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/web/controllers/exchange/ExchangeController.kt +++ b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/web/controllers/exchange/ExchangeController.kt @@ -2,7 +2,9 @@ package id.walt.webwallet.web.controllers.exchange import id.walt.oid4vc.OpenID4VCI import id.walt.oid4vc.data.CredentialOffer +import id.walt.oid4vc.data.CredentialOfferSerializer import id.walt.oid4vc.data.OpenIDProviderMetadata +import id.walt.oid4vc.data.OpenIDProviderMetadataSerializer import id.walt.oid4vc.data.dif.PresentationDefinition import id.walt.oid4vc.requests.CredentialOfferRequest import id.walt.sdjwt.SDJWTVCTypeMetadata @@ -25,6 +27,7 @@ import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.util.* import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlin.uuid.ExperimentalUuidApi @@ -235,7 +238,10 @@ fun Application.exchange() = walletRoute { val request = call.receiveText() val reqParams = Url(request).parameters.toMap() val parsedOffer = wallet.resolveCredentialOffer(CredentialOfferRequest.fromHttpParameters(reqParams)) - context.respond(parsedOffer as CredentialOffer.Draft13) + + val serializedOffer = Json.encodeToString(CredentialOfferSerializer, parsedOffer) + + context.respondText(serializedOffer, ContentType.Application.Json) } get("resolveVctUrl", { summary = "Receive an verifiable credential type (VCT) URL and return resolved vct object as described in IETF SD-JWT VC" @@ -277,7 +283,8 @@ fun Application.exchange() = walletRoute { } }) { val issuer = call.request.queryParameters["issuer"] ?: throw BadRequestException("Issuer base url not set") - context.respond(HttpStatusCode.OK, OpenID4VCI.resolveCIProviderMetadata(issuer).toJSON()) + val serializedMetadata = Json.encodeToString(OpenIDProviderMetadataSerializer, OpenID4VCI.resolveCIProviderMetadata(issuer)) + context.respondText(serializedMetadata, ContentType.Application.Json) } } } From d8b1d1a23ac5916d58ba48d96c1ef9cd72db8060 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Thu, 19 Dec 2024 11:16:40 +0200 Subject: [PATCH 50/53] feat: add tests --- .../test/kotlin/{IssuerD11.kt => Draft11.kt} | 85 ++++++++++++++++++- .../src/test/kotlin/WaltidServicesE2ETests.kt | 14 ++- 2 files changed, 95 insertions(+), 4 deletions(-) rename waltid-services/waltid-e2e-tests/src/test/kotlin/{IssuerD11.kt => Draft11.kt} (72%) diff --git a/waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD11.kt b/waltid-services/waltid-e2e-tests/src/test/kotlin/Draft11.kt similarity index 72% rename from waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD11.kt rename to waltid-services/waltid-e2e-tests/src/test/kotlin/Draft11.kt index 5c2d49fc9..dc6bb1c67 100644 --- a/waltid-services/waltid-e2e-tests/src/test/kotlin/IssuerD11.kt +++ b/waltid-services/waltid-e2e-tests/src/test/kotlin/Draft11.kt @@ -1,4 +1,5 @@ import com.nimbusds.jose.JWSAlgorithm +import id.walt.credentials.schemes.JwsSignatureScheme import id.walt.credentials.utils.VCFormat import id.walt.crypto.keys.jwk.JWKKey import id.walt.crypto.utils.JsonUtils.toJsonElement @@ -10,6 +11,7 @@ import id.walt.oid4vc.data.dif.PresentationDefinition import id.walt.oid4vc.definitions.JWTClaims import id.walt.oid4vc.requests.AuthorizationRequest import id.walt.oid4vc.requests.CredentialOfferRequest +import id.walt.oid4vc.util.JwtUtils import io.ktor.client.* import io.ktor.client.call.* import io.ktor.client.request.* @@ -19,8 +21,10 @@ import io.ktor.util.* import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.* import kotlin.test.* +import kotlin.uuid.Uuid +import kotlin.uuid.ExperimentalUuidApi -class IssuerDraft11(private val client: HttpClient) { +class Draft11(private val client: HttpClient) { fun testIssuerAPIDraft11AuthFlowWithJar(issuanceReq: IssuanceRequest) = runBlocking { lateinit var offerUrl: String @@ -183,6 +187,85 @@ class IssuerDraft11(private val client: HttpClient) { } } + @OptIn(ExperimentalUuidApi::class) + fun testIssuanceDraft11PreAuthFlow(issuanceReq: IssuanceRequest, wallet: Uuid) = runBlocking { + lateinit var offerUrl: String + + val issuerApi = IssuerApi(client) + + issuerApi.jwt(issuanceReq) { + offerUrl = it + } + + val offerUrlParams = Url(offerUrl).parameters.toMap() + val offerObj = CredentialOfferRequest.fromHttpParameters(offerUrlParams) + val credOffer = client.get(offerObj.credentialOfferUri!!).body() + + assertNotNull(credOffer.credentialIssuer) + assertNotNull(credOffer.credentials) + assertNotNull(credOffer.grants) + + val issuerMetadataUrl = getCIProviderMetadataUrl(credOffer.credentialIssuer) + val rawJsonMetadata = client.get(issuerMetadataUrl).bodyAsText() + val jsonElementMetadata = Json.parseToJsonElement(rawJsonMetadata) + assertTrue(jsonElementMetadata.jsonObject["credentials_supported"] is JsonArray, "Expected credentials_supported in Open ID Provider Metadata to be a JsonArray") + + val issuerMetadata = OpenIDProviderMetadata.fromJSONString(rawJsonMetadata) as OpenIDProviderMetadata.Draft11 + assertNull(issuerMetadata.authorizationServer) + assertContains(issuerMetadata.grantTypesSupported, GrantType.authorization_code) + assertContains(issuerMetadata.grantTypesSupported, GrantType.pre_authorized_code) + assertNotNull(issuerMetadata.jwksUri) + assertTrue(issuerMetadata.credentialSupported!!.keys.all { it.toIntOrNull() != null }, "Expected credentials_supported keys to be array indices (e.g., '0', '1')") + + assertEquals(issuerMetadata.issuer, credOffer.credentialIssuer) + assertEquals(issuerMetadata.credentialIssuer, credOffer.credentialIssuer) + assertEquals(issuerMetadata.credentialIssuer, credOffer.credentialIssuer) + + val rawJsonJwks = client.get(issuerMetadata.jwksUri!!).bodyAsText() + + val keysArray = Json.parseToJsonElement(rawJsonJwks).jsonObject["keys"]?.jsonArray + ?: throw AssertionError("JWKS response must contain a 'keys' array") + + assertTrue( + keysArray.any { key -> + key.jsonObject.run { + this["kty"]?.jsonPrimitive?.content == "EC" && + this["crv"]?.jsonPrimitive?.content == "P-256" + } + }, + "JWKS must contain at least one key with 'kty': 'EC' and 'crv': 'P-256'" + ) + + + val matchingCredential = issuerMetadata.credentialSupported + ?.values + ?.find { it.id == issuanceReq.credentialConfigurationId } + ?: throw AssertionError("No matching credential found for the credentialConfigurationId '${issuanceReq.credentialConfigurationId}'.") + + assertEquals( + matchingCredential.id, + issuanceReq.credentialConfigurationId + ) + + assertEquals( + CredentialFormat.jwt_vc_json, + matchingCredential.format + ) + + val exchangeApi = ExchangeApi(client) + lateinit var newCredentialId: String + exchangeApi.resolveCredentialOffer(wallet, offerUrl) + exchangeApi.useOfferRequest(wallet, offerUrl, 1) { + assertNotNull(it) + val cred = it.first() + assertContains(JwtUtils.parseJWTPayload(cred.document).keys, JwsSignatureScheme.JwsOption.VC) + newCredentialId = cred.id + } + + assertNotNull(newCredentialId) + + } + private fun validateAuthorizationData( issuerMetadata: OpenIDProviderMetadata. Draft11, holderAuthorizationRequest: AuthorizationRequest, diff --git a/waltid-services/waltid-e2e-tests/src/test/kotlin/WaltidServicesE2ETests.kt b/waltid-services/waltid-e2e-tests/src/test/kotlin/WaltidServicesE2ETests.kt index 97f3bbe97..ee2afc9f1 100644 --- a/waltid-services/waltid-e2e-tests/src/test/kotlin/WaltidServicesE2ETests.kt +++ b/waltid-services/waltid-e2e-tests/src/test/kotlin/WaltidServicesE2ETests.kt @@ -341,7 +341,7 @@ class WaltidServicesE2ETests { authorizationCodeFlow.testIssuerAPI() // Test Issuer Draft 11 - val issuerDraft11 = IssuerDraft11(testHttpClient(doFollowRedirects = false)) + val draft11Issuer = Draft11(testHttpClient(doFollowRedirects = false)) val idTokenIssuanceReq = Json.decodeFromString(loadResource("issuance/openbadgecredential-issuance-request-with-authorization-code-flow-and-id-token.json")).copy( credentialConfigurationId = "OpenBadgeCredential_jwt_vc", @@ -349,7 +349,7 @@ class WaltidServicesE2ETests { useJar = true ) - issuerDraft11.testIssuerAPIDraft11AuthFlowWithJar(idTokenIssuanceReq) + draft11Issuer.testIssuerAPIDraft11AuthFlowWithJar(idTokenIssuanceReq) val vpTokenIssuanceReq = Json.decodeFromString(loadResource("issuance/openbadgecredential-issuance-request-with-authorization-code-flow-and-vp-token.json")).copy( credentialConfigurationId = "OpenBadgeCredential_jwt_vc", @@ -357,7 +357,15 @@ class WaltidServicesE2ETests { useJar = true ) - issuerDraft11.testIssuerAPIDraft11AuthFlowWithJar(vpTokenIssuanceReq) + draft11Issuer.testIssuerAPIDraft11AuthFlowWithJar(vpTokenIssuanceReq) + + val draft11 = Draft11(client) + + val preAuthFlowIssuanceReq = Json.decodeFromString(loadResource("issuance/openbadgecredential-issuance-request.json")).copy( + standardVersion = OpenID4VCIVersion.DRAFT11, + ) + + draft11.testIssuanceDraft11PreAuthFlow(preAuthFlowIssuanceReq, wallet) // Test External Signature API Endpoints From a6fc48b4af1215430d4d50825f05aed0b9bca1c6 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Thu, 19 Dec 2024 11:19:53 +0200 Subject: [PATCH 51/53] feat: add support for d11 in wallet-ui --- .../libs/composables/issuance.ts | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/waltid-applications/waltid-web-wallet/libs/composables/issuance.ts b/waltid-applications/waltid-web-wallet/libs/composables/issuance.ts index 61c71cfc3..afbdae46a 100644 --- a/waltid-applications/waltid-web-wallet/libs/composables/issuance.ts +++ b/waltid-applications/waltid-web-wallet/libs/composables/issuance.ts @@ -1,8 +1,8 @@ -import { useLazyAsyncData, createError, navigateTo } from "nuxt/app"; -import { useCurrentWallet } from "./accountWallet.ts"; -import { decodeRequest } from "./siop-requests.ts"; -import { type Ref, ref, watch } from "vue"; -import { groupBy } from "./groupings.ts"; +import {createError, navigateTo, useLazyAsyncData} from "nuxt/app"; +import {useCurrentWallet} from "./accountWallet.ts"; +import {decodeRequest} from "./siop-requests.ts"; +import {type Ref, ref, watch} from "vue"; +import {groupBy} from "./groupings.ts"; export async function useIssuance(query: any) { const currentWallet = useCurrentWallet() @@ -19,6 +19,7 @@ export async function useIssuance(query: any) { const response: { credential_issuer: string; credential_configuration_ids: string[]; + credentials: string[]; } = await $fetch(`/wallet-api/wallet/${currentWallet.value}/exchange/resolveCredentialOffer`, { method: "POST", body: request @@ -47,12 +48,33 @@ export async function useIssuance(query: any) { issuerHost = issuer; } - const credential_issuer: { credential_configurations_supported: Array<{ types: Array; }>; } = await $fetch(`/wallet-api/wallet/${currentWallet.value}/exchange/resolveIssuerOpenIDMetadata?issuer=${issuer}`) - const credentialList = credentialOffer.credential_configuration_ids.map((id) => credential_issuer.credential_configurations_supported[id]); + const credential_issuer: { + credential_configurations_supported: Array<{ types: Array; }>; // Draft13 + credentials_supported?: Array<{ id: string; types: Array }>; // Draft11 + } = await $fetch(`/wallet-api/wallet/${currentWallet.value}/exchange/resolveIssuerOpenIDMetadata?issuer=${issuer}`) + + + const credentialList = credentialOffer.credential_configuration_ids + // Draft13 + ? credentialOffer.credential_configuration_ids.map((id) => credential_issuer.credential_configurations_supported[id]) + + // Draft11 + : credentialOffer.credentials.map((id) => { + return credential_issuer.credentials_supported?.find( + (credential_supported) => credential_supported.id === id + ); + }).filter(Boolean); + let credentialTypes: String[] = []; for (let credentialListElement of credentialList) { + if (typeof credentialListElement["types"] !== 'undefined') { + const typeList = credentialListElement["types"] as Array; + const lastType = typeList[typeList.length - 1] as String; + credentialTypes.push(lastType); + } + if (typeof credentialListElement["credential_definition"] !== 'undefined') { const typeList = credentialListElement["credential_definition"]["type"] as Array; const lastType = typeList[typeList.length - 1] as String; From 7f99ae3535ca5c7fbf254c1c8771a07ccf9130d6 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Thu, 19 Dec 2024 11:33:13 +0200 Subject: [PATCH 52/53] fix: add {standardVersion} for oidc paths in swagger examples --- .../kotlin/id/walt/issuer/issuance/OidcApi.kt | 50 +++++++++++++++++-- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt index 976b93a8c..218c791eb 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt @@ -58,7 +58,15 @@ object OidcApi : CIProvider() { route("", { tags = listOf("oidc") }) { - get("{standardVersion}/.well-known/openid-configuration") { + get("{standardVersion}/.well-known/openid-configuration", { + request { + queryParameter("standardVersion") { + description = "The value of the standard version. Supported values are: draft13 and draft11" + example("Example") { value = "draft13" } + required = true + } + } + }) { val metadata = getMetadataForVersion( standardVersion = call.parameters["standardVersion"] ?: throw IllegalArgumentException("standardVersion parameter is required"), ) @@ -66,7 +74,15 @@ object OidcApi : CIProvider() { call.respond(metadata.toJSON()) } - get("{standardVersion}/.well-known/openid-credential-issuer") { + get("{standardVersion}/.well-known/openid-credential-issuer", { + request { + queryParameter("standardVersion") { + description = "The value of the standard version. Supported values are: draft13 and draft11" + example("Example") { value = "draft13" } + required = true + } + } + }) { val metadata = getMetadataForVersion( standardVersion = call.parameters["standardVersion"] ?: throw IllegalArgumentException("standardVersion parameter is required"), ) @@ -74,7 +90,15 @@ object OidcApi : CIProvider() { call.respond(metadata.toJSON()) } - get("{standardVersion}/.well-known/oauth-authorization-server") { + get("{standardVersion}/.well-known/oauth-authorization-server", { + request { + queryParameter("standardVersion") { + description = "The value of the standard version. Supported values are: draft13 and draft11" + example("Example") { value = "draft13" } + required = true + } + } + }) { val metadata = getMetadataForVersion( standardVersion = call.parameters["standardVersion"] ?: throw IllegalArgumentException("standardVersion parameter is required"), ) @@ -82,7 +106,15 @@ object OidcApi : CIProvider() { call.respond(metadata.toJSON()) } - get("/.well-known/jwt-vc-issuer/{standardVersion}") { + get("/.well-known/jwt-vc-issuer/{standardVersion}", { + request { + queryParameter("standardVersion") { + description = "The value of the standard version. Supported values are: draft13 and draft11" + example("Example") { value = "draft13" } + required = true + } + } + }) { call.respond(HttpStatusCode.OK, JWTVCIssuerMetadata(issuer = metadata.issuer, jwksUri = metadata.jwksUri)) } @@ -120,7 +152,15 @@ object OidcApi : CIProvider() { } } - get("{standardVersion}/jwks") { + get("{standardVersion}/jwks", { + request { + queryParameter("standardVersion") { + description = "The value of the standard version. Supported values are: draft13 and draft11" + example("Example") { value = "draft13" } + required = true + } + } + }) { call.respond(HttpStatusCode.OK, getJwksSessions()) } From 6f270e712ddb5ac56bf06542c440e66fdc98d2a1 Mon Sep 17 00:00:00 2001 From: chsavvaidis Date: Thu, 19 Dec 2024 11:58:33 +0200 Subject: [PATCH 53/53] fix: add function for {standardVersion} for oidc paths in swagger examples --- .../kotlin/id/walt/issuer/issuance/OidcApi.kt | 37 ++++++------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt index 218c791eb..8519b8f9f 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/OidcApi.kt @@ -22,6 +22,7 @@ import id.walt.oid4vc.util.randomUUID import id.walt.sdjwt.JWTVCIssuerMetadata import id.walt.sdjwt.SDJWTVCTypeMetadata import io.github.oshai.kotlinlogging.KotlinLogging +import io.github.smiley4.ktorswaggerui.dsl.routes.OpenApiRequest import io.github.smiley4.ktorswaggerui.dsl.routing.get import io.github.smiley4.ktorswaggerui.dsl.routing.route import io.ktor.http.* @@ -60,11 +61,7 @@ object OidcApi : CIProvider() { }) { get("{standardVersion}/.well-known/openid-configuration", { request { - queryParameter("standardVersion") { - description = "The value of the standard version. Supported values are: draft13 and draft11" - example("Example") { value = "draft13" } - required = true - } + standardVersionQueryParameter() } }) { val metadata = getMetadataForVersion( @@ -76,11 +73,7 @@ object OidcApi : CIProvider() { get("{standardVersion}/.well-known/openid-credential-issuer", { request { - queryParameter("standardVersion") { - description = "The value of the standard version. Supported values are: draft13 and draft11" - example("Example") { value = "draft13" } - required = true - } + standardVersionQueryParameter() } }) { val metadata = getMetadataForVersion( @@ -92,11 +85,7 @@ object OidcApi : CIProvider() { get("{standardVersion}/.well-known/oauth-authorization-server", { request { - queryParameter("standardVersion") { - description = "The value of the standard version. Supported values are: draft13 and draft11" - example("Example") { value = "draft13" } - required = true - } + standardVersionQueryParameter() } }) { val metadata = getMetadataForVersion( @@ -108,11 +97,7 @@ object OidcApi : CIProvider() { get("/.well-known/jwt-vc-issuer/{standardVersion}", { request { - queryParameter("standardVersion") { - description = "The value of the standard version. Supported values are: draft13 and draft11" - example("Example") { value = "draft13" } - required = true - } + standardVersionQueryParameter() } }) { call.respond(HttpStatusCode.OK, JWTVCIssuerMetadata(issuer = metadata.issuer, jwksUri = metadata.jwksUri)) @@ -154,11 +139,7 @@ object OidcApi : CIProvider() { get("{standardVersion}/jwks", { request { - queryParameter("standardVersion") { - description = "The value of the standard version. Supported values are: draft13 and draft11" - example("Example") { value = "draft13" } - required = true - } + standardVersionQueryParameter() } }) { call.respond(HttpStatusCode.OK, getJwksSessions()) @@ -569,6 +550,12 @@ object OidcApi : CIProvider() { } } + private fun OpenApiRequest.standardVersionQueryParameter() = queryParameter("standardVersion") { + description = "The value of the standard version. Supported values are: draft13 and draft11" + example("Example") { value = "draft13" } + required = true + } + private fun getPushedAuthorizationSession(authorizationRequest: AuthorizationRequest): IssuanceSession { return authorizationRequest.requestUri?.let { getVerifiedSession(OpenID4VC.getPushedAuthorizationSessionId(it)) ?: throw AuthorizationError(