-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Implemented KMS, support to JWKS and JWT signing
- Loading branch information
Zoe Maas
committed
Jul 12, 2024
1 parent
95fe29e
commit a0265ca
Showing
5 changed files
with
189 additions
and
0 deletions.
There are no files selected for viewing
30 changes: 30 additions & 0 deletions
30
...federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/jwks/JWKSGenerator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
...deration-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/kms/AbstractKeyStore.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
50 changes: 50 additions & 0 deletions
50
...federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/kms/MemoryKeyStore.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} | ||
} |
47 changes: 47 additions & 0 deletions
47
...ration-common/src/commonTest/kotlin/com/sphereon/oid/fed/common/jwks/JWKSGeneratorTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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")) | ||
} | ||
} |
51 changes: 51 additions & 0 deletions
51
...ration-common/src/commonTest/kotlin/com/sphereon/oid/fed/common/kms/MemoryKeyStoreTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
} | ||
} |