Skip to content

Commit 253bef2

Browse files
committed
refactor: Simplify logic
1 parent c66b92c commit 253bef2

9 files changed

Lines changed: 458 additions & 1145 deletions

File tree

app/src/main/java/uk/gov/android/securestore/RetrievalEventV2.kt

Lines changed: 0 additions & 29 deletions
This file was deleted.

app/src/main/java/uk/gov/android/securestore/SecureStoreAsyncV2.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ interface SecureStoreAsyncV2 {
5454
*/
5555
suspend fun retrieve(
5656
vararg key: String,
57-
): RetrievalEventV2
57+
): Map<String, String?>
5858

5959
/**
6060
* Access the data for a given key when authentication is required; access control level is not OPEN
@@ -69,7 +69,7 @@ interface SecureStoreAsyncV2 {
6969
vararg key: String,
7070
authPromptConfig: AuthenticatorPromptConfiguration,
7171
context: FragmentActivity,
72-
): RetrievalEventV2
72+
): Map<String, String?>
7373

7474
/**
7575
* Check if a certain key exists in the store

app/src/main/java/uk/gov/android/securestore/SharedPrefsStoreAsyncV2.kt

Lines changed: 64 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import uk.gov.android.securestore.authentication.AuthenticatorPromptConfiguratio
1010
import uk.gov.android.securestore.authentication.UserAuthenticator
1111
import uk.gov.android.securestore.crypto.HybridCryptoManagerAsync
1212
import uk.gov.android.securestore.crypto.HybridCryptoManagerAsyncImpl
13-
import uk.gov.android.securestore.error.ErrorTypeHandlerV2
1413
import uk.gov.android.securestore.error.SecureStorageErrorV2
15-
import uk.gov.android.securestore.error.SecureStoreErrorTypeV2
14+
import uk.gov.android.securestore.error.SecureStorageErrorV2.Companion.mapToSecureStorageError
15+
import kotlin.coroutines.resumeWithException
1616
import kotlin.coroutines.suspendCoroutine
1717

1818
@Suppress("TooGenericExceptionCaught", "TooManyFunctions")
@@ -22,7 +22,6 @@ class SharedPrefsStoreAsyncV2(
2222
) : SecureStoreAsyncV2 {
2323
private var configurationAsync: SecureStorageConfigurationAsync? = null
2424
private var sharedPrefs: SharedPreferences? = null
25-
private val errorHandler = ErrorTypeHandlerV2
2625

2726
override fun init(
2827
context: Context,
@@ -46,7 +45,7 @@ class SharedPrefsStoreAsyncV2(
4645
}
4746
result.data
4847
} catch (e: Exception) {
49-
throw SecureStorageErrorV2(e)
48+
throw e.mapToSecureStorageError()
5049
}
5150
}
5251

@@ -63,96 +62,53 @@ class SharedPrefsStoreAsyncV2(
6362
try {
6463
hybridCryptoManagerAsync.deleteKey()
6564
} catch (e: Exception) {
66-
throw SecureStorageErrorV2(e)
65+
throw e.mapToSecureStorageError()
6766
}
6867
}
6968

7069
override suspend fun retrieve(
7170
vararg key: String,
72-
): RetrievalEventV2 {
71+
): Map<String, String?> {
7372
return configurationAsync?.let { configuration ->
7473
if (configuration.accessControlLevel != AccessControlLevel.OPEN) {
75-
RetrievalEventV2.Failed(
76-
SecureStoreErrorTypeV2.RECOVERABLE,
77-
"Access control level must be OPEN to use this retrieve method",
78-
)
74+
throw SecureStorageErrorV2(Exception(REQUIRE_OPEN_ACCESS_LEVEL))
7975
} else {
8076
try {
81-
val results = handleResults(*key)
82-
RetrievalEventV2.Success(results)
83-
} catch (e: SecureStorageErrorV2) {
84-
RetrievalEventV2.Failed(
85-
errorHandler.getErrorType(e),
86-
e.message,
87-
)
77+
handleResults(*key)
78+
} catch (e: Exception) {
79+
throw e.mapToSecureStorageError()
8880
}
8981
}
90-
} ?: RetrievalEventV2.Failed(
91-
SecureStoreErrorTypeV2.RECOVERABLE,
92-
"Must call init on SecureStore first!",
93-
)
82+
} ?: throw SecureStorageErrorV2(INIT_ERROR)
9483
}
9584

96-
@Suppress("NestedBlockDepth", "LongMethod")
85+
@Suppress("NestedBlockDepth")
9786
override suspend fun retrieveWithAuthentication(
9887
vararg key: String,
9988
authPromptConfig: AuthenticatorPromptConfiguration,
10089
context: FragmentActivity,
101-
): RetrievalEventV2 {
102-
var result: RetrievalEventV2 = RetrievalEventV2.Failed(
103-
SecureStoreErrorTypeV2.RECOVERABLE,
104-
"Must call init on SecureStore first!",
105-
)
106-
configurationAsync?.let { configuration ->
90+
): Map<String, String?> {
91+
return configurationAsync?.let { configuration ->
92+
// When access control is set to open on the secureStore instance, then redirect consumer to use the retrieve method
10793
if (configuration.accessControlLevel == AccessControlLevel.OPEN) {
108-
result = RetrievalEventV2.Failed(
109-
SecureStoreErrorTypeV2.RECOVERABLE,
110-
"Use retrieve method, access control is set to OPEN, " +
111-
"no need for auth",
112-
)
94+
throw SecureStorageErrorV2(Exception(AUTH_ON_OPEN_STORE_ERROR_MSG))
11395
} else {
96+
// Attempt to surface the Biometrics prompt and handle the result of that
11497
try {
11598
authenticator.init(context)
116-
val authenticateResultSuccess: Boolean = suspendCoroutine { continuation ->
117-
authenticator.authenticate(
118-
configuration.accessControlLevel,
119-
authPromptConfig,
120-
AuthenticatorCallbackHandler(
121-
onSuccess = {
122-
continuation.resumeWith(Result.success(true))
123-
},
124-
onError = { errorCode, errorString ->
125-
result = RetrievalEventV2.Failed(
126-
errorHandler.getErrorType(errorCode),
127-
"$BIOMETRIC_PREFIX$errorCode $errorString",
128-
)
129-
continuation.resumeWith(Result.success(false))
130-
},
131-
onFailure = {
132-
// Do nothing to allow user to try again
133-
},
134-
),
135-
)
136-
}
137-
if (authenticateResultSuccess) {
138-
result = processSafeHandleResultsOnAuthenticateSuccess(*key)
139-
}
140-
} catch (e: SecureStorageErrorV2) {
141-
result = RetrievalEventV2.Failed(
142-
errorHandler.getErrorType(e),
143-
"authenticate call throws SecureStorageError ${e.message}",
144-
)
145-
} catch (e: Exception) {
146-
result = RetrievalEventV2.Failed(
147-
SecureStoreErrorTypeV2.RECOVERABLE,
148-
"authenticate call throws Exception ${e.message}",
149-
)
99+
handleBiometricPrompt(configuration, authPromptConfig)
100+
handleResults(*key)
101+
// Catches errors thrown from the BiometricPrompt onError(...)
102+
} catch (sse: SecureStorageErrorV2) {
103+
throw sse
104+
// Catches any other errors (mainly the java.security and java.crypto fron the KeyStore)
105+
} catch (e: Throwable) {
106+
throw e.mapToSecureStorageError()
150107
} finally {
151108
authenticator.close()
152109
}
153110
}
154-
}
155-
return result
111+
} ?: throw SecureStorageErrorV2(INIT_ERROR)
156112
}
157113

158114
override fun exists(key: String): Boolean {
@@ -164,26 +120,22 @@ class SharedPrefsStoreAsyncV2(
164120
it.edit {
165121
putString(key, value)
166122
}
167-
} ?: throw SecureStorageErrorV2(Exception("You must call init first!"))
123+
} ?: throw INIT_ERROR
168124
}
169125

170126
private suspend fun cryptoDecryptText(
171127
alias: String,
172128
onTextReady: (String?) -> Unit,
173129
) {
174130
sharedPrefs?.let {
175-
try {
176-
val encryptedData = it.getString(alias, null)
177-
val encryptedKey = it.getString(alias + KEY_SUFFIX, null)
178-
if (encryptedData.isNullOrEmpty() || encryptedKey.isNullOrEmpty()) {
179-
onTextReady(null)
180-
} else {
181-
onTextReady(hybridCryptoManagerAsync.decrypt(encryptedData, encryptedKey))
182-
}
183-
} catch (e: Exception) {
184-
throw SecureStorageErrorV2(e)
131+
val encryptedData = it.getString(alias, null)
132+
val encryptedKey = it.getString(alias + KEY_SUFFIX, null)
133+
if (encryptedData.isNullOrEmpty() || encryptedKey.isNullOrEmpty()) {
134+
onTextReady(null)
135+
} else {
136+
onTextReady(hybridCryptoManagerAsync.decrypt(encryptedData, encryptedKey))
185137
}
186-
} ?: throw SecureStorageErrorV2(Exception("You must call init first!"))
138+
} ?: throw INIT_ERROR
187139
}
188140

189141
private suspend fun handleResults(vararg key: String): MutableMap<String, String?> {
@@ -196,23 +148,42 @@ class SharedPrefsStoreAsyncV2(
196148
return results
197149
}
198150

199-
private suspend fun processSafeHandleResultsOnAuthenticateSuccess(
200-
vararg key: String,
201-
): RetrievalEventV2 =
202-
try {
203-
RetrievalEventV2.Success(handleResults(*key))
204-
} catch (e: SecureStorageErrorV2) {
205-
RetrievalEventV2.Failed(
206-
errorHandler.getErrorType(e),
207-
"authenticate call onSuccess callback throws " +
208-
"SecureStorageError ${e.message}",
151+
private suspend fun handleBiometricPrompt(
152+
config: SecureStorageConfigurationAsync,
153+
authPromptConfig: AuthenticatorPromptConfiguration,
154+
) {
155+
suspendCoroutine { continuation ->
156+
authenticator.authenticate(
157+
config.accessControlLevel,
158+
authPromptConfig,
159+
AuthenticatorCallbackHandler(
160+
onSuccess = {
161+
// This just allows for continuation
162+
continuation.resumeWith(Result.success(true))
163+
},
164+
onError = { errorCode, errorString ->
165+
// Continues the coroutine with the error
166+
continuation.resumeWithException(
167+
SecureStorageErrorV2
168+
.getErrorFromBiometricsError(errorCode, errorString),
169+
)
170+
},
171+
onFailure = {
172+
// Do nothing to allow user to try again
173+
},
174+
),
209175
)
210176
}
177+
}
211178

212179
companion object {
213180
// DO NOT CHANGE THIS
214-
private const val KEY_SUFFIX = "Key"
181+
internal const val KEY_SUFFIX = "Key"
215182

216-
private const val BIOMETRIC_PREFIX = "biometric error code "
183+
private val INIT_ERROR = Exception("Must call init on SecureStore first!")
184+
private const val AUTH_ON_OPEN_STORE_ERROR_MSG = "Use retrieve method, access control is" +
185+
" set to OPEN, no need for auth"
186+
private const val REQUIRE_OPEN_ACCESS_LEVEL = "Access control level must be OPEN to use" +
187+
" this retrieve method"
217188
}
218189
}

app/src/main/java/uk/gov/android/securestore/crypto/HybridCryptoManagerAsync.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package uk.gov.android.securestore.crypto
22

33
import kotlinx.coroutines.CoroutineDispatcher
44
import uk.gov.android.securestore.AccessControlLevel
5+
import java.lang.Exception
6+
import java.security.GeneralSecurityException
7+
import kotlin.jvm.Throws
58

69
/**
710
* Interface to handle encryption and decryption of [String] data
@@ -21,6 +24,10 @@ interface HybridCryptoManagerAsync {
2124
*
2225
* @throws [java.security.GeneralSecurityException] if encryption fails
2326
*/
27+
@Throws(
28+
GeneralSecurityException::class,
29+
Exception::class,
30+
)
2431
suspend fun encrypt(
2532
input: String,
2633
): EncryptedData
@@ -33,6 +40,10 @@ interface HybridCryptoManagerAsync {
3340
*
3441
* @throws [java.security.GeneralSecurityException] if decryption fails
3542
*/
43+
@Throws(
44+
GeneralSecurityException::class,
45+
Exception::class,
46+
)
3647
suspend fun decrypt(
3748
encryptedData: String,
3849
encryptedKey: String,
@@ -43,5 +54,9 @@ interface HybridCryptoManagerAsync {
4354
*
4455
* @throws [java.security.KeyStoreException] If Keystore is not initialized or entry not removed
4556
*/
57+
@Throws(
58+
GeneralSecurityException::class,
59+
Exception::class,
60+
)
4661
suspend fun deleteKey()
4762
}

app/src/main/java/uk/gov/android/securestore/crypto/HybridCryptoManagerAsyncImpl.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,16 @@ internal class HybridCryptoManagerAsyncImpl : HybridCryptoManagerAsync {
5454
encryptedData: String,
5555
encryptedKey: String,
5656
): String = withContext(dispatcher) {
57+
println("Decrypting results")
5758
val cipher = Cipher.getInstance(TRANSFORMATION)
5859
val encryptedKeyBytes = Base64.decode(encryptedKey)
5960
val decryptedKey = initCipherAndDecryptKey(
6061
cipher,
6162
encryptedKeyBytes,
6263
)
63-
aesCryptoManager.decrypt(encryptedData, decryptedKey)
64+
val result = aesCryptoManager.decrypt(encryptedData, decryptedKey)
65+
println("Decrypting results: $result")
66+
result
6467
}
6568

6669
private fun initCipherAndDecryptKey(

app/src/main/java/uk/gov/android/securestore/error/ErrorTypeHandlerV2.kt

Lines changed: 0 additions & 49 deletions
This file was deleted.

0 commit comments

Comments
 (0)