Skip to content

Commit

Permalink
chore: Trust Chain validation implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Zoe Maas committed Aug 8, 2024
1 parent f246023 commit b948001
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -96,46 +96,106 @@ components:
type: object
x-tags:
- federation
required:
- kty
properties:
kty:
type: string
description: The "kty" (key type) parameter identifies the cryptographic algorithm family used with the key, such as "RSA" or "EC".
description: The key type (e.g., EC, RSA).
example: RSA
use:
crv:
type: string
description: The "use" (public key use) parameter identifies the intended use of the public key.
example: sig
key_ops:
description: The elliptic curve used (only for EC keys).
example: P-256
nullable: true
kid:
type: string
description: The key ID (optional).
example: 12345
nullable: true
x:
type: string
description: The X coordinate for EC keys (optional).
example: o-7zraXKDaoBte2PsuTXo-MSLzsyWdAElNptGgI4aH8
nullable: true
y:
type: string
description: The "key_ops" (key operations) parameter identifies the operation(s) for which the key is intended to be used.
example: encrypt
description: The Y coordinate for EC keys (optional).
example: Xr_wCzJ1XnsgAIV5qHruzSwaNnwy87UjmevVklTpIv8
nullable: true
n:
type: string
description: The modulus for RSA keys.
example: modulus_value
nullable: true
e:
type: string
description: The exponent for RSA keys.
example: AQAB
nullable: true
alg:
type: string
description: The "alg" (algorithm) parameter identifies the algorithm intended for use with the key.
example: RS256
kid:
description: The algorithm associated with the key.
example: ES256
use:
type: string
description: The "kid" (key ID) parameter is used to match a specific key.
example: 1
description: The intended use of the key (e.g., sig, enc).
example: sig
nullable: true
x5u:
type: string
description: The "x5u" (X.509 URL) parameter is a URI that refers to a resource for an X.509 public key certificate or certificate chain.
format: uri
description: A URL that points to an X.509 public key certificate or certificate chain.
example: https://example.com/cert.pem
nullable: true
x5c:
type: array
description: The "x5c" (X.509 certificate chain) parameter contains a chain of one or more PKIX certificates.
items:
type: string
example:
- MIIDQzCCA...+3whvMF1XEt0K2bA8wpPmSTPgQ==
description: A base64-encoded string representing an X.509 certificate.
example: MIICoTCCAYkCAQ...
description: The X.509 certificate chain.
nullable: true
x5t:
type: string
description: The "x5t" (X.509 certificate SHA-1 thumbprint) parameter is a base64url-encoded SHA-1 thumbprint of the DER encoding of an X.509 certificate.
example: 0fVuYF8jJ3onI+9Zk2/Iy+Oh5ZpE
x5t#S256:
description: The SHA-1 thumbprint of the X.509 certificate.
example: dGhpcyBpcyBqdXN0IGEgdGh1bWJwcmludA
nullable: true
x5tS256:
type: string
description: The SHA-256 thumbprint of the X.509 certificate.
example: sM4KhEI1Y2Sb6-EVr6tJabmJuoP-ZE...
nullable: true
d:
type: string
description: The private key value (for RSA and EC keys).
example: base64url_encoded_private_key
nullable: true
p:
type: string
description: The first prime factor (for RSA private key).
example: base64url_encoded_p
nullable: true
q:
type: string
description: The "x5t#S256" (X.509 certificate SHA-256 thumbprint) parameter is a base64url-encoded SHA-256 thumbprint of the DER encoding of an X.509 certificate.
example: 1MvI4/VhnEzTz7Jo/0Q/d/jI3rE7IMoMT34wvAjyLvs
description: The second prime factor (for RSA private key).
example: base64url_encoded_q
nullable: true
dp:
type: string
description: The first factor CRT exponent (for RSA private key).
example: base64url_encoded_dp
nullable: true
dq:
type: string
description: The second factor CRT exponent (for RSA private key).
example: base64url_encoded_dq
nullable: true
qi:
type: string
description: The first CRT coefficient (for RSA private key).
example: base64url_encoded_qi
nullable: true
revoked:
$ref: '#/components/schemas/JWTRevoked'

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.sphereon.oid.fed.common.op

