Skip to content

Commit

Permalink
WIP try to extract more isolated abstractions
Browse files Browse the repository at this point in the history
  • Loading branch information
mrFlick72 committed Oct 10, 2024
1 parent 4ac7e32 commit 575e038
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package com.vauthenticator.server.keys.adapter.dynamo

import com.vauthenticator.server.extentions.*
import com.vauthenticator.server.keys.domain.*
import com.vauthenticator.server.keys.domain.KeyType
import org.slf4j.LoggerFactory
import software.amazon.awssdk.services.dynamodb.DynamoDbClient
import software.amazon.awssdk.services.dynamodb.model.*
import java.time.Clock
import java.time.Duration

class DynamoDbKeyStorage(
private val clock: Clock,
private val dynamoDbClient: DynamoDbClient,
private val signatureTableName: String,
private val mfaTableName: String,
) : KeyStorage {

private val logger = LoggerFactory.getLogger(DynamoDbKeyStorage::class.java)

override fun store(
masterKid: MasterKid,
kidContent: String,
dataKey: DataKey,
keyType: KeyType,
keyPurpose: KeyPurpose
) {
val tableName = tableNameBasedOn(keyPurpose)
dynamoDbClient.putItem(
PutItemRequest.builder()
.tableName(tableName)
.item(
mapOf(
"master_key_id" to masterKid.content().asDynamoAttribute(),
"key_id" to kidContent.asDynamoAttribute(),
"encrypted_private_key" to dataKey.encryptedPrivateKeyAsString().asDynamoAttribute(),
"public_key" to dataKey.publicKeyAsString().asDynamoAttribute(),
"key_purpose" to keyPurpose.name.asDynamoAttribute(),
"key_type" to keyType.name.asDynamoAttribute(),
"enabled" to true.asDynamoAttribute(),
"key_expiration_date_timestamp" to Duration.ofSeconds(0).toSeconds().asDynamoAttribute()

)
).build()
) }

override fun signatureKeys(): Keys {
val keysOnDynamo = findAllFrom(signatureTableName)
val items = keysOnDynamo.items()
val keys = keysListFrom(items)
return Keys(keys) }

override fun findOne(kid: Kid, keyPurpose: KeyPurpose): Key {
val tableName = tableNameBasedOn(keyPurpose)
return dynamoDbClient.getItem(
GetItemRequest.builder().tableName(tableName).key(
mapOf(
"key_id" to kid.content().asDynamoAttribute()
)
).build()
).item().let { keyFromDynamoFor(it) }
}

override fun justDeleteKey(kid: Kid, keyPurpose: KeyPurpose) {
val tableName = tableNameBasedOn(keyPurpose)
dynamoDbClient.deleteItem(
DeleteItemRequest.builder().tableName(tableName).key(
mapOf(
"key_id" to kid.content().asDynamoAttribute(),
)
).build()
)
}

override fun keyDeleteJodPlannedFor(kid: Kid, ttl: Duration, keyPurpose: KeyPurpose) {
try {
val tableName = tableNameBasedOn(keyPurpose)
dynamoDbClient.updateItem(
UpdateItemRequest.builder().tableName(tableName).key(
mapOf(
"key_id" to kid.content().asDynamoAttribute(),
)
).updateExpression("set enabled=:enabled, key_expiration_date_timestamp=:timestamp")
.expressionAttributeValues(
mapOf(
":enabled" to false.asDynamoAttribute(),
":timestamp" to ttl.expirationTimeStampInSecondFromNow(clock).asDynamoAttribute()
)
)
.conditionExpression("key_expiration_date_timestamp <> :timestamp")
.build()
)
} catch (e: ConditionalCheckFailedException) {
logger.info("The key ${kid.content()} delete request is ignored... key is already disabled")
}
}

private fun tableNameBasedOn(keyPurpose: KeyPurpose) = when (keyPurpose) {
KeyPurpose.MFA -> mfaTableName
KeyPurpose.SIGNATURE -> signatureTableName
}

private fun keyFromDynamoFor(it: MutableMap<String, AttributeValue>) = Key(
DataKey.from(
it.valueAsStringFor("encrypted_private_key"), it.valueAsStringFor("public_key")
),
MasterKid(it.valueAsStringFor("master_key_id")),
Kid(it.valueAsStringFor("key_id")),
it.valueAsBoolFor("enabled"),
KeyType.valueOf(it.valueAsStringFor("key_type")),
KeyPurpose.valueOf(it.valueAsStringFor("key_purpose")),
it.valueAsLongFor("key_expiration_date_timestamp", 0)
)

private fun findAllFrom(table: String) = dynamoDbClient.scan(
ScanRequest.builder().tableName(table).build()
)

private fun keysListFrom(items: MutableList<MutableMap<String, AttributeValue>>) =
items.map { keyFromDynamoFor(it) }


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.vauthenticator.server.keys.domain

import java.time.Duration

class KeyRepositoryImpl(
private val kidGenerator: () -> String,
private val keyStorage : KeyStorage,
private val keyGenerator: KeyGenerator,
) : KeyRepository {


override fun createKeyFrom(masterKid: MasterKid, keyType: KeyType, keyPurpose: KeyPurpose): Kid {
val dataKey = keyPairFor(masterKid, keyType)
val kidContent = kidGenerator.invoke()

keyStorage.store(masterKid, kidContent, dataKey, keyType, keyPurpose)

return Kid(kidContent)
}

private fun keyPairFor(masterKid: MasterKid, keyType: KeyType) =
if (keyType == KeyType.ASYMMETRIC) {
keyGenerator.dataKeyPairFor(masterKid)
} else {
keyGenerator.dataKeyFor(masterKid)
}

override fun deleteKeyFor(kid: Kid, keyPurpose: KeyPurpose, ttl: Duration) {
val key = keyFor(kid, keyPurpose)
if (!key.enabled){
throw KeyDeletionException(
"The key with kid: $kid is a rotated key.... it can not be deleted or rotated again." +
" Let's wait for the expiration time." +
" This key is not enabled to sign new token, only to verify already signed token before the rotation request"
)
}
val keys = validSignatureKeys()

val noTtl = ttl.isZero

if (noTtl) {
if (keyPurpose == KeyPurpose.SIGNATURE && keys.size <= 1) {
throw KeyDeletionException("at least one signature key is mandatory")
}

keyStorage.justDeleteKey(kid, keyPurpose)
} else {
keyStorage.keyDeleteJodPlannedFor(kid, ttl, keyPurpose)
}
}

private fun validSignatureKeys() = Keys(signatureKeys().keys).validKeys().keys


override fun signatureKeys(): Keys {
return keyStorage.signatureKeys()
}

override fun keyFor(kid: Kid, keyPurpose: KeyPurpose): Key {
return keyStorage.findOne(kid, keyPurpose)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.vauthenticator.server.keys.domain

import java.time.Duration

interface KeyStorage {
fun store(masterKid: MasterKid, kidContent: String, dataKey: DataKey, keyType: KeyType, keyPurpose: KeyPurpose)

fun signatureKeys(): Keys

fun findOne(kid: Kid, keyPurpose: KeyPurpose): Key

fun justDeleteKey(kid: Kid, keyPurpose: KeyPurpose)

fun keyDeleteJodPlannedFor(kid: Kid, ttl: Duration, keyPurpose: KeyPurpose)
}

0 comments on commit 575e038

Please sign in to comment.