|
12 | 12 |
|
13 | 13 | import com.google.android.apps.forscience.auth0.Auth0Token; |
14 | 14 | import com.google.android.apps.forscience.whistlepunk.ActivityWithNavigationView; |
| 15 | +import com.google.android.apps.forscience.whistlepunk.MainActivity; |
15 | 16 | import com.google.android.apps.forscience.whistlepunk.accounts.AbstractAccountsProvider; |
16 | 17 | import com.google.android.apps.forscience.whistlepunk.accounts.AppAccount; |
17 | 18 | import com.google.android.apps.forscience.whistlepunk.remote.StringUtils; |
18 | 19 |
|
| 20 | +import java.io.File; |
19 | 21 | import java.io.IOException; |
20 | 22 | import java.security.GeneralSecurityException; |
| 23 | +import java.security.KeyStore; |
| 24 | +import java.security.KeyStoreException; |
| 25 | +import java.security.NoSuchAlgorithmException; |
| 26 | +import java.security.cert.CertificateException; |
21 | 27 |
|
22 | 28 | public class ArduinoAccountProvider extends AbstractAccountsProvider { |
23 | 29 |
|
24 | 30 | private static final String LOG_TAG = "ArduinoAccountProvider"; |
| 31 | + private static final String SHARED_PREFS_FILENAME = "ArduinoSharedPreferences"; |
| 32 | + private static final String UNENCRYPTED_SHARED_PREFS_FILENAME = "ArduinoSharedPreferencesSafe"; |
| 33 | + private static final String KEY_KEYSTORE_RESET = "keystore_reset"; |
25 | 34 |
|
26 | 35 | private ArduinoAccount arduinoAccount; |
27 | 36 |
|
28 | 37 | public ArduinoAccountProvider(final Context context) { |
29 | 38 | super(context); |
| 39 | + |
30 | 40 | final SharedPreferences prefs = getSharedPreferences(); |
31 | 41 | final String jsonToken = prefs.getString("token", null); |
32 | 42 | if (!StringUtils.isEmpty(jsonToken)) { |
@@ -100,27 +110,86 @@ public void showAddAccountDialog(Activity activity) { |
100 | 110 | public void showAccountSwitcherDialog(Fragment fragment, int requestCode) { |
101 | 111 | } |
102 | 112 |
|
| 113 | + private Boolean getKeystoreResetState() { |
| 114 | + SharedPreferences prefs = applicationContext.getSharedPreferences(UNENCRYPTED_SHARED_PREFS_FILENAME, Context.MODE_PRIVATE); |
| 115 | + return prefs.getBoolean(KEY_KEYSTORE_RESET, false); |
| 116 | + } |
| 117 | + private void setKeystoreResetState(Boolean state) { |
| 118 | + SharedPreferences prefs = applicationContext.getSharedPreferences(UNENCRYPTED_SHARED_PREFS_FILENAME, Context.MODE_PRIVATE); |
| 119 | + prefs.edit().putBoolean(KEY_KEYSTORE_RESET, state).apply(); |
| 120 | + } |
| 121 | + |
| 122 | + private void resetKeystoreAndRestart() { |
| 123 | + // delete shared preferences file |
| 124 | + File sharedPrefsFile = new File(applicationContext.getFilesDir().getParent() + "/shared_prefs/" + SHARED_PREFS_FILENAME + ".xml"); |
| 125 | + if (sharedPrefsFile.exists()) { |
| 126 | + Boolean deleted = sharedPrefsFile.delete(); |
| 127 | + Log.i(LOG_TAG, String.format("Shared prefs file \"%s\" deleted: %s", sharedPrefsFile.getAbsolutePath(), deleted)); |
| 128 | + } else { |
| 129 | + Log.i(LOG_TAG, String.format("Shared prefs file \"%s\" non-existent", sharedPrefsFile.getAbsolutePath())); |
| 130 | + } |
| 131 | + |
| 132 | + // delete master key |
| 133 | + try { |
| 134 | + KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); |
| 135 | + keyStore.load(null); |
| 136 | + keyStore.deleteEntry(MasterKey.DEFAULT_MASTER_KEY_ALIAS); |
| 137 | + |
| 138 | + Log.i(LOG_TAG, "Master key deleted"); |
| 139 | + } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) { |
| 140 | + Log.i(LOG_TAG, "Unable to delete master key"); |
| 141 | + throw new RuntimeException("Unable to delete master key", e); |
| 142 | + } |
| 143 | + |
| 144 | + // save on (non-encrypted) shared preferences the reset state to avoid loops |
| 145 | + setKeystoreResetState(true); |
| 146 | + Log.i(LOG_TAG, "Set keystore reset state to TRUE."); |
| 147 | + |
| 148 | + // restart app |
| 149 | + Intent intent = new Intent(applicationContext, MainActivity.class); |
| 150 | + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| 151 | + Log.i(LOG_TAG, "Restarting application..."); |
| 152 | + applicationContext.startActivity(intent); |
| 153 | + if (applicationContext instanceof Activity) { |
| 154 | + ((Activity) applicationContext).finish(); |
| 155 | + } |
| 156 | + |
| 157 | + Runtime.getRuntime().exit(0); |
| 158 | + } |
| 159 | + |
103 | 160 | private SharedPreferences getSharedPreferences() { |
104 | 161 | MasterKey masterKey = null; |
105 | 162 | try { |
| 163 | + // build MasterKey |
106 | 164 | masterKey = new MasterKey.Builder(applicationContext, MasterKey.DEFAULT_MASTER_KEY_ALIAS) |
107 | 165 | .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) |
108 | 166 | .build(); |
109 | 167 |
|
110 | | - return EncryptedSharedPreferences.create( |
| 168 | + // get or create shared preferences |
| 169 | + SharedPreferences prefs = EncryptedSharedPreferences.create( |
111 | 170 | applicationContext, |
112 | | - "ArduinoSharedPreferences", |
| 171 | + SHARED_PREFS_FILENAME, |
113 | 172 | masterKey, |
114 | 173 | EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, |
115 | 174 | EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM |
116 | 175 | ); |
117 | | - } catch (GeneralSecurityException e) { |
118 | | - Log.e(LOG_TAG, "Unable to retrieve encrypted shared preferences", e); |
119 | | - throw new RuntimeException("Unable to retrieve encrypted shared preferences", e); |
120 | | - } catch (IOException e) { |
121 | | - Log.e(LOG_TAG, "Unable to retrieve encrypted shared preferences", e); |
122 | | - throw new RuntimeException("Unable to retrieve encrypted shared preferences", e); |
| 176 | + |
| 177 | + // set keystore reset to false to enable future resets |
| 178 | + setKeystoreResetState(false); |
| 179 | + Log.i(LOG_TAG, "Set keystore reset state to FALSE."); |
| 180 | + |
| 181 | + return prefs; |
| 182 | + } catch (GeneralSecurityException | IOException e) { |
| 183 | + Boolean keystoreReset = getKeystoreResetState(); |
| 184 | + if (keystoreReset) { |
| 185 | + // a keystore reset just occurred, interrupt execution. |
| 186 | + throw new RuntimeException("Unable to retrieve encrypted shared preferences", e); |
| 187 | + } else { |
| 188 | + Log.i(LOG_TAG, "Unable to retrieve encrypted shared preferences, regenerating master key.", e); |
| 189 | + resetKeystoreAndRestart(); |
| 190 | + } |
| 191 | + |
| 192 | + return null; |
123 | 193 | } |
124 | 194 | } |
125 | | - |
126 | 195 | } |
0 commit comments