From 20fe3dbb8648f0656075d87cb90e898016074ab2 Mon Sep 17 00:00:00 2001 From: SuperBatata Date: Tue, 26 Nov 2024 15:33:13 +0100 Subject: [PATCH 01/31] feat : Added Azure kms integration --- .../id/walt/crypto/keys/azure/AZUREAuth.kt | 29 ++ .../id/walt/crypto/keys/azure/AZUREKEY.kt | 324 ++++++++++++++++++ .../walt/crypto/keys/azure/AZUREKeyCreator.kt | 10 + .../crypto/keys/azure/AZUREKeyMetadata.kt | 17 + 4 files changed, 380 insertions(+) create mode 100644 waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREAuth.kt create mode 100644 waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKEY.kt create mode 100644 waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKeyCreator.kt create mode 100644 waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKeyMetadata.kt diff --git a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREAuth.kt b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREAuth.kt new file mode 100644 index 000000000..2e7a16115 --- /dev/null +++ b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREAuth.kt @@ -0,0 +1,29 @@ +package id.walt.crypto.keys.azure + +import kotlinx.serialization.Serializable +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport + + +@OptIn(ExperimentalJsExport::class) +@JsExport +@Serializable +data class AZUREAuth( + val clientId: String? = null, + val clientSecret: String? = null, + val tenantId: String? = null, + val keyVaultUrl: String? = null, +) { + init { + requireAuthenticationMethod() + } + + private fun requireAuthenticationMethod() { + val servicePrincipal = clientId != null && clientSecret != null && tenantId != null + + + if (!servicePrincipal) { + throw IllegalArgumentException("AZUREAuth requires clientId, clientSecret, and tenantId") + } + } +} \ No newline at end of file diff --git a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKEY.kt b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKEY.kt new file mode 100644 index 000000000..245fb15df --- /dev/null +++ b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKEY.kt @@ -0,0 +1,324 @@ +package id.walt.crypto.keys.azure + +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.encodeToBase64Url +import id.walt.crypto.utils.JsonUtils.toJsonElement +import id.walt.crypto.utils.jwsSigningAlgorithm +import io.github.oshai.kotlinlogging.KotlinLogging +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import io.ktor.serialization.kotlinx.json.* +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.* +import love.forte.plugin.suspendtrans.annotation.JsPromise +import love.forte.plugin.suspendtrans.annotation.JvmAsync +import love.forte.plugin.suspendtrans.annotation.JvmBlocking +import org.kotlincrypto.hash.sha2.SHA256 +import kotlin.io.encoding.ExperimentalEncodingApi +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport + +private val logger = KotlinLogging.logger { } +var _accessAzureToken: String? = null + +@OptIn(ExperimentalJsExport::class) +@JsExport +@Suppress("TRANSIENT_IS_REDUNDANT") +@Serializable +@SerialName("azure") +class AZUREKEY( + private var _publicKey: String? = null, + private var _keyType: KeyType? = null, + val config: AZUREKeyMetadata, + val id: String +) : Key() { + + + override var keyType: KeyType + get() = _keyType!! + set(value) { + _keyType = value + } + + override val hasPrivateKey: Boolean + get() = false + + override fun toString(): String = "[AZURE ${keyType.name} key @AZURE-Vault ${config.auth.keyVaultUrl} - $id]" + + + override suspend fun getKeyId(): String { + TODO("Not yet implemented") + } + + override suspend fun getThumbprint(): String { + TODO("Not yet implemented") + } + + override suspend fun exportJWK(): String { + TODO("Not yet implemented") + } + + override suspend fun exportJWKObject(): JsonObject = Json.parseToJsonElement(_publicKey!!).jsonObject + + override suspend fun exportPEM(): String { + TODO("Not yet implemented") + } + + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore + @OptIn(ExperimentalStdlibApi::class) + override suspend fun signRaw(plaintext: ByteArray): ByteArray { + + val sha256Digest: ByteArray = SHA256().digest(plaintext) + val base64UrlEncoded: String = sha256Digest.encodeToBase64Url() + + println("digest for signing: $base64UrlEncoded") + val accessToken = getAzureAccessToken( + config.auth.tenantId.toString(), + config.auth.clientId.toString(), config.auth.clientSecret.toString() + ) + val signingAlgorithm = jwsSigningAlgorithm(keyType) + + val body = buildJsonObject { + put("alg", JsonPrimitive(signingAlgorithm)) // Use JsonPrimitive to serialize the string + put("value", JsonPrimitive(base64UrlEncoded)) // Also serialize the base64UrlEncoded string + } + val signature = client.post("$id/sign?api-version=7.4") { + contentType(ContentType.Application.Json) + bearerAuth(accessToken) + setBody( + body + ) + } + println("signature: ${signature.bodyAsText()}") + return signature.azureJsonDataBody()["value"]!!.jsonPrimitive.content.encodeToByteArray() + } + + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore + @OptIn(ExperimentalEncodingApi::class) + 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()) + + println("the key type used here is: $keyType") + if (keyType in listOf(KeyType.secp256r1, KeyType.secp256k1)) { + rawSignature = EccUtils.convertDERtoIEEEP1363(rawSignature) + } + + val encodedSignature = rawSignature.encodeToBase64Url() + val jws = "$header.$payload.$encodedSignature" + + return jws + } + + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore + @OptIn(ExperimentalEncodingApi::class) + override suspend fun verifyRaw( + signed: ByteArray, + detachedPlaintext: ByteArray? + ): Result { + + val publicKey = getPublicKey() + println("public key to verify with: $publicKey") + val verification = publicKey.verifyRaw(signed, detachedPlaintext) + return Result.success( + verification.getOrThrow() + ) + } + + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore + @OptIn(ExperimentalEncodingApi::class) + override suspend fun verifyJws(signedJws: String): Result { + val publicKey = getPublicKey() + val verification = publicKey.verifyJws(signedJws) + return verification + } + + @Transient + private var backedKey: Key? = null + + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore + override suspend fun getPublicKey(): Key = backedKey ?: when { + _publicKey != null -> _publicKey!!.let { + JWKKey.importJWK(it).getOrThrow() + } + + else -> getPublicKey() + }.also { newBackedKey -> backedKey = newBackedKey } + + override suspend fun getPublicKeyRepresentation(): ByteArray { + TODO("Not yet implemented") + } + + override suspend fun getMeta(): KeyMeta { + TODO("Not yet implemented") + } + + override suspend fun deleteKey(): Boolean { + TODO("Not yet implemented") + } + + @Serializable + data class KeyCreateRequest( + val kty: String, + val crv: String? = null, + val key_ops: List, + ) + + + companion object : AZUREKeyCreator { + private suspend fun HttpResponse.azureJsonDataBody(): JsonObject { + val baseMsg = { "AZURE server (URL: ${this.request.url}) returned an invalid response: " } + + return runCatching { + // First, get the body as a string + val bodyStr = this.bodyAsText() + + // Parse the string as JsonObject + Json.parseToJsonElement(bodyStr).jsonObject + }.getOrElse { + val bodyStr = this.bodyAsText() // Get the body in case of an exception + throw IllegalArgumentException( + baseMsg.invoke() + if (bodyStr.isEmpty()) "empty response (instead of JSON data)" + else "invalid response: $bodyStr" + ) + } + } + + val client = HttpClient { + install(ContentNegotiation) { + json(Json { prettyPrint = true }) + } + } + + @Serializable + data class AzureTokenResponse(val access_token: String) + + suspend fun getAzureAccessToken(tenantId: String, clientId: String, clientSecret: String): String { + + val response: HttpResponse = client.post("https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token") { + contentType(ContentType.Application.FormUrlEncoded) + setBody( + listOf( + "grant_type" to "client_credentials", + "client_id" to clientId, + "client_secret" to clientSecret, + "scope" to "https://vault.azure.net/.default" + ).formUrlEncode() + ) + } + val responseBody: String = response.body() + val json = Json { ignoreUnknownKeys = true } + val tokenResponse = json.decodeFromString(responseBody) + + _accessAzureToken = tokenResponse.access_token + return tokenResponse.access_token + } + + private fun keyTypeToAzureKeyMapping(type: KeyType): Pair = when (type) { + KeyType.secp256r1 -> "EC" to "P-256" // EC key with P-256 curve + KeyType.secp256k1 -> "EC" to "P-256K" // EC key with P-256K curve + KeyType.RSA -> "RSA" to null // RSA key, no curve + else -> throw KeyTypeNotSupportedException(type.name) + } + + + private fun azureKeyToKeyTypeMapping(crv: String, kty: String): KeyType = when (kty) { + "EC" -> when (crv) { + "P-256" -> KeyType.secp256r1 // Mapping P-256 curve to secp256r1 + "P-256K" -> KeyType.secp256k1 // Mapping P-256K curve to secp256k1 + else -> throw KeyTypeNotSupportedException(crv) + } + + "RSA" -> KeyType.RSA // Mapping RSA key type + else -> throw KeyTypeNotSupportedException(kty) + } + + + override suspend fun generate(type: KeyType, keyName: String, metadata: AZUREKeyMetadata): Key { + + + val accessToken = getAzureAccessToken( + metadata.auth.tenantId.toString(), + metadata.auth.clientId.toString(), metadata.auth.clientSecret.toString() + ) + val (kty, crv) = keyTypeToAzureKeyMapping(type) + val keyRequestBody = if (kty == "RSA") { + KeyCreateRequest( + kty = kty, + key_ops = listOf("sign", "verify") + ) + } else { + KeyCreateRequest( + kty = kty, + crv = crv!!, + key_ops = listOf("sign", "verify") + ) + } + val key = + client.post("${metadata.auth.keyVaultUrl}/keys/$keyName/create?api-version=7.4") { + contentType(ContentType.Application.Json) + bearerAuth(accessToken) + setBody(keyRequestBody) + } + + println("generation req: ${key.bodyAsText()}") + + + val keyId = key.azureJsonDataBody()["key"]?.jsonObject?.get("kid")?.jsonPrimitive?.content + ?: throw IllegalArgumentException("AZURE server returned an invalid response: key ID not found") + + val keyType = key.azureJsonDataBody()["key"]?.jsonObject?.get("kty")?.jsonPrimitive?.content!! + val crvFromResponse = key.azureJsonDataBody()["key"]?.jsonObject?.get("crv")?.jsonPrimitive?.content + + println( + "Generated key with ID: $keyId, type: $keyType, curve: $crvFromResponse, metadata: $metadata" + ) + + return AZUREKEY( + _keyType = azureKeyToKeyTypeMapping(crvFromResponse ?: "", keyType), + config = metadata, + id = keyId, + _publicKey = key.azureJsonDataBody()["key"].toString() + ) + } + } + + +} + diff --git a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKeyCreator.kt b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKeyCreator.kt new file mode 100644 index 000000000..0240fd6e8 --- /dev/null +++ b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKeyCreator.kt @@ -0,0 +1,10 @@ +package id.walt.crypto.keys.azure + +import id.walt.crypto.keys.Key +import id.walt.crypto.keys.KeyType + + +interface AZUREKeyCreator { + + suspend fun generate(type: KeyType, keyName: String, metadata: AZUREKeyMetadata): Key +} \ No newline at end of file diff --git a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKeyMetadata.kt b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKeyMetadata.kt new file mode 100644 index 000000000..eb8475e49 --- /dev/null +++ b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKeyMetadata.kt @@ -0,0 +1,17 @@ +package id.walt.crypto.keys.azure + +import kotlinx.serialization.Serializable + + +@Serializable +data class AZUREKeyMetadata( + val auth: AZUREAuth, +) { + constructor( + clientId: String, + clientSecret: String, + keyVaultUrl: String, + tenantId: String, + ) : this(AZUREAuth(clientId, clientSecret, tenantId, keyVaultUrl)) +} + From 051260cc285967b3982afccc8ab6384bcade778c Mon Sep 17 00:00:00 2001 From: SuperBatata Date: Tue, 26 Nov 2024 16:14:02 +0100 Subject: [PATCH 02/31] fix: signRaw function --- .../kotlin/id/walt/crypto/keys/azure/AZUREKEY.kt | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKEY.kt b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKEY.kt index 245fb15df..fbba19b65 100644 --- a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKEY.kt +++ b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKEY.kt @@ -6,6 +6,7 @@ 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.decodeFromBase64Url import id.walt.crypto.utils.Base64Utils.encodeToBase64Url import id.walt.crypto.utils.JsonUtils.toJsonElement import id.walt.crypto.utils.jwsSigningAlgorithm @@ -94,8 +95,8 @@ class AZUREKEY( val signingAlgorithm = jwsSigningAlgorithm(keyType) val body = buildJsonObject { - put("alg", JsonPrimitive(signingAlgorithm)) // Use JsonPrimitive to serialize the string - put("value", JsonPrimitive(base64UrlEncoded)) // Also serialize the base64UrlEncoded string + put("alg", JsonPrimitive(signingAlgorithm)) + put("value", JsonPrimitive(base64UrlEncoded)) } val signature = client.post("$id/sign?api-version=7.4") { contentType(ContentType.Application.Json) @@ -105,7 +106,7 @@ class AZUREKEY( ) } println("signature: ${signature.bodyAsText()}") - return signature.azureJsonDataBody()["value"]!!.jsonPrimitive.content.encodeToByteArray() + return signature.azureJsonDataBody()["value"]!!.jsonPrimitive.content.decodeFromBase64Url() } @JvmBlocking @@ -149,6 +150,8 @@ class AZUREKEY( val publicKey = getPublicKey() println("public key to verify with: $publicKey") + println("signed data: $signed") + println("detached plaintext: $detachedPlaintext") val verification = publicKey.verifyRaw(signed, detachedPlaintext) return Result.success( verification.getOrThrow() @@ -197,6 +200,7 @@ class AZUREKEY( data class KeyCreateRequest( val kty: String, val crv: String? = null, + val key_size: Int? = null, val key_ops: List, ) @@ -281,7 +285,8 @@ class AZUREKEY( val keyRequestBody = if (kty == "RSA") { KeyCreateRequest( kty = kty, - key_ops = listOf("sign", "verify") + key_ops = listOf("sign", "verify"), + key_size = 2048 ) } else { KeyCreateRequest( From 764d55addde712afe331a797c77baa23d0ed4210 Mon Sep 17 00:00:00 2001 From: SuperBatata Date: Tue, 26 Nov 2024 16:41:24 +0100 Subject: [PATCH 03/31] fix: build errors --- .../id/walt/crypto/keys/azure/AZUREKEY.kt | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKEY.kt b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKEY.kt index fbba19b65..1d3304f72 100644 --- a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKEY.kt +++ b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKEY.kt @@ -58,21 +58,40 @@ class AZUREKEY( override fun toString(): String = "[AZURE ${keyType.name} key @AZURE-Vault ${config.auth.keyVaultUrl} - $id]" - + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun getKeyId(): String { TODO("Not yet implemented") } + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun getThumbprint(): String { TODO("Not yet implemented") } + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun exportJWK(): String { TODO("Not yet implemented") } + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun exportJWKObject(): JsonObject = Json.parseToJsonElement(_publicKey!!).jsonObject + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun exportPEM(): String { TODO("Not yet implemented") } @@ -184,14 +203,26 @@ class AZUREKEY( else -> getPublicKey() }.also { newBackedKey -> backedKey = newBackedKey } + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun getPublicKeyRepresentation(): ByteArray { TODO("Not yet implemented") } + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun getMeta(): KeyMeta { TODO("Not yet implemented") } + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun deleteKey(): Boolean { TODO("Not yet implemented") } @@ -233,6 +264,10 @@ class AZUREKEY( @Serializable data class AzureTokenResponse(val access_token: String) + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore suspend fun getAzureAccessToken(tenantId: String, clientId: String, clientSecret: String): String { val response: HttpResponse = client.post("https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token") { @@ -274,6 +309,7 @@ class AZUREKEY( } + @JsExport.Ignore override suspend fun generate(type: KeyType, keyName: String, metadata: AZUREKeyMetadata): Key { From dbe296d143b3c3a645daf9a59c565a611ab241fe Mon Sep 17 00:00:00 2001 From: SuperBatata Date: Tue, 26 Nov 2024 17:20:37 +0100 Subject: [PATCH 04/31] fix: generate key function --- .../commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKEY.kt | 2 +- .../kotlin/id/walt/crypto/keys/azure/AZUREKeyCreator.kt | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKEY.kt b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKEY.kt index 1d3304f72..0950303bd 100644 --- a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKEY.kt +++ b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKEY.kt @@ -310,7 +310,7 @@ class AZUREKEY( @JsExport.Ignore - override suspend fun generate(type: KeyType, keyName: String, metadata: AZUREKeyMetadata): Key { + override suspend fun generate(type: KeyType, keyName: String?, metadata: AZUREKeyMetadata): AZUREKEY { val accessToken = getAzureAccessToken( diff --git a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKeyCreator.kt b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKeyCreator.kt index 0240fd6e8..e7a6c9490 100644 --- a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKeyCreator.kt +++ b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKeyCreator.kt @@ -1,10 +1,9 @@ package id.walt.crypto.keys.azure -import id.walt.crypto.keys.Key import id.walt.crypto.keys.KeyType interface AZUREKeyCreator { - suspend fun generate(type: KeyType, keyName: String, metadata: AZUREKeyMetadata): Key + suspend fun generate(type: KeyType, keyName: String? = "waltid", metadata: AZUREKeyMetadata): AZUREKEY } \ No newline at end of file From 6dcbd414aa0f09405ed91b75cd032a56f5f3db64 Mon Sep 17 00:00:00 2001 From: SuperBatata Date: Tue, 26 Nov 2024 17:21:16 +0100 Subject: [PATCH 05/31] feat: added azure key example to issuer-api --- .../walt/issuer/issuance/IssuanceExamples.kt | 33 ++++++++++++++++++- .../id/walt/issuer/issuance/IssuerApi.kt | 9 ++++- 2 files changed, 40 insertions(+), 2 deletions(-) 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 352afc28f..f3028bfd8 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 @@ -5,7 +5,10 @@ import id.walt.credentials.utils.VCFormat import id.walt.crypto.keys.KeyType import id.walt.issuer.lspPotential.LspPotentialIssuanceInterop import io.github.smiley4.ktorswaggerui.dsl.routes.ValueExampleDescriptorDsl -import kotlinx.serialization.json.* +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.add +import kotlinx.serialization.json.buildJsonArray object IssuanceExamples { @@ -911,6 +914,33 @@ object IssuanceExamples { """.trimIndent() ) + //language=JSON + val issuerOnboardingRequestAzureRestApiExample = typedValueExampleDescriptorDsl( + """ + { + "key": + { + "backend": "azure", + "keyType": "RSA", + "config": + { + "auth": { + "clientId": "client id", + "clientSecret": "cleint secret", + "tenantId": "tenant id", + "keyVaultUrl": "url to the vault" + } + + } + }, + "did": + { + "method": "jwk" + } + } + """.trimIndent() + ) + //language=JSON val issuerOnboardingResponseOciRestApiExample = typedValueExampleDescriptorDsl( """ @@ -1003,6 +1033,7 @@ object IssuanceExamples { """.trimIndent() ) + // language=JSON val issuerOnboardingResponseTseExample = 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 b89c57cf4..738cff74e 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 @@ -22,7 +22,10 @@ import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* import io.ktor.util.pipeline.* -import kotlinx.serialization.json.* +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import redis.clients.jedis.exceptions.JedisAccessControlException import redis.clients.jedis.exceptions.JedisConnectionException import kotlin.reflect.KClass @@ -121,6 +124,10 @@ fun Application.issuerApi() { "did:jwk + AWS REST API key (AWS - Secp256r1) + Role (Auth)", IssuanceExamples.issuerOnboardingRequestAwsRestApiExampleWithRole ) + example( + "did:jwk + AZURE REST API key (Azure - RSA)", + IssuanceExamples.issuerOnboardingRequestAzureRestApiExample + ) required = true } } From 169739a3d51dc81b5d6cfb575e492ff63768ce51 Mon Sep 17 00:00:00 2001 From: SuperBatata Date: Tue, 26 Nov 2024 17:21:50 +0100 Subject: [PATCH 06/31] feat: added arrayList to exportjwkobject --- .../src/jvmMain/kotlin/id/walt/crypto/keys/jwk/JWKKey.jvm.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/waltid-libraries/crypto/waltid-crypto/src/jvmMain/kotlin/id/walt/crypto/keys/jwk/JWKKey.jvm.kt b/waltid-libraries/crypto/waltid-crypto/src/jvmMain/kotlin/id/walt/crypto/keys/jwk/JWKKey.jvm.kt index 90723c104..84317941a 100644 --- a/waltid-libraries/crypto/waltid-crypto/src/jvmMain/kotlin/id/walt/crypto/keys/jwk/JWKKey.jvm.kt +++ b/waltid-libraries/crypto/waltid-crypto/src/jvmMain/kotlin/id/walt/crypto/keys/jwk/JWKKey.jvm.kt @@ -88,6 +88,7 @@ actual class JWKKey actual constructor( when (value) { is String -> JsonPrimitive(value) is Number -> JsonPrimitive(value) + is ArrayList<*> -> JsonPrimitive(value.toString()) else -> throw IllegalArgumentException("Unsupported value type: ${value::class.simpleName} in field ${it.key}") } From d0f16621355e656704fb8ed13793e21758025e0a Mon Sep 17 00:00:00 2001 From: SuperBatata Date: Tue, 26 Nov 2024 17:22:13 +0100 Subject: [PATCH 07/31] feat: added support to azure key --- .../commonMain/kotlin/id/walt/crypto/keys/KeyManager.kt | 9 +++++++++ .../kotlin/id/walt/crypto/keys/KeySerialization.kt | 2 ++ 2 files changed, 11 insertions(+) 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 a9e8ed6bd..bb36cb071 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 @@ -4,6 +4,7 @@ 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.azure.AZUREKEY import id.walt.crypto.keys.jwk.JWKKey import id.walt.crypto.keys.oci.OCIKeyRestApi import id.walt.crypto.keys.tse.TSEKey @@ -43,6 +44,14 @@ object KeyManager { Json.decodeFromJsonElement(generateRequest.config!!) ) } + + register("azure") { generateRequest: KeyGenerationRequest -> + AZUREKEY.generate( + generateRequest.keyType, + keyName = null, + 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 73eb920bc..c89b98fec 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,6 +1,7 @@ package id.walt.crypto.keys import id.walt.crypto.keys.aws.AWSKey +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 @@ -28,6 +29,7 @@ object KeySerialization { subclass(TSEKey::class) subclass(OCIKeyRestApi::class) subclass(AWSKey::class) + subclass(AZUREKEY::class) } } From ca519fbdfe68fd03356208c7eb6e388c7c7509d9 Mon Sep 17 00:00:00 2001 From: waltkb <68587968+waltkb@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:40:47 +0100 Subject: [PATCH 08/31] Fixed Azure key --- .../kotlin/id/walt/crypto/keys/EccUtils.kt | 49 +++++++++++++ .../kotlin/id/walt/crypto/keys/KeyManager.kt | 6 +- .../id/walt/crypto/keys/KeySerialization.kt | 4 +- .../kotlin/id/walt/crypto/keys/KeyUtils.kt | 39 ++++++++++ .../keys/azure/{AZUREAuth.kt => AzureAuth.kt} | 6 +- .../keys/azure/{AZUREKEY.kt => AzureKey.kt} | 73 ++++++++++--------- ...{AZUREKeyCreator.kt => AzureKeyCreator.kt} | 6 +- ...ZUREKeyMetadata.kt => AzureKeyMetadata.kt} | 6 +- .../id/walt/crypto/keys/jwk/JWKKey.jvm.kt | 1 - .../id/walt/issuer/issuance/IssuerApi.kt | 2 +- 10 files changed, 140 insertions(+), 52 deletions(-) create mode 100644 waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/KeyUtils.kt rename waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/{AZUREAuth.kt => AzureAuth.kt} (87%) rename waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/{AZUREKEY.kt => AzureKey.kt} (85%) rename waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/{AZUREKeyCreator.kt => AzureKeyCreator.kt} (65%) rename waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/{AZUREKeyMetadata.kt => AzureKeyMetadata.kt} (66%) diff --git a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/EccUtils.kt b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/EccUtils.kt index 8373f5a24..0bde8ca5e 100644 --- a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/EccUtils.kt +++ b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/EccUtils.kt @@ -1,5 +1,7 @@ package id.walt.crypto.keys +import kotlin.experimental.and + object EccUtils { @@ -88,4 +90,51 @@ object EccUtils { return fixedLengthR + fixedLengthS } + + fun convertP1363toDER(p1363Signature: ByteArray): ByteArray { + val keySize = p1363Signature.size / 2 + if (p1363Signature.size % 2 != 0 || keySize == 0) { + throw IllegalArgumentException("Invalid P1363 signature format") + } + + // Split P1363 signature into r and s values + val r = p1363Signature.sliceArray(0 until keySize) + val s = p1363Signature.sliceArray(keySize until p1363Signature.size) + + // Convert r and s to ASN.1 integer encoding + val encodedR = encodeAsASN1Integer(r) + val encodedS = encodeAsASN1Integer(s) + + // Combine r and s into a DER SEQUENCE + val sequenceLength = encodedR.size + encodedS.size + val der = mutableListOf() + + // DER Sequence: 0x30 [length] [encodedR] [encodedS] + der.add(0x30) // Sequence tag + der.add(sequenceLength.toByte()) // Length of the sequence + der.addAll(encodedR.toList()) // Add r + der.addAll(encodedS.toList()) // Add s + + return der.toByteArray() + } + + // Helper function to encode a byte array as ASN.1 INTEGER + private fun encodeAsASN1Integer(value: ByteArray): ByteArray { + val mutableValue = value.toMutableList() + + // If the most significant bit of the first byte is set, prepend a 0x00 byte to avoid interpretation as negative + if (mutableValue[0] and 0x80.toByte() != 0.toByte()) { + mutableValue.add(0, 0x00) + } + + val length = mutableValue.size + val asn1Integer = mutableListOf() + + // ASN.1 Integer: 0x02 [length] [value] + asn1Integer.add(0x02) // Integer tag + asn1Integer.add(length.toByte()) // Length of the integer + asn1Integer.addAll(mutableValue) // The value itself + + return asn1Integer.toByteArray() + } } 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 bb36cb071..c902b400f 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 @@ -4,7 +4,7 @@ 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.azure.AZUREKEY +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 @@ -45,8 +45,8 @@ object KeyManager { ) } - register("azure") { generateRequest: KeyGenerationRequest -> - AZUREKEY.generate( + register("azure") { generateRequest: KeyGenerationRequest -> + AzureKey.generate( generateRequest.keyType, keyName = null, Json.decodeFromJsonElement(generateRequest.config!!) 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 c89b98fec..a15119e7b 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,7 +1,7 @@ package id.walt.crypto.keys import id.walt.crypto.keys.aws.AWSKey -import id.walt.crypto.keys.azure.AZUREKEY +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 @@ -29,7 +29,7 @@ object KeySerialization { subclass(TSEKey::class) subclass(OCIKeyRestApi::class) subclass(AWSKey::class) - subclass(AZUREKEY::class) + subclass(AzureKey::class) } } diff --git a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/KeyUtils.kt b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/KeyUtils.kt new file mode 100644 index 000000000..385d27c4f --- /dev/null +++ b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/KeyUtils.kt @@ -0,0 +1,39 @@ +package id.walt.crypto.keys + +import id.walt.crypto.utils.Base64Utils.encodeToBase64Url +import id.walt.crypto.utils.JsonUtils.toJsonElement +import id.walt.crypto.utils.jwsSigningAlgorithm +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement + +object KeyUtils { + + + fun rawSignaturePayloadForJws( + plaintext: ByteArray, + headers: Map, + keyType: KeyType, + ): Triple { + val appendedHeader = HashMap(headers).apply { + put("alg", jwsSigningAlgorithm(keyType).toJsonElement()) + } + + val header = Json.encodeToString(appendedHeader).encodeToByteArray().encodeToBase64Url() + val payload = plaintext.encodeToBase64Url() + + return Triple(header, payload, "$header.$payload".encodeToByteArray()) + } + + fun signJwsWithRawSignature( + rawSignature: ByteArray, + header: String, + payload: String + ): String { + val encodedSignature = rawSignature.encodeToBase64Url() + val jws = "$header.$payload.$encodedSignature" + + return jws + } + +} diff --git a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREAuth.kt b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureAuth.kt similarity index 87% rename from waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREAuth.kt rename to waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureAuth.kt index 2e7a16115..7cbaeeb1c 100644 --- a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREAuth.kt +++ b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureAuth.kt @@ -8,7 +8,7 @@ import kotlin.js.JsExport @OptIn(ExperimentalJsExport::class) @JsExport @Serializable -data class AZUREAuth( +data class AzureAuth( val clientId: String? = null, val clientSecret: String? = null, val tenantId: String? = null, @@ -23,7 +23,7 @@ data class AZUREAuth( if (!servicePrincipal) { - throw IllegalArgumentException("AZUREAuth requires clientId, clientSecret, and tenantId") + throw IllegalArgumentException("AzureAuth requires clientId, clientSecret, and tenantId") } } -} \ No newline at end of file +} diff --git a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKEY.kt b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKey.kt similarity index 85% rename from waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKEY.kt rename to waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKey.kt index 0950303bd..998f5fdec 100644 --- a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKEY.kt +++ b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKey.kt @@ -5,10 +5,11 @@ 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.KeyUtils.rawSignaturePayloadForJws +import id.walt.crypto.keys.KeyUtils.signJwsWithRawSignature import id.walt.crypto.keys.jwk.JWKKey import id.walt.crypto.utils.Base64Utils.decodeFromBase64Url import id.walt.crypto.utils.Base64Utils.encodeToBase64Url -import id.walt.crypto.utils.JsonUtils.toJsonElement import id.walt.crypto.utils.jwsSigningAlgorithm import io.github.oshai.kotlinlogging.KotlinLogging import io.ktor.client.* @@ -21,7 +22,6 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient -import kotlinx.serialization.encodeToString import kotlinx.serialization.json.* import love.forte.plugin.suspendtrans.annotation.JsPromise import love.forte.plugin.suspendtrans.annotation.JvmAsync @@ -39,10 +39,10 @@ var _accessAzureToken: String? = null @Suppress("TRANSIENT_IS_REDUNDANT") @Serializable @SerialName("azure") -class AZUREKEY( +class AzureKey( private var _publicKey: String? = null, private var _keyType: KeyType? = null, - val config: AZUREKeyMetadata, + val config: AzureKeyMetadata, val id: String ) : Key() { @@ -56,7 +56,7 @@ class AZUREKEY( override val hasPrivateKey: Boolean get() = false - override fun toString(): String = "[AZURE ${keyType.name} key @AZURE-Vault ${config.auth.keyVaultUrl} - $id]" + override fun toString(): String = "[Azure ${keyType.name} key @Azure-Vault ${config.auth.keyVaultUrl} - $id]" @JvmBlocking @JvmAsync @@ -101,12 +101,14 @@ class AZUREKEY( @JsPromise @JsExport.Ignore @OptIn(ExperimentalStdlibApi::class) - override suspend fun signRaw(plaintext: ByteArray): ByteArray { - + /** + * Executes Azure sign operation, and converts Azure signature to DER by default (for ECC keys) + * @param ieeeP1363Signature set to true to leave signature in Azure IEEE P1363 format (no conversion) + */ + suspend fun signRaw(plaintext: ByteArray, ieeeP1363Signature: Boolean): ByteArray { val sha256Digest: ByteArray = SHA256().digest(plaintext) val base64UrlEncoded: String = sha256Digest.encodeToBase64Url() - println("digest for signing: $base64UrlEncoded") val accessToken = getAzureAccessToken( config.auth.tenantId.toString(), config.auth.clientId.toString(), config.auth.clientSecret.toString() @@ -117,15 +119,31 @@ class AZUREKEY( put("alg", JsonPrimitive(signingAlgorithm)) put("value", JsonPrimitive(base64UrlEncoded)) } - val signature = client.post("$id/sign?api-version=7.4") { + val signatureResponse = client.post("$id/sign?api-version=7.4") { contentType(ContentType.Application.Json) bearerAuth(accessToken) setBody( body ) } - println("signature: ${signature.bodyAsText()}") - return signature.azureJsonDataBody()["value"]!!.jsonPrimitive.content.decodeFromBase64Url() + var signature = signatureResponse.azureJsonDataBody()["value"]!!.jsonPrimitive.content.decodeFromBase64Url() + + // Convert signature from Azure IEEE P1363 to default DER format for raw sign + // if not explicitly asked to leave it in IEEE P1363 with `ieeeP1363Signature` + if (!ieeeP1363Signature && keyType in listOf(KeyType.secp256r1, KeyType.secp256k1)) { + signature = EccUtils.convertP1363toDER(signature) + } + + return signature + } + + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore + @OptIn(ExperimentalStdlibApi::class) + override suspend fun signRaw(plaintext: ByteArray): ByteArray { + return signRaw(plaintext, ieeeP1363Signature = false) } @JvmBlocking @@ -137,22 +155,9 @@ class AZUREKEY( 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()) - - println("the key type used here is: $keyType") - if (keyType in listOf(KeyType.secp256r1, KeyType.secp256k1)) { - rawSignature = EccUtils.convertDERtoIEEEP1363(rawSignature) - } - - val encodedSignature = rawSignature.encodeToBase64Url() - val jws = "$header.$payload.$encodedSignature" + val (header, payload, toSign) = rawSignaturePayloadForJws(plaintext, headers, keyType) + val rawSignature = signRaw(toSign, ieeeP1363Signature = true) + val jws = signJwsWithRawSignature(rawSignature, header, payload) return jws } @@ -236,9 +241,9 @@ class AZUREKEY( ) - companion object : AZUREKeyCreator { + companion object : AzureKeyCreator { private suspend fun HttpResponse.azureJsonDataBody(): JsonObject { - val baseMsg = { "AZURE server (URL: ${this.request.url}) returned an invalid response: " } + val baseMsg = { "Azure server (URL: ${this.request.url}) returned an invalid response: " } return runCatching { // First, get the body as a string @@ -296,7 +301,6 @@ class AZUREKEY( else -> throw KeyTypeNotSupportedException(type.name) } - private fun azureKeyToKeyTypeMapping(crv: String, kty: String): KeyType = when (kty) { "EC" -> when (crv) { "P-256" -> KeyType.secp256r1 // Mapping P-256 curve to secp256r1 @@ -310,7 +314,7 @@ class AZUREKEY( @JsExport.Ignore - override suspend fun generate(type: KeyType, keyName: String?, metadata: AZUREKeyMetadata): AZUREKEY { + override suspend fun generate(type: KeyType, keyName: String?, metadata: AzureKeyMetadata): AzureKey { val accessToken = getAzureAccessToken( @@ -342,7 +346,7 @@ class AZUREKEY( val keyId = key.azureJsonDataBody()["key"]?.jsonObject?.get("kid")?.jsonPrimitive?.content - ?: throw IllegalArgumentException("AZURE server returned an invalid response: key ID not found") + ?: throw IllegalArgumentException("Azure server returned an invalid response: key ID not found") val keyType = key.azureJsonDataBody()["key"]?.jsonObject?.get("kty")?.jsonPrimitive?.content!! val crvFromResponse = key.azureJsonDataBody()["key"]?.jsonObject?.get("crv")?.jsonPrimitive?.content @@ -351,7 +355,7 @@ class AZUREKEY( "Generated key with ID: $keyId, type: $keyType, curve: $crvFromResponse, metadata: $metadata" ) - return AZUREKEY( + return AzureKey( _keyType = azureKeyToKeyTypeMapping(crvFromResponse ?: "", keyType), config = metadata, id = keyId, @@ -359,7 +363,4 @@ class AZUREKEY( ) } } - - } - diff --git a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKeyCreator.kt b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKeyCreator.kt similarity index 65% rename from waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKeyCreator.kt rename to waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKeyCreator.kt index e7a6c9490..00550a4c8 100644 --- a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKeyCreator.kt +++ b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKeyCreator.kt @@ -3,7 +3,7 @@ package id.walt.crypto.keys.azure import id.walt.crypto.keys.KeyType -interface AZUREKeyCreator { +interface AzureKeyCreator { - suspend fun generate(type: KeyType, keyName: String? = "waltid", metadata: AZUREKeyMetadata): AZUREKEY -} \ No newline at end of file + suspend fun generate(type: KeyType, keyName: String? = "waltid", metadata: AzureKeyMetadata): AzureKey +} diff --git a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKeyMetadata.kt b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKeyMetadata.kt similarity index 66% rename from waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKeyMetadata.kt rename to waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKeyMetadata.kt index eb8475e49..a14f7e37f 100644 --- a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AZUREKeyMetadata.kt +++ b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKeyMetadata.kt @@ -4,14 +4,14 @@ import kotlinx.serialization.Serializable @Serializable -data class AZUREKeyMetadata( - val auth: AZUREAuth, +data class AzureKeyMetadata( + val auth: AzureAuth, ) { constructor( clientId: String, clientSecret: String, keyVaultUrl: String, tenantId: String, - ) : this(AZUREAuth(clientId, clientSecret, tenantId, keyVaultUrl)) + ) : this(AzureAuth(clientId, clientSecret, tenantId, keyVaultUrl)) } diff --git a/waltid-libraries/crypto/waltid-crypto/src/jvmMain/kotlin/id/walt/crypto/keys/jwk/JWKKey.jvm.kt b/waltid-libraries/crypto/waltid-crypto/src/jvmMain/kotlin/id/walt/crypto/keys/jwk/JWKKey.jvm.kt index 84317941a..4834702d2 100644 --- a/waltid-libraries/crypto/waltid-crypto/src/jvmMain/kotlin/id/walt/crypto/keys/jwk/JWKKey.jvm.kt +++ b/waltid-libraries/crypto/waltid-crypto/src/jvmMain/kotlin/id/walt/crypto/keys/jwk/JWKKey.jvm.kt @@ -255,7 +255,6 @@ actual class JWKKey actual constructor( actual override suspend fun verifyRaw(signed: ByteArray, detachedPlaintext: ByteArray?): Result { check(detachedPlaintext != null) { "Detached plaintext is required." } - if (keyType == KeyType.Ed25519) { val tinkVerifier = Ed25519Verify(_internalJwk.toOctetKeyPair().toPublicJWK().decodedX) return runCatching { tinkVerifier.verify(signed, detachedPlaintext) }.map { detachedPlaintext } 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 738cff74e..83bd16e95 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 @@ -125,7 +125,7 @@ fun Application.issuerApi() { IssuanceExamples.issuerOnboardingRequestAwsRestApiExampleWithRole ) example( - "did:jwk + AZURE REST API key (Azure - RSA)", + "did:jwk + Azure REST API key (Azure - RSA)", IssuanceExamples.issuerOnboardingRequestAzureRestApiExample ) required = true From fa62e2c198e0791c94f1b0ca35cbae8cf391ca3b Mon Sep 17 00:00:00 2001 From: waltkb <68587968+waltkb@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:45:54 +0100 Subject: [PATCH 09/31] Fix JavaScript declaration clash --- .../commonMain/kotlin/id/walt/crypto/keys/azure/AzureKey.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKey.kt b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKey.kt index 998f5fdec..ee97224f7 100644 --- a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKey.kt +++ b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKey.kt @@ -105,7 +105,7 @@ class AzureKey( * Executes Azure sign operation, and converts Azure signature to DER by default (for ECC keys) * @param ieeeP1363Signature set to true to leave signature in Azure IEEE P1363 format (no conversion) */ - suspend fun signRaw(plaintext: ByteArray, ieeeP1363Signature: Boolean): ByteArray { + suspend fun signRawAzure(plaintext: ByteArray, ieeeP1363Signature: Boolean): ByteArray { val sha256Digest: ByteArray = SHA256().digest(plaintext) val base64UrlEncoded: String = sha256Digest.encodeToBase64Url() @@ -143,7 +143,7 @@ class AzureKey( @JsExport.Ignore @OptIn(ExperimentalStdlibApi::class) override suspend fun signRaw(plaintext: ByteArray): ByteArray { - return signRaw(plaintext, ieeeP1363Signature = false) + return signRawAzure(plaintext, ieeeP1363Signature = false) } @JvmBlocking @@ -156,7 +156,7 @@ class AzureKey( headers: Map ): String { val (header, payload, toSign) = rawSignaturePayloadForJws(plaintext, headers, keyType) - val rawSignature = signRaw(toSign, ieeeP1363Signature = true) + val rawSignature = signRawAzure(toSign, ieeeP1363Signature = true) val jws = signJwsWithRawSignature(rawSignature, header, payload) return jws From 1d5005fdb23121c939587063dc391d501f92a9d9 Mon Sep 17 00:00:00 2001 From: waltkb <68587968+waltkb@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:51:29 +0100 Subject: [PATCH 10/31] Increase timeout for Entra test --- .../src/jvmTest/kotlin/id/walt/wallet/core/EntraTest.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/waltid-libraries/waltid-core-wallet/src/jvmTest/kotlin/id/walt/wallet/core/EntraTest.kt b/waltid-libraries/waltid-core-wallet/src/jvmTest/kotlin/id/walt/wallet/core/EntraTest.kt index aca63c47e..251851dae 100644 --- a/waltid-libraries/waltid-core-wallet/src/jvmTest/kotlin/id/walt/wallet/core/EntraTest.kt +++ b/waltid-libraries/waltid-core-wallet/src/jvmTest/kotlin/id/walt/wallet/core/EntraTest.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.test.runTest import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.jsonPrimitive import kotlin.test.Test +import kotlin.time.Duration.Companion.minutes class EntraTest : BaseTest() { @@ -49,7 +50,7 @@ class EntraTest : BaseTest() { } @Test - fun testEntraFlow() = runTest { + fun testEntraFlow() = runTest(timeout = 3.minutes) { val receivedCredentials = entraFlowIssuance() entraFlowVerification(receivedCredentials) } From d33e728e0a638e06d92bf194d05480c90f6b11ce Mon Sep 17 00:00:00 2001 From: SuperBatata Date: Wed, 27 Nov 2024 13:43:21 +0100 Subject: [PATCH 11/31] feat: added support getKeyId --- .../commonMain/kotlin/id/walt/crypto/keys/azure/AzureKey.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKey.kt b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKey.kt index ee97224f7..f1ec09f5f 100644 --- a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKey.kt +++ b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKey.kt @@ -62,9 +62,7 @@ class AzureKey( @JvmAsync @JsPromise @JsExport.Ignore - override suspend fun getKeyId(): String { - TODO("Not yet implemented") - } + override suspend fun getKeyId(): String = getPublicKey().getKeyId() @JvmBlocking @JvmAsync From 0c70f7444a649266acebcea1266ba5b0afea79e3 Mon Sep 17 00:00:00 2001 From: SuperBatata Date: Wed, 27 Nov 2024 13:43:55 +0100 Subject: [PATCH 12/31] feat: made keyname randomly generated --- .../commonMain/kotlin/id/walt/crypto/keys/azure/AzureKey.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKey.kt b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKey.kt index f1ec09f5f..d313b942d 100644 --- a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKey.kt +++ b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKey.kt @@ -30,6 +30,7 @@ import org.kotlincrypto.hash.sha2.SHA256 import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.js.ExperimentalJsExport import kotlin.js.JsExport +import kotlin.random.Random private val logger = KotlinLogging.logger { } var _accessAzureToken: String? = null @@ -312,9 +313,9 @@ class AzureKey( @JsExport.Ignore - override suspend fun generate(type: KeyType, keyName: String?, metadata: AzureKeyMetadata): AzureKey { - + override suspend fun generate(type: KeyType, metadata: AzureKeyMetadata): AzureKey { + val keyName = "waltid${Random(1000).nextInt()}" val accessToken = getAzureAccessToken( metadata.auth.tenantId.toString(), metadata.auth.clientId.toString(), metadata.auth.clientSecret.toString() From d4e133c72d66c4b72c52ae6e35426fb1b8f32511 Mon Sep 17 00:00:00 2001 From: SuperBatata Date: Wed, 27 Nov 2024 13:44:00 +0100 Subject: [PATCH 13/31] feat: made keyname randomly generated --- .../kotlin/id/walt/crypto/keys/azure/AzureKeyCreator.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKeyCreator.kt b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKeyCreator.kt index 00550a4c8..b49bbf3d7 100644 --- a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKeyCreator.kt +++ b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKeyCreator.kt @@ -5,5 +5,5 @@ import id.walt.crypto.keys.KeyType interface AzureKeyCreator { - suspend fun generate(type: KeyType, keyName: String? = "waltid", metadata: AzureKeyMetadata): AzureKey + suspend fun generate(type: KeyType, metadata: AzureKeyMetadata): AzureKey } From 8c95d028c4df648fc35adfd565649696a9357491 Mon Sep 17 00:00:00 2001 From: SuperBatata Date: Wed, 27 Nov 2024 13:44:08 +0100 Subject: [PATCH 14/31] feat: made keyname randomly generated --- .../kotlin/id/walt/crypto/keys/azure/AzureKeyMetadata.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKeyMetadata.kt b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKeyMetadata.kt index a14f7e37f..d254895d7 100644 --- a/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKeyMetadata.kt +++ b/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/azure/AzureKeyMetadata.kt @@ -12,6 +12,7 @@ data class AzureKeyMetadata( clientSecret: String, keyVaultUrl: String, tenantId: String, + keyName: String = "waltid" ) : this(AzureAuth(clientId, clientSecret, tenantId, keyVaultUrl)) } From 6037f5572f7e0a9398118080ce243bb2615a90fa Mon Sep 17 00:00:00 2001 From: SuperBatata Date: Wed, 27 Nov 2024 13:44:30 +0100 Subject: [PATCH 15/31] feat: added keyGenerationRequest for azure --- .../src/pages/wallet/[wallet]/settings/keys/generate.vue | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/waltid-applications/waltid-web-wallet/apps/waltid-dev-wallet/src/pages/wallet/[wallet]/settings/keys/generate.vue b/waltid-applications/waltid-web-wallet/apps/waltid-dev-wallet/src/pages/wallet/[wallet]/settings/keys/generate.vue index d22d038c6..2cd4b152a 100644 --- a/waltid-applications/waltid-web-wallet/apps/waltid-dev-wallet/src/pages/wallet/[wallet]/settings/keys/generate.vue +++ b/waltid-applications/waltid-web-wallet/apps/waltid-dev-wallet/src/pages/wallet/[wallet]/settings/keys/generate.vue @@ -172,6 +172,15 @@ const options = ref([ ], config: ["roleName", "region"] }, + { + keyGenerationRequest: ["Azure with Client access key", "azure"], + keyType: [ + ["ECDSA_Secp256r1", "secp256r1"], + ["ECDSA_Secp256k1", "secp256k1"], + ["RSA", "RSA"] + ], + config: ["clientId", "clientSecret", "tenantId", "keyVaultUrl"] + }, ]); const data = reactive<{ From 416a15157c40fcaaf3c83698a97162d3420a6559 Mon Sep 17 00:00:00 2001 From: SuperBatata Date: Wed, 27 Nov 2024 13:45:07 +0100 Subject: [PATCH 16/31] refactor: refactored key generation function in wallet front end --- .../[wallet]/settings/keys/generate.vue | 77 +++++++++++-------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/waltid-applications/waltid-web-wallet/apps/waltid-dev-wallet/src/pages/wallet/[wallet]/settings/keys/generate.vue b/waltid-applications/waltid-web-wallet/apps/waltid-dev-wallet/src/pages/wallet/[wallet]/settings/keys/generate.vue index 2cd4b152a..b095b0acd 100644 --- a/waltid-applications/waltid-web-wallet/apps/waltid-dev-wallet/src/pages/wallet/[wallet]/settings/keys/generate.vue +++ b/waltid-applications/waltid-web-wallet/apps/waltid-dev-wallet/src/pages/wallet/[wallet]/settings/keys/generate.vue @@ -108,8 +108,8 @@