Skip to content

Commit

Permalink
Merge pull request #833 from walt-id/Azure-kms-inetgration
Browse files Browse the repository at this point in the history
WAL-697 : Added Azure kms integration
  • Loading branch information
waltkb authored Dec 11, 2024
2 parents 183ff59 + 071915a commit 9f51f81
Show file tree
Hide file tree
Showing 21 changed files with 853 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@

<script lang="ts" setup>
import CenterMain from "@waltid-web-wallet/components/CenterMain.vue";
import { ArrowUturnLeftIcon, CheckIcon, KeyIcon, } from "@heroicons/vue/24/outline";
import { useCurrentWallet } from "@waltid-web-wallet/composables/accountWallet.ts";
import {ArrowUturnLeftIcon, CheckIcon, KeyIcon,} from "@heroicons/vue/24/outline";
import {useCurrentWallet} from "@waltid-web-wallet/composables/accountWallet.ts";
import InlineLoadingCircle from "@waltid-web-wallet/components/loading/InlineLoadingCircle.vue";
const loading = ref(false);
Expand Down Expand Up @@ -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<{
Expand All @@ -188,55 +197,60 @@ const data = reactive<{
const currentWallet = useCurrentWallet();
async function generateKey() {
const body = {
backend: data.keyGenerationRequest.type.includes("aws") ? "aws" : data.keyGenerationRequest.type,
const keyGenerationRequest = data.keyGenerationRequest;
const type = keyGenerationRequest?.type;
const config = keyGenerationRequest?.config;
// Build the body based on the key generation type and config
const body: any = {
backend: type?.includes("aws") ? "aws" : type,
keyType: data.type,
config: {
// If AWS, wrap config keys in an 'auth' object, otherwise leave them in the config
...(data.keyGenerationRequest.type === "aws-access-key"
? {
auth: {
accessKeyId: data.keyGenerationRequest.config.accessKeyId,
secretAccessKey: data.keyGenerationRequest.config.secretAccessKey,
region: data.keyGenerationRequest.config.region,
},
}
: data.keyGenerationRequest.type === "aws-role-name"
? {
auth: {
roleName: data.keyGenerationRequest.config.roleName,
region: data.keyGenerationRequest.config.region,
},
}
: {
// If not AWS, keep keys in the config directly
...data.keyGenerationRequest.config,
}),
},
config: {},
};
// Configure the 'config' object depending on the type (AWS, Azure, etc.)
if (type === "aws-access-key") {
body.config.auth = {
accessKeyId: config?.accessKeyId,
secretAccessKey: config?.secretAccessKey,
region: config?.region,
};
} else if (type === "aws-role-name") {
body.config.auth = {
roleName: config?.roleName,
region: config?.region,
};
} else if (type === "azure") {
body.config.auth = {
clientId: config?.clientId,
clientSecret: config?.clientSecret,
tenantId: config?.tenantId,
keyVaultUrl: config?.keyVaultUrl,
};
} else {
// For other types, just include the config directly
body.config = {...config};
}
loading.value = true;
try {
response.value = await $fetch(
`/wallet-api/wallet/${currentWallet.value}/keys/generate`,
{
method: "POST",
body: JSON.stringify(body), // Ensure the body is a JSON string
headers: {
"Content-Type": "application/json",
},
}
);
response.value = await $fetch(`/wallet-api/wallet/${currentWallet.value}/keys/generate`, {
method: "POST",
body, // fetch automatically handles JSON conversion
headers: {
"Content-Type": "application/json",
},
});
} catch (e: any) {
console.error("Error generating key:", e);
alert("Failed to generate key: " + e.message);
alert("Failed to generate key: " + (e.message || e));
} finally {
loading.value = false;
}
}
useHead({
title: "Generate key - walt.id",
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package id.walt.ktorauthnz.tokens.jwttoken

import id.walt.crypto.keys.Key
import id.walt.crypto.utils.JwsUtils.decodeJws
import id.walt.ktorauthnz.exceptions.authCheck
import id.walt.ktorauthnz.sessions.AuthSession
import id.walt.ktorauthnz.tokens.TokenHandler
import kotlinx.datetime.Clock
Expand All @@ -28,7 +29,7 @@ class JwtTokenHandler : TokenHandler {
jwtPayload["exp"]?.jsonPrimitive?.long?.let { exp ->
val expirationDate = Instant.fromEpochSeconds(exp)
val now = Clock.System.now()
check(now < expirationDate) { "JWT Token expired since: ${now - expirationDate}" }
authCheck(now < expirationDate) { "JWT Login Token expired since: ${now - expirationDate}" }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ class KtorAuthnzE2ETest {
}

@Test
fun testNonJwt() = runTest(timeout = 10.seconds) {
fun testNonJwt() = runTest(timeout = 20.seconds) {
val s = startExample(wait = false, jwt = false)

implicit1Test()
Expand All @@ -104,7 +104,7 @@ class KtorAuthnzE2ETest {
}

@Test
fun testJwt() = runTest(timeout = 10.seconds) {
fun testJwt() = runTest(timeout = 20.seconds) {
val s = startExample(wait = false, jwt = true)

implicit1Test()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package id.walt.crypto.keys

import kotlin.experimental.and

object EccUtils {


Expand Down Expand Up @@ -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<Byte>()

// 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<Byte>()

// 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()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -43,6 +44,13 @@ object KeyManager {
Json.decodeFromJsonElement(generateRequest.config!!)
)
}

register<AzureKey>("azure") { generateRequest: KeyGenerationRequest ->
AzureKey.generate(
generateRequest.keyType,
Json.decodeFromJsonElement(generateRequest.config!!)
)
}
}

fun registerByType(type: KType, typeId: String, createFunction: suspend (KeyGenerationRequest) -> Key) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,9 @@ data class JwkKeyMeta(
data class AwsKeyMeta(
override val keyId: String,
val keySize: Int? = null,
) : KeyMeta()
) : KeyMeta()

@Serializable
data class AzureKeyMeta(
override val keyId: String
) : KeyMeta()
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -28,6 +29,7 @@ object KeySerialization {
subclass(TSEKey::class)
subclass(OCIKeyRestApi::class)
subclass(AWSKey::class)
subclass(AzureKey::class)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, JsonElement>,
keyType: KeyType,
): Triple<String, String, ByteArray> {
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
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ class AWSKey(


companion object : AWSKeyCreator {
val client = HttpClient()
private val client = HttpClient()

@JsExport.Ignore
suspend fun authAccess(config: AWSKeyMetadata) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
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,
val clientSecret: String,
val tenantId: String,
val keyVaultUrl: String,
)
Loading

0 comments on commit 9f51f81

Please sign in to comment.