Skip to content

Commit

Permalink
fix: make Trustchain a class to simplify dep injection
Browse files Browse the repository at this point in the history
  • Loading branch information
jcmelati committed Oct 17, 2024
1 parent a8b0733 commit 4c413c9
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 126 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import com.sphereon.oid.fed.client.crypto.CryptoServiceObject
import com.sphereon.oid.fed.client.crypto.ICryptoService
import com.sphereon.oid.fed.client.fetch.FetchServiceObject
import com.sphereon.oid.fed.client.fetch.IFetchService
import com.sphereon.oid.fed.client.trustchain.resolve
import com.sphereon.oid.fed.client.trustchain.TrustChain

class FederationClient(fetchServiceCallback: IFetchService?, cryptoServiceCallback: ICryptoService) {
private val fetchService = FetchServiceObject.register(fetchServiceCallback)
private val cryptoService = CryptoServiceObject.register(cryptoServiceCallback)
private val trustChainService = TrustChain(fetchService, cryptoService)

suspend fun resolveTrustChain(entityIdentifier: String, trustAnchors: Array<String>): MutableList<String>? {
return resolve(entityIdentifier, trustAnchors, fetchService, cryptoService)
return trustChainService.resolve(entityIdentifier, trustAnchors)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,145 +22,134 @@ class SimpleCache<K, V> {
}
}

suspend fun resolve(
entityIdentifier: String,
trustAnchors: Array<String>,
fetchService: IFetchCallbackService,
cryptoService: ICryptoCallbackService
): MutableList<String>? {
val cache = SimpleCache<String, String>()
val chain: MutableList<String> = arrayListOf()
return buildTrustChainRecursive(entityIdentifier, trustAnchors, chain, cache, fetchService, cryptoService)
}
class TrustChain(private val fetchService: IFetchCallbackService, private val cryptoService: ICryptoCallbackService) {
suspend fun resolve(
entityIdentifier: String, trustAnchors: Array<String>
): MutableList<String>? {
val cache = SimpleCache<String, String>()
val chain: MutableList<String> = arrayListOf()
return buildTrustChainRecursive(entityIdentifier, trustAnchors, chain, cache)
}

private suspend fun buildTrustChainRecursive(
entityIdentifier: String,
trustAnchors: Array<String>,
chain: MutableList<String>,
cache: SimpleCache<String, String>,
fetchService: IFetchCallbackService,
cryptoService: ICryptoCallbackService
): MutableList<String>? {
private suspend fun buildTrustChainRecursive(
entityIdentifier: String,
trustAnchors: Array<String>,
chain: MutableList<String>,
cache: SimpleCache<String, String>
): MutableList<String>? {

val entityConfigurationJwt =
fetchService.fetchStatement(getEntityConfigurationEndpoint(entityIdentifier))
val entityConfigurationJwt = this.fetchService.fetchStatement(getEntityConfigurationEndpoint(entityIdentifier))

val decodedEntityConfiguration = decodeJWTComponents(entityConfigurationJwt)
val decodedEntityConfiguration = decodeJWTComponents(entityConfigurationJwt)

if (!cryptoService.verify(entityConfigurationJwt)) {
return null
}
if (!cryptoService.verify(entityConfigurationJwt)) {
return null
}

val entityStatement: EntityConfigurationStatement =
mapEntityStatement(entityConfigurationJwt, EntityConfigurationStatement::class) ?: return null
val entityStatement: EntityConfigurationStatement =
mapEntityStatement(entityConfigurationJwt, EntityConfigurationStatement::class) ?: return null

if (chain.isEmpty()) {
chain.add(entityConfigurationJwt)
}
if (chain.isEmpty()) {
chain.add(entityConfigurationJwt)
}

val authorityHints = entityStatement.authorityHints ?: return null
val authorityHints = entityStatement.authorityHints ?: return null

for (authority in authorityHints) {
val result =
processAuthority(
for (authority in authorityHints) {
val result = processAuthority(
authority,
entityIdentifier,
trustAnchors,
chain,
decodedEntityConfiguration.header.kid,
cache,
fetchService,
cryptoService
cache
)
if (result != null) {
return result
if (result != null) {
return result
}
}
}

return null
}

private suspend fun processAuthority(
authority: String,
entityIdentifier: String,
trustAnchors: Array<String>,
chain: MutableList<String>,
lastStatementKid: String,
cache: SimpleCache<String, String>,
fetchService: IFetchCallbackService,
cryptoService: ICryptoCallbackService
): MutableList<String>? {

try {
val authorityConfigurationEndpoint = getEntityConfigurationEndpoint(authority)

// Avoid processing the same entity twice
if (cache.get(authorityConfigurationEndpoint) != null) return null

val authorityEntityConfigurationJwt =
fetchService.fetchStatement(authorityConfigurationEndpoint)
cache.put(authorityConfigurationEndpoint, authorityEntityConfigurationJwt)

if (!cryptoService.verify(authorityEntityConfigurationJwt)) {
return null
}

val authorityEntityConfiguration: EntityConfigurationStatement =
mapEntityStatement(authorityEntityConfigurationJwt, EntityConfigurationStatement::class)
?: return null

val federationEntityMetadata =
authorityEntityConfiguration.metadata?.get("federation_entity") as? JsonObject
if (federationEntityMetadata == null || !federationEntityMetadata.containsKey("federation_fetch_endpoint")) return null

val authorityEntityFetchEndpoint =
federationEntityMetadata["federation_fetch_endpoint"]?.toString()?.trim('"') ?: return null

val subordinateStatementEndpoint =
getSubordinateStatementEndpoint(authorityEntityFetchEndpoint, entityIdentifier)

val subordinateStatementJwt = fetchService.fetchStatement(subordinateStatementEndpoint)
return null
}

if (!cryptoService.verify(subordinateStatementJwt)) {
private suspend fun processAuthority(
authority: String,
entityIdentifier: String,
trustAnchors: Array<String>,
chain: MutableList<String>,
lastStatementKid: String,
cache: SimpleCache<String, String>
): MutableList<String>? {

try {
val authorityConfigurationEndpoint = getEntityConfigurationEndpoint(authority)

// Avoid processing the same entity twice
if (cache.get(authorityConfigurationEndpoint) != null) return null

val authorityEntityConfigurationJwt = fetchService.fetchStatement(authorityConfigurationEndpoint)
cache.put(authorityConfigurationEndpoint, authorityEntityConfigurationJwt)

if (!cryptoService.verify(authorityEntityConfigurationJwt)) {
return null
}

val authorityEntityConfiguration: EntityConfigurationStatement =
mapEntityStatement(authorityEntityConfigurationJwt, EntityConfigurationStatement::class) ?: return null

val federationEntityMetadata =
authorityEntityConfiguration.metadata?.get("federation_entity") as? JsonObject
if (federationEntityMetadata == null || !federationEntityMetadata.containsKey("federation_fetch_endpoint")) return null

val authorityEntityFetchEndpoint =
federationEntityMetadata["federation_fetch_endpoint"]?.toString()?.trim('"') ?: return null

val subordinateStatementEndpoint =
getSubordinateStatementEndpoint(authorityEntityFetchEndpoint, entityIdentifier)

val subordinateStatementJwt = fetchService.fetchStatement(subordinateStatementEndpoint)

if (!cryptoService.verify(subordinateStatementJwt)) {
return null
}

val subordinateStatement: SubordinateStatement =
mapEntityStatement(subordinateStatementJwt, SubordinateStatement::class) ?: return null

val jwks = subordinateStatement.jwks
val keys = jwks.propertyKeys ?: return null

// Check if the entity key exists in subordinate statement
val entityKeyExistsInSubordinateStatement = checkKidInJwks(keys, lastStatementKid)
if (!entityKeyExistsInSubordinateStatement) return null
// If authority is in trust anchors, return the completed chain
if (trustAnchors.contains(authority)) {
chain.add(subordinateStatementJwt)
chain.add(authorityEntityConfigurationJwt)
return chain
}

// Recursively build trust chain if there are authority hints
if (authorityEntityConfiguration.authorityHints?.isNotEmpty() == true) {
chain.add(subordinateStatementJwt)
val result =
buildTrustChainRecursive(authority, trustAnchors, chain, cache)
if (result != null) return result
chain.removeLast()
}
} catch (_: Exception) {
return null
}

val subordinateStatement: SubordinateStatement =
mapEntityStatement(subordinateStatementJwt, SubordinateStatement::class)
?: return null

val jwks = subordinateStatement.jwks
val keys = jwks.propertyKeys ?: return null

// Check if the entity key exists in subordinate statement
val entityKeyExistsInSubordinateStatement = checkKidInJwks(keys, lastStatementKid)
if (!entityKeyExistsInSubordinateStatement) return null
// If authority is in trust anchors, return the completed chain
if (trustAnchors.contains(authority)) {
chain.add(subordinateStatementJwt)
chain.add(authorityEntityConfigurationJwt)
return chain
}

// Recursively build trust chain if there are authority hints
if (authorityEntityConfiguration.authorityHints?.isNotEmpty() == true) {
chain.add(subordinateStatementJwt)
val result = buildTrustChainRecursive(authority, trustAnchors, chain, cache, fetchService, cryptoService)
if (result != null) return result
chain.removeLast()
}
} catch (_: Exception) {
return null
}

return null
}

private fun checkKidInJwks(keys: Array<Jwk>, kid: String): Boolean {
for (key in keys) {
if (key.kid == kid) {
return true
private fun checkKidInJwks(keys: Array<Jwk>, kid: String): Boolean {
for (key in keys) {
if (key.kid == kid) {
return true
}
}
return false
}
return false
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.sphereon.oid.fed.client

import com.sphereon.oid.fed.client.crypto.cryptoService
import com.sphereon.oid.fed.client.crypto.CryptoServiceObject
import com.sphereon.oid.fed.client.fetch.FetchServiceObject
import com.sphereon.oid.fed.client.trustchain.resolve
import com.sphereon.oid.fed.client.trustchain.TrustChain
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.promise
Expand All @@ -12,17 +12,16 @@ import kotlin.js.Promise
@JsName("FederationClient")
class FederationClientJS {
private val fetchService = FetchServiceObject.register(null)
private val cryptoService = cryptoService()
private val cryptoService = CryptoServiceObject.register(null)
private val trustChainService = TrustChain(fetchService, cryptoService)

@OptIn(DelicateCoroutinesApi::class)
@JsName("resolveTrustChain")
fun resolveTrustChainJS(entityIdentifier: String, trustAnchors: Array<String>): Promise<Array<String>?> {
return GlobalScope.promise {
resolve(
trustChainService.resolve(
entityIdentifier,
trustAnchors,
fetchService,
cryptoService
trustAnchors
)?.toTypedArray()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ open class CryptoServiceJSAdapter(private val cryptoServiceCallbackJS: CryptoSer

object CryptoServiceJSAdapterObject : CryptoServiceJSAdapter(CryptoServiceJS)


actual fun cryptoService(): ICryptoCallbackService = CryptoServiceJSAdapterObject

@JsModule("jose")
Expand Down

0 comments on commit 4c413c9

Please sign in to comment.