From d6d553e8e0650a74341edb333ce05390eb1bc7ae Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 18 Oct 2023 11:26:02 +0200 Subject: [PATCH] Pin code: add some tests --- features/lockscreen/impl/build.gradle.kts | 2 + .../impl/pin/DefaultPinCodeManager.kt | 14 +++-- .../impl/pin/DefaultPinCodeManagerTest.kt | 53 +++++++++++++++++ .../impl/pin/storage/InMemoryPinCodeStore.kt | 59 +++++++++++++++++++ .../cryptography/api/AESEncryptionSpecs.kt | 27 +++++++++ ...vice.kt => EncryptionDecryptionService.kt} | 5 +- .../cryptography/api/EncryptionResult.kt | 9 ++- .../cryptography/api/SecretKeyProvider.kt | 27 +++++++++ libraries/cryptography/impl/build.gradle.kts | 4 ++ ...e.kt => AESEncryptionDecryptionService.kt} | 41 +++---------- .../impl/KeyStoreSecretKeyProvider.kt | 55 +++++++++++++++++ .../AESEncryptionDecryptionServiceTest.kt | 54 +++++++++++++++++ libraries/cryptography/test/build.gradle.kts | 27 +++++++++ .../test/SimpleSecretKeyProvider.kt | 39 ++++++++++++ 14 files changed, 371 insertions(+), 45 deletions(-) create mode 100644 features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManagerTest.kt create mode 100644 features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/storage/InMemoryPinCodeStore.kt create mode 100644 libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/AESEncryptionSpecs.kt rename libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/{CryptoService.kt => EncryptionDecryptionService.kt} (88%) create mode 100644 libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/SecretKeyProvider.kt rename libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/{DefaultCryptoService.kt => AESEncryptionDecryptionService.kt} (52%) create mode 100644 libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/KeyStoreSecretKeyProvider.kt create mode 100644 libraries/cryptography/impl/src/test/kotlin/io/element/android/libraries/cryptography/impl/AESEncryptionDecryptionServiceTest.kt create mode 100644 libraries/cryptography/test/build.gradle.kts create mode 100644 libraries/cryptography/test/src/main/kotlin/io/element/android/libraries/cryptography/test/SimpleSecretKeyProvider.kt diff --git a/features/lockscreen/impl/build.gradle.kts b/features/lockscreen/impl/build.gradle.kts index 51e766bd36..af63538db5 100644 --- a/features/lockscreen/impl/build.gradle.kts +++ b/features/lockscreen/impl/build.gradle.kts @@ -47,6 +47,8 @@ dependencies { testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.cryptography.test) + testImplementation(projects.libraries.cryptography.impl) ksp(libs.showkase.processor) } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManager.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManager.kt index e7e27bf5f3..e7529e9280 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManager.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManager.kt @@ -18,8 +18,9 @@ package io.element.android.features.lockscreen.impl.pin import com.squareup.anvil.annotations.ContributesBinding import io.element.android.features.lockscreen.impl.pin.storage.PinCodeStore -import io.element.android.libraries.cryptography.api.CryptoService +import io.element.android.libraries.cryptography.api.EncryptionDecryptionService import io.element.android.libraries.cryptography.api.EncryptionResult +import io.element.android.libraries.cryptography.api.SecretKeyProvider import io.element.android.libraries.di.AppScope import javax.inject.Inject @@ -27,7 +28,8 @@ private const val SECRET_KEY_ALIAS = "SECRET_KEY_ALIAS_PIN_CODE" @ContributesBinding(AppScope::class) class DefaultPinCodeManager @Inject constructor( - private val cryptoService: CryptoService, + private val secretKeyProvider: SecretKeyProvider, + private val encryptionDecryptionService: EncryptionDecryptionService, private val pinCodeStore: PinCodeStore, ) : PinCodeManager { @@ -36,16 +38,16 @@ class DefaultPinCodeManager @Inject constructor( } override suspend fun createPinCode(pinCode: String) { - val secretKey = cryptoService.getOrCreateSecretKey(SECRET_KEY_ALIAS) - val encryptedPinCode = cryptoService.encrypt(secretKey, pinCode.toByteArray()).toBase64() + val secretKey = secretKeyProvider.getOrCreateKey(SECRET_KEY_ALIAS) + val encryptedPinCode = encryptionDecryptionService.encrypt(secretKey, pinCode.toByteArray()).toBase64() pinCodeStore.saveEncryptedPinCode(encryptedPinCode) } override suspend fun verifyPinCode(pinCode: String): Boolean { val encryptedPinCode = pinCodeStore.getEncryptedCode() ?: return false return try { - val secretKey = cryptoService.getOrCreateSecretKey(SECRET_KEY_ALIAS) - val decryptedPinCode = cryptoService.decrypt(secretKey, EncryptionResult.fromBase64(encryptedPinCode)) + val secretKey = secretKeyProvider.getOrCreateKey(SECRET_KEY_ALIAS) + val decryptedPinCode = encryptionDecryptionService.decrypt(secretKey, EncryptionResult.fromBase64(encryptedPinCode)) decryptedPinCode.contentEquals(pinCode.toByteArray()) } catch (failure: Throwable) { false diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManagerTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManagerTest.kt new file mode 100644 index 0000000000..a3f29c5351 --- /dev/null +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManagerTest.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.lockscreen.impl.pin + +import com.google.common.truth.Truth.assertThat +import io.element.android.features.lockscreen.impl.pin.storage.InMemoryPinCodeStore +import io.element.android.libraries.cryptography.impl.AESEncryptionDecryptionService +import io.element.android.libraries.cryptography.test.SimpleSecretKeyProvider +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class DefaultPinCodeManagerTest { + + private val pinCodeStore = InMemoryPinCodeStore() + private val secretKeyProvider = SimpleSecretKeyProvider() + private val encryptionDecryptionService = AESEncryptionDecryptionService() + private val pinCodeManager = DefaultPinCodeManager(secretKeyProvider, encryptionDecryptionService, pinCodeStore) + + @Test + fun given_a_pin_code_when_create_and_delete_assert_no_pin_code_left() = runTest { + pinCodeManager.createPinCode("1234") + assertThat(pinCodeManager.isPinCodeAvailable()).isTrue() + pinCodeManager.deletePinCode() + assertThat(pinCodeManager.isPinCodeAvailable()).isFalse() + } + + @Test + fun given_a_pin_code_when_create_and_verify_with_the_same_pin_succeed() = runTest { + val pinCode = "1234" + pinCodeManager.createPinCode(pinCode) + assertThat(pinCodeManager.verifyPinCode(pinCode)).isTrue() + } + + @Test + fun given_a_pin_code_when_create_and_verify_with_a_different_pin_fails() = runTest { + pinCodeManager.createPinCode("1234") + assertThat(pinCodeManager.verifyPinCode("1235")).isFalse() + } +} diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/storage/InMemoryPinCodeStore.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/storage/InMemoryPinCodeStore.kt new file mode 100644 index 0000000000..ed949c08de --- /dev/null +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/storage/InMemoryPinCodeStore.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.lockscreen.impl.pin.storage + +class InMemoryPinCodeStore : PinCodeStore { + + private var pinCode: String? = null + private var remainingAttempts: Int = 3 + + override suspend fun getRemainingPinCodeAttemptsNumber(): Int { + return remainingAttempts + } + + override suspend fun onWrongPin(): Int { + return remainingAttempts-- + } + + override suspend fun resetCounter() { + remainingAttempts = 3 + } + + override fun addListener(listener: PinCodeStore.Listener) { + // no-op + } + + override fun removeListener(listener: PinCodeStore.Listener) { + // no-op + } + + override suspend fun getEncryptedCode(): String? { + return pinCode + } + + override suspend fun saveEncryptedPinCode(pinCode: String) { + this.pinCode = pinCode + } + + override suspend fun deleteEncryptedPinCode() { + pinCode = null + } + + override suspend fun hasPinCode(): Boolean { + return pinCode != null + } +} diff --git a/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/AESEncryptionSpecs.kt b/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/AESEncryptionSpecs.kt new file mode 100644 index 0000000000..d4be4a1f4f --- /dev/null +++ b/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/AESEncryptionSpecs.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.cryptography.api + +import android.security.keystore.KeyProperties + +object AESEncryptionSpecs { + const val BLOCK_MODE = KeyProperties.BLOCK_MODE_GCM + const val PADDINGS = KeyProperties.ENCRYPTION_PADDING_NONE + const val ALGORITHM = KeyProperties.KEY_ALGORITHM_AES + const val KEY_SIZE = 128 + const val CIPHER_TRANSFORMATION = "$ALGORITHM/$BLOCK_MODE/$PADDINGS" +} diff --git a/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/CryptoService.kt b/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/EncryptionDecryptionService.kt similarity index 88% rename from libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/CryptoService.kt rename to libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/EncryptionDecryptionService.kt index a05e11da2a..d670f7b1d2 100644 --- a/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/CryptoService.kt +++ b/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/EncryptionDecryptionService.kt @@ -20,10 +20,9 @@ import javax.crypto.Cipher import javax.crypto.SecretKey /** - * Simple service to provide cryptographic operations. + * Simple service to provide encryption and decryption operations. */ -interface CryptoService { - fun getOrCreateSecretKey(alias: String): SecretKey +interface EncryptionDecryptionService { fun createEncryptionCipher(key: SecretKey): Cipher fun createDecryptionCipher(key: SecretKey, initializationVector: ByteArray): Cipher fun encrypt(key: SecretKey, input: ByteArray): EncryptionResult diff --git a/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/EncryptionResult.kt b/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/EncryptionResult.kt index 10affcfdcb..5aa3a0cbea 100644 --- a/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/EncryptionResult.kt +++ b/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/EncryptionResult.kt @@ -14,10 +14,13 @@ * limitations under the License. */ +@file:OptIn(ExperimentalEncodingApi::class) + package io.element.android.libraries.cryptography.api -import android.util.Base64 import java.nio.ByteBuffer +import kotlin.io.encoding.Base64 +import kotlin.io.encoding.ExperimentalEncodingApi /** * Holds the result of an encryption operation. @@ -34,7 +37,7 @@ class EncryptionResult( .put(initializationVector) .put(encryptedByteArray) .array() - return Base64.encodeToString(cipherTextWithIv, Base64.NO_WRAP) + return Base64.encode(cipherTextWithIv) } companion object { @@ -43,7 +46,7 @@ class EncryptionResult( * @return the [EncryptionResult] from the base64 representation. */ fun fromBase64(base64: String): EncryptionResult { - val cipherTextWithIv = Base64.decode(base64, Base64.NO_WRAP) + val cipherTextWithIv = Base64.decode(base64) val buffer = ByteBuffer.wrap(cipherTextWithIv) val initializationVectorSize = buffer.int val initializationVector = ByteArray(initializationVectorSize) diff --git a/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/SecretKeyProvider.kt b/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/SecretKeyProvider.kt new file mode 100644 index 0000000000..85f57ac07f --- /dev/null +++ b/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/SecretKeyProvider.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.cryptography.api + +import javax.crypto.SecretKey + +/** + * Simple interface to get or create a secret key for a given alias. + * Implementation should be able to store the generated key securely. + */ +interface SecretKeyProvider { + fun getOrCreateKey(alias: String): SecretKey +} diff --git a/libraries/cryptography/impl/build.gradle.kts b/libraries/cryptography/impl/build.gradle.kts index fa6f9db7ea..263fecec27 100644 --- a/libraries/cryptography/impl/build.gradle.kts +++ b/libraries/cryptography/impl/build.gradle.kts @@ -29,7 +29,11 @@ anvil { dependencies { anvil(projects.anvilcodegen) + implementation(libs.dagger) implementation(projects.anvilannotations) implementation(projects.libraries.di) implementation(projects.libraries.cryptography.api) + + testImplementation(libs.test.junit) + testImplementation(libs.test.truth) } diff --git a/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/DefaultCryptoService.kt b/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/AESEncryptionDecryptionService.kt similarity index 52% rename from libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/DefaultCryptoService.kt rename to libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/AESEncryptionDecryptionService.kt index dc4f771acf..cf1ea93e3a 100644 --- a/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/DefaultCryptoService.kt +++ b/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/AESEncryptionDecryptionService.kt @@ -16,56 +16,31 @@ package io.element.android.libraries.cryptography.impl -import android.security.keystore.KeyGenParameterSpec -import android.security.keystore.KeyProperties import com.squareup.anvil.annotations.ContributesBinding -import io.element.android.libraries.cryptography.api.CryptoService +import io.element.android.libraries.cryptography.api.AESEncryptionSpecs +import io.element.android.libraries.cryptography.api.EncryptionDecryptionService import io.element.android.libraries.cryptography.api.EncryptionResult import io.element.android.libraries.di.AppScope -import java.security.KeyStore import javax.crypto.Cipher -import javax.crypto.KeyGenerator import javax.crypto.SecretKey import javax.crypto.spec.GCMParameterSpec import javax.inject.Inject -private const val ANDROID_KEYSTORE = "AndroidKeyStore" -private const val ENCRYPTION_BLOCK_MODE = KeyProperties.BLOCK_MODE_GCM -private const val ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_NONE -private const val ENCRYPTION_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES -private const val ENCRYPTION_AES_TRANSFORMATION = "$ENCRYPTION_ALGORITHM/$ENCRYPTION_BLOCK_MODE/$ENCRYPTION_PADDING" - +/** + * Default implementation of [EncryptionDecryptionService] using AES encryption. + */ @ContributesBinding(AppScope::class) -class DefaultCryptoService @Inject constructor() : CryptoService { - - override fun getOrCreateSecretKey(alias: String): SecretKey { - val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE) - val secretKeyEntry = (keyStore.getEntry(alias, null) as? KeyStore.SecretKeyEntry) - ?.secretKey - return if (secretKeyEntry == null) { - val generator = KeyGenerator.getInstance(ENCRYPTION_ALGORITHM, ANDROID_KEYSTORE) - val keyGenSpec = KeyGenParameterSpec.Builder( - alias, - KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT - ) - .setBlockModes(ENCRYPTION_BLOCK_MODE) - .setEncryptionPaddings(ENCRYPTION_PADDING) - .setKeySize(128) - .build() - generator.init(keyGenSpec) - generator.generateKey() - } else secretKeyEntry - } +class AESEncryptionDecryptionService @Inject constructor() : EncryptionDecryptionService { override fun createEncryptionCipher(key: SecretKey): Cipher { - return Cipher.getInstance(ENCRYPTION_AES_TRANSFORMATION).apply { + return Cipher.getInstance(AESEncryptionSpecs.CIPHER_TRANSFORMATION).apply { init(Cipher.ENCRYPT_MODE, key) } } override fun createDecryptionCipher(key: SecretKey, initializationVector: ByteArray): Cipher { val spec = GCMParameterSpec(128, initializationVector) - return Cipher.getInstance(ENCRYPTION_AES_TRANSFORMATION).apply { + return Cipher.getInstance(AESEncryptionSpecs.CIPHER_TRANSFORMATION).apply { init(Cipher.DECRYPT_MODE, key, spec) } } diff --git a/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/KeyStoreSecretKeyProvider.kt b/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/KeyStoreSecretKeyProvider.kt new file mode 100644 index 0000000000..1da8bd7f1b --- /dev/null +++ b/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/KeyStoreSecretKeyProvider.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.cryptography.impl + +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties +import io.element.android.libraries.cryptography.api.AESEncryptionSpecs +import io.element.android.libraries.cryptography.api.SecretKeyProvider +import java.security.KeyStore +import javax.crypto.KeyGenerator +import javax.crypto.SecretKey + +private const val ANDROID_KEYSTORE = "AndroidKeyStore" + +/** + * Default implementation of [SecretKeyProvider] that uses the Android Keystore to store the keys. + * The generated key uses AES algorithm, with a key size of 128 bits, and the GCM block mode. + */ +class KeyStoreSecretKeyProvider : SecretKeyProvider { + + override fun getOrCreateKey(alias: String): SecretKey { + val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE) + val secretKeyEntry = (keyStore.getEntry(alias, null) as? KeyStore.SecretKeyEntry) + ?.secretKey + return if (secretKeyEntry == null) { + val generator = KeyGenerator.getInstance(AESEncryptionSpecs.ALGORITHM, ANDROID_KEYSTORE) + val keyGenSpec = KeyGenParameterSpec.Builder( + alias, + KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT + ) + .setBlockModes(AESEncryptionSpecs.BLOCK_MODE) + .setEncryptionPaddings(AESEncryptionSpecs.PADDINGS) + .setKeySize(AESEncryptionSpecs.KEY_SIZE) + .build() + generator.init(keyGenSpec) + generator.generateKey() + } else { + secretKeyEntry + } + } +} diff --git a/libraries/cryptography/impl/src/test/kotlin/io/element/android/libraries/cryptography/impl/AESEncryptionDecryptionServiceTest.kt b/libraries/cryptography/impl/src/test/kotlin/io/element/android/libraries/cryptography/impl/AESEncryptionDecryptionServiceTest.kt new file mode 100644 index 0000000000..a693e29ef5 --- /dev/null +++ b/libraries/cryptography/impl/src/test/kotlin/io/element/android/libraries/cryptography/impl/AESEncryptionDecryptionServiceTest.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.cryptography.impl + +import android.security.keystore.KeyProperties +import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertThrows +import org.junit.Test +import java.security.GeneralSecurityException +import javax.crypto.KeyGenerator + +class AESEncryptionDecryptionServiceTest { + + private val encryptionDecryptionService = AESEncryptionDecryptionService() + + @Test + fun given_a_valid_key_then_encrypt_decrypt_work() { + val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES) + keyGenerator.init(128) + val key = keyGenerator.generateKey() + val input = "Hello World".toByteArray() + val encryptionResult = encryptionDecryptionService.encrypt(key, input) + val decrypted = encryptionDecryptionService.decrypt(key, encryptionResult) + assertThat(decrypted).isEqualTo(input) + } + + @Test + fun given_a_wrong_key_then_decrypt_fail() { + val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES) + keyGenerator.init(128) + val encryptionKey = keyGenerator.generateKey() + val input = "Hello World".toByteArray() + val encryptionResult = encryptionDecryptionService.encrypt(encryptionKey, input) + val decryptionKey = keyGenerator.generateKey() + assertThrows(GeneralSecurityException::class.java) { + encryptionDecryptionService.decrypt(decryptionKey, encryptionResult) + } + } + +} diff --git a/libraries/cryptography/test/build.gradle.kts b/libraries/cryptography/test/build.gradle.kts new file mode 100644 index 0000000000..3b9074c897 --- /dev/null +++ b/libraries/cryptography/test/build.gradle.kts @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.libraries.cryptography.test" + + dependencies { + api(projects.libraries.cryptography.api) + } +} diff --git a/libraries/cryptography/test/src/main/kotlin/io/element/android/libraries/cryptography/test/SimpleSecretKeyProvider.kt b/libraries/cryptography/test/src/main/kotlin/io/element/android/libraries/cryptography/test/SimpleSecretKeyProvider.kt new file mode 100644 index 0000000000..d06a545d78 --- /dev/null +++ b/libraries/cryptography/test/src/main/kotlin/io/element/android/libraries/cryptography/test/SimpleSecretKeyProvider.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.cryptography.test + +import io.element.android.libraries.cryptography.api.AESEncryptionSpecs +import io.element.android.libraries.cryptography.api.SecretKeyProvider +import javax.crypto.KeyGenerator +import javax.crypto.SecretKey + +class SimpleSecretKeyProvider : SecretKeyProvider { + + private var secretKeyForAlias = HashMap() + + override fun getOrCreateKey(alias: String): SecretKey { + return secretKeyForAlias.getOrPut(alias) { + generateKey() + } + } + + private fun generateKey(): SecretKey { + val keyGenerator = KeyGenerator.getInstance(AESEncryptionSpecs.ALGORITHM) + keyGenerator.init(AESEncryptionSpecs.KEY_SIZE) + return keyGenerator.generateKey() + } +}