Skip to content

Commit

Permalink
feat: Implemented KMS, support to JWKS and JWT signing
Browse files Browse the repository at this point in the history
  • Loading branch information
Zoe Maas committed Jul 12, 2024
1 parent 95fe29e commit a0265ca
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.sphereon.oid.fed.jwks

import com.nimbusds.jose.jwk.JWK
import com.nimbusds.jose.jwk.JWKSet
import com.nimbusds.jose.jwk.KeyUse
import com.nimbusds.jose.jwk.gen.RSAKeyGenerator
import com.sphereon.oid.fed.kms.AbstractKeyStore
import java.util.*

class JWKSGenerator (
val kms: AbstractKeyStore
) {
fun generateJWKS(kid: String? = null): JWK {
val jwk = RSAKeyGenerator(2048)
.keyUse(KeyUse.SIGNATURE)
.keyID(kid ?: UUID.randomUUID().toString())
.generate()
kms.importKey(jwk)
return jwk.toPublicJWK()
}

fun getJWKSet(vararg kid: String): JWKSet {
val keys = kms.listKeys(*kid)
return JWKSet(keys.map { it.toPublicJWK() })
}

fun sign(kid: String, payload: String): String {
return kms.sign(kid, payload)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.sphereon.oid.fed.kms

import com.nimbusds.jose.jwk.JWK

interface AbstractKeyStore {
fun importKey(key: JWK): Boolean
fun getKey(kid: String): JWK?
fun deleteKey(kid: String): Boolean
fun listKeys(vararg kid: String): List<JWK>
fun sign(kid: String, payload: String): String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.sphereon.oid.fed.kms

import com.nimbusds.jose.JWSAlgorithm
import com.nimbusds.jose.JWSHeader
import com.nimbusds.jose.crypto.RSASSASigner
import com.nimbusds.jose.jwk.JWK
import com.nimbusds.jose.jwk.RSAKey
import com.nimbusds.jwt.JWTClaimsSet
import com.nimbusds.jwt.SignedJWT
import java.util.concurrent.ConcurrentHashMap

class MemoryKeyStore : AbstractKeyStore {

private val keyStore = ConcurrentHashMap<String, JWK>()

override fun importKey(key: JWK): Boolean {
if (key.keyID == null) throw IllegalArgumentException("Key ID cannot be null")
keyStore[key.keyID] = key
return keyStore.containsKey(key.keyID)
}

override fun getKey(kid: String): JWK? {
return keyStore[kid]
}

override fun deleteKey(kid: String): Boolean {
return keyStore.remove(kid) != null
}

override fun listKeys(vararg kid: String): List<JWK> {
if (kid.isNotEmpty()) {
return kid.mapNotNull { keyStore[it] }
}
return keyStore.values.toList()
}

override fun sign(kid: String, payload: String): String {
val privateKey = (this.getKey(kid) as RSAKey).toRSAPrivateKey()

val claims = JWTClaimsSet.parse(payload)

val signer = RSASSASigner(privateKey)
val jwt = SignedJWT(
JWSHeader.Builder(JWSAlgorithm.RS256).keyID(kid).build(),
claims
)
jwt.sign(signer)
return jwt.serialize()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.sphereon.oid.fed.jwks

import com.sphereon.oid.fed.kms.MemoryKeyStore
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals


class JWKSGenerationTest {

private lateinit var jwksGenerator: JWKSGenerator

@BeforeEach
fun setUp() {
jwksGenerator = JWKSGenerator(MemoryKeyStore())
}

@Test
fun `it should generate the JWKS` () {
assertNotNull(jwksGenerator.generateJWKS())
}

@Test
fun `It should generate JWKS with all keys` () {
jwksGenerator.generateJWKS()
jwksGenerator.generateJWKS()
assertTrue(jwksGenerator.getJWKSet().size() == 2)
}

@Test
fun `It should generate JWKS with selected keys` () {
val keyOne = jwksGenerator.generateJWKS()
val keyTwo = jwksGenerator.generateJWKS()
jwksGenerator.generateJWKS()
jwksGenerator.generateJWKS()
assertTrue(jwksGenerator.getJWKSet(keyOne.keyID, keyTwo.keyID).size() == 2)
}

@Test
fun `It should sign a JWT` () {
val key = jwksGenerator.generateJWKS()
val payload = "{\"iss\":\"test\",\"sub\":\"test\"}"
assertTrue(jwksGenerator.sign(key.keyID, payload).startsWith("ey"))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.sphereon.oid.fed.jwks

import com.nimbusds.jose.jwk.KeyUse
import com.nimbusds.jose.jwk.gen.RSAKeyGenerator
import com.sphereon.oid.fed.kms.MemoryKeyStore
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.util.*

class MemoryKeyStoreTest {

lateinit var kms: MemoryKeyStore
lateinit var keyId: String

@BeforeEach
fun setUp() {
kms = MemoryKeyStore()
keyId = UUID.randomUUID().toString()
val jwk = RSAKeyGenerator(2048)
.keyUse(KeyUse.SIGNATURE)
.keyID(keyId)
.generate()
kms.importKey(jwk)
}

@Test
fun `It should import a key` () {
val jwk = RSAKeyGenerator(2048)
.keyUse(KeyUse.SIGNATURE)
.keyID(UUID.randomUUID().toString())
.generate()
assertTrue(kms.importKey(jwk))
}

@Test
fun `It should retrieve a key` () {
assertNotNull(kms.getKey(keyId))
}

@Test
fun `It should retrieve a list of keys` () {
assertTrue(kms.listKeys().size == 1)
}

@Test
fun `It should delete a key` () {
assertTrue(kms.deleteKey(keyId))
}
}

0 comments on commit a0265ca

Please sign in to comment.