diff --git a/.env b/.env index adb8b036..15eeba69 100644 --- a/.env +++ b/.env @@ -1,6 +1,15 @@ +APP_KEY=Nit5tWts42QeCynT1Q476LyStDeSd4xb + +ROOT_IDENTIFIER=http://localhost:8080 + DATASOURCE_URL=jdbc:postgresql://db:5432/openid-federation-db DATASOURCE_USER=openid-federation-db-user DATASOURCE_PASSWORD=openid-federation-db-password DATASOURCE_DB=openid-federation-db -APP_KEY=Nit5tWts42QeCynT1Q476LyStDeSd4xb -ROOT_IDENTIFIER=http://localhost:8080 + +KMS_PROVIDER=local + +LOCAL_KMS_DATASOURCE_URL=jdbc:postgresql://local-kms-db:5432/openid-federation-local-kms-db +LOCAL_KMS_DATASOURCE_USER=openid-federation-local-kms-db-user +LOCAL_KMS_DATASOURCE_PASSWORD=openid-federation-local-kms-db-password +LOCAL_KMS_DATASOURCE_DB=openid-federation-local-kms-db diff --git a/docker-compose.yaml b/docker-compose.yaml index ce0cc8bf..36223302 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -18,6 +18,25 @@ services: timeout: 5s retries: 20 + local-kms-db: + image: postgres:latest + container_name: openid-federation-local-kms-datastore + environment: + POSTGRES_USER: ${LOCAL_KMS_DATASOURCE_USER} + POSTGRES_PASSWORD: ${LOCAL_KMS_DATASOURCE_PASSWORD} + POSTGRES_DB: ${LOCAL_KMS_DATASOURCE_DB} + ports: + - "5433:5432" + volumes: + - local_kms_data:/var/lib/postgresql/data + networks: + - openid_network + healthcheck: + test: [ "CMD-SHELL", "pg_isready -d ${LOCAL_KMS_DATASOURCE_DB} -U ${LOCAL_KMS_DATASOURCE_USER}" ] + interval: 3s + timeout: 5s + retries: 20 + federation-server: build: context: . @@ -49,9 +68,17 @@ services: DATASOURCE_USER: ${DATASOURCE_USER} DATASOURCE_PASSWORD: ${DATASOURCE_PASSWORD} APP_KEY: ${APP_KEY} + KMS_PROVIDER: ${KMS_PROVIDER} + LOCAL_KMS_DATASOURCE_URL: ${LOCAL_KMS_DATASOURCE_URL} + LOCAL_KMS_DATASOURCE_USER: ${LOCAL_KMS_DATASOURCE_USER} + LOCAL_KMS_DATASOURCE_PASSWORD: ${LOCAL_KMS_DATASOURCE_PASSWORD} + LOCAL_KMS_DATASOURCE_DB: ${LOCAL_KMS_DATASOURCE_DB} + depends_on: db: condition: service_healthy + local-kms-db: + condition: service_healthy networks: - openid_network @@ -61,3 +88,4 @@ networks: volumes: postgres_data: + local_kms_data: diff --git a/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/EntityConfigurationMetadataController.kt b/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/EntityConfigurationMetadataController.kt index 9caa1e74..a7ff56ce 100644 --- a/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/EntityConfigurationMetadataController.kt +++ b/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/EntityConfigurationMetadataController.kt @@ -1,9 +1,15 @@ package com.sphereon.oid.fed.server.admin.controllers import com.sphereon.oid.fed.openapi.models.CreateMetadataDTO -import com.sphereon.oid.fed.persistence.models.EntityConfigurationMetadata +import com.sphereon.oid.fed.openapi.models.EntityConfigurationMetadataDTO import com.sphereon.oid.fed.services.EntityConfigurationMetadataService -import org.springframework.web.bind.annotation.* +import org.springframework.web.bind.annotation.DeleteMapping +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController @RestController @RequestMapping("/accounts/{accountUsername}/metadata") @@ -13,19 +19,19 @@ class EntityConfigurationMetadataController { @GetMapping fun get( @PathVariable accountUsername: String - ): Array { + ): Array { return entityConfigurationMetadataService.findByAccountUsername(accountUsername) } @PostMapping fun create( @PathVariable accountUsername: String, - @RequestBody metadata: CreateMetadataDTO - ): EntityConfigurationMetadata { + @RequestBody body: CreateMetadataDTO + ): EntityConfigurationMetadataDTO { return entityConfigurationMetadataService.createEntityConfigurationMetadata( accountUsername, - metadata.key, - metadata.value + body.key, + body.metadata ) } @@ -33,7 +39,7 @@ class EntityConfigurationMetadataController { fun delete( @PathVariable accountUsername: String, @PathVariable id: Int - ): EntityConfigurationMetadata { + ): EntityConfigurationMetadataDTO { return entityConfigurationMetadataService.deleteEntityConfigurationMetadata(accountUsername, id) } } diff --git a/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/EntityStatementController.kt b/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/EntityStatementController.kt index cf444b49..668bc76b 100644 --- a/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/EntityStatementController.kt +++ b/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/EntityStatementController.kt @@ -1,10 +1,12 @@ package com.sphereon.oid.fed.server.admin.controllers import com.sphereon.oid.fed.openapi.models.EntityConfigurationStatement +import com.sphereon.oid.fed.openapi.models.PublishEntityStatementDTO import com.sphereon.oid.fed.services.EntityConfigurationStatementService import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController @@ -19,7 +21,10 @@ class EntityStatementController { } @PostMapping - fun publishEntityStatement(@PathVariable accountUsername: String): EntityConfigurationStatement { - return entityConfigurationStatementService.publishByUsername(accountUsername) + fun publishEntityStatement( + @PathVariable accountUsername: String, + @RequestBody body: PublishEntityStatementDTO? + ): String { + return entityConfigurationStatementService.publishByUsername(accountUsername, body?.dryRun ?: false) } } diff --git a/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/KeyController.kt b/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/KeyController.kt index 9bc819b2..4ceb636a 100644 --- a/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/KeyController.kt +++ b/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/controllers/KeyController.kt @@ -2,8 +2,13 @@ package com.sphereon.oid.fed.server.admin.controllers import com.sphereon.oid.fed.openapi.models.JwkAdminDTO import com.sphereon.oid.fed.services.KeyService -import com.sphereon.oid.fed.services.extensions.toJwkAdminDTO -import org.springframework.web.bind.annotation.* +import org.springframework.web.bind.annotation.DeleteMapping +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController @RestController @RequestMapping("/accounts/{accountUsername}/keys") @@ -13,13 +18,13 @@ class KeyController { @PostMapping fun create(@PathVariable accountUsername: String): JwkAdminDTO { val key = keyService.create(accountUsername) - return key.toJwkAdminDTO() + return key } @GetMapping - fun getKeys(@PathVariable accountUsername: String): List { + fun getKeys(@PathVariable accountUsername: String): Array { val keys = keyService.getKeys(accountUsername) - return keys.map { it.toJwkAdminDTO() } + return keys } @DeleteMapping("/{keyId}") @@ -30,4 +35,4 @@ class KeyController { ): JwkAdminDTO { return keyService.revokeKey(accountUsername, keyId, reason) } -} \ No newline at end of file +} diff --git a/modules/local-kms/src/commonMain/kotlin/com/sphereon/oid/fed/kms/local/Constants.kt b/modules/local-kms/src/commonMain/kotlin/com/sphereon/oid/fed/kms/local/Constants.kt index 421da153..928a9356 100644 --- a/modules/local-kms/src/commonMain/kotlin/com/sphereon/oid/fed/kms/local/Constants.kt +++ b/modules/local-kms/src/commonMain/kotlin/com/sphereon/oid/fed/kms/local/Constants.kt @@ -2,9 +2,9 @@ package com.sphereon.oid.fed.kms.local class Constants { companion object { - const val DATASOURCE_URL = "DATASOURCE_URL" - const val DATASOURCE_USER = "DATASOURCE_USER" - const val DATASOURCE_PASSWORD = "DATASOURCE_PASSWORD" + const val LOCAL_KMS_DATASOURCE_URL = "LOCAL_KMS_DATASOURCE_URL" + const val LOCAL_KMS_DATASOURCE_USER = "LOCAL_KMS_DATASOURCE_USER" + const val LOCAL_KMS_DATASOURCE_PASSWORD = "LOCAL_KMS_DATASOURCE_PASSWORD" const val SQLITE_IS_NOT_SUPPORTED_IN_JVM = "SQLite is not supported in JVM" } } 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 dc88b2c3..eae176d6 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 @@ -1,51 +1,44 @@ package com.sphereon.oid.fed.kms.local import com.sphereon.oid.fed.kms.local.database.LocalKmsDatabase +import com.sphereon.oid.fed.kms.local.encryption.AesEncryption +import com.sphereon.oid.fed.kms.local.extensions.toJwkAdminDto import com.sphereon.oid.fed.kms.local.jwk.generateKeyPair import com.sphereon.oid.fed.kms.local.jwt.sign import com.sphereon.oid.fed.kms.local.jwt.verify import com.sphereon.oid.fed.openapi.models.JWTHeader import com.sphereon.oid.fed.openapi.models.Jwk -import kotlinx.serialization.json.* +import com.sphereon.oid.fed.openapi.models.JwkAdminDTO +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject class LocalKms { private val database: LocalKmsDatabase = LocalKmsDatabase() + private val aesEncryption: AesEncryption = AesEncryption() - fun generateKey(keyId: String) { + fun generateKey(): JwkAdminDTO { val jwk = generateKeyPair() - database.insertKey(keyId = keyId, privateKey = jwk.toString()) + + database.insertKey( + keyId = jwk.kid!!, + key = aesEncryption.encrypt(Json.encodeToString(Jwk.serializer(), jwk)) + ) + + return jwk.toJwkAdminDto() } fun sign(header: JWTHeader, payload: JsonObject, keyId: String): String { val jwk = database.getKey(keyId) - val jwkString: String = Json.decodeFromString(jwk.private_key) - val jwkObject: Jwk = Json.decodeFromString(jwkString) - // Adding necessary parameter is header + val jwkObject: Jwk = Json.decodeFromString(aesEncryption.decrypt(jwk.key)) + val mHeader = header.copy(alg = jwkObject.alg, kid = jwkObject.kid) - // Adding JWKs object in payload - val mutablePayload = payload.toMutableMap() - mutablePayload["kid"] = JsonPrimitive(jwkObject.kid) - val keyArrayOfJwks = buildJsonObject { - putJsonArray("keys") { - addJsonObject { - put("kty", jwkObject.kty) - put("n", jwkObject.n) - put("e", jwkObject.e) - put("kid", jwkObject.kid) - put("use", jwkObject.use) - } - } - } - mutablePayload["jwks"] = keyArrayOfJwks - val mPayload = JsonObject(mutablePayload) - - return sign(header = mHeader, payload = mPayload, key = jwkObject) + return sign(header = mHeader, payload = payload, key = jwkObject) } fun verify(token: String, jwk: Jwk): Boolean { return verify(jwt = token, key = jwk) } -} \ No newline at end of file +} diff --git a/modules/local-kms/src/commonMain/kotlin/com/sphereon/oid/fed/kms/local/database/LocalKmsDatabase.kt b/modules/local-kms/src/commonMain/kotlin/com/sphereon/oid/fed/kms/local/database/LocalKmsDatabase.kt index 3caebd98..c698c30f 100644 --- a/modules/local-kms/src/commonMain/kotlin/com/sphereon/oid/fed/kms/local/database/LocalKmsDatabase.kt +++ b/modules/local-kms/src/commonMain/kotlin/com/sphereon/oid/fed/kms/local/database/LocalKmsDatabase.kt @@ -4,8 +4,8 @@ import com.sphereon.oid.fed.kms.local.models.Keys expect class LocalKmsDatabase() { fun getKey(keyId: String): Keys - fun insertKey(keyId: String, privateKey: String) + fun insertKey(keyId: String, key: String) fun deleteKey(keyId: String) } -class KeyNotFoundException(message: String) : Exception(message) \ No newline at end of file +class KeyNotFoundException(message: String) : Exception(message) diff --git a/modules/local-kms/src/commonMain/kotlin/com/sphereon/oid/fed/kms/local/encryption/AesEncryption.kt b/modules/local-kms/src/commonMain/kotlin/com/sphereon/oid/fed/kms/local/encryption/AesEncryption.kt new file mode 100644 index 00000000..36f03f78 --- /dev/null +++ b/modules/local-kms/src/commonMain/kotlin/com/sphereon/oid/fed/kms/local/encryption/AesEncryption.kt @@ -0,0 +1,31 @@ +package com.sphereon.oid.fed.kms.local.encryption + +import java.util.* +import javax.crypto.Cipher +import javax.crypto.spec.SecretKeySpec + +private const val KEY_SIZE = 32 +private const val ALGORITHM = "AES" + +class AesEncryption { + + private val secretKey: SecretKeySpec = + SecretKeySpec(System.getenv("APP_KEY").padEnd(KEY_SIZE, '0').toByteArray(Charsets.UTF_8), ALGORITHM) + + fun encrypt(data: String): String { + val cipher = Cipher.getInstance(ALGORITHM) + cipher.init(Cipher.ENCRYPT_MODE, secretKey) + + val encryptedValue = cipher.doFinal(data.toByteArray(Charsets.UTF_8)) + return Base64.getEncoder().encodeToString(encryptedValue) + } + + fun decrypt(data: String): String { + val cipher = Cipher.getInstance(ALGORITHM) + cipher.init(Cipher.DECRYPT_MODE, secretKey) + + val decodedValue = Base64.getDecoder().decode(data) + val decryptedValue = cipher.doFinal(decodedValue) + return String(decryptedValue, Charsets.UTF_8) + } +} diff --git a/modules/local-kms/src/commonMain/kotlin/com/sphereon/oid/fed/kms/local/extensions/JwkExtension.kt b/modules/local-kms/src/commonMain/kotlin/com/sphereon/oid/fed/kms/local/extensions/JwkExtension.kt new file mode 100644 index 00000000..4876609d --- /dev/null +++ b/modules/local-kms/src/commonMain/kotlin/com/sphereon/oid/fed/kms/local/extensions/JwkExtension.kt @@ -0,0 +1,20 @@ +package com.sphereon.oid.fed.kms.local.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/jwk/Jwk.kt b/modules/local-kms/src/commonMain/kotlin/com/sphereon/oid/fed/kms/local/jwk/Jwk.kt index 44cc96f6..93f65ed3 100644 --- a/modules/local-kms/src/commonMain/kotlin/com/sphereon/oid/fed/kms/local/jwk/Jwk.kt +++ b/modules/local-kms/src/commonMain/kotlin/com/sphereon/oid/fed/kms/local/jwk/Jwk.kt @@ -3,4 +3,3 @@ package com.sphereon.oid.fed.kms.local.jwk import com.sphereon.oid.fed.openapi.models.Jwk expect fun generateKeyPair(): Jwk - diff --git a/modules/local-kms/src/commonMain/sqldelight/com/sphereon/oid/fed/kms/local/models/1.sqm b/modules/local-kms/src/commonMain/sqldelight/com/sphereon/oid/fed/kms/local/models/1.sqm index 403f4b65..aaee9711 100644 --- a/modules/local-kms/src/commonMain/sqldelight/com/sphereon/oid/fed/kms/local/models/1.sqm +++ b/modules/local-kms/src/commonMain/sqldelight/com/sphereon/oid/fed/kms/local/models/1.sqm @@ -1,5 +1,5 @@ CREATE TABLE Keys ( id TEXT PRIMARY KEY, - private_key TEXT NOT NULL, + key TEXT NOT NULL, deleted_at TIMESTAMP -); \ No newline at end of file +); diff --git a/modules/local-kms/src/commonMain/sqldelight/com/sphereon/oid/fed/kms/local/models/Keys.sq b/modules/local-kms/src/commonMain/sqldelight/com/sphereon/oid/fed/kms/local/models/Keys.sq index 7644afc1..d9677f09 100644 --- a/modules/local-kms/src/commonMain/sqldelight/com/sphereon/oid/fed/kms/local/models/Keys.sq +++ b/modules/local-kms/src/commonMain/sqldelight/com/sphereon/oid/fed/kms/local/models/Keys.sq @@ -2,10 +2,10 @@ findAll: SELECT * FROM Keys; create: -INSERT INTO Keys (id, private_key) VALUES (?, ?) RETURNING *; +INSERT INTO Keys (id, key) VALUES (?, ?) RETURNING *; findById: SELECT * FROM Keys WHERE id = ?; delete: -UPDATE Keys SET deleted_at = CURRENT_TIMESTAMP WHERE id = ?; \ No newline at end of file +UPDATE Keys SET deleted_at = CURRENT_TIMESTAMP WHERE id = ?; diff --git a/modules/local-kms/src/jvmMain/kotlin/com/sphereon/oid/fed/kms/local/database/LocalKmsDatabase.jvm.kt b/modules/local-kms/src/jvmMain/kotlin/com/sphereon/oid/fed/kms/local/database/LocalKmsDatabase.jvm.kt index 6fee47ff..7aab0e68 100644 --- a/modules/local-kms/src/jvmMain/kotlin/com/sphereon/oid/fed/kms/local/database/LocalKmsDatabase.jvm.kt +++ b/modules/local-kms/src/jvmMain/kotlin/com/sphereon/oid/fed/kms/local/database/LocalKmsDatabase.jvm.kt @@ -1,5 +1,7 @@ package com.sphereon.oid.fed.kms.local.database +import app.cash.sqldelight.db.QueryResult +import app.cash.sqldelight.db.SqlCursor import app.cash.sqldelight.db.SqlDriver import com.sphereon.oid.fed.kms.local.Constants import com.sphereon.oid.fed.kms.local.Database @@ -12,25 +14,59 @@ actual class LocalKmsDatabase { init { val driver = getDriver() + runMigrations(driver) database = Database(driver) } private fun getDriver(): SqlDriver { return PlatformSqlDriver().createPostgresDriver( - System.getenv(Constants.DATASOURCE_URL), - System.getenv(Constants.DATASOURCE_USER), - System.getenv(Constants.DATASOURCE_PASSWORD) + System.getenv(Constants.LOCAL_KMS_DATASOURCE_URL), + System.getenv(Constants.LOCAL_KMS_DATASOURCE_USER), + System.getenv(Constants.LOCAL_KMS_DATASOURCE_PASSWORD) ) } + private fun runMigrations(driver: SqlDriver) { + setupSchemaVersioningTable(driver) + + val currentVersion = getCurrentDatabaseVersion(driver) + val newVersion = Database.Schema.version + + if (currentVersion < newVersion) { + Database.Schema.migrate(driver, currentVersion, newVersion) + updateDatabaseVersion(driver, newVersion) + } + } + + private fun setupSchemaVersioningTable(driver: SqlDriver) { + driver.execute(null, "CREATE TABLE IF NOT EXISTS schema_version (version INTEGER NOT NULL)", 0) + } + + private fun getCurrentDatabaseVersion(driver: SqlDriver): Long { + val versionQuery = "SELECT version FROM schema_version ORDER BY version DESC LIMIT 1" + + val version = driver.executeQuery(null, versionQuery, parameters = 0, mapper = { cursor: SqlCursor -> + QueryResult.Value(if (cursor.next().value) cursor.getLong(0) else null) + }) + + return version.value ?: 0 + } + + private fun updateDatabaseVersion(driver: SqlDriver, newVersion: Long) { + val updateQuery = "INSERT INTO schema_version (version) VALUES (?)" + driver.execute(null, updateQuery, 1) { + bindLong(0, newVersion) + } + } + actual fun getKey(keyId: String): Keys { return database.keysQueries.findById(keyId).executeAsOneOrNull() ?: throw KeyNotFoundException("$keyId not found") } - actual fun insertKey(keyId: String, privateKey: String) { - database.keysQueries.create(keyId, privateKey).executeAsOneOrNull() + actual fun insertKey(keyId: String, key: String) { + database.keysQueries.create(keyId, key).executeAsOneOrNull() } actual fun deleteKey(keyId: String) { diff --git a/modules/local-kms/src/jvmMain/kotlin/com/sphereon/oid/fed/kms/local/jwk/Jwk.jvm.kt b/modules/local-kms/src/jvmMain/kotlin/com/sphereon/oid/fed/kms/local/jwk/Jwk.jvm.kt index 3f4e77f8..78e9442d 100644 --- a/modules/local-kms/src/jvmMain/kotlin/com/sphereon/oid/fed/kms/local/jwk/Jwk.jvm.kt +++ b/modules/local-kms/src/jvmMain/kotlin/com/sphereon/oid/fed/kms/local/jwk/Jwk.jvm.kt @@ -7,11 +7,12 @@ import com.nimbusds.jose.jwk.gen.ECKeyGenerator import com.sphereon.oid.fed.openapi.models.Jwk import java.util.* + actual fun generateKeyPair(): Jwk { try { val ecKey: ECKey = ECKeyGenerator(Curve.P_256) .keyIDFromThumbprint(true) - .algorithm(Algorithm("EC")) + .algorithm(Algorithm("ES256")) .issueTime(Date()) .generate() diff --git a/modules/openapi/src/commonMain/kotlin/com/sphereon/oid/fed/openapi/openapi.yaml b/modules/openapi/src/commonMain/kotlin/com/sphereon/oid/fed/openapi/openapi.yaml index 426a8b4b..c40ef801 100644 --- a/modules/openapi/src/commonMain/kotlin/com/sphereon/oid/fed/openapi/openapi.yaml +++ b/modules/openapi/src/commonMain/kotlin/com/sphereon/oid/fed/openapi/openapi.yaml @@ -2495,12 +2495,20 @@ components: type: string description: The metadata key. example: openid_relying_party - value: + metadata: additionalProperties: true description: The metadata object. required: - key - - value + - metadata + + PublishEntityStatementDTO: + type: object + properties: + dry-run: + type: boolean + description: If true, the statement will not be published. + example: false CreateAuthorityHintDTO: type: object diff --git a/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/builder/EntityConfigurationStatementBuilder.kt b/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/builder/EntityConfigurationStatementBuilder.kt index f24a7e25..dab956da 100644 --- a/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/builder/EntityConfigurationStatementBuilder.kt +++ b/modules/openid-federation-common/src/commonMain/kotlin/com/sphereon/oid/fed/common/builder/EntityConfigurationStatementBuilder.kt @@ -32,7 +32,8 @@ class EntityConfigurationStatementBuilder { @OptIn(ExperimentalSerializationApi::class) private fun createJwks(jwks: Array): JsonObject { - val jsonArray: JsonArray = Json.encodeToJsonElement(ArraySerializer(JwkDTO.serializer()), jwks) as JsonArray + val jsonArray: JsonArray = + Json.encodeToJsonElement(ArraySerializer(JwkDTO.serializer()), jwks) as JsonArray return buildJsonObject { put("keys", jsonArray) diff --git a/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/2.sqm b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/2.sqm index 61d5198e..4cdb7165 100644 --- a/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/2.sqm +++ b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/2.sqm @@ -1,26 +1,8 @@ CREATE TABLE Jwk ( id SERIAL PRIMARY KEY, - uuid UUID DEFAULT gen_random_uuid(), account_id INT NOT NULL, - kty VARCHAR(10) NOT NULL, - crv VARCHAR(10), kid VARCHAR(255) UNIQUE, - x TEXT, - y TEXT, - d TEXT, - n TEXT, - e TEXT, - p TEXT, - q TEXT, - dp TEXT, - dq TEXT, - qi TEXT, - x5u TEXT, - x5c TEXT[], - x5t TEXT, - x5t_s256 TEXT, - alg VARCHAR(10), - use VARCHAR(10) NULL, + key TEXT NOT NULL, revoked_at TIMESTAMP, revoked_reason TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, diff --git a/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/5.sqm b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/5.sqm index ef6a2bac..1b5c22f1 100644 --- a/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/5.sqm +++ b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/5.sqm @@ -2,7 +2,7 @@ CREATE TABLE EntityConfigurationMetadata ( id SERIAL PRIMARY KEY, account_id INT NOT NULL, key TEXT NOT NULL, - value TEXT NOT NULL, + metadata TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, deleted_at TIMESTAMP, CONSTRAINT FK_ParentEntityConfigurationMetadata FOREIGN KEY (account_id) REFERENCES Account (id) diff --git a/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/EntityConfigurationMetadata.sq b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/EntityConfigurationMetadata.sq index 72873978..bd083732 100644 --- a/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/EntityConfigurationMetadata.sq +++ b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/EntityConfigurationMetadata.sq @@ -2,7 +2,7 @@ create: INSERT INTO EntityConfigurationMetadata ( account_id, key, - value + metadata ) VALUES (?, ?, ?) RETURNING *; delete: diff --git a/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/Key.sq b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/Key.sq index 1db776f8..108a8dc0 100644 --- a/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/Key.sq +++ b/modules/persistence/src/commonMain/sqldelight/com/sphereon/oid/fed/persistence/models/Key.sq @@ -1,33 +1,15 @@ create: INSERT INTO Jwk ( account_id, - kty, - crv, kid, - x, - y, - d, - n, - e, - p, - q, - dp, - dq, - qi, - x5u, - x5c, - x5t, - x5t_s256, - alg, - use -) -VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *; + key +) VALUES (?, ?, ?) RETURNING *; revoke: UPDATE Jwk SET (revoked_at, revoked_reason) = (CURRENT_TIMESTAMP, ?) WHERE id = ?; findByAccountId: -SELECT * FROM Jwk WHERE account_id = ?; +SELECT * FROM Jwk WHERE account_id = ? AND revoked_at IS NULL ORDER BY created_at DESC; findById: SELECT * FROM Jwk WHERE id = ?; diff --git a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/Constants.kt b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/Constants.kt index d4c04fe3..8df7a059 100644 --- a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/Constants.kt +++ b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/Constants.kt @@ -14,5 +14,6 @@ class Constants { const val AUTHORITY_HINT_NOT_FOUND = "Authority hint not found" const val FAILED_TO_DELETE_AUTHORITY_HINT = "Failed to delete authority hint" const val AUTHORITY_HINT_ALREADY_EXISTS = "Authority hint already exists" + const val NO_KEYS_FOUND = "No keys found" } } diff --git a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/EntityConfigurationMetadataService.kt b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/EntityConfigurationMetadataService.kt index d5e26837..61c9261b 100644 --- a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/EntityConfigurationMetadataService.kt +++ b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/EntityConfigurationMetadataService.kt @@ -1,7 +1,8 @@ package com.sphereon.oid.fed.services +import com.sphereon.oid.fed.openapi.models.EntityConfigurationMetadataDTO import com.sphereon.oid.fed.persistence.Persistence -import com.sphereon.oid.fed.persistence.models.EntityConfigurationMetadata +import com.sphereon.oid.fed.services.extensions.toEntityConfigurationMetadataDTO import kotlinx.serialization.json.JsonObject class EntityConfigurationMetadataService { @@ -9,7 +10,7 @@ class EntityConfigurationMetadataService { accountUsername: String, key: String, metadata: JsonObject - ): EntityConfigurationMetadata { + ): EntityConfigurationMetadataDTO { val account = Persistence.accountQueries.findByUsername(accountUsername).executeAsOneOrNull() ?: throw IllegalArgumentException(Constants.ACCOUNT_NOT_FOUND) @@ -20,22 +21,22 @@ class EntityConfigurationMetadataService { throw IllegalStateException(Constants.ENTITY_CONFIGURATION_METADATA_ALREADY_EXISTS) } - return Persistence.entityConfigurationMetadataQueries.create(account.id, key, metadata.toString()) - .executeAsOneOrNull() - ?: throw IllegalStateException(Constants.FAILED_TO_CREATE_ENTITY_CONFIGURATION_METADATA) - } + var createdMetadata = + Persistence.entityConfigurationMetadataQueries.create(account.id, key, metadata.toString()) + .executeAsOneOrNull() + ?: throw IllegalStateException(Constants.FAILED_TO_CREATE_ENTITY_CONFIGURATION_METADATA) - fun findByAccountId(accountId: Int): Array { - return Persistence.entityConfigurationMetadataQueries.findByAccountId(accountId).executeAsList().toTypedArray() + return createdMetadata.toEntityConfigurationMetadataDTO() } - fun findByAccountUsername(accountUsername: String): Array { + fun findByAccountUsername(accountUsername: String): Array { val account = Persistence.accountQueries.findByUsername(accountUsername).executeAsOneOrNull() ?: throw IllegalArgumentException(Constants.ACCOUNT_NOT_FOUND) - return Persistence.entityConfigurationMetadataQueries.findByAccountId(account.id).executeAsList().toTypedArray() + return Persistence.entityConfigurationMetadataQueries.findByAccountId(account.id).executeAsList() + .map { it.toEntityConfigurationMetadataDTO() }.toTypedArray() } - fun deleteEntityConfigurationMetadata(accountUsername: String, id: Int): EntityConfigurationMetadata { + fun deleteEntityConfigurationMetadata(accountUsername: String, id: Int): EntityConfigurationMetadataDTO { val account = Persistence.accountQueries.findByUsername(accountUsername).executeAsOneOrNull() ?: throw IllegalArgumentException(Constants.ACCOUNT_NOT_FOUND) @@ -47,7 +48,9 @@ class EntityConfigurationMetadataService { throw IllegalArgumentException(Constants.ENTITY_CONFIGURATION_METADATA_NOT_FOUND) } - return Persistence.entityConfigurationMetadataQueries.delete(id).executeAsOneOrNull() + val deletedMetadata = Persistence.entityConfigurationMetadataQueries.delete(id).executeAsOneOrNull() ?: throw IllegalArgumentException(Constants.ENTITY_CONFIGURATION_METADATA_NOT_FOUND) + + return deletedMetadata.toEntityConfigurationMetadataDTO() } } diff --git a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/EntityConfigurationStatementService.kt b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/EntityConfigurationStatementService.kt index 36a4e8ad..981bd2cf 100644 --- a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/EntityConfigurationStatementService.kt +++ b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/EntityConfigurationStatementService.kt @@ -6,15 +6,14 @@ import com.sphereon.oid.fed.openapi.models.EntityConfigurationStatement import com.sphereon.oid.fed.openapi.models.FederationEntityMetadata import com.sphereon.oid.fed.openapi.models.JWTHeader import com.sphereon.oid.fed.persistence.Persistence -import com.sphereon.oid.fed.services.extensions.toJwkDTO -import kotlinx.serialization.encodeToString +import com.sphereon.oid.fed.services.extensions.toJwkDto import kotlinx.serialization.json.Json import kotlinx.serialization.json.jsonObject class EntityConfigurationStatementService { private val accountService = AccountService() private val keyService = KeyService() - private val kmsService = KmsService("local") + private val kmsClient = KmsService.getKmsClient() private val entityConfigurationStatementQueries = Persistence.entityConfigurationStatementQueries private val accountQueries = Persistence.accountQueries private val subordinateQueries = Persistence.subordinateQueries @@ -24,7 +23,7 @@ class EntityConfigurationStatementService { val account = accountQueries.findByUsername(accountUsername).executeAsOneOrNull() ?: throw IllegalArgumentException(Constants.ACCOUNT_NOT_FOUND) val identifier = accountService.getAccountIdentifier(account.username) - val keys = keyService.getKeys(accountUsername).map { it.toJwkDTO() }.toTypedArray() + val keys = keyService.getKeys(accountUsername).map { it }.toTypedArray() val hasSubordinates = subordinateQueries.findByAccountId(account.id).executeAsList().isNotEmpty() val authorityHints = authorityHintQueries.findByAccountId(account.id).executeAsList().map { it.identifier }.toTypedArray() @@ -34,7 +33,7 @@ class EntityConfigurationStatementService { .iss(identifier) .iat((System.currentTimeMillis() / 1000).toInt()) .exp((System.currentTimeMillis() / 1000 + 3600 * 24 * 365).toInt()) - .jwks(keys) + .jwks(keys.map { it.toJwkDto() }.toTypedArray()) if (hasSubordinates) { val federationEntityMetadata = FederationEntityMetadataBuilder() @@ -55,33 +54,45 @@ class EntityConfigurationStatementService { metadata.forEach { entityConfigurationStatement.metadata( - Pair(it.key, Json.parseToJsonElement(it.value_).jsonObject) + Pair(it.key, Json.parseToJsonElement(it.metadata).jsonObject) ) } return entityConfigurationStatement.build() } - fun publishByUsername(accountUsername: String): EntityConfigurationStatement { + fun publishByUsername(accountUsername: String, dryRun: Boolean? = false): String { val account = accountService.getAccountByUsername(accountUsername) val entityConfigurationStatement = findByUsername(accountUsername) - val entityConfigurationStatementStr = Json.encodeToString(entityConfigurationStatement) - val entityConfigurationStatementObject = Json.parseToJsonElement(entityConfigurationStatementStr).jsonObject - val key = "key_id" - val jwt = kmsService.sign( - payload = entityConfigurationStatementObject, + val keys = keyService.getKeys(accountUsername) + + if (keys.isEmpty()) { + throw IllegalArgumentException(Constants.NO_KEYS_FOUND) + } + + val key = keys[0].kid + + val jwt = kmsClient.sign( + payload = Json.encodeToJsonElement( + EntityConfigurationStatement.serializer(), + entityConfigurationStatement + ).jsonObject, header = JWTHeader(typ = "entity-statement+jwt"), - keyId = key + keyId = key!! ) + if (dryRun == true) { + return jwt + } + entityConfigurationStatementQueries.create( account_id = account.id, expires_at = entityConfigurationStatement.exp.toLong(), statement = jwt ).executeAsOne() - return entityConfigurationStatement + return jwt } } diff --git a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/KeyService.kt b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/KeyService.kt index 95ffde0f..32abaf7f 100644 --- a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/KeyService.kt +++ b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/KeyService.kt @@ -1,58 +1,34 @@ package com.sphereon.oid.fed.services -import com.sphereon.oid.fed.kms.local.jwk.generateKeyPair import com.sphereon.oid.fed.openapi.models.JwkAdminDTO import com.sphereon.oid.fed.persistence.Persistence -import com.sphereon.oid.fed.persistence.models.Jwk -import com.sphereon.oid.fed.services.extensions.decrypt -import com.sphereon.oid.fed.services.extensions.encrypt import com.sphereon.oid.fed.services.extensions.toJwkAdminDTO +import kotlinx.serialization.json.Json class KeyService { + private val kmsClient = KmsService.getKmsClient() private val accountQueries = Persistence.accountQueries private val keyQueries = Persistence.keyQueries - fun create(accountUsername: String): Jwk { + fun create(accountUsername: String): JwkAdminDTO { val account = accountQueries.findByUsername(accountUsername).executeAsOne() - val encryptedKeyPair = generateKeyPair().encrypt() + val jwk = kmsClient.generateKeyPair() - val key = keyQueries.create( - account.id, - y = encryptedKeyPair.y, - x = encryptedKeyPair.x, - d = encryptedKeyPair.d, - crv = encryptedKeyPair.crv, - kty = encryptedKeyPair.kty, - use = encryptedKeyPair.use, - alg = encryptedKeyPair.alg, - kid = encryptedKeyPair.kid, - e = encryptedKeyPair.e, - n = encryptedKeyPair.n, - p = encryptedKeyPair.p, - x5c = encryptedKeyPair.x5c, - dp = encryptedKeyPair.dp, - x5t_s256 = encryptedKeyPair.x5tS256, - q = encryptedKeyPair.q, - qi = encryptedKeyPair.qi, - dq = encryptedKeyPair.dq, - x5u = encryptedKeyPair.x5u, - x5t = encryptedKeyPair.x5t, + keyQueries.create( + account_id = account.id, + kid = jwk.kid!!, + key = Json.encodeToString(JwkAdminDTO.serializer(), jwk), ).executeAsOne() - return key + return jwk } - fun getDecryptedKey(keyId: Int): Jwk { - var key = keyQueries.findById(keyId).executeAsOne() - return key.decrypt() - } - - fun getKeys(accountUsername: String): Array { + fun getKeys(accountUsername: String): Array { val account = accountQueries.findByUsername(accountUsername).executeAsOne() - return keyQueries.findByAccountId(account.id).executeAsList().toTypedArray() + return keyQueries.findByAccountId(account.id).executeAsList().map { it.toJwkAdminDTO() }.toTypedArray() } fun revokeKey(accountUsername: String, keyId: Int, reason: String?): JwkAdminDTO { 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 a692c2db..5a95d04d 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 @@ -2,30 +2,22 @@ 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 -class KmsService(private val provider: String) { +object KmsService { + private val provider: String = System.getenv("KMS_PROVIDER") ?: "local" private val kmsClient: KmsClient = when (provider) { "local" -> LocalKmsClient() else -> throw IllegalArgumentException("Unsupported KMS provider: $provider") } - fun generateKeyPair(keyId: String) { - kmsClient.generateKeyPair(keyId) - } - - fun sign(header: JWTHeader, payload: JsonObject, keyId: String): String { - return kmsClient.sign(header, payload, keyId) - } - - fun verify(token: String, jwk: Jwk): Boolean { - return kmsClient.verify(token, jwk) - } + fun getKmsClient(): KmsClient = kmsClient } interface KmsClient { - fun generateKeyPair(keyId: String) + fun generateKeyPair(): JwkAdminDTO fun sign(header: JWTHeader, payload: JsonObject, keyId: String): String fun verify(token: String, jwk: Jwk): Boolean -} \ No newline at end of file +} 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 b5b8c5eb..64edca2f 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 @@ -3,14 +3,17 @@ package com.sphereon.oid.fed.services import com.sphereon.oid.fed.kms.local.LocalKms 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 class LocalKmsClient : KmsClient { private val localKms = LocalKms() - override fun generateKeyPair(keyId: String) { - return localKms.generateKey(keyId) + override fun generateKeyPair(): JwkAdminDTO { + return localKms.generateKey() } override fun sign(header: JWTHeader, payload: JsonObject, keyId: String): String { @@ -20,4 +23,4 @@ class LocalKmsClient : KmsClient { override fun verify(token: String, jwk: Jwk): Boolean { return localKms.verify(token, jwk) } -} \ No newline at end of file +} diff --git a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/extensions/EntityConfigurationMetadataExtension.kt b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/extensions/EntityConfigurationMetadataExtension.kt new file mode 100644 index 00000000..d583715e --- /dev/null +++ b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/extensions/EntityConfigurationMetadataExtension.kt @@ -0,0 +1,18 @@ +package com.sphereon.oid.fed.services.extensions + +import com.sphereon.oid.fed.openapi.models.EntityConfigurationMetadataDTO +import com.sphereon.oid.fed.persistence.models.EntityConfigurationMetadata +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonObject + + +fun EntityConfigurationMetadata.toEntityConfigurationMetadataDTO(): EntityConfigurationMetadataDTO { + return EntityConfigurationMetadataDTO( + id = this.id, + key = this.key, + accountId = this.account_id, + metadata = Json.parseToJsonElement(this.metadata).jsonObject, + createdAt = this.created_at.toString(), + deletedAt = this.deleted_at?.toString() + ) +} diff --git a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/extensions/KeyExtensions.kt b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/extensions/KeyExtensions.kt index e70fb2df..1011e0d9 100644 --- a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/extensions/KeyExtensions.kt +++ b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/extensions/KeyExtensions.kt @@ -3,75 +3,45 @@ package com.sphereon.oid.fed.services.extensions import com.sphereon.oid.fed.openapi.models.Jwk import com.sphereon.oid.fed.openapi.models.JwkAdminDTO import com.sphereon.oid.fed.openapi.models.JwkDTO +import kotlinx.serialization.json.Json import com.sphereon.oid.fed.persistence.models.Jwk as JwkPersistence -fun JwkPersistence.toJwkAdminDTO(): JwkAdminDTO = JwkAdminDTO( - id = id, - accountId = account_id, - uuid = uuid.toString(), - e = e, - n = n, - x = x, - y = y, - alg = alg, - crv = crv, - kid = kid, - kty = kty, - use = use, - x5c = x5c, - x5t = x5t, - x5u = x5u, - x5tHashS256 = x5t_s256, - createdAt = created_at.toString(), - revokedAt = revoked_at.toString(), - revokedReason = revoked_reason -) - -fun JwkPersistence.toJwkDTO(): JwkDTO = JwkDTO( - e = e, - n = n, - x = x, - y = y, - alg = alg, - crv = crv, - kid = kid, - kty = kty, - use = use, - x5c = x5c, - x5t = x5t, - x5u = x5u, -) - -fun Jwk.encrypt(): Jwk { - if (System.getenv("APP_KEY") == null) return this - - fun String?.encryptOrNull() = this?.let { aesEncrypt(it, System.getenv("APP_KEY")) } - - return copy( - d = d.encryptOrNull(), - dq = dq.encryptOrNull(), - qi = qi.encryptOrNull(), - dp = dp.encryptOrNull(), - p = p.encryptOrNull(), - q = q.encryptOrNull() +fun JwkPersistence.toJwkAdminDTO(): JwkAdminDTO { + val key = Json.decodeFromString(this.key) + + return JwkAdminDTO( + id = id, + accountId = account_id, + e = key.e, + x = key.x, + y = key.y, + n = key.n, + alg = key.alg, + crv = key.crv, + kid = key.kid, + kty = key.kty, + use = key.use, + x5c = key.x5c, + x5t = key.x5t, + x5u = key.x5u, + x5tHashS256 = key.x5tS256, ) } -fun JwkPersistence.decrypt(): JwkPersistence { - if (System.getenv("APP_KEY") == null) return this - - fun String?.decryptOrNull() = this?.let { aesDecrypt(it, System.getenv("APP_KEY")) } - - return copy( - d = d.decryptOrNull(), - dq = dq.decryptOrNull(), - qi = qi.decryptOrNull(), - dp = dp.decryptOrNull(), - p = p.decryptOrNull(), - q = q.decryptOrNull() +fun JwkAdminDTO.toJwkDto(): JwkDTO { + return JwkDTO( + crv = crv, + e = e, + x = x, + y = y, + n = n, + alg = alg, + kid = kid, + kty = kty!!, + use = use, + x5c = x5c, + x5t = x5t, + x5u = x5u, + x5tS256 = x5tHashS256, ) } - -expect fun aesEncrypt(data: String, key: String): String -expect fun aesDecrypt(data: String, key: String): String - diff --git a/modules/services/src/jvmMain/kotlin/com/sphereon/oid/fed/services/extensions/KeyExtensions.jvm.kt b/modules/services/src/jvmMain/kotlin/com/sphereon/oid/fed/services/extensions/KeyExtensions.jvm.kt deleted file mode 100644 index 9aa632c6..00000000 --- a/modules/services/src/jvmMain/kotlin/com/sphereon/oid/fed/services/extensions/KeyExtensions.jvm.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.sphereon.oid.fed.services.extensions - -import java.util.* -import javax.crypto.Cipher -import javax.crypto.spec.SecretKeySpec - -private const val ALGORITHM = "AES" -private const val KEY_SIZE = 32 - -actual fun aesEncrypt(data: String, key: String): String { - val secretKey = SecretKeySpec(key.padEnd(KEY_SIZE, '0').toByteArray(Charsets.UTF_8), ALGORITHM) - - val cipher = Cipher.getInstance(ALGORITHM) - cipher.init(Cipher.ENCRYPT_MODE, secretKey) - - val encryptedValue = cipher.doFinal(data.toByteArray(Charsets.UTF_8)) - return Base64.getEncoder().encodeToString(encryptedValue) -} - -actual fun aesDecrypt(data: String, key: String): String { - val secretKey = SecretKeySpec(key.padEnd(KEY_SIZE, '0').toByteArray(Charsets.UTF_8), ALGORITHM) - - val cipher = Cipher.getInstance(ALGORITHM) - cipher.init(Cipher.DECRYPT_MODE, secretKey) - - val decodedValue = Base64.getDecoder().decode(data) - val decryptedValue = cipher.doFinal(decodedValue) - return String(decryptedValue, Charsets.UTF_8) -} diff --git a/modules/services/src/jvmTest/kotlin/com/sphereon/oid/fed/services/KeyServiceTest.jvm.kt b/modules/services/src/jvmTest/kotlin/com/sphereon/oid/fed/services/KeyServiceTest.jvm.kt deleted file mode 100644 index cdb367a1..00000000 --- a/modules/services/src/jvmTest/kotlin/com/sphereon/oid/fed/services/KeyServiceTest.jvm.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.sphereon.oid.fed.services - -import com.sphereon.oid.fed.kms.local.jwk.generateKeyPair -import com.sphereon.oid.fed.services.extensions.decrypt -import com.sphereon.oid.fed.services.extensions.encrypt -import org.junit.Test -import java.time.LocalDateTime -import java.util.* -import kotlin.test.assertEquals -import kotlin.test.assertNotEquals -import com.sphereon.oid.fed.persistence.models.Jwk as JwkPersistence - -class KeyServiceTest { - @Test - fun testEncryption() { - val key = generateKeyPair() - val encryptedKey = key.encrypt() - - if (System.getenv("APP_KEY") == null) { - assertEquals(key.d, encryptedKey.d) - } else { - assertNotEquals(key.d, encryptedKey.d) - } - - val persistenceJwk = JwkPersistence( - id = 1, - account_id = 1, - d = encryptedKey.d, - e = encryptedKey.e, - n = encryptedKey.n, - x = encryptedKey.x, - y = encryptedKey.y, - alg = encryptedKey.alg, - crv = encryptedKey.crv, - p = encryptedKey.p, - q = encryptedKey.q, - dp = encryptedKey.dp, - qi = encryptedKey.qi, - dq = encryptedKey.dq, - x5t = encryptedKey.x5t, - x5t_s256 = encryptedKey.x5tS256, - x5u = encryptedKey.x5u, - kid = encryptedKey.kid, - kty = encryptedKey.kty, - x5c = encryptedKey.x5c, - created_at = LocalDateTime.now(), - revoked_reason = null, - revoked_at = null, - uuid = UUID.randomUUID(), - use = encryptedKey.use - ) - - val decryptedPersistenceJwk = persistenceJwk.decrypt() - - assertEquals(key.d, decryptedPersistenceJwk.d) - } -}