import com.sphereon.oid.fed.common.httpclient.OidFederationClient
import com.sphereon.oid.fed.common.jwt.verify
import com.sphereon.oid.fed.common.mapper.JsonMapper
import com.sphereon.oid.fed.openapi.models.EntityStatement
import io.ktor.client.engine.*
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import java.time.OffsetDateTime

suspend fun readAuthorityHints(jwt: String, engine: HttpClientEngine) = coroutineScope {
val entityStatementList = mutableListOf<EntityStatement?>()
val entityStatement = JsonMapper().mapEntityStatement(jwt)
launch {
entityStatement?.authorityHints?.forEach {
val intermediateJwt = requestEntityConfiguration(it, engine)
val intermediateES = JsonMapper().mapEntityStatement(intermediateJwt)
entityStatementList.add(intermediateES)
//readAuthorityHints(intermediateJwt, engine)
}
}
return@coroutineScope entityStatementList
}

//TODO fetch entity configuration
//TODO iterate through intermediates listed in authority_hints, ignore unknown trust anchor, repeat if intermediate

fun validateEntityStatement(jwts: List<String>) {
val entityStatements = jwts.map { JsonMapper().mapEntityStatement(it) }
if(entityStatements[0]?.iss != entityStatements[0]?.sub) {
throw IllegalArgumentException("Entity Configuration of the Trust Chain subject requires that iss is equal to sub")
}
if (!verify(jwts[0], entityStatements[0]?.jwks?.let { it.propertyKeys?.first()} as Any , emptyMap())) {
throw IllegalArgumentException("Invalid signature")
}
entityStatements.forEachIndexed { index, element ->
if(element?.iss != entityStatements[index + 1]?.sub) {
throw IllegalArgumentException("Entity Configuration of the Trust Chain subject requires that iss is equal to sub")
}
val offsetTime = OffsetDateTime.now()
if(element?.iat?.compareTo(offsetTime.toEpochSecond().toInt())!! > 0) {
throw IllegalArgumentException("Invalid iat")
}
if(element.exp?.compareTo(offsetTime.toEpochSecond().toInt())!! < 0) {
throw IllegalArgumentException("Invalid exp")
}

if(!verify(jwts[index], entityStatements[index +1]?.jwks?.let { it.propertyKeys?.first()} as Any, emptyMap())) {
throw IllegalArgumentException("Invalid signature")
}
}
if(entityStatements[entityStatements.size -1]?.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], entityStatements[entityStatements.size - 1]?.jwks?.let { it.propertyKeys?.first()} as Any , emptyMap())) {
throw IllegalArgumentException("Invalid signature")
}
}

