diff --git a/CHANGELOG.md b/CHANGELOG.md index aad782f75..cf5a786ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ Release 5.1.0: - Refactor `issueCredential` of `Issuer` to directly get the credential-to-be-issued - Replace `buildIssuerCredentialDataProviderOverride` in `CredentialIssuer` with `credentialProvider` to extract user information into a credential - Remove `dataProvider` from `IssuerAgent`s constructor, as it is not needed with the new issuing interface anyway + - Replace `relyingPartyUrl` with `clientIdScheme` on `OidcSiopVerifier`s constructor, to clarify use of `client_id` in requests + - Rename objects in `OpenIdConstants.ProofType`, `OpenIdConstants.CliendIdScheme` and `OpenIdConstants.ResponseMode` Release 5.0.1: - Update JsonPath4K to 2.4.0 diff --git a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/AuthenticationRequestParameters.kt b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/AuthenticationRequestParameters.kt index 295d322ea..7d73f9898 100644 --- a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/AuthenticationRequestParameters.kt +++ b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/AuthenticationRequestParameters.kt @@ -2,20 +2,12 @@ package at.asitplus.openid import at.asitplus.KmmResult.Companion.wrap import at.asitplus.dif.PresentationDefinition -import at.asitplus.signum.indispensable.io.ByteArrayBase64Serializer -import at.asitplus.signum.indispensable.io.ByteArrayBase64UrlSerializer import at.asitplus.signum.indispensable.josef.JsonWebToken import at.asitplus.signum.indispensable.josef.io.InstantLongSerializer import kotlinx.datetime.Instant -import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encodeToString -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder /** * Contents of an OIDC Authentication Request. @@ -41,7 +33,7 @@ data class AuthenticationRequestParameters( * OIDC: REQUIRED. OAuth 2.0 Client Identifier valid at the Authorization Server. */ @SerialName("client_id") - val clientId: String? = null, + val clientId: String, /** * OIDC: REQUIRED. Redirection URI to which the response will be sent. This URI MUST exactly match one of the diff --git a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/OpenIdConstants.kt b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/OpenIdConstants.kt index a0f9ce73e..5b5155416 100644 --- a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/OpenIdConstants.kt +++ b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/OpenIdConstants.kt @@ -78,7 +78,7 @@ object OpenIdConstants { * Any proof type not natively supported by this library */ @Serializable(with = Serializer::class) - class OTHER(stringRepresentation: String) : ProofType(stringRepresentation) + class Other(stringRepresentation: String) : ProofType(stringRepresentation) object Serializer : KSerializer { override val descriptor: SerialDescriptor = @@ -88,7 +88,7 @@ object OpenIdConstants { return when (val str = decoder.decodeString()) { STRING_JWT -> JWT STRING_CWT -> CWT - else -> OTHER(str) + else -> Other(str) } } @@ -121,21 +121,22 @@ object OpenIdConstants { } /** - * This value represents the RFC6749 default behavior, i.e., the Client Identifier needs to be known to the - * Wallet in advance of the Authorization Request. The Verifier metadata is obtained using RFC7591 or - * through out-of-band mechanisms. + * This value represents the RFC6749 default behavior, i.e., the Client Identifier needs to be known to the + * Wallet in advance of the Authorization Request. The Verifier metadata is obtained using RFC7591 or through + * out-of-band mechanisms. */ @Serializable(with = Serializer::class) - object PRE_REGISTERED : ClientIdScheme(STRING_PRE_REGISTERED) + object PreRegistered : ClientIdScheme(STRING_PRE_REGISTERED) /** - * This value indicates that the Verifier's redirect URI is also the value of the Client Identifier. - * In this case, the Authorization Request MUST NOT be signed, the Verifier MAY omit the `redirect_uri` - * Authorization Request parameter, and all Verifier metadata parameters MUST be passed using the - * `client_metadata` or `client_metadata_uri` parameter. + * This value indicates that the Verifier's Redirect URI (or Response URI when Response Mode `direct_post` is + * used) is also the value of the Client Identifier. The Authorization Request MUST NOT be signed. + * The Verifier MAY omit the `redirect_uri` Authorization Request parameter (or `response_uri` when Response + * Mode `direct_post` is used). All Verifier metadata parameters MUST be passed using the `client_metadata` + * parameter. */ @Serializable(with = Serializer::class) - object REDIRECT_URI : ClientIdScheme(STRING_REDIRECT_URI) + object RedirectUri : ClientIdScheme(STRING_REDIRECT_URI) /** * When the Client Identifier Scheme is x509_san_dns, the Client Identifier MUST be a DNS name and match a @@ -152,24 +153,21 @@ object OpenIdConstants { * Client Identifier. */ @Serializable(with = Serializer::class) - object X509_SAN_DNS : ClientIdScheme(STRING_X509_SAN_DNS) + object X509SanDns : ClientIdScheme(STRING_X509_SAN_DNS) /** - * When the Client Identifier Scheme is x509_san_uri, the Client Identifier MUST be a URI name and match a - * `uniformResourceIdentifier` Subject Alternative Name (SAN) [RFC5280](https://www.rfc-editor.org/info/rfc5280) entry in the leaf - * certificate passed with the request. The request MUST be signed with the private key corresponding to the - * public key in the leaf X.509 certificate of the certificate chain added to the request in the `x5c` JOSE - * header [RFC7515](https://www.rfc-editor.org/info/rfc7515) of the signed request object. - * - * The Wallet MUST validate the signature and the trust chain of the X.509 certificate. - * All Verifier metadata other than the public key MUST be obtained from the `client_metadata` parameter. - * If the Wallet can establish trust in the Client Identifier authenticated through the certificate, e.g. - * because the Client Identifier is contained in a list of trusted Client Identifiers, it may allow the client - * to freely choose the `redirect_uri` value. If not, the FQDN of the `redirect_uri` value MUST match the - * Client Identifier. + * When the Client Identifier Scheme is `x509_san_uri`, the Client Identifier MUST be a URI and match a + * `uniformResourceIdentifier` Subject Alternative Name (SAN) RFC5280 entry in the leaf certificate passed with + * the request. The request MUST be signed with the private key corresponding to the public key in the leaf + * X.509 certificate of the certificate chain added to the request in the `x5c` JOSE header RFC7515 of the + * signed request object. The Wallet MUST validate the signature and the trust chain of the X.509 certificate. + * All Verifier metadata other than the public key MUST be obtained from the `client_metadata` parameter. If + * the Wallet can establish trust in the Client Identifier authenticated through the certificate, e.g. because + * the Client Identifier is contained in a list of trusted Client Identifiers, it may allow the client to + * freely choose the `redirect_uri` value. If not, the `redirect_uri` value MUST match the Client Identifier. */ @Serializable(with = Serializer::class) - object X509_SAN_URI : ClientIdScheme(STRING_X509_SAN_URI) + object X509SanUri : ClientIdScheme(STRING_X509_SAN_URI) /** * This value indicates that the Client Identifier is an Entity Identifier defined in OpenID Connect Federation. @@ -180,7 +178,7 @@ object OpenIdConstants { * Identifier scheme is used. */ @Serializable(with = Serializer::class) - object ENTITY_ID : ClientIdScheme(STRING_ENTITY_ID) + object EntityId : ClientIdScheme(STRING_ENTITY_ID) /** * This value indicates that the Client Identifier is a DID defined in DID-Core. The request MUST be signed @@ -192,43 +190,43 @@ object OpenIdConstants { * or the `client_metadata_uri` parameter. */ @Serializable(with = Serializer::class) - object DID : ClientIdScheme(STRING_DID) + object Did : ClientIdScheme(STRING_DID) /** - * This scheme allows the Verifier to authenticate using a JWT that is bound to a certain public key. When the - * scheme is `verifier_attestation`, the Client Identifier MUST equal the `sub` claim value in the Verifier - * attestation JWT. The request MUST be signed with the private key corresponding to the public key in the `cnf` - * claim in the Verifier attestation JWT. This serves as proof of possession of this key. The Verifier - * attestation JWT MUST be added to the `jwt` JOSE Header of the request object. The Wallet MUST validate the - * signature on the Verifier attestation JWT. The `iss` claim value of the Verifier Attestation JWT MUST - * identify a party the Wallet trusts for issuing Verifier Attestation JWTs. If the Wallet cannot establish - * trust, it MUST refuse the request. If the issuer of the Verifier Attestation JWT adds a `redirect_uris` claim - * to the attestation, the Wallet MUST ensure the `redirect_uri` request parameter value exactly matches one of - * the `redirect_uris` claim entries. All Verifier metadata other than the public key MUST be obtained from the - * `client_metadata` or the `client_metadata_uri parameter`. + * This Client Identifier Scheme allows the Verifier to authenticate using a JWT that is bound to a certain + * public key. When the Client Identifier Scheme is `verifier_attestation`, the Client Identifier MUST equal + * the `sub` claim value in the Verifier attestation JWT. The request MUST be signed with the private key + * corresponding to the public key in the `cnf` claim in the Verifier attestation JWT. This serves as proof of + * possession of this key. The Verifier attestation JWT MUST be added to the `jwt` JOSE Header of the request + * object. The Wallet MUST validate the signature on the Verifier attestation JWT. The `iss` claim value of the + * Verifier Attestation JWT MUST identify a party the Wallet trusts for issuing Verifier Attestation JWTs. + * If the Wallet cannot establish trust, it MUST refuse the request. If the issuer of the Verifier Attestation + * JWT adds a `redirect_uris` claim to the attestation, the Wallet MUST ensure the `redirect_uri` request + * parameter value exactly matches one of the `redirect_uris` claim entries. All Verifier metadata other than + * the public key MUST be obtained from the `client_metadata` parameter. */ @Serializable(with = Serializer::class) - object VERIFIER_ATTESTATION : ClientIdScheme(STRING_VERIFIER_ATTESTATION) + object VerifierAttestation : ClientIdScheme(STRING_VERIFIER_ATTESTATION) /** * Any not natively supported client id scheme, so it can still be parsed */ @Serializable(with = Serializer::class) - class OTHER(stringRepresentation: String) : ClientIdScheme(stringRepresentation) + class Other(stringRepresentation: String) : ClientIdScheme(stringRepresentation) object Serializer : KSerializer { override val descriptor = PrimitiveSerialDescriptor("ClientIdScheme", PrimitiveKind.STRING) override fun deserialize(decoder: Decoder): ClientIdScheme { return when (val string = decoder.decodeString()) { - STRING_PRE_REGISTERED -> PRE_REGISTERED - STRING_REDIRECT_URI -> REDIRECT_URI - STRING_X509_SAN_DNS -> X509_SAN_DNS - STRING_X509_SAN_URI -> X509_SAN_URI - STRING_ENTITY_ID -> ENTITY_ID - STRING_DID -> DID - STRING_VERIFIER_ATTESTATION -> VERIFIER_ATTESTATION - else -> OTHER(string) + STRING_PRE_REGISTERED -> PreRegistered + STRING_REDIRECT_URI -> RedirectUri + STRING_X509_SAN_DNS -> X509SanDns + STRING_X509_SAN_URI -> X509SanUri + STRING_ENTITY_ID -> EntityId + STRING_DID -> Did + STRING_VERIFIER_ATTESTATION -> VerifierAttestation + else -> Other(string) } } @@ -265,7 +263,7 @@ object OpenIdConstants { * with a redirect URI to the Wallet. */ @Serializable(with = Serializer::class) - object DIRECT_POST : ResponseMode(STRING_DIRECT_POST) + object DirectPost : ResponseMode(STRING_DIRECT_POST) /** * OID4VP: The Response Mode `direct_post.jwt` causes the Wallet to send the Authorization Response using an @@ -274,37 +272,37 @@ object OpenIdConstants { * using the `application/x-www-form-urlencoded` content type. */ @Serializable(with = Serializer::class) - object DIRECT_POST_JWT : ResponseMode(STRING_DIRECT_POST_JWT) + object DirectPostJwt : ResponseMode(STRING_DIRECT_POST_JWT) /** * OAuth 2.0: In this mode, Authorization Response parameters are encoded in the query string added to the * `redirect_uri` when redirecting back to the Client. */ @Serializable(with = Serializer::class) - object QUERY : ResponseMode(STRING_QUERY) + object Query : ResponseMode(STRING_QUERY) /** * OAuth 2.0: In this mode, Authorization Response parameters are encoded in the fragment added to the * `redirect_uri` when redirecting back to the Client. */ @Serializable(with = Serializer::class) - object FRAGMENT : ResponseMode(STRING_FRAGMENT) + object Fragment : ResponseMode(STRING_FRAGMENT) /** * Any not natively supported Client ID Scheme, so it can still be parsed */ @Serializable(with = Serializer::class) - class OTHER(stringRepresentation: String) : ResponseMode(stringRepresentation) + class Other(stringRepresentation: String) : ResponseMode(stringRepresentation) object Serializer : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ResponseMode", PrimitiveKind.STRING) override fun deserialize(decoder: Decoder): ResponseMode { return when (val string = decoder.decodeString()) { - STRING_DIRECT_POST -> DIRECT_POST - STRING_DIRECT_POST_JWT -> DIRECT_POST_JWT - STRING_QUERY -> QUERY - STRING_FRAGMENT -> FRAGMENT - else -> OTHER(string) + STRING_DIRECT_POST -> DirectPost + STRING_DIRECT_POST_JWT -> DirectPostJwt + STRING_QUERY -> Query + STRING_FRAGMENT -> Fragment + else -> Other(string) } } diff --git a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/RelyingPartyMetadata.kt b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/RelyingPartyMetadata.kt index bf4d6adc2..62967b1e5 100644 --- a/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/RelyingPartyMetadata.kt +++ b/openid-data-classes/src/commonMain/kotlin/at/asitplus/openid/RelyingPartyMetadata.kt @@ -121,7 +121,7 @@ data class RelyingPartyMetadata( * If omitted, the default value is `pre-registered`. */ @SerialName("client_id_scheme") - val clientIdScheme: OpenIdConstants.ClientIdScheme? = OpenIdConstants.ClientIdScheme.PRE_REGISTERED, + val clientIdScheme: OpenIdConstants.ClientIdScheme? = OpenIdConstants.ClientIdScheme.PreRegistered, ) { fun serialize() = jsonSerializer.encodeToString(this) diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopVerifier.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopVerifier.kt index 4fe74039c..468b82dbd 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopVerifier.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopVerifier.kt @@ -17,7 +17,6 @@ import at.asitplus.openid.OpenIdConstants.URN_TYPE_JWK_THUMBPRINT import at.asitplus.openid.OpenIdConstants.VP_TOKEN import at.asitplus.signum.indispensable.josef.* import at.asitplus.signum.indispensable.pki.CertificateChain -import at.asitplus.signum.indispensable.pki.leaf import at.asitplus.wallet.lib.agent.* import at.asitplus.wallet.lib.data.* import at.asitplus.wallet.lib.data.ConstantIndex.supportsSdJwt @@ -48,51 +47,92 @@ import kotlin.time.toDuration */ class OidcSiopVerifier private constructor( private val verifier: Verifier, - private val relyingPartyUrl: String?, private val jwsService: JwsService, private val verifierJwsService: VerifierJwsService, timeLeewaySeconds: Long = 300L, private val clock: Clock = Clock.System, private val nonceService: NonceService = DefaultNonceService(), - private val clientIdScheme: ClientIdScheme = ClientIdScheme.RedirectUri, + private val clientIdScheme: ClientIdScheme, private val stateToNonceStore: MapStore = DefaultMapStore(), private val stateToResponseTypeStore: MapStore = DefaultMapStore(), ) { private val timeLeeway = timeLeewaySeconds.toDuration(DurationUnit.SECONDS) - sealed class ClientIdScheme(val clientIdScheme: OpenIdConstants.ClientIdScheme) { + sealed class ClientIdScheme( + val scheme: OpenIdConstants.ClientIdScheme, + open val clientId: String, + ) { /** - * Verifier Attestation JWT to include (in header `jwt`) when creating request objects as JWS, - * to allow the Wallet to verify the authenticity of this Verifier. - * OID4VP client id scheme `verifier attestation`, - * see [at.asitplus.openid.OpenIdConstants.ClientIdScheme.VERIFIER_ATTESTATION]. + * This Client Identifier Scheme allows the Verifier to authenticate using a JWT that is bound to a certain + * public key. When the Client Identifier Scheme is `verifier_attestation`, the Client Identifier MUST equal + * the `sub` claim value in the Verifier attestation JWT. The request MUST be signed with the private key + * corresponding to the public key in the `cnf` claim in the Verifier attestation JWT. This serves as proof of + * possession of this key. The Verifier attestation JWT MUST be added to the `jwt` JOSE Header of the request + * object. The Wallet MUST validate the signature on the Verifier attestation JWT. The `iss` claim value of the + * Verifier Attestation JWT MUST identify a party the Wallet trusts for issuing Verifier Attestation JWTs. + * If the Wallet cannot establish trust, it MUST refuse the request. If the issuer of the Verifier Attestation + * JWT adds a `redirect_uris` claim to the attestation, the Wallet MUST ensure the `redirect_uri` request + * parameter value exactly matches one of the `redirect_uris` claim entries. All Verifier metadata other than + * the public key MUST be obtained from the `client_metadata` parameter. */ - data class VerifierAttestation(val attestationJwt: JwsSigned) : ClientIdScheme(VERIFIER_ATTESTATION) + data class VerifierAttestation( + val attestationJwt: JwsSigned, + override val clientId: String + ) : ClientIdScheme( + VerifierAttestation, + JsonWebToken.deserialize(attestationJwt.payload.decodeToString()).getOrThrow().subject!! + ) + + /** + * When the Client Identifier Scheme is x509_san_dns, the Client Identifier MUST be a DNS name and match a + * `dNSName` Subject Alternative Name (SAN) [RFC5280](https://www.rfc-editor.org/info/rfc5280) entry in the leaf + * certificate passed with the request. The request MUST be signed with the private key corresponding to the + * public key in the leaf X.509 certificate of the certificate chain added to the request in the `x5c` JOSE + * header [RFC7515](https://www.rfc-editor.org/info/rfc7515) of the signed request object. + * + * The Wallet MUST validate the signature and the trust chain of the X.509 certificate. + * All Verifier metadata other than the public key MUST be obtained from the `client_metadata` parameter. + * If the Wallet can establish trust in the Client Identifier authenticated through the certificate, e.g. + * because the Client Identifier is contained in a list of trusted Client Identifiers, it may allow the client + * to freely choose the `redirect_uri` value. If not, the FQDN of the `redirect_uri` value MUST match the + * Client Identifier. + */ + data class CertificateSanDns( + val chain: CertificateChain, + override val clientId: String + ) : ClientIdScheme(X509SanDns, clientId) /** - * Certificate chain to include in JWS headers and to extract `client_id` from (in SAN extension), from OID4VP - * client id scheme `x509_san_dns`, - * see [at.asitplus.openid.OpenIdConstants.ClientIdScheme.X509_SAN_DNS]. + * This value indicates that the Verifier's Redirect URI (or Response URI when Response Mode `direct_post` is + * used) is also the value of the Client Identifier. The Authorization Request MUST NOT be signed. + * The Verifier MAY omit the `redirect_uri` Authorization Request parameter (or `response_uri` when Response + * Mode `direct_post` is used). All Verifier metadata parameters MUST be passed using the `client_metadata` + * parameter. */ - data class CertificateSanDns(val chain: CertificateChain) : ClientIdScheme(X509_SAN_DNS) + data class RedirectUri( + override val clientId: String + ) : ClientIdScheme(RedirectUri, clientId) /** - * Simple: `redirect_uri` has to match `client_id` + * This value represents the RFC6749 default behavior, i.e., the Client Identifier needs to be known to the + * Wallet in advance of the Authorization Request. The Verifier metadata is obtained using RFC7591 or through + * out-of-band mechanisms. */ - data object RedirectUri : ClientIdScheme(REDIRECT_URI) + data class PreRegistered( + override val clientId: String, + ) : ClientIdScheme(PreRegistered, clientId) } constructor( keyMaterial: KeyMaterial = EphemeralKeyWithoutCert(), verifier: Verifier = VerifierAgent(keyMaterial), - relyingPartyUrl: String? = null, verifierJwsService: VerifierJwsService = DefaultVerifierJwsService(DefaultVerifierCryptoService()), jwsService: JwsService = DefaultJwsService(DefaultCryptoService(keyMaterial)), timeLeewaySeconds: Long = 300L, clock: Clock = Clock.System, nonceService: NonceService = DefaultNonceService(), - clientIdScheme: ClientIdScheme = ClientIdScheme.RedirectUri, + clientIdScheme: ClientIdScheme, /** * Used to store the nonce, associated to the state, to first send [AuthenticationRequestParameters.nonce], * and then verify the challenge in the submitted verifiable presentation in @@ -102,7 +142,6 @@ class OidcSiopVerifier private constructor( stateToResponseTypeStore: MapStore = DefaultMapStore(), ) : this( verifier = verifier, - relyingPartyUrl = relyingPartyUrl, jwsService = jwsService, verifierJwsService = verifierJwsService, timeLeewaySeconds = timeLeewaySeconds, @@ -118,7 +157,7 @@ class OidcSiopVerifier private constructor( val metadata by lazy { RelyingPartyMetadata( - redirectUris = relyingPartyUrl?.let { listOf(it) }, + redirectUris = listOfNotNull((clientIdScheme as? ClientIdScheme.RedirectUri)?.clientId), jsonWebKeySet = JsonWebKeySet(listOf(verifier.keyMaterial.publicKey.toJsonWebKey())), subjectSyntaxTypesSupported = setOf(URN_TYPE_JWK_THUMBPRINT, PREFIX_DID_KEY, BINDING_METHOD_JWK), vpFormats = FormatHolder( @@ -143,7 +182,8 @@ class OidcSiopVerifier private constructor( /** * Create a URL to be displayed as a static QR code for Wallet initiation. - * URL is the [walletUrl], with query parameters appended for [relyingPartyUrl], [clientMetadataUrl], [requestUrl]. + * URL is the [walletUrl], with query parameters appended for [clientMetadataUrl], [requestUrl] and + * [clientIdScheme.clientId]. */ fun createQrCodeUrl( walletUrl: String, @@ -152,7 +192,7 @@ class OidcSiopVerifier private constructor( ): String { val urlBuilder = URLBuilder(walletUrl) AuthenticationRequestParameters( - clientId = this.clientId, + clientId = clientIdScheme.clientId, clientMetadataUri = clientMetadataUrl, requestUri = requestUrl, ).encodeToParameters() @@ -177,14 +217,14 @@ class OidcSiopVerifier private constructor( val credentials: Set, /** * Response mode to request, see [OpenIdConstants.ResponseMode], - * by default [OpenIdConstants.ResponseMode.FRAGMENT]. + * by default [OpenIdConstants.ResponseMode.Fragment]. * Setting this to any other value may require setting [responseUrl] too. */ - val responseMode: OpenIdConstants.ResponseMode = OpenIdConstants.ResponseMode.FRAGMENT, + val responseMode: OpenIdConstants.ResponseMode = OpenIdConstants.ResponseMode.Fragment, /** * Response URL to set in the [AuthenticationRequestParameters.responseUrl], - * required if [responseMode] is set to [OpenIdConstants.ResponseMode.DIRECT_POST] or - * [OpenIdConstants.ResponseMode.DIRECT_POST_JWT]. + * required if [responseMode] is set to [OpenIdConstants.ResponseMode.DirectPost] or + * [OpenIdConstants.ResponseMode.DirectPostJwt]. */ val responseUrl: String? = null, /** @@ -248,7 +288,7 @@ class OidcSiopVerifier private constructor( val jar = createAuthnRequestAsSignedRequestObject(requestOptions).getOrThrow() val urlBuilder = URLBuilder(walletUrl) AuthenticationRequestParameters( - clientId = relyingPartyUrl, + clientId = clientIdScheme.clientId, request = jar.serialize(), ).encodeToParameters() .forEach { urlBuilder.parameters.append(it.key, it.value) } @@ -271,7 +311,7 @@ class OidcSiopVerifier private constructor( val jar = createAuthnRequestAsSignedRequestObject(requestOptions).getOrThrow() val urlBuilder = URLBuilder(walletUrl) AuthenticationRequestParameters( - clientId = relyingPartyUrl, + clientId = clientIdScheme.clientId, requestUri = requestUrl, ).encodeToParameters() .forEach { urlBuilder.parameters.append(it.key, it.value) } @@ -285,7 +325,7 @@ class OidcSiopVerifier private constructor( * `jar` being the result of this function: * ``` * val urlToSendToWallet = io.ktor.http.URLBuilder(walletUrl).apply { - * parameters.append("client_id", relyingPartyUrl) + * parameters.append("client_id", clientId) * parameters.append("request_uri", requestUrl) * }.buildString() * // on an GET to requestUrl, return `jar.serialize()` @@ -296,7 +336,7 @@ class OidcSiopVerifier private constructor( ): KmmResult = catching { val requestObject = createAuthnRequest(requestOptions) val requestObjectSerialized = jsonSerializer.encodeToString( - requestObject.copy(audience = relyingPartyUrl, issuer = relyingPartyUrl) + requestObject.copy(audience = "https://self-issued.me/v2", issuer = "https://self-issued.me/v2") ) val attestationJwt = (clientIdScheme as? ClientIdScheme.VerifierAttestation)?.attestationJwt?.serialize() val certificateChain = (clientIdScheme as? ClientIdScheme.CertificateSanDns)?.chain @@ -322,10 +362,10 @@ class OidcSiopVerifier private constructor( ) = AuthenticationRequestParameters( responseType = requestOptions.responseType .also { stateToResponseTypeStore.put(requestOptions.state, it) }, - clientId = clientId, - redirectUrl = requestOptions.buildRedirectUrl(), + clientId = clientIdScheme.clientId, + redirectUrl = if (!requestOptions.isAnyDirectPost) clientIdScheme.clientId else null, responseUrl = requestOptions.responseUrl, - clientIdScheme = clientIdScheme.clientIdScheme, + clientIdScheme = clientIdScheme.scheme, scope = requestOptions.buildScope(), nonce = nonceService.provideNonce() .also { stateToNonceStore.put(requestOptions.state, it) }, @@ -353,18 +393,9 @@ class OidcSiopVerifier private constructor( + credentials.mapNotNull { it.credentialScheme.isoNamespace } ).joinToString(" ") - private val clientId: String? by lazy { - clientIdFromCertificateChain ?: relyingPartyUrl - } - - private val clientIdFromCertificateChain: String? by lazy { - (clientIdScheme as? ClientIdScheme.CertificateSanDns)?.chain - ?.let { it.leaf.tbsCertificate.subjectAlternativeNames?.dnsNames?.firstOrNull() } - } - - private fun RequestOptions.buildRedirectUrl() = if ((responseMode == OpenIdConstants.ResponseMode.DIRECT_POST) - || (responseMode == OpenIdConstants.ResponseMode.DIRECT_POST_JWT) - ) null else relyingPartyUrl + private val RequestOptions.isAnyDirectPost + get() = (responseMode == OpenIdConstants.ResponseMode.DirectPost) || + (responseMode == OpenIdConstants.ResponseMode.DirectPostJwt) //TODO extend for InputDescriptor interface in case QES private fun RequestOptionsCredential.toInputDescriptor() = DifInputDescriptor( @@ -599,8 +630,7 @@ class OidcSiopVerifier private constructor( if (idToken.issuer != idToken.subject) throw IllegalArgumentException("idToken.iss") .also { Napier.d("Wrong issuer: ${idToken.issuer}, expected: ${idToken.subject}") } - val validAudiences = listOfNotNull(relyingPartyUrl, clientIdFromCertificateChain) - if (idToken.audience !in validAudiences) + if (idToken.audience != clientIdScheme.clientId) throw IllegalArgumentException("idToken.aud") .also { Napier.d("audience not valid: ${idToken.audience}") } if (idToken.expiration < (clock.now() - timeLeeway)) diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWallet.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWallet.kt index 584bef167..ed3d3a724 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWallet.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWallet.kt @@ -55,7 +55,7 @@ class OidcSiopWallet( private val remoteResourceRetriever: RemoteResourceRetrieverFunction, /** * Need to verify the request object serialized as a JWS, - * which may be signed with a pre-registered key (see [OpenIdConstants.ClientIdScheme.PRE_REGISTERED]). + * which may be signed with a pre-registered key (see [OpenIdConstants.ClientIdScheme.PreRegistered]). */ private val requestObjectJwsVerifier: RequestObjectJwsVerifier, /** @@ -79,7 +79,7 @@ class OidcSiopWallet( remoteResourceRetriever: RemoteResourceRetrieverFunction = { null }, /** * Need to verify the request object serialized as a JWS, - * which may be signed with a pre-registered key (see [OpenIdConstants.ClientIdScheme.PRE_REGISTERED]). + * which may be signed with a pre-registered key (see [OpenIdConstants.ClientIdScheme.PreRegistered]). */ requestObjectJwsVerifier: RequestObjectJwsVerifier = RequestObjectJwsVerifier { _, _ -> true }, /** diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthenticationRequestParser.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthenticationRequestParser.kt index 1715582c9..31b5a2191 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthenticationRequestParser.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthenticationRequestParser.kt @@ -28,7 +28,7 @@ internal class AuthenticationRequestParser( private val remoteResourceRetriever: RemoteResourceRetrieverFunction, /** * Need to verify the request object serialized as a JWS, - * which may be signed with a pre-registered key (see [OpenIdConstants.ClientIdScheme.PRE_REGISTERED]). + * which may be signed with a pre-registered key (see [OpenIdConstants.ClientIdScheme.PreRegistered]). */ private val requestObjectJwsVerifier: RequestObjectJwsVerifier, ) { diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthenticationResponseFactory.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthenticationResponseFactory.kt index ab4df1a65..4323fe539 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthenticationResponseFactory.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthenticationResponseFactory.kt @@ -26,11 +26,11 @@ internal class AuthenticationResponseFactory( request: AuthenticationRequestParametersFrom, response: AuthenticationResponse, ) = when (request.parameters.responseMode) { - DIRECT_POST -> authnResponseDirectPost(request, response) - DIRECT_POST_JWT -> authnResponseDirectPostJwt(request, response) - QUERY -> authnResponseQuery(request, response) - FRAGMENT, null -> authnResponseFragment(request, response) - is OTHER -> TODO() + DirectPost -> authnResponseDirectPost(request, response) + DirectPostJwt -> authnResponseDirectPostJwt(request, response) + Query -> authnResponseQuery(request, response) + Fragment, null -> authnResponseFragment(request, response) + is Other -> TODO() } /** diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthorizationRequestValidator.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthorizationRequestValidator.kt index 7bbef220e..4e312bb26 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthorizationRequestValidator.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/helper/AuthorizationRequestValidator.kt @@ -1,17 +1,17 @@ package at.asitplus.wallet.lib.oidc.helper -import at.asitplus.signum.indispensable.pki.leaf import at.asitplus.openid.AuthenticationRequestParameters -import at.asitplus.wallet.lib.oidc.AuthenticationRequestParametersFrom import at.asitplus.openid.OpenIdConstants import at.asitplus.openid.OpenIdConstants.Errors import at.asitplus.openid.OpenIdConstants.ID_TOKEN -import at.asitplus.openid.OpenIdConstants.ResponseMode.DIRECT_POST -import at.asitplus.openid.OpenIdConstants.ResponseMode.DIRECT_POST_JWT +import at.asitplus.openid.OpenIdConstants.ResponseMode.DirectPost +import at.asitplus.openid.OpenIdConstants.ResponseMode.DirectPostJwt import at.asitplus.openid.OpenIdConstants.VP_TOKEN +import at.asitplus.signum.indispensable.pki.leaf +import at.asitplus.wallet.lib.oidc.AuthenticationRequestParametersFrom import at.asitplus.wallet.lib.oidvci.OAuth2Exception import io.github.aakira.napier.Napier -import io.ktor.http.Url +import io.ktor.http.* internal class AuthorizationRequestValidator { @Throws(OAuth2Exception::class) @@ -28,7 +28,7 @@ internal class AuthorizationRequestValidator { val clientIdScheme = request.parameters.clientIdScheme - if (clientIdScheme == OpenIdConstants.ClientIdScheme.REDIRECT_URI) { + if (clientIdScheme == OpenIdConstants.ClientIdScheme.RedirectUri) { request.parameters.verifyClientMetadata() } if (request.parameters.responseMode.isAnyDirectPost()) { @@ -43,7 +43,6 @@ internal class AuthorizationRequestValidator { } } - @Throws(OAuth2Exception::class) private fun AuthenticationRequestParameters.verifyRedirectUrl() { if (redirectUrl != null) { @@ -55,7 +54,7 @@ internal class AuthorizationRequestValidator { } private fun OpenIdConstants.ClientIdScheme?.isAnyX509() = - (this == OpenIdConstants.ClientIdScheme.X509_SAN_DNS) || (this == OpenIdConstants.ClientIdScheme.X509_SAN_URI) + (this == OpenIdConstants.ClientIdScheme.X509SanDns) || (this == OpenIdConstants.ClientIdScheme.X509SanUri) @Throws(OAuth2Exception::class) private fun AuthenticationRequestParameters.verifyClientMetadata() { @@ -82,7 +81,7 @@ internal class AuthorizationRequestValidator { Napier.w("$prefix, but no extensions were found in the leaf certificate") throw OAuth2Exception(Errors.INVALID_REQUEST) } - if (clientIdScheme == OpenIdConstants.ClientIdScheme.X509_SAN_DNS) { + if (clientIdScheme == OpenIdConstants.ClientIdScheme.X509SanDns) { val dnsNames = leaf.tbsCertificate.subjectAlternativeNames?.dnsNames ?: run { Napier.w("$prefix, but no dnsNames were found in the leaf certificate") throw OAuth2Exception(Errors.INVALID_REQUEST) @@ -120,7 +119,7 @@ internal class AuthorizationRequestValidator { } private fun OpenIdConstants.ResponseMode?.isAnyDirectPost() = - (this == DIRECT_POST) || (this == DIRECT_POST_JWT) + (this == DirectPost) || (this == DirectPostJwt) @Throws(OAuth2Exception::class) private fun AuthenticationRequestParameters.verifyResponseModeDirectPost() { diff --git a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/AuthenticationRequestParameterFromSerializerTest.kt b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/AuthenticationRequestParameterFromSerializerTest.kt index 7f877af55..cb0ef24c5 100644 --- a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/AuthenticationRequestParameterFromSerializerTest.kt +++ b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/AuthenticationRequestParameterFromSerializerTest.kt @@ -14,7 +14,7 @@ import io.ktor.http.* class AuthenticationRequestParameterFromSerializerTest : FreeSpec({ - val relyingPartyUrl = "https://example.com/rp/${uuid4()}" + val clientId = "https://example.com/rp/${uuid4()}" val walletUrl = "https://example.com/wallet/${uuid4()}" val holderKeyMaterial = EphemeralKeyWithoutCert() @@ -25,7 +25,7 @@ class AuthenticationRequestParameterFromSerializerTest : FreeSpec({ val verifierSiop = OidcSiopVerifier( verifier = VerifierAgent(EphemeralKeyWithoutCert()), - relyingPartyUrl = relyingPartyUrl, + clientIdScheme = OidcSiopVerifier.ClientIdScheme.RedirectUri(clientId), ) val representations = listOf( @@ -73,7 +73,7 @@ class AuthenticationRequestParameterFromSerializerTest : FreeSpec({ ).getOrThrow() val interim1: AuthenticationRequestParameters = Url(authnRequestUrl).encodedQuery.decodeFromUrlQuery() - interim1.clientId shouldBe relyingPartyUrl + interim1.clientId shouldBe clientId val interim2 = interim1.request ?: throw Exception("Authn request is null") val params = oidcSiopWallet.parseAuthenticationRequestParameters(interim2).getOrThrow() diff --git a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/EqualityTests.kt b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/EqualityTests.kt index d841edfc7..8dc0a0247 100644 --- a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/EqualityTests.kt +++ b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/EqualityTests.kt @@ -7,7 +7,6 @@ import io.kotest.matchers.booleans.shouldBeTrue import kotlinx.serialization.SerialName import kotlin.random.Random -@Suppress("unused") class EqualityTests : FreeSpec({ lateinit var jwk1: JsonWebKey diff --git a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopCombinedProtocolTest.kt b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopCombinedProtocolTest.kt index 09798ff6f..27f47a74a 100644 --- a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopCombinedProtocolTest.kt +++ b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopCombinedProtocolTest.kt @@ -20,7 +20,7 @@ import io.kotest.matchers.types.shouldBeInstanceOf class OidcSiopCombinedProtocolTest : FreeSpec({ - lateinit var relyingPartyUrl: String + lateinit var clientId: String lateinit var holderKeyMaterial: KeyMaterial lateinit var verifierKeyMaterial: KeyMaterial @@ -33,7 +33,7 @@ class OidcSiopCombinedProtocolTest : FreeSpec({ beforeEach { holderKeyMaterial = EphemeralKeyWithoutCert() verifierKeyMaterial = EphemeralKeyWithoutCert() - relyingPartyUrl = "https://example.com/rp/${uuid4()}" + clientId = "https://example.com/rp/${uuid4()}" holderAgent = HolderAgent(holderKeyMaterial) holderSiop = OidcSiopWallet( @@ -42,7 +42,7 @@ class OidcSiopCombinedProtocolTest : FreeSpec({ ) verifierSiop = OidcSiopVerifier( keyMaterial = verifierKeyMaterial, - relyingPartyUrl = relyingPartyUrl, + clientIdScheme = OidcSiopVerifier.ClientIdScheme.RedirectUri(clientId), ) } diff --git a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopCombinedProtocolTwoStepTest.kt b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopCombinedProtocolTwoStepTest.kt index 17d9e850f..e136ed253 100644 --- a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopCombinedProtocolTwoStepTest.kt +++ b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopCombinedProtocolTwoStepTest.kt @@ -12,7 +12,7 @@ import io.kotest.matchers.types.shouldBeInstanceOf class OidcSiopCombinedProtocolTwoStepTest : FreeSpec({ - lateinit var relyingPartyUrl: String + lateinit var clientId: String lateinit var holderKeyMaterial: KeyMaterial lateinit var verifierKeyMaterial: KeyMaterial @@ -25,7 +25,7 @@ class OidcSiopCombinedProtocolTwoStepTest : FreeSpec({ beforeEach { holderKeyMaterial = EphemeralKeyWithoutCert() verifierKeyMaterial = EphemeralKeyWithoutCert() - relyingPartyUrl = "https://example.com/rp/${uuid4()}" + clientId = "https://example.com/rp/${uuid4()}" holderAgent = HolderAgent(holderKeyMaterial) holderSiop = OidcSiopWallet( @@ -34,7 +34,7 @@ class OidcSiopCombinedProtocolTwoStepTest : FreeSpec({ ) verifierSiop = OidcSiopVerifier( keyMaterial = verifierKeyMaterial, - relyingPartyUrl = relyingPartyUrl, + clientIdScheme = OidcSiopVerifier.ClientIdScheme.RedirectUri(clientId), ) } diff --git a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopInteropTest.kt b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopInteropTest.kt index cfac1c66e..bad1d5ae2 100644 --- a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopInteropTest.kt +++ b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopInteropTest.kt @@ -19,7 +19,6 @@ import at.asitplus.wallet.lib.data.ConstantIndex.AtomicAttribute2023.CLAIM_FAMIL import at.asitplus.wallet.lib.data.ConstantIndex.AtomicAttribute2023.CLAIM_GIVEN_NAME import at.asitplus.wallet.lib.jws.DefaultJwsService import at.asitplus.wallet.lib.oidvci.decode -import at.asitplus.wallet.lib.oidvci.decodeFromUrlQuery import com.benasher44.uuid.uuid4 import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.collections.shouldBeSingleton @@ -27,8 +26,6 @@ import io.kotest.matchers.collections.shouldHaveSingleElement import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe import io.kotest.matchers.types.shouldBeInstanceOf -import io.ktor.http.* -import io.ktor.util.* import kotlinx.datetime.Instant /** @@ -248,11 +245,11 @@ class OidcSiopInteropTest : FreeSpec({ parsed.shouldNotBeNull() parsed.responseUrl shouldBe "https://verifier-backend.eudiw.dev/wallet/direct_post" - parsed.clientIdScheme shouldBe OpenIdConstants.ClientIdScheme.X509_SAN_DNS + parsed.clientIdScheme shouldBe OpenIdConstants.ClientIdScheme.X509SanDns parsed.responseType shouldBe "vp_token" parsed.nonce shouldBe "nonce" parsed.clientId shouldBe "verifier-backend.eudiw.dev" - parsed.responseMode shouldBe OpenIdConstants.ResponseMode.DIRECT_POST_JWT + parsed.responseMode shouldBe OpenIdConstants.ResponseMode.DirectPostJwt parsed.audience shouldBe "https://self-issued.me/v2" parsed.scope shouldBe "" val pd = parsed.presentationDefinition @@ -283,7 +280,8 @@ class OidcSiopInteropTest : FreeSpec({ } "Request in request URI" { - val input = "mdoc-openid4vp://?request_uri=https%3A%2F%2Fexample.com%2Fd15b5b6f-7821-4031-9a18-ebe491b720a6" + val input = "mdoc-openid4vp://?client_id=https://example.com/ef391e30-bacc-4441-af5d-7f42fb682e02" + + "&request_uri=https%3A%2F%2Fexample.com%2Fd15b5b6f-7821-4031-9a18-ebe491b720a6" val jws = DefaultJwsService(DefaultCryptoService(EphemeralKeyWithoutCert())).createSignedJwsAddingParams( payload = AuthenticationRequestParameters( nonce = "RjEQKQeG8OUaKT4ij84E8mCvry6pVSgDyqRBMW5eBTPItP4DIfbKaT6M6v6q2Dvv8fN7Im7Ifa6GI2j6dHsJaQ==", @@ -307,13 +305,6 @@ class OidcSiopInteropTest : FreeSpec({ parsed.parameters.clientId shouldBe parsed.parameters.responseUrl } - "empty client_id" { - val input = "mdoc-openid4vp://?response_type=vp_token&client_id=&response_mode=direct_post.jwt" - - Url(input).parameters.flattenEntries().toMap() - .decodeFromUrlQuery().shouldNotBeNull() - } - "process with cross-device flow with request_uri and x509_san_dns" { val extensions = listOf(X509CertificateExtension( KnownOIDs.subjectAltName_2_5_29_17, @@ -329,8 +320,10 @@ class OidcSiopInteropTest : FreeSpec({ verifierKeyMaterial = EphemeralKeyWithSelfSignedCert(extensions = extensions) verifierSiop = OidcSiopVerifier( keyMaterial = verifierKeyMaterial, - relyingPartyUrl = "https://example.com/rp", - clientIdScheme = OidcSiopVerifier.ClientIdScheme.CertificateSanDns(listOf(verifierKeyMaterial.getCertificate()!!)), + clientIdScheme = OidcSiopVerifier.ClientIdScheme.CertificateSanDns( + listOf(verifierKeyMaterial.getCertificate()!!), + "example.com" + ), ) val nonce = uuid4().toString() val requestUrl = "https://example.com/request/$nonce" @@ -338,7 +331,7 @@ class OidcSiopInteropTest : FreeSpec({ walletUrl = "https://wallet.a-sit.at/mobile", requestUrl = requestUrl, requestOptions = OidcSiopVerifier.RequestOptions( - responseMode = OpenIdConstants.ResponseMode.DIRECT_POST, + responseMode = OpenIdConstants.ResponseMode.DirectPost, responseUrl = "https://example.com/response", credentials = setOf( OidcSiopVerifier.RequestOptionsCredential( diff --git a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopIsoProtocolTest.kt b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopIsoProtocolTest.kt index 3aceb9c12..3eb0e28e9 100644 --- a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopIsoProtocolTest.kt +++ b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopIsoProtocolTest.kt @@ -16,10 +16,9 @@ import io.kotest.matchers.collections.shouldHaveSingleElement import io.kotest.matchers.collections.shouldNotBeEmpty import io.kotest.matchers.types.shouldBeInstanceOf -@Suppress("unused") class OidcSiopIsoProtocolTest : FreeSpec({ - lateinit var relyingPartyUrl: String + lateinit var clientId: String lateinit var walletUrl: String lateinit var holderKeyMaterial: KeyMaterial @@ -33,7 +32,7 @@ class OidcSiopIsoProtocolTest : FreeSpec({ beforeEach { holderKeyMaterial = EphemeralKeyWithoutCert() verifierKeyMaterial = EphemeralKeyWithoutCert() - relyingPartyUrl = "https://example.com/rp/${uuid4()}" + clientId = "https://example.com/rp/${uuid4()}" walletUrl = "https://example.com/wallet/${uuid4()}" holderAgent = HolderAgent(holderKeyMaterial) @@ -67,7 +66,7 @@ class OidcSiopIsoProtocolTest : FreeSpec({ "test with Fragment for mDL" { verifierSiop = OidcSiopVerifier( keyMaterial = verifierKeyMaterial, - relyingPartyUrl = relyingPartyUrl, + clientIdScheme = OidcSiopVerifier.ClientIdScheme.RedirectUri(clientId), ) val document = runProcess( verifierSiop, @@ -91,7 +90,7 @@ class OidcSiopIsoProtocolTest : FreeSpec({ "test with Fragment for custom attributes" { verifierSiop = OidcSiopVerifier( keyMaterial = verifierKeyMaterial, - relyingPartyUrl = relyingPartyUrl, + clientIdScheme = OidcSiopVerifier.ClientIdScheme.RedirectUri(clientId), ) val document = runProcess( verifierSiop, @@ -116,7 +115,7 @@ class OidcSiopIsoProtocolTest : FreeSpec({ val requestedClaim = MobileDrivingLicenceDataElements.FAMILY_NAME verifierSiop = OidcSiopVerifier( keyMaterial = verifierKeyMaterial, - relyingPartyUrl = relyingPartyUrl, + clientIdScheme = OidcSiopVerifier.ClientIdScheme.RedirectUri(clientId), ) val document = runProcess( verifierSiop, @@ -143,7 +142,7 @@ class OidcSiopIsoProtocolTest : FreeSpec({ val requestedClaim = MobileDrivingLicenceDataElements.FAMILY_NAME verifierSiop = OidcSiopVerifier( keyMaterial = verifierKeyMaterial, - relyingPartyUrl = relyingPartyUrl, + clientIdScheme = OidcSiopVerifier.ClientIdScheme.RedirectUri(clientId), ) val requestOptions = OidcSiopVerifier.RequestOptions( credentials = setOf( @@ -151,7 +150,7 @@ class OidcSiopIsoProtocolTest : FreeSpec({ MobileDrivingLicenceScheme, ConstantIndex.CredentialRepresentation.ISO_MDOC, listOf(requestedClaim) ) ), - responseMode = OpenIdConstants.ResponseMode.DIRECT_POST_JWT, + responseMode = OpenIdConstants.ResponseMode.DirectPostJwt, responseUrl = "https://example.com/response", encryption = true ) @@ -177,7 +176,7 @@ class OidcSiopIsoProtocolTest : FreeSpec({ "Selective Disclosure with mDL JSON Path syntax" { verifierSiop = OidcSiopVerifier( keyMaterial = verifierKeyMaterial, - relyingPartyUrl = relyingPartyUrl, + clientIdScheme = OidcSiopVerifier.ClientIdScheme.RedirectUri(clientId), ) val document = runProcess( verifierSiop, diff --git a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopProtocolTest.kt b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopProtocolTest.kt index 7b03796d2..6f84f10de 100644 --- a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopProtocolTest.kt +++ b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopProtocolTest.kt @@ -29,10 +29,9 @@ import io.ktor.http.* import kotlinx.datetime.Clock import kotlin.time.Duration.Companion.seconds -@Suppress("unused") class OidcSiopProtocolTest : FreeSpec({ - lateinit var relyingPartyUrl: String + lateinit var clientId: String lateinit var walletUrl: String lateinit var holderKeyMaterial: KeyMaterial @@ -46,8 +45,7 @@ class OidcSiopProtocolTest : FreeSpec({ beforeEach { holderKeyMaterial = EphemeralKeyWithoutCert() verifierKeyMaterial = EphemeralKeyWithoutCert() - val rpUUID = uuid4() - relyingPartyUrl = "https://example.com/rp/$rpUUID" + clientId = "https://example.com/rp/${uuid4()}" walletUrl = "https://example.com/wallet/${uuid4()}" holderAgent = HolderAgent(holderKeyMaterial) @@ -66,7 +64,7 @@ class OidcSiopProtocolTest : FreeSpec({ ) verifierSiop = OidcSiopVerifier( keyMaterial = verifierKeyMaterial, - relyingPartyUrl = relyingPartyUrl, + clientIdScheme = OidcSiopVerifier.ClientIdScheme.RedirectUri(clientId), ) } @@ -78,7 +76,7 @@ class OidcSiopProtocolTest : FreeSpec({ authnResponse.url.shouldNotContain("?") authnResponse.url.shouldContain("#") - authnResponse.url.shouldStartWith(relyingPartyUrl) + authnResponse.url.shouldStartWith(clientId) val result = verifierSiop.validateAuthnResponse(authnResponse.url) result.shouldBeInstanceOf() @@ -90,7 +88,7 @@ class OidcSiopProtocolTest : FreeSpec({ "wrong client nonce in id_token should lead to error" { verifierSiop = OidcSiopVerifier( keyMaterial = verifierKeyMaterial, - relyingPartyUrl = relyingPartyUrl, + clientIdScheme = OidcSiopVerifier.ClientIdScheme.RedirectUri(clientId), nonceService = object : NonceService { override suspend fun provideNonce() = uuid4().toString() override suspend fun verifyNonce(it: String) = false @@ -114,7 +112,7 @@ class OidcSiopProtocolTest : FreeSpec({ "wrong client nonce in vp_token should lead to error" { verifierSiop = OidcSiopVerifier( keyMaterial = verifierKeyMaterial, - relyingPartyUrl = relyingPartyUrl, + clientIdScheme = OidcSiopVerifier.ClientIdScheme.RedirectUri(clientId), stateToNonceStore = object : MapStore { override suspend fun put(key: String, value: String) {} override suspend fun get(key: String): String? = null @@ -147,7 +145,7 @@ class OidcSiopProtocolTest : FreeSpec({ .getOrThrow() val authnRequest: AuthenticationRequestParameters = Url(authnRequestUrl).encodedQuery.decodeFromUrlQuery() - authnRequest.clientId shouldBe relyingPartyUrl + authnRequest.clientId shouldBe clientId val jar = authnRequest.request jar.shouldNotBeNull() DefaultVerifierJwsService().verifyJwsObject(JwsSigned.deserialize(jar).getOrThrow()).shouldBeTrue() @@ -164,14 +162,14 @@ class OidcSiopProtocolTest : FreeSpec({ walletUrl = walletUrl, requestOptions = RequestOptions( credentials = setOf(OidcSiopVerifier.RequestOptionsCredential(ConstantIndex.AtomicAttribute2023)), - responseMode = OpenIdConstants.ResponseMode.DIRECT_POST, - responseUrl = relyingPartyUrl, + responseMode = OpenIdConstants.ResponseMode.DirectPost, + responseUrl = clientId, ) ) val authnResponse = holderSiop.createAuthnResponse(authnRequest).getOrThrow() authnResponse.shouldBeInstanceOf() - authnResponse.url.shouldBe(relyingPartyUrl) + authnResponse.url.shouldBe(clientId) val result = verifierSiop.validateAuthnResponseFromPost(authnResponse.params.formUrlEncode()) @@ -184,14 +182,14 @@ class OidcSiopProtocolTest : FreeSpec({ walletUrl = walletUrl, requestOptions = RequestOptions( credentials = setOf(OidcSiopVerifier.RequestOptionsCredential(ConstantIndex.AtomicAttribute2023)), - responseMode = OpenIdConstants.ResponseMode.DIRECT_POST_JWT, - responseUrl = relyingPartyUrl, + responseMode = OpenIdConstants.ResponseMode.DirectPostJwt, + responseUrl = clientId, ) ) val authnResponse = holderSiop.createAuthnResponse(authnRequest).getOrThrow() authnResponse.shouldBeInstanceOf() - authnResponse.url.shouldBe(relyingPartyUrl) + authnResponse.url.shouldBe(clientId) authnResponse.params.shouldHaveSize(2) val jarmResponse = authnResponse.params.entries.first { it.key == "response" }.value DefaultVerifierJwsService().verifyJwsObject(JwsSigned.deserialize(jarmResponse).getOrThrow()).shouldBeTrue() @@ -208,7 +206,7 @@ class OidcSiopProtocolTest : FreeSpec({ walletUrl = walletUrl, requestOptions = RequestOptions( credentials = setOf(OidcSiopVerifier.RequestOptionsCredential(ConstantIndex.AtomicAttribute2023)), - responseMode = OpenIdConstants.ResponseMode.QUERY, + responseMode = OpenIdConstants.ResponseMode.Query, state = expectedState ) ) @@ -218,7 +216,7 @@ class OidcSiopProtocolTest : FreeSpec({ authnResponse.url.shouldContain("?") authnResponse.url.shouldNotContain("#") - authnResponse.url.shouldStartWith(relyingPartyUrl) + authnResponse.url.shouldStartWith(clientId) val result = verifierSiop.validateAuthnResponse(authnResponse.url) result.shouldBeInstanceOf() @@ -283,11 +281,10 @@ class OidcSiopProtocolTest : FreeSpec({ "test with request object and Attestation JWT" { val sprsCryptoService = DefaultCryptoService(EphemeralKeyWithoutCert()) - val attestationJwt = buildAttestationJwt(sprsCryptoService, relyingPartyUrl, verifierKeyMaterial) + val attestationJwt = buildAttestationJwt(sprsCryptoService, clientId, verifierKeyMaterial) verifierSiop = OidcSiopVerifier( keyMaterial = verifierKeyMaterial, - relyingPartyUrl = relyingPartyUrl, - clientIdScheme = OidcSiopVerifier.ClientIdScheme.VerifierAttestation(attestationJwt), + clientIdScheme = OidcSiopVerifier.ClientIdScheme.VerifierAttestation(attestationJwt, clientId), ) val authnRequestWithRequestObject = verifierSiop.createAuthnRequestUrlWithRequestObject( walletUrl = walletUrl, @@ -310,12 +307,11 @@ class OidcSiopProtocolTest : FreeSpec({ } "test with request object and invalid Attestation JWT" { val sprsCryptoService = DefaultCryptoService(EphemeralKeyWithoutCert()) - val attestationJwt = buildAttestationJwt(sprsCryptoService, relyingPartyUrl, verifierKeyMaterial) + val attestationJwt = buildAttestationJwt(sprsCryptoService, clientId, verifierKeyMaterial) verifierSiop = OidcSiopVerifier( keyMaterial = verifierKeyMaterial, - relyingPartyUrl = relyingPartyUrl, - clientIdScheme = OidcSiopVerifier.ClientIdScheme.VerifierAttestation(attestationJwt) + clientIdScheme = OidcSiopVerifier.ClientIdScheme.VerifierAttestation(attestationJwt, clientId) ) val authnRequestWithRequestObject = verifierSiop.createAuthnRequestUrlWithRequestObject( walletUrl = walletUrl, @@ -370,7 +366,7 @@ class OidcSiopProtocolTest : FreeSpec({ val requestUrl = "https://www.example.com/request/${uuid4()}" val authRequestUrlWithRequestUri = URLBuilder(walletUrl).apply { - parameters.append("client_id", relyingPartyUrl) + parameters.append("client_id", clientId) parameters.append("request_uri", requestUrl) }.buildString() @@ -399,7 +395,7 @@ class OidcSiopProtocolTest : FreeSpec({ val requestUrl = "https://www.example.com/request/${uuid4()}" val authRequestUrlWithRequestUri = URLBuilder(walletUrl).apply { - parameters.append("client_id", relyingPartyUrl) + parameters.append("client_id", clientId) parameters.append("request_uri", requestUrl) }.buildString() @@ -427,7 +423,7 @@ private fun requestOptionsAtomicAttribute() = RequestOptions( private suspend fun buildAttestationJwt( sprsCryptoService: DefaultCryptoService, - relyingPartyUrl: String, + clientId: String, verifierKeyMaterial: KeyMaterial ): JwsSigned = DefaultJwsService(sprsCryptoService).createSignedJws( header = JwsHeader( @@ -435,7 +431,7 @@ private suspend fun buildAttestationJwt( ), payload = JsonWebToken( issuer = "sprs", // allows Wallet to determine the issuer's key - subject = relyingPartyUrl, + subject = clientId, issuedAt = Clock.System.now(), expiration = Clock.System.now().plus(10.seconds), notBefore = Clock.System.now(), diff --git a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopSdJwtProtocolTest.kt b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopSdJwtProtocolTest.kt index 7cf5a6ce3..f4db8e6de 100644 --- a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopSdJwtProtocolTest.kt +++ b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopSdJwtProtocolTest.kt @@ -15,7 +15,7 @@ import io.kotest.matchers.types.shouldBeInstanceOf class OidcSiopSdJwtProtocolTest : FreeSpec({ - lateinit var relyingPartyUrl: String + lateinit var clientId: String lateinit var walletUrl: String lateinit var holderKeyMaterial: KeyMaterial @@ -30,7 +30,7 @@ class OidcSiopSdJwtProtocolTest : FreeSpec({ beforeEach { holderKeyMaterial = EphemeralKeyWithoutCert() verifierKeyMaterial = EphemeralKeyWithoutCert() - relyingPartyUrl = "https://example.com/rp/${uuid4()}" + clientId = "https://example.com/rp/${uuid4()}" walletUrl = "https://example.com/wallet/${uuid4()}" holderAgent = HolderAgent(holderKeyMaterial) verifierAgent = VerifierAgent(verifierKeyMaterial) @@ -50,7 +50,7 @@ class OidcSiopSdJwtProtocolTest : FreeSpec({ ) verifierSiop = OidcSiopVerifier( keyMaterial = verifierKeyMaterial, - relyingPartyUrl = relyingPartyUrl, + clientIdScheme = OidcSiopVerifier.ClientIdScheme.RedirectUri(clientId) ) } @@ -58,7 +58,7 @@ class OidcSiopSdJwtProtocolTest : FreeSpec({ val requestedClaim = CLAIM_GIVEN_NAME verifierSiop = OidcSiopVerifier( keyMaterial = verifierKeyMaterial, - relyingPartyUrl = relyingPartyUrl, + clientIdScheme = OidcSiopVerifier.ClientIdScheme.RedirectUri(clientId) ) val authnRequest = verifierSiop.createAuthnRequestUrl( walletUrl = walletUrl, diff --git a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWalletScopeSupportTest.kt b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWalletScopeSupportTest.kt index f9b785247..ca1cbc2e5 100644 --- a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWalletScopeSupportTest.kt +++ b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWalletScopeSupportTest.kt @@ -19,7 +19,6 @@ import io.kotest.matchers.collections.shouldNotBeEmpty import io.kotest.matchers.shouldBe import io.kotest.matchers.types.shouldBeInstanceOf -@Suppress("unused") class OidcSiopWalletScopeSupportTest : FreeSpec({ "specified well known scopes" - { // no scopes with a mapping to a presentation definition are known yet @@ -59,7 +58,7 @@ class OidcSiopWalletScopeSupportTest : FreeSpec({ ), )::get - lateinit var relyingPartyUrl: String + lateinit var clientId: String lateinit var holderKeyMaterial: KeyMaterial lateinit var verifierKeyMaterial: KeyMaterial @@ -73,7 +72,7 @@ class OidcSiopWalletScopeSupportTest : FreeSpec({ beforeEach { holderKeyMaterial = EphemeralKeyWithoutCert() verifierKeyMaterial = EphemeralKeyWithoutCert() - relyingPartyUrl = "https://example.com/rp/${uuid4()}" + clientId = "https://example.com/rp/${uuid4()}" holderAgent = HolderAgent(holderKeyMaterial) verifierAgent = VerifierAgent(verifierKeyMaterial) @@ -84,7 +83,7 @@ class OidcSiopWalletScopeSupportTest : FreeSpec({ ) verifierSiop = OidcSiopVerifier( keyMaterial = verifierKeyMaterial, - relyingPartyUrl = relyingPartyUrl, + clientIdScheme = OidcSiopVerifier.ClientIdScheme.RedirectUri(clientId) ) } diff --git a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopX509SanDnsTest.kt b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopX509SanDnsTest.kt index f17a50e98..00a3e486e 100644 --- a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopX509SanDnsTest.kt +++ b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopX509SanDnsTest.kt @@ -28,6 +28,7 @@ class OidcSiopX509SanDnsTest : FreeSpec({ lateinit var verifierSiop: OidcSiopVerifier beforeEach { + val clientId = "example.com" val extensions = listOf(X509CertificateExtension( KnownOIDs.subjectAltName_2_5_29_17, critical = false, @@ -35,7 +36,7 @@ class OidcSiopX509SanDnsTest : FreeSpec({ Asn1.Sequence { +Asn1Primitive( SubjectAltNameImplicitTags.dNSName, - Asn1String.UTF8("example.com").encodeToTlv().content + Asn1String.UTF8(clientId).encodeToTlv().content ) } )))) @@ -58,7 +59,10 @@ class OidcSiopX509SanDnsTest : FreeSpec({ ) verifierSiop = OidcSiopVerifier( keyMaterial = verifierKeyMaterial, - clientIdScheme = OidcSiopVerifier.ClientIdScheme.CertificateSanDns(listOf(verifierKeyMaterial.getCertificate()!!)), + clientIdScheme = OidcSiopVerifier.ClientIdScheme.CertificateSanDns( + listOf(verifierKeyMaterial.getCertificate()!!), + clientId + ), ) } @@ -72,7 +76,7 @@ class OidcSiopX509SanDnsTest : FreeSpec({ listOf(CLAIM_GIVEN_NAME) ) ), - responseMode = OpenIdConstants.ResponseMode.DIRECT_POST_JWT, + responseMode = OpenIdConstants.ResponseMode.DirectPostJwt, responseUrl = "https://example.com/response", ) ).getOrThrow() diff --git a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidvci/SerializationTest.kt b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidvci/SerializationTest.kt index 33292010d..e93220088 100644 --- a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidvci/SerializationTest.kt +++ b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidvci/SerializationTest.kt @@ -61,7 +61,7 @@ class SerializationTest : FunSpec({ types = setOf(randomString(), randomString()), ), proof = CredentialRequestProof( - proofType = OpenIdConstants.ProofType.OTHER(randomString()), + proofType = OpenIdConstants.ProofType.Other(randomString()), jwt = randomString() ) ) diff --git a/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/data/SubmissionRequirementsTest.kt b/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/data/SubmissionRequirementsTest.kt index ad1c40b30..3654bf116 100644 --- a/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/data/SubmissionRequirementsTest.kt +++ b/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/data/SubmissionRequirementsTest.kt @@ -5,7 +5,6 @@ import at.asitplus.dif.SubmissionRequirementRuleEnum import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe -@Suppress("unused") class SubmissionRequirementsTest : FreeSpec({ "all" - { "from" - {