Skip to content

Commit

Permalink
chore: Trust Chain Validation refactoring - Build Trust Chain for tes…
Browse files Browse the repository at this point in the history
…ting
  • Loading branch information
Zoe Maas committed Sep 26, 2024
1 parent 599c984 commit 7d2ce75
Show file tree
Hide file tree
Showing 3 changed files with 343 additions and 113 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.sphereon.oid.fed.common.mapper
import com.sphereon.oid.fed.openapi.models.EntityConfigurationStatement
import com.sphereon.oid.fed.openapi.models.JWTHeader
import com.sphereon.oid.fed.openapi.models.JWTSignature
import com.sphereon.oid.fed.openapi.models.SubordinateStatement
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.decodeFromJsonElement
Expand All @@ -23,6 +24,10 @@ class JsonMapper {
decodeJWTComponents(jwtToken).payload.let { Json.decodeFromJsonElement(it)
}

fun mapSubordinateStatement(jwtToken: String): SubordinateStatement =
decodeJWTComponents(jwtToken).payload.let { Json.decodeFromJsonElement(it)
}

/*
* Used for mapping trust chain
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@ 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

class TrustChainValidation {

Expand All @@ -13,9 +19,9 @@ class TrustChainValidation {
engine: HttpClientEngine,
trustChains: MutableList<List<EntityConfigurationStatement>> = mutableListOf(),
trustChain: MutableList<EntityConfigurationStatement> = mutableListOf()
): List<List<EntityConfigurationStatement>>{
): List<List<EntityConfigurationStatement>> {
requestEntityStatement(partyBId, engine).run {
JsonMapper().mapEntityConfigurationStatement(this)?.let {
JsonMapper().mapEntityConfigurationStatement(this).let {
if (it.authorityHints.isNullOrEmpty()) {
trustChain.add(it)
trustChains.add(trustChain.map { content -> content.copy() })
Expand All @@ -36,71 +42,102 @@ class TrustChainValidation {
return trustChains
}

//
//// TODO must validate subordinate statements too
//fun validateTrustChain(jwts: List<String>): 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")
// }
//}
//
fun fetchSubordinateStatements(
entityConfigurationStatementsList: List<List<EntityConfigurationStatement>>,
engine: HttpClientEngine
): List<List<String>> {
val trustChains: MutableList<List<String>> = mutableListOf()
val trustChain: MutableList<String> = mutableListOf()
entityConfigurationStatementsList.forEach { entityConfigurationStatements ->
entityConfigurationStatements.forEach { it ->
it.metadata?.jsonObject?.get("federation_entity")?.jsonObject?.get("federation_fetch_endpoint")?.jsonPrimitive?.content.let { url ->
requestEntityStatement(url.toString(), engine).run {
trustChain.add(this)
}
}
}
trustChains.add(trustChain)
}
return trustChains
}

fun validateTrustChain(jwts: List<String>): Boolean {
val entityStatements = jwts.toMutableList()
val firstEntityConfiguration =
entityStatements.removeFirst().let { JsonMapper().mapEntityConfigurationStatement(it) }
val lastEntityConfiguration =
entityStatements.removeLast().let { JsonMapper().mapEntityConfigurationStatement(it) }
val subordinateStatements = entityStatements.map { JsonMapper().mapSubordinateStatement(it) }

if (firstEntityConfiguration.iss != firstEntityConfiguration.sub) {
throw IllegalArgumentException("Entity Configuration of the Trust Chain subject requires that iss is equal to sub")
}

if (!verify(jwts[0], retrieveJwk(firstEntityConfiguration))) {
throw IllegalArgumentException("Invalid signature")
}

subordinateStatements.forEachIndexed { index, current ->
val next =
if (index < subordinateStatements.size - 1) subordinateStatements[index + 1] else lastEntityConfiguration
when (next) {
is EntityConfigurationStatement ->
if (current.iss != next.sub) {
throw IllegalArgumentException("Entity Configuration of the Trust Chain subject requires that iss is equal to sub")
}

is 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 (lastEntityConfiguration.iss != firstEntityConfiguration.iss) {
throw IllegalArgumentException("Entity Configuration of the Trust Chain subject requires that iss is equal to the Entity Identifier of the Trust Anchor")
}
if (!verify(jwts[jwts.size - 1], retrieveJwk(lastEntityConfiguration))) {
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)
}
Expand Down
Loading

0 comments on commit 7d2ce75

Please sign in to comment.