diff --git a/.gitignore b/.gitignore index 5a9ec0e2..855cd87b 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ captures /.temp/ /.run/* kotlin-js-store/ +.env diff --git a/modules/openapi/src/commonMain/kotlin/com/sphereon/oid/fed/openapi/openapi.yaml b/modules/openapi/src/commonMain/kotlin/com/sphereon/oid/fed/openapi/openapi.yaml index c8ba18d7..e910b387 100644 --- a/modules/openapi/src/commonMain/kotlin/com/sphereon/oid/fed/openapi/openapi.yaml +++ b/modules/openapi/src/commonMain/kotlin/com/sphereon/oid/fed/openapi/openapi.yaml @@ -1530,7 +1530,6 @@ components: type: string description: The key ID. example: 12345 - nullable: true x: type: string description: The X coordinate for EC keys (optional). diff --git a/modules/openid-federation-client/build.gradle.kts b/modules/openid-federation-client/build.gradle.kts index 72d8b36c..4db63435 100644 --- a/modules/openid-federation-client/build.gradle.kts +++ b/modules/openid-federation-client/build.gradle.kts @@ -68,7 +68,7 @@ kotlin { val commonMain by getting { dependencies { api(projects.modules.openapi) - implementation(projects.modules.logger) + api(projects.modules.logger) implementation(libs.ktor.client.core) implementation(libs.ktor.client.logging) implementation(libs.ktor.client.content.negotiation) @@ -128,7 +128,7 @@ npmPublish { authToken.set(System.getenv("NPM_TOKEN") ?: "") } } - packages{ + packages { named("js") { packageJson { "name" by "@sphereon/openid-federation-client" diff --git a/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/Client.kt b/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/Client.kt index ffac7fb5..a52e7aec 100644 --- a/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/Client.kt +++ b/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/Client.kt @@ -1,11 +1,34 @@ package com.sphereon.oid.fed.client -import com.sphereon.oid.fed.client.service.DefaultCallbacks -import com.sphereon.oid.fed.client.trustchain.ITrustChainCallbackService +import com.sphereon.oid.fed.client.crypto.ICryptoService +import com.sphereon.oid.fed.client.crypto.cryptoService +import com.sphereon.oid.fed.client.fetch.IFetchService +import com.sphereon.oid.fed.client.fetch.fetchService +import com.sphereon.oid.fed.client.trustchain.TrustChain +import kotlin.js.JsExport -class FederationClient(val trustChainService: ITrustChainCallbackService? = DefaultCallbacks.trustChainService()) { +@JsExport.Ignore +interface IFederationClient { + val fetchServiceCallback: IFetchService? + val cryptoServiceCallback: ICryptoService? +} + +@JsExport.Ignore +class FederationClient( + override val fetchServiceCallback: IFetchService? = null, + override val cryptoServiceCallback: ICryptoService? = null +) : IFederationClient { + private val fetchService: IFetchService = + fetchServiceCallback ?: fetchService() + private val cryptoService: ICryptoService = cryptoServiceCallback ?: cryptoService() + + private val trustChainService: TrustChain = TrustChain(fetchService, cryptoService) - suspend fun resolveTrustChain(entityIdentifier: String, trustAnchors: Array): MutableList? { - return trustChainService?.resolve(entityIdentifier, trustAnchors) + suspend fun resolveTrustChain( + entityIdentifier: String, + trustAnchors: Array, + maxDepth: Int = 5 + ): MutableList? { + return trustChainService.resolve(entityIdentifier, trustAnchors, maxDepth) } } diff --git a/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/crypto/Crypto.kt b/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/crypto/Crypto.kt index a32143b0..5d74fb9e 100644 --- a/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/crypto/Crypto.kt +++ b/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/crypto/Crypto.kt @@ -1,90 +1,14 @@ package com.sphereon.oid.fed.client.crypto -import com.sphereon.oid.fed.client.mapper.decodeJWTComponents -import com.sphereon.oid.fed.client.service.DefaultCallbacks -import com.sphereon.oid.fed.client.service.ICallbackService import com.sphereon.oid.fed.openapi.models.Jwk -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive import kotlin.js.JsExport -expect interface ICryptoCallbackMarkerType -interface ICryptoMarkerType - -@JsExport.Ignore -interface ICryptoCallbackService: ICryptoCallbackMarkerType { - suspend fun verify( - jwt: String, - key: Jwk, - ): Boolean -} - @JsExport.Ignore -interface ICryptoService: ICryptoMarkerType { +interface ICryptoService { suspend fun verify( jwt: String, - key: Jwk, + key: Jwk ): Boolean } -expect fun cryptoService(platformCallback: ICryptoCallbackMarkerType = DefaultCallbacks.jwtService()): ICryptoService - -abstract class AbstractCryptoService(open val platformCallback: CallbackServiceType?): ICallbackService { - private var disabled = false - - override fun isEnabled(): Boolean { - return !this.disabled - } - - override fun disable() = apply { - this.disabled = true - } - - override fun enable() = apply { - this.disabled = false - } - - protected fun assertEnabled() { - if (!isEnabled()) { - CryptoConst.LOG.info("CRYPTO verify has been disabled") - throw IllegalStateException("CRYPTO service is disable; cannot verify") - } else if (this.platformCallback == null) { - CryptoConst.LOG.error("CRYPTO callback is not registered") - throw IllegalStateException("CRYPTO has not been initialized. Please register your CryptoCallback implementation, or register a default implementation") - } - } -} - -class CryptoService(override val platformCallback: ICryptoCallbackService = DefaultCallbacks.jwtService()): AbstractCryptoService(platformCallback), ICryptoService { - override fun platform(): ICryptoCallbackService { - return this.platformCallback - } - - override suspend fun verify(jwt: String, key: Jwk): Boolean { - assertEnabled() - return this.platformCallback.verify(jwt, key) - } - -} - -fun findKeyInJwks(keys: JsonArray, kid: String): Jwk? { - val key = keys.firstOrNull { it.jsonObject["kid"]?.jsonPrimitive?.content?.trim() == kid.trim() } - - if (key == null) return null - - return Json.decodeFromJsonElement(Jwk.serializer(), key) -} - -fun getKeyFromJwt(jwt: String): Jwk { - val decodedJwt = decodeJWTComponents(jwt) - - val key = findKeyInJwks( - decodedJwt.payload["jwks"]?.jsonObject?.get("keys")?.jsonArray ?: JsonArray(emptyList()), - decodedJwt.header.kid - ) ?: throw IllegalStateException("Key not found") - - return key -} +expect fun cryptoService(): ICryptoService diff --git a/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/crypto/CryptoConst.kt b/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/crypto/CryptoConst.kt index 86fc3530..4d6802a6 100644 --- a/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/crypto/CryptoConst.kt +++ b/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/crypto/CryptoConst.kt @@ -5,5 +5,4 @@ import com.sphereon.oid.fed.logger.Logger object CryptoConst { val LOG_NAMESPACE = "sphereon:oidf:client:crypto" val LOG = Logger(LOG_NAMESPACE) - val CRYPTO_LITERAL = "CRYPTO" } diff --git a/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/fetch/Fetch.kt b/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/fetch/Fetch.kt index 36c7b034..ddc7e1d1 100644 --- a/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/fetch/Fetch.kt +++ b/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/fetch/Fetch.kt @@ -1,70 +1,9 @@ package com.sphereon.oid.fed.client.fetch -import com.sphereon.oid.fed.client.service.DefaultCallbacks -import com.sphereon.oid.fed.client.service.ICallbackService -import io.ktor.client.* -import kotlin.js.JsExport - -expect interface IFetchCallbackMarkerType -interface IFetchMarkerType - -@JsExport.Ignore -interface IFetchCallbackService: IFetchCallbackMarkerType { +interface IFetchService { suspend fun fetchStatement( endpoint: String ): String - suspend fun getHttpClient(): HttpClient -} - -@JsExport.Ignore -interface IFetchService: IFetchMarkerType { - suspend fun fetchStatement( - endpoint: String - ): String - suspend fun getHttpClient(): HttpClient -} - -expect fun fetchService(platformCallback: IFetchCallbackMarkerType = DefaultCallbacks.fetchService()): IFetchService - -abstract class AbstractFetchService(open val platformCallback: CallbackServiceType): ICallbackService { - private var disabled = false - - override fun isEnabled(): Boolean { - return !this.disabled - } - - override fun disable() = apply { - this.disabled = true - } - - override fun enable() = apply { - this.disabled = false - } - - protected fun assertEnabled() { - if (!isEnabled()) { - FetchConst.LOG.info("CRYPTO verify has been disabled") - throw IllegalStateException("CRYPTO service is disable; cannot verify") - } else if (this.platformCallback == null) { - FetchConst.LOG.error("CRYPTO callback is not registered") - throw IllegalStateException("CRYPTO has not been initialized. Please register your CryptoCallback implementation, or register a default implementation") - } - } } -class FetchService(override val platformCallback: IFetchCallbackService = DefaultCallbacks.fetchService()): AbstractFetchService(platformCallback), IFetchService { - - override fun platform(): IFetchCallbackService { - return this.platformCallback - } - - override suspend fun fetchStatement(endpoint: String): String { - assertEnabled() - return this.platformCallback.fetchStatement(endpoint) - } - - override suspend fun getHttpClient(): HttpClient { - assertEnabled() - return this.platformCallback.getHttpClient() - } -} +expect fun fetchService(): IFetchService diff --git a/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/helpers/Helpers.kt b/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/helpers/Helpers.kt index f2121e8b..c19022d4 100644 --- a/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/helpers/Helpers.kt +++ b/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/helpers/Helpers.kt @@ -1,5 +1,11 @@ package com.sphereon.oid.fed.client.helpers +import com.sphereon.oid.fed.openapi.models.Jwk +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive + fun getEntityConfigurationEndpoint(iss: String): String { return "${if (iss.endsWith("/")) iss.dropLast(1) else iss}/.well-known/openid-federation" } @@ -7,3 +13,20 @@ fun getEntityConfigurationEndpoint(iss: String): String { fun getSubordinateStatementEndpoint(fetchEndpoint: String, sub: String, iss: String): String { return "${fetchEndpoint}?sub=$sub&iss=$iss" } + +fun findKeyInJwks(keys: JsonArray, kid: String): Jwk? { + val key = keys.firstOrNull { it.jsonObject["kid"]?.jsonPrimitive?.content?.trim() == kid.trim() } + + if (key == null) return null + + return Json.decodeFromJsonElement(Jwk.serializer(), key) +} + +fun checkKidInJwks(keys: Array, kid: String): Boolean { + for (key in keys) { + if (key.kid == kid) { + return true + } + } + return false +} diff --git a/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/mapper/JsonMapper.kt b/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/mapper/JsonMapper.kt index 5cd3c302..d3d43505 100644 --- a/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/mapper/JsonMapper.kt +++ b/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/mapper/JsonMapper.kt @@ -46,6 +46,5 @@ fun decodeJWTComponents(jwtToken: String): JWT { } } -// Custom Exceptions class InvalidJwtException(message: String) : Exception(message) class JwtDecodingException(message: String, cause: Throwable) : Exception(message, cause) diff --git a/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/service/OIDFClientServices.kt b/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/service/OIDFClientServices.kt deleted file mode 100644 index b79800a9..00000000 --- a/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/service/OIDFClientServices.kt +++ /dev/null @@ -1,70 +0,0 @@ -package com.sphereon.oid.fed.client.service - -import com.sphereon.oid.fed.client.crypto.ICryptoCallbackMarkerType -import com.sphereon.oid.fed.client.fetch.IFetchCallbackMarkerType -import com.sphereon.oid.fed.client.trustchain.ITrustChainCallbackMarkerType -import kotlin.js.JsExport - -@JsExport -object DefaultCallbacks { - private var cryptoCallbackService: ICryptoCallbackMarkerType? = null - private var fetchCallbackService: IFetchCallbackMarkerType? = null - private var trustChainCallbackService: ITrustChainCallbackMarkerType? = null - - fun jwtService(): CallbackType { - if (cryptoCallbackService == null) { - throw IllegalStateException("No default Crypto Platform Callback implementation was registered") - } - return cryptoCallbackService as CallbackType - } - - fun setCryptoServiceDefault(cryptoCallbackService: ICryptoCallbackMarkerType?) { - this.cryptoCallbackService = cryptoCallbackService - } - - fun fetchService(): CallbackType { - if (fetchCallbackService == null) { - throw IllegalStateException("No default Fetch Platform Callback implementation was registered") - } - return fetchCallbackService as CallbackType - } - - fun setFetchServiceDefault(fetchCallbackService: IFetchCallbackMarkerType?) { - this.fetchCallbackService = fetchCallbackService - } - - fun trustChainService(): CallbackType { - if (trustChainCallbackService == null) { - throw IllegalStateException("No default TrustChain Platform Callback implementation was registered") - } - return this.trustChainCallbackService as CallbackType - } - - fun setTrustChainServiceDefault(trustChainCallbackService: ITrustChainCallbackMarkerType?) { - this.trustChainCallbackService = trustChainCallbackService - } -} - -/** - * The main entry point for platform validation, delegating to a platform specific callback implemented by external developers - */ - -interface ICallbackService { - - /** - * Disable callback verification (be careful!) - */ - fun disable(): ICallbackService - - /** - * Enable the callback verification (default) - */ - fun enable(): ICallbackService - - /** - * Is the service enabled or not - */ - fun isEnabled(): Boolean - - fun platform(): PlatformCallbackType -} diff --git a/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/trustchain/TrustChain.kt b/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/trustchain/TrustChain.kt index 05fec6f0..6207cbfc 100644 --- a/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/trustchain/TrustChain.kt +++ b/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/trustchain/TrustChain.kt @@ -1,104 +1,30 @@ package com.sphereon.oid.fed.client.trustchain -import com.sphereon.oid.fed.client.crypto.ICryptoCallbackMarkerType -import com.sphereon.oid.fed.client.crypto.cryptoService -import com.sphereon.oid.fed.client.crypto.findKeyInJwks -import com.sphereon.oid.fed.client.fetch.IFetchCallbackMarkerType -import com.sphereon.oid.fed.client.fetch.fetchService +import com.sphereon.oid.fed.client.crypto.ICryptoService +import com.sphereon.oid.fed.client.fetch.IFetchService +import com.sphereon.oid.fed.client.helpers.checkKidInJwks +import com.sphereon.oid.fed.client.helpers.findKeyInJwks import com.sphereon.oid.fed.client.helpers.getEntityConfigurationEndpoint import com.sphereon.oid.fed.client.helpers.getSubordinateStatementEndpoint import com.sphereon.oid.fed.client.mapper.decodeJWTComponents import com.sphereon.oid.fed.client.mapper.mapEntityStatement -import com.sphereon.oid.fed.client.service.DefaultCallbacks -import com.sphereon.oid.fed.client.service.ICallbackService import com.sphereon.oid.fed.openapi.models.EntityConfigurationStatement -import com.sphereon.oid.fed.openapi.models.Jwk import com.sphereon.oid.fed.openapi.models.SubordinateStatement import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import kotlin.collections.set -import kotlin.js.JsExport -expect interface ITrustChainCallbackMarkerType -interface ITrustChainMarkerType - -@JsExport.Ignore -interface ITrustChainCallbackService : ITrustChainMarkerType { - suspend fun resolve( - entityIdentifier: String, trustAnchors: Array, maxDepth: Int = 5 - ): MutableList? -} - -@JsExport.Ignore -interface ITrustChainService : ITrustChainMarkerType { +/* + * TrustChain is a class that implements the logic to resolve and validate a trust chain. + */ +class TrustChain + ( + private val fetchService: IFetchService, + private val cryptoService: ICryptoService +) { suspend fun resolve( - entityIdentifier: String, trustAnchors: Array, maxDepth: Int = 5 - ): MutableList? -} - -expect fun trustChainService(platformCallback: ITrustChainCallbackMarkerType = DefaultCallbacks.trustChainService()): ITrustChainService - -abstract class AbstractTrustChainService(open val platformCallback: CallbackServiceType) : - ICallbackService { - private var disabled = false - - override fun isEnabled(): Boolean { - return !this.disabled - } - - override fun disable() = apply { - this.disabled = true - } - - override fun enable() = apply { - this.disabled = false - } - - protected fun assertEnabled() { - if (!isEnabled()) { - TrustChainConst.LOG.info("TRUST CHAIN verify has been disabled") - throw IllegalStateException("TRUST CHAIN service is disable; cannot verify") - } else if (this.platformCallback == null) { - TrustChainConst.LOG.error("TRUST CHAIN callback is not registered") - throw IllegalStateException("TRUST CHAIN has not been initialized. Please register your TrustChainCallback implementation, or register a default implementation") - } - } -} - -class TrustChainService(override val platformCallback: ITrustChainCallbackService = DefaultCallbacks.trustChainService()) : - AbstractTrustChainService(platformCallback), ITrustChainService { - - override fun platform(): ITrustChainCallbackService { - return this.platformCallback - } - - override suspend fun resolve( - entityIdentifier: String, - trustAnchors: Array, - maxDepth: Int - ): MutableList? { - assertEnabled() - return platformCallback.resolve(entityIdentifier, trustAnchors, maxDepth) - } -} - -class SimpleCache { - private val cacheMap = mutableMapOf() - - fun get(key: K): V? = cacheMap[key] - - fun put(key: K, value: V) { - cacheMap[key] = value - } -} - -class DefaultTrustChainImpl( - private val fetchService: IFetchCallbackMarkerType?, - private val cryptoService: ICryptoCallbackMarkerType? -) : ITrustChainCallbackService, ITrustChainCallbackMarkerType { - override suspend fun resolve( entityIdentifier: String, trustAnchors: Array, maxDepth: Int ): MutableList? { val cache = SimpleCache() @@ -121,7 +47,7 @@ class DefaultTrustChainImpl( ): MutableList? { if (depth == maxDepth) return null - val entityConfigurationJwt = fetchService(fetchService ?: DefaultCallbacks.fetchService()).fetchStatement( + val entityConfigurationJwt = this.fetchService.fetchStatement( getEntityConfigurationEndpoint(entityIdentifier) ) val decodedEntityConfiguration = decodeJWTComponents(entityConfigurationJwt) @@ -133,7 +59,7 @@ class DefaultTrustChainImpl( if (key == null) return null - if (!cryptoService(this.cryptoService ?: DefaultCallbacks.jwtService()).verify(entityConfigurationJwt, key)) { + if (!this.cryptoService.verify(entityConfigurationJwt, key)) { return null } @@ -188,7 +114,7 @@ class DefaultTrustChainImpl( if (cache.get(authorityConfigurationEndpoint) != null) return null val authorityEntityConfigurationJwt = - fetchService(this.fetchService ?: DefaultCallbacks.fetchService()).fetchStatement( + this.fetchService.fetchStatement( authorityConfigurationEndpoint ) cache.put(authorityConfigurationEndpoint, authorityEntityConfigurationJwt) @@ -203,7 +129,7 @@ class DefaultTrustChainImpl( if (key == null) return null - if (!cryptoService(this.cryptoService ?: DefaultCallbacks.jwtService()).verify( + if (!this.cryptoService.verify( authorityEntityConfigurationJwt, key ) @@ -225,7 +151,7 @@ class DefaultTrustChainImpl( getSubordinateStatementEndpoint(authorityEntityFetchEndpoint, entityIdentifier, authority) val subordinateStatementJwt = - fetchService(this.fetchService ?: DefaultCallbacks.fetchService()).fetchStatement( + this.fetchService.fetchStatement( subordinateStatementEndpoint ) @@ -239,7 +165,7 @@ class DefaultTrustChainImpl( if (subordinateStatementKey == null) return null - if (!cryptoService(this.cryptoService ?: DefaultCallbacks.jwtService()).verify( + if (!this.cryptoService.verify( subordinateStatementJwt, subordinateStatementKey ) @@ -278,13 +204,14 @@ class DefaultTrustChainImpl( return null } +} - private fun checkKidInJwks(keys: Array, kid: String): Boolean { - for (key in keys) { - if (key.kid == kid) { - return true - } - } - return false +class SimpleCache { + private val cacheMap = mutableMapOf() + + fun get(key: K): V? = cacheMap[key] + + fun put(key: K, value: V) { + cacheMap[key] = value } } diff --git a/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/trustchain/TrustChainConst.kt b/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/trustchain/TrustChainConst.kt index c0eda50b..6226e519 100644 --- a/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/trustchain/TrustChainConst.kt +++ b/modules/openid-federation-client/src/commonMain/kotlin/com/sphereon/oid/fed/client/trustchain/TrustChainConst.kt @@ -5,5 +5,4 @@ import com.sphereon.oid.fed.logger.Logger object TrustChainConst { val LOG_NAMESPACE = "sphereon:oidf:client:trust_chain" val LOG = Logger(LOG_NAMESPACE) - val TRUST_CHAIN_LITERAL = "TRUST_CHAIN" } diff --git a/modules/openid-federation-client/src/commonTest/kotlin/com/sphereon/oid/fed/client/trustchain/TrustChainTest.kt b/modules/openid-federation-client/src/commonTest/kotlin/com/sphereon/oid/fed/client/trustchain/TrustChainTest.kt index 1d493b25..218c525d 100644 --- a/modules/openid-federation-client/src/commonTest/kotlin/com/sphereon/oid/fed/client/trustchain/TrustChainTest.kt +++ b/modules/openid-federation-client/src/commonTest/kotlin/com/sphereon/oid/fed/client/trustchain/TrustChainTest.kt @@ -1,7 +1,86 @@ package com.sphereon.oid.fed.client.trustchain -expect class PlatformCallback +import com.sphereon.oid.fed.client.FederationClient +import com.sphereon.oid.fed.client.crypto.ICryptoService +import com.sphereon.oid.fed.client.fetch.IFetchService +import com.sphereon.oid.fed.openapi.models.Jwk +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull -expect class CryptoCallbackService +object FetchService : IFetchService { + override suspend fun fetchStatement(endpoint: String): String { + return mockResponses.find { it[0] == endpoint }?.get(1) ?: throw Exception("Not found") + } +} -expect class TrustChainTest() +object CryptoService : ICryptoService { + override suspend fun verify(jwt: String, key: Jwk): Boolean { + return true + } +} + +class TrustChainTest { + @Test + fun buildTrustChain() = runTest { + val client = FederationClient(FetchService, CryptoService) + + val trustChain = client.resolveTrustChain( + "https://spid.wbss.it/Spid/oidc/rp/ipasv_lt", + arrayOf("https://oidc.registry.servizicie.interno.gov.it") + ) + + assertNotNull(trustChain) + + assertEquals(4, trustChain.size) + + assertEquals( + trustChain[0], + mockResponses.find { it[0] == "https://spid.wbss.it/Spid/oidc/rp/ipasv_lt/.well-known/openid-federation" } + ?.get(1) + ) + + assertEquals( + trustChain[1], + mockResponses.find { it[0] == "https://spid.wbss.it/Spid/oidc/sa/fetch?sub=https://spid.wbss.it/Spid/oidc/rp/ipasv_lt&iss=https://spid.wbss.it/Spid/oidc/sa" } + ?.get(1) + ) + + assertEquals( + trustChain[2], + mockResponses.find { it[0] == "https://oidc.registry.servizicie.interno.gov.it/fetch?sub=https://spid.wbss.it/Spid/oidc/sa&iss=https://oidc.registry.servizicie.interno.gov.it" } + ?.get(1) + ) + + assertEquals( + trustChain[3], + mockResponses.find { it[0] == "https://oidc.registry.servizicie.interno.gov.it/.well-known/openid-federation" } + ?.get(1) + ) + + val trustChain2 = client.resolveTrustChain( + "https://spid.wbss.it/Spid/oidc/sa", + arrayOf("https://oidc.registry.servizicie.interno.gov.it") + ) + + assertNotNull(trustChain2) + assertEquals(3, trustChain2.size) + assertEquals( + trustChain2[0], + mockResponses.find { it[0] == "https://spid.wbss.it/Spid/oidc/sa/.well-known/openid-federation" }?.get(1) + ) + + assertEquals( + trustChain2[1], + mockResponses.find { it[0] == "https://oidc.registry.servizicie.interno.gov.it/fetch?sub=https://spid.wbss.it/Spid/oidc/sa&iss=https://oidc.registry.servizicie.interno.gov.it" } + ?.get(1) + ) + + assertEquals( + trustChain2[2], + mockResponses.find { it[0] == "https://oidc.registry.servizicie.interno.gov.it/.well-known/openid-federation" } + ?.get(1) + ) + } +} diff --git a/modules/openid-federation-client/src/jsMain/kotlin/com/sphereon/oid/fed/client/Client.js.kt b/modules/openid-federation-client/src/jsMain/kotlin/com/sphereon/oid/fed/client/Client.js.kt index b10f8d1e..4e34f024 100644 --- a/modules/openid-federation-client/src/jsMain/kotlin/com/sphereon/oid/fed/client/Client.js.kt +++ b/modules/openid-federation-client/src/jsMain/kotlin/com/sphereon/oid/fed/client/Client.js.kt @@ -1,26 +1,59 @@ -package com.sphereon.oid.fed.client - -import com.sphereon.oid.fed.client.service.DefaultCallbacks -import com.sphereon.oid.fed.client.trustchain.ITrustChainCallbackServiceJS -import kotlinx.coroutines.CoroutineName +import com.sphereon.oid.fed.client.crypto.CryptoServiceAdapter +import com.sphereon.oid.fed.client.crypto.ICryptoService +import com.sphereon.oid.fed.client.crypto.cryptoService +import com.sphereon.oid.fed.client.fetch.FetchServiceAdapter +import com.sphereon.oid.fed.client.fetch.IFetchService +import com.sphereon.oid.fed.client.fetch.fetchService +import com.sphereon.oid.fed.client.trustchain.TrustChain +import com.sphereon.oid.fed.openapi.models.Jwk import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.asPromise -import kotlinx.coroutines.async -import kotlinx.coroutines.await +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.promise import kotlin.js.Promise +@JsExport +@JsName("ICryptoService") +external interface ICryptoServiceJS { + fun verify( + jwt: String, + key: Jwk + ): Promise +} + +@JsExport +@JsName("IFetchService") +external interface IFetchServiceJS { + fun fetchStatement(endpoint: String): Promise +} + @JsExport @JsName("FederationClient") -class FederationClientJS(val trustChainServiceCallback: ITrustChainCallbackServiceJS? = DefaultCallbacks.trustChainService()) { +class FederationClientJS( + fetchServiceCallback: IFetchServiceJS?, + cryptoServiceCallback: ICryptoServiceJS?, +) { + private val fetchService: IFetchService = + if (fetchServiceCallback != null) FetchServiceAdapter(fetchServiceCallback) else fetchService() + private val cryptoService: ICryptoService = + if (cryptoServiceCallback != null) CryptoServiceAdapter(cryptoServiceCallback) else cryptoService() + + private val trustChainService: TrustChain = TrustChain(fetchService, cryptoService) - private val CLIENT_JS_SCOPE = "ClientJS" + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) - @OptIn(DelicateCoroutinesApi::class) @JsName("resolveTrustChain") - fun resolveTrustChainJS(entityIdentifier: String, trustAnchors: Array): Promise?> { - return CoroutineScope(context = CoroutineName(CLIENT_JS_SCOPE)).async { - return@async trustChainServiceCallback?.resolve(entityIdentifier, trustAnchors)?.await() - }.asPromise() + fun resolveTrustChainJS( + entityIdentifier: String, + trustAnchors: Array, + maxDepth: Int = 10 + ): Promise?> { + return scope.promise { + try { + trustChainService.resolve(entityIdentifier, trustAnchors, maxDepth)?.toTypedArray() + } catch (e: Exception) { + throw RuntimeException("Failed to resolve trust chain: ${e.message}", e) + } + } } } diff --git a/modules/openid-federation-client/src/jsMain/kotlin/com/sphereon/oid/fed/client/crypto/Crypto.js.kt b/modules/openid-federation-client/src/jsMain/kotlin/com/sphereon/oid/fed/client/crypto/Crypto.js.kt index 468037d9..c0c1f1cf 100644 --- a/modules/openid-federation-client/src/jsMain/kotlin/com/sphereon/oid/fed/client/crypto/Crypto.js.kt +++ b/modules/openid-federation-client/src/jsMain/kotlin/com/sphereon/oid/fed/client/crypto/Crypto.js.kt @@ -1,67 +1,56 @@ package com.sphereon.oid.fed.client.crypto -import com.sphereon.oid.fed.client.service.DefaultCallbacks +import ICryptoServiceJS +import com.sphereon.oid.fed.client.mapper.decodeJWTComponents import com.sphereon.oid.fed.openapi.models.Jwk -import kotlinx.coroutines.CoroutineName -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.asPromise -import kotlinx.coroutines.async import kotlinx.coroutines.await +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import kotlin.js.Promise -@JsExport -external interface ICryptoCallbackServiceJS: ICryptoCallbackMarkerType { - fun verify( - jwt: String, - key: Jwk, - ): Promise +@JsModule("jose") +@JsNonModule +external object Jose { + fun importJWK(jwk: Jwk, alg: String, options: dynamic = definedExternally): Promise + fun jwtVerify(jwt: String, key: Any, options: dynamic = definedExternally): Promise } -@JsExport -external interface ICryptoServiceJS { - fun verify( - jwt: String, - key: Jwk - ): Promise -} - -private const val CRYPTO_SERVICE_JS_SCOPE = "CryptoServiceJS" - -@JsExport -class CryptoServiceJS(override val platformCallback: ICryptoCallbackServiceJS = DefaultCallbacks.jwtService()): AbstractCryptoService(platformCallback), ICryptoServiceJS { - - override fun platform(): ICryptoCallbackServiceJS { - return this.platformCallback - } - - override fun verify( - jwt: String, - key: Jwk - ): Promise { - return CoroutineScope(context = CoroutineName(CRYPTO_SERVICE_JS_SCOPE)).async { - return@async platformCallback.verify(jwt, key).await() - }.asPromise() +class CryptoServiceAdapter(private val jsCryptoService: ICryptoServiceJS) : ICryptoService { + override suspend fun verify(jwt: String, key: Jwk): Boolean { + return jsCryptoService.verify(jwt, key).await() } } -class CryptoServiceJSAdapter(val cryptoCallbackJS: CryptoServiceJS = CryptoServiceJS()): AbstractCryptoService(cryptoCallbackJS.platformCallback), ICryptoService { - - override fun platform(): ICryptoCallbackServiceJS = cryptoCallbackJS.platformCallback - - override suspend fun verify( - jwt: String, - key: Jwk - ): Boolean = this.cryptoCallbackJS.verify(jwt, key).await() +object CryptoServiceJS : ICryptoServiceJS { + override fun verify(jwt: String, key: Jwk): Promise { + return Promise { resolve, reject -> + try { + val decodedJwt = decodeJWTComponents(jwt) + + Jose.importJWK( + JSON.parse(Json.encodeToString(key)), + alg = decodedJwt.header.alg ?: "RS256" + ).then { publicKey: Any -> + Jose.jwtVerify(jwt, publicKey).then { + resolve(true) + }.catch { + resolve(false) + } + }.catch { + resolve(false) + } + } catch (e: Throwable) { + resolve(false) + } + } + } } @JsExport.Ignore -actual fun cryptoService(platformCallback: ICryptoCallbackMarkerType): ICryptoService { - val jsPlatformCallback = platformCallback.unsafeCast() - if (jsPlatformCallback === undefined) { - throw IllegalStateException("Invalid platform callback supplied: Needs to be of type ICryptoCallbackServiceJS, but is of type ${platformCallback::class::simpleName} instead") +actual fun cryptoService(): ICryptoService { + return object : ICryptoService { + override suspend fun verify(jwt: String, key: Jwk): Boolean { + return CryptoServiceJS.verify(jwt, key).await() + } } - return CryptoServiceJSAdapter(CryptoServiceJS(jsPlatformCallback)) } - -@JsExport -actual external interface ICryptoCallbackMarkerType diff --git a/modules/openid-federation-client/src/jsMain/kotlin/com/sphereon/oid/fed/client/fetch/Fetch.js.kt b/modules/openid-federation-client/src/jsMain/kotlin/com/sphereon/oid/fed/client/fetch/Fetch.js.kt index efb91b73..b1cf1eaa 100644 --- a/modules/openid-federation-client/src/jsMain/kotlin/com/sphereon/oid/fed/client/fetch/Fetch.js.kt +++ b/modules/openid-federation-client/src/jsMain/kotlin/com/sphereon/oid/fed/client/fetch/Fetch.js.kt @@ -1,99 +1,29 @@ package com.sphereon.oid.fed.client.fetch -import com.sphereon.oid.fed.client.crypto.AbstractCryptoService -import com.sphereon.oid.fed.client.service.DefaultCallbacks -import io.ktor.client.HttpClient -import io.ktor.client.call.body -import io.ktor.client.engine.js.Js -import io.ktor.client.request.get -import io.ktor.http.HttpHeaders -import io.ktor.http.headers -import kotlinx.coroutines.CoroutineName -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.asPromise -import kotlinx.coroutines.async +import IFetchServiceJS +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.engine.js.* +import io.ktor.client.request.* +import io.ktor.http.* import kotlinx.coroutines.await -import kotlin.js.Promise - -@JsExport -interface IFetchCallbackServiceJS: IFetchCallbackMarkerType { - fun fetchStatement( - endpoint: String - ): Promise - fun getHttpClient(): Promise -} - -@JsExport.Ignore -interface IFetchServiceJS: IFetchMarkerType { - fun fetchStatement( - endpoint: String - ): Promise - fun getHttpClient(): Promise -} - -private const val FETCH_SERVICE_JS_SCOPE = "FetchServiceJS" - -@JsExport -class FetchServiceJS(override val platformCallback: IFetchCallbackServiceJS = DefaultCallbacks.fetchService()): AbstractCryptoService(platformCallback), IFetchServiceJS { - - override fun platform(): IFetchCallbackServiceJS { - return this.platformCallback - } - - override fun fetchStatement(endpoint: String): Promise { - return CoroutineScope(context = CoroutineName(FETCH_SERVICE_JS_SCOPE)).async { - return@async platformCallback.fetchStatement(endpoint).await() - }.asPromise() - } - - override fun getHttpClient(): Promise { - return CoroutineScope(context = CoroutineName(FETCH_SERVICE_JS_SCOPE)).async { - return@async platformCallback.getHttpClient().await() - }.asPromise() +class FetchServiceAdapter(private val jsFetchService: IFetchServiceJS) : IFetchService { + override suspend fun fetchStatement(endpoint: String): String { + return jsFetchService.fetchStatement(endpoint).await() } } -class FetchServiceJSAdapter(val fetchCallbackJS: FetchServiceJS = FetchServiceJS()): AbstractFetchService(fetchCallbackJS.platformCallback), IFetchService { - - override fun platform(): IFetchCallbackServiceJS = fetchCallbackJS.platformCallback - - override suspend fun fetchStatement(endpoint: String): String = - this.platformCallback.fetchStatement(endpoint).await() - - override suspend fun getHttpClient(): HttpClient = this.platformCallback.getHttpClient().await() -} - -@JsExport.Ignore -actual fun fetchService(platformCallback: IFetchCallbackMarkerType): IFetchService { - val jsPlatformCallback = platformCallback.unsafeCast() - if (jsPlatformCallback === undefined) { - throw IllegalStateException("Invalid platform callback supplied: Needs to be of type IFetchCallbackServiceJS, but is of type ${platformCallback::class::simpleName} instead") - } - return FetchServiceJSAdapter(FetchServiceJS(jsPlatformCallback)) -} - -@JsExport -actual external interface IFetchCallbackMarkerType - -@JsExport -class DefaultFetchJSImpl : IFetchCallbackServiceJS { - - private val FETCH_SERVICE_JS_SCOPE = "FetchServiceJS" - - override fun getHttpClient(): Promise { - return CoroutineScope(context = CoroutineName(FETCH_SERVICE_JS_SCOPE)).async { - return@async HttpClient(Js) - }.asPromise() - } +actual fun fetchService(): IFetchService { + return object : IFetchService { + private val httpClient = HttpClient(Js) - override fun fetchStatement(endpoint: String): Promise { - return CoroutineScope(context = CoroutineName(FETCH_SERVICE_JS_SCOPE)).async { - return@async getHttpClient().await().get(endpoint) { + override suspend fun fetchStatement(endpoint: String): String { + return httpClient.get(endpoint) { headers { append(HttpHeaders.Accept, "application/entity-statement+jwt") } - }.body() as String - }.asPromise() + }.body() + } } } diff --git a/modules/openid-federation-client/src/jsMain/kotlin/com/sphereon/oid/fed/client/service/OIDFClientServices.js.kt b/modules/openid-federation-client/src/jsMain/kotlin/com/sphereon/oid/fed/client/service/OIDFClientServices.js.kt deleted file mode 100644 index 8deb4998..00000000 --- a/modules/openid-federation-client/src/jsMain/kotlin/com/sphereon/oid/fed/client/service/OIDFClientServices.js.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.sphereon.oid.fed.client.service - -import com.sphereon.oid.fed.client.crypto.CryptoServiceJS -import com.sphereon.oid.fed.client.crypto.ICryptoCallbackServiceJS -import com.sphereon.oid.fed.client.fetch.FetchServiceJS -import com.sphereon.oid.fed.client.fetch.IFetchCallbackServiceJS -import com.sphereon.oid.fed.client.trustchain.ITrustChainCallbackServiceJS -import com.sphereon.oid.fed.client.trustchain.TrustChainServiceJS - -@JsExport -object CryptoServicesJS { - fun crypto(platformCallback: ICryptoCallbackServiceJS = DefaultCallbacks.jwtService()) = CryptoServiceJS(platformCallback) - fun fetch(platformCallback: IFetchCallbackServiceJS = DefaultCallbacks.fetchService()) = FetchServiceJS(platformCallback) - fun trustChain(platformCallback: ITrustChainCallbackServiceJS = DefaultCallbacks.trustChainService()) = TrustChainServiceJS(platformCallback) -} - -@JsExport -external interface ICallbackServiceJS { - /** - * Disable callback verification (be careful!) - */ - fun disable(): ICallbackServiceJS - - /** - * Enable the callback verification (default) - */ - fun enable(): ICallbackServiceJS - - - /** - * Is the service enabled or not - */ - fun isEnabled(): Boolean - - fun platform(): PlatformCallbackType -} diff --git a/modules/openid-federation-client/src/jsMain/kotlin/com/sphereon/oid/fed/client/trustchain/TrustChain.js.kt b/modules/openid-federation-client/src/jsMain/kotlin/com/sphereon/oid/fed/client/trustchain/TrustChain.js.kt deleted file mode 100644 index 1c92c561..00000000 --- a/modules/openid-federation-client/src/jsMain/kotlin/com/sphereon/oid/fed/client/trustchain/TrustChain.js.kt +++ /dev/null @@ -1,282 +0,0 @@ -package com.sphereon.oid.fed.client.trustchain - -import com.sphereon.oid.fed.client.crypto.ICryptoCallbackMarkerType -import com.sphereon.oid.fed.client.crypto.cryptoService -import com.sphereon.oid.fed.client.crypto.findKeyInJwks -import com.sphereon.oid.fed.client.fetch.IFetchCallbackMarkerType -import com.sphereon.oid.fed.client.fetch.fetchService -import com.sphereon.oid.fed.client.helpers.getEntityConfigurationEndpoint -import com.sphereon.oid.fed.client.helpers.getSubordinateStatementEndpoint -import com.sphereon.oid.fed.client.mapper.decodeJWTComponents -import com.sphereon.oid.fed.client.mapper.mapEntityStatement -import com.sphereon.oid.fed.client.service.DefaultCallbacks -import com.sphereon.oid.fed.openapi.models.EntityConfigurationStatement -import com.sphereon.oid.fed.openapi.models.Jwk -import com.sphereon.oid.fed.openapi.models.SubordinateStatement -import kotlinx.coroutines.CoroutineName -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.asPromise -import kotlinx.coroutines.async -import kotlinx.coroutines.await -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import kotlin.js.Promise - -@JsExport -interface ITrustChainCallbackServiceJS : ITrustChainCallbackMarkerType { - fun resolve( - entityIdentifier: String, trustAnchors: Array, maxDepth: Int = 5 - ): Promise?> -} - -@JsExport.Ignore -interface ITrustChainServiceJS : ITrustChainMarkerType { - fun resolve( - entityIdentifier: String, trustAnchors: Array, maxDepth: Int = 5 - ): Promise?> -} - -private const val TRUST_CHAIN_SERVICE_JS_SCOPE = "TrustChainServiceJS" - -@JsExport -class TrustChainServiceJS(override val platformCallback: ITrustChainCallbackServiceJS = DefaultCallbacks.trustChainService()) : - AbstractTrustChainService(platformCallback), ITrustChainServiceJS { - - override fun platform(): ITrustChainCallbackServiceJS { - return this.platformCallback - } - - override fun resolve( - entityIdentifier: String, - trustAnchors: Array, - maxDepth: Int - ): Promise?> { - return CoroutineScope(context = CoroutineName(TRUST_CHAIN_SERVICE_JS_SCOPE)).async { - return@async platformCallback.resolve(entityIdentifier, trustAnchors, maxDepth).await() - }.asPromise() - } -} - -class TrustChainServiceJSAdapter(val trustChainCallbackJS: TrustChainServiceJS = TrustChainServiceJS()) : - AbstractTrustChainService(trustChainCallbackJS.platformCallback), ITrustChainService { - - override fun platform(): ITrustChainCallbackServiceJS = trustChainCallbackJS.platformCallback - - override suspend fun resolve( - entityIdentifier: String, - trustAnchors: Array, - maxDepth: Int - ): MutableList? = - this.trustChainCallbackJS.resolve(entityIdentifier, trustAnchors, maxDepth).await()?.toMutableList() -} - -@JsExport.Ignore -actual fun trustChainService(platformCallback: ITrustChainCallbackMarkerType): ITrustChainService { - val jsPlatformCallback = platformCallback.unsafeCast() - if (jsPlatformCallback === undefined) { - throw IllegalStateException("Invalid platform callback supplied: Needs to be of type ITrustChainCallbackServiceJS, but is of type ${platformCallback::class::simpleName} instead") - } - return TrustChainServiceJSAdapter(TrustChainServiceJS(jsPlatformCallback)) -} - -@JsExport -actual external interface ITrustChainCallbackMarkerType - -@JsExport -class DefaultTrustChainJSImpl( - private val fetchService: IFetchCallbackMarkerType? = DefaultCallbacks.fetchService(), - private val cryptoService: ICryptoCallbackMarkerType? = DefaultCallbacks.jwtService() -) : ITrustChainCallbackServiceJS, ITrustChainCallbackMarkerType { - override fun resolve( - entityIdentifier: String, trustAnchors: Array, maxDepth: Int - ): Promise?> = CoroutineScope(context = CoroutineName(TRUST_CHAIN_SERVICE_JS_SCOPE)).async { - val cache = SimpleCache() - val chain: MutableList = arrayListOf() - return@async try { - buildTrustChainRecursive(entityIdentifier, trustAnchors, chain, cache, 0, maxDepth).await() - } catch (_: Exception) { - // Log error - null - } - }.asPromise() - - private fun buildTrustChainRecursive( - entityIdentifier: String, - trustAnchors: Array, - chain: MutableList, - cache: SimpleCache, - depth: Int, - maxDepth: Int - ): Promise?> = CoroutineScope(context = CoroutineName(TRUST_CHAIN_SERVICE_JS_SCOPE)).async { - if (depth == maxDepth) return@async null - - val entityConfigurationJwt = fetchService(fetchService ?: DefaultCallbacks.fetchService()).fetchStatement( - getEntityConfigurationEndpoint(entityIdentifier) - ) - - val decodedEntityConfiguration = decodeJWTComponents(entityConfigurationJwt) - - val key = findKeyInJwks( - decodedEntityConfiguration.payload["jwks"]?.jsonObject?.get("keys")?.jsonArray ?: return@async null, - decodedEntityConfiguration.header.kid - ) - - if (key == null) return@async null - - if (!cryptoService(cryptoService ?: DefaultCallbacks.jwtService()).verify(entityConfigurationJwt, key)) { - return@async null - } - - val entityStatement: EntityConfigurationStatement = - mapEntityStatement(entityConfigurationJwt, EntityConfigurationStatement::class) ?: return@async null - - if (chain.isEmpty()) { - chain.add(entityConfigurationJwt) - } - - val authorityHints = entityStatement.authorityHints ?: return@async null - - val reorderedAuthorityHints = authorityHints.sortedBy { hint -> - if (trustAnchors.contains(hint)) 0 else 1 - } - - for (authority in reorderedAuthorityHints) { - val result = processAuthority( - authority, - entityIdentifier, - trustAnchors, - chain, - decodedEntityConfiguration.header.kid, - cache, - depth + 1, - maxDepth - ).await() - - if (result != null) { - return@async result - } - } - - return@async null - }.asPromise() - - private fun processAuthority( - authority: String, - entityIdentifier: String, - trustAnchors: Array, - chain: MutableList, - lastStatementKid: String, - cache: SimpleCache, - depth: Int, - maxDepth: Int - ): Promise?> = CoroutineScope(context = CoroutineName(TRUST_CHAIN_SERVICE_JS_SCOPE)).async { - try { - val authorityConfigurationEndpoint = getEntityConfigurationEndpoint(authority) - - // Avoid processing the same entity twice - if (cache.get(authorityConfigurationEndpoint) != null) return@async null - - val authorityEntityConfigurationJwt = - fetchService(fetchService ?: DefaultCallbacks.fetchService()).fetchStatement( - authorityConfigurationEndpoint - ) - cache.put(authorityConfigurationEndpoint, authorityEntityConfigurationJwt) - - val decodedJwt = decodeJWTComponents(authorityEntityConfigurationJwt) - val kid = decodedJwt.header.kid - - val key = findKeyInJwks( - decodedJwt.payload["jwks"]?.jsonObject?.get("keys")?.jsonArray ?: return@async null, - kid - ) - - if (key == null) return@async null - - if (!cryptoService(cryptoService ?: DefaultCallbacks.jwtService()).verify( - authorityEntityConfigurationJwt, - key - ) - ) { - return@async null - } - - val authorityEntityConfiguration: EntityConfigurationStatement = - mapEntityStatement(authorityEntityConfigurationJwt, EntityConfigurationStatement::class) - ?: return@async null - - val federationEntityMetadata = - authorityEntityConfiguration.metadata?.get("federation_entity") as? JsonObject - if (federationEntityMetadata == null || !federationEntityMetadata.containsKey("federation_fetch_endpoint")) return@async null - - val authorityEntityFetchEndpoint = - federationEntityMetadata["federation_fetch_endpoint"]?.jsonPrimitive?.content ?: return@async null - - val subordinateStatementEndpoint = - getSubordinateStatementEndpoint(authorityEntityFetchEndpoint, entityIdentifier, authority) - - val subordinateStatementJwt = - fetchService(fetchService ?: DefaultCallbacks.fetchService()).fetchStatement( - subordinateStatementEndpoint - ) - - val decodedSubordinateStatement = decodeJWTComponents(subordinateStatementJwt) - - val subordinateStatementKey = findKeyInJwks( - decodedJwt.payload["jwks"]?.jsonObject?.get("keys")?.jsonArray - ?: return@async null, - decodedSubordinateStatement.header.kid - ) - - if (subordinateStatementKey == null) return@async null - - if (!cryptoService(cryptoService ?: DefaultCallbacks.jwtService()).verify( - subordinateStatementJwt, - subordinateStatementKey - ) - ) { - return@async null - } - - val subordinateStatement: SubordinateStatement = - mapEntityStatement(subordinateStatementJwt, SubordinateStatement::class) ?: return@async null - - val jwks = subordinateStatement.jwks - val keys = jwks.propertyKeys ?: return@async null - - // Check if the entity key exists in subordinate statement - val entityKeyExistsInSubordinateStatement = checkKidInJwks(keys, lastStatementKid).await() - if (!entityKeyExistsInSubordinateStatement) return@async null - - // If authority is in trust anchors, return the completed chain - if (trustAnchors.contains(authority)) { - chain.add(subordinateStatementJwt) - chain.add(authorityEntityConfigurationJwt) - return@async chain.toTypedArray() - } - - // Recursively build trust chain if there are authority hints - if (authorityEntityConfiguration.authorityHints?.isNotEmpty() == true) { - chain.add(subordinateStatementJwt) - val result = - buildTrustChainRecursive(authority, trustAnchors, chain, cache, depth, maxDepth).await() - if (result != null) return@async result - chain.removeLast() - } - } catch (_: Exception) { - return@async null - } - - return@async null - }.asPromise() - - private fun checkKidInJwks(keys: Array, kid: String): Promise { - for (key in keys) { - if (key.kid == kid) { - return Promise.resolve(true) - } - } - return Promise.resolve(false) - } -} diff --git a/modules/openid-federation-client/src/jsTest/kotlin/com/sphereon/oid/fed/client/crypto/CryptoPlatformTestCallback.js.kt b/modules/openid-federation-client/src/jsTest/kotlin/com/sphereon/oid/fed/client/crypto/CryptoPlatformTestCallback.js.kt deleted file mode 100644 index edf58943..00000000 --- a/modules/openid-federation-client/src/jsTest/kotlin/com/sphereon/oid/fed/client/crypto/CryptoPlatformTestCallback.js.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.sphereon.oid.fed.client.crypto - -import com.sphereon.oid.fed.client.mapper.decodeJWTComponents -import com.sphereon.oid.fed.openapi.models.Jwk -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import kotlin.js.Promise - -class CryptoPlatformCallback : ICryptoCallbackServiceJS { - override fun verify(jwt: String, key: Jwk): Promise { - return try { - val decodedJwt = decodeJWTComponents(jwt) - - Jose.importJWK( - JSON.parse(Json.encodeToString(key)), alg = decodedJwt.header.alg ?: "RS256" - ).then { publicKey: dynamic -> - val options: dynamic = js("({})") - options["currentDate"] = js("new Date(Date.parse(\"Oct 14, 2024 01:00:00\"))") - - Jose.jwtVerify(jwt, publicKey, options).then { verification: dynamic -> - verification != undefined - }.catch { - false - } - }.catch { - false - } - } catch (e: Throwable) { - Promise.resolve(false) - } - } -} diff --git a/modules/openid-federation-client/src/jsTest/kotlin/com/sphereon/oid/fed/client/crypto/CryptoTest.js.kt b/modules/openid-federation-client/src/jsTest/kotlin/com/sphereon/oid/fed/client/crypto/CryptoTest.js.kt deleted file mode 100644 index 49d37c3f..00000000 --- a/modules/openid-federation-client/src/jsTest/kotlin/com/sphereon/oid/fed/client/crypto/CryptoTest.js.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.sphereon.oid.fed.client.crypto - -import com.sphereon.oid.fed.openapi.models.Jwk -import kotlinx.coroutines.await -import kotlinx.coroutines.test.runTest -import kotlin.js.Promise -import kotlin.test.Test -import kotlin.test.assertEquals - -@JsModule("jose") -@JsNonModule -external object Jose { - fun importJWK(jwk: Jwk, alg: String, options: dynamic = definedExternally): Promise - fun jwtVerify(jwt: String, key: Any, options: dynamic): Promise -} - -class CryptoTest { - private val cryptoService = CryptoServiceJS(CryptoPlatformCallback()) - - @Test - fun testVerifyValidJwt() = runTest { - val jwt = - "eyJraWQiOiJkZWZhdWx0UlNBU2lnbiIsInR5cCI6ImVudGl0eS1zdGF0ZW1lbnQrand0IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdCIsIm1ldGFkYXRhIjp7ImZlZGVyYXRpb25fZW50aXR5Ijp7ImZlZGVyYXRpb25fZmV0Y2hfZW5kcG9pbnQiOiJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9mZXRjaCIsImZlZGVyYXRpb25fcmVzb2x2ZV9lbmRwb2ludCI6Imh0dHBzOi8vb2lkYy5yZWdpc3RyeS5zZXJ2aXppY2llLmludGVybm8uZ292Lml0L3Jlc29sdmUiLCJmZWRlcmF0aW9uX3RydXN0X21hcmtfc3RhdHVzX2VuZHBvaW50IjoiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQvdHJ1c3RfbWFya19zdGF0dXMiLCJmZWRlcmF0aW9uX2xpc3RfZW5kcG9pbnQiOiJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9saXN0In19LCJqd2tzIjp7ImtleXMiOlt7Imt0eSI6IlJTQSIsImUiOiJBUUFCIiwidXNlIjoic2lnIiwia2lkIjoiZGVmYXVsdFJTQVNpZ24iLCJuIjoicVJUSkhRZ2IyZjhjbG45ZEpiLVdnaWs0cUVMNUdHX19zUHpsQVU0aTY5UzZ5SHhlTWczMllnTGZVenBOQnhfOGtYMm5kellYTV9SS21vM2poalF4dXhDSzFJSFNRY01rZzFoR2lpLXhSdzh4NDV0OFNHbFdjU0hpN182UmFBWTFTeUZjRUVsTkFxSGk1b2VCYUIzRkd2ZnJWLUVQLWNOa1V2R0VWYnlzX0NieHlHRFE5UU0wTkVyc2lsVmxNQVJERXJFTlpjclkwck5LdDUyV29aZ3kzcHNWY2Q4VTVEMExxZkM3N2JQakczNVBhVmh3WUFubFAwZXowSGY2dHV5V0pIZUE1MmRDZGUtbmEzV2ptUGFya2NscEZyLUtqWGVJQzhCd2ZqRXBBWGJLY3A4Tm11UUZqOWZEOUtuUjZ2Q2RPOTFSeUJJYkRsdUw1TEg4czBxRENRIn0seyJrdHkiOiJFQyIsInVzZSI6InNpZyIsImNydiI6IlAtMjU2Iiwia2lkIjoiZGVmYXVsdEVDU2lnbiIsIngiOiJ4TWtXSWExRVp5amdtazNKUUx0SERBOXAwVHBQOXdNU2JKSzBvQWl0Z2NrIiwieSI6IkNWTEZzdE93S3d0UXJ1dF92b0hqWU82SnoxSzBOWFJ1OE9MQ1RtS29zTGcifSx7Imt0eSI6IlJTQSIsImUiOiJBUUFCIiwidXNlIjoiZW5jIiwia2lkIjoiZGVmYXVsdFJTQUVuYyIsIm4iOiJ3ZXcyMnhjcGZBU2tRUXA3U09vX0dzNmNLajJYeTd4VlpLX3RnWnh6QXlReExTeG01c1U0WkdzNm1kSUFIZEV2UTkxU25FSFR0anBlQVM5d0N2TlhWbVZ4TklqRkFQSnpDWXBzZkZ4R3pXMVBSM1NDQmVLUFl6VWpTeUJTZWw1LW1Td1U4MHlZQXFPbFoxUVJaTlFJNUVTVXZOUG9lUEZqR0NvZnhuRlJzbXF5X21Bd1p5bmQyTnJyc1QyQXlwMEw2UFF3ei1Fa09oakVCcHpzeXEwcE11am5aRWZ2UHk5UC1YdjJTVUZMZUpQcm1jRHllNjRaMlk5V1BoMmpwa25oT3hESzhSTUwtMllUdmI0dVNPalowWFpPVzltVm9nTkpSSm0yemVQVGVlTFBxR2x1TGNEenBsYnkwbkxiTGpkWDdLM29MYnFoRGFld2o3VnJhS2Vtc1EifV19LCJ0cnVzdF9tYXJrX2lzc3VlcnMiOnsiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQvb2F1dGhfcmVzb3VyY2UvcHJpdmF0ZSI6WyJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdCJdLCJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9vcGVuaWRfcHJvdmlkZXIvcHJpdmF0ZSI6WyJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdCJdLCJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9vYXV0aF9yZXNvdXJjZS9wdWJsaWMiOlsiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQiXSwiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQvaW50ZXJtZWRpYXRlL3B1YmxpYyI6WyJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdCJdLCJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9vcGVuaWRfcmVseWluZ19wYXJ0eS9wdWJsaWMiOlsiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQiLCJodHRwczovL2NvaGVzaW9uMi5yZWdpb25lLm1hcmNoZS5pdC9vaWRjL3NhLyIsImh0dHBzOi8vYXV0aC50b3NjYW5hLml0L2F1dGgvcmVhbG1zL2VudGkvZmVkZXJhdGlvbi1lbnRpdHkvcl90b3NjYW5fc2FfZW50aSIsImh0dHBzOi8vYXV0ZW50aWNhemlvbmUuY2xvdWQucHJvdmluY2lhLnRuLml0L2FnZ3JlZ2F0b3JlIiwiaHR0cHM6Ly9vaWRjc2Eud2VibG9vbS5pdCIsImh0dHBzOi8vc3BpZC53YnNzLml0L1NwaWQvb2lkYy9zYSIsImh0dHBzOi8vc2VjdXJlLmVyZW1pbmQuaXQvaWRlbnRpdGEtZGlnaXRhbGUtb2lkYy9vaWRjLWZlZCIsImh0dHBzOi8vY2llLW9pZGMuY29tdW5lLW9ubGluZS5pdC9BdXRoU2VydmljZU9JREMvb2lkYy9zYSIsImh0dHBzOi8vcGhwLWNpZS5hbmR4b3IuaXQiLCJodHRwczovL2xvZ2luLmFzZndlYi5pdC8iLCJodHRwczovL29pZGMuc3R1ZGlvYW1pY2EuY29tIiwiaHR0cHM6Ly9pZHAuZW50cmFuZXh0Lml0L3NlcnZpY2VzL29pZGMvc2Evc3NvIiwiaHR0cHM6Ly9jd29sc3NvLm51dm9sYXBhbGl0YWxzb2Z0Lml0L3NlcnZpY2VzL29pZGMvc2Evc3NvIiwiaHR0cHM6Ly9mZWRlcmEubGVwaWRhLml0L2d3L09pZGNTYUZ1bGwvIiwiaHR0cHM6Ly93d3cuZXVyb2NvbnRhYi5pdC9hcGkiXSwiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQvaW50ZXJtZWRpYXRlL3ByaXZhdGUiOlsiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQiXSwiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQvb3BlbmlkX3Byb3ZpZGVyL3B1YmxpYyI6WyJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdCJdLCJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9vcGVuaWRfcmVseWluZ19wYXJ0eS9wcml2YXRlIjpbImh0dHBzOi8vb2lkYy5yZWdpc3RyeS5zZXJ2aXppY2llLmludGVybm8uZ292Lml0IiwiaHR0cHM6Ly9vaWRjc2Eud2VibG9vbS5pdCIsImh0dHBzOi8vc3BpZC53YnNzLml0L1NwaWQvb2lkYy9zYSIsImh0dHBzOi8vc2VjdXJlLmVyZW1pbmQuaXQvaWRlbnRpdGEtZGlnaXRhbGUtb2lkYy9vaWRjLWZlZCIsImh0dHBzOi8vY2llLW9pZGMuY29tdW5lLW9ubGluZS5pdC9BdXRoU2VydmljZU9JREMvb2lkYy9zYSIsImh0dHBzOi8vcGhwLWNpZS5hbmR4b3IuaXQiLCJodHRwczovL2xvZ2luLmFzZndlYi5pdC8iLCJodHRwczovL29pZGMuc3R1ZGlvYW1pY2EuY29tIiwiaHR0cHM6Ly9pZHAuZW50cmFuZXh0Lml0L3NlcnZpY2VzL29pZGMvc2Evc3NvIiwiaHR0cHM6Ly9jd29sc3NvLm51dm9sYXBhbGl0YWxzb2Z0Lml0L3NlcnZpY2VzL29pZGMvc2Evc3NvIiwiaHR0cHM6Ly9mZWRlcmEubGVwaWRhLml0L2d3L09pZGNTYUZ1bGwvIiwiaHR0cHM6Ly93d3cuZXVyb2NvbnRhYi5pdC9hcGkiXX0sImlzcyI6Imh0dHBzOi8vb2lkYy5yZWdpc3RyeS5zZXJ2aXppY2llLmludGVybm8uZ292Lml0IiwiZXhwIjoxNzI5MTAzMjQxLCJpYXQiOjE3MjkwMTY4NDEsImNvbnN0cmFpbnRzIjp7Im1heF9wYXRoX2xlbmd0aCI6MX0sInRydXN0X21hcmtzX2lzc3VlcnMiOnsiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQvb2F1dGhfcmVzb3VyY2UvcHJpdmF0ZSI6WyJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdCJdLCJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9vcGVuaWRfcHJvdmlkZXIvcHJpdmF0ZSI6WyJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdCJdLCJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9vYXV0aF9yZXNvdXJjZS9wdWJsaWMiOlsiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQiXSwiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQvaW50ZXJtZWRpYXRlL3B1YmxpYyI6WyJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdCJdLCJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9vcGVuaWRfcmVseWluZ19wYXJ0eS9wdWJsaWMiOlsiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQiLCJodHRwczovL2NvaGVzaW9uMi5yZWdpb25lLm1hcmNoZS5pdC9vaWRjL3NhLyIsImh0dHBzOi8vYXV0aC50b3NjYW5hLml0L2F1dGgvcmVhbG1zL2VudGkvZmVkZXJhdGlvbi1lbnRpdHkvcl90b3NjYW5fc2FfZW50aSIsImh0dHBzOi8vYXV0ZW50aWNhemlvbmUuY2xvdWQucHJvdmluY2lhLnRuLml0L2FnZ3JlZ2F0b3JlIiwiaHR0cHM6Ly9vaWRjc2Eud2VibG9vbS5pdCIsImh0dHBzOi8vc3BpZC53YnNzLml0L1NwaWQvb2lkYy9zYSIsImh0dHBzOi8vc2VjdXJlLmVyZW1pbmQuaXQvaWRlbnRpdGEtZGlnaXRhbGUtb2lkYy9vaWRjLWZlZCIsImh0dHBzOi8vY2llLW9pZGMuY29tdW5lLW9ubGluZS5pdC9BdXRoU2VydmljZU9JREMvb2lkYy9zYSIsImh0dHBzOi8vcGhwLWNpZS5hbmR4b3IuaXQiLCJodHRwczovL2xvZ2luLmFzZndlYi5pdC8iLCJodHRwczovL29pZGMuc3R1ZGlvYW1pY2EuY29tIiwiaHR0cHM6Ly9pZHAuZW50cmFuZXh0Lml0L3NlcnZpY2VzL29pZGMvc2Evc3NvIiwiaHR0cHM6Ly9jd29sc3NvLm51dm9sYXBhbGl0YWxzb2Z0Lml0L3NlcnZpY2VzL29pZGMvc2Evc3NvIiwiaHR0cHM6Ly9mZWRlcmEubGVwaWRhLml0L2d3L09pZGNTYUZ1bGwvIiwiaHR0cHM6Ly93d3cuZXVyb2NvbnRhYi5pdC9hcGkiXSwiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQvaW50ZXJtZWRpYXRlL3ByaXZhdGUiOlsiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQiXSwiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQvb3BlbmlkX3Byb3ZpZGVyL3B1YmxpYyI6WyJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdCJdLCJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9vcGVuaWRfcmVseWluZ19wYXJ0eS9wcml2YXRlIjpbImh0dHBzOi8vb2lkYy5yZWdpc3RyeS5zZXJ2aXppY2llLmludGVybm8uZ292Lml0IiwiaHR0cHM6Ly9vaWRjc2Eud2VibG9vbS5pdCIsImh0dHBzOi8vc3BpZC53YnNzLml0L1NwaWQvb2lkYy9zYSIsImh0dHBzOi8vc2VjdXJlLmVyZW1pbmQuaXQvaWRlbnRpdGEtZGlnaXRhbGUtb2lkYy9vaWRjLWZlZCIsImh0dHBzOi8vY2llLW9pZGMuY29tdW5lLW9ubGluZS5pdC9BdXRoU2VydmljZU9JREMvb2lkYy9zYSIsImh0dHBzOi8vcGhwLWNpZS5hbmR4b3IuaXQiLCJodHRwczovL2xvZ2luLmFzZndlYi5pdC8iLCJodHRwczovL29pZGMuc3R1ZGlvYW1pY2EuY29tIiwiaHR0cHM6Ly9pZHAuZW50cmFuZXh0Lml0L3NlcnZpY2VzL29pZGMvc2Evc3NvIiwiaHR0cHM6Ly9jd29sc3NvLm51dm9sYXBhbGl0YWxzb2Z0Lml0L3NlcnZpY2VzL29pZGMvc2Evc3NvIiwiaHR0cHM6Ly9mZWRlcmEubGVwaWRhLml0L2d3L09pZGNTYUZ1bGwvIiwiaHR0cHM6Ly93d3cuZXVyb2NvbnRhYi5pdC9hcGkiXX19.j9bmJRlLokkmWTpPkrphtB5dyVrKQPwY7U9jtl_PXlgVODmDXla0vbQszR_b0aUfk7j-Sh5v_UwtHRF6P5vPcaTvUiaPcbtFEIVq0xW9xcyjgPmYEkfHyB9CWxfq-AC6OOoRunGyTOO5G9xdup6QSLFxLQBlMZh5sE_X8wzkG02dZOfl8RTzuoquzNMl-yWpyb0Rxk_iY-ZhGa1yDPHm16tFmXMY3sf0QOBQAAGxBaRhcjekRnXPEijrPIaV381_VnQdd4xtbikI_XNRiGeyuoMii40K4l6qiznZ-_mz8GaRdS21Dc5XL5cjwMc4EDGxSNnW9NgBr7R4HDURyiixcA" - - val key = getKeyFromJwt(jwt) - - val result = cryptoService.verify(jwt, key).await() - assertEquals(true, result) - } - - @Test - fun testVerifyValidJwtExpired() = runTest { - val jwt = - "eyJraWQiOiJkZWZhdWx0UlNBU2lnbiIsInR5cCI6ImVudGl0eS1zdGF0ZW1lbnQrand0IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdCIsIm1ldGFkYXRhIjp7ImZlZGVyYXRpb25fZW50aXR5Ijp7ImZlZGVyYXRpb25fZmV0Y2hfZW5kcG9pbnQiOiJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9mZXRjaCIsImZlZGVyYXRpb25fcmVzb2x2ZV9lbmRwb2ludCI6Imh0dHBzOi8vb2lkYy5yZWdpc3RyeS5zZXJ2aXppY2llLmludGVybm8uZ292Lml0L3Jlc29sdmUiLCJmZWRlcmF0aW9uX3RydXN0X21hcmtfc3RhdHVzX2VuZHBvaW50IjoiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQvdHJ1c3RfbWFya19zdGF0dXMiLCJmZWRlcmF0aW9uX2xpc3RfZW5kcG9pbnQiOiJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9saXN0In19LCJqd2tzIjp7ImtleXMiOlt7Imt0eSI6IlJTQSIsImUiOiJBUUFCIiwidXNlIjoic2lnIiwia2lkIjoiZGVmYXVsdFJTQVNpZ24iLCJuIjoicVJUSkhRZ2IyZjhjbG45ZEpiLVdnaWs0cUVMNUdHX19zUHpsQVU0aTY5UzZ5SHhlTWczMllnTGZVenBOQnhfOGtYMm5kellYTV9SS21vM2poalF4dXhDSzFJSFNRY01rZzFoR2lpLXhSdzh4NDV0OFNHbFdjU0hpN182UmFBWTFTeUZjRUVsTkFxSGk1b2VCYUIzRkd2ZnJWLUVQLWNOa1V2R0VWYnlzX0NieHlHRFE5UU0wTkVyc2lsVmxNQVJERXJFTlpjclkwck5LdDUyV29aZ3kzcHNWY2Q4VTVEMExxZkM3N2JQakczNVBhVmh3WUFubFAwZXowSGY2dHV5V0pIZUE1MmRDZGUtbmEzV2ptUGFya2NscEZyLUtqWGVJQzhCd2ZqRXBBWGJLY3A4Tm11UUZqOWZEOUtuUjZ2Q2RPOTFSeUJJYkRsdUw1TEg4czBxRENRIn0seyJrdHkiOiJFQyIsInVzZSI6InNpZyIsImNydiI6IlAtMjU2Iiwia2lkIjoiZGVmYXVsdEVDU2lnbiIsIngiOiJ4TWtXSWExRVp5amdtazNKUUx0SERBOXAwVHBQOXdNU2JKSzBvQWl0Z2NrIiwieSI6IkNWTEZzdE93S3d0UXJ1dF92b0hqWU82SnoxSzBOWFJ1OE9MQ1RtS29zTGcifSx7Imt0eSI6IlJTQSIsImUiOiJBUUFCIiwidXNlIjoiZW5jIiwia2lkIjoiZGVmYXVsdFJTQUVuYyIsIm4iOiJ3ZXcyMnhjcGZBU2tRUXA3U09vX0dzNmNLajJYeTd4VlpLX3RnWnh6QXlReExTeG01c1U0WkdzNm1kSUFIZEV2UTkxU25FSFR0anBlQVM5d0N2TlhWbVZ4TklqRkFQSnpDWXBzZkZ4R3pXMVBSM1NDQmVLUFl6VWpTeUJTZWw1LW1Td1U4MHlZQXFPbFoxUVJaTlFJNUVTVXZOUG9lUEZqR0NvZnhuRlJzbXF5X21Bd1p5bmQyTnJyc1QyQXlwMEw2UFF3ei1Fa09oakVCcHpzeXEwcE11am5aRWZ2UHk5UC1YdjJTVUZMZUpQcm1jRHllNjRaMlk5V1BoMmpwa25oT3hESzhSTUwtMllUdmI0dVNPalowWFpPVzltVm9nTkpSSm0yemVQVGVlTFBxR2x1TGNEenBsYnkwbkxiTGpkWDdLM29MYnFoRGFld2o3VnJhS2Vtc1EifV19LCJ0cnVzdF9tYXJrX2lzc3VlcnMiOnsiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQvb2F1dGhfcmVzb3VyY2UvcHJpdmF0ZSI6WyJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdCJdLCJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9vcGVuaWRfcHJvdmlkZXIvcHJpdmF0ZSI6WyJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdCJdLCJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9vYXV0aF9yZXNvdXJjZS9wdWJsaWMiOlsiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQiXSwiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQvaW50ZXJtZWRpYXRlL3B1YmxpYyI6WyJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdCJdLCJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9vcGVuaWRfcmVseWluZ19wYXJ0eS9wdWJsaWMiOlsiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQiLCJodHRwczovL2NvaGVzaW9uMi5yZWdpb25lLm1hcmNoZS5pdC9vaWRjL3NhLyIsImh0dHBzOi8vYXV0aC50b3NjYW5hLml0L2F1dGgvcmVhbG1zL2VudGkvZmVkZXJhdGlvbi1lbnRpdHkvcl90b3NjYW5fc2FfZW50aSIsImh0dHBzOi8vYXV0ZW50aWNhemlvbmUuY2xvdWQucHJvdmluY2lhLnRuLml0L2FnZ3JlZ2F0b3JlIiwiaHR0cHM6Ly9vaWRjc2Eud2VibG9vbS5pdCIsImh0dHBzOi8vc3BpZC53YnNzLml0L1NwaWQvb2lkYy9zYSIsImh0dHBzOi8vc2VjdXJlLmVyZW1pbmQuaXQvaWRlbnRpdGEtZGlnaXRhbGUtb2lkYy9vaWRjLWZlZCIsImh0dHBzOi8vY2llLW9pZGMuY29tdW5lLW9ubGluZS5pdC9BdXRoU2VydmljZU9JREMvb2lkYy9zYSIsImh0dHBzOi8vcGhwLWNpZS5hbmR4b3IuaXQiLCJodHRwczovL2xvZ2luLmFzZndlYi5pdC8iLCJodHRwczovL29pZGMuc3R1ZGlvYW1pY2EuY29tIiwiaHR0cHM6Ly9pZHAuZW50cmFuZXh0Lml0L3NlcnZpY2VzL29pZGMvc2Evc3NvIiwiaHR0cHM6Ly9jd29sc3NvLm51dm9sYXBhbGl0YWxzb2Z0Lml0L3NlcnZpY2VzL29pZGMvc2Evc3NvIiwiaHR0cHM6Ly9mZWRlcmEubGVwaWRhLml0L2d3L09pZGNTYUZ1bGwvIiwiaHR0cHM6Ly93d3cuZXVyb2NvbnRhYi5pdC9hcGkiXSwiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQvaW50ZXJtZWRpYXRlL3ByaXZhdGUiOlsiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQiXSwiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQvb3BlbmlkX3Byb3ZpZGVyL3B1YmxpYyI6WyJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdCJdLCJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9vcGVuaWRfcmVseWluZ19wYXJ0eS9wcml2YXRlIjpbImh0dHBzOi8vb2lkYy5yZWdpc3RyeS5zZXJ2aXppY2llLmludGVybm8uZ292Lml0IiwiaHR0cHM6Ly9vaWRjc2Eud2VibG9vbS5pdCIsImh0dHBzOi8vc3BpZC53YnNzLml0L1NwaWQvb2lkYy9zYSIsImh0dHBzOi8vc2VjdXJlLmVyZW1pbmQuaXQvaWRlbnRpdGEtZGlnaXRhbGUtb2lkYy9vaWRjLWZlZCIsImh0dHBzOi8vY2llLW9pZGMuY29tdW5lLW9ubGluZS5pdC9BdXRoU2VydmljZU9JREMvb2lkYy9zYSIsImh0dHBzOi8vcGhwLWNpZS5hbmR4b3IuaXQiLCJodHRwczovL2xvZ2luLmFzZndlYi5pdC8iLCJodHRwczovL29pZGMuc3R1ZGlvYW1pY2EuY29tIiwiaHR0cHM6Ly9pZHAuZW50cmFuZXh0Lml0L3NlcnZpY2VzL29pZGMvc2Evc3NvIiwiaHR0cHM6Ly9jd29sc3NvLm51dm9sYXBhbGl0YWxzb2Z0Lml0L3NlcnZpY2VzL29pZGMvc2Evc3NvIiwiaHR0cHM6Ly9mZWRlcmEubGVwaWRhLml0L2d3L09pZGNTYUZ1bGwvIiwiaHR0cHM6Ly93d3cuZXVyb2NvbnRhYi5pdC9hcGkiXX0sImlzcyI6Imh0dHBzOi8vb2lkYy5yZWdpc3RyeS5zZXJ2aXppY2llLmludGVybm8uZ292Lml0IiwiZXhwIjoxNzI4NDI4MDI1LCJpYXQiOjE3MjgzNDE2MjUsImNvbnN0cmFpbnRzIjp7Im1heF9wYXRoX2xlbmd0aCI6MX0sInRydXN0X21hcmtzX2lzc3VlcnMiOnsiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQvb2F1dGhfcmVzb3VyY2UvcHJpdmF0ZSI6WyJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdCJdLCJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9vcGVuaWRfcHJvdmlkZXIvcHJpdmF0ZSI6WyJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdCJdLCJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9vYXV0aF9yZXNvdXJjZS9wdWJsaWMiOlsiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQiXSwiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQvaW50ZXJtZWRpYXRlL3B1YmxpYyI6WyJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdCJdLCJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9vcGVuaWRfcmVseWluZ19wYXJ0eS9wdWJsaWMiOlsiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQiLCJodHRwczovL2NvaGVzaW9uMi5yZWdpb25lLm1hcmNoZS5pdC9vaWRjL3NhLyIsImh0dHBzOi8vYXV0aC50b3NjYW5hLml0L2F1dGgvcmVhbG1zL2VudGkvZmVkZXJhdGlvbi1lbnRpdHkvcl90b3NjYW5fc2FfZW50aSIsImh0dHBzOi8vYXV0ZW50aWNhemlvbmUuY2xvdWQucHJvdmluY2lhLnRuLml0L2FnZ3JlZ2F0b3JlIiwiaHR0cHM6Ly9vaWRjc2Eud2VibG9vbS5pdCIsImh0dHBzOi8vc3BpZC53YnNzLml0L1NwaWQvb2lkYy9zYSIsImh0dHBzOi8vc2VjdXJlLmVyZW1pbmQuaXQvaWRlbnRpdGEtZGlnaXRhbGUtb2lkYy9vaWRjLWZlZCIsImh0dHBzOi8vY2llLW9pZGMuY29tdW5lLW9ubGluZS5pdC9BdXRoU2VydmljZU9JREMvb2lkYy9zYSIsImh0dHBzOi8vcGhwLWNpZS5hbmR4b3IuaXQiLCJodHRwczovL2xvZ2luLmFzZndlYi5pdC8iLCJodHRwczovL29pZGMuc3R1ZGlvYW1pY2EuY29tIiwiaHR0cHM6Ly9pZHAuZW50cmFuZXh0Lml0L3NlcnZpY2VzL29pZGMvc2Evc3NvIiwiaHR0cHM6Ly9jd29sc3NvLm51dm9sYXBhbGl0YWxzb2Z0Lml0L3NlcnZpY2VzL29pZGMvc2Evc3NvIiwiaHR0cHM6Ly9mZWRlcmEubGVwaWRhLml0L2d3L09pZGNTYUZ1bGwvIiwiaHR0cHM6Ly93d3cuZXVyb2NvbnRhYi5pdC9hcGkiXSwiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQvaW50ZXJtZWRpYXRlL3ByaXZhdGUiOlsiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQiXSwiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQvb3BlbmlkX3Byb3ZpZGVyL3B1YmxpYyI6WyJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdCJdLCJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9vcGVuaWRfcmVseWluZ19wYXJ0eS9wcml2YXRlIjpbImh0dHBzOi8vb2lkYy5yZWdpc3RyeS5zZXJ2aXppY2llLmludGVybm8uZ292Lml0IiwiaHR0cHM6Ly9vaWRjc2Eud2VibG9vbS5pdCIsImh0dHBzOi8vc3BpZC53YnNzLml0L1NwaWQvb2lkYy9zYSIsImh0dHBzOi8vc2VjdXJlLmVyZW1pbmQuaXQvaWRlbnRpdGEtZGlnaXRhbGUtb2lkYy9vaWRjLWZlZCIsImh0dHBzOi8vY2llLW9pZGMuY29tdW5lLW9ubGluZS5pdC9BdXRoU2VydmljZU9JREMvb2lkYy9zYSIsImh0dHBzOi8vcGhwLWNpZS5hbmR4b3IuaXQiLCJodHRwczovL2xvZ2luLmFzZndlYi5pdC8iLCJodHRwczovL29pZGMuc3R1ZGlvYW1pY2EuY29tIiwiaHR0cHM6Ly9pZHAuZW50cmFuZXh0Lml0L3NlcnZpY2VzL29pZGMvc2Evc3NvIiwiaHR0cHM6Ly9jd29sc3NvLm51dm9sYXBhbGl0YWxzb2Z0Lml0L3NlcnZpY2VzL29pZGMvc2Evc3NvIiwiaHR0cHM6Ly9mZWRlcmEubGVwaWRhLml0L2d3L09pZGNTYUZ1bGwvIiwiaHR0cHM6Ly93d3cuZXVyb2NvbnRhYi5pdC9hcGkiXX19.QVndoAzYG4-r-f1mq2szTurjN4IWG5GN6aUBeIm6k5EXOdjEa2oOmP8iANBjCFWF6eNPNN2t342pBpb6-46o9kJv9MxyWASIaBkOv_X8RJGEgv2ghDLLnfOLv4R6J9XH9IIsQPzjlezgWJYk61ukfYN7kWA_aIT5Hf42zEU14V5kLbl50r8wjgJVRwmSBsDLKsWbOnbzfkiKv4druFhfhDZjiyBeCjYajh9MFYdAR1awYihNM-JVib89Z7XgOqxq4qGogPt_XU-YMuf917lw4kpphPRoUe1QIoj1KXfgbpJUdgiLMlXQoBl57Ej3b1mVWgEkC6oKjNyNvZR57Kx8AQ" - - val key = getKeyFromJwt(jwt) - val result = cryptoService.verify(jwt, key).await() - assertEquals(false, result) - } - - @Test - fun testVerifyInvalidSignature() = runTest { - val jwt = - "eyJraWQiOiJkZWZhdWx0UlNBU2lnbiIsInR5cCI6ImVudGl0eS1zdGF0ZW1lbnQrand0IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdCIsIm1ldGFkYXRhIjp7ImZlZGVyYXRpb25fZW50aXR5Ijp7ImZlZGVyYXRpb25fZmV0Y2hfZW5kcG9pbnQiOiJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9mZXRjaCIsImZlZGVyYXRpb25fcmVzb2x2ZV9lbmRwb2ludCI6Imh0dHBzOi8vb2lkYy5yZWdpc3RyeS5zZXJ2aXppY2llLmludGVybm8uZ292Lml0L3Jlc29sdmUiLCJmZWRlcmF0aW9uX3RydXN0X21hcmtfc3RhdHVzX2VuZHBvaW50IjoiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQvdHJ1c3RfbWFya19zdGF0dXMiLCJmZWRlcmF0aW9uX2xpc3RfZW5kcG9pbnQiOiJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9saXN0In19LCJqd2tzIjp7ImtleXMiOlt7Imt0eSI6IlJTQSIsImUiOiJBUUFCIiwidXNlIjoic2lnIiwia2lkIjoiZGVmYXVsdFJTQVNpZ24iLCJuIjoicVJUSkhRZ2IyZjhjbG45ZEpiLVdnaWs0cUVMNUdHX19zUHpsQVU0aTY5UzZ5SHhlTWczMllnTGZVenBOQnhfOGtYMm5kellYTV9SS21vM2poalF4dXhDSzFJSFNRY01rZzFoR2lpLXhSdzh4NDV0OFNHbFdjU0hpN182UmFBWTFTeUZjRUVsTkFxSGk1b2VCYUIzRkd2ZnJWLUVQLWNOa1V2R0VWYnlzX0NieHlHRFE5UU0wTkVyc2lsVmxNQVJERXJFTlpjclkwck5LdDUyV29aZ3kzcHNWY2Q4VTVEMExxZkM3N2JQakczNVBhVmh3WUFubFAwZXowSGY2dHV5V0pIZUE1MmRDZGUtbmEzV2ptUGFya2NscEZyLUtqWGVJQzhCd2ZqRXBBWGJLY3A4Tm11UUZqOWZEOUtuUjZ2Q2RPOTFSeUJJYkRsdUw1TEg4czBxRENRIn0seyJrdHkiOiJFQyIsInVzZSI6InNpZyIsImNydiI6IlAtMjU2Iiwia2lkIjoiZGVmYXVsdEVDU2lnbiIsIngiOiJ4TWtXSWExRVp5amdtazNKUUx0SERBOXAwVHBQOXdNU2JKSzBvQWl0Z2NrIiwieSI6IkNWTEZzdE93S3d0UXJ1dF92b0hqWU82SnoxSzBOWFJ1OE9MQ1RtS29zTGcifSx7Imt0eSI6IlJTQSIsImUiOiJBUUFCIiwidXNlIjoiZW5jIiwia2lkIjoiZGVmYXVsdFJTQUVuYyIsIm4iOiJ3ZXcyMnhjcGZBU2tRUXA3U09vX0dzNmNLajJYeTd4VlpLX3RnWnh6QXlReExTeG01c1U0WkdzNm1kSUFIZEV2UTkxU25FSFR0anBlQVM5d0N2TlhWbVZ4TklqRkFQSnpDWXBzZkZ4R3pXMVBSM1NDQmVLUFl6VWpTeUJTZWw1LW1Td1U4MHlZQXFPbFoxUVJaTlFJNUVTVXZOUG9lUEZqR0NvZnhuRlJzbXF5X21Bd1p5bmQyTnJyc1QyQXlwMEw2UFF3ei1Fa09oakVCcHpzeXEwcE11am5aRWZ2UHk5UC1YdjJTVUZMZUpQcm1jRHllNjRaMlk5V1BoMmpwa25oT3hESzhSTUwtMllUdmI0dVNPalowWFpPVzltVm9nTkpSSm0yemVQVGVlTFBxR2x1TGNEenBsYnkwbkxiTGpkWDdLM29MYnFoRGFld2o3VnJhS2Vtc1EifV19LCJ0cnVzdF9tYXJrX2lzc3VlcnMiOnsiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQvb2F1dGhfcmVzb3VyY2UvcHJpdmF0ZSI6WyJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdCJdLCJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9vcGVuaWRfcHJvdmlkZXIvcHJpdmF0ZSI6WyJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdCJdLCJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9vYXV0aF9yZXNvdXJjZS9wdWJsaWMiOlsiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQiXSwiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQvaW50ZXJtZWRpYXRlL3B1YmxpYyI6WyJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdCJdLCJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9vcGVuaWRfcmVseWluZ19wYXJ0eS9wdWJsaWMiOlsiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQiLCJodHRwczovL2NvaGVzaW9uMi5yZWdpb25lLm1hcmNoZS5pdC9vaWRjL3NhLyIsImh0dHBzOi8vYXV0aC50b3NjYW5hLml0L2F1dGgvcmVhbG1zL2VudGkvZmVkZXJhdGlvbi1lbnRpdHkvcl90b3NjYW5fc2FfZW50aSIsImh0dHBzOi8vYXV0ZW50aWNhemlvbmUuY2xvdWQucHJvdmluY2lhLnRuLml0L2FnZ3JlZ2F0b3JlIiwiaHR0cHM6Ly9vaWRjc2Eud2VibG9vbS5pdCIsImh0dHBzOi8vc3BpZC53YnNzLml0L1NwaWQvb2lkYy9zYSIsImh0dHBzOi8vc2VjdXJlLmVyZW1pbmQuaXQvaWRlbnRpdGEtZGlnaXRhbGUtb2lkYy9vaWRjLWZlZCIsImh0dHBzOi8vY2llLW9pZGMuY29tdW5lLW9ubGluZS5pdC9BdXRoU2VydmljZU9JREMvb2lkYy9zYSIsImh0dHBzOi8vcGhwLWNpZS5hbmR4b3IuaXQiLCJodHRwczovL2xvZ2luLmFzZndlYi5pdC8iLCJodHRwczovL29pZGMuc3R1ZGlvYW1pY2EuY29tIiwiaHR0cHM6Ly9pZHAuZW50cmFuZXh0Lml0L3NlcnZpY2VzL29pZGMvc2Evc3NvIiwiaHR0cHM6Ly9jd29sc3NvLm51dm9sYXBhbGl0YWxzb2Z0Lml0L3NlcnZpY2VzL29pZGMvc2Evc3NvIiwiaHR0cHM6Ly9mZWRlcmEubGVwaWRhLml0L2d3L09pZGNTYUZ1bGwvIiwiaHR0cHM6Ly93d3cuZXVyb2NvbnRhYi5pdC9hcGkiXSwiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQvaW50ZXJtZWRpYXRlL3ByaXZhdGUiOlsiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQiXSwiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQvb3BlbmlkX3Byb3ZpZGVyL3B1YmxpYyI6WyJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdCJdLCJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9vcGVuaWRfcmVseWluZ19wYXJ0eS9wcml2YXRlIjpbImh0dHBzOi8vb2lkYy5yZWdpc3RyeS5zZXJ2aXppY2llLmludGVybm8uZ292Lml0IiwiaHR0cHM6Ly9vaWRjc2Eud2VibG9vbS5pdCIsImh0dHBzOi8vc3BpZC53YnNzLml0L1NwaWQvb2lkYy9zYSIsImh0dHBzOi8vc2VjdXJlLmVyZW1pbmQuaXQvaWRlbnRpdGEtZGlnaXRhbGUtb2lkYy9vaWRjLWZlZCIsImh0dHBzOi8vY2llLW9pZGMuY29tdW5lLW9ubGluZS5pdC9BdXRoU2VydmljZU9JREMvb2lkYy9zYSIsImh0dHBzOi8vcGhwLWNpZS5hbmR4b3IuaXQiLCJodHRwczovL2xvZ2luLmFzZndlYi5pdC8iLCJodHRwczovL29pZGMuc3R1ZGlvYW1pY2EuY29tIiwiaHR0cHM6Ly9pZHAuZW50cmFuZXh0Lml0L3NlcnZpY2VzL29pZGMvc2Evc3NvIiwiaHR0cHM6Ly9jd29sc3NvLm51dm9sYXBhbGl0YWxzb2Z0Lml0L3NlcnZpY2VzL29pZGMvc2Evc3NvIiwiaHR0cHM6Ly9mZWRlcmEubGVwaWRhLml0L2d3L09pZGNTYUZ1bGwvIiwiaHR0cHM6Ly93d3cuZXVyb2NvbnRhYi5pdC9hcGkiXX0sImlzcyI6Imh0dHBzOi8vb2lkYy5yZWdpc3RyeS5zZXJ2aXppY2llLmludGVybm8uZ292Lml0IiwiZXhwIjoxNzI5MTAzMjQxLCJpYXQiOjE3MjkwMTY4NDEsImNvbnN0cmFpbnRzIjp7Im1heF9wYXRoX2xlbmd0aCI6MX0sInRydXN0X21hcmtzX2lzc3VlcnMiOnsiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQvb2F1dGhfcmVzb3VyY2UvcHJpdmF0ZSI6WyJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdCJdLCJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9vcGVuaWRfcHJvdmlkZXIvcHJpdmF0ZSI6WyJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdCJdLCJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9vYXV0aF9yZXNvdXJjZS9wdWJsaWMiOlsiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQiXSwiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQvaW50ZXJtZWRpYXRlL3B1YmxpYyI6WyJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdCJdLCJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9vcGVuaWRfcmVseWluZ19wYXJ0eS9wdWJsaWMiOlsiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQiLCJodHRwczovL2NvaGVzaW9uMi5yZWdpb25lLm1hcmNoZS5pdC9vaWRjL3NhLyIsImh0dHBzOi8vYXV0aC50b3NjYW5hLml0L2F1dGgvcmVhbG1zL2VudGkvZmVkZXJhdGlvbi1lbnRpdHkvcl90b3NjYW5fc2FfZW50aSIsImh0dHBzOi8vYXV0ZW50aWNhemlvbmUuY2xvdWQucHJvdmluY2lhLnRuLml0L2FnZ3JlZ2F0b3JlIiwiaHR0cHM6Ly9vaWRjc2Eud2VibG9vbS5pdCIsImh0dHBzOi8vc3BpZC53YnNzLml0L1NwaWQvb2lkYy9zYSIsImh0dHBzOi8vc2VjdXJlLmVyZW1pbmQuaXQvaWRlbnRpdGEtZGlnaXRhbGUtb2lkYy9vaWRjLWZlZCIsImh0dHBzOi8vY2llLW9pZGMuY29tdW5lLW9ubGluZS5pdC9BdXRoU2VydmljZU9JREMvb2lkYy9zYSIsImh0dHBzOi8vcGhwLWNpZS5hbmR4b3IuaXQiLCJodHRwczovL2xvZ2luLmFzZndlYi5pdC8iLCJodHRwczovL29pZGMuc3R1ZGlvYW1pY2EuY29tIiwiaHR0cHM6Ly9pZHAuZW50cmFuZXh0Lml0L3NlcnZpY2VzL29pZGMvc2Evc3NvIiwiaHR0cHM6Ly9jd29sc3NvLm51dm9sYXBhbGl0YWxzb2Z0Lml0L3NlcnZpY2VzL29pZGMvc2Evc3NvIiwiaHR0cHM6Ly9mZWRlcmEubGVwaWRhLml0L2d3L09pZGNTYUZ1bGwvIiwiaHR0cHM6Ly93d3cuZXVyb2NvbnRhYi5pdC9hcGkiXSwiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQvaW50ZXJtZWRpYXRlL3ByaXZhdGUiOlsiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQiXSwiaHR0cHM6Ly9vaWRjLnJlZ2lzdHJ5LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQvb3BlbmlkX3Byb3ZpZGVyL3B1YmxpYyI6WyJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdCJdLCJodHRwczovL29pZGMucmVnaXN0cnkuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9vcGVuaWRfcmVseWluZ19wYXJ0eS9wcml2YXRlIjpbImh0dHBzOi8vb2lkYy5yZWdpc3RyeS5zZXJ2aXppY2llLmludGVybm8uZ292Lml0IiwiaHR0cHM6Ly9vaWRjc2Eud2VibG9vbS5pdCIsImh0dHBzOi8vc3BpZC53YnNzLml0L1NwaWQvb2lkYy9zYSIsImh0dHBzOi8vc2VjdXJlLmVyZW1pbmQuaXQvaWRlbnRpdGEtZGlnaXRhbGUtb2lkYy9vaWRjLWZlZCIsImh0dHBzOi8vY2llLW9pZGMuY29tdW5lLW9ubGluZS5pdC9BdXRoU2VydmljZU9JREMvb2lkYy9zYSIsImh0dHBzOi8vcGhwLWNpZS5hbmR4b3IuaXQiLCJodHRwczovL2xvZ2luLmFzZndlYi5pdC8iLCJodHRwczovL29pZGMuc3R1ZGlvYW1pY2EuY29tIiwiaHR0cHM6Ly9pZHAuZW50cmFuZXh0Lml0L3NlcnZpY2VzL29pZGMvc2Evc3NvIiwiaHR0cHM6Ly9jd29sc3NvLm51dm9sYXBhbGl0YWxzb2Z0Lml0L3NlcnZpY2VzL29pZGMvc2Evc3NvIiwiaHR0cHM6Ly9mZWRlcmEubGVwaWRhLml0L2d3L09pZGNTYUZ1bGwvIiwiaHR0cHM6Ly93d3cuZXVyb2NvbnRhYi5pdC9hcGkiXX19.j9bmJRlLokkmWTpPkrphtB5dyVrKQPwY7U9jtl_PXlgVODmDXla0vbQszR_b0aUfk7j-Sh5v_UwtHRF6P5vPcaTvUiaPcbtFEIVq0xW9xcyjgPmYEkfHyB9CWxfq-AC6OOoRunGyTOO5G9xdup6QSLFxLQBlMZh5sE_X8wzkG02dZOfl8RTzuoquzNMl-yWpyb0Rxk_iY-ZhGa1yDPHm16tFmXMY3sf0QOBQAAGxBaRhcjekRnXPEijrPIaV381_VnQdd4xtbikI_XNRiGeyuoMii40K4l6qiznZ-_mz8GaRdS21Dc5XL5cjwMc4EDGxSNnW9NgBr7R4HDURyii" - val key = getKeyFromJwt(jwt) - val result = cryptoService.verify(jwt, key).await() - assertEquals(false, result) - } -} diff --git a/modules/openid-federation-client/src/jsTest/kotlin/com/sphereon/oid/fed/client/trustchain/TrustChainTest.js.kt b/modules/openid-federation-client/src/jsTest/kotlin/com/sphereon/oid/fed/client/trustchain/TrustChainTest.js.kt deleted file mode 100644 index c8f4e4fa..00000000 --- a/modules/openid-federation-client/src/jsTest/kotlin/com/sphereon/oid/fed/client/trustchain/TrustChainTest.js.kt +++ /dev/null @@ -1,129 +0,0 @@ -package com.sphereon.oid.fed.client.trustchain - -import com.sphereon.oid.fed.client.FederationClientJS -import com.sphereon.oid.fed.client.crypto.ICryptoCallbackServiceJS -import com.sphereon.oid.fed.client.fetch.IFetchCallbackServiceJS -import com.sphereon.oid.fed.client.service.DefaultCallbacks -import com.sphereon.oid.fed.openapi.models.Jwk -import io.ktor.client.* -import io.ktor.client.call.* -import io.ktor.client.engine.mock.* -import io.ktor.client.request.get -import io.ktor.http.* -import kotlinx.coroutines.CoroutineName -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.asPromise -import kotlinx.coroutines.async -import kotlinx.coroutines.await -import kotlinx.coroutines.test.runTest -import kotlin.js.Promise -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotNull - -actual class PlatformCallback : IFetchCallbackServiceJS { - - private val FETCH_SERVICE_JS_SCOPE = "FetchServiceTestJS" - - override fun getHttpClient(): Promise { - return CoroutineScope(context = CoroutineName(FETCH_SERVICE_JS_SCOPE)).async { - return@async HttpClient(MockEngine { request -> - val responseContent = mockResponses.find { it[0] == request.url.toString() }?.get(1) - ?: error("Unhandled ${request.url}") - - respond( - content = responseContent, - status = HttpStatusCode.OK, - headers = headersOf(HttpHeaders.ContentType, "application/entity-statement+jwt") - ) - }) - }.asPromise() - } - - override fun fetchStatement(endpoint: String): Promise { - return CoroutineScope(context = CoroutineName(FETCH_SERVICE_JS_SCOPE)).async { - return@async getHttpClient().await().get(endpoint) { - headers { - append(HttpHeaders.Accept, "application/entity-statement+jwt") - } - }.body() as String - }.asPromise() - } -} - -actual class CryptoCallbackService : ICryptoCallbackServiceJS { - override fun verify(jwt: String, jwk: Jwk): Promise { - return Promise.resolve(true) - } -} - -actual class TrustChainTest { - @Test - fun buildTrustChain() = runTest { - val fetchService = PlatformCallback() - DefaultCallbacks.setFetchServiceDefault(fetchService) - val cryptoService = CryptoCallbackService() - DefaultCallbacks.setCryptoServiceDefault(cryptoService) - val trustChainService = DefaultTrustChainJSImpl() - DefaultCallbacks.setTrustChainServiceDefault(trustChainService) - - val client = FederationClientJS() - - val trustChain = client.resolveTrustChainJS( - "https://spid.wbss.it/Spid/oidc/rp/ipasv_lt", - arrayOf("https://oidc.registry.servizicie.interno.gov.it") - ).await() - - assertNotNull(trustChain) - - assertEquals(4, trustChain.size) - - assertEquals( - trustChain[0], - mockResponses.find { it[0] == "https://spid.wbss.it/Spid/oidc/rp/ipasv_lt/.well-known/openid-federation" } - ?.get(1) - ) - - assertEquals( - trustChain[1], - mockResponses.find { it[0] == "https://spid.wbss.it/Spid/oidc/sa/fetch?sub=https://spid.wbss.it/Spid/oidc/rp/ipasv_lt&iss=https://spid.wbss.it/Spid/oidc/sa" } - ?.get(1) - ) - - assertEquals( - trustChain[2], - mockResponses.find { it[0] == "https://oidc.registry.servizicie.interno.gov.it/fetch?sub=https://spid.wbss.it/Spid/oidc/sa&iss=https://oidc.registry.servizicie.interno.gov.it" } - ?.get(1) - ) - - assertEquals( - trustChain[3], - mockResponses.find { it[0] == "https://oidc.registry.servizicie.interno.gov.it/.well-known/openid-federation" } - ?.get(1) - ) - - val trustChain2 = client.resolveTrustChainJS( - "https://spid.wbss.it/Spid/oidc/sa", - arrayOf("https://oidc.registry.servizicie.interno.gov.it") - ).await() - - assertNotNull(trustChain2) - assertEquals(3, trustChain2.size) - assertEquals( - trustChain2[0], - mockResponses.find { it[0] == "https://spid.wbss.it/Spid/oidc/sa/.well-known/openid-federation" }?.get(1) - ) - - assertEquals( - trustChain2[1], - mockResponses.find { it[0] == "https://oidc.registry.servizicie.interno.gov.it/fetch?sub=https://spid.wbss.it/Spid/oidc/sa&iss=https://oidc.registry.servizicie.interno.gov.it" } - ?.get(1) - ) - - assertEquals( - trustChain2[2], - mockResponses.find { it[0] == "https://oidc.registry.servizicie.interno.gov.it/.well-known/openid-federation" } - ?.get(1) - ) - } -} diff --git a/modules/openid-federation-client/src/jvmMain/kotlin/com/sphereon/oid/fed/client/crypto/Crypto.jvm.kt b/modules/openid-federation-client/src/jvmMain/kotlin/com/sphereon/oid/fed/client/crypto/Crypto.jvm.kt index 2cc6a4c1..50bb9872 100644 --- a/modules/openid-federation-client/src/jvmMain/kotlin/com/sphereon/oid/fed/client/crypto/Crypto.jvm.kt +++ b/modules/openid-federation-client/src/jvmMain/kotlin/com/sphereon/oid/fed/client/crypto/Crypto.jvm.kt @@ -1,10 +1,60 @@ package com.sphereon.oid.fed.client.crypto -actual fun cryptoService(platformCallback: ICryptoCallbackMarkerType): ICryptoService { - if (platformCallback !is ICryptoCallbackService) { - throw IllegalArgumentException("Platform callback is not of type ICryptoCallbackService, but ${platformCallback.javaClass.canonicalName}") +import com.nimbusds.jose.JWSVerifier +import com.nimbusds.jose.crypto.ECDSAVerifier +import com.nimbusds.jose.crypto.MACVerifier +import com.nimbusds.jose.crypto.RSASSAVerifier +import com.nimbusds.jose.jwk.Curve +import com.nimbusds.jose.jwk.JWK +import com.nimbusds.jose.jwk.KeyType +import com.nimbusds.jwt.SignedJWT +import com.sphereon.oid.fed.openapi.models.Jwk +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.text.ParseException + +actual fun cryptoService(): ICryptoService { + return object : ICryptoService { + override suspend fun verify(jwt: String, key: Jwk): Boolean { + return try { + val signedJWT = SignedJWT.parse(jwt) + + val nimbusJWK = JWK.parse(Json.encodeToString(key)) + + val verifier: JWSVerifier = when (nimbusJWK.keyType) { + KeyType.RSA -> { + RSASSAVerifier(nimbusJWK.toRSAKey()) + } + + KeyType.EC -> { + val ecKey = nimbusJWK.toECKey() + if (!supportedCurve(ecKey.curve)) { + throw IllegalArgumentException("Unsupported EC curve: ${ecKey.curve}") + } + ECDSAVerifier(ecKey) + } + + KeyType.OCT -> { + MACVerifier(nimbusJWK.toOctetSequenceKey()) + } + + else -> { + throw IllegalArgumentException("Unsupported key type: ${nimbusJWK.keyType}") + } + } + + signedJWT.verify(verifier) + } catch (e: ParseException) { + false + } catch (e: IllegalArgumentException) { + false + } catch (e: Exception) { + false + } + } + + fun supportedCurve(curve: Curve): Boolean { + return curve == Curve.P_256 || curve == Curve.P_384 || curve == Curve.P_521 || curve == Curve.SECP256K1 + } } - return CryptoService(platformCallback) } - -actual interface ICryptoCallbackMarkerType diff --git a/modules/openid-federation-client/src/jvmMain/kotlin/com/sphereon/oid/fed/client/fetch/Fetch.jvm.kt b/modules/openid-federation-client/src/jvmMain/kotlin/com/sphereon/oid/fed/client/fetch/Fetch.jvm.kt index a84dcb73..a1b3b272 100644 --- a/modules/openid-federation-client/src/jvmMain/kotlin/com/sphereon/oid/fed/client/fetch/Fetch.jvm.kt +++ b/modules/openid-federation-client/src/jvmMain/kotlin/com/sphereon/oid/fed/client/fetch/Fetch.jvm.kt @@ -1,31 +1,21 @@ package com.sphereon.oid.fed.client.fetch -import io.ktor.client.HttpClient -import io.ktor.client.call.body +import io.ktor.client.* +import io.ktor.client.call.* import io.ktor.client.engine.java.* -import io.ktor.client.request.get -import io.ktor.http.HttpHeaders -import io.ktor.http.headers +import io.ktor.client.request.* +import io.ktor.http.* -actual fun fetchService(platformCallback: IFetchCallbackMarkerType): IFetchService { - if (platformCallback !is IFetchCallbackService) { - throw IllegalArgumentException("Platform callback is not of type IFetchCallbackService, but ${platformCallback.javaClass.canonicalName}") - } - return FetchService(platformCallback) -} - -actual interface IFetchCallbackMarkerType - -class DefaultFetchJvmImpl : IFetchCallbackService { - override suspend fun fetchStatement(endpoint: String): String { - return getHttpClient().get(endpoint) { - headers { - append(HttpHeaders.Accept, "application/entity-statement+jwt") - } - }.body() as String - } +actual fun fetchService(): IFetchService { + return object : IFetchService { + private val httpClient = HttpClient(Java) - override suspend fun getHttpClient(): HttpClient { - return HttpClient(Java) + override suspend fun fetchStatement(endpoint: String): String { + return httpClient.get(endpoint) { + headers { + append(HttpHeaders.Accept, "application/entity-statement+jwt") + } + }.body() + } } } diff --git a/modules/openid-federation-client/src/jvmMain/kotlin/com/sphereon/oid/fed/client/trustchain/TrustChain.jvm.kt b/modules/openid-federation-client/src/jvmMain/kotlin/com/sphereon/oid/fed/client/trustchain/TrustChain.jvm.kt deleted file mode 100644 index bcde3edf..00000000 --- a/modules/openid-federation-client/src/jvmMain/kotlin/com/sphereon/oid/fed/client/trustchain/TrustChain.jvm.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.sphereon.oid.fed.client.trustchain - -actual fun trustChainService(platformCallback: ITrustChainCallbackMarkerType): ITrustChainService { - if (platformCallback !is ITrustChainCallbackService) { - throw IllegalArgumentException("Platform callback is not of type IFetchCallbackService, but ${platformCallback.javaClass.canonicalName}") - } - return TrustChainService(platformCallback) -} - -actual interface ITrustChainCallbackMarkerType diff --git a/modules/openid-federation-client/src/jvmTest/kotlin/com/sphereon/oid/fed/client/crypto/CryptoTest.jvm.kt b/modules/openid-federation-client/src/jvmTest/kotlin/com/sphereon/oid/fed/client/crypto/CryptoTest.jvm.kt deleted file mode 100644 index e69de29b..00000000 diff --git a/modules/openid-federation-client/src/jvmTest/kotlin/com/sphereon/oid/fed/client/trustchain/TrustChainTest.jvm.kt b/modules/openid-federation-client/src/jvmTest/kotlin/com/sphereon/oid/fed/client/trustchain/TrustChainTest.jvm.kt deleted file mode 100644 index 32b673e2..00000000 --- a/modules/openid-federation-client/src/jvmTest/kotlin/com/sphereon/oid/fed/client/trustchain/TrustChainTest.jvm.kt +++ /dev/null @@ -1,117 +0,0 @@ -package com.sphereon.oid.fed.client.trustchain - -import com.sphereon.oid.fed.client.FederationClient -import com.sphereon.oid.fed.client.crypto.ICryptoCallbackService -import com.sphereon.oid.fed.client.fetch.IFetchCallbackService -import com.sphereon.oid.fed.client.service.DefaultCallbacks -import com.sphereon.oid.fed.openapi.models.Jwk -import io.ktor.client.* -import io.ktor.client.call.* -import io.ktor.client.engine.mock.* -import io.ktor.client.request.get -import io.ktor.http.* -import kotlinx.coroutines.test.runTest -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotNull - -actual class PlatformCallback : IFetchCallbackService { - - override suspend fun getHttpClient(): HttpClient { - return HttpClient(MockEngine { request -> - val responseContent = mockResponses.find { it[0] == request.url.toString() }?.get(1) - ?: error("Unhandled ${request.url}") - - respond( - content = responseContent, - status = HttpStatusCode.OK, - headers = headersOf(HttpHeaders.ContentType, "application/entity-statement+jwt") - ) - }) - } - - override suspend fun fetchStatement(endpoint: String): String { - return getHttpClient().get(endpoint) { - headers { - append(HttpHeaders.Accept, "application/entity-statement+jwt") - } - }.body() - } -} - -actual class CryptoCallbackService : ICryptoCallbackService { - override suspend fun verify(jwt: String, jwk: Jwk): Boolean { - return true - } -} - -actual class TrustChainTest { - @Test - fun buildTrustChain() = runTest { - val fetchService = PlatformCallback() - DefaultCallbacks.setFetchServiceDefault(fetchService) - val cryptoService = CryptoCallbackService() - DefaultCallbacks.setCryptoServiceDefault(cryptoService) - val trustChainService = DefaultTrustChainImpl(null, null) - DefaultCallbacks.setTrustChainServiceDefault(trustChainService) - - val client = FederationClient() - - val trustChain = client.resolveTrustChain( - "https://spid.wbss.it/Spid/oidc/rp/ipasv_lt", - arrayOf("https://oidc.registry.servizicie.interno.gov.it") - ) - - assertNotNull(trustChain) - - assertEquals(4, trustChain.size) - - assertEquals( - trustChain[0], - mockResponses.find { it[0] == "https://spid.wbss.it/Spid/oidc/rp/ipasv_lt/.well-known/openid-federation" } - ?.get(1) - ) - - assertEquals( - trustChain[1], - mockResponses.find { it[0] == "https://spid.wbss.it/Spid/oidc/sa/fetch?sub=https://spid.wbss.it/Spid/oidc/rp/ipasv_lt&iss=https://spid.wbss.it/Spid/oidc/sa" } - ?.get(1) - ) - - assertEquals( - trustChain[2], - mockResponses.find { it[0] == "https://oidc.registry.servizicie.interno.gov.it/fetch?sub=https://spid.wbss.it/Spid/oidc/sa&iss=https://oidc.registry.servizicie.interno.gov.it" } - ?.get(1) - ) - - assertEquals( - trustChain[3], - mockResponses.find { it[0] == "https://oidc.registry.servizicie.interno.gov.it/.well-known/openid-federation" } - ?.get(1) - ) - - val trustChain2 = client.resolveTrustChain( - "https://spid.wbss.it/Spid/oidc/sa", - arrayOf("https://oidc.registry.servizicie.interno.gov.it") - ) - - assertNotNull(trustChain2) - assertEquals(3, trustChain2.size) - assertEquals( - trustChain2[0], - mockResponses.find { it[0] == "https://spid.wbss.it/Spid/oidc/sa/.well-known/openid-federation" }?.get(1) - ) - - assertEquals( - trustChain2[1], - mockResponses.find { it[0] == "https://oidc.registry.servizicie.interno.gov.it/fetch?sub=https://spid.wbss.it/Spid/oidc/sa&iss=https://oidc.registry.servizicie.interno.gov.it" } - ?.get(1) - ) - - assertEquals( - trustChain2[2], - mockResponses.find { it[0] == "https://oidc.registry.servizicie.interno.gov.it/.well-known/openid-federation" } - ?.get(1) - ) - } -}