-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
WIP try to extract more isolated abstractions
- Loading branch information
Showing
3 changed files
with
201 additions
and
0 deletions.
There are no files selected for viewing
123 changes: 123 additions & 0 deletions
123
src/main/kotlin/com/vauthenticator/server/keys/adapter/dynamo/DynamoDbKeyStorage.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) } | ||
|
||
|
||
} |
63 changes: 63 additions & 0 deletions
63
src/main/kotlin/com/vauthenticator/server/keys/domain/KeyRepositoryImpl.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
|
||
} |
15 changes: 15 additions & 0 deletions
15
src/main/kotlin/com/vauthenticator/server/keys/domain/KeyStorage.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |