diff --git a/app/build.gradle b/app/build.gradle index 69d2834e49..be8e991228 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -141,6 +141,8 @@ dependencies { implementation applicationDependencies.photoView implementation applicationDependencies.zxing + implementation applicationDependencies.androidxSecurityCrypto + //Unit/Integration tests dependencies testImplementation unitTestDependencies.junit testImplementation unitTestDependencies.robolectric diff --git a/app/src/main/java/com/anytypeio/anytype/di/main/DataModule.kt b/app/src/main/java/com/anytypeio/anytype/di/main/DataModule.kt index 191fa0794d..d2c35ca81b 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/main/DataModule.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/main/DataModule.kt @@ -2,6 +2,8 @@ package com.anytypeio.anytype.di.main import android.content.Context import android.content.SharedPreferences +import androidx.security.crypto.EncryptedSharedPreferences +import androidx.security.crypto.MasterKeys import com.anytypeio.anytype.data.auth.repo.* import com.anytypeio.anytype.data.auth.repo.block.BlockDataRepository import com.anytypeio.anytype.data.auth.repo.block.BlockDataStoreFactory @@ -27,6 +29,7 @@ import com.anytypeio.anytype.persistence.repo.DefaultAuthCache import com.anytypeio.anytype.persistence.repo.DefaultDebugSettingsCache import dagger.Module import dagger.Provides +import javax.inject.Named import javax.inject.Singleton @Module @@ -80,11 +83,13 @@ object DataModule { @Singleton fun provideAuthCache( db: AnytypeDatabase, - prefs: SharedPreferences + @Named("default") defaultPrefs: SharedPreferences, + @Named("encrypted") encryptedPrefs: SharedPreferences ): AuthCache { return DefaultAuthCache( db = db, - prefs = prefs + defaultPrefs = defaultPrefs, + encryptedPrefs = encryptedPrefs ) } @@ -92,7 +97,7 @@ object DataModule { @Provides @Singleton fun provideDebugSettingsCache( - prefs: SharedPreferences + @Named("default") prefs: SharedPreferences, ): DebugSettingsCache { return DefaultDebugSettingsCache( prefs = prefs @@ -120,10 +125,25 @@ object DataModule { @JvmStatic @Provides @Singleton + @Named("default") fun provideSharedPreferences(context: Context): SharedPreferences { return context.getSharedPreferences("prefs", Context.MODE_PRIVATE) } + @JvmStatic + @Provides + @Singleton + @Named("encrypted") + fun provideEncryptedSharedPreferences( + context: Context + ): SharedPreferences = EncryptedSharedPreferences.create( + "encrypted_prefs", + MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC), + context, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM + ) + @JvmStatic @Provides @Singleton diff --git a/dependencies.gradle b/dependencies.gradle index 10645e541a..2123aeb6ce 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -15,6 +15,7 @@ ext { androidx_core_ktx_version = '1.5.0-alpha02' androidx_test_core_version = '1.2.0' androidx_core_testing_version = '2.1.0' + androidx_security_crypto_version = '1.0.0-rc03' appcompat_version = '1.2.0' constraintLayout_version = '2.0.4' recyclerview_version = '1.2.0-alpha06' @@ -130,7 +131,8 @@ ext { kotlinxSerializationJson: "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinx_serialization_json_version", crashlytics: "com.google.firebase:firebase-crashlytics:$crashlytics_version", firebaseCore: "com.google.firebase:firebase-core:$firebase_core_version", - shimmerLayout: "com.facebook.shimmer:shimmer:$shimmer_layout_version" + shimmerLayout: "com.facebook.shimmer:shimmer:$shimmer_layout_version", + androidxSecurityCrypto: "androidx.security:security-crypto:$androidx_security_crypto_version" ] unitTesting = [ diff --git a/persistence/build.gradle b/persistence/build.gradle index a4d884c487..b3f1ba2f8e 100644 --- a/persistence/build.gradle +++ b/persistence/build.gradle @@ -23,7 +23,6 @@ android { } dependencies { - implementation project(':data') def applicationDependencies = rootProject.ext.mainApplication diff --git a/persistence/src/main/java/com/anytypeio/anytype/persistence/db/AnytypeDatabase.kt b/persistence/src/main/java/com/anytypeio/anytype/persistence/db/AnytypeDatabase.kt index 3465a90200..bd7e76bfbb 100644 --- a/persistence/src/main/java/com/anytypeio/anytype/persistence/db/AnytypeDatabase.kt +++ b/persistence/src/main/java/com/anytypeio/anytype/persistence/db/AnytypeDatabase.kt @@ -25,5 +25,4 @@ abstract class AnytypeDatabase : RoomDatabase() { Config.DATABASE_NAME ).build() } - } \ No newline at end of file diff --git a/persistence/src/main/java/com/anytypeio/anytype/persistence/repo/DefaultAuthCache.kt b/persistence/src/main/java/com/anytypeio/anytype/persistence/repo/DefaultAuthCache.kt index 0f02e4d3b2..0760fa0745 100644 --- a/persistence/src/main/java/com/anytypeio/anytype/persistence/repo/DefaultAuthCache.kt +++ b/persistence/src/main/java/com/anytypeio/anytype/persistence/repo/DefaultAuthCache.kt @@ -9,7 +9,8 @@ import com.anytypeio.anytype.persistence.mapper.toTable class DefaultAuthCache( private val db: AnytypeDatabase, - private val prefs: SharedPreferences + private val defaultPrefs: SharedPreferences, + private val encryptedPrefs: SharedPreferences ) : AuthCache { override suspend fun saveAccount(account: AccountEntity) { @@ -25,30 +26,66 @@ class DefaultAuthCache( ?: throw IllegalStateException("Account with the following id not found: $id") } + /** + * N.B. Migrating sensitive data from default to encrypted prefs. + */ override suspend fun getCurrentAccountId(): String { - val id: String? = prefs.getString(CURRENT_ACCOUNT_ID_KEY, null) - return id ?: throw IllegalStateException("Current account not set") + val nonEncryptedId = defaultPrefs.getString(CURRENT_ACCOUNT_ID_KEY, null) + return if (nonEncryptedId != null) { + encryptedPrefs.edit().putString(CURRENT_ACCOUNT_ID_KEY, nonEncryptedId).apply() + defaultPrefs.edit().remove(CURRENT_ACCOUNT_ID_KEY).apply() + nonEncryptedId + } else { + val encryptedId = encryptedPrefs.getString(CURRENT_ACCOUNT_ID_KEY, null) + encryptedId ?: throw IllegalStateException("Current account not set") + } } + /** + * N.B. Migrating sensitive data from default to encrypted prefs. + */ override suspend fun saveMnemonic(mnemonic: String) { - prefs.edit().putString(MNEMONIC_KEY, mnemonic).apply() + defaultPrefs.edit().remove(MNEMONIC_KEY).apply() + encryptedPrefs.edit().putString(MNEMONIC_KEY, mnemonic).apply() } + /** + * N.B. Migrating sensitive data from default to encrypted prefs. + */ override suspend fun getMnemonic(): String { - return prefs.getString(MNEMONIC_KEY, null) - ?: throw IllegalStateException("Mnemonic is missing.") + val nonEncryptedMnemonic = defaultPrefs.getString(MNEMONIC_KEY, null) + return if (nonEncryptedMnemonic != null) { + encryptedPrefs.edit().putString(MNEMONIC_KEY, nonEncryptedMnemonic).apply() + defaultPrefs.edit().remove(MNEMONIC_KEY).apply() + nonEncryptedMnemonic + } else { + val encryptedMnemonic = encryptedPrefs.getString(MNEMONIC_KEY, null) + encryptedMnemonic ?: throw IllegalStateException("Mnemonic is missing") + } } override suspend fun logout() { db.accountDao().clear() - prefs.edit().putString(MNEMONIC_KEY, null).apply() + defaultPrefs + .edit() + .remove(MNEMONIC_KEY) + .remove(CURRENT_ACCOUNT_ID_KEY) + .apply() + encryptedPrefs + .edit() + .remove(MNEMONIC_KEY) + .remove(CURRENT_ACCOUNT_ID_KEY) + .apply() } override suspend fun getAccounts() = db.accountDao().getAccounts().map { it.toEntity() } - + /** + * N.B. Migrating sensitive data from default to encrypted prefs. + */ override suspend fun setCurrentAccount(id: String) { - prefs.edit().putString(CURRENT_ACCOUNT_ID_KEY, id).apply() + defaultPrefs.edit().remove(CURRENT_ACCOUNT_ID_KEY).apply() + encryptedPrefs.edit().putString(CURRENT_ACCOUNT_ID_KEY, id).apply() } companion object {