diff --git a/.env.example b/.env.example index 8700fb84..c45bc34b 100644 --- a/.env.example +++ b/.env.example @@ -23,14 +23,17 @@ POSTGRES_DB=${DATASOURCE_DB} # Then just point the `OAUTH2_RESOURCE_SERVER_JWT_ISSUER_URI` value to the correct OIDC realm/issuer # The keycloak admin user name that the server will be bootstrapped with. Change this value! KC_BOOTSTRAP_ADMIN_USERNAME=admin + # The keycloak admin password that the server will be bootstrapped with. Change this value! KC_BOOTSTRAP_ADMIN_PASSWORD=admin -# Whether to enable healt checks. Do not change normally! + +# Whether to enable health checks. Do not change normally! KC_HEALTH_ENABLED=true -# The JAVA memeory related options for the Keycloak docker image + +# The JAVA memory related options for the Keycloak docker image JAVA_OPTS=-XX:MaxRAMPercentage=75.0 -XX:InitialRAMPercentage=50.0 -XX:+UseG1GC -# The OIDC/OAUTH2 Isser. This is your OIDC issuer, provuding the trust/authentication. Defaults to the local KC instance deployed as docker container. +# The OIDC/OAUTH2 Issuer. This is your OIDC issuer, providing the trust/authentication. Defaults to the local KC instance deployed as docker container. # Change to your OIDC environment if you have one, and then disable the keycloak deployment altogether OAUTH2_RESOURCE_SERVER_JWT_ISSUER_URI=http://keycloak:8080/realms/openid-federation diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54c2e203..70fa1708 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,6 +44,9 @@ jobs: POSTGRES_DB: "openid-federation-db" NEXUS_USERNAME: ${{ secrets.NEXUS_USERNAME }} NEXUS_PASSWORD: ${{ secrets.NEXUS_PASSWORD }} + AWS_REGION: ${{ secrets.AWS_REGION }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} KMS_PROVIDER: memory run: | diff --git a/build.gradle.kts b/build.gradle.kts index 32e901f0..19b82868 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -108,7 +108,7 @@ fun getNpmVersion(): String { allprojects { group = "com.sphereon.oid.fed" - version = "0.22.0-SNAPSHOT" + version = "0.22.2-SNAPSHOT" val npmVersion by extra { getNpmVersion() } configurations { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0553a604..3e84d89f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -27,7 +27,7 @@ nimbusJoseJwt = "9.40" node-gradle = "7.1.0" npmPublish = "3.5.3" openapi = "7.12.0" -sphereonKmp = "0.2.20-SNAPSHOT" +sphereonKmp = "0.2.24-SNAPSHOT" springboot = "3.4.4" springsecurity = "6.4.4" springdoc = "2.8.5" diff --git a/modules/openid-federation-services/src/commonTest/kotlin/com.sphereon.oid.fed.services/KmsServiceTest.kt b/modules/openid-federation-services/src/commonTest/kotlin/com.sphereon.oid.fed.services/KmsServiceTest.kt index f8ed1358..3ecd2142 100644 --- a/modules/openid-federation-services/src/commonTest/kotlin/com.sphereon.oid.fed.services/KmsServiceTest.kt +++ b/modules/openid-federation-services/src/commonTest/kotlin/com.sphereon.oid.fed.services/KmsServiceTest.kt @@ -1,14 +1,22 @@ package com.sphereon.oid.fed.services - +import com.sphereon.crypto.jose.JwaAlgorithm import com.sphereon.crypto.kms.aws.AwsKmsCryptoProvider import com.sphereon.crypto.kms.azure.AzureKeyVaultCryptoProvider import com.sphereon.crypto.kms.ecdsa.EcDSACryptoProvider +import com.sphereon.oid.fed.client.crypto.cryptoService +import com.sphereon.oid.fed.openapi.models.JwtHeader +import com.sphereon.oid.fed.services.mappers.jsonSerialization +import com.sphereon.oid.fed.services.mappers.toJsonString +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertIs import kotlin.test.assertNotNull +import kotlin.test.assertTrue class KmsServiceTest { @@ -75,4 +83,77 @@ class KmsServiceTest { KmsType.fromString("") } } + + + @Test + fun `create key with AWS KMS, sign JWT, map to Jwk model and verify signature`() = runTest { + // Create KmsService with AWS provider + val kmsService = KmsService.createAwsKms( + applicationId = "test-app-id", + region = System.getenv("AWS_REGION"), + accessKeyId = System.getenv("AWS_ACCESS_KEY_ID"), + secretAccessKey = System.getenv("AWS_SECRET_ACCESS_KEY") + ) + + // Get the KMS provider + val provider = kmsService.getKmsProvider() + assertIs(provider) + + // 1. Try to get an existing key or generate a new one + var generatedKey = provider.generateKeyAsync() + + assertNotNull(generatedKey) + assertNotNull(generatedKey.kid) + assertNotNull(generatedKey.kmsKeyRef) + + // 2. Create a JWT header and payload + val kid = requireNotNull(generatedKey.kid) { "Generated key must have a kid" } + val header = JwtHeader( + alg = JwaAlgorithm.ES256.value, + kid = kid, + typ = "JWT" + ) + + val payload = JsonObject( + mapOf( + "iss" to JsonPrimitive("test-issuer"), + "sub" to JsonPrimitive("test-subject"), + "iat" to JsonPrimitive((System.currentTimeMillis() / 1000).toInt()), + "exp" to JsonPrimitive((System.currentTimeMillis() / 1000 + 3600).toInt()), + "test-claim" to JsonPrimitive("test-value") + ) + ) + + // 3. Sign the JWT using the JwtService + val jwtService = JwtService(provider) + val kmsKeyRef = requireNotNull(generatedKey.kmsKeyRef) { "Generated key must have a kmsKeyRef" } + val jwt = jwtService.sign(payload, header, kid, kmsKeyRef) + + // Verify the JWT format + assertNotNull(jwt) + val jwtParts = jwt.split(".") + assertEquals(3, jwtParts.size, "JWT should have three parts separated by dots") + + // Verify the JWT signature using the CryptoService + val cryptoSvc = cryptoService() + val jwkJson = generatedKey.jose.publicJwk.toJsonString() + val jwk: com.sphereon.oid.fed.openapi.models.Jwk = jsonSerialization.decodeFromString(jwkJson) + val isValid = cryptoSvc.verify(jwt, jwk) + if (!isValid) { + // Let's print the JWK and JWT for easy debugging in case it is not valid + println("JWK:\n${jwk.toJsonString()}\n\n") + println("JWT: $jwt") + } + + assertTrue(isValid, "JWT signature should be valid") + + // Verify the Jwk model properties + assertNotNull(jwk) + assertEquals(generatedKey.kid, jwk.kid) + assertEquals(JwaAlgorithm.ES256.value, jwk.alg) + assertEquals("EC", jwk.kty) + assertEquals("P-256", jwk.crv) + assertNotNull(jwk.x) + assertNotNull(jwk.y) + } }