mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
DROID-3609 Auth | Fix | Centralize EncryptedSharedPreferences Initialization in KeystoreManager (#2356)
This commit is contained in:
parent
d38ead4273
commit
adc36f1a6b
2 changed files with 90 additions and 31 deletions
|
@ -58,6 +58,7 @@ import com.anytypeio.anytype.persistence.repo.DefaultDebugSettingsCache
|
|||
import com.anytypeio.anytype.persistence.repo.DefaultUserSettingsCache
|
||||
import com.anytypeio.anytype.presentation.util.downloader.UriFileProvider
|
||||
import com.anytypeio.anytype.providers.DefaultUriFileProvider
|
||||
import com.anytypeio.anytype.security.KeystoreManager
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
@ -171,37 +172,8 @@ object DataModule {
|
|||
@Singleton
|
||||
@Named("encrypted")
|
||||
fun provideEncryptedSharedPreferences(
|
||||
context: Context
|
||||
): SharedPreferences = try {
|
||||
initializeEncryptedPrefs(context)
|
||||
} catch (e: GeneralSecurityException) {
|
||||
// https://issuetracker.google.com/issues/164901843
|
||||
Timber.e(e, "Security error while initializing encrypted prefs")
|
||||
// Clearing pre-existing prefs
|
||||
retryInstantiatingEncryptedPrefs(context)
|
||||
} catch (e: Exception) {
|
||||
// https://issuetracker.google.com/issues/164901843
|
||||
Timber.e(e, "Unknown error while initializing encrypted prefs")
|
||||
// Clearing pre-existing prefs
|
||||
retryInstantiatingEncryptedPrefs(context)
|
||||
}
|
||||
|
||||
private fun retryInstantiatingEncryptedPrefs(context: Context): SharedPreferences {
|
||||
context
|
||||
.getSharedPreferences(ENCRYPTED_PREFS_NAME, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
.clear()
|
||||
.commit()
|
||||
return initializeEncryptedPrefs(context)
|
||||
}
|
||||
|
||||
private fun initializeEncryptedPrefs(context: Context) = EncryptedSharedPreferences.create(
|
||||
ENCRYPTED_PREFS_NAME,
|
||||
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
|
||||
context,
|
||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
||||
)
|
||||
keystoreManager: KeystoreManager
|
||||
): SharedPreferences = keystoreManager.initializeEncryptedPreferences()
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
|
@ -350,6 +322,10 @@ object DataModule {
|
|||
localeProvider = localeProvider
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideKeystoreManager(context: Context): KeystoreManager = KeystoreManager(context)
|
||||
|
||||
@Module
|
||||
interface Bindings {
|
||||
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
package com.anytypeio.anytype.security
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
import android.security.KeyStoreException
|
||||
import androidx.security.crypto.EncryptedSharedPreferences
|
||||
import androidx.security.crypto.MasterKeys
|
||||
import com.anytypeio.anytype.di.main.ENCRYPTED_PREFS_NAME
|
||||
import java.security.KeyStore
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Handles initialization and recovery for EncryptedSharedPreferences
|
||||
* using AndroidX Security Crypto (v1.0.0).
|
||||
*
|
||||
* This implementation:
|
||||
* - Uses the default stable MasterKeys setup
|
||||
* - Handles known keystore corruption errors (e.g. InvalidKeyBlob)
|
||||
* - Deletes and regenerates the key only in scoped recovery
|
||||
*
|
||||
* Alias used by MasterKeys.getOrCreate(...) is:
|
||||
* "_androidx_security_master_key_"
|
||||
* (verified from source: https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:security/security-crypto/src/main/java/androidx/security/crypto/MasterKeys.java)
|
||||
*/
|
||||
class KeystoreManager(private val context: Context) {
|
||||
|
||||
companion object {
|
||||
private const val KEYSTORE_PROVIDER = "AndroidKeyStore"
|
||||
private const val DEFAULT_KEY_ALIAS = "_androidx_security_master_key_"
|
||||
}
|
||||
|
||||
fun initializeEncryptedPreferences(): SharedPreferences {
|
||||
return try {
|
||||
EncryptedSharedPreferences.create(
|
||||
ENCRYPTED_PREFS_NAME,
|
||||
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
|
||||
context,
|
||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
return if (isKeystoreCorruptionError(e)) {
|
||||
Timber.e(e, "Keystore key corruption detected. Attempting recovery.")
|
||||
recoverEncryptedPreferences()
|
||||
} else {
|
||||
Timber.e(e, "Unexpected error initializing encrypted prefs. Not recovering.")
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun recoverEncryptedPreferences(): SharedPreferences {
|
||||
|
||||
try {
|
||||
val keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER)
|
||||
keyStore.load(null)
|
||||
if (keyStore.containsAlias(DEFAULT_KEY_ALIAS)) {
|
||||
keyStore.deleteEntry(DEFAULT_KEY_ALIAS)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Failed to delete corrupted keystore alias: $DEFAULT_KEY_ALIAS")
|
||||
}
|
||||
|
||||
context.getSharedPreferences(ENCRYPTED_PREFS_NAME, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
.clear()
|
||||
.commit()
|
||||
|
||||
return EncryptedSharedPreferences.create(
|
||||
ENCRYPTED_PREFS_NAME,
|
||||
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
|
||||
context,
|
||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
||||
)
|
||||
}
|
||||
|
||||
private fun isKeystoreCorruptionError(e: Exception): Boolean {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return false
|
||||
return (e is KeyStoreException && e.message?.contains("Invalid key blob", ignoreCase = true) == true) ||
|
||||
(e.message?.contains("Failed to create operation", ignoreCase = true) == true)
|
||||
}}
|
Loading…
Add table
Add a link
Reference in a new issue