Skip to content

Commit

Permalink
feat: Created sign and verify jwt functions
Browse files Browse the repository at this point in the history
  • Loading branch information
Zoe Maas committed Jul 17, 2024
1 parent b3f03c1 commit 14c6a80
Show file tree
Hide file tree
Showing 12 changed files with 191 additions and 189 deletions.
9 changes: 8 additions & 1 deletion modules/openid-federation-common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ kotlin {
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.0")
implementation("com.nimbusds:nimbus-jose-jwt:9.40")
}
}
val commonTest by getting {
Expand All @@ -64,6 +63,7 @@ kotlin {
val jvmMain by getting {
dependencies {
implementation("io.ktor:ktor-client-core-jvm:$ktorVersion")
implementation("com.nimbusds:nimbus-jose-jwt:9.40")
}
}
val jvmTest by getting {
Expand Down Expand Up @@ -109,6 +109,11 @@ kotlin {
val jsMain by getting {
dependencies {
implementation("io.ktor:ktor-client-js:$ktorVersion")
implementation(npm("typescript", "5.5.3"))
implementation(npm("jose", "5.6.3"))
implementation(npm("uuid", "10.0.0"))
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0-RC")
}
}

Expand All @@ -117,6 +122,8 @@ kotlin {
dependencies {
implementation(kotlin("test-js"))
implementation(kotlin("test-annotations-common"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0-RC")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0-RC")
}
}
}
Expand Down

This file was deleted.

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

expect fun sign(payload: String, opts: MutableMap<String, Any>?): String
expect fun verify(jwt: String, key: Any, opts: MutableMap<String, Any>? = mutableMapOf()): Boolean

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

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

actual fun sign(
payload: String,
opts: MutableMap<String, Any>?
): String {
TODO("Not yet implemented")
}

actual fun verify(
jwt: String,
key: Any,
opts: MutableMap<String, Any>?
): Boolean {
TODO("Not yet implemented")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.sphereon.oid.fed.common.jwt

@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
}

@JsModule("uuid")
@JsNonModule
external object Uuid {
fun v4(): String
}

@ExperimentalJsExport
@JsExport
actual fun sign(
payload: String,
opts: MutableMap<String, Any>?
): String {
val privateKey = opts?.get("privateKey") ?: throw IllegalArgumentException("JWK private key is required")
val header = opts["jwtHeader"] as String? ?: "{\"typ\":\"JWT\",\"alg\":\"RS256\",\"kid\":\"${Uuid.v4()}\"}"
return Jose.SignJWT(JSON.parse<Any>(payload).asDynamic())
.setProtectedHeader(JSON.parse<Any>(header).asDynamic())
.sign(key = privateKey, signOptions = opts)
}

@ExperimentalJsExport
@JsExport
actual fun verify(
jwt: String,
key: Any,
opts: MutableMap<String, Any>?
): Boolean {
return Jose.jwtVerify(jwt, key, opts)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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("{ \"iss\": \"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("{ \"iss\": \"test\" }", mutableMapOf("privateKey" to keyPair.privateKey)) as Promise<dynamic>).await()
val result = async { verify(signed, keyPair.publicKey) }
assertTrue((result.await() as Promise<Boolean>).await())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.sphereon.oid.fed.common.jwt

import com.nimbusds.jose.JWSAlgorithm
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.jose.jwk.gen.RSAKeyGenerator
import com.nimbusds.jwt.JWTClaimsSet
import com.nimbusds.jwt.SignedJWT
import java.util.*

actual fun sign(
payload: String,
opts: MutableMap<String, Any>?
): String {
var rsaJWK = opts?.get("key") as RSAKey?
val kid = rsaJWK?.keyID ?: UUID.randomUUID().toString()
val header: JWSHeader?
if (opts?.get("jwtHeader") != null) {
header = JWSHeader.parse(opts["jwtHeader"] as String?)
} else {
header = JWSHeader.Builder(JWSAlgorithm.RS256).keyID(kid).build()
}

if (rsaJWK == null) {
rsaJWK = RSAKeyGenerator(2048)
.keyID(kid)
.generate()
}

val signer: JWSSigner = RSASSASigner(rsaJWK)

val claimsSet = JWTClaimsSet.parse(payload)

val signedJWT = SignedJWT(
header,
claimsSet
)

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

actual fun verify(
jwt: String,
key: Any,
opts: MutableMap<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}")
}
}
Loading

0 comments on commit 14c6a80

Please sign in to comment.