private suspend fun requestEntityConfiguration(url: String, engine: HttpClientEngine) = coroutineScope {
var result: String? = null
launch {
result = OidFederationClient(engine).fetchEntityStatement(url)
}
return@coroutineScope result as String
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.sphereon.oid.fed.common.op

import com.sphereon.oid.fed.common.mapper.JsonMapper
import com.sphereon.oid.fed.openapi.models.EntityStatement
import io.ktor.client.engine.mock.*
import io.ktor.http.*
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals

class TrustChainValidationTest {

private val entityStatementJwt = """
eyJhbGciOiJSUzI1NiIsInR5cCI6ImVudGl0eS1zdGF0ZW1lbnQrand0Iiwia2lkIjoiTnpiTHNYaDh1RENjZC02TU53WEY0V183bm9XWEZaQWZIa3ha
c1JHQzlYcyJ9.eyJpc3MiOiJodHRwczovL2ZlaWRlLm5vIiwic3ViIjoiaHR0cHM6Ly9udG51Lm5vIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTY
yOTgwMjIsImp3a3MiOnsia2V5cyI6W3sia3R5IjoiUlNBIiwiYWxnIjoiUlMyNTYiLCJ1c2UiOiJzaWciLCJraWQiOiJOemJMc1hoOHVEQ2NkLTZNTnd
YRjRXXzdub1dYRlpBZkhreFpzUkdDOVhzIiwibiI6InBuWEJPdXNFQU51dWc2ZXdlemI5Sl8uLi4iLCJlIjoiQVFBQiJ9XX0sIm1ldGFkYXRhIjp7Im9
wZW5pZF9wcm92aWRlciI6eyJpc3N1ZXIiOiJodHRwczovL250bnUubm8iLCJvcmdhbml6YXRpb25fbmFtZSI6Ik5UTlUifSwib2F1dGhfY2xpZW50Ijp
7Im9yZ2FuaXphdGlvbl9uYW1lIjoiTlROVSJ9fSwibWV0YWRhdGFfcG9saWN5Ijp7Im9wZW5pZF9wcm92aWRlciI6eyJpZF90b2tlbl9zaWduaW5nX2F
sZ192YWx1ZXNfc3VwcG9ydGVkIjp7InN1YnNldF9vZiI6WyJSUzI1NiIsIlJTMzg0IiwiUlM1MTIiXX0sIm9wX3BvbGljeV91cmkiOnsicmVnZXhwIjo
iXmh0dHBzOi8vW1xcdy1dK1xcLmV4YW1wbGVcXC5jb20vW1xcdy1dK1xcLmh0bWwifX0sIm9hdXRoX2NsaWVudCI6eyJncmFudF90eXBlcyI6eyJvbmV
fb2YiOlsiYXV0aG9yaXphdGlvbl9jb2RlIiwiY2xpZW50X2NyZWRlbnRpYWxzIl19fX0sImNvbnN0cmFpbnRzIjp7Im1heF9wYXRoX2xlbmd0aCI6Mn0
sImNyaXQiOlsianRpIl0sIm1ldGFkYXRhX3BvbGljeV9jcml0IjpbInJlZ2V4cCJdLCJzb3VyY2VfZW5kcG9pbnQiOiJodHRwczovL2ZlaWRlLm5vL2Z
lZGVyYXRpb25fYXBpL2ZldGNoIiwianRpIjoiN2wybG5jRmRZNlNsaE5pYSJ9.cb0Xxqskr77xvJcF_rOfe1LiDjI-F9W-M7TMqEAJSYVrxEZAcaQfrL
wIyeyh_gE3_KVt1bBpdod1XPG9Eied5oqwuf6_TPjrtKI6W9pdNwzXExwDSjUCk726UIxhOkakViBFrtptxB0fz_JCwtqOHP7fhdcJY2KhpUNJQjlJkl
00Bh83MwYuTcYwcbPkr9zl3hf38dDtziZgqOs7Ig9UZUw4FCajC4fcho88NIoBDM3XajfiIblDKd8B-sSJUz8WAJxSzWnD9sLPpbwe5qjOwSms3gSiTg
Jxl_8N7QV9-bSzEshSU0XvpMPBfP8Hl3RJkgJ7a9Ng6PKDa0eqFLLLQA
""".replace("[\\n\\s]+".toRegex(), "")

private val mockEngine = MockEngine { request ->
when(request.url) {
Url("https://www.example.com/.well-known/openid-federation") -> respond(entityStatementJwt)
Url("https://www.example.com/entity-statement") -> respond(entityStatementJwt)
else -> error("Unhandled ${request.url}")
}
}

@Test
fun readAuthorityHintsTest() = runTest {
val entityStatements = listOf(JsonMapper().mapEntityStatement(entityStatementJwt))
assertEquals(entityStatements, readAuthorityHints(entityStatementJwt, mockEngine))
}
}

0 comments on commit b948001

Please sign in to comment.