From a188cef87e9bebbe390548bae87d0b9b1b3715e9 Mon Sep 17 00:00:00 2001 From: Aleksei Arsenev Date: Fri, 25 Dec 2020 01:27:26 +0300 Subject: [PATCH] fix: Fix library crashing on android 5.x --- android/build.gradle | 15 +++- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../RNEncryptedStorageModuleUnitTest.java | 89 +++++++++++++++++++ .../RNEncryptedStorageModule.java | 19 ++-- 4 files changed, 114 insertions(+), 11 deletions(-) create mode 100644 android/src/androidTest/java/com/emeraldsanto/encryptedstorage/RNEncryptedStorageModuleUnitTest.java diff --git a/android/build.gradle b/android/build.gradle index ea3e672..3e36b36 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -14,7 +14,7 @@ import groovy.json.JsonSlurper def DEFAULT_COMPILE_SDK_VERSION = 29 def DEFAULT_BUILD_TOOLS_VERSION = '28.0.3' -def DEFAULT_MIN_SDK_VERSION = 23 +def DEFAULT_MIN_SDK_VERSION = 21 def DEFAULT_TARGET_SDK_VERSION = 29 def safeExtGet(prop, fallback) { @@ -55,10 +55,15 @@ android { targetSdkVersion safeExtGet('targetSdkVersion', DEFAULT_TARGET_SDK_VERSION) versionCode 41 versionName "3.0.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { abortOnError false } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } repositories { @@ -80,7 +85,13 @@ dependencies { //noinspection GradleDynamicVersion implementation 'com.facebook.react:react-native:+' - implementation "androidx.security:security-crypto:1.1.0-alpha02" + implementation "androidx.security:security-crypto:1.1.0-alpha03" + + testImplementation 'junit:junit:4.13.1' + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + //noinspection GradleDependency + androidTestImplementation 'org.mockito:mockito-android:3.4.6' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' } def configureReactNativePom(def pom) { diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 8013248..b913e23 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip diff --git a/android/src/androidTest/java/com/emeraldsanto/encryptedstorage/RNEncryptedStorageModuleUnitTest.java b/android/src/androidTest/java/com/emeraldsanto/encryptedstorage/RNEncryptedStorageModuleUnitTest.java new file mode 100644 index 0000000..b133209 --- /dev/null +++ b/android/src/androidTest/java/com/emeraldsanto/encryptedstorage/RNEncryptedStorageModuleUnitTest.java @@ -0,0 +1,89 @@ +package com.emeraldsanto.encryptedstorage; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +@RunWith(AndroidJUnit4.class) +public class RNEncryptedStorageModuleUnitTest { + private RNEncryptedStorageModule module; + + @Before + public void setUp() { + module = new RNEncryptedStorageModule(new ReactApplicationContext(InstrumentationRegistry.getInstrumentation().getTargetContext())); + module.clear(mock(Promise.class)); + } + + @Test + public void shouldGetAndSet() { + Promise promise1 = mock(Promise.class); + module.getItem("test", promise1); + verify(promise1).resolve(null); + + Promise promise2 = mock(Promise.class); + module.setItem("test", "asd", promise2); + verify(promise2).resolve("asd"); + + Promise promise3 = mock(Promise.class); + module.getItem("test", promise3); + verify(promise3).resolve("asd"); + } + + @Test + public void shouldRemove() { + Promise promise1 = mock(Promise.class); + module.setItem("test", "asd", promise1); + verify(promise1).resolve("asd"); + + Promise promise2 = mock(Promise.class); + module.getItem("test", promise2); + verify(promise2).resolve("asd"); + + Promise promise3 = mock(Promise.class); + module.removeItem("test", promise3); + verify(promise3).resolve("test"); + + Promise promise4 = mock(Promise.class); + module.getItem("test", promise4); + verify(promise4).resolve(null); + } + + @Test + public void shouldClear() { + Promise promise1 = mock(Promise.class); + module.setItem("test", "asd", promise1); + verify(promise1).resolve("asd"); + + Promise promise2 = mock(Promise.class); + module.getItem("test", promise2); + verify(promise2).resolve("asd"); + + Promise promise3 = mock(Promise.class); + module.clear(promise3); + verify(promise3).resolve(null); + + Promise promise4 = mock(Promise.class); + module.getItem("test", promise4); + verify(promise4).resolve(null); + } + + @Test + public void shouldKeepValuesWhenRecreated() { + Promise promise1 = mock(Promise.class); + module.setItem("test", "asd", promise1); + verify(promise1).resolve("asd"); + + module = new RNEncryptedStorageModule(new ReactApplicationContext(InstrumentationRegistry.getInstrumentation().getTargetContext())); + + Promise promise2 = mock(Promise.class); + module.getItem("test", promise2); + verify(promise2).resolve("asd"); + } +} diff --git a/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStorageModule.java b/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStorageModule.java index 53849ac..68a6c9b 100644 --- a/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStorageModule.java +++ b/android/src/main/java/com/emeraldsanto/encryptedstorage/RNEncryptedStorageModule.java @@ -2,11 +2,9 @@ import android.content.Context; import android.content.SharedPreferences; -import android.os.Build; - +import android.util.Log; import androidx.security.crypto.EncryptedSharedPreferences; -import androidx.security.crypto.MasterKeys; - +import androidx.security.crypto.MasterKey; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; @@ -14,8 +12,8 @@ public class RNEncryptedStorageModule extends ReactContextBaseJavaModule { - private static String NATIVE_MODULE_NAME = "RNEncryptedStorage"; - private static String SHARED_PREFERENCES_FILENAME = "RN_ENCRYPTED_STORAGE_SHARED_PREF"; + private static final String NATIVE_MODULE_NAME = "RNEncryptedStorage"; + private static final String SHARED_PREFERENCES_FILENAME = "RN_ENCRYPTED_STORAGE_SHARED_PREF"; private SharedPreferences sharedPreferences; @@ -23,16 +21,21 @@ public RNEncryptedStorageModule(ReactApplicationContext context) { super(context); try { + MasterKey key = new MasterKey.Builder(context) + .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) + .build(); + this.sharedPreferences = EncryptedSharedPreferences.create( - RNEncryptedStorageModule.SHARED_PREFERENCES_FILENAME, - MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC), context, + RNEncryptedStorageModule.SHARED_PREFERENCES_FILENAME, + key, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM ); } catch (Exception ex) { + Log.e(NATIVE_MODULE_NAME, "Failed to create encrypted shared preferences! Failing back to standard SharedPreferences", ex); this.sharedPreferences = context.getSharedPreferences(RNEncryptedStorageModule.SHARED_PREFERENCES_FILENAME, Context.MODE_PRIVATE); } }