From 599c9844e97179db927604648159111ea8dfbc2a Mon Sep 17 00:00:00 2001 From: Zoe Maas Date: Wed, 25 Sep 2024 12:49:14 +0200 Subject: [PATCH] chore: Trust Chain Validation refactoring - Separate Entity Configuration Statement from Subordinate Statements --- .../sphereon/oid/fed/kms/local/Constants.kt | 2 + .../oid/fed/common/mapper/JsonMapper.kt | 2 +- .../common/validation/TrustChainValidation.kt | 212 ++++++++---------- 3 files changed, 93 insertions(+), 123 deletions(-) diff --git a/modules/local-kms/src/commonMain/kotlin/com/sphereon/oid/fed/kms/local/Constants.kt b/modules/local-kms/src/commonMain/kotlin/com/sphereon/oid/fed/kms/local/Constants.kt index 928a9356..0389591e 100644 --- a/modules/local-kms/src/commonMain/kotlin/com/sphereon/oid/fed/kms/local/Constants.kt +++ b/modules/local-kms/src/commonMain/kotlin/com/sphereon/oid/fed/kms/local/Constants.kt @@ -6,5 +6,7 @@ class Constants { const val LOCAL_KMS_DATASOURCE_USER = "LOCAL_KMS_DATASOURCE_USER" const val LOCAL_KMS_DATASOURCE_PASSWORD = "LOCAL_KMS_DATASOURCE_PASSWORD" const val SQLITE_IS_NOT_SUPPORTED_IN_JVM = "SQLite is not supported in JVM" + const val SQLITE_IS_NOT_SUPPORTED_IN_JS = "SQLite is not supported in JS" + const val POSTGRESQL_IS_NOT_SUPPORTED_IN_JS = "PostgreSQL is not supported in JS" } } diff --git a/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/mapper/JsonMapper.kt b/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/mapper/JsonMapper.kt index 8528a026..81622a40 100644 --- a/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/mapper/JsonMapper.kt +++ b/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/mapper/JsonMapper.kt @@ -19,7 +19,7 @@ class JsonMapper { } - fun mapEntityConfigurationStatement(jwtToken: String): EntityConfigurationStatement? = + fun mapEntityConfigurationStatement(jwtToken: String): EntityConfigurationStatement = decodeJWTComponents(jwtToken).payload.let { Json.decodeFromJsonElement(it) } diff --git a/modules/openid-federation-common/src/jvmMain/kotlin/com/sphereon/oid/fed/common/validation/TrustChainValidation.kt b/modules/openid-federation-common/src/jvmMain/kotlin/com/sphereon/oid/fed/common/validation/TrustChainValidation.kt index e84d016c..eb7369c1 100644 --- a/modules/openid-federation-common/src/jvmMain/kotlin/com/sphereon/oid/fed/common/validation/TrustChainValidation.kt +++ b/modules/openid-federation-common/src/jvmMain/kotlin/com/sphereon/oid/fed/common/validation/TrustChainValidation.kt @@ -2,138 +2,106 @@ package com.sphereon.oid.fed.common.validation import com.sphereon.oid.fed.common.httpclient.OidFederationClient import com.sphereon.oid.fed.common.mapper.JsonMapper -import com.sphereon.oid.fed.kms.local.jwt.verify 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 io.ktor.client.engine.* import kotlinx.coroutines.runBlocking -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import java.time.OffsetDateTime -fun readAuthorityHints(jwt: String? = null, partyBId: String? = null, engine: HttpClientEngine): List> { - val trustChains = mutableListOf>() - val subordinateStatements = mutableListOf>() - val subordinateStatement = mutableListOf() +class TrustChainValidation { - if (jwt != null && partyBId != null) { - throw IllegalArgumentException("Only one of jwt or partyBId should be provided") - } - - if (jwt == null && partyBId == null) { - throw IllegalArgumentException("Either jwt or partyBId should be provided") - } - - val entityConfigurationStatement = jwt?.let { - JsonMapper().mapEntityConfigurationStatement(it) - } ?: partyBId?.let { - JsonMapper().mapEntityConfigurationStatement(requestEntityStatement(it, engine)) - } - - entityConfigurationStatement?.authorityHints?.forEach { authorityHint -> - buildTrustChain(authorityHint, engine, subordinateStatements, trustChains, subordinateStatement = subordinateStatement) - } - return subordinateStatements -} - -fun buildTrustChain( - authorityHint: String, - engine: HttpClientEngine, - subordinateStatements: MutableList>, - trustChains: MutableList>, - trustChain: MutableList = mutableListOf(), - subordinateStatement: MutableList = mutableListOf() -) -{ - requestEntityStatement(authorityHint, engine).run { - JsonMapper().mapEntityConfigurationStatement(this)?.let { - if (it.authorityHints.isNullOrEmpty()) { - it.metadata?.get("federation_entity")?.jsonObject?.get("federation_fetch_endpoint")?.jsonPrimitive?.content.let { url -> - requestEntityStatement(url.toString(), engine).let { jwt -> subordinateStatement.add(jwt) } - } - trustChain.add(it) - trustChains.add(trustChain.map { content -> content.copy() }) - subordinateStatements.add(subordinateStatement.map { content -> content.substring(0)}) - it.authorityHints ?: trustChain.clear() - it.authorityHints ?: subordinateStatement.clear() - } else { - it.authorityHints?.forEach { hint -> - it.metadata?.get("federation_entity")?.jsonObject?.get("federation_fetch_endpoint")?.jsonPrimitive?.content.let { url -> - requestEntityStatement(url.toString(), engine).let { jwt -> subordinateStatement.add(jwt) } - } + fun readAuthorityHints( + partyBId: String, + engine: HttpClientEngine, + trustChains: MutableList> = mutableListOf(), + trustChain: MutableList = mutableListOf() + ): List>{ + requestEntityStatement(partyBId, engine).run { + JsonMapper().mapEntityConfigurationStatement(this)?.let { + if (it.authorityHints.isNullOrEmpty()) { trustChain.add(it) - buildTrustChain(hint, engine, subordinateStatements, trustChains, trustChain, subordinateStatement) + trustChains.add(trustChain.map { content -> content.copy() }) + it.authorityHints ?: trustChain.clear() + } else { + it.authorityHints?.forEach { hint -> + trustChain.add(it) + readAuthorityHints( + hint, + engine, + trustChains, + trustChain + ) + } } } } - } -} - -// TODO must validate subordinate statements too -fun validateTrustChain(jwts: List): Boolean { - val entityStatements = jwts.map { JsonMapper().mapEntityStatement(it) } - - val firstEntityConfigurationStatement = entityStatements[0] as EntityConfigurationStatement - val subordinateStatements = entityStatements.map { it as SubordinateStatement }.subList(1, entityStatements.size - 1) - val lastEntityConfigurationStatement = entityStatements[entityStatements.size - 1] as EntityConfigurationStatement - - if(firstEntityConfigurationStatement.iss != firstEntityConfigurationStatement.sub) { - throw IllegalArgumentException("Entity Configuration of the Trust Chain subject requires that iss is equal to sub") + return trustChains } - if (!verify(jwts[0], retrieveJwk(firstEntityConfigurationStatement))) { - throw IllegalArgumentException("Invalid signature") + // +//// TODO must validate subordinate statements too +//fun validateTrustChain(jwts: List): Boolean { +// val entityStatements = jwts.map { JsonMapper().mapEntityStatement(it) } +// +// val firstEntityConfigurationStatement = entityStatements[0] as EntityConfigurationStatement +// val subordinateStatements = entityStatements.map { it as SubordinateStatement }.subList(1, entityStatements.size - 1) +// val lastEntityConfigurationStatement = entityStatements[entityStatements.size - 1] as EntityConfigurationStatement +// +// if(firstEntityConfigurationStatement.iss != firstEntityConfigurationStatement.sub) { +// throw IllegalArgumentException("Entity Configuration of the Trust Chain subject requires that iss is equal to sub") +// } +// +// if (!verify(jwts[0], retrieveJwk(firstEntityConfigurationStatement))) { +// throw IllegalArgumentException("Invalid signature") +// } +// +// subordinateStatements.forEachIndexed { index, current -> +// val next = entityStatements[index + 1] as SubordinateStatement +// if(current.iss != next.sub) { +// throw IllegalArgumentException("Entity Configuration of the Trust Chain subject requires that iss is equal to sub") +// } +// val offsetTime = OffsetDateTime.now() +// if(current.iat > offsetTime.toEpochSecond().toInt()) { +// throw IllegalArgumentException("Invalid iat") +// } +// if(current.exp < offsetTime.toEpochSecond().toInt()) { +// throw IllegalArgumentException("Invalid exp") +// } +// +// if(!verify(jwts[index], retrieveJwk(next))) { +// throw IllegalArgumentException("Invalid signature") +// } +// } +// if(lastEntityConfigurationStatement.iss != "entity_identifier") { +// throw IllegalArgumentException("Entity Configuration of the Trust Chain subject requires that iss is equal to sub") +// } +// if (!verify(jwts[jwts.size - 1], retrieveJwk(lastEntityConfigurationStatement))) { +// throw IllegalArgumentException("Invalid signature") +// } +// return true +//} +// +//fun retrieveJwk(entityStatement: Any?): Jwk { +// return when(entityStatement) { +// is EntityConfigurationStatement -> entityStatement.jwks.let { key -> +// Jwk( +// kid = key.jsonObject["kid"]?.jsonPrimitive?.content, +// kty = key.jsonObject["kty"]?.jsonPrimitive?.content ?: "", +// crv = key.jsonObject["crv"]?.jsonPrimitive?.content, +// x = key.jsonObject["x"]?.jsonPrimitive?.content +// ) +// } +// is SubordinateStatement -> entityStatement.jwks.let { key -> +// Jwk( +// kid = key.jsonObject["kid"]?.jsonPrimitive?.content, +// kty = key.jsonObject["kty"]?.jsonPrimitive?.content ?: "", +// crv = key.jsonObject["crv"]?.jsonPrimitive?.content, +// x = key.jsonObject["x"]?.jsonPrimitive?.content +// ) +// } +// else -> throw IllegalArgumentException("Invalid entity statement") +// } +//} +// + private fun requestEntityStatement(url: String, engine: HttpClientEngine) = runBlocking { + return@runBlocking OidFederationClient(engine).fetchEntityStatement(url) } - - subordinateStatements.forEachIndexed { index, current -> - val next = entityStatements[index + 1] as SubordinateStatement - if(current.iss != next.sub) { - throw IllegalArgumentException("Entity Configuration of the Trust Chain subject requires that iss is equal to sub") - } - val offsetTime = OffsetDateTime.now() - if(current.iat > offsetTime.toEpochSecond().toInt()) { - throw IllegalArgumentException("Invalid iat") - } - if(current.exp < offsetTime.toEpochSecond().toInt()) { - throw IllegalArgumentException("Invalid exp") - } - - if(!verify(jwts[index], retrieveJwk(next))) { - throw IllegalArgumentException("Invalid signature") - } - } - if(lastEntityConfigurationStatement.iss != "entity_identifier") { - throw IllegalArgumentException("Entity Configuration of the Trust Chain subject requires that iss is equal to sub") - } - if (!verify(jwts[jwts.size - 1], retrieveJwk(lastEntityConfigurationStatement))) { - throw IllegalArgumentException("Invalid signature") - } - return true -} - -fun retrieveJwk(entityStatement: Any?): Jwk { - return when(entityStatement) { - is EntityConfigurationStatement -> entityStatement.jwks.let { key -> - Jwk( - kid = key.jsonObject["kid"]?.jsonPrimitive?.content, - kty = key.jsonObject["kty"]?.jsonPrimitive?.content ?: "", - crv = key.jsonObject["crv"]?.jsonPrimitive?.content, - x = key.jsonObject["x"]?.jsonPrimitive?.content - ) - } - is SubordinateStatement -> entityStatement.jwks.let { key -> - Jwk( - kid = key.jsonObject["kid"]?.jsonPrimitive?.content, - kty = key.jsonObject["kty"]?.jsonPrimitive?.content ?: "", - crv = key.jsonObject["crv"]?.jsonPrimitive?.content, - x = key.jsonObject["x"]?.jsonPrimitive?.content - ) - } - else -> throw IllegalArgumentException("Invalid entity statement") - } -} - -fun requestEntityStatement(url: String, engine: HttpClientEngine) = runBlocking { - return@runBlocking OidFederationClient(engine).fetchEntityStatement(url) }