diff --git a/README.md b/README.md index e58ddd9a..606ce865 100644 --- a/README.md +++ b/README.md @@ -43,4 +43,9 @@ Right now it is based, as said before to the latest version on spring oauth2/ope ### local environment -For more details please follow to this link [readme.md](local-environment%2Freadme.md) \ No newline at end of file +For more details please follow to this link [readme.md](local-environment%2Freadme.md) + +### profiling + +The application configuration is very versatile and you can decide what persistence and key management provider to use AWS or not AWS native. +For more details please refer to the detailed page [here](docs/profiles.md) \ No newline at end of file diff --git a/docs/profiles.md b/docs/profiles.md new file mode 100644 index 00000000..11ee38a9 --- /dev/null +++ b/docs/profiles.md @@ -0,0 +1,28 @@ +# Profile + +VAuthenticator can be configured to be strongly AWS integrated using DynamoDB for the persistence layer and KMS for Key management. + +If your organization or for you run VAuthenticator so tiny integrated with AWS does not is suitable you can decide to switch postgresql instead dynamodb for the persistence +and plain java security key management instead of KMS + +All what you need is enable the relative spring profile as below: + +use ```spring.profiles.active``` with + +- ```database```: to use PostgresSQL +- ```dynamo```: to use DyanamoDB +- ```kms``` to use KMS +- omitting ```kms``` to use plain java security api + +in case of plain java security implementation the follow configuration is required: + + +```yaml +key: + master-key: + storage: + content: + key : value + key2 : value2 +``` + diff --git a/local-environment/application.yml b/local-environment/application.yml index 0ec0a463..fe1a62dc 100644 --- a/local-environment/application.yml +++ b/local-environment/application.yml @@ -19,6 +19,11 @@ mfa: otp: length: 6 timeToLiveInSeconds: 600 +key: + master-key: + storage: + content: + key : "CrZKwm8YWGN5xYeKlaC9vXUBAFFzKYsqfaOFSrrqQgA=" document: engine: file-system @@ -141,4 +146,4 @@ spring: password: postgres config: activate: - on-profile: experimental_database_persistence \ No newline at end of file + on-profile: database \ No newline at end of file diff --git a/local-environment/readme.md b/local-environment/readme.md index 30a3bde6..25a87334 100644 --- a/local-environment/readme.md +++ b/local-environment/readme.md @@ -94,11 +94,8 @@ cd ../../../../../communication/default/mail cp * ../../../dist/mail/templates ``` -## Postgres usage +## Installation in a NON AWS Environment -Postgres is an available option as storage, it is experimental right now, and it is supported only for account and roles. +Postgres and plain java key management is an available option -In order to activate it is needed to add the corresponding spring profile '''experimental_database_persistence''' and -for the init process add to the docker run the environment variable '''experimental_database_persistence=true'''with the command like below: - -> docker run --pull=always -e experimental_database_persistence=true -it mrflick72/vauthenticator-local-tenant-installer:latest \ No newline at end of file +In order to activate aws native service usage like KMS, DynamoDB and so on please use the spring profile '''aws''' default otherwise \ No newline at end of file diff --git a/src/main/kotlin/com/vauthenticator/server/account/AccountConfig.kt b/src/main/kotlin/com/vauthenticator/server/account/AccountConfig.kt index 07e98351..a8e18a28 100644 --- a/src/main/kotlin/com/vauthenticator/server/account/AccountConfig.kt +++ b/src/main/kotlin/com/vauthenticator/server/account/AccountConfig.kt @@ -56,7 +56,7 @@ class AccountConfig { @Bean("accountRepository") - @Profile("experimental_database_persistence") + @Profile("database") fun jdbcAccountRepository( jdbcTemplate: JdbcTemplate ) = JdbcAccountRepository(jdbcTemplate) @@ -68,7 +68,7 @@ class AccountConfig { havingValue = "false", matchIfMissing = true ) - @Profile("!experimental_database_persistence") + @Profile("dynamo") fun dynamoDbAccountRepository( mapper: ObjectMapper, dynamoDbClient: DynamoDbClient, @@ -85,7 +85,7 @@ class AccountConfig { havingValue = "true", matchIfMissing = false ) - @Profile("!experimental_database_persistence") + @Profile("dynamo") fun cachedDynamoDbAccountRepository( mapper: ObjectMapper, dynamoDbClient: DynamoDbClient, @@ -106,7 +106,7 @@ class AccountConfig { havingValue = "true", matchIfMissing = false ) - @Profile("!experimental_database_persistence") + @Profile("dynamo") fun accountCacheOperation( redisTemplate: RedisTemplate<*, *>, @Value("\${vauthenticator.dynamo-db.account.cache.ttl}") ttl: Duration, diff --git a/src/main/kotlin/com/vauthenticator/server/config/AuthorizationServerConfig.kt b/src/main/kotlin/com/vauthenticator/server/config/AuthorizationServerConfig.kt index 99e2002c..64517074 100644 --- a/src/main/kotlin/com/vauthenticator/server/config/AuthorizationServerConfig.kt +++ b/src/main/kotlin/com/vauthenticator/server/config/AuthorizationServerConfig.kt @@ -98,14 +98,14 @@ class AuthorizationServerConfig { } @Bean("oAuth2AuthorizationService") - @Profile("!experimental_database_persistence") + @Profile("!database") fun redisOAuth2AuthorizationService(redisTemplate: RedisTemplate): OAuth2AuthorizationService { return RedisOAuth2AuthorizationService(redisTemplate) } @Bean("oAuth2AuthorizationService") - @Profile("experimental_database_persistence") + @Profile("database") fun jdbcOAuth2AuthorizationService( jdbcTemplate : JdbcTemplate, registeredClientRepository : RegisteredClientRepository diff --git a/src/main/kotlin/com/vauthenticator/server/config/DatabaseConfig.kt b/src/main/kotlin/com/vauthenticator/server/config/DatabaseConfig.kt index d67ae8c4..a787859d 100644 --- a/src/main/kotlin/com/vauthenticator/server/config/DatabaseConfig.kt +++ b/src/main/kotlin/com/vauthenticator/server/config/DatabaseConfig.kt @@ -10,6 +10,6 @@ import org.springframework.context.annotation.Profile @Configuration @EnableAutoConfiguration(exclude = [DataSourceAutoConfiguration::class, DataSourceTransactionManagerAutoConfiguration::class, HibernateJpaAutoConfiguration::class]) -@Profile("!experimental_database_persistence") +@Profile("aws") class ExcludeDatabaseConfig diff --git a/src/main/kotlin/com/vauthenticator/server/keys/KeyConfig.kt b/src/main/kotlin/com/vauthenticator/server/keys/KeyConfig.kt index c13fb96a..ce7a88c4 100644 --- a/src/main/kotlin/com/vauthenticator/server/keys/KeyConfig.kt +++ b/src/main/kotlin/com/vauthenticator/server/keys/KeyConfig.kt @@ -4,6 +4,7 @@ import com.vauthenticator.server.keys.adapter.dynamo.DynamoDbKeyStorage import com.vauthenticator.server.keys.adapter.jdbc.JdbcKeyStorage import com.vauthenticator.server.keys.adapter.kms.KmsKeyDecrypter import com.vauthenticator.server.keys.adapter.kms.KmsKeyGenerator +import com.vauthenticator.server.keys.adapter.java.* import com.vauthenticator.server.keys.domain.* import org.springframework.beans.factory.annotation.Value import org.springframework.context.annotation.Bean @@ -18,14 +19,39 @@ import java.util.* @Configuration(proxyBeanMethods = false) class KeyConfig { - @Bean - fun keyGenerator(kmsClient: KmsClient): KeyGenerator = KmsKeyGenerator(kmsClient) + @Profile("kms") + @Bean("keyGenerator") + fun kmsKeyGenerator(kmsClient: KmsClient): KeyGenerator = KmsKeyGenerator(kmsClient) - @Bean - fun keyDecrypter(kmsClient: KmsClient): KeyDecrypter = KmsKeyDecrypter(kmsClient) + @Profile("!kms") + @Bean("keyGenerator") + fun JavaSecurityKeyGenerator( + kmsClient: KmsClient, + storage: KeyGeneratorMasterKeyStorage + ): KeyGenerator = JavaSecurityKeyGenerator( + JavaSecurityCryptographicOperations( + KeyGeneratorMasterKeyRepository(storage) + ) + ) + + @Profile("kms") + @Bean("keyDecrypter") + fun kmsKeyDecrypter(kmsClient: KmsClient): KeyDecrypter = KmsKeyDecrypter(kmsClient) + + @Profile("!kms") + @Bean("keyDecrypter") + fun JavaSecurityKeyDecrypter( + @Value("\${key.master-key}") maserKid: String, + storage: KeyGeneratorMasterKeyStorage + ): KeyDecrypter = JavaSecurityKeyDecrypter( + maserKid, + JavaSecurityCryptographicOperations( + KeyGeneratorMasterKeyRepository(storage) + ) + ) @Bean("keyStorage") - @Profile("!experimental_database_persistence") + @Profile("dynamo") fun dynamoDbKeyStorage( clock: Clock, dynamoDbClient: DynamoDbClient, @@ -34,7 +60,7 @@ class KeyConfig { ) = DynamoDbKeyStorage(clock, dynamoDbClient, signatureTableName, mfaTableName) @Bean("keyStorage") - @Profile("experimental_database_persistence") + @Profile("database") fun jdbcKeyStorage(jdbcTemplate: JdbcTemplate, clock: Clock) = JdbcKeyStorage(jdbcTemplate, clock) @Bean("keyRepository") diff --git a/src/main/kotlin/com/vauthenticator/server/keys/adapter/java/JavaSecurityCryptographicOperations.kt b/src/main/kotlin/com/vauthenticator/server/keys/adapter/java/JavaSecurityCryptographicOperations.kt new file mode 100644 index 00000000..32f65301 --- /dev/null +++ b/src/main/kotlin/com/vauthenticator/server/keys/adapter/java/JavaSecurityCryptographicOperations.kt @@ -0,0 +1,52 @@ +package com.vauthenticator.server.keys.adapter.java + +import com.vauthenticator.server.extentions.decoder +import com.vauthenticator.server.keys.domain.MasterKid +import org.bouncycastle.jce.provider.BouncyCastleProvider +import java.security.KeyPair +import java.security.KeyPairGenerator +import java.security.Security +import java.security.spec.RSAKeyGenParameterSpec +import javax.crypto.Cipher +import javax.crypto.spec.SecretKeySpec + + +class JavaSecurityCryptographicOperations( + private val repository: KeyGeneratorMasterKeyRepository +) { + companion object { + init { + Security.addProvider(BouncyCastleProvider()) + } + } + + fun generateRSAKeyPair(): KeyPair { + val keyPair: KeyPair + try { + val keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC") + keyPairGenerator.initialize(RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4)) + keyPair = keyPairGenerator.generateKeyPair() + } catch (ex: Exception) { + throw IllegalStateException(ex) + } + return keyPair + } + + fun encryptKeyWith(masterKid: MasterKid, encodedPlainText: ByteArray): ByteArray { + val masterKey = decoder.decode(repository.maskerKeyFor(masterKid)); + val key = SecretKeySpec(masterKey, "AES") + val cipher = Cipher.getInstance("AES") + cipher.init(Cipher.ENCRYPT_MODE, key) + return cipher.doFinal(encodedPlainText) + } + + fun decryptKeyWith(masterKid: MasterKid, encodedEncryptedText: ByteArray): ByteArray { + val masterKey = decoder.decode(repository.maskerKeyFor(masterKid)); + val key = SecretKeySpec(masterKey, "AES") + val cipher = Cipher.getInstance("AES") + cipher.init(Cipher.DECRYPT_MODE, key) + return cipher.doFinal(decoder.decode(encodedEncryptedText)) + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/com/vauthenticator/server/keys/adapter/java/JavaSecurityKeyDecrypter.kt b/src/main/kotlin/com/vauthenticator/server/keys/adapter/java/JavaSecurityKeyDecrypter.kt new file mode 100644 index 00000000..570019d4 --- /dev/null +++ b/src/main/kotlin/com/vauthenticator/server/keys/adapter/java/JavaSecurityKeyDecrypter.kt @@ -0,0 +1,15 @@ +package com.vauthenticator.server.keys.adapter.java + +import com.vauthenticator.server.extentions.encoder +import com.vauthenticator.server.keys.domain.KeyDecrypter +import com.vauthenticator.server.keys.domain.MasterKid + +class JavaSecurityKeyDecrypter( + private val maserKid: String, + private val javaSecurityCryptographicOperations: JavaSecurityCryptographicOperations +) : KeyDecrypter { + override fun decryptKey(encrypted: String): String { + return encoder.encode(javaSecurityCryptographicOperations.decryptKeyWith(MasterKid(maserKid), encrypted.toByteArray())) + .decodeToString() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/vauthenticator/server/keys/adapter/java/JavaSecurityKeyGenerator.kt b/src/main/kotlin/com/vauthenticator/server/keys/adapter/java/JavaSecurityKeyGenerator.kt new file mode 100644 index 00000000..5f541e93 --- /dev/null +++ b/src/main/kotlin/com/vauthenticator/server/keys/adapter/java/JavaSecurityKeyGenerator.kt @@ -0,0 +1,32 @@ +package com.vauthenticator.server.keys.adapter.java + +import com.vauthenticator.server.keys.domain.DataKey +import com.vauthenticator.server.keys.domain.KeyGenerator +import com.vauthenticator.server.keys.domain.MasterKid +import java.util.* + + +class JavaSecurityKeyGenerator( + private val javaSecurityCryptographicOperations: JavaSecurityCryptographicOperations +) : KeyGenerator { + + + override fun dataKeyPairFor(masterKid: MasterKid): DataKey { + val generateRSAKeyPair = javaSecurityCryptographicOperations.generateRSAKeyPair() + return DataKey( + javaSecurityCryptographicOperations.encryptKeyWith(masterKid, generateRSAKeyPair.private.encoded), + Optional.of(generateRSAKeyPair.public.encoded) + ) + } + + override fun dataKeyFor(masterKid: MasterKid): DataKey { + val generateRSAKeyPair = javaSecurityCryptographicOperations.generateRSAKeyPair() + return DataKey( + javaSecurityCryptographicOperations.encryptKeyWith(masterKid, generateRSAKeyPair.private.encoded), + Optional.empty() + ) + } + + + +} \ No newline at end of file diff --git a/src/main/kotlin/com/vauthenticator/server/keys/adapter/java/KeyGeneratorMasterKeyRepository.kt b/src/main/kotlin/com/vauthenticator/server/keys/adapter/java/KeyGeneratorMasterKeyRepository.kt new file mode 100644 index 00000000..e5596f15 --- /dev/null +++ b/src/main/kotlin/com/vauthenticator/server/keys/adapter/java/KeyGeneratorMasterKeyRepository.kt @@ -0,0 +1,30 @@ +package com.vauthenticator.server.keys.adapter.java + +import com.vauthenticator.server.keys.domain.MasterKid +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.boot.context.properties.EnableConfigurationProperties +import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Profile + + +class KeyGeneratorMasterKeyRepository( + val storage: KeyGeneratorMasterKeyStorage +) { + + fun maskerKeyFor(masterKeyId: MasterKid): String { + return storage.content[masterKeyId.content()]!! + } + +} + +@Profile("!kms") +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties(KeyGeneratorMasterKeyStorage::class) +class KeyGeneratorMasterKeyRepositoryConfig { + +} + +@ConfigurationProperties(prefix = "key.master-key.storage") +data class KeyGeneratorMasterKeyStorage(val content: Map) { + +} \ No newline at end of file diff --git a/src/main/kotlin/com/vauthenticator/server/keys/adapter/java/KeyInitJob.kt b/src/main/kotlin/com/vauthenticator/server/keys/adapter/java/KeyInitJob.kt new file mode 100644 index 00000000..58077ddc --- /dev/null +++ b/src/main/kotlin/com/vauthenticator/server/keys/adapter/java/KeyInitJob.kt @@ -0,0 +1,31 @@ +package com.vauthenticator.server.keys.adapter.java + +import com.vauthenticator.server.keys.domain.* +import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.ApplicationArguments +import org.springframework.boot.ApplicationRunner +import org.springframework.context.annotation.Profile +import org.springframework.stereotype.Service + +@Service +@Profile("!kms") +class KeyInitJob( + @Value("\${key.master-key}") private val maserKid: String, + private val keyStorage: KeyStorage, + private val keyRepository: KeyRepository +) : ApplicationRunner { + + override fun run(args: ApplicationArguments) { + + if (keyStorage.signatureKeys().keys.isEmpty()) { + val kid = keyRepository.createKeyFrom( + masterKid = MasterKid(maserKid), + keyPurpose = KeyPurpose.SIGNATURE, + keyType = KeyType.ASYMMETRIC, + ) + println(kid) + } + + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/vauthenticator/server/mfa/MfaConfig.kt b/src/main/kotlin/com/vauthenticator/server/mfa/MfaConfig.kt index 24c50c34..b7f8d048 100644 --- a/src/main/kotlin/com/vauthenticator/server/mfa/MfaConfig.kt +++ b/src/main/kotlin/com/vauthenticator/server/mfa/MfaConfig.kt @@ -35,7 +35,7 @@ import java.util.* class MfaConfig { @Bean("mfaAccountMethodsRepository") - @Profile("!experimental_database_persistence") + @Profile("dynamo") fun dynamoDbMfaAccountMethodsRepository( keyRepository: KeyRepository, dynamoDbClient: DynamoDbClient, @@ -52,7 +52,7 @@ class MfaConfig { ) { MfaDeviceId(UUID.randomUUID().toString()) } @Bean("mfaAccountMethodsRepository") - @Profile("experimental_database_persistence") + @Profile("database") fun jdbcMfaAccountMethodsRepository( keyRepository: KeyRepository, jdbcTemplate: JdbcTemplate, diff --git a/src/main/kotlin/com/vauthenticator/server/oauth2/clientapp/ClientApplicationConfig.kt b/src/main/kotlin/com/vauthenticator/server/oauth2/clientapp/ClientApplicationConfig.kt index c829b4c6..6ecdfda8 100644 --- a/src/main/kotlin/com/vauthenticator/server/oauth2/clientapp/ClientApplicationConfig.kt +++ b/src/main/kotlin/com/vauthenticator/server/oauth2/clientapp/ClientApplicationConfig.kt @@ -29,7 +29,7 @@ class ClientApplicationConfig { @Bean("clientApplicationRepository") - @Profile("experimental_database_persistence") + @Profile("database") fun jdbcClientApplicationRepository(jdbcTemplate: JdbcTemplate, objectMapper: ObjectMapper) : ClientApplicationRepository = JdbcClientApplicationRepository(jdbcTemplate, objectMapper) @@ -39,7 +39,7 @@ class ClientApplicationConfig { havingValue = "false", matchIfMissing = true ) - @Profile("!experimental_database_persistence") + @Profile("dynamo") fun dynamoDbClientApplicationRepository( dynamoDbClient: DynamoDbClient, @Value("\${vauthenticator.dynamo-db.client-application.table-name}") clientAppTableName: String @@ -51,7 +51,7 @@ class ClientApplicationConfig { havingValue = "true", matchIfMissing = false ) - @Profile("!experimental_database_persistence") + @Profile("dynamo") fun cachedClientApplicationRepository( dynamoDbClient: DynamoDbClient, clientApplicationCacheOperation: CacheOperation, @@ -70,7 +70,7 @@ class ClientApplicationConfig { havingValue = "true", matchIfMissing = false ) - @Profile("!experimental_database_persistence") + @Profile("dynamo") fun clientApplicationCacheOperation( redisTemplate: RedisTemplate<*, *>, @Value("\${vauthenticator.dynamo-db.client-application.cache.ttl}") ttl: Duration, diff --git a/src/main/kotlin/com/vauthenticator/server/role/PermissionConfig.kt b/src/main/kotlin/com/vauthenticator/server/role/PermissionConfig.kt index d0ff5f07..d6780781 100644 --- a/src/main/kotlin/com/vauthenticator/server/role/PermissionConfig.kt +++ b/src/main/kotlin/com/vauthenticator/server/role/PermissionConfig.kt @@ -23,7 +23,7 @@ import java.time.Duration class PermissionConfig { @Bean("roleRepository") - @Profile("experimental_database_persistence") + @Profile("database") fun jdbcRoleRepository( jdbcTemplate: JdbcTemplate, @Value("\${vauthenticator.dynamo-db.role.protected-from-delete}") protectedRoleFromDeletion: List @@ -35,7 +35,7 @@ class PermissionConfig { havingValue = "false", matchIfMissing = true ) - @Profile("!experimental_database_persistence") + @Profile("dynamo") fun dynamoDbRoleRepository( dynamoDbClient: DynamoDbClient, @Value("\${vauthenticator.dynamo-db.role.table-name}") roleTableName: String, @@ -49,7 +49,7 @@ class PermissionConfig { havingValue = "true", matchIfMissing = false ) - @Profile("!experimental_database_persistence") + @Profile("dynamo") fun cachedDynamoDbRoleRepository( mapper: ObjectMapper, roleCacheOperation: CacheOperation, @@ -68,7 +68,7 @@ class PermissionConfig { havingValue = "true", matchIfMissing = false ) - @Profile("!experimental_database_persistence") + @Profile("dynamo") fun roleCacheOperation( redisTemplate: RedisTemplate<*, *>, @Value("\${vauthenticator.dynamo-db.role.cache.ttl}") ttl: Duration, diff --git a/src/main/kotlin/com/vauthenticator/server/ticket/TicketConfig.kt b/src/main/kotlin/com/vauthenticator/server/ticket/TicketConfig.kt index 803129cd..3e501db3 100644 --- a/src/main/kotlin/com/vauthenticator/server/ticket/TicketConfig.kt +++ b/src/main/kotlin/com/vauthenticator/server/ticket/TicketConfig.kt @@ -20,7 +20,7 @@ import java.util.* class TicketConfig { @Bean("ticketRepository") - @Profile("!experimental_database_persistence") + @Profile("dynamo") fun dynamoDbTicketRepository( @Value("\${vauthenticator.dynamo-db.ticket.table-name}") tableName: String, dynamoDbClient: DynamoDbClient @@ -28,7 +28,7 @@ class TicketConfig { @Bean("ticketRepository") - @Profile("experimental_database_persistence") + @Profile("database") fun jdbCTicketRepository( jdbcTemplate: JdbcTemplate, objectMapper: ObjectMapper diff --git a/src/test/kotlin/com/vauthenticator/server/keys/adapter/java/JavaSecurityCryptographicOperationsTest.kt b/src/test/kotlin/com/vauthenticator/server/keys/adapter/java/JavaSecurityCryptographicOperationsTest.kt new file mode 100644 index 00000000..352eecfc --- /dev/null +++ b/src/test/kotlin/com/vauthenticator/server/keys/adapter/java/JavaSecurityCryptographicOperationsTest.kt @@ -0,0 +1,81 @@ +package com.vauthenticator.server.keys.adapter.java + +import com.vauthenticator.server.extentions.decoder +import com.vauthenticator.server.support.KeysUtils +import io.mockk.* +import io.mockk.impl.annotations.MockK +import io.mockk.junit5.MockKExtension +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import java.security.KeyPair +import java.security.KeyPairGenerator +import java.security.spec.RSAKeyGenParameterSpec +import javax.crypto.Cipher +import javax.crypto.spec.SecretKeySpec + +@ExtendWith(MockKExtension::class) +class JavaSecurityCryptographicOperationsTest { + + @MockK + lateinit var repository: KeyGeneratorMasterKeyRepository + + lateinit var uut: JavaSecurityCryptographicOperations + + @BeforeEach + fun setUp() { + uut = JavaSecurityCryptographicOperations(repository) + } + + @Test + fun `when a new rsa key pair is created`() { + mockkStatic(KeyPairGenerator::class) + val expected = mockk() + val generator = mockk(relaxed = true) + every { KeyPairGenerator.getInstance("RSA", "BC") } returns generator + every { generator.initialize(RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4)) } just runs + every { generator.generateKeyPair() } returns expected + + val actual = uut.generateRSAKeyPair() + Assertions.assertEquals(expected, actual) + } + + @Test + fun `when an encoded plain text is encrypted with some master key`() { + val expected = "ENCRYPTED_DATA".toByteArray() + val encodedPlainText = "INPUT_TEXT".toByteArray() + val masterKeyValue = "QV9LRVk=" + val key = SecretKeySpec(decoder.decode(masterKeyValue), "AES") + val cipher = mockk(relaxed = true) + mockkStatic(Cipher::class) + + every { repository.maskerKeyFor(KeysUtils.aMasterKey) } returns masterKeyValue + every { Cipher.getInstance("AES") } returns cipher + every { cipher.init(Cipher.ENCRYPT_MODE, key) } just runs + every { cipher.doFinal(encodedPlainText) } returns expected + + val actual = uut.encryptKeyWith(KeysUtils.aMasterKey, encodedPlainText) + Assertions.assertEquals(expected, actual) + } + + @Test + fun `when an encoded encrypted text is decrypted with some master key`() { + val expected = "DECRYPTED_DATA".toByteArray() + val encodedEncryptedText = "RU5DUllQVEVEX0lOUFVUX1RFWFQ=".toByteArray() + val masterKeyValue = "QV9LRVk=" + val key = SecretKeySpec(decoder.decode(masterKeyValue), "AES") + val cipher = mockk(relaxed = true) + mockkStatic(Cipher::class) + + every { repository.maskerKeyFor(KeysUtils.aMasterKey) } returns masterKeyValue + every { Cipher.getInstance("AES") } returns cipher + every { cipher.init(Cipher.DECRYPT_MODE, key) } just runs + every { cipher.doFinal(decoder.decode(encodedEncryptedText)) } returns expected + + val actual = uut.decryptKeyWith(KeysUtils.aMasterKey, encodedEncryptedText) + Assertions.assertEquals(expected, actual) + } + + +} \ No newline at end of file diff --git a/src/test/kotlin/com/vauthenticator/server/keys/adapter/java/JavaSecurityKeyDecrypterTest.kt b/src/test/kotlin/com/vauthenticator/server/keys/adapter/java/JavaSecurityKeyDecrypterTest.kt new file mode 100644 index 00000000..b043eb40 --- /dev/null +++ b/src/test/kotlin/com/vauthenticator/server/keys/adapter/java/JavaSecurityKeyDecrypterTest.kt @@ -0,0 +1,33 @@ +package com.vauthenticator.server.keys.adapter.java + +import com.vauthenticator.server.extentions.encoder +import com.vauthenticator.server.keys.domain.MasterKid +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.junit5.MockKExtension +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(MockKExtension::class) +class JavaSecurityKeyDecrypterTest { + + @MockK + lateinit var javaSecurityCryptographicOperations: JavaSecurityCryptographicOperations + + @Test + fun `happy path`() { + val encrypted = "AN_ENCRYPTED_VALUE" + val decrypted = "AN_UNENCRYPTED_VALUE".toByteArray() + val maserKid = "A_MASTER_KEY" + + val uut = JavaSecurityKeyDecrypter(maserKid, javaSecurityCryptographicOperations) + + every { javaSecurityCryptographicOperations.decryptKeyWith(MasterKid(maserKid), encrypted.toByteArray()) } returns decrypted + + val actual = uut.decryptKey(encrypted) + val expected = encoder.encode(decrypted).decodeToString() + + Assertions.assertEquals(expected, actual) + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/vauthenticator/server/keys/adapter/java/JavaSecurityKeyGeneratorTest.kt b/src/test/kotlin/com/vauthenticator/server/keys/adapter/java/JavaSecurityKeyGeneratorTest.kt new file mode 100644 index 00000000..026f578a --- /dev/null +++ b/src/test/kotlin/com/vauthenticator/server/keys/adapter/java/JavaSecurityKeyGeneratorTest.kt @@ -0,0 +1,84 @@ +package com.vauthenticator.server.keys.adapter.java + +import com.vauthenticator.server.extentions.encoder +import com.vauthenticator.server.keys.domain.DataKey +import com.vauthenticator.server.keys.domain.MasterKid +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.junit5.MockKExtension +import io.mockk.mockk +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import java.security.KeyPair +import java.security.PrivateKey +import java.security.PublicKey + +@ExtendWith(MockKExtension::class) +class JavaSecurityKeyGeneratorTest { + + @MockK + lateinit var javaSecurityCryptographicOperations: JavaSecurityCryptographicOperations + + private val masterKid = MasterKid("A_MASTER_KEY") + private val anEncryptedPrivateKEyValueAsByteArray = "AN_ENCRYPTED_PRIVATE_KEY_VALUE".toByteArray() + private val aPublicKeyValueAsByteArray = "A_PUBLIC_KEY_VALUE".toByteArray() + + lateinit var uut: JavaSecurityKeyGenerator + + @BeforeEach + fun setUp() { + uut = JavaSecurityKeyGenerator(javaSecurityCryptographicOperations) + } + + @Test + fun `when a new data key is created`() { + val keyPair = mockk() + val privateKey = mockk() + + every { javaSecurityCryptographicOperations.generateRSAKeyPair() } returns keyPair + every { keyPair.private } returns privateKey + every { privateKey.encoded } returns anEncryptedPrivateKEyValueAsByteArray + + every { + javaSecurityCryptographicOperations.encryptKeyWith( + masterKid, + anEncryptedPrivateKEyValueAsByteArray + ) + } returns anEncryptedPrivateKEyValueAsByteArray + + val actual = uut.dataKeyFor(masterKid) + val expected = DataKey.from(encoder.encode(anEncryptedPrivateKEyValueAsByteArray).decodeToString(), "") + Assertions.assertEquals(expected, actual) + } + + + @Test + fun `when a new data key pair is created`() { + val keyPair = mockk() + val privateKey = mockk() + val publicKey = mockk() + + every { javaSecurityCryptographicOperations.generateRSAKeyPair() } returns keyPair + every { keyPair.private } returns privateKey + every { privateKey.encoded } returns anEncryptedPrivateKEyValueAsByteArray + + every { keyPair.public } returns publicKey + every { publicKey.encoded } returns aPublicKeyValueAsByteArray + + every { + javaSecurityCryptographicOperations.encryptKeyWith( + masterKid, + anEncryptedPrivateKEyValueAsByteArray + ) + } returns anEncryptedPrivateKEyValueAsByteArray + + val actual = uut.dataKeyPairFor(masterKid) + val expected = DataKey.from( + encoder.encode(anEncryptedPrivateKEyValueAsByteArray).decodeToString(), + encoder.encode(aPublicKeyValueAsByteArray).decodeToString() + ) + Assertions.assertEquals(expected, actual) + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/vauthenticator/server/keys/adapter/java/KeyGeneratorMasterKeyRepositoryTest.kt b/src/test/kotlin/com/vauthenticator/server/keys/adapter/java/KeyGeneratorMasterKeyRepositoryTest.kt new file mode 100644 index 00000000..3d999894 --- /dev/null +++ b/src/test/kotlin/com/vauthenticator/server/keys/adapter/java/KeyGeneratorMasterKeyRepositoryTest.kt @@ -0,0 +1,31 @@ +package com.vauthenticator.server.keys.adapter.java + +import com.vauthenticator.server.keys.domain.MasterKid +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class KeyGeneratorMasterKeyRepositoryTest { + + lateinit var uut: KeyGeneratorMasterKeyRepository + + @BeforeEach + fun setUp() { + uut = KeyGeneratorMasterKeyRepository(KeyGeneratorMasterKeyStorage(mapOf("a_key" to "a_value"))) + } + + + @Test + fun `when a key is retrieved`() { + val expected = "a_value" + val actual = uut.maskerKeyFor(MasterKid("a_key")) + assertEquals(expected, actual) + } + + + @Test + fun `when get a key from the storage fails`() { + assertThrows(NullPointerException::class.java) { uut.maskerKeyFor(MasterKid("a_key_2")) } + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/vauthenticator/server/keys/adapter/kms/KmsKeyGeneratorTest.kt b/src/test/kotlin/com/vauthenticator/server/keys/adapter/kms/KmsKeyGeneratorTest.kt new file mode 100644 index 00000000..c6309e45 --- /dev/null +++ b/src/test/kotlin/com/vauthenticator/server/keys/adapter/kms/KmsKeyGeneratorTest.kt @@ -0,0 +1,8 @@ +package com.vauthenticator.server.keys.adapter.kms + +import org.junit.jupiter.api.Assertions.* + +class KmsKeyGeneratorTest { + + +} \ No newline at end of file