diff --git a/cryptography/build.gradle.kts b/cryptography/build.gradle.kts index de7374b2d3c..f81ba0df722 100644 --- a/cryptography/build.gradle.kts +++ b/cryptography/build.gradle.kts @@ -100,7 +100,7 @@ kotlin { addCommonKotlinJvmSourceDir() dependencies { implementation(libs.cryptoboxAndroid) - implementation(libs.javaxCrypto) + implementation(libs.androidCrypto) implementation(libs.coreCryptoAndroid) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 23b624b3f57..ec085e6ec08 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,6 @@ compose-ui = "1.3.2" compose-material = "1.3.1" cryptobox4j = "1.3.0" cryptobox-android = "1.1.3" -javax-crypto = "1.1.0-alpha06" android-security = "1.1.0-alpha06" ktor = "2.3.1" okio = "3.2.0" @@ -92,7 +91,6 @@ composeTooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "com coroutinesAndroid = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" } ktor = { module = "io.ktor:ktor-client-android", version.ref = "ktor" } paging3 = { module = "androidx.paging:paging-runtime", version.ref = "android-paging3" } -securityCrypto = { module = "androidx.security:security-crypto", version.ref = "android-security" } desugarJdkLibs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar-jdk" } annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" } @@ -114,7 +112,7 @@ coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", ve # cryptobox and crypto dependencies cryptoboxAndroid = { module = "com.wire:cryptobox-android", version.ref = "cryptobox-android" } cryptobox4j = { module = "com.wire:cryptobox4j", version.ref = "cryptobox4j" } -javaxCrypto = { module = "androidx.security:security-crypto-ktx", version.ref = "javax-crypto" } +androidCrypto = { module = "androidx.security:security-crypto-ktx", version.ref = "android-security" } coreCrypto = { module = "com.wire:core-crypto", version.ref = "core-crypto-multiplatform" } coreCryptoJvm = { module = "com.wire:core-crypto-jvm", version.ref = "core-crypto" } coreCryptoAndroid = { module = "com.wire:core-crypto-android", version.ref = "core-crypto" } diff --git a/persistence/build.gradle.kts b/persistence/build.gradle.kts index 5336dd89db6..92ae4d931d9 100644 --- a/persistence/build.gradle.kts +++ b/persistence/build.gradle.kts @@ -100,7 +100,7 @@ kotlin { val jsTest by getting val androidMain by getting { dependencies { - implementation(libs.securityCrypto) + implementation(libs.androidCrypto) implementation(libs.sqldelight.androidDriver) implementation(libs.sqldelight.androidxPaging) implementation(libs.paging3) diff --git a/persistence/src/androidInstrumentedTest/kotlin/com/wire/kalium/persistence/kmmSettings/EncryptedSettingsBuilderTest.kt b/persistence/src/androidInstrumentedTest/kotlin/com/wire/kalium/persistence/kmmSettings/EncryptedSettingsBuilderTest.kt index aa4ad1e1233..0043371c2ec 100644 --- a/persistence/src/androidInstrumentedTest/kotlin/com/wire/kalium/persistence/kmmSettings/EncryptedSettingsBuilderTest.kt +++ b/persistence/src/androidInstrumentedTest/kotlin/com/wire/kalium/persistence/kmmSettings/EncryptedSettingsBuilderTest.kt @@ -19,13 +19,11 @@ package com.wire.kalium.persistence.kmmSettings import android.content.Context import androidx.test.core.app.ApplicationProvider -import com.russhwolf.settings.set import com.wire.kalium.persistence.dao.UserIDEntity import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch -import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.advanceUntilIdle @@ -41,7 +39,6 @@ import kotlin.test.Test class EncryptedSettingsBuilderTest { private val coroutineDispatcher = UnconfinedTestDispatcher() - private val testScope = TestScope(coroutineDispatcher) @BeforeTest fun before() { @@ -50,25 +47,23 @@ class EncryptedSettingsBuilderTest { @Ignore @Test - fun givenShouldEncryptDataIsTrue_whenEncryptingData_thenShouldEncryptWithoutFailing() = runTest { + fun givenShouldEncryptDataIsTrue_whenEncryptingData_thenShouldEncryptWithoutFailing() = runTest(coroutineDispatcher) { val context = ApplicationProvider.getApplicationContext() - (1..500).map { - testScope.launch { + (1..5000).map { + launch() { EncryptedSettingsBuilder.build( options = SettingOptions.UserSettings( shouldEncryptData = true, userIDEntity = UserIDEntity( - value = "userValue", + value = "userValue$it", domain = "domainValue" ) ), param = EncryptedSettingsPlatformParam(context) - ).also { - it["key$it"] = "value$it" - } + ) } }.joinAll() - testScope.advanceUntilIdle() + advanceUntilIdle() } @AfterTest diff --git a/persistence/src/androidMain/kotlin/com/wire/kalium/persistence/kmmSettings/EncryptedSettingsHolder.kt b/persistence/src/androidMain/kotlin/com/wire/kalium/persistence/kmmSettings/EncryptedSettingsHolder.kt index 36495747c91..f743dd3cf9a 100644 --- a/persistence/src/androidMain/kotlin/com/wire/kalium/persistence/kmmSettings/EncryptedSettingsHolder.kt +++ b/persistence/src/androidMain/kotlin/com/wire/kalium/persistence/kmmSettings/EncryptedSettingsHolder.kt @@ -19,6 +19,7 @@ package com.wire.kalium.persistence.kmmSettings import android.content.Context +import android.content.SharedPreferences import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.MasterKey import com.russhwolf.settings.Settings @@ -30,7 +31,10 @@ private fun SettingOptions.keyAlias(): String = when (this) { } internal actual object EncryptedSettingsBuilder { - private fun getOrCreateMasterKey(context: Context, keyAlias: String = MasterKey.DEFAULT_MASTER_KEY_ALIAS): MasterKey = + private fun getOrCreateMasterKey( + context: Context, + keyAlias: String + ): MasterKey = MasterKey .Builder(context, keyAlias) .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) @@ -41,19 +45,35 @@ internal actual object EncryptedSettingsBuilder { options: SettingOptions, param: EncryptedSettingsPlatformParam ): Settings = synchronized(this) { - SharedPreferencesSettings( - if (options.shouldEncryptData) { - EncryptedSharedPreferences.create( - param.appContext, - options.fileName, - getOrCreateMasterKey(param.appContext, options.keyAlias()), - EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, - EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM, - ) - } else { - param.appContext.getSharedPreferences(options.fileName, Context.MODE_PRIVATE) - }, false - ) + val settings = if (options.shouldEncryptData) { + encryptedSharedPref(options, param, false) + } else { + param.appContext.getSharedPreferences(options.fileName, Context.MODE_PRIVATE) + } + SharedPreferencesSettings(settings, false) + } + + private fun encryptedSharedPref( + options: SettingOptions, + param: EncryptedSettingsPlatformParam, + isRetry: Boolean + ): SharedPreferences { + @Suppress("TooGenericExceptionCaught") + return try { + val masterKey = getOrCreateMasterKey(param.appContext, options.keyAlias()) + EncryptedSharedPreferences.create( + param.appContext, + options.fileName, + masterKey, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM + ) + } catch (e: Exception) { + if (isRetry) { + throw e + } + encryptedSharedPref(options, param, true) + } } } diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/util/JsonSerializer.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/util/JsonSerializer.kt index cd1a0e51bc1..56e704b7a99 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/util/JsonSerializer.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/util/JsonSerializer.kt @@ -18,10 +18,12 @@ package com.wire.kalium.persistence.util +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json internal object JsonSerializer { + @OptIn(ExperimentalSerializationApi::class) private val instance: Json by lazy { Json { encodeDefaults = true