From 284fe2ea2bb812773bcd574ef92350585ab54de4 Mon Sep 17 00:00:00 2001 From: Sumeru Chatterjee Date: Wed, 18 Dec 2024 17:07:41 +0000 Subject: [PATCH] [MOB-9235] Use shared prefs and try to migrate date from encrypted to shared prefs --- .../iterable/iterableapi/IterableKeychain.kt | 109 ++---------------- .../IterableKeychainEncryptedDataMigrator.kt | 79 +++++++++++++ 2 files changed, 88 insertions(+), 100 deletions(-) create mode 100644 iterableapi/src/main/java/com/iterable/iterableapi/IterableKeychainEncryptedDataMigrator.kt diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableKeychain.kt b/iterableapi/src/main/java/com/iterable/iterableapi/IterableKeychain.kt index df9aae23a..9973c30e2 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableKeychain.kt +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableKeychain.kt @@ -7,13 +7,13 @@ import androidx.annotation.RequiresApi import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.MasterKey +import com.iterable.iterableapi.IterableKeychainEncryptedDataMigrator + class IterableKeychain { private val TAG = "IterableKeychain" private var sharedPrefs: SharedPreferences - private val encryptedSharedPrefsFileName = "iterable-encrypted-shared-preferences" - private val emailKey = "iterable-email" private val userIdKey = "iterable-user-id" private val authTokenKey = "iterable-auth-token" @@ -22,61 +22,14 @@ class IterableKeychain { constructor(context: Context, encryptionEnforced: Boolean) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - encryptionEnabled = false - sharedPrefs = context.getSharedPreferences( - IterableConstants.SHARED_PREFS_FILE, - Context.MODE_PRIVATE - ) - IterableLogger.v(TAG, "SharedPreferences being used") - } else { - // See if EncryptedSharedPreferences can be created successfully - try { - val masterKeyAlias = MasterKey.Builder(context) - .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) - .build() - sharedPrefs = EncryptedSharedPreferences.create( - context, - encryptedSharedPrefsFileName, - masterKeyAlias, - EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, - EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM - ) - encryptionEnabled = true - } catch (e: Throwable) { - if (e is Error) { - IterableLogger.e( - TAG, - "EncryptionSharedPreference creation failed with Error. Attempting to continue" - ) - } - - if (encryptionEnforced) { - //TODO: In-memory or similar solution needs to be implemented in the future. - IterableLogger.w( - TAG, - "Encryption is enforced. PII will not be persisted due to EncryptionSharedPreference failure. Email/UserId and Auth token will have to be passed for every app session.", - e - ) - throw e.fillInStackTrace() - } else { - sharedPrefs = context.getSharedPreferences( - IterableConstants.SHARED_PREFS_FILE, - Context.MODE_PRIVATE - ) - IterableLogger.w( - TAG, - "Using SharedPreference as EncryptionSharedPreference creation failed." - ) - encryptionEnabled = false - } - } + sharedPrefs = context.getSharedPreferences( + IterableConstants.SHARED_PREFS_FILE, + Context.MODE_PRIVATE + ) + IterableLogger.v(TAG, "SharedPreferences being used") - //Try to migrate data from SharedPreferences to EncryptedSharedPreferences - if (encryptionEnabled) { - migrateAuthDataFromSharedPrefsToKeychain(context) - } - } + // Attempt migration from encrypted preferences + IterableKeychainEncryptedDataMigrator(context, sharedPrefs).attemptMigration() } fun getEmail(): String? { @@ -108,48 +61,4 @@ class IterableKeychain { .putString(authTokenKey, authToken) .apply() } - - @RequiresApi(api = Build.VERSION_CODES.M) - private fun migrateAuthDataFromSharedPrefsToKeychain(context: Context) { - val oldPrefs: SharedPreferences = context.getSharedPreferences( - IterableConstants.SHARED_PREFS_FILE, - Context.MODE_PRIVATE - ) - val sharedPrefsEmail = oldPrefs.getString(IterableConstants.SHARED_PREFS_EMAIL_KEY, null) - val sharedPrefsUserId = oldPrefs.getString(IterableConstants.SHARED_PREFS_USERID_KEY, null) - val sharedPrefsAuthToken = - oldPrefs.getString(IterableConstants.SHARED_PREFS_AUTH_TOKEN_KEY, null) - val editor: SharedPreferences.Editor = oldPrefs.edit() - if (getEmail() == null && sharedPrefsEmail != null) { - saveEmail(sharedPrefsEmail) - editor.remove(IterableConstants.SHARED_PREFS_EMAIL_KEY) - IterableLogger.v( - TAG, - "UPDATED: migrated email from SharedPreferences to IterableKeychain" - ) - } else if (sharedPrefsEmail != null) { - editor.remove(IterableConstants.SHARED_PREFS_EMAIL_KEY) - } - if (getUserId() == null && sharedPrefsUserId != null) { - saveUserId(sharedPrefsUserId) - editor.remove(IterableConstants.SHARED_PREFS_USERID_KEY) - IterableLogger.v( - TAG, - "UPDATED: migrated userId from SharedPreferences to IterableKeychain" - ) - } else if (sharedPrefsUserId != null) { - editor.remove(IterableConstants.SHARED_PREFS_USERID_KEY) - } - if (getAuthToken() == null && sharedPrefsAuthToken != null) { - saveAuthToken(sharedPrefsAuthToken) - editor.remove(IterableConstants.SHARED_PREFS_AUTH_TOKEN_KEY) - IterableLogger.v( - TAG, - "UPDATED: migrated authToken from SharedPreferences to IterableKeychain" - ) - } else if (sharedPrefsAuthToken != null) { - editor.remove(IterableConstants.SHARED_PREFS_AUTH_TOKEN_KEY) - } - editor.apply() - } } \ No newline at end of file diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableKeychainEncryptedDataMigrator.kt b/iterableapi/src/main/java/com/iterable/iterableapi/IterableKeychainEncryptedDataMigrator.kt new file mode 100644 index 000000000..350befb9c --- /dev/null +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableKeychainEncryptedDataMigrator.kt @@ -0,0 +1,79 @@ +package com.iterable.iterableapi + +import android.content.Context +import android.content.SharedPreferences +import android.os.Build +import androidx.security.crypto.EncryptedSharedPreferences +import androidx.security.crypto.MasterKey + +class IterableKeychainEncryptedDataMigrator( + private val context: Context, + private val sharedPrefs: SharedPreferences +) { + private val TAG = "IterableKeychainMigrator" + + private val encryptedSharedPrefsFileName = "iterable-encrypted-shared-preferences" + private val migrationAttemptedKey = "iterable-encrypted-migration-attempted" + + private val emailKey = "iterable-email" + private val userIdKey = "iterable-user-id" + private val authTokenKey = "iterable-auth-token" + + fun attemptMigration() { + // Skip if migration was already attempted + if (sharedPrefs.getBoolean(migrationAttemptedKey, false)) { + IterableLogger.v(TAG, "Migration was already attempted, skipping") + return + } + + // Mark that we attempted migration + sharedPrefs.edit() + .putBoolean(migrationAttemptedKey, true) + .apply() + + // Only attempt migration on Android M and above + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return + } + + // Run migration in background thread + Thread { + try { + val masterKeyAlias = MasterKey.Builder(context) + .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) + .build() + + val encryptedPrefs = EncryptedSharedPreferences.create( + context, + encryptedSharedPrefsFileName, + masterKeyAlias, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM + ) + + // Migrate data + migrateData(encryptedPrefs) + + // Clear encrypted prefs after successful migration + encryptedPrefs.edit().clear().apply() + + IterableLogger.v(TAG, "Successfully migrated data from encrypted preferences") + } catch (e: Throwable) { + IterableLogger.w(TAG, "Failed to access encrypted preferences, skipping migration", e) + } + }.start() + } + + private fun migrateData(encryptedPrefs: SharedPreferences) { + val email = encryptedPrefs.getString(emailKey, null) + val userId = encryptedPrefs.getString(userIdKey, null) + val authToken = encryptedPrefs.getString(authTokenKey, null) + + // Only migrate non-null values + sharedPrefs.edit().apply { + email?.let { putString(emailKey, it) } + userId?.let { putString(userIdKey, it) } + authToken?.let { putString(authTokenKey, it) } + }.apply() + } +} \ No newline at end of file