diff --git a/patches/expo-secure-store+12.0.0.patch b/patches/expo-secure-store+12.0.0.patch new file mode 100644 --- /dev/null +++ b/patches/expo-secure-store+12.0.0.patch @@ -0,0 +1,98 @@ +diff --git a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.java b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.java +index 91e9b85..93208a6 100644 +--- a/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.java ++++ b/node_modules/expo-secure-store/android/src/main/java/expo/modules/securestore/SecureStoreModule.java +@@ -8,6 +8,7 @@ import android.os.Build; + import android.preference.PreferenceManager; + import android.security.KeyPairGeneratorSpec; + import android.security.keystore.KeyGenParameterSpec; ++import android.security.keystore.KeyPermanentlyInvalidatedException; + import android.security.keystore.KeyProperties; + import android.text.TextUtils; + import android.util.Base64; +@@ -39,6 +40,7 @@ import java.security.spec.InvalidParameterSpecException; + import java.util.Date; + + import javax.crypto.Cipher; ++import javax.crypto.IllegalBlockSizeException; + import javax.crypto.KeyGenerator; + import javax.crypto.NoSuchPaddingException; + import javax.crypto.SecretKey; +@@ -81,14 +83,14 @@ public class SecureStoreModule extends ExportedModule { + @SuppressWarnings("unused") + public void setValueWithKeyAsync(String value, String key, ReadableArguments options, Promise promise) { + try { +- setItemImpl(key, value, options, promise); ++ setItemImpl(key, value, options, promise, false); + } catch (Exception e) { + Log.e(TAG, "Caught unexpected exception when writing to SecureStore", e); + promise.reject("E_SECURESTORE_WRITE_ERROR", "An unexpected error occurred when writing to SecureStore", e); + } + } + +- private void setItemImpl(String key, String value, ReadableArguments options, Promise promise) { ++ private void setItemImpl(String key, String value, ReadableArguments options, Promise promise, boolean keyIsInvalidated) { + if (key == null) { + promise.reject("E_SECURESTORE_NULL_KEY", "SecureStore keys must not be null"); + return; +@@ -109,11 +111,19 @@ public class SecureStoreModule extends ExportedModule { + try { + KeyStore keyStore = getKeyStore(); + ++ ++ + // Android API 23+ supports storing symmetric keys in the keystore and on older Android + // versions we store an asymmetric key pair and use hybrid encryption. We store the scheme we + // use in the encrypted JSON item so that we know how to decode and decrypt it when reading + // back a value. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { ++ // Fixing key invalidated crash ++ if(keyIsInvalidated) { ++ String alias = mAESEncrypter.getKeyStoreAlias(options); ++ keyStore.deleteEntry(alias); ++ } ++ + KeyStore.SecretKeyEntry secretKeyEntry = getKeyEntry(KeyStore.SecretKeyEntry.class, mAESEncrypter, options); + mAESEncrypter.createEncryptedItem(promise, value, keyStore, secretKeyEntry, options, mAuthenticationHelper.getDefaultCallback(), (innerPromise, result) -> { + JSONObject obj = (JSONObject) result; +@@ -132,10 +142,32 @@ public class SecureStoreModule extends ExportedModule { + Log.w(TAG, e); + promise.reject("E_SECURESTORE_IO_ERROR", "There was an I/O error loading the keystore for SecureStore", e); + return; ++ } catch (IllegalBlockSizeException e){ ++ boolean isInvalidationException = e.getCause() != null && e.getCause().getMessage() != null && e.getCause().getMessage().contains("Key user not authenticated"); ++ ++ if(isInvalidationException && !keyIsInvalidated) { ++ setItemImpl(key, value, options, promise, true); ++ Log.w(TAG, "IllegalBlockSizeException, retrying with the key deleted"); ++ return; ++ } ++ // If the issue persists after deleting the key it is likely not related to invalidation ++ promise.reject("E_SECURESTORE_ENCRYPT_ERROR", "Unable to decrypt the key", e); + } catch (GeneralSecurityException e) { +- Log.w(TAG, e); +- promise.reject("E_SECURESTORE_ENCRYPT_ERROR", "Could not encrypt the value for SecureStore", e); +- return; ++ boolean isInvalidationException = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && e instanceof KeyPermanentlyInvalidatedException; ++ ++ if (isInvalidationException && !keyIsInvalidated) { ++ // If the key has been invalidated by the OS we try to reinitialize it. ++ Log.w(TAG, "Key has been invalidated, retrying with the key deleted"); ++ setItemImpl(key, value, options, promise, true); ++ } else if (isInvalidationException) { ++ Log.w(TAG, e); ++ // If reinitialization of the key fails, reject the promise ++ promise.reject("E_SECURESTORE_ENCRYPT_ERROR", "Encryption Failed. The key has been permanently invalidated and cannot be reinitialized", e); ++ } else { ++ Log.w(TAG, e); ++ promise.reject("E_SECURESTORE_ENCRYPT_ERROR", "Could not encrypt the value for SecureStore", e); ++ return; ++ } + } catch (JSONException e) { + Log.w(TAG, e); + promise.reject("E_SECURESTORE_ENCODE_ERROR", "Could not create an encrypted JSON item for SecureStore", e); +@@ -661,3 +693,4 @@ public class SecureStoreModule extends ExportedModule { + } + } + } ++