Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/oidf 15 #30

Closed
wants to merge 56 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
2105846
feat: Implemented KMS, JWKS generation and JWT sign
Jul 12, 2024
b3f03c1
fix: Test dependencies
Jul 12, 2024
14c6a80
feat: Created sign and verify jwt functions
Jul 17, 2024
6b0a610
refactor: Added trailing new line to the files
Jul 17, 2024
d5c34e9
fix: Removed some targets temporarily to fix build issues.
Jul 18, 2024
440300c
refactor: made the second paramenter of functions a Map without defau…
Jul 23, 2024
bfb9cd9
Merge branch 'develop' of github.com:Sphereon-Opensource/OpenID-Feder…
Jul 23, 2024
859d788
refactor: Fixed build issues and removed commented-out code
Jul 23, 2024
3fb5bb6
fix: Fixed failing test and null pointer exception
Jul 23, 2024
56ad998
chore: Removed redundant HTTPCache
Jul 30, 2024
7bd61ba
chore: Uncommented ios targets back
Jul 30, 2024
6aec24c
refactor: refactored serializeNullable()
Jul 30, 2024
3ed536d
refactor: refactored deserialize()
Jul 30, 2024
dc19f4c
refactor: refactored OutgoingEntityStatementContent.bytes()
Jul 30, 2024
c7537cd
refactor: refactored the tests to use assertEquals()
Jul 30, 2024
3321a0b
Merge branch 'develop' of github.com:/Sphereon-Opensource/OpenID-Fede…
Jul 30, 2024
4222e59
refactor: Fixed dependencies and made the protectedHeader a param
Jul 30, 2024
bc3ecc8
refactor: Fixed code formatting
Jul 30, 2024
fe0df4c
Merge branch 'develop' of github.com:/Sphereon-Opensource/OpenID-Fede…
Jul 30, 2024
8e55b2e
refactor: Changed the response body to jwt string
Jul 31, 2024
0354192
Merge branch 'refs/heads/develop' into feature/OIDF-7
Jul 31, 2024
c7b90d3
refactor: Removed unnecessary converter
Jul 31, 2024
db8e116
refactor: Made JWT payload and header classes to be used as input
Aug 2, 2024
4848ba3
fix: add missing repositories for windows (#22)
jcmelati Aug 5, 2024
44dbf40
feat: implement jwk persistence
jcmelati Aug 6, 2024
25c1752
fix: remove unused statement
jcmelati Aug 6, 2024
2346fb6
fix: github CI
jcmelati Aug 6, 2024
a4c56ff
Merge branch 'feature/OIDF-42' into feature/OIDF-54
jcmelati Aug 6, 2024
0052c3c
feat/OIDF-51 - Implement Persistence Module (#21)
jcmelati Aug 6, 2024
01c7152
fix: add missing entity to openapi spec
jcmelati Aug 6, 2024
8661c36
Merge branch 'feature/OIDF-54' of github.com:Sphereon-Opensource/Open…
jcmelati Aug 6, 2024
7fc6982
feat: persist generated keys
jcmelati Aug 6, 2024
9d0f0e8
fix: typo
jcmelati Aug 6, 2024
ce0d261
fix: missing deps
jcmelati Aug 7, 2024
3114595
fix: ci docker command
jcmelati Aug 7, 2024
e754b13
fix: dependency
jcmelati Aug 7, 2024
5091d9c
fix: remove unnecessary statement
jcmelati Aug 7, 2024
428401c
feat: abstract jwk to its own module
jcmelati Aug 7, 2024
9b7a530
feat: Added POST call
robertmathew Aug 8, 2024
3321886
feat: encrypt private keys when saving to database
jcmelati Aug 12, 2024
a26cd53
feat: add note to README regarding usage of Local KMS in prod envs
jcmelati Aug 12, 2024
dd242a3
fix: adapt key encryption test cases for when APP_KEY is null
jcmelati Aug 12, 2024
979d4cf
fix: adjust function name
jcmelati Aug 12, 2024
aaa8914
fix: add kotlin-js-store to gitignore
jcmelati Aug 12, 2024
1787ee6
fix: clean common gradle file
jcmelati Aug 12, 2024
7a58f32
fix: disable android build
jcmelati Aug 12, 2024
168a824
merge feature/OIDF-7
jcmelati Aug 13, 2024
88e2c90
fix: remove js implementation from services
jcmelati Aug 13, 2024
43b873a
Merge branch 'feature/OIDF-42' into feature/OIDF-15
robertmathew Aug 14, 2024
e62e736
Merge branch 'feature/OIDF-54' into feature/OIDF-15
robertmathew Aug 14, 2024
185a88e
feat: created KMS interface
robertmathew Aug 16, 2024
986cfcd
fix: added missing return
robertmathew Aug 16, 2024
171b0e4
fix: using payload for POST for now
robertmathew Aug 16, 2024
bb94439
Merge branch 'develop' into feature/OIDF-15
robertmathew Aug 19, 2024
ba25e3c
refactor: Fix imports
robertmathew Aug 19, 2024
19e38b3
fix: entity statement to entity configuration statement
robertmathew Aug 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package com.sphereon.oid.fed.server.admin

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.ComponentScan

@SpringBootApplication
@ComponentScan(basePackages = ["com.sphereon.oid.fed.services"])
class Application

fun main(args: Array<String>) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.sphereon.oid.fed.common.httpclient

import com.sphereon.oid.fed.openapi.models.JWTHeader
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.*
Expand All @@ -10,20 +11,28 @@ import io.ktor.client.plugins.logging.*
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.http.*
import io.ktor.http.HttpMethod.Companion.Delete
import io.ktor.http.HttpMethod.Companion.Get
import io.ktor.http.HttpMethod.Companion.Post
import io.ktor.utils.io.core.*
import kotlinx.serialization.json.JsonObject

class OidFederationClient(
engine: HttpClientEngine,
// TODO need KMS implementation
//private val kmsInterface: KMSInterface,
private val isRequestAuthenticated: Boolean = false,
private val isRequestCached: Boolean = false
) {
private val client: HttpClient = HttpClient(engine) {
install(HttpCache)
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.INFO
logger = object : Logger {
override fun log(message: String) {
com.sphereon.oid.fed.common.logging.Logger.info("API", message)
}
}
level = LogLevel.ALL
}
if (isRequestAuthenticated) {
install(Auth) {
Expand All @@ -41,13 +50,12 @@ class OidFederationClient(
}

suspend fun fetchEntityStatement(
url: String,
httpMethod: HttpMethod = Get,
parameters: Parameters = Parameters.Empty
url: String, httpMethod: HttpMethod = Get, postParameters: PostEntityParameters? = null
): String {

return when (httpMethod) {
Get -> getEntityStatement(url)
Post -> postEntityStatement(url, parameters)
Post -> postEntityStatement(url, postParameters)
else -> throw IllegalArgumentException("Unsupported HTTP method: $httpMethod")
}
}
Expand All @@ -56,11 +64,50 @@ class OidFederationClient(
return client.use { it.get(url).body() }
}

private suspend fun postEntityStatement(url: String, parameters: Parameters): String {
private suspend fun postEntityStatement(url: String, postParameters: PostEntityParameters?): String {
val body = postParameters?.let { params ->
// TODO need KMS implementation
//kmsInterface.createJWT(header = params.header, payload = params.payload)
params.payload.toString()
}

return client.use {
it.post(url) {
setBody(body)
}.body()
}
}

suspend fun fetchAccount(
url: String, httpMethod: HttpMethod = Get, parameters: Parameters = Parameters.Empty
): String {
return when (httpMethod) {
Get -> getAccount(url)
Post -> postAccount(url, parameters)
Delete -> deleteAccount(url)
else -> throw IllegalArgumentException("Unsupported HTTP method: $httpMethod")
}
}

private suspend fun getAccount(url: String): String {
return client.use { it.get(url).body() }
}

private suspend fun postAccount(url: String, parameters: Parameters): String {
return client.use {
it.post(url) {
setBody(FormDataContent(parameters))
}.body()
}
}

private suspend fun deleteAccount(url: String): String {
return client.use { it.delete(url).body() }
}


// Data class for POST parameters
data class PostEntityParameters(
val payload: JsonObject, val header: JWTHeader
)
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.sphereon.oid.fed.common.jwt

expect class JwtHeader
expect class JwtPayload
import com.sphereon.oid.fed.openapi.models.JWTHeader
import kotlinx.serialization.json.JsonObject

expect fun sign(payload: JwtPayload, header: JwtHeader, opts: Map<String, Any>): String
expect fun sign(payload: JsonObject, header: JWTHeader, opts: Map<String, Any>): String
expect fun verify(jwt: String, key: Any, opts: Map<String, Any>): Boolean
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.sphereon.oid.fed.common.jwt

import com.sphereon.oid.fed.openapi.models.JWTHeader
import kotlinx.serialization.json.JsonObject

interface KMSInterface {

fun createJWT(header: JWTHeader, payload: JsonObject): String

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.sphereon.oid.fed.common.jwt

import com.sphereon.oid.fed.openapi.models.JWTHeader
import kotlinx.serialization.json.JsonObject

class KMSInterfaceImpl: KMSInterface {

override fun createJWT(header: JWTHeader, payload: JsonObject):String {

// TODO get key and pass it
val jwt = sign(payload = payload, header = header, opts = mapOf())
return jwt
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class JsonMapper {
* Used for mapping JWT token to EntityStatement object
*/
fun mapEntityStatement(jwtToken: String): EntityConfigurationStatement? =
decodeJWTComponents(jwtToken)?.payload?.let { Json.decodeFromJsonElement(it) }
decodeJWTComponents(jwtToken).payload.let { Json.decodeFromJsonElement(it) }

/*
* Used for mapping trust chain
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.sphereon.oid.fed.common.jwt

import com.sphereon.oid.fed.openapi.models.EntityConfigurationStatement
import com.sphereon.oid.fed.openapi.models.JWTHeader
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject

@JsModule("jose")
@JsNonModule
Expand All @@ -26,15 +26,10 @@ external object Jose {
fun jwtVerify(jwt: String, key: Any, options: dynamic = definedExternally): dynamic
}

actual typealias JwtPayload = EntityConfigurationStatement
actual typealias JwtHeader = JWTHeader

@ExperimentalJsExport
@JsExport
actual fun sign(
payload: JwtPayload,
header: JwtHeader,
opts: Map<String, Any>
payload: JsonObject, header: JWTHeader, opts: Map<String, Any>
): String {
val privateKey = opts["privateKey"] ?: throw IllegalArgumentException("JWK private key is required")

Expand All @@ -46,9 +41,7 @@ actual fun sign(
@ExperimentalJsExport
@JsExport
actual fun verify(
jwt: String,
key: Any,
opts: Map<String, Any>
jwt: String, key: Any, opts: Map<String, Any>
): Boolean {
return Jose.jwtVerify(jwt, key, opts)
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package com.sphereon.oid.fed.common.jwt

import com.sphereon.oid.fed.common.jwt.Jose.generateKeyPair
import com.sphereon.oid.fed.openapi.models.EntityConfigurationStatement
import com.sphereon.oid.fed.openapi.models.JWKS
import com.sphereon.oid.fed.openapi.models.JWTHeader
import kotlinx.coroutines.async
import kotlinx.coroutines.await
import kotlinx.coroutines.test.runTest
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.encodeToJsonElement
import kotlin.js.Promise
import kotlin.test.Test
import kotlin.test.assertTrue
Expand All @@ -14,10 +19,13 @@ class JoseJwtTest {
@Test
fun signTest() = runTest {
val keyPair = (generateKeyPair("RS256") as Promise<dynamic>).await()
val entityStatement =
EntityConfigurationStatement(iss = "test", sub = "test", exp = 111111, iat = 111111, jwks = JWKS())
val payload: JsonObject = Json.encodeToJsonElement(entityStatement) as JsonObject
val result = async {
sign(
JwtPayload(iss = "test", sub = "test", exp = 111111, iat = 111111, jwks = JWKS()),
JwtHeader(typ = "JWT", alg = "RS256", kid = "test"),
payload,
JWTHeader(typ = "JWT", alg = "RS256", kid = "test"),
mutableMapOf("privateKey" to keyPair.privateKey)
)
}
Expand All @@ -28,9 +36,12 @@ class JoseJwtTest {
@Test
fun verifyTest() = runTest {
val keyPair = (generateKeyPair("RS256") as Promise<dynamic>).await()
val entityStatement =
EntityConfigurationStatement(iss = "test", sub = "test", exp = 111111, iat = 111111, jwks = JWKS())
val payload: JsonObject = Json.encodeToJsonElement(entityStatement) as JsonObject
val signed = (sign(
JwtPayload(iss = "test", sub = "test", exp = 111111, iat = 111111, jwks = JWKS()),
JwtHeader(typ = "JWT", alg = "RS256", kid = "test"),
payload,
JWTHeader(typ = "JWT", alg = "RS256", kid = "test"),
mutableMapOf("privateKey" to keyPair.privateKey)
) as Promise<dynamic>).await()
val result = async { verify(signed, keyPair.publicKey, emptyMap()) }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,27 @@
package com.sphereon.oid.fed.common.jwt

import com.nimbusds.jose.JWSHeader
import com.nimbusds.jose.JWSSigner
import com.nimbusds.jose.JWSVerifier
import com.nimbusds.jose.*
import com.nimbusds.jose.crypto.RSASSASigner
import com.nimbusds.jose.crypto.RSASSAVerifier
import com.nimbusds.jose.jwk.RSAKey
import com.nimbusds.jwt.JWTClaimsSet
import com.nimbusds.jwt.SignedJWT
import com.sphereon.oid.fed.openapi.models.JWTHeader
import kotlinx.serialization.json.JsonObject

actual typealias JwtPayload = JWTClaimsSet
actual typealias JwtHeader = JWSHeader

actual fun sign(
payload: JwtPayload,
header: JwtHeader,
payload: JsonObject,
header: JWTHeader,
opts: Map<String, Any>
): String {
val rsaJWK = opts["key"] as RSAKey? ?: throw IllegalArgumentException("The RSA key pair is required")

val signer: JWSSigner = RSASSASigner(rsaJWK)

val signedJWT = SignedJWT(
header,
payload
header.toJWSHeader(),
JWTClaimsSet.parse(payload.toString())
)

signedJWT.sign(signer)
Expand All @@ -44,4 +42,12 @@ actual fun verify(
} catch (e: Exception) {
throw Exception("Couldn't verify the JWT Signature: ${e.message}", e)
}
}
}

fun JWTHeader.toJWSHeader(): JWSHeader {
val type = typ
return JWSHeader.Builder(JWSAlgorithm.parse(alg)).apply {
type(JOSEObjectType(type))
keyID(kid)
}.build()
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.sphereon.oid.fed.common.httpclient

import com.sphereon.oid.fed.openapi.models.*
import com.nimbusds.jose.jwk.gen.RSAKeyGenerator
import com.sphereon.oid.fed.openapi.models.EntityConfigurationStatement
import com.sphereon.oid.fed.openapi.models.JWKS
import com.sphereon.oid.fed.openapi.models.JWTHeader
import io.ktor.client.engine.mock.*
import io.ktor.http.*
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.encodeToJsonElement
import kotlin.test.Test
import kotlin.test.assertEquals

Expand Down Expand Up @@ -38,7 +42,9 @@ class OidFederationClientTest {
fun testGetEntityStatement() {
runBlocking {
val client = OidFederationClient(mockEngine)
val response = client.fetchEntityStatement("https://www.example.com?iss=https://edugain.org/federation&sub=https://openid.sunet.se", HttpMethod.Get)
val response = client.fetchEntityStatement(
"https://www.example.com?iss=https://edugain.org/federation&sub=https://openid.sunet.se", HttpMethod.Get
)
assertEquals(jwt, response)
}
}
Expand All @@ -47,11 +53,20 @@ class OidFederationClientTest {
fun testPostEntityStatement() {
runBlocking {
val client = OidFederationClient(mockEngine)
val response = client.fetchEntityStatement("https://www.example.com", HttpMethod.Post,
Parameters.build {
append("iss","https://edugain.org/federation")
append("sub","https://openid.sunet.se")
})
val key = RSAKeyGenerator(2048).keyID("key1").generate()
val entityStatement = EntityConfigurationStatement(
iss = "https://edugain.org/federation",
sub = "https://openid.sunet.se",
exp = 111111,
iat = 111111,
jwks = JWKS()
)
val payload: JsonObject = Json.encodeToJsonElement(entityStatement) as JsonObject
val response = client.fetchEntityStatement(
"https://www.example.com", HttpMethod.Post, OidFederationClient.PostEntityParameters(
payload = payload, header = JWTHeader(typ = "JWT", alg = "RS256", kid = key.keyID)
)
)
assertEquals(jwt, response)
}
}
Expand Down
Loading
Loading