@@ -2,6 +2,9 @@ package com.iterable.iterableapi
2
2
3
3
import android.content.Context
4
4
import android.content.SharedPreferences
5
+ import java.util.concurrent.Callable
6
+ import java.util.concurrent.Executors
7
+ import java.util.concurrent.TimeUnit
5
8
6
9
class IterableKeychain {
7
10
companion object {
@@ -10,53 +13,73 @@ class IterableKeychain {
10
13
const val KEY_USER_ID = " iterable-user-id"
11
14
const val KEY_AUTH_TOKEN = " iterable-auth-token"
12
15
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()
13
20
}
14
21
15
22
private var sharedPrefs: SharedPreferences
16
- internal var encryptor: IterableDataEncryptor
23
+ internal var encryptor: IterableDataEncryptor ? = null
17
24
private val decryptionFailureHandler: IterableDecryptionFailureHandler ?
25
+ private var encryption: Boolean
18
26
19
27
@JvmOverloads
20
28
constructor (
21
29
context: Context ,
22
30
decryptionFailureHandler: IterableDecryptionFailureHandler ? = null ,
23
- migrator: IterableKeychainEncryptedDataMigrator ? = null
31
+ migrator: IterableKeychainEncryptedDataMigrator ? = null ,
32
+ encryption: Boolean = true
24
33
) {
25
- this .decryptionFailureHandler = decryptionFailureHandler
26
34
sharedPrefs = context.getSharedPreferences(
27
35
IterableConstants .SHARED_PREFS_FILE ,
28
36
Context .MODE_PRIVATE
29
37
)
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 )
32
40
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
+ }
40
55
}
56
+ dataMigrator.attemptMigration()
57
+ IterableLogger .v(TAG , " Migration completed" )
41
58
}
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
+ }
48
63
}
49
64
}
50
65
66
+ private fun <T > runWithTimeout (callable : Callable <T >): T {
67
+ return cryptoExecutor.submit(callable).get(CRYPTO_OPERATION_TIMEOUT_MS , TimeUnit .MILLISECONDS )
68
+ }
69
+
51
70
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
53
74
sharedPrefs.edit()
54
75
.remove(KEY_EMAIL )
55
76
.remove(KEY_USER_ID )
56
77
.remove(KEY_AUTH_TOKEN )
78
+ .putBoolean(KEY_ENCRYPTION_ENABLED , false )
57
79
.apply ()
58
80
59
- encryptor.resetKeys()
81
+ encryption = false
82
+
60
83
decryptionFailureHandler?.let { handler ->
61
84
val exception = e ? : Exception (" Unknown decryption error" )
62
85
try {
@@ -75,13 +98,20 @@ class IterableKeychain {
75
98
}
76
99
77
100
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) {
80
109
return sharedPrefs.getString(key, null )
81
110
}
82
111
112
+ val encryptedValue = sharedPrefs.getString(key, null ) ? : return null
83
113
return try {
84
- sharedPrefs.getString(key, null ) ?.let { encryptor .decrypt(it) }
114
+ encryptor ?.let { runWithTimeout { it .decrypt(encryptedValue) } }
85
115
} catch (e: Exception ) {
86
116
handleDecryptionError(e)
87
117
null
@@ -95,10 +125,18 @@ class IterableKeychain {
95
125
return
96
126
}
97
127
128
+ if (! encryption) {
129
+ editor.putString(key, value).putBoolean(key + PLAINTEXT_SUFFIX , true ).apply ()
130
+ return
131
+ }
132
+
98
133
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
+ }
102
140
} catch (e: Exception ) {
103
141
handleDecryptionError(e)
104
142
editor.putString(key, value)
@@ -115,4 +153,4 @@ class IterableKeychain {
115
153
116
154
fun getAuthToken () = secureGet(KEY_AUTH_TOKEN )
117
155
fun saveAuthToken (authToken : String? ) = secureSave(KEY_AUTH_TOKEN , authToken)
118
- }
156
+ }
0 commit comments