From c3029f5ff038971815aa5d73cfd6d9d68c6b9906 Mon Sep 17 00:00:00 2001 From: Robert Mathew Date: Wed, 4 Sep 2024 18:40:27 +0530 Subject: [PATCH] feat: added Amazon KMS sign, verify and generate key pair --- modules/amazon-kms/build.gradle.kts | 1 + .../amazon-kms/src/main/kotlin/AmazonKms.kt | 86 +++++++++++++++++++ modules/amazon-kms/src/main/kotlin/Main.kt | 5 -- .../main/kotlin/extensions/JwkExtension.kt | 20 +++++ .../sphereon/oid/fed/kms/local/LocalKms.kt | 4 +- modules/services/build.gradle.kts | 1 + .../oid/fed/services/AmazonKmsClient.kt | 23 +++++ .../sphereon/oid/fed/services/KmsService.kt | 4 +- .../oid/fed/services/LocalKmsClient.kt | 4 +- 9 files changed, 137 insertions(+), 11 deletions(-) create mode 100644 modules/amazon-kms/src/main/kotlin/AmazonKms.kt delete mode 100644 modules/amazon-kms/src/main/kotlin/Main.kt create mode 100644 modules/amazon-kms/src/main/kotlin/extensions/JwkExtension.kt create mode 100644 modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/AmazonKmsClient.kt diff --git a/modules/amazon-kms/build.gradle.kts b/modules/amazon-kms/build.gradle.kts index d7faee52..b0cf72e9 100644 --- a/modules/amazon-kms/build.gradle.kts +++ b/modules/amazon-kms/build.gradle.kts @@ -14,6 +14,7 @@ dependencies { api(projects.modules.openapi) implementation(platform("software.amazon.awssdk:bom:2.21.1")) implementation("software.amazon.awssdk:kms") + implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.11") } tasks.test { diff --git a/modules/amazon-kms/src/main/kotlin/AmazonKms.kt b/modules/amazon-kms/src/main/kotlin/AmazonKms.kt new file mode 100644 index 00000000..f014e16b --- /dev/null +++ b/modules/amazon-kms/src/main/kotlin/AmazonKms.kt @@ -0,0 +1,86 @@ +package com.sphereon.oid.fed.kms.local + +import com.sphereon.oid.fed.kms.amazon.extensions.toJwkAdminDto +import com.sphereon.oid.fed.openapi.models.JWTHeader +import com.sphereon.oid.fed.openapi.models.Jwk +import com.sphereon.oid.fed.openapi.models.JwkAdminDTO +import kotlinx.serialization.json.JsonObject +import software.amazon.awssdk.core.SdkBytes +import software.amazon.awssdk.regions.Region +import software.amazon.awssdk.services.kms.KmsClient +import software.amazon.awssdk.services.kms.model.* +import java.nio.charset.StandardCharsets +import java.util.* + +class AmazonKms { + + private val kmsClient = KmsClient.builder().region(Region.US_WEST_2) // Replace with your desired region + .build() + + fun generateKey(): JwkAdminDTO { + val keyId = createKey() + + val request = + GenerateDataKeyPairRequest.builder().keyId(keyId).keyPairSpec(DataKeyPairSpec.ECC_NIST_P256).build() + val response = kmsClient.generateDataKeyPair(request) + + //TODO: Check this logic + val jwk = Jwk(kty = "EC", kid = response.keyId()) + return jwk.toJwkAdminDto() + } + + fun sign(header: JWTHeader, payload: JsonObject, keyId: String): String { + val encodedHeader = Base64.getUrlEncoder().withoutPadding().encodeToString( + header.toString().toByteArray( + StandardCharsets.UTF_8 + ) + ) + val encodedPayload = Base64.getUrlEncoder().withoutPadding() + .encodeToString(payload.toString().toByteArray(StandardCharsets.UTF_8)) + + val messageBytes = (encodedHeader + "." + encodedPayload).toByteArray(StandardCharsets.UTF_8) + + val signingRequest = SignRequest.builder().keyId(keyId).message(SdkBytes.fromByteArray(messageBytes)) + .signingAlgorithm(SigningAlgorithmSpec.ECDSA_SHA_256) // Adjust if needed + .build() + + val signingResponse = kmsClient.sign(signingRequest) + val signature = + Base64.getUrlEncoder().withoutPadding().encodeToString(signingResponse.signature().asByteArray()) + + return encodedHeader + "." + encodedPayload + "." + signature + } + + fun verify(token: String, keyId: String): Boolean { + try { + val parts = token.split(".") + if (parts.size != 3) { + return false // Invalid token format + } + + val header = parts[0] + val payload = parts[1] + val signature = parts[2] + + val verificationRequest = VerifyRequest.builder().keyId(keyId) + .message(SdkBytes.fromString(header + "." + payload, StandardCharsets.UTF_8)) + .signature(SdkBytes.fromByteArray(Base64.getUrlDecoder().decode(signature))) + .signingAlgorithm(SigningAlgorithmSpec.ECDSA_SHA_256) // Adjust if needed + .build() + + val verificationResponse = kmsClient.verify(verificationRequest) + + return verificationResponse.signatureValid() + } catch (e: Exception) { + return false + } + } + + private fun createKey(): String { + val request = CreateKeyRequest.builder().keyUsage(KeyUsageType.SIGN_VERIFY) // Or adjust based on your needs + .build() + + val response = kmsClient.createKey(request) + return response.keyMetadata().keyId() + } +} \ No newline at end of file diff --git a/modules/amazon-kms/src/main/kotlin/Main.kt b/modules/amazon-kms/src/main/kotlin/Main.kt deleted file mode 100644 index d75aa9fd..00000000 --- a/modules/amazon-kms/src/main/kotlin/Main.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.sphereon.oid.fed.kms.local - -fun main() { - println("Hello World!") -} \ No newline at end of file diff --git a/modules/amazon-kms/src/main/kotlin/extensions/JwkExtension.kt b/modules/amazon-kms/src/main/kotlin/extensions/JwkExtension.kt new file mode 100644 index 00000000..db5e0f32 --- /dev/null +++ b/modules/amazon-kms/src/main/kotlin/extensions/JwkExtension.kt @@ -0,0 +1,20 @@ +package com.sphereon.oid.fed.kms.amazon.extensions + +import com.sphereon.oid.fed.openapi.models.Jwk +import com.sphereon.oid.fed.openapi.models.JwkAdminDTO + +fun Jwk.toJwkAdminDto(): JwkAdminDTO = JwkAdminDTO( + kid = this.kid, + use = this.use, + crv = this.crv, + n = this.n, + e = this.e, + x = this.x, + y = this.y, + kty = this.kty, + alg = this.alg, + x5u = this.x5u, + x5t = this.x5t, + x5c = this.x5c, + x5tHashS256 = this.x5tS256 +) diff --git a/modules/local-kms/src/commonMain/kotlin/com/sphereon/oid/fed/kms/local/LocalKms.kt b/modules/local-kms/src/commonMain/kotlin/com/sphereon/oid/fed/kms/local/LocalKms.kt index eae176d6..5f8f3916 100644 --- a/modules/local-kms/src/commonMain/kotlin/com/sphereon/oid/fed/kms/local/LocalKms.kt +++ b/modules/local-kms/src/commonMain/kotlin/com/sphereon/oid/fed/kms/local/LocalKms.kt @@ -38,7 +38,7 @@ class LocalKms { return sign(header = mHeader, payload = payload, key = jwkObject) } - fun verify(token: String, jwk: Jwk): Boolean { - return verify(jwt = token, key = jwk) + fun verify(token: String, keyId: String): Boolean { + return verify(jwt = token, key = Jwk(kty = keyId)) } } diff --git a/modules/services/build.gradle.kts b/modules/services/build.gradle.kts index 56eeb580..dbce798a 100644 --- a/modules/services/build.gradle.kts +++ b/modules/services/build.gradle.kts @@ -22,6 +22,7 @@ kotlin { api(projects.modules.persistence) api(projects.modules.openidFederationCommon) api(projects.modules.localKms) + api(projects.modules.amazonKms) implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.11") } } diff --git a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/AmazonKmsClient.kt b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/AmazonKmsClient.kt new file mode 100644 index 00000000..aef50f79 --- /dev/null +++ b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/AmazonKmsClient.kt @@ -0,0 +1,23 @@ +package com.sphereon.oid.fed.services + +import com.sphereon.oid.fed.kms.local.AmazonKms +import com.sphereon.oid.fed.openapi.models.JWTHeader +import com.sphereon.oid.fed.openapi.models.JwkAdminDTO +import kotlinx.serialization.json.JsonObject + +class AmazonKmsClient : KmsClient { + + private val amazonKms = AmazonKms() + + override fun generateKeyPair(): JwkAdminDTO { + return amazonKms.generateKey() + } + + override fun sign(header: JWTHeader, payload: JsonObject, keyId: String): String { + return amazonKms.sign(header, payload, keyId) + } + + override fun verify(token: String, keyId: String): Boolean { + return amazonKms.verify(token, keyId) + } +} \ No newline at end of file diff --git a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/KmsService.kt b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/KmsService.kt index 5a95d04d..25df3e12 100644 --- a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/KmsService.kt +++ b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/KmsService.kt @@ -1,7 +1,6 @@ package com.sphereon.oid.fed.services import com.sphereon.oid.fed.openapi.models.JWTHeader -import com.sphereon.oid.fed.openapi.models.Jwk import com.sphereon.oid.fed.openapi.models.JwkAdminDTO import kotlinx.serialization.json.JsonObject @@ -10,6 +9,7 @@ object KmsService { private val kmsClient: KmsClient = when (provider) { "local" -> LocalKmsClient() + "amazon" -> AmazonKmsClient() else -> throw IllegalArgumentException("Unsupported KMS provider: $provider") } @@ -19,5 +19,5 @@ object KmsService { interface KmsClient { fun generateKeyPair(): JwkAdminDTO fun sign(header: JWTHeader, payload: JsonObject, keyId: String): String - fun verify(token: String, jwk: Jwk): Boolean + fun verify(token: String, keyId: String): Boolean } diff --git a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/LocalKmsClient.kt b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/LocalKmsClient.kt index 64edca2f..0a31b1e6 100644 --- a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/LocalKmsClient.kt +++ b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/LocalKmsClient.kt @@ -20,7 +20,7 @@ class LocalKmsClient : KmsClient { return localKms.sign(header, payload, keyId) } - override fun verify(token: String, jwk: Jwk): Boolean { - return localKms.verify(token, jwk) + override fun verify(token: String, keyId: String): Boolean { + return localKms.verify(token, keyId) } }