@@ -2,6 +2,9 @@ package com.iterable.iterableapi
22
33import android.content.Context
44import android.content.SharedPreferences
5+ import java.util.concurrent.Callable
6+ import java.util.concurrent.Executors
7+ import java.util.concurrent.TimeUnit
58
69class IterableKeychain {
710 companion object {
@@ -10,53 +13,73 @@ class IterableKeychain {
1013 const val KEY_USER_ID = " iterable-user-id"
1114 const val KEY_AUTH_TOKEN = " iterable-auth-token"
1215 private const val PLAINTEXT_SUFFIX = " _plaintext"
16+ private const val CRYPTO_OPERATION_TIMEOUT_MS = 500L
17+ private const val KEY_ENCRYPTION_ENABLED = " iterable-encryption-enabled"
18+
19+ private val cryptoExecutor = Executors .newSingleThreadExecutor()
1320 }
1421
1522 private var sharedPrefs: SharedPreferences
16- internal var encryptor: IterableDataEncryptor
23+ internal var encryptor: IterableDataEncryptor ? = null
1724 private val decryptionFailureHandler: IterableDecryptionFailureHandler ?
25+ private var encryption: Boolean
1826
1927 @JvmOverloads
2028 constructor (
2129 context: Context ,
2230 decryptionFailureHandler: IterableDecryptionFailureHandler ? = null ,
23- migrator: IterableKeychainEncryptedDataMigrator ? = null
31+ migrator: IterableKeychainEncryptedDataMigrator ? = null ,
32+ encryption: Boolean = true
2433 ) {
25- this .decryptionFailureHandler = decryptionFailureHandler
2634 sharedPrefs = context.getSharedPreferences(
2735 IterableConstants .SHARED_PREFS_FILE ,
2836 Context .MODE_PRIVATE
2937 )
30- encryptor = IterableDataEncryptor ()
31- IterableLogger .v( TAG , " SharedPreferences being used with encryption " )
38+ this .decryptionFailureHandler = decryptionFailureHandler
39+ this .encryption = encryption && sharedPrefs.getBoolean( KEY_ENCRYPTION_ENABLED , true )
3240
33- try {
34- val dataMigrator = migrator ? : IterableKeychainEncryptedDataMigrator (context, sharedPrefs, this )
35- if (! dataMigrator.isMigrationCompleted()) {
36- dataMigrator.setMigrationCompletionCallback { error ->
37- error?.let {
38- IterableLogger .w(TAG , " Migration failed" , it)
39- handleDecryptionError(Exception (it))
41+ if (! encryption) {
42+ IterableLogger .v(TAG , " SharedPreferences being used without encryption" )
43+ } else {
44+ encryptor = IterableDataEncryptor ()
45+ IterableLogger .v(TAG , " SharedPreferences being used with encryption" )
46+
47+ try {
48+ val dataMigrator = migrator ? : IterableKeychainEncryptedDataMigrator (context, sharedPrefs, this )
49+ if (! dataMigrator.isMigrationCompleted()) {
50+ dataMigrator.setMigrationCompletionCallback { error ->
51+ error?.let {
52+ IterableLogger .w(TAG , " Migration failed" , it)
53+ handleDecryptionError(Exception (it))
54+ }
4055 }
56+ dataMigrator.attemptMigration()
57+ IterableLogger .v(TAG , " Migration completed" )
4158 }
42- dataMigrator.attemptMigration()
43- IterableLogger .v(TAG , " Migration completed" )
44- }
45- } catch (e: Exception ) {
46- IterableLogger .w(TAG , " Migration failed, clearing data" , e)
47- handleDecryptionError(e)
59+ } catch (e: Exception ) {
60+ IterableLogger .w(TAG , " Migration failed, clearing data" , e)
61+ handleDecryptionError(e)
62+ }
4863 }
4964 }
5065
66+ private fun <T > runWithTimeout (callable : Callable <T >): T {
67+ return cryptoExecutor.submit(callable).get(CRYPTO_OPERATION_TIMEOUT_MS , TimeUnit .MILLISECONDS )
68+ }
69+
5170 private fun handleDecryptionError (e : Exception ? = null) {
52- IterableLogger .w(TAG , " Decryption failed, clearing all data and regenerating key" )
71+ IterableLogger .w(TAG , " Decryption failed, permanently disabling encryption for this device. Please login again." )
72+
73+ // Permanently disable encryption for this device
5374 sharedPrefs.edit()
5475 .remove(KEY_EMAIL )
5576 .remove(KEY_USER_ID )
5677 .remove(KEY_AUTH_TOKEN )
78+ .putBoolean(KEY_ENCRYPTION_ENABLED , false )
5779 .apply ()
5880
59- encryptor.resetKeys()
81+ encryption = false
82+
6083 decryptionFailureHandler?.let { handler ->
6184 val exception = e ? : Exception (" Unknown decryption error" )
6285 try {
@@ -75,13 +98,20 @@ class IterableKeychain {
7598 }
7699
77100 private fun secureGet (key : String ): String? {
78- // First check if it's stored in plaintext
79- if (sharedPrefs.getBoolean(key + PLAINTEXT_SUFFIX , false )) {
101+ val hasPlainText = sharedPrefs.getBoolean(key + PLAINTEXT_SUFFIX , false )
102+ if (! encryption) {
103+ if (hasPlainText) {
104+ return sharedPrefs.getString(key, null )
105+ } else {
106+ return null
107+ }
108+ } else if (hasPlainText) {
80109 return sharedPrefs.getString(key, null )
81110 }
82111
112+ val encryptedValue = sharedPrefs.getString(key, null ) ? : return null
83113 return try {
84- sharedPrefs.getString(key, null ) ?.let { encryptor .decrypt(it) }
114+ encryptor ?.let { runWithTimeout { it .decrypt(encryptedValue) } }
85115 } catch (e: Exception ) {
86116 handleDecryptionError(e)
87117 null
@@ -95,10 +125,18 @@ class IterableKeychain {
95125 return
96126 }
97127
128+ if (! encryption) {
129+ editor.putString(key, value).putBoolean(key + PLAINTEXT_SUFFIX , true ).apply ()
130+ return
131+ }
132+
98133 try {
99- editor.putString(key, encryptor.encrypt(value))
100- .remove(key + PLAINTEXT_SUFFIX )
101- .apply ()
134+ encryptor?.let {
135+ val encrypted = runWithTimeout { it.encrypt(value) }
136+ editor.putString(key, encrypted)
137+ .remove(key + PLAINTEXT_SUFFIX )
138+ .apply ()
139+ }
102140 } catch (e: Exception ) {
103141 handleDecryptionError(e)
104142 editor.putString(key, value)
@@ -115,4 +153,4 @@ class IterableKeychain {
115153
116154 fun getAuthToken () = secureGet(KEY_AUTH_TOKEN )
117155 fun saveAuthToken (authToken : String? ) = secureSave(KEY_AUTH_TOKEN , authToken)
118- }
156+ }
0 commit comments