From db8e1162631eb75ede151c2d61db3f82f4c4b638 Mon Sep 17 00:00:00 2001 From: Zoe Maas Date: Fri, 2 Aug 2024 16:21:30 +0200 Subject: [PATCH] refactor: Made JWT payload and header classes to be used as input --- .../sphereon/oid/fed/common/jwt/JoseJwt.kt | 5 +++- .../sphereon/oid/fed/common/jwt/JoseJwt.js.kt | 21 +++++++++------- .../oid/fed/common/jwt/JoseJwtTest.js.kt | 12 +++++---- .../oid/fed/common/jwt/JoseJwt.jvm.kt | 15 ++++++----- .../oid/fed/common/jwt/JoseJwtTest.jvm.kt | 25 ++++++++++++++----- 5 files changed, 49 insertions(+), 29 deletions(-) diff --git a/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwt.kt b/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwt.kt index fb1bc18a..a6ccd627 100644 --- a/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwt.kt +++ b/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwt.kt @@ -1,4 +1,7 @@ package com.sphereon.oid.fed.common.jwt -expect fun sign(payload: String, header: String, opts: Map): String +expect class JwtHeader +expect class JwtPayload + +expect fun sign(payload: JwtPayload, header: JwtHeader, opts: Map): String expect fun verify(jwt: String, key: Any, opts: Map): Boolean diff --git a/modules/openid-federation-common/src/jsMain/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwt.js.kt b/modules/openid-federation-common/src/jsMain/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwt.js.kt index c1405ab3..4286f44f 100644 --- a/modules/openid-federation-common/src/jsMain/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwt.js.kt +++ b/modules/openid-federation-common/src/jsMain/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwt.js.kt @@ -1,5 +1,10 @@ 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 { @@ -18,22 +23,20 @@ external object Jose { fun jwtVerify(jwt: String, key: Any, options: dynamic = definedExternally): dynamic } -@JsModule("uuid") -@JsNonModule -external object Uuid { - fun v4(): String -} +actual typealias JwtPayload = EntityStatement +actual typealias JwtHeader = JWTHeader @ExperimentalJsExport @JsExport actual fun sign( - payload: String, - header: String, + payload: JwtPayload, + header: JwtHeader, opts: Map ): String { val privateKey = opts["privateKey"] ?: throw IllegalArgumentException("JWK private key is required") - return Jose.SignJWT(JSON.parse(payload).asDynamic()) - .setProtectedHeader(JSON.parse(header).asDynamic()) + + return Jose.SignJWT(JSON.parse(Json.encodeToString(payload))) + .setProtectedHeader(JSON.parse(Json.encodeToString(header))) .sign(key = privateKey, signOptions = opts) } diff --git a/modules/openid-federation-common/src/jsTest/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwtTest.js.kt b/modules/openid-federation-common/src/jsTest/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwtTest.js.kt index 4d3f41ac..3f4c3e63 100644 --- a/modules/openid-federation-common/src/jsTest/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwtTest.js.kt +++ b/modules/openid-federation-common/src/jsTest/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwtTest.js.kt @@ -13,8 +13,10 @@ class JoseJwtTest { @Test fun signTest() = runTest { val keyPair = (generateKeyPair("RS256") as Promise).await() - val result = async { sign("{\"iss\":\"test\"}", - "{\"typ\":\"JWT\",\"alg\":\"RS256\",\"kid\":\"test\"}", + val result = async { + sign( + JwtPayload(iss="test"), + JwtHeader(typ="JWT",alg="RS256",kid="test"), mutableMapOf("privateKey" to keyPair.privateKey)) } assertTrue((result.await() as Promise).await().startsWith("ey")) } @@ -24,10 +26,10 @@ class JoseJwtTest { fun verifyTest() = runTest { val keyPair = (generateKeyPair("RS256") as Promise).await() val signed = (sign( - "{\"iss\":\"test\"}", - "{\"typ\":\"JWT\",\"alg\":\"RS256\",\"kid\":\"test\" }", + JwtPayload(iss="test"), + JwtHeader(typ="JWT",alg="RS256",kid="test"), mutableMapOf("privateKey" to keyPair.privateKey)) as Promise).await() val result = async { verify(signed, keyPair.publicKey, emptyMap()) } - assertTrue((result.await() as Promise).await()) + assertTrue((result.await())) } } diff --git a/modules/openid-federation-common/src/jvmMain/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwt.jvm.kt b/modules/openid-federation-common/src/jvmMain/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwt.jvm.kt index 84ff9a6b..a0e9f17b 100644 --- a/modules/openid-federation-common/src/jvmMain/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwt.jvm.kt +++ b/modules/openid-federation-common/src/jvmMain/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwt.jvm.kt @@ -9,22 +9,21 @@ 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: String, - header: String, + payload: JwtPayload, + header: JwtHeader, opts: Map ): String { val rsaJWK = opts["key"] as RSAKey? ?: throw IllegalArgumentException("The RSA key pair is required") - - val protectedHeader = JWSHeader.parse(header) val signer: JWSSigner = RSASSASigner(rsaJWK) - val claimsSet = JWTClaimsSet.parse(payload) - val signedJWT = SignedJWT( - protectedHeader, - claimsSet + header, + payload ) signedJWT.sign(signer) diff --git a/modules/openid-federation-common/src/jvmTest/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwtTest.jvm.kt b/modules/openid-federation-common/src/jvmTest/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwtTest.jvm.kt index 03715ce6..54e8ddc3 100644 --- a/modules/openid-federation-common/src/jvmTest/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwtTest.jvm.kt +++ b/modules/openid-federation-common/src/jvmTest/kotlin/com/sphereon/oid/fed/common/jwt/JoseJwtTest.jvm.kt @@ -11,8 +11,15 @@ class JoseJwtTest { fun signTest() { val key = RSAKeyGenerator(2048).keyID("key1").generate() val signature = sign( - "{ \"iss\": \"test\" }", - "{\"typ\":\"JWT\",\"alg\":\"RS256\",\"kid\":\"${key.keyID}\"}", + JwtPayload.parse( + mutableMapOf( + "iss" to "test" + ) + ), + JwtHeader.parse(mutableMapOf( + "typ" to "JWT", + "alg" to "RS256", + "kid" to key.keyID)), mutableMapOf("key" to key) ) assertTrue { signature.startsWith("ey") } @@ -22,10 +29,16 @@ class JoseJwtTest { fun verifyTest() { val kid = "key1" val key: RSAKey = RSAKeyGenerator(2048).keyID(kid).generate() - val signature = sign("{ \"iss\": \"test\" }","{\"typ\":\"JWT\",\"alg\":\"RS256\",\"kid\":\"test\"}", mutableMapOf( - "key" to key, - "jwtHeader" to "{\"typ\":\"JWT\",\"alg\":\"RS256\",\"kid\":\"${key.keyID}\"}" - )) + val signature = sign( + JwtPayload.parse( + mutableMapOf("iss" to "test") + ), + JwtHeader.parse(mutableMapOf( + "typ" to "JWT", + "alg" to "RS256", + "kid" to key.keyID)), + mutableMapOf("key" to key) + ) assertTrue { verify(signature, key, emptyMap()) } } }