From e5dd0330a7a9aedc2ce79cbef73b02bb7df85b1d Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 17 Oct 2023 14:04:57 +0200 Subject: [PATCH] Crypto: add a small cryptography library module with CipherFactory --- gradle/libs.versions.toml | 10 +++ libraries/cryptography/api/build.gradle.kts | 23 ++++++ .../cryptography/api/CipherFactory.kt | 40 ++++++++++ libraries/cryptography/impl/build.gradle.kts | 35 +++++++++ .../impl/KeyStoreCipherFactory.kt | 76 +++++++++++++++++++ settings.gradle.kts | 2 + 6 files changed, 186 insertions(+) create mode 100644 libraries/cryptography/api/build.gradle.kts create mode 100644 libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/CipherFactory.kt create mode 100644 libraries/cryptography/impl/build.gradle.kts create mode 100644 libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/KeyStoreCipherFactory.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7071a98879..15cf29dd4f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -57,6 +57,11 @@ autoservice = "1.1.1" # quality detekt = "1.23.1" dependencygraph = "0.12" +junit = "4.13.2" +androidx-test-ext-junit = "1.1.5" +espresso-core = "3.5.1" +appcompat = "1.6.1" +material = "1.9.0" [libraries] # Project @@ -184,6 +189,11 @@ google_autoservice_annotations = { module = "com.google.auto.service:auto-servic # value of `composecompiler` (which is used to set composeOptions.kotlinCompilerExtensionVersion. # See https://github.com/renovatebot/renovate/issues/18354 android_composeCompiler = { module = "androidx.compose.compiler:compiler", version.ref = "composecompiler" } +junit = { group = "junit", name = "junit", version.ref = "junit" } +androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" } +espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" } +appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } +material = { group = "com.google.android.material", name = "material", version.ref = "material" } [bundles] diff --git a/libraries/cryptography/api/build.gradle.kts b/libraries/cryptography/api/build.gradle.kts new file mode 100644 index 0000000000..e8cee5dbd6 --- /dev/null +++ b/libraries/cryptography/api/build.gradle.kts @@ -0,0 +1,23 @@ +/* + * 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.api" +} diff --git a/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/CipherFactory.kt b/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/CipherFactory.kt new file mode 100644 index 0000000000..0eec6e9fa9 --- /dev/null +++ b/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/CipherFactory.kt @@ -0,0 +1,40 @@ +/* + * 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.Cipher + +/** + * Factory to create [Cipher] instances for encryption and decryption. + * The implementation should use a secure way to store the keys. + */ +interface CipherFactory { + /** + * Create a [Cipher] instance for encryption. + * @param alias the alias of the key used for encryption. + * @return the [Cipher] instance. + */ + fun createEncryptionCipher(alias: String): Cipher + + /** + * Create a [Cipher] instance for decryption. + * @param alias the alias of the key used for encryption. + * @param initializationVector the initialization vector used for encryption. + * @return the [Cipher] instance. + */ + fun createDecryptionCipher(alias: String, initializationVector: ByteArray): Cipher +} diff --git a/libraries/cryptography/impl/build.gradle.kts b/libraries/cryptography/impl/build.gradle.kts new file mode 100644 index 0000000000..fa6f9db7ea --- /dev/null +++ b/libraries/cryptography/impl/build.gradle.kts @@ -0,0 +1,35 @@ +/* + * 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") + alias(libs.plugins.anvil) +} + +android { + namespace = "io.element.android.libraries.cryptography.impl" +} + +anvil { + generateDaggerFactories.set(true) +} + +dependencies { + anvil(projects.anvilcodegen) + implementation(projects.anvilannotations) + implementation(projects.libraries.di) + implementation(projects.libraries.cryptography.api) +} diff --git a/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/KeyStoreCipherFactory.kt b/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/KeyStoreCipherFactory.kt new file mode 100644 index 0000000000..eb88b66239 --- /dev/null +++ b/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/KeyStoreCipherFactory.kt @@ -0,0 +1,76 @@ +/* + * 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 com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.cryptography.api.CipherFactory +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" + +/** + * Implementation of [CipherFactory] that uses the Android Keystore to store the keys. + */ +@ContributesBinding(AppScope::class) +class KeyStoreCipherFactory @Inject constructor() : CipherFactory { + + override fun createEncryptionCipher(alias: String): Cipher { + val cipher = Cipher.getInstance(ENCRYPTION_AES_TRANSFORMATION) + val secretKey = getOrGenerateKeyForAlias(alias) + cipher.init(Cipher.ENCRYPT_MODE, secretKey) + return cipher + } + + override fun createDecryptionCipher(alias: String, initializationVector: ByteArray): Cipher { + val cipher = Cipher.getInstance(ENCRYPTION_AES_TRANSFORMATION) + val secretKey = getOrGenerateKeyForAlias(alias) + val spec = GCMParameterSpec(128, initializationVector) + cipher.init(Cipher.DECRYPT_MODE, secretKey, spec) + return cipher + } + + private fun getOrGenerateKeyForAlias(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 + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 105befcd04..590089adc6 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,7 @@ import java.net.URI +include(":libraries:cryptography:api") + /* * Copyright (c) 2022 New Vector Ltd *