diff --git a/app/src/androidTest/java/de/adorsys/android/securestoragetest/SecureStorageLogicTest.kt b/app/src/androidTest/java/de/adorsys/android/securestoragetest/SecureStorageLogicTest.kt index 67c1f17..8c8804b 100644 --- a/app/src/androidTest/java/de/adorsys/android/securestoragetest/SecureStorageLogicTest.kt +++ b/app/src/androidTest/java/de/adorsys/android/securestoragetest/SecureStorageLogicTest.kt @@ -51,6 +51,76 @@ open class SecureStorageLogicTest : SecureStorageBaseTest() { SecurePreferences.clearAllValues(context) } + @Test + fun testStoreRetrieveAndRemoveStringSetValue() { + val KEY_STRING_SET = "KEY_STRING_SET" + val VALUE_STRING_SET = setOf( + "The wheels on the \uD83D\uDE8C go, Round and round, Round and round, Round and round.", + "The wheels on the \uD83D\uDE8C go Round and round, All through the town. The doors on", + "the \uD83D\uDE8C go, Open and shut ♫, Open and shut ♫, Open and shut." + ) + val context = activityRule.activity.applicationContext + + // Store a String Set value in SecureStorage + SecurePreferences.setValue(context, KEY_STRING_SET, VALUE_STRING_SET) + + // Check if the value exists in SecureStorage + Assert.assertTrue(SecurePreferences.contains(context, KEY_STRING_SET)) + + // Retrieve the previously stored String Set value from the SecureStorage + val retrievedValue: MutableSet = SecurePreferences.getStringSetValue(context, KEY_STRING_SET, setOf()) + + // Check if the retrievedValue is not null + Assert.assertEquals(VALUE_STRING_SET.size, retrievedValue.size) + + // Check if the retrievedValue equals the pre-stored value + Assert.assertEquals(VALUE_STRING_SET, retrievedValue) + + // Remove the String Set value from SecureStorage + SecurePreferences.removeValue(context, KEY_STRING_SET) + + // Check if the String Set value has been removed from SecureStorage + Assert.assertFalse(SecurePreferences.contains(context, KEY_STRING_SET)) + + // Delete keys and clear SecureStorage + SecurePreferences.clearAllValues(context) + } + + @Test + fun testStoreRetrieveAndRemoveStringListValue() { + val KEY_STRING_LIST = "KEY_STRING_LIST" + val VALUE_STRING_LIST = listOf( + "The wheels on the \uD83D\uDE8C go, Round and round, Round and round, Round and round.", + "The wheels on the \uD83D\uDE8C go Round and round, All through the town. The doors on", + "the \uD83D\uDE8C go, Open and shut ♫, Open and shut ♫, Open and shut." + ) + val context = activityRule.activity.applicationContext + + // Store a String Set value in SecureStorage + SecurePreferences.setValue(context, KEY_STRING_LIST, VALUE_STRING_LIST) + + // Check if the value exists in SecureStorage + Assert.assertTrue(SecurePreferences.contains(context, KEY_STRING_LIST)) + + // Retrieve the previously stored String Set value from the SecureStorage + val retrievedValue = SecurePreferences.getStringListValue(context, KEY_STRING_LIST, listOf()) + + // Check if the retrievedValue is not null + Assert.assertEquals(VALUE_STRING_LIST.size, retrievedValue.size) + + // Check if the retrievedValue equals the pre-stored value + Assert.assertEquals(VALUE_STRING_LIST, retrievedValue) + + // Remove the String Set value from SecureStorage + SecurePreferences.removeValue(context, KEY_STRING_LIST) + + // Check if the String Set value has been removed from SecureStorage + Assert.assertFalse(SecurePreferences.contains(context, KEY_STRING_LIST)) + + // Delete keys and clear SecureStorage + SecurePreferences.clearAllValues(context) + } + @Test fun testStoreRetrieveAndRemoveBooleanValue() { val KEY_BOOLEAN = "KEY_BOOLEAN" diff --git a/securestoragelibrary/src/main/java/de/adorsys/android/securestoragelibrary/SecurePreferences.java b/securestoragelibrary/src/main/java/de/adorsys/android/securestoragelibrary/SecurePreferences.java index 58b3550..0401c75 100644 --- a/securestoragelibrary/src/main/java/de/adorsys/android/securestoragelibrary/SecurePreferences.java +++ b/securestoragelibrary/src/main/java/de/adorsys/android/securestoragelibrary/SecurePreferences.java @@ -20,12 +20,15 @@ import android.content.SharedPreferences; import android.text.TextUtils; -import java.util.HashSet; -import java.util.Set; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + import static android.content.Context.MODE_PRIVATE; import static de.adorsys.android.securestoragelibrary.SecureStorageException.ExceptionType.CRYPTO_EXCEPTION; @@ -65,7 +68,7 @@ public static void setValue(@NonNull Context context, } /** - * Takes plain string value, encrypts it and stores it encrypted in the SecureStorage on the Android Device + * Takes plain boolean value, encrypts it and stores it encrypted in the SecureStorage on the Android Device * * @param context Context is used internally * @param key Key used to identify the stored value in SecureStorage @@ -78,7 +81,7 @@ public static void setValue(@NonNull Context context, } /** - * Takes plain string value, encrypts it and stores it encrypted in the SecureStorage on the Android Device + * Takes plain float value, encrypts it and stores it encrypted in the SecureStorage on the Android Device * * @param context Context is used internally * @param key Key used to identify the stored value in SecureStorage @@ -91,7 +94,7 @@ public static void setValue(@NonNull Context context, } /** - * Takes plain string value, encrypts it and stores it encrypted in the SecureStorage on the Android Device + * Takes plain long value, encrypts it and stores it encrypted in the SecureStorage on the Android Device * * @param context Context is used internally * @param key Key used to identify the stored value in SecureStorage @@ -104,7 +107,7 @@ public static void setValue(@NonNull Context context, } /** - * Takes plain string value, encrypts it and stores it encrypted in the SecureStorage on the Android Device + * Takes plain int value, encrypts it and stores it encrypted in the SecureStorage on the Android Device * * @param context Context is used internally * @param key Key used to identify the stored value in SecureStorage @@ -117,21 +120,29 @@ public static void setValue(@NonNull Context context, } /** - * Takes plain string value, encrypts it and stores it encrypted in the SecureStorage on the Android Device + * Takes plain Set<String> value, encrypts it and stores it encrypted in the SecureStorage on the Android Device * * @param context Context is used internally * @param key Key used to identify the stored value in SecureStorage - * @param value Plain Set(type: String) value that will be encrypted and stored in the SecureStorage + * @param value Plain Set<String> value that will be encrypted and stored in the SecureStorage */ public static void setValue(@NonNull Context context, @NonNull String key, @NonNull Set value) throws SecureStorageException { - setValue(context, key + KEY_SET_COUNT_POSTFIX, String.valueOf(value.size())); + setCollectionValue(context, key, value); + } - int i = 0; - for (String s : value) { - setValue(context, key + "_" + (i++), s); - } + /** + * Takes plain List<String> value, encrypts it and stores it encrypted in the SecureStorage on the Android Device + * + * @param context Context is used internally + * @param key Key used to identify the stored value in SecureStorage + * @param value Plain List<String> value that will be encrypted and stored in the SecureStorage + */ + public static void setValue(@NonNull Context context, + @NonNull String key, + @NonNull List value) throws SecureStorageException { + setCollectionValue(context, key, value); } /** @@ -216,29 +227,33 @@ public static int getIntValue(@NonNull Context context, } /** - * Gets encrypted int value for given key from the SecureStorage on the Android Device, decrypts it and returns it + * Gets encrypted Set<String> value for given key from the SecureStorage on the Android Device, decrypts it and returns it * * @param context Context is used internally * @param key Key used to identify the stored value in SecureStorage * @param defValue Default Set(type: String) value that will be returned if the value with given key doesn't exist or an exception is thrown - * @return Decrypted Set(type: String) value associated with given key from SecureStorage + * @return Decrypted Set<String> value associated with given key from SecureStorage */ @NonNull public static Set getStringSetValue(@NonNull Context context, @NonNull String key, @NonNull Set defValue) { - int size = getIntValue(context, key + KEY_SET_COUNT_POSTFIX, -1); - - if (size == -1) { - return defValue; - } - - Set res = new HashSet<>(size); - for (int i = 0; i < size; i++) { - res.add(getStringValue(context, key + "_" + i, "")); - } + return getStringCollectionValue(context, key, defValue, new LinkedHashSet<>()); + } - return res; + /** + * Gets encrypted List<String> value for given key from the SecureStorage on the Android Device, decrypts it and returns it + * + * @param context Context is used internally + * @param key Key used to identify the stored value in SecureStorage + * @param defValue Default List<String> value that will be returned if the value with given key doesn't exist or an exception is thrown + * @return Decrypted List<String> value associated with given key from SecureStorage + */ + @NonNull + public static List getStringListValue(@NonNull Context context, + @NonNull String key, + @NonNull List defValue) { + return getStringCollectionValue(context, key, defValue, new ArrayList<>()); } /** @@ -254,7 +269,8 @@ public static boolean contains(@NonNull Context context, SharedPreferences preferences = applicationContext .getSharedPreferences(KEY_SHARED_PREFERENCES_NAME, MODE_PRIVATE); try { - return preferences.contains(key) && KeystoreTool.keyPairExists(); + return (preferences.contains(key) || containsCollection(context, key)) + && KeystoreTool.keyPairExists(); } catch (SecureStorageException e) { return false; } @@ -270,6 +286,7 @@ public static void removeValue(@NonNull Context context, @NonNull String key) { Context applicationContext = context.getApplicationContext(); removeSecureValue(applicationContext, key); + removeSecureCollectionValue(applicationContext, key); } /** @@ -322,6 +339,24 @@ private static void setSecureValue(@NonNull Context context, preferences.edit().putString(key, value).apply(); } + /** + * Takes plain Collection<String> value, encrypts it and stores it encrypted in the SecureStorage on the Android Device + * + * @param context Context is used internally + * @param key Key used to identify the stored value in SecureStorage + * @param value Plain Collection<String> value that will be encrypted and stored in the SecureStorage + */ + private static void setCollectionValue(@NonNull Context context, + @NonNull String key, + @NonNull Collection value) throws SecureStorageException { + setValue(context, key + KEY_SET_COUNT_POSTFIX, String.valueOf(value.size())); + + int i = 0; + for (String s : value) { + setValue(context, key + "_" + (i++), s); + } + } + @Nullable private static String getSecureValue(@NonNull Context context, @NonNull String key) { @@ -330,6 +365,32 @@ private static String getSecureValue(@NonNull Context context, return preferences.getString(key, null); } + /** + * Gets encrypted Collection<String> value for given key from the SecureStorage on the Android Device, decrypts it and returns it + * + * @param context Context is used internally + * @param key Key used to identify the stored value in SecureStorage + * @param defValue Default Set(type: String) value that will be returned if the value with given key doesn't exist or an exception is thrown + * @return Decrypted Collection<String> value associated with given key from SecureStorage + */ + @NonNull + private static > T getStringCollectionValue(@NonNull Context context, + @NonNull String key, + @NonNull T defValue, + @NonNull T retValue) { + int size = getIntValue(context, key + KEY_SET_COUNT_POSTFIX, -1); + + if (size == -1) { + return defValue; + } + + for (int i = 0; i < size; i++) { + retValue.add(getStringValue(context, key + "_" + i, "")); + } + + return retValue; + } + private static void removeSecureValue(@NonNull Context context, @NonNull String key) { SharedPreferences preferences = context @@ -337,6 +398,45 @@ private static void removeSecureValue(@NonNull Context context, preferences.edit().remove(key).apply(); } + private static void removeSecureCollectionValue(@NonNull Context context, + @NonNull String key) { + int size = getIntValue(context, key + KEY_SET_COUNT_POSTFIX, -1); + + if (size != -1) { + for (int i = 0; i < size; i++) { + removeSecureValue(context, key + "_" + i); + } + removeSecureValue(context,key + KEY_SET_COUNT_POSTFIX); + } + } + + /** + * Checks if SecureStorage contains a String Set value for the given key. Since sets are stored + * under multiple keys, this verifies that each expected key is present but does not verify the + * value types of those keys. + * + * @param context Context is used internally + * @param key Key used to identify the stored value in SecureStorage + * @return True if the keys for the set value exist in SecureStorage, otherwise false + */ + private static boolean containsCollection(@NonNull Context context, + @NonNull String key) { + Context applicationContext = context.getApplicationContext(); + SharedPreferences preferences = applicationContext + .getSharedPreferences(KEY_SHARED_PREFERENCES_NAME, MODE_PRIVATE); + + int size = getIntValue(context, key + KEY_SET_COUNT_POSTFIX, -1); + + if (size == -1) { + return false; + } + + for (int i = 0; i < size; i++) { + if (!preferences.contains(key + "_" + i)) return false; + } + return true; + } + private static void clearAllSecureValues(@NonNull Context context) { SharedPreferences preferences = context .getSharedPreferences(KEY_SHARED_PREFERENCES_NAME, MODE_PRIVATE);