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 42 #35

Merged
merged 27 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ba581e3
feat: added KmsService and local KMS module
robertmathew Aug 23, 2024
0948b7a
fix: linked service layer to local KMS module
robertmathew Aug 23, 2024
26c7ce6
feat: Setup Spring JDBC for local kms
robertmathew Aug 28, 2024
a613730
fix: added missing properties file
robertmathew Aug 28, 2024
52b0c0d
fix: added missing function in LocalKmsDatabaseConnection
robertmathew Aug 28, 2024
e6d167f
fix: change to SQLDelight
robertmathew Aug 29, 2024
659c4c9
fix: Fixed binary data store for Postgres
robertmathew Aug 29, 2024
8d4b9fa
feat: Added query for delete key
robertmathew Aug 29, 2024
5b3e946
feat: changed dependencies
robertmathew Aug 29, 2024
273f96e
feat: moved JWT, Jwk to local kms module
robertmathew Aug 29, 2024
08e5e16
feat: linked generate key pair and sign functions
robertmathew Aug 30, 2024
ede0e94
fix: fixed verify function
robertmathew Aug 30, 2024
c22d139
fix: updated sign and verify function with ECkey
robertmathew Aug 30, 2024
5228299
fix: Fixed jvm test for sign and verify
robertmathew Sep 1, 2024
282aadf
fix: Fixed verify parameter
robertmathew Sep 2, 2024
623d2be
fix: Added JWK object into payload body
robertmathew Sep 2, 2024
e9147e7
fix: Added signing for EntityConfigurationStatement
robertmathew Sep 2, 2024
0bd7594
feat: create Entity Configuration Statement JWT
jcmelati Sep 2, 2024
1464a69
fix: add missing type
jcmelati Sep 2, 2024
e202b8d
fix: remove unnecessary statement
jcmelati Sep 2, 2024
8b0d253
fix: ci
jcmelati Sep 2, 2024
e944e88
fix: ci
jcmelati Sep 2, 2024
07da407
fix: ci
jcmelati Sep 2, 2024
c7a9030
merge develop into feature/OIDF-42
jcmelati Sep 2, 2024
6c59d06
fix: missing dto
jcmelati Sep 2, 2024
f4ac52d
fix: remove wrong attributes from openapi spec
jcmelati Sep 2, 2024
4e99b34
fix: bump openapi version
jcmelati Sep 3, 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
66 changes: 66 additions & 0 deletions modules/local-kms/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
plugins {
kotlin("multiplatform") version "2.0.0"
id("app.cash.sqldelight") version "2.0.2"
}

group = "com.sphereon.oid.fed.kms.local"
version = "0.1.0"

repositories {
mavenCentral()
mavenLocal()
google()
}

sqldelight {
databases {
create("Database") {
packageName = "com.sphereon.oid.fed.kms.local"
dialect("app.cash.sqldelight:postgresql-dialect:2.0.2")
schemaOutputDirectory = file("src/commonMain/resources/db/migration")
migrationOutputDirectory = file("src/commonMain/resources/db/migration")
deriveSchemaFromMigrations = true
migrationOutputFileFormat = ".sql"
srcDirs.from(
"src/commonMain/sqldelight"
)
}
}
}

kotlin {
jvm()

sourceSets {
commonMain {
dependencies {
api(projects.modules.openapi)
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.1")
}
}

jvmMain {
dependencies {
implementation("app.cash.sqldelight:jdbc-driver:2.0.2")
implementation("com.zaxxer:HikariCP:5.1.0")
implementation("org.postgresql:postgresql:42.7.3")
implementation("com.nimbusds:nimbus-jose-jwt:9.40")
}
}

// jsMain {
// dependencies {
// implementation(npm("typescript", "5.5.3"))
// implementation(npm("jose", "5.6.3"))
// implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1")
// }
// }

jvmTest {
dependencies {
implementation(kotlin("test-junit"))
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.sphereon.oid.fed.kms.local

class Constants {
companion object {
const val DATASOURCE_URL = "DATASOURCE_URL"
const val DATASOURCE_USER = "DATASOURCE_USER"
const val DATASOURCE_PASSWORD = "DATASOURCE_PASSWORD"
const val SQLITE_IS_NOT_SUPPORTED_IN_JVM = "SQLite is not supported in JVM"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.sphereon.oid.fed.kms.local

import com.sphereon.oid.fed.kms.local.database.LocalKmsDatabase
import com.sphereon.oid.fed.kms.local.jwk.generateKeyPair
import com.sphereon.oid.fed.openapi.models.JWTHeader
import com.sphereon.oid.fed.kms.local.jwt.sign
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject

class LocalKms {

private val database: LocalKmsDatabase = LocalKmsDatabase()

fun generateKey(keyId: String) {
val jwk = generateKeyPair()
database.insertKey(keyId = keyId, privateKey = jwk.toString())
}

fun sign(header: JWTHeader, payload: JsonObject, keyId: String): String {
val jwk = database.getKey(keyId)

return sign(header = header, payload = payload, key = Json.decodeFromString(jwk.private_key))
}

fun verify(token: String, keyId: String): Boolean {
TODO("Pending")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.sphereon.oid.fed.kms.local.database

import com.sphereon.oid.fed.kms.local.models.Keys

expect class LocalKmsDatabase() {
fun getKey(keyId: String): Keys
fun insertKey(keyId: String, privateKey: String)
fun deleteKey(keyId: String)
}

class KeyNotFoundException(message: String) : Exception(message)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.sphereon.oid.fed.kms.local.database

import app.cash.sqldelight.db.SqlDriver

expect class PlatformSqlDriver {
fun createPostgresDriver(url: String, username: String, password: String): SqlDriver
fun createSqliteDriver(path: String): SqlDriver
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.sphereon.oid.fed.common.jwk
package com.sphereon.oid.fed.kms.local.jwk

import com.sphereon.oid.fed.openapi.models.Jwk

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.sphereon.oid.fed.kms.local.jwt

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

expect fun sign(payload: JsonObject, header: JWTHeader, key: Jwk): 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,5 @@
CREATE TABLE Keys (
jcmelati marked this conversation as resolved.
Show resolved Hide resolved
id TEXT PRIMARY KEY,
private_key TEXT NOT NULL,
deleted_at TIMESTAMP
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
findAll:
SELECT * FROM Keys;

create:
INSERT INTO Keys (id, private_key) VALUES (?, ?) RETURNING *;

findById:
SELECT * FROM Keys WHERE id = ?;

delete:
UPDATE Keys SET deleted_at = CURRENT_TIMESTAMP WHERE id = ?;
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.sphereon.oid.fed.common.jwk
package com.sphereon.oid.fed.kms.local.jwk

import com.sphereon.oid.fed.common.jwt.Jose
import com.sphereon.oid.fed.kms.local.jwt.Jose
import com.sphereon.oid.fed.openapi.models.Jwk

@ExperimentalJsExport
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.sphereon.oid.fed.common.jwt
package com.sphereon.oid.fed.kms.local.jwt

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

Expand All @@ -26,17 +27,12 @@ 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, key: Jwk
): String {
val privateKey = opts["privateKey"] ?: throw IllegalArgumentException("JWK private key is required")
val privateKey = key.privateKey ?: throw IllegalArgumentException("JWK private key is required")

return Jose.SignJWT(JSON.parse<Any>(Json.encodeToString(payload)))
.setProtectedHeader(JSON.parse<Any>(Json.encodeToString(header)))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.sphereon.oid.fed.kms.local.database

import app.cash.sqldelight.db.SqlDriver
import com.sphereon.oid.fed.kms.local.Constants
import com.sphereon.oid.fed.kms.local.Database
import com.sphereon.oid.fed.kms.local.models.Keys


actual class LocalKmsDatabase {

private var database: Database

init {
val driver = getDriver()

database = Database(driver)
}

private fun getDriver(): SqlDriver {
return PlatformSqlDriver().createPostgresDriver(
System.getenv(Constants.DATASOURCE_URL),
System.getenv(Constants.DATASOURCE_USER),
System.getenv(Constants.DATASOURCE_PASSWORD)
)
}

actual fun getKey(keyId: String): Keys {
return database.keysQueries.findById(keyId).executeAsOneOrNull()
?: throw KeyNotFoundException("$keyId not found")
}

actual fun insertKey(keyId: String, privateKey: String) {
database.keysQueries.create(keyId, privateKey).executeAsOneOrNull()
}

actual fun deleteKey(keyId: String) {
database.keysQueries.delete(keyId)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.sphereon.oid.fed.kms.local.database

import app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.driver.jdbc.asJdbcDriver
import com.sphereon.oid.fed.kms.local.Constants
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource

actual class PlatformSqlDriver {
actual fun createPostgresDriver(url: String, username: String, password: String): SqlDriver {
val config = HikariConfig()
config.jdbcUrl = url
config.username = username
config.password = password

val dataSource = HikariDataSource(config)
return dataSource.asJdbcDriver()
}

actual fun createSqliteDriver(path: String): SqlDriver {
throw UnsupportedOperationException(Constants.SQLITE_IS_NOT_SUPPORTED_IN_JVM)
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.sphereon.oid.fed.common.jwk
package com.sphereon.oid.fed.kms.local.jwk

import com.nimbusds.jose.Algorithm
import com.nimbusds.jose.jwk.Curve
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
package com.sphereon.oid.fed.common.jwt
package com.sphereon.oid.fed.kms.local.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 com.sphereon.oid.fed.openapi.models.Jwk
import kotlinx.serialization.json.JsonObject

actual typealias JwtPayload = JWTClaimsSet
actual typealias JwtHeader = JWSHeader

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

val signer: JWSSigner = RSASSASigner(rsaJWK)

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

signedJWT.sign(signer)
Expand All @@ -44,4 +43,17 @@ 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()
}

//TODO: Double check the logic
fun Jwk.toRsaKey(): RSAKey {
return RSAKey.parse(this.toString())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.sphereon.oid.fed.kms.local.jwt

import com.nimbusds.jose.jwk.RSAKey
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 kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.encodeToJsonElement
import kotlin.test.Test
import kotlin.test.assertTrue

class JoseJwtTest {

//TODO Fix it
// @Test
// fun signTest() {
// val key = RSAKeyGenerator(2048).keyID("key1").generate()
// val entityStatement =
// EntityConfigurationStatement(iss = "test", sub = "test", exp = 111111, iat = 111111, jwks = JWKS())
// val payload: JsonObject = Json.encodeToJsonElement(entityStatement) as JsonObject
// val signature = sign(
// payload, JWTHeader(alg = "RS256", typ = "JWT", kid = key.keyID), mutableMapOf("key" to key)
// )
// assertTrue { signature.startsWith("ey") }
// }
//
// @Test
// fun verifyTest() {
// val kid = "key1"
// val key: RSAKey = RSAKeyGenerator(2048).keyID(kid).generate()
// val entityStatement =
// EntityConfigurationStatement(iss = "test", sub = "test", exp = 111111, iat = 111111, jwks = JWKS())
// val payload: JsonObject = Json.encodeToJsonElement(entityStatement) as JsonObject
// val signature = sign(
// payload, JWTHeader(alg = "RS256", typ = "JWT", kid = key.keyID), mutableMapOf("key" to key)
// )
// assertTrue { verify(signature, key, emptyMap()) }
// }
}
3 changes: 0 additions & 3 deletions modules/openid-federation-common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ kotlin {
dependencies {
implementation("io.ktor:ktor-client-core-jvm:$ktorVersion")
runtimeOnly("io.ktor:ktor-client-cio-jvm:$ktorVersion")
implementation("com.nimbusds:nimbus-jose-jwt:9.40")
}
}
val jvmTest by getting {
Expand Down Expand Up @@ -130,8 +129,6 @@ kotlin {
dependencies {
runtimeOnly("io.ktor:ktor-client-core-js:$ktorVersion")
runtimeOnly("io.ktor:ktor-client-js:$ktorVersion")
implementation(npm("typescript", "5.5.3"))
implementation(npm("jose", "5.6.3"))
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0-RC")
}
Expand Down
Loading
Loading