diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 497ec7cd3f..b475a759cf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -81,6 +81,7 @@ google_firebase_bom = "com.google.firebase:firebase-bom:33.10.0" firebase_appdistribution_gradle = { module = "com.google.firebase:firebase-appdistribution-gradle", version.ref = "firebaseAppDistribution" } autonomousapps_dependencyanalysis_plugin = { module = "com.autonomousapps:dependency-analysis-gradle-plugin", version.ref = "dependencyAnalysis" } ksp_plugin = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" } +google_tink = "com.google.crypto.tink:tink-android:1.16.0" # AndroidX androidx_core = { module = "androidx.core:core", version.ref = "core" } @@ -100,7 +101,6 @@ androidx_browser = "androidx.browser:browser:1.8.0" androidx_lifecycle_runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" } androidx_lifecycle_process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "lifecycle" } androidx_splash = "androidx.core:core-splashscreen:1.0.1" -androidx_security_crypto = "androidx.security:security-crypto:1.1.0-alpha06" androidx_media3_exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3" } androidx_media3_ui = { module = "androidx.media3:media3-ui", version.ref = "media3" } androidx_biometric = "androidx.biometric:biometric-ktx:1.2.0-alpha05" diff --git a/libraries/androidutils/build.gradle.kts b/libraries/androidutils/build.gradle.kts index 02c8061aa2..1aa23a09e8 100644 --- a/libraries/androidutils/build.gradle.kts +++ b/libraries/androidutils/build.gradle.kts @@ -31,7 +31,6 @@ dependencies { implementation(libs.androidx.activity.activity) implementation(libs.androidx.recyclerview) implementation(libs.androidx.exifinterface) - implementation(libs.androidx.security.crypto) api(libs.androidx.browser) testImplementation(projects.tests.testutils) diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/EncryptedFileFactory.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/EncryptedFileFactory.kt deleted file mode 100644 index 64f88e5d50..0000000000 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/EncryptedFileFactory.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.androidutils.file - -import android.content.Context -import androidx.security.crypto.EncryptedFile -import androidx.security.crypto.MasterKey -import java.io.File - -class EncryptedFileFactory( - private val context: Context, -) { - fun create(file: File): EncryptedFile { - // We need to use the same key for all the encrypted files. - val masterKey = MasterKey.Builder(context) - .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) - .build() - return EncryptedFile.Builder( - context, - file, - masterKey, - EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB - ).build() - } -} diff --git a/libraries/encrypted-db/build.gradle.kts b/libraries/encrypted-db/build.gradle.kts index 7e7b5bab84..ea53646cfc 100644 --- a/libraries/encrypted-db/build.gradle.kts +++ b/libraries/encrypted-db/build.gradle.kts @@ -23,7 +23,7 @@ dependencies { implementation(libs.sqldelight.driver.android) implementation(libs.sqlcipher) implementation(libs.sqlite) - implementation(libs.androidx.security.crypto) + implementation(libs.google.tink) implementation(projects.libraries.androidutils) } diff --git a/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/crypto/EncryptedFile.kt b/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/crypto/EncryptedFile.kt new file mode 100644 index 0000000000..9e9f6e1b2f --- /dev/null +++ b/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/crypto/EncryptedFile.kt @@ -0,0 +1,82 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.encrypteddb.crypto + +import android.content.Context +import com.google.crypto.tink.KeyTemplates +import com.google.crypto.tink.RegistryConfiguration +import com.google.crypto.tink.StreamingAead +import com.google.crypto.tink.integration.android.AndroidKeysetManager +import com.google.crypto.tink.streamingaead.StreamingAeadConfig +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream + +/** + * This class is used to write and read encrypted data to/from a file. + * + * It's a simplified version of the same class in [androidx.security.crypto](https://developer.android.com/reference/androidx/security/crypto/package-summary). + * + * It uses hardcoded constants that are used in that library, for backwards compatibility reasons. + */ +internal class EncryptedFile( + private val context: Context, + private val file: File +) { + companion object { + /** + * The file content is encrypted using StreamingAead with AES-GCM, with the file name as associated data. + */ + private const val KEYSET_ENCRYPTION_SCHEME = "AES256_GCM_HKDF_4KB" + + /** + * The keyset is stored in a shared preference file with this name. + */ + private const val KEYSET_PREF_FILE_NAME = "__androidx_security_crypto_encrypted_file_pref__" + + /** + * The keyset is stored in a shared preference with this key, inside the specified file. + */ + private const val KEYSET_ALIAS = "__androidx_security_crypto_encrypted_file_keyset__" + + /** + * The URI referencing the master key in the Android Keystore used to encrypt/decrypt the keyset. + */ + private const val MASTER_KEY_URI = "android-keystore://_androidx_security_master_key_" + } + + private val androidKeysetManager by lazy { + val keysetManagerBuilder = AndroidKeysetManager.Builder() + .withKeyTemplate(KeyTemplates.get(KEYSET_ENCRYPTION_SCHEME)) + .withSharedPref(context, KEYSET_ALIAS, KEYSET_PREF_FILE_NAME) + .withMasterKeyUri(MASTER_KEY_URI) + + keysetManagerBuilder.build() + } + + private val streamingAead: StreamingAead by lazy { + val streamingAeadKeysetHandle = androidKeysetManager.keysetHandle + streamingAeadKeysetHandle.getPrimitive(RegistryConfiguration.get(), StreamingAead::class.java) + } + + init { + StreamingAeadConfig.register() + } + + fun openFileOutput(): FileOutputStream { + val fos = FileOutputStream(file) + val stream = streamingAead.newEncryptingStream(fos, file.name.toByteArray()) + return EncryptedFileOutputStream(fos.fd, stream) + } + + fun openFileInput(): FileInputStream { + val fis = FileInputStream(file) + val stream = streamingAead.newDecryptingStream(fis, file.name.toByteArray()) + return EncryptedFileInputStream(fis.fd, stream) + } +} diff --git a/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/crypto/EncryptedFileInputStream.kt b/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/crypto/EncryptedFileInputStream.kt new file mode 100644 index 0000000000..8ef15f3be5 --- /dev/null +++ b/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/crypto/EncryptedFileInputStream.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.encrypteddb.crypto + +import android.os.Build +import androidx.annotation.RequiresApi +import java.io.FileDescriptor +import java.io.FileInputStream +import java.io.InputStream + +/** + * This class is used to read encrypted data from a file. + * + * It comes directly from [androidx.security.crypto](https://developer.android.com/reference/androidx/security/crypto/package-summary). + */ +internal class EncryptedFileInputStream( + fileDescriptor: FileDescriptor, + private val inputStream: InputStream, +) : FileInputStream(fileDescriptor) { + private val lock = Any() + + override fun read(): Int = inputStream.read() + + override fun read(b: ByteArray?): Int = inputStream.read(b) + + override fun read(b: ByteArray?, off: Int, len: Int): Int = inputStream.read(b, off, len) + + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + override fun readAllBytes(): ByteArray? = inputStream.readAllBytes() + + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + override fun readNBytes(b: ByteArray?, off: Int, len: Int): Int = inputStream.readNBytes(b, off, len) + + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + override fun readNBytes(len: Int): ByteArray? = inputStream.readNBytes(len) + + override fun skip(n: Long): Long = inputStream.skip(n) + + override fun available(): Int = inputStream.available() + + override fun mark(readlimit: Int) = synchronized(lock) { inputStream.mark(readlimit) } + + override fun markSupported(): Boolean = inputStream.markSupported() + + override fun reset() = synchronized(lock) { inputStream.reset() } + + override fun close() = inputStream.close() +} diff --git a/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/crypto/EncryptedFileOutputStream.kt b/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/crypto/EncryptedFileOutputStream.kt new file mode 100644 index 0000000000..539ca0f490 --- /dev/null +++ b/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/crypto/EncryptedFileOutputStream.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.encrypteddb.crypto + +import java.io.FileDescriptor +import java.io.FileOutputStream +import java.io.OutputStream + +/** + * This class is used to write encrypted data to a file. + * + * It comes directly from [androidx.security.crypto](https://developer.android.com/reference/androidx/security/crypto/package-summary). + */ +internal class EncryptedFileOutputStream( + fileDescriptor: FileDescriptor, + private val outputStream: OutputStream +) : FileOutputStream(fileDescriptor) { + override fun write(b: ByteArray?) = outputStream.write(b) + + override fun write(b: ByteArray?, off: Int, len: Int) = outputStream.write(b, off, len) + + override fun write(b: Int) = outputStream.write(b) + + override fun flush() = outputStream.flush() + + override fun close() = outputStream.close() +} diff --git a/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/passphrase/RandomSecretPassphraseProvider.kt b/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/passphrase/RandomSecretPassphraseProvider.kt index 39bd683f27..601dce508a 100644 --- a/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/passphrase/RandomSecretPassphraseProvider.kt +++ b/libraries/encrypted-db/src/main/kotlin/io/element/encrypteddb/passphrase/RandomSecretPassphraseProvider.kt @@ -8,8 +8,7 @@ package io.element.encrypteddb.passphrase import android.content.Context -import androidx.security.crypto.EncryptedFile -import io.element.android.libraries.androidutils.file.EncryptedFileFactory +import io.element.encrypteddb.crypto.EncryptedFile import java.io.File import java.security.SecureRandom @@ -25,7 +24,7 @@ class RandomSecretPassphraseProvider( private val secretSize: Int = 256, ) : PassphraseProvider { override fun getPassphrase(): ByteArray { - val encryptedFile = EncryptedFileFactory(context).create(file) + val encryptedFile = EncryptedFile(context, file) return if (!file.exists()) { val secret = generateSecret() encryptedFile.openFileOutput().use { it.write(secret) } diff --git a/libraries/push/impl/build.gradle.kts b/libraries/push/impl/build.gradle.kts index ef85740f68..1c13a67ae5 100644 --- a/libraries/push/impl/build.gradle.kts +++ b/libraries/push/impl/build.gradle.kts @@ -27,7 +27,6 @@ dependencies { implementation(libs.dagger) implementation(libs.androidx.corektx) implementation(libs.androidx.datastore.preferences) - implementation(libs.androidx.security.crypto) implementation(platform(libs.network.retrofit.bom)) implementation(libs.network.retrofit) implementation(libs.serialization.json) diff --git a/libraries/session-storage/impl/build.gradle.kts b/libraries/session-storage/impl/build.gradle.kts index 22eb2338b6..4d21d63724 100644 --- a/libraries/session-storage/impl/build.gradle.kts +++ b/libraries/session-storage/impl/build.gradle.kts @@ -28,7 +28,6 @@ dependencies { implementation(libs.sqldelight.driver.android) implementation(libs.sqlcipher) implementation(libs.sqlite) - implementation(libs.androidx.security.crypto) implementation(projects.libraries.di) implementation(libs.sqldelight.coroutines)