Skip to content

Commit

Permalink
Merge branch 'refs/heads/feature/OIDF-42' into feature/OIDF-53
Browse files Browse the repository at this point in the history
# Conflicts:
#	modules/openid-federation-common/build.gradle.kts
  • Loading branch information
Zoe Maas committed Aug 7, 2024
2 parents c7b90d3 + 4848ba3 commit f246023
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 66 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
java-version: 17

- name: Build the stack
run: docker-compose -f docker-compose.yaml up -d
run: docker compose -f docker-compose.yaml up -d
env:
DATASOURCE_USER: ${{ secrets.DATASOURCE_USER }}
DATASOURCE_PASSWORD: ${{ secrets.DATASOURCE_PASSWORD }}
Expand Down
140 changes: 75 additions & 65 deletions modules/openid-federation-common/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig

plugins {
Expand All @@ -11,9 +8,16 @@ plugins {

val ktorVersion = "2.3.11"

repositories {
mavenCentral()
google()
}

kotlin {
@OptIn(ExperimentalWasmDsl::class)
jvm()

// wasmJs is not available yet for ktor until v3.x is released which is still in alpha
// @OptIn(ExperimentalWasmDsl::class)
js {
browser {
commonWebpackConfig {
Expand All @@ -31,32 +35,30 @@ kotlin {
}
}

// wasmJs is not available yet for ktor until v3.x is released which is still in alpha
// TODO Should be placed back at a later point in time: https://sphereon.atlassian.net/browse/OIDF-50
// androidTarget {
// @OptIn(ExperimentalKotlinGradlePluginApi::class)
// compilerOptions {
// jvmTarget.set(JvmTarget.JVM_11)
// }
// }

androidTarget {
@OptIn(ExperimentalKotlinGradlePluginApi::class)
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
}

iosX64()
iosArm64()
iosSimulatorArm64()

jvm()
// iosX64()
// iosArm64()
// iosSimulatorArm64()
// androidTarget()

sourceSets {
val commonMain by getting {
dependencies {
implementation("com.sphereon.oid.fed:openapi:0.1.0-SNAPSHOT")
api(projects.modules.openapi)
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-logging:$ktorVersion")
implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
implementation("io.ktor:ktor-client-auth:$ktorVersion")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.1")
implementation(libs.kermit.logging)
}
}
Expand All @@ -65,73 +67,81 @@ kotlin {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
implementation("io.ktor:ktor-client-mock:$ktorVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0-RC")
}
}
val jvmMain by getting {
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 {
dependencies {
implementation(kotlin("test-junit"))
}
}

val androidMain by getting {
dependencies {
implementation("io.ktor:ktor-client-core-jvm:$ktorVersion")
implementation("io.ktor:ktor-client-cio-jvm:$ktorVersion")
}
}
val androidUnitTest by getting {
dependencies {
implementation(kotlin("test-junit"))
}
}

val iosMain by creating {
dependsOn(commonMain)
}
val iosX64Main by getting {
dependsOn(iosMain)
dependencies {
implementation("io.ktor:ktor-client-core-iosx64:$ktorVersion")
implementation("io.ktor:ktor-client-cio-iosx64:$ktorVersion")
}
}
val iosArm64Main by getting {
dependsOn(iosMain)
dependencies {
implementation("io.ktor:ktor-client-core-iosarm64:$ktorVersion")
implementation("io.ktor:ktor-client-cio-iosarm64:$ktorVersion")
}
}
val iosSimulatorArm64Main by getting {
dependsOn(iosMain)
dependencies {
implementation("io.ktor:ktor-client-core-iossimulatorarm64:$ktorVersion")
implementation("io.ktor:ktor-client-cio-iossimulatorarm64:$ktorVersion")
}
}

val iosTest by creating {
dependsOn(commonTest)
dependencies {
implementation(kotlin("test"))
}
}
// TODO Should be placed back at a later point in time: https://sphereon.atlassian.net/browse/OIDF-50
// val androidMain by getting {
// dependencies {
// implementation("io.ktor:ktor-client-core-jvm:$ktorVersion")
// implementation("io.ktor:ktor-client-cio-jvm:$ktorVersion")
// }
// }
// val androidUnitTest by getting {
// dependencies {
// implementation(kotlin("test-junit"))
// }
// }

// val iosMain by creating {
// dependsOn(commonMain)
// dependencies {
// implementation("io.ktor:ktor-client-core-ios:$ktorVersion")
// }
// }
// val iosX64Main by getting {
// dependsOn(iosMain)
// dependencies {
// implementation("io.ktor:ktor-client-core-iosx64:$ktorVersion")
// implementation("io.ktor:ktor-client-cio-iosx64:$ktorVersion")
// }
// }
// val iosArm64Main by getting {
// dependsOn(iosMain)
// dependencies {
// implementation("io.ktor:ktor-client-core-iosarm64:$ktorVersion")
// implementation("io.ktor:ktor-client-cio-iosarm64:$ktorVersion")
// }
// }
// val iosSimulatorArm64Main by getting {
// dependsOn(iosMain)
// dependencies {
// implementation("io.ktor:ktor-client-core-iossimulatorarm64:$ktorVersion")
// implementation("io.ktor:ktor-client-cio-iossimulatorarm64:$ktorVersion")
// }
// }

// val iosTest by creating {
// dependsOn(commonTest)
// dependencies {
// implementation(kotlin("test"))
// }
// }

val jsMain by getting {
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")
}
}

val jsTest by getting {
dependsOn(commonTest)
dependencies {
implementation(kotlin("test-js"))
implementation(kotlin("test-annotations-common"))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.sphereon.oid.fed.common.jwt

expect class JwtHeader
expect class JwtPayload

expect fun sign(payload: JwtPayload, 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,51 @@
package com.sphereon.oid.fed.common.jwt

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

@JsModule("jose")
@JsNonModule
external object Jose {
class SignJWT {
constructor(payload: dynamic) {
definedExternally
}
fun setProtectedHeader(protectedHeader: dynamic): SignJWT {
definedExternally
}
fun sign(key: Any?, signOptions: Any?): String {
definedExternally
}
}
fun generateKeyPair(alg: String, options: dynamic = definedExternally): dynamic
fun jwtVerify(jwt: String, key: Any, options: dynamic = definedExternally): dynamic
}

actual typealias JwtPayload = EntityStatement
actual typealias JwtHeader = JWTHeader

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

return Jose.SignJWT(JSON.parse<Any>(Json.encodeToString(payload)))
.setProtectedHeader(JSON.parse<Any>(Json.encodeToString(header)))
.sign(key = privateKey, signOptions = opts)
}

@ExperimentalJsExport
@JsExport
actual fun verify(
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
@@ -0,0 +1,35 @@
package com.sphereon.oid.fed.common.jwt

import com.sphereon.oid.fed.common.jwt.Jose.generateKeyPair
import kotlinx.coroutines.async
import kotlinx.coroutines.await
import kotlinx.coroutines.test.runTest
import kotlin.js.Promise
import kotlin.test.Test
import kotlin.test.assertTrue

class JoseJwtTest {
@OptIn(ExperimentalJsExport::class)
@Test
fun signTest() = runTest {
val keyPair = (generateKeyPair("RS256") as Promise<dynamic>).await()
val result = async {
sign(
JwtPayload(iss="test"),
JwtHeader(typ="JWT",alg="RS256",kid="test"),
mutableMapOf("privateKey" to keyPair.privateKey)) }
assertTrue((result.await() as Promise<String>).await().startsWith("ey"))
}

@OptIn(ExperimentalJsExport::class)
@Test
fun verifyTest() = runTest {
val keyPair = (generateKeyPair("RS256") as Promise<dynamic>).await()
val signed = (sign(
JwtPayload(iss="test"),
JwtHeader(typ="JWT",alg="RS256",kid="test"),
mutableMapOf("privateKey" to keyPair.privateKey)) as Promise<dynamic>).await()
val result = async { verify(signed, keyPair.publicKey, emptyMap()) }
assertTrue((result.await()))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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.crypto.RSASSASigner
import com.nimbusds.jose.crypto.RSASSAVerifier
import com.nimbusds.jose.jwk.RSAKey
import com.nimbusds.jwt.JWTClaimsSet
import com.nimbusds.jwt.SignedJWT

actual typealias JwtPayload = JWTClaimsSet
actual typealias JwtHeader = JWSHeader

actual fun sign(
payload: JwtPayload,
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
)

signedJWT.sign(signer)
return signedJWT.serialize()
}

actual fun verify(
jwt: String,
key: Any,
opts: Map<String, Any>
): Boolean {
try {
val rsaKey = key as RSAKey
val verifier: JWSVerifier = RSASSAVerifier(rsaKey)
val signedJWT = SignedJWT.parse(jwt)
val verified = signedJWT.verify(verifier)
return verified
} catch (e: Exception) {
throw Exception("Couldn't verify the JWT Signature: ${e.message}", e)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.sphereon.oid.fed.common.jwt

import com.nimbusds.jose.jwk.RSAKey
import com.nimbusds.jose.jwk.gen.RSAKeyGenerator
import kotlin.test.Test
import kotlin.test.assertTrue

class JoseJwtTest {

@Test
fun signTest() {
val key = RSAKeyGenerator(2048).keyID("key1").generate()
val signature = sign(
JwtPayload.parse(
mutableMapOf<String, Any>(
"iss" to "test"
)
),
JwtHeader.parse(mutableMapOf<String, Any>(
"typ" to "JWT",
"alg" to "RS256",
"kid" to 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 signature = sign(
JwtPayload.parse(
mutableMapOf<String, Any>("iss" to "test")
),
JwtHeader.parse(mutableMapOf<String, Any>(
"typ" to "JWT",
"alg" to "RS256",
"kid" to key.keyID)),
mutableMapOf("key" to key)
)
assertTrue { verify(signature, key, emptyMap()) }
}
}

0 comments on commit f246023

Please sign in to comment.