From a7f0903d51a43563793fcfa128ab4cb3b2524f8e Mon Sep 17 00:00:00 2001 From: Dario Carella Date: Wed, 3 Nov 2021 15:08:31 +0100 Subject: [PATCH 1/3] Recreate shared prefs on failure --- .../arduino/ArduinoAccountProvider.java | 48 ++++++++++++-- .../whistlepunk/gdrivesync/GDriveShared.java | 62 ++++++++++++++++--- 2 files changed, 96 insertions(+), 14 deletions(-) diff --git a/OpenScienceJournal/whistlepunk_library/src/main/java/com/google/android/apps/forscience/whistlepunk/accounts/arduino/ArduinoAccountProvider.java b/OpenScienceJournal/whistlepunk_library/src/main/java/com/google/android/apps/forscience/whistlepunk/accounts/arduino/ArduinoAccountProvider.java index aaf044cf..bd6c0e14 100644 --- a/OpenScienceJournal/whistlepunk_library/src/main/java/com/google/android/apps/forscience/whistlepunk/accounts/arduino/ArduinoAccountProvider.java +++ b/OpenScienceJournal/whistlepunk_library/src/main/java/com/google/android/apps/forscience/whistlepunk/accounts/arduino/ArduinoAccountProvider.java @@ -16,17 +16,21 @@ import com.google.android.apps.forscience.whistlepunk.accounts.AppAccount; import com.google.android.apps.forscience.whistlepunk.remote.StringUtils; +import java.io.File; import java.io.IOException; import java.security.GeneralSecurityException; +import java.security.KeyStore; public class ArduinoAccountProvider extends AbstractAccountsProvider { private static final String LOG_TAG = "ArduinoAccountProvider"; + private static final String SHARED_PREFS_FILENAME = "ArduinoSharedPreferences"; private ArduinoAccount arduinoAccount; public ArduinoAccountProvider(final Context context) { super(context); + final SharedPreferences prefs = getSharedPreferences(); final String jsonToken = prefs.getString("token", null); if (!StringUtils.isEmpty(jsonToken)) { @@ -100,27 +104,59 @@ public void showAddAccountDialog(Activity activity) { public void showAccountSwitcherDialog(Fragment fragment, int requestCode) { } - private SharedPreferences getSharedPreferences() { + private SharedPreferences getBrandNewSharedPreferences() { MasterKey masterKey = null; + try { + File sharedPrefsFile = new File(applicationContext.getFilesDir().getParent() + "/shared_prefs/" + SHARED_PREFS_FILENAME + ".xml"); + boolean deleted = sharedPrefsFile.delete(); + + Log.d(LOG_TAG, String.format("Shared prefs file deleted: %s", deleted)); + + // delete MasterKey + KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); + keyStore.load(null); + keyStore.deleteEntry(MasterKey.DEFAULT_MASTER_KEY_ALIAS); + + // build MasterKey masterKey = new MasterKey.Builder(applicationContext, MasterKey.DEFAULT_MASTER_KEY_ALIAS) .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) .build(); + // create shared preferences return EncryptedSharedPreferences.create( applicationContext, - "ArduinoSharedPreferences", + SHARED_PREFS_FILENAME, masterKey, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM ); - } catch (GeneralSecurityException e) { - Log.e(LOG_TAG, "Unable to retrieve encrypted shared preferences", e); - throw new RuntimeException("Unable to retrieve encrypted shared preferences", e); - } catch (IOException e) { + } catch (GeneralSecurityException | IOException e) { Log.e(LOG_TAG, "Unable to retrieve encrypted shared preferences", e); throw new RuntimeException("Unable to retrieve encrypted shared preferences", e); } } + private SharedPreferences getSharedPreferences() { + MasterKey masterKey = null; + try { + // build MasterKey + masterKey = new MasterKey.Builder(applicationContext, MasterKey.DEFAULT_MASTER_KEY_ALIAS) + .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) + .build(); + + // get or create shared preferences + return EncryptedSharedPreferences.create( + applicationContext, + SHARED_PREFS_FILENAME, + masterKey, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM + ); + } catch (GeneralSecurityException | IOException e) { + Log.e(LOG_TAG, "Unable to retrieve encrypted shared preferences, regenerating master key.", e); + return this.getBrandNewSharedPreferences(); + } + } + } diff --git a/OpenScienceJournal/whistlepunk_library/src/main/java/com/google/android/apps/forscience/whistlepunk/gdrivesync/GDriveShared.java b/OpenScienceJournal/whistlepunk_library/src/main/java/com/google/android/apps/forscience/whistlepunk/gdrivesync/GDriveShared.java index 769840c8..b9f6cd2d 100644 --- a/OpenScienceJournal/whistlepunk_library/src/main/java/com/google/android/apps/forscience/whistlepunk/gdrivesync/GDriveShared.java +++ b/OpenScienceJournal/whistlepunk_library/src/main/java/com/google/android/apps/forscience/whistlepunk/gdrivesync/GDriveShared.java @@ -2,11 +2,18 @@ import android.content.Context; import android.content.SharedPreferences; +import android.util.Log; import androidx.security.crypto.EncryptedSharedPreferences; -import androidx.security.crypto.MasterKeys; +import androidx.security.crypto.MasterKey; + +import java.io.File; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.KeyStore; public class GDriveShared { + private static final String LOG_TAG = "GDriveShared"; private static boolean mAccountLoaded = false; @@ -54,23 +61,62 @@ public static GDriveAccount getCredentials(final Context context) { return mAccount; } - private static SharedPreferences getSharedPreferences(final Context context) { + private static SharedPreferences getBrandNewSharedPreferences(Context context) { + MasterKey masterKey = null; + try { - final String masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC); + File sharedPrefsFile = new File(context.getFilesDir().getParent() + "/shared_prefs/" + SHARED_PREFS_FILENAME + ".xml"); + boolean deleted = sharedPrefsFile.delete(); + + Log.d(LOG_TAG, String.format("Shared prefs file deleted: %s", deleted)); + + // delete MasterKey + KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); + keyStore.load(null); + keyStore.deleteEntry(MasterKey.DEFAULT_MASTER_KEY_ALIAS); + + // build MasterKey + masterKey = new MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS) + .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) + .build(); + + // create shared preferences return EncryptedSharedPreferences.create( - PREFS_NAME, - masterKeyAlias, context, + SHARED_PREFS_FILENAME, + masterKey, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM ); - } catch (Exception e) { - throw new RuntimeException(e); + } catch (GeneralSecurityException | IOException e) { + Log.e(LOG_TAG, "Unable to retrieve encrypted shared preferences", e); + throw new RuntimeException("Unable to retrieve encrypted shared preferences", e); } } - private static final String PREFS_NAME = "GoogleDriveSync"; + private static SharedPreferences getSharedPreferences(final Context context) { + MasterKey masterKey = null; + try { + // build MasterKey + masterKey = new MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS) + .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) + .build(); + + // get or create shared preferences + return EncryptedSharedPreferences.create( + context, + SHARED_PREFS_FILENAME, + masterKey, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM + ); + } catch (GeneralSecurityException | IOException e) { + Log.e(LOG_TAG, "Unable to retrieve encrypted shared preferences, regenerating master key.", e); + return getBrandNewSharedPreferences(context); + } + } + private static final String SHARED_PREFS_FILENAME = "GoogleDriveSync"; private static final String KEY_ACCOUNT_ID = "account_id"; private static final String KEY_EMAIL = "email"; private static final String KEY_TOKEN = "token"; From eb086ba98a0e428149baaf0064df9b08fae34f11 Mon Sep 17 00:00:00 2001 From: Dario Carella Date: Thu, 4 Nov 2021 11:19:49 +0100 Subject: [PATCH 2/3] Restart application when keystore reset occurs --- .../arduino/ArduinoAccountProvider.java | 87 ++++++++++++------ .../whistlepunk/gdrivesync/GDriveShared.java | 90 +++++++++++++------ 2 files changed, 124 insertions(+), 53 deletions(-) diff --git a/OpenScienceJournal/whistlepunk_library/src/main/java/com/google/android/apps/forscience/whistlepunk/accounts/arduino/ArduinoAccountProvider.java b/OpenScienceJournal/whistlepunk_library/src/main/java/com/google/android/apps/forscience/whistlepunk/accounts/arduino/ArduinoAccountProvider.java index bd6c0e14..bd2a9c21 100644 --- a/OpenScienceJournal/whistlepunk_library/src/main/java/com/google/android/apps/forscience/whistlepunk/accounts/arduino/ArduinoAccountProvider.java +++ b/OpenScienceJournal/whistlepunk_library/src/main/java/com/google/android/apps/forscience/whistlepunk/accounts/arduino/ArduinoAccountProvider.java @@ -12,6 +12,7 @@ import com.google.android.apps.forscience.auth0.Auth0Token; import com.google.android.apps.forscience.whistlepunk.ActivityWithNavigationView; +import com.google.android.apps.forscience.whistlepunk.MainActivity; import com.google.android.apps.forscience.whistlepunk.accounts.AbstractAccountsProvider; import com.google.android.apps.forscience.whistlepunk.accounts.AppAccount; import com.google.android.apps.forscience.whistlepunk.remote.StringUtils; @@ -20,11 +21,16 @@ import java.io.IOException; import java.security.GeneralSecurityException; import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; public class ArduinoAccountProvider extends AbstractAccountsProvider { private static final String LOG_TAG = "ArduinoAccountProvider"; private static final String SHARED_PREFS_FILENAME = "ArduinoSharedPreferences"; + private static final String UNENCRYPTED_SHARED_PREFS_FILENAME = "ArduinoSharedPreferencesSafe"; + private static final String KEY_KEYSTORE_RESET = "keystore_reset"; private ArduinoAccount arduinoAccount; @@ -104,37 +110,51 @@ public void showAddAccountDialog(Activity activity) { public void showAccountSwitcherDialog(Fragment fragment, int requestCode) { } - private SharedPreferences getBrandNewSharedPreferences() { - MasterKey masterKey = null; - - try { - File sharedPrefsFile = new File(applicationContext.getFilesDir().getParent() + "/shared_prefs/" + SHARED_PREFS_FILENAME + ".xml"); - boolean deleted = sharedPrefsFile.delete(); + private Boolean getKeystoreResetState() { + SharedPreferences prefs = applicationContext.getSharedPreferences(UNENCRYPTED_SHARED_PREFS_FILENAME, Context.MODE_PRIVATE); + return prefs.getBoolean(KEY_KEYSTORE_RESET, false); + } + private void setKeystoreResetState(Boolean state) { + SharedPreferences prefs = applicationContext.getSharedPreferences(UNENCRYPTED_SHARED_PREFS_FILENAME, Context.MODE_PRIVATE); + prefs.edit().putBoolean(KEY_KEYSTORE_RESET, state).apply(); + } - Log.d(LOG_TAG, String.format("Shared prefs file deleted: %s", deleted)); + private void resetKeystoreAndRestart() { + // delete shared preferences file + File sharedPrefsFile = new File(applicationContext.getFilesDir().getParent() + "/shared_prefs/" + SHARED_PREFS_FILENAME + ".xml"); + if (sharedPrefsFile.exists()) { + Boolean deleted = sharedPrefsFile.delete(); + Log.i(LOG_TAG, String.format("Shared prefs file \"%s\" deleted: %s", sharedPrefsFile.getAbsolutePath(), deleted)); + } else { + Log.i(LOG_TAG, String.format("Shared prefs file \"%s\" non-existent", sharedPrefsFile.getAbsolutePath())); + } - // delete MasterKey + // delete master key + try { KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); keyStore.load(null); keyStore.deleteEntry(MasterKey.DEFAULT_MASTER_KEY_ALIAS); - // build MasterKey - masterKey = new MasterKey.Builder(applicationContext, MasterKey.DEFAULT_MASTER_KEY_ALIAS) - .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) - .build(); + Log.i(LOG_TAG, "Master key deleted"); + } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) { + Log.i(LOG_TAG, "Unable to delete master key"); + throw new RuntimeException("Unable to delete master key", e); + } - // create shared preferences - return EncryptedSharedPreferences.create( - applicationContext, - SHARED_PREFS_FILENAME, - masterKey, - EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, - EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM - ); - } catch (GeneralSecurityException | IOException e) { - Log.e(LOG_TAG, "Unable to retrieve encrypted shared preferences", e); - throw new RuntimeException("Unable to retrieve encrypted shared preferences", e); + // save on (non-encrypted) shared preferences the reset state to avoid loops + setKeystoreResetState(true); + Log.i(LOG_TAG, "Set keystore reset state to TRUE."); + + // restart app + Intent intent = new Intent(applicationContext, MainActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + Log.i(LOG_TAG, "Restarting application..."); + applicationContext.startActivity(intent); + if (applicationContext instanceof Activity) { + ((Activity) applicationContext).finish(); } + + Runtime.getRuntime().exit(0); } private SharedPreferences getSharedPreferences() { @@ -146,17 +166,30 @@ private SharedPreferences getSharedPreferences() { .build(); // get or create shared preferences - return EncryptedSharedPreferences.create( + SharedPreferences prefs = EncryptedSharedPreferences.create( applicationContext, SHARED_PREFS_FILENAME, masterKey, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM ); + + // set keystore reset to false to enable future resets + setKeystoreResetState(false); + Log.i(LOG_TAG, "Set keystore reset state to FALSE."); + + return prefs; } catch (GeneralSecurityException | IOException e) { - Log.e(LOG_TAG, "Unable to retrieve encrypted shared preferences, regenerating master key.", e); - return this.getBrandNewSharedPreferences(); + Boolean keystoreReset = getKeystoreResetState(); + if (keystoreReset) { + // a keystore reset just occurred, interrupt execution. + throw new RuntimeException("Unable to retrieve encrypted shared preferences", e); + } else { + Log.i(LOG_TAG, "Unable to retrieve encrypted shared preferences, regenerating master key.", e); + resetKeystoreAndRestart(); + } + + return null; } } - } diff --git a/OpenScienceJournal/whistlepunk_library/src/main/java/com/google/android/apps/forscience/whistlepunk/gdrivesync/GDriveShared.java b/OpenScienceJournal/whistlepunk_library/src/main/java/com/google/android/apps/forscience/whistlepunk/gdrivesync/GDriveShared.java index b9f6cd2d..c9d56641 100644 --- a/OpenScienceJournal/whistlepunk_library/src/main/java/com/google/android/apps/forscience/whistlepunk/gdrivesync/GDriveShared.java +++ b/OpenScienceJournal/whistlepunk_library/src/main/java/com/google/android/apps/forscience/whistlepunk/gdrivesync/GDriveShared.java @@ -1,16 +1,23 @@ package com.google.android.apps.forscience.whistlepunk.gdrivesync; +import android.app.Activity; import android.content.Context; +import android.content.Intent; import android.content.SharedPreferences; import android.util.Log; import androidx.security.crypto.EncryptedSharedPreferences; import androidx.security.crypto.MasterKey; +import com.google.android.apps.forscience.whistlepunk.MainActivity; + import java.io.File; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; public class GDriveShared { private static final String LOG_TAG = "GDriveShared"; @@ -61,37 +68,51 @@ public static GDriveAccount getCredentials(final Context context) { return mAccount; } - private static SharedPreferences getBrandNewSharedPreferences(Context context) { - MasterKey masterKey = null; - - try { - File sharedPrefsFile = new File(context.getFilesDir().getParent() + "/shared_prefs/" + SHARED_PREFS_FILENAME + ".xml"); - boolean deleted = sharedPrefsFile.delete(); + private static Boolean getKeystoreResetState(final Context context) { + SharedPreferences prefs = context.getSharedPreferences(UNENCRYPTED_SHARED_PREFS_FILENAME, Context.MODE_PRIVATE); + return prefs.getBoolean(KEY_KEYSTORE_RESET, false); + } + private static void setKeystoreResetState(final Context context, Boolean state) { + SharedPreferences prefs = context.getSharedPreferences(UNENCRYPTED_SHARED_PREFS_FILENAME, Context.MODE_PRIVATE); + prefs.edit().putBoolean(KEY_KEYSTORE_RESET, state).apply(); + } - Log.d(LOG_TAG, String.format("Shared prefs file deleted: %s", deleted)); + private static void resetKeystoreAndRestart(final Context context) { + // delete shared preferences file + File sharedPrefsFile = new File(context.getFilesDir().getParent() + "/shared_prefs/" + SHARED_PREFS_FILENAME + ".xml"); + if (sharedPrefsFile.exists()) { + Boolean deleted = sharedPrefsFile.delete(); + Log.i(LOG_TAG, String.format("Shared prefs file \"%s\" deleted: %s", sharedPrefsFile.getAbsolutePath(), deleted)); + } else { + Log.i(LOG_TAG, String.format("Shared prefs file \"%s\" non-existent", sharedPrefsFile.getAbsolutePath())); + } - // delete MasterKey + // delete master key + try { KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); keyStore.load(null); keyStore.deleteEntry(MasterKey.DEFAULT_MASTER_KEY_ALIAS); - // build MasterKey - masterKey = new MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS) - .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) - .build(); + Log.i(LOG_TAG, "Master key deleted"); + } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) { + Log.i(LOG_TAG, "Unable to delete master key"); + throw new RuntimeException("Unable to delete master key", e); + } - // create shared preferences - return EncryptedSharedPreferences.create( - context, - SHARED_PREFS_FILENAME, - masterKey, - EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, - EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM - ); - } catch (GeneralSecurityException | IOException e) { - Log.e(LOG_TAG, "Unable to retrieve encrypted shared preferences", e); - throw new RuntimeException("Unable to retrieve encrypted shared preferences", e); + // save on (non-encrypted) shared preferences the reset state to avoid loops + setKeystoreResetState(context, true); + Log.i(LOG_TAG, "Set keystore reset state to TRUE."); + + // restart app + Intent intent = new Intent(context, MainActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + Log.i(LOG_TAG, "Restarting application..."); + context.startActivity(intent); + if (context instanceof Activity) { + ((Activity) context).finish(); } + + Runtime.getRuntime().exit(0); } private static SharedPreferences getSharedPreferences(final Context context) { @@ -103,20 +124,37 @@ private static SharedPreferences getSharedPreferences(final Context context) { .build(); // get or create shared preferences - return EncryptedSharedPreferences.create( + SharedPreferences prefs = EncryptedSharedPreferences.create( context, SHARED_PREFS_FILENAME, masterKey, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM ); + + // set keystore reset to false to enable future resets + setKeystoreResetState(context, false); + Log.i(LOG_TAG, "Set keystore reset state to FALSE."); + + return prefs; } catch (GeneralSecurityException | IOException e) { - Log.e(LOG_TAG, "Unable to retrieve encrypted shared preferences, regenerating master key.", e); - return getBrandNewSharedPreferences(context); + Boolean keystoreReset = getKeystoreResetState(context); + if (keystoreReset) { + // a keystore reset just occurred, interrupt execution to avoid loops + throw new RuntimeException("Unable to retrieve encrypted shared preferences", e); + } else { + Log.i(LOG_TAG, "Unable to retrieve encrypted shared preferences, regenerating master key.", e); + resetKeystoreAndRestart(context); + } + + return null; } } + private static final String SHARED_PREFS_FILENAME = "GoogleDriveSync"; + private static final String UNENCRYPTED_SHARED_PREFS_FILENAME = "ArduinoSharedPreferencesSafe"; + private static final String KEY_KEYSTORE_RESET = "keystore_reset"; private static final String KEY_ACCOUNT_ID = "account_id"; private static final String KEY_EMAIL = "email"; private static final String KEY_TOKEN = "token"; From 8b8ffffab58fdb986ba2a98b9061253216971f81 Mon Sep 17 00:00:00 2001 From: Dario Carella Date: Thu, 4 Nov 2021 11:21:48 +0100 Subject: [PATCH 3/3] Bump version to 1.3.4 --- OpenScienceJournal/app/build.gradle | 4 ++-- OpenScienceJournal/whistlepunk_library/build.gradle | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/OpenScienceJournal/app/build.gradle b/OpenScienceJournal/app/build.gradle index 86a1bd94..bb83ebfb 100644 --- a/OpenScienceJournal/app/build.gradle +++ b/OpenScienceJournal/app/build.gradle @@ -9,8 +9,8 @@ android { applicationId "cc.arduino.sciencejournal" minSdkVersion 19 targetSdkVersion 29 - versionCode 9 - versionName "1.3.3" + versionCode 10 + versionName "1.3.4" multiDexEnabled true vectorDrawables.useSupportLibrary = true } diff --git a/OpenScienceJournal/whistlepunk_library/build.gradle b/OpenScienceJournal/whistlepunk_library/build.gradle index 6dbb6204..ee70cdee 100644 --- a/OpenScienceJournal/whistlepunk_library/build.gradle +++ b/OpenScienceJournal/whistlepunk_library/build.gradle @@ -21,8 +21,8 @@ android { defaultConfig { minSdkVersion 19 targetSdkVersion 29 - versionCode 9 - versionName "1.3.3" + versionCode 10 + versionName "1.3.4" vectorDrawables.useSupportLibrary = true }