Skip to content

Commit

Permalink
Feat/oidf 65 3 (#43)
Browse files Browse the repository at this point in the history
* fix: clean up tests

* fix: implement js client adapter and interfaces

* reorganize code

* fix: verify key type
  • Loading branch information
jcmelati authored Nov 13, 2024
1 parent 3947fdf commit b749c6e
Show file tree
Hide file tree
Showing 26 changed files with 342 additions and 1,163 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ captures
/.temp/
/.run/*
kotlin-js-store/
.env
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
4 changes: 2 additions & 2 deletions modules/openid-federation-client/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -128,7 +128,7 @@ npmPublish {
authToken.set(System.getenv("NPM_TOKEN") ?: "")
}
}
packages{
packages {
named("js") {
packageJson {
"name" by "@sphereon/openid-federation-client"
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String>): MutableList<String>? {
return trustChainService?.resolve(entityIdentifier, trustAnchors)
suspend fun resolveTrustChain(
entityIdentifier: String,
trustAnchors: Array<String>,
maxDepth: Int = 5
): MutableList<String>? {
return trustChainService.resolve(entityIdentifier, trustAnchors, maxDepth)
}
}
Original file line number Diff line number Diff line change
@@ -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<CallbackServiceType>(open val platformCallback: CallbackServiceType?): ICallbackService<CallbackServiceType> {
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<ICryptoCallbackService>(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
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Original file line number Diff line number Diff line change
@@ -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<CallbackServiceType>(open val platformCallback: CallbackServiceType): ICallbackService<CallbackServiceType> {
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<IFetchCallbackService>(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
Original file line number Diff line number Diff line change
@@ -1,9 +1,32 @@
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"
}

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<Jwk>, kid: String): Boolean {
for (key in keys) {
if (key.kid == kid) {
return true
}
}
return false
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)

This file was deleted.

Loading

0 comments on commit b749c6e

Please sign in to comment.