diff --git a/waltid-libraries/crypto/waltid-crypto/README.md b/waltid-libraries/crypto/waltid-crypto/README.md
index ffca0825b..55ceda6fb 100644
--- a/waltid-libraries/crypto/waltid-crypto/README.md
+++ b/waltid-libraries/crypto/waltid-crypto/README.md
@@ -15,13 +15,13 @@
The library provides the following key entities to work with:
-- [JWKKey](https://github.com/walt-id/waltid-identity/blob/main/waltid-libraries/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/jwk/JWKKey.kt) -
+- [JWKKey](https://github.com/walt-id/waltid-identity/blob/main/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/jwk/JWKKey.kt) -
an implementation of a local (in-memory) key (private / public)
-- [TSEKey](https://github.com/walt-id/waltid-identity/blob/main/waltid-libraries/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/tse/TSEKey.kt) -
+- [TSEKey](https://github.com/walt-id/waltid-identity/blob/main/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/tse/TSEKey.kt) -
an implementation of a Hashicorp Vault Transit Secrets Engine key (private / public)
-- [AWSKey](https://github.com/walt-id/waltid-identity/blob/main/waltid-libraries/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/aws/AWSKey.kt) -
+- [AWSKey](https://github.com/walt-id/waltid-identity/blob/main/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/aws/AWSKey.kt) -
an implementation of an AWS key (private / public)
-- [OCIKey](https://github.com/walt-id/waltid-identity/blob/main/waltid-libraries/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/oci/OCIKeyRestApi.kt) -
+- [OCIKey](https://github.com/walt-id/waltid-identity/blob/main/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/oci/OCIKeyRestApi.kt) -
an implementation of an Oracle OCI key (private / public)
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/AWSKey.kt
index de9460c1a..9ef733643 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/AWSKey.kt
@@ -22,6 +22,7 @@ import io.ktor.util.*
import io.ktor.utils.io.charsets.*
import io.ktor.utils.io.core.*
import kotlinx.datetime.Clock
+import kotlinx.datetime.Instant
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toInstant
import kotlinx.datetime.toLocalDateTime
@@ -40,9 +41,21 @@ import kotlin.collections.component2
import kotlin.io.encoding.ExperimentalEncodingApi
import kotlin.js.ExperimentalJsExport
import kotlin.js.JsExport
+import kotlin.time.Duration.Companion.seconds
private val logger = KotlinLogging.logger { }
+data class AWSAuthConfiguration(
+ val accessKeyId: String?,
+ val secretAccessKey: String?,
+ val sessionToken: String?,
+ val expiration: String?
+)
+
+var _accessAWS: AWSAuthConfiguration? = null
+var timeoutAt: Instant? = null
+
+
@OptIn(ExperimentalJsExport::class)
@JsExport
@Suppress("TRANSIENT_IS_REDUNDANT")
@@ -249,9 +262,34 @@ class AWSKey(
@JsExport.Ignore
override suspend fun getMeta(): AwsKeyMeta = AwsKeyMeta(getKeyId())
+
companion object : AWSKeyCreator {
val client = HttpClient()
+ @JsExport.Ignore
+ suspend fun authAccess(config: AWSKeyMetadata) {
+ val isAccessDataProvided = config.accessKeyId.isNotEmpty() && config.secretAccessKey.isNotEmpty()
+
+ if (isAccessDataProvided) {
+ _accessAWS = AWSAuthConfiguration(config.accessKeyId, config.secretAccessKey, null, null)
+ timeoutAt = null
+ } else {
+ val token = getIMDSv2Token()
+ val role = getRoleName(token)
+ _accessAWS = getTemporaryCredentials(token, role)
+ timeoutAt = Clock.System.now().plus(3600.seconds)
+ }
+ }
+
+ @JsExport.Ignore
+ suspend fun getAccess(config: AWSKeyMetadata): AWSAuthConfiguration? {
+ if (_accessAWS == null || (timeoutAt != null && timeoutAt!! <= Clock.System.now())) {
+ authAccess(config)
+ }
+
+ return _accessAWS
+ }
+
// Utility to hash data using SHA256
@OptIn(ExperimentalStdlibApi::class)
@@ -360,6 +398,63 @@ ${sha256Hex(canonicalRequest)}
)
}
+
+ // Function to get IMDSv2 token
+ @JsExport.Ignore
+ suspend fun getIMDSv2Token(ttlSeconds: Int = 21600): String {
+ val url = "http://169.254.169.254/latest/api/token"
+ val token = client.put(url) {
+ headers {
+ append("X-aws-ec2-metadata-token-ttl-seconds", ttlSeconds.toString())
+ }
+ }
+ logger.trace { "AWS TOKEN: $token" }
+ return token.bodyAsText()
+ }
+
+ // Function to get role name
+ @JsExport.Ignore
+ suspend fun getRoleName(token: String): String {
+ val url = "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
+ val roleName = client.get(url) {
+ headers {
+ append("X-aws-ec2-metadata-token", token)
+ }
+ }
+ logger.debug { "AWS Role Name: $roleName" }
+ return roleName.bodyAsText()
+ }
+
+ // Function to get temporary credentials using role name
+ @JsExport.Ignore
+ suspend fun getTemporaryCredentials(token: String, roleName: String): AWSAuthConfiguration {
+ val url = "http://169.254.169.254/latest/meta-data/iam/security-credentials/$roleName"
+ val response = client.get(url) {
+ headers {
+ append("X-aws-ec2-metadata-token", token)
+ }
+ }
+ val json = Json.parseToJsonElement(response.bodyAsText()).jsonObject
+
+
+ val accessKeyId =
+ json["AccessKeyId"]?.jsonPrimitive?.content ?: throw IllegalArgumentException("AccessKeyId not found")
+ val secretAccessKey = json["SecretAccessKey"]?.jsonPrimitive?.content
+ ?: throw IllegalArgumentException("SecretAccessKey not found")
+ val sessionToken =
+ json["Token"]?.jsonPrimitive?.content ?: throw IllegalArgumentException("Token not found")
+ val expiration =
+ json["Expiration"]?.jsonPrimitive?.content ?: throw IllegalArgumentException("Expiration not found")
+
+ return AWSAuthConfiguration(
+ accessKeyId = accessKeyId,
+ secretAccessKey = secretAccessKey,
+ sessionToken = sessionToken,
+ expiration = expiration
+ )
+ }
+
+
@JvmBlocking
@JvmAsync
@JsPromise
@@ -442,6 +537,12 @@ $public
@JsExport.Ignore
override suspend fun generate(type: KeyType, config: AWSKeyMetadata): AWSKey {
+
+
+ if (config.accessKeyId.isEmpty() && config.secretAccessKey.isEmpty()) {
+ getAccess(config)
+ }
+
val keyType = keyTypeToAwsKeyMapping(type)
val body =
"""{