diff --git a/settings.gradle.kts b/settings.gradle.kts index bf63a3380..29b4544ea 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -18,6 +18,7 @@ val modules = listOf( * "$libraries:crypto".group( "waltid-crypto", "waltid-crypto-oci", + "waltid-crypto-aws", "waltid-crypto-android" whenEnabled enableAndroidBuild, "waltid-crypto-ios" whenEnabled enableIosBuild, "waltid-target-ios" whenEnabled enableIosBuild, diff --git a/waltid-libraries/crypto/waltid-crypto-aws/README.md b/waltid-libraries/crypto/waltid-crypto-aws/README.md new file mode 100644 index 000000000..32c32e901 --- /dev/null +++ b/waltid-libraries/crypto/waltid-crypto-aws/README.md @@ -0,0 +1,33 @@ +# AWS SDK Extension for walt.id Crypto + +A Kotlin-based extension that enhances walt.id crypto with native AWS key management capabilities. + +## Overview + +This extension introduces `AwsKey`, a robust implementation leveraging the AWS SDK for Kotlin to manage cryptographic keys. It serves as a more integrated alternative to the platform-agnostic `AWSKeyRestAPI` found in the base walt.id crypto library. + +## Key Features + +- Native AWS SDK integration for optimal performance +- Kotlin-specific implementation +- Seamless key management through AWS KMS +- Direct SDK access instead of REST API calls + +## Authentication + +The extension utilizes AWS SDK's default credential provider chain for authentication, automatically detecting credentials from multiple sources including: + +- Environment variables +- AWS credentials file +- IAM roles for EC2 +- Container credentials +- SSO credentials + +## Comparison to Base Implementation + +While the base `AWSKeyRestAPI` offers cross-platform compatibility through REST endpoints, this extension provides: + +- Improved performance through direct SDK calls +- Enhanced error handling +- Native integration with AWS services + diff --git a/waltid-libraries/crypto/waltid-crypto-aws/build.gradle.kts b/waltid-libraries/crypto/waltid-crypto-aws/build.gradle.kts new file mode 100644 index 000000000..25c487dd9 --- /dev/null +++ b/waltid-libraries/crypto/waltid-crypto-aws/build.gradle.kts @@ -0,0 +1,115 @@ +plugins { + kotlin("jvm") version "2.0.21" + kotlin("plugin.serialization") version "2.0.21" + id("com.github.ben-manes.versions") + id("maven-publish") +} + +group = "id.walt.crypto" + +repositories { + mavenCentral() + maven("https://jitpack.io") +} + +dependencies { + testImplementation(kotlin("test")) + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0") + + // walt.id + api(project(":waltid-libraries:crypto:waltid-crypto")) + + // JSON + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3") + + // Coroutines + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") + + // AWS + implementation("aws.sdk.kotlin:kms:1.3.91") + + // JOSE + implementation("com.nimbusds:nimbus-jose-jwt:9.41.1") +} + +java { + sourceCompatibility = JavaVersion.VERSION_15 + targetCompatibility = JavaVersion.VERSION_15 + withJavadocJar() + withSourcesJar() +} + +tasks.test { + useJUnitPlatform() +} + +tasks.withType { + enabled = false +} + + +kotlin { + jvmToolchain(15) + sourceSets { + all { + languageSettings.enableLanguageFeature("InlineClasses") + } + } +} + +publishing { + publications { + create("maven") { + from(components["java"]) + + pom { + name.set("Walt.id Crypto AWS") + description.set("Walt.id Crypto AWS Integration") + url.set("https://walt.id") + + licenses { + license { + name.set("Apache License 2.0") + url.set("https://www.apache.org/licenses/LICENSE-2.0") + } + } + + developers { + developer { + id.set("walt.id") + name.set("walt.id") + email.set("office@walt.id") + } + } + } + } + } + + repositories { + maven { + val releasesRepoUrl = uri("https://maven.waltid.dev/releases") + val snapshotsRepoUrl = uri("https://maven.waltid.dev/snapshots") + url = uri( + if (version.toString().endsWith("SNAPSHOT") + ) snapshotsRepoUrl else releasesRepoUrl + ) + + val envUsername = System.getenv("MAVEN_USERNAME") + val envPassword = System.getenv("MAVEN_PASSWORD") + + val usernameFile = File("$rootDir/secret_maven_username.txt") + val passwordFile = File("$rootDir/secret_maven_password.txt") + + val secretMavenUsername = envUsername ?: usernameFile.let { + if (it.isFile) it.readLines().first() else "" + } + val secretMavenPassword = envPassword ?: passwordFile.let { + if (it.isFile) it.readLines().first() else "" + } + credentials { + username = secretMavenUsername + password = secretMavenPassword + } + } + } +} \ No newline at end of file diff --git a/waltid-libraries/crypto/waltid-crypto-aws/src/main/kotlin/id.walt.crypto.keys.aws/AWSKey.kt b/waltid-libraries/crypto/waltid-crypto-aws/src/main/kotlin/id.walt.crypto.keys.aws/AWSKey.kt new file mode 100644 index 000000000..a458e4d1d --- /dev/null +++ b/waltid-libraries/crypto/waltid-crypto-aws/src/main/kotlin/id.walt.crypto.keys.aws/AWSKey.kt @@ -0,0 +1,224 @@ +package id.walt.crypto.keys.aws + +import aws.sdk.kotlin.services.kms.KmsClient +import aws.sdk.kotlin.services.kms.model.* +import id.walt.crypto.exceptions.KeyTypeNotSupportedException +import id.walt.crypto.keys.EccUtils +import id.walt.crypto.keys.Key +import id.walt.crypto.keys.KeyMeta +import id.walt.crypto.keys.KeyType +import id.walt.crypto.keys.jwk.JWKKey +import id.walt.crypto.utils.Base64Utils.decodeFromBase64 +import id.walt.crypto.utils.Base64Utils.encodeToBase64 +import id.walt.crypto.utils.Base64Utils.encodeToBase64Url +import id.walt.crypto.utils.JsonUtils.toJsonElement +import id.walt.crypto.utils.jwsSigningAlgorithm +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.jsonObject + + +@Serializable +@SerialName("aws") +class AWSKey( + val config: AWSKeyMetadataSDK, + val id: String, + private var _publicKey: String? = null, + private var _keyType: KeyType? = null, + + ) : Key() { + + @Transient + override var keyType: KeyType + get() = _keyType!! + set(value) { + _keyType = value + } + + override val hasPrivateKey: Boolean + get() = false + + + override fun toString(): String = "[AWS ${keyType.name} key @AWS ${config.region} - $id]" + + override suspend fun getKeyId(): String = getPublicKey().getKeyId() + + override suspend fun getThumbprint(): String { + TODO("Not yet implemented") + } + + override suspend fun exportJWK(): String = throw NotImplementedError("JWK export is not available for remote keys.") + + override suspend fun exportJWKObject(): JsonObject = Json.parseToJsonElement(_publicKey!!).jsonObject + + override suspend fun exportPEM(): String = throw NotImplementedError("PEM export is not available for remote keys.") + + private val awsSigningAlgorithm by lazy { + when (keyType) { + KeyType.secp256r1, KeyType.secp256k1 -> "ECDSA_SHA_256" + KeyType.RSA -> "RSASSA_PKCS1_V1_5_SHA_256" + else -> throw KeyTypeNotSupportedException(keyType.name) + } + } + + override suspend fun signRaw(plaintext: ByteArray): ByteArray { + val signRequest = SignRequest { + this.keyId = id + signingAlgorithm = SigningAlgorithmSpec.fromValue(awsSigningAlgorithm) + message = plaintext + messageType = MessageType.Raw + } + + return KmsClient { region = config.region }.use { kmsClient -> + kmsClient.sign(signRequest).signature ?: throw IllegalStateException("Signature not returned") + } + } + + + override suspend fun signJws( + plaintext: ByteArray, + headers: Map + ): String { + val appendedHeader = HashMap(headers).apply { + put("alg", jwsSigningAlgorithm(keyType).toJsonElement()) + } + + val header = Json.encodeToString(appendedHeader).encodeToByteArray().encodeToBase64Url() + val payload = plaintext.encodeToBase64Url() + + var rawSignature = signRaw("$header.$payload".encodeToByteArray()) + + if (keyType in listOf(KeyType.secp256r1, KeyType.secp256k1)) { // TODO: Add RSA support + rawSignature = EccUtils.convertDERtoIEEEP1363(rawSignature) + } + + val encodedSignature = rawSignature.encodeToBase64Url() + val jws = "$header.$payload.$encodedSignature" + + return jws + } + + override suspend fun verifyRaw( + signed: ByteArray, + detachedPlaintext: ByteArray? + ): Result { + val verifyRequest = VerifyRequest { + this.keyId = id + signingAlgorithm = SigningAlgorithmSpec.fromValue(awsSigningAlgorithm) + message = detachedPlaintext + this.signature = signed + messageType = MessageType.Raw + } + + return KmsClient { region = config.region }.use { kmsClient -> + val response = kmsClient.verify(verifyRequest) + Result.success(response.signatureValid.toString().decodeFromBase64()) + } + } + + + override suspend fun verifyJws(signedJws: String): Result { + val publicKey = getPublicKey() + val verification = publicKey.verifyJws(signedJws) + return verification + } + + @Transient + private var backedKey: Key? = null + + override suspend fun getPublicKey(): Key = backedKey ?: when { + _publicKey != null -> _publicKey!!.let { JWKKey.importJWK(it).getOrThrow() } + else -> retrievePublicKey() + }.also { newBackedKey -> backedKey = newBackedKey } + + + private suspend fun retrievePublicKey(): Key { + val publicKey = getAwsPublicKey(config, id) + _publicKey = publicKey.exportJWK() + return publicKey + } + + override suspend fun getPublicKeyRepresentation(): ByteArray { + TODO("Not yet implemented") + } + + override suspend fun getMeta(): KeyMeta { + TODO("Not yet implemented") + } + + override suspend fun deleteKey(): Boolean { + val request = ScheduleKeyDeletionRequest { + this.keyId = id + pendingWindowInDays = 7 + } + + val delete = KmsClient { region = config.region }.use { kmsClient -> + kmsClient.scheduleKeyDeletion(request) + } + return delete.keyState?.value == "PendingDeletion" + } + + + companion object { + + + suspend fun generateKey(keyType: KeyType, config: AWSKeyMetadataSDK): AWSKey { + val request = CreateKeyRequest { + this.description = description + keySpec = KeySpec.fromValue(keyTypeToAwsKeyMapping(keyType)) + keyUsage = KeyUsageType.SignVerify + } + val response = KmsClient { region = config.region }.use { kmsClient -> + kmsClient.createKey(request) + } + + val keyid = response.keyMetadata?.keyId ?: throw IllegalStateException("Key ID not returned") + val publicKey = getAwsPublicKey(config, keyid) + val keyType = response.keyMetadata?.keySpec?.value + return AWSKey( + config = config, + id = keyid, + _publicKey = publicKey.exportJWK(), + _keyType = awsKeyToKeyTypeMapping(keyType.toString()) + ) + } + + + suspend fun getAwsPublicKey(config: AWSKeyMetadataSDK, keyId: String): Key { + KmsClient { region = config.region }.use { kmsClient -> + val pk = kmsClient.getPublicKey(GetPublicKeyRequest { + this.keyId = keyId + }).publicKey ?: throw IllegalStateException("Public key not returned") + val encodedPk = pk.encodeToBase64() + + val pemKey = """ +-----BEGIN PUBLIC KEY----- +$encodedPk +-----END PUBLIC KEY----- +""".trimIndent() + val keyJWK = JWKKey.importPEM(pemKey) + return keyJWK.getOrThrow() + } + } + + private fun keyTypeToAwsKeyMapping(type: KeyType) = when (type) { + KeyType.secp256r1 -> "ECC_NIST_P256" + KeyType.secp256k1 -> "ECC_SECG_P256K1" + KeyType.RSA -> "RSA_2048" + else -> throw KeyTypeNotSupportedException(type.name) + } + + private fun awsKeyToKeyTypeMapping(type: String) = when (type) { + "ECC_NIST_P256" -> KeyType.secp256r1 + "ECC_SECG_P256K1" -> KeyType.secp256k1 + "RSA_2048" -> KeyType.RSA + else -> throw KeyTypeNotSupportedException(type) + } + } + +} \ No newline at end of file diff --git a/waltid-libraries/crypto/waltid-crypto-aws/src/main/kotlin/id.walt.crypto.keys.aws/AWSKeyMetadataSDK.kt b/waltid-libraries/crypto/waltid-crypto-aws/src/main/kotlin/id.walt.crypto.keys.aws/AWSKeyMetadataSDK.kt new file mode 100644 index 000000000..94347f044 --- /dev/null +++ b/waltid-libraries/crypto/waltid-crypto-aws/src/main/kotlin/id.walt.crypto.keys.aws/AWSKeyMetadataSDK.kt @@ -0,0 +1,9 @@ +package id.walt.crypto.keys.aws + +import kotlinx.serialization.Serializable + + +@Serializable +data class AWSKeyMetadataSDK ( + val region: String +) \ No newline at end of file diff --git a/waltid-libraries/crypto/waltid-crypto-aws/src/main/kotlin/id.walt.crypto.keys.aws/WaltCryptoAws.kt b/waltid-libraries/crypto/waltid-crypto-aws/src/main/kotlin/id.walt.crypto.keys.aws/WaltCryptoAws.kt new file mode 100644 index 000000000..38a4df662 --- /dev/null +++ b/waltid-libraries/crypto/waltid-crypto-aws/src/main/kotlin/id.walt.crypto.keys.aws/WaltCryptoAws.kt @@ -0,0 +1,21 @@ +package id.walt.crypto.keys.aws + +import id.walt.crypto.keys.KeyGenerationRequest +import id.walt.crypto.keys.KeyManager +import id.walt.crypto.keys.KeySerialization +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.decodeFromJsonElement + +object WaltCryptoAws { + fun init() { + KeyManager.register("aws") { generateRequest: KeyGenerationRequest -> + AWSKey.generateKey( + generateRequest.keyType, + Json.decodeFromJsonElement(generateRequest.config!!) + ) + } + KeySerialization.registerExternalKeyType(AWSKey::class) + + } +} + diff --git a/waltid-libraries/crypto/waltid-crypto-aws/src/test/kotlin/AwsSDKKeyTest.kt b/waltid-libraries/crypto/waltid-crypto-aws/src/test/kotlin/AwsSDKKeyTest.kt new file mode 100644 index 000000000..aa1131f8e --- /dev/null +++ b/waltid-libraries/crypto/waltid-crypto-aws/src/test/kotlin/AwsSDKKeyTest.kt @@ -0,0 +1,96 @@ +import id.walt.crypto.keys.KeyType +import id.walt.crypto.keys.aws.AWSKey +import id.walt.crypto.keys.aws.AWSKeyMetadataSDK +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class AwsSDKKeyTest { + + private object Config { + val payloadJWS = JsonObject( + mapOf( + "sub" to JsonPrimitive("16bb17e0-e733-4622-9384-122bc2fc6290"), + "iss" to JsonPrimitive("http://localhost:3000"), + "aud" to JsonPrimitive("TOKEN") + ) + ) + const val payload = "Hello, World!" + val TESTABLE_KEY_TYPES = listOf(KeyType.RSA, KeyType.secp256r1, KeyType.secp256k1) + } + + private lateinit var keys: List + + @Test + fun testAws() = runTest { + awsTestKeyCreation() + awsTestPublicKeys() + awsTestSignRaw() + awsTestSignJws() + awsTestDeleteKey() + } + + private suspend fun awsTestKeyCreation() { + keys = Config.TESTABLE_KEY_TYPES.map { + AWSKey.generateKey( + it, + AWSKeyMetadataSDK( + region = "eu-central-1", + ) + ).also { + println("Generated key: ${it.keyType} - ${it.getKeyId()}") + assertNotNull(it, "Key generation failed for $it") + assertNotNull(it.getKeyId(), "Key ID should not be null") + assertTrue(it.getKeyId().isNotBlank(), "Key ID should not be blank") + } + } + println("Generated ${keys.size} keys") + assertTrue(keys.isNotEmpty(), "No keys were created") + } + + private suspend fun awsTestPublicKeys() { + keys.forEach { key -> + val publicKey = key.getPublicKey() + println("Public key: ${publicKey.exportJWK()}") + assertNotNull(publicKey, "Public key should not be null") + } + } + + private suspend fun awsTestSignRaw() { + keys.forEach { key -> + println("RAW sign/verification test with key type: ${key.keyType}") + val signature = key.signRaw(Config.payload.toByteArray()) + val verified = key.verifyRaw(signature, Config.payload.toByteArray()) + assertNotNull(signature, "Signature should not be null") + assertTrue(signature.isNotEmpty(), "Signature should not be empty") + assertTrue(verified.isSuccess, "Raw signature verification failed") + } + } + + private suspend fun awsTestSignJws() { + keys.forEach { key -> + println("JWS sign/verification test with key type: ${key.keyType}") + val signature = key.signJws(Config.payloadJWS.toString().toByteArray()) + val verified = key.verifyJws(signature) + assertNotNull(signature, "JWS signature should not be null") + assertTrue(signature.isNotEmpty(), "JWS signature should not be empty") + assertTrue(verified.isSuccess, "JWS signature verification failed") + assertEquals(Config.payloadJWS, verified.getOrThrow(), "JWS payload mismatch after verification") + } + } + + private suspend fun awsTestDeleteKey() { + keys.forEach { key -> + println("Deleting key: ${key.getKeyId()}") + val delete = key.deleteKey() + println("Delete result: $delete") + assertTrue(delete, "Key deletion failed") + + } + + } +} diff --git a/waltid-libraries/crypto/waltid-crypto/README.md b/waltid-libraries/crypto/waltid-crypto/README.md index 55ceda6fb..440f7d429 100644 --- a/waltid-libraries/crypto/waltid-crypto/README.md +++ b/waltid-libraries/crypto/waltid-crypto/README.md @@ -706,7 +706,7 @@ vault secrets enable transit ### Working with AWSKey -An AWS account is required in order to be able to use an `AWSKey` for signing and verification. This +An AWS account is required in order to be able to use an `AWSKeyRestAPI` for signing and verification. This implies covering the following steps: 1. [create an AWS account](https://aws.amazon.com/resources/create-account/) @@ -715,16 +715,24 @@ implies covering the following steps: #### AwsKeyMetadata -The `AWSKeyMetadata` class is used to specify the AWS access key ID, secret access key, and region. -It is used when creating an `AWSKey` instance. +The `AWSKeyMetadata` class is used to specify the AWS access key ID, secret access key, roleName and region. +It is used when creating an `AWSKeyRestAPI` instance. + + + ```kotlin @Serializable data class AWSKeyMetadata( - val accessKeyId: String, - val secretAccessKey: String, - val region: String -) + val auth: AWSAuth, +) { + constructor( + accessKeyId: String? = null, + secretAccessKey: String? = null, + region: String? = null, + roleName: String? = null, + ) : this(AWSAuth(accessKeyId, secretAccessKey, region, roleName)) +} ``` For usage examples on _create_, _sign_, _verify_, _import_ and _export_ functions see diff --git a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/KeyManager.kt b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/KeyManager.kt index 792d8f58d..d0f0adb69 100644 --- a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/KeyManager.kt +++ b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/KeyManager.kt @@ -3,7 +3,7 @@ package id.walt.crypto.keys import id.walt.crypto.exceptions.KeyBackendNotSupportedException import id.walt.crypto.exceptions.KeyTypeMissingException import id.walt.crypto.exceptions.KeyTypeNotSupportedException -import id.walt.crypto.keys.aws.AWSKey +import id.walt.crypto.keys.aws.AWSKeyRestAPI import id.walt.crypto.keys.azure.AzureKey import id.walt.crypto.keys.jwk.JWKKey import id.walt.crypto.keys.oci.OCIKeyRestApi @@ -38,8 +38,8 @@ object KeyManager { Json.decodeFromJsonElement(generateRequest.config!!) ) } - register("aws") { generateRequest: KeyGenerationRequest -> - AWSKey.generate( + register("aws-rest-api") { generateRequest: KeyGenerationRequest -> + AWSKeyRestAPI.generate( generateRequest.keyType, Json.decodeFromJsonElement(generateRequest.config!!) ) @@ -51,6 +51,7 @@ object KeyManager { Json.decodeFromJsonElement(generateRequest.config!!) ) } + } fun registerByType(type: KType, typeId: String, createFunction: suspend (KeyGenerationRequest) -> Key) { diff --git a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/KeySerialization.kt b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/KeySerialization.kt index a15119e7b..61402686c 100644 --- a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/KeySerialization.kt +++ b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/KeySerialization.kt @@ -1,21 +1,24 @@ package id.walt.crypto.keys -import id.walt.crypto.keys.aws.AWSKey +import id.walt.crypto.keys.aws.AWSKeyRestAPI import id.walt.crypto.keys.azure.AzureKey import id.walt.crypto.keys.jwk.JWKKey import id.walt.crypto.keys.oci.OCIKeyRestApi import id.walt.crypto.keys.tse.TSEKey import id.walt.crypto.utils.JsonUtils.toJsonElement +import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.encodeToString import kotlinx.serialization.json.* import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.polymorphic import kotlinx.serialization.modules.subclass +import kotlinx.serialization.serializer import love.forte.plugin.suspendtrans.annotation.JsPromise import love.forte.plugin.suspendtrans.annotation.JvmAsync import love.forte.plugin.suspendtrans.annotation.JvmBlocking import kotlin.js.ExperimentalJsExport import kotlin.js.JsExport +import kotlin.reflect.KClass // TODO: Deprecate this in favour of KeyManager @@ -23,21 +26,40 @@ import kotlin.js.JsExport @JsExport object KeySerialization { - private val keySerializationModule = SerializersModule { + private var keySerializationModule = SerializersModule { polymorphic(Key::class) { subclass(JWKKey::class) subclass(TSEKey::class) subclass(OCIKeyRestApi::class) - subclass(AWSKey::class) + subclass(AWSKeyRestAPI::class) subclass(AzureKey::class) } + } - private val keySerializationJson = Json { + private var keySerializationJson = Json { serializersModule = keySerializationModule } + @OptIn(InternalSerializationApi::class) + fun registerExternalKeyType(keyClass: KClass) { + keySerializationModule = SerializersModule { + polymorphic(Key::class) { + subclass(JWKKey::class) + subclass(TSEKey::class) + subclass(OCIKeyRestApi::class) + subclass(AWSKeyRestAPI::class) + subclass(AzureKey::class) + subclass(keyClass, keyClass.serializer()) + } + } + + keySerializationJson = Json { + serializersModule = keySerializationModule + } + } + fun serializeKey(key: Key): String = keySerializationJson.encodeToString(key) @Suppress("NON_EXPORTABLE_TYPE") diff --git a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/aws/AWSKeyCreator.kt b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/aws/AWSKeyCreator.kt index 16be30f85..a0c0148e5 100644 --- a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/aws/AWSKeyCreator.kt +++ b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/aws/AWSKeyCreator.kt @@ -4,6 +4,6 @@ import id.walt.crypto.keys.KeyType interface AWSKeyCreator { - suspend fun generate(type: KeyType, metadata: AWSKeyMetadata): AWSKey + suspend fun generate(type: KeyType, metadata: AWSKeyMetadata): AWSKeyRestAPI } \ No newline at end of file diff --git a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/aws/AWSKey.kt b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/aws/AWSKeyRestAPI.kt similarity index 99% rename from waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/aws/AWSKey.kt rename to waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/aws/AWSKeyRestAPI.kt index fac1482c9..96d16be36 100644 --- a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/aws/AWSKey.kt +++ b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/aws/AWSKeyRestAPI.kt @@ -62,8 +62,8 @@ var timeoutAt: Instant? = null @JsExport @Suppress("TRANSIENT_IS_REDUNDANT") @Serializable -@SerialName("aws") -class AWSKey( +@SerialName("aws-rest-api") +class AWSKeyRestAPI( val config: AWSKeyMetadata, val id: String, private var _publicKey: String? = null, @@ -608,7 +608,7 @@ $public @JsExport.Ignore - override suspend fun generate(type: KeyType, config: AWSKeyMetadata): AWSKey { + override suspend fun generate(type: KeyType, config: AWSKeyMetadata): AWSKeyRestAPI { if (config.auth.accessKeyId.isNullOrBlank() && config.auth.secretAccessKey.isNullOrBlank()) { getAccess(config) @@ -648,7 +648,7 @@ $public val publicKey = getPublicKey(config, keyId.toString()) - return AWSKey( + return AWSKeyRestAPI( config = config, id = keyId.toString(), _publicKey = publicKey.exportJWK(), diff --git a/waltid-libraries/crypto/waltid-crypto/src/commonTest/kotlin/AWSKeyTest.kt b/waltid-libraries/crypto/waltid-crypto/src/commonTest/kotlin/AWSKeyTest.kt index 4b0607bc5..b3d9e0aec 100644 --- a/waltid-libraries/crypto/waltid-crypto/src/commonTest/kotlin/AWSKeyTest.kt +++ b/waltid-libraries/crypto/waltid-crypto/src/commonTest/kotlin/AWSKeyTest.kt @@ -1,7 +1,7 @@ import AWSKeyTest.Config.payloadJWS import id.walt.crypto.keys.KeyType import id.walt.crypto.keys.aws.AWSAuth -import id.walt.crypto.keys.aws.AWSKey +import id.walt.crypto.keys.aws.AWSKeyRestAPI import id.walt.crypto.keys.aws.AWSKeyMetadata import io.ktor.utils.io.core.* import kotlinx.coroutines.test.runTest @@ -43,7 +43,7 @@ class AWSKeyTest { }.fold(onSuccess = { it }, onFailure = { false }) - lateinit var keys: List + lateinit var keys: List @Test @@ -77,7 +77,7 @@ class AWSKeyTest { ) keys = Config.TESTABLE_KEY_TYPES.map { - AWSKey.generate(it, awsMetadata).also { key -> + AWSKeyRestAPI.generate(it, awsMetadata).also { key -> println("Generated key: ${key.keyType} - ${key.getKeyId()}") assertNotNull(key, "Key generation failed for $it") assertNotNull(key.getKeyId(), "Key ID should not be null") diff --git a/waltid-services/waltid-issuer-api/Dockerfile b/waltid-services/waltid-issuer-api/Dockerfile index 2fabca878..a78a300f7 100644 --- a/waltid-services/waltid-issuer-api/Dockerfile +++ b/waltid-services/waltid-issuer-api/Dockerfile @@ -7,6 +7,7 @@ COPY waltid-libraries/credentials/waltid-verifiable-credentials/build.gradle.kts COPY waltid-libraries/credentials/waltid-verification-policies/build.gradle.kts /work/waltid-libraries/credentials/waltid-verification-policies/ COPY waltid-libraries/credentials/waltid-dif-definitions-parser/build.gradle.kts /work/waltid-libraries/credentials/waltid-dif-definitions-parser/ COPY waltid-libraries/crypto/waltid-crypto/build.gradle.kts /work/waltid-libraries/crypto/waltid-crypto/ +COPY waltid-libraries/crypto/waltid-crypto-aws/build.gradle.kts /work/waltid-libraries/crypto/waltid-crypto-aws/ COPY waltid-libraries/waltid-did/build.gradle.kts /work/waltid-libraries/waltid-did/ COPY waltid-libraries/protocols/waltid-openid4vc/build.gradle.kts /work/waltid-libraries/protocols/waltid-openid4vc/ COPY waltid-libraries/sdjwt/waltid-sdjwt/build.gradle.kts /work/waltid-libraries/sdjwt/waltid-sdjwt/ @@ -23,6 +24,7 @@ COPY waltid-libraries/credentials/waltid-verifiable-credentials/. /work/waltid-l COPY waltid-libraries/credentials/waltid-verification-policies/. /work/waltid-libraries/credentials/waltid-verification-policies COPY waltid-libraries/credentials/waltid-dif-definitions-parser/. /work/waltid-libraries/credentials/waltid-dif-definitions-parser COPY waltid-libraries/crypto/waltid-crypto/. /work/waltid-libraries/crypto/waltid-crypto +COPY waltid-libraries/crypto/waltid-crypto-aws/. /work/waltid-libraries/crypto/waltid-crypto-aws COPY waltid-libraries/waltid-did/. /work/waltid-libraries/waltid-did COPY waltid-libraries/protocols/waltid-openid4vc/. /work/waltid-libraries/protocols/waltid-openid4vc COPY waltid-libraries/sdjwt/waltid-sdjwt/. /work/waltid-libraries/sdjwt/waltid-sdjwt diff --git a/waltid-services/waltid-issuer-api/build.gradle.kts b/waltid-services/waltid-issuer-api/build.gradle.kts index bc00d7bff..dc9ae9420 100644 --- a/waltid-services/waltid-issuer-api/build.gradle.kts +++ b/waltid-services/waltid-issuer-api/build.gradle.kts @@ -92,6 +92,8 @@ dependencies { // walt.id api(project(":waltid-libraries:crypto:waltid-crypto")) + implementation(project(":waltid-libraries:crypto:waltid-crypto-aws")) + api(project(":waltid-libraries:waltid-did")) api(project(":waltid-libraries:credentials:waltid-verifiable-credentials")) diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/Main.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/Main.kt index 91c4d68ba..5abbcedde 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/Main.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/Main.kt @@ -6,6 +6,7 @@ import id.walt.commons.ServiceMain import id.walt.commons.featureflag.CommonsFeatureCatalog import id.walt.commons.featureflag.FeatureManager.whenFeature import id.walt.commons.web.WebService +import id.walt.crypto.keys.aws.WaltCryptoAws import id.walt.did.helpers.WaltidServices import id.walt.issuer.entra.entraIssuance import id.walt.issuer.issuance.OidcApi.oidcApi @@ -26,6 +27,7 @@ suspend fun main(args: Array) { ), init = { WaltidServices.minimalInit() + WaltCryptoAws.init() }, run = WebService(Application::issuerModule).run() ) diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuanceExamples.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuanceExamples.kt index c1e90b641..4037b9e43 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuanceExamples.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuanceExamples.kt @@ -12,9 +12,10 @@ import kotlinx.serialization.json.buildJsonArray object IssuanceExamples { - private inline fun typedValueExampleDescriptorDsl(content: String): ValueExampleDescriptorDsl.() -> Unit = { - value = Json.decodeFromString(content) - } + private inline fun typedValueExampleDescriptorDsl(content: String): ValueExampleDescriptorDsl.() -> Unit = + { + value = Json.decodeFromString(content) + } // language=json val openBadgeCredentialData = """ @@ -869,7 +870,7 @@ object IssuanceExamples { { "key": { - "backend": "aws", + "backend": "aws-rest-api", "keyType": "secp256r1", "config": { @@ -889,8 +890,9 @@ object IssuanceExamples { """.trimIndent() ) + //language=JSON - val issuerOnboardingRequestAwsRestApiExampleWithRole = typedValueExampleDescriptorDsl( + val issuerOnboardingRequestAwsSdkExample = typedValueExampleDescriptorDsl( """ { "key": @@ -898,6 +900,28 @@ object IssuanceExamples { "backend": "aws", "keyType": "secp256r1", "config": + { + "region": "eu-central-1" + + } + }, + "did": + { + "method": "jwk" + } + } + """.trimIndent() + ) + + //language=JSON + val issuerOnboardingRequestAwsRestApiExampleWithRole = typedValueExampleDescriptorDsl( + """ + { + "key": + { + "backend": "aws-rest-api", + "keyType": "secp256r1", + "config": { "auth": { "roleName": "access", @@ -941,6 +965,7 @@ object IssuanceExamples { """.trimIndent() ) + //language=JSON val issuerOnboardingResponseOciRestApiExample = typedValueExampleDescriptorDsl( """ diff --git a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuerApi.kt b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuerApi.kt index 22eabd892..2f5b318f9 100644 --- a/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuerApi.kt +++ b/waltid-services/waltid-issuer-api/src/main/kotlin/id/walt/issuer/issuance/IssuerApi.kt @@ -128,6 +128,10 @@ fun Application.issuerApi() { "did:jwk + Azure REST API key (Azure - Secp256r1)", IssuanceExamples.issuerOnboardingRequestAzureRestApiExample ) + example( + "did:jwk + AWS SDK key (AWS - Secp256r1)", + IssuanceExamples.issuerOnboardingRequestAwsSdkExample + ) required = true } } diff --git a/waltid-services/waltid-wallet-api/Dockerfile b/waltid-services/waltid-wallet-api/Dockerfile index adac3501f..6b7082dd4 100644 --- a/waltid-services/waltid-wallet-api/Dockerfile +++ b/waltid-services/waltid-wallet-api/Dockerfile @@ -7,6 +7,7 @@ COPY waltid-libraries/protocols/waltid-openid4vc/build.gradle.kts /work/waltid-l COPY waltid-libraries/sdjwt/waltid-sdjwt/build.gradle.kts /work/waltid-libraries/sdjwt/waltid-sdjwt/ COPY waltid-libraries/crypto/waltid-crypto/build.gradle.kts /work/waltid-libraries/crypto/waltid-crypto/ COPY waltid-libraries/crypto/waltid-crypto-oci/build.gradle.kts /work/waltid-libraries/crypto/waltid-crypto-oci/ +COPY waltid-libraries/crypto/waltid-crypto-aws/build.gradle.kts /work/waltid-libraries/crypto/waltid-crypto-aws/ COPY waltid-libraries/waltid-did/build.gradle.kts /work/waltid-libraries/waltid-did/ COPY waltid-libraries/credentials/waltid-mdoc-credentials/build.gradle.kts /work/waltid-libraries/credentials/waltid-mdoc-credentials/ COPY waltid-libraries/credentials/waltid-verifiable-credentials/build.gradle.kts /work/waltid-libraries/credentials/waltid-verifiable-credentials/ @@ -27,6 +28,7 @@ COPY waltid-libraries/protocols/waltid-openid4vc/. /work/waltid-libraries/protoc COPY waltid-libraries/sdjwt/waltid-sdjwt/. /work/waltid-libraries/sdjwt/waltid-sdjwt COPY waltid-libraries/crypto/waltid-crypto/. /work/waltid-libraries/crypto/waltid-crypto COPY waltid-libraries/crypto/waltid-crypto-oci/. /work/waltid-libraries/crypto/waltid-crypto-oci +COPY waltid-libraries/crypto/waltid-crypto-aws/. /work/waltid-libraries/crypto/waltid-crypto-aws COPY waltid-libraries/waltid-did/. /work/waltid-libraries/waltid-did COPY waltid-libraries/credentials/waltid-mdoc-credentials/. /work/waltid-libraries/credentials/waltid-mdoc-credentials COPY waltid-libraries/credentials/waltid-verifiable-credentials/. /work/waltid-libraries/credentials/waltid-verifiable-credentials diff --git a/waltid-services/waltid-wallet-api/build.gradle.kts b/waltid-services/waltid-wallet-api/build.gradle.kts index 2deabd594..cab5e2f66 100644 --- a/waltid-services/waltid-wallet-api/build.gradle.kts +++ b/waltid-services/waltid-wallet-api/build.gradle.kts @@ -121,6 +121,9 @@ dependencies { implementation(project(":waltid-libraries:crypto:waltid-crypto")) implementation(project(":waltid-libraries:crypto:waltid-crypto-oci")) + implementation(project(":waltid-libraries:crypto:waltid-crypto-aws")) + + implementation(project(":waltid-libraries:waltid-did")) implementation(project(":waltid-libraries:credentials:waltid-verification-policies")) implementation(project(":waltid-libraries:credentials:waltid-dif-definitions-parser")) diff --git a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/Main.kt b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/Main.kt index 49ed21b96..00ab680e3 100644 --- a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/Main.kt +++ b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/Main.kt @@ -6,6 +6,7 @@ import id.walt.commons.ServiceMain import id.walt.commons.featureflag.CommonsFeatureCatalog import id.walt.commons.featureflag.FeatureManager.whenFeature import id.walt.commons.web.WebService +import id.walt.crypto.keys.aws.WaltCryptoAws import id.walt.crypto.keys.oci.WaltCryptoOci import id.walt.did.helpers.WaltidServices import id.walt.webwallet.db.Db @@ -39,6 +40,7 @@ suspend fun main(args: Array) { webWalletSetup() WaltidServices.minimalInit() WaltCryptoOci.init() + WaltCryptoAws.init() Db.start() }, run = WebService(Application::webWalletModule).run() diff --git a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/web/controllers/KeyController.kt b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/web/controllers/KeyController.kt index cfc386bb0..50abff8f9 100644 --- a/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/web/controllers/KeyController.kt +++ b/waltid-services/waltid-wallet-api/src/main/kotlin/id/walt/webwallet/web/controllers/KeyController.kt @@ -104,7 +104,7 @@ fun Application.keys() = walletRoute { } example("AWS key generation request") { value = KeyGenerationRequest( - backend = "aws", + backend = "aws-rest-api", keyType = KeyType.secp256r1, config = buildJsonObject { putJsonObject("auth") { @@ -129,6 +129,15 @@ fun Application.keys() = walletRoute { } ) } + example("AWS key generation request SDK") { + value = KeyGenerationRequest( + backend = "aws", + keyType = KeyType.secp256r1, + config = buildJsonObject { + put("region", JsonPrimitive("eu-central-1")) + } + ) + } } } response {