From b40862dfda8789d06009cce805996da5e432657c Mon Sep 17 00:00:00 2001 From: Justin Stephenson Date: Tue, 7 Jan 2025 12:13:49 -0500 Subject: [PATCH] Overwrite master key in keystore on restore --- .../freeotp/TokenPersistenceTest.java | 71 +++++++++++++++++++ .../freeotp/TokenPersistence.java | 8 +++ mobile/src/main/res/values/strings.xml | 2 +- 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/mobile/src/androidTest/java/org/fedorahosted/freeotp/TokenPersistenceTest.java b/mobile/src/androidTest/java/org/fedorahosted/freeotp/TokenPersistenceTest.java index 2ab979b7..3186a069 100644 --- a/mobile/src/androidTest/java/org/fedorahosted/freeotp/TokenPersistenceTest.java +++ b/mobile/src/androidTest/java/org/fedorahosted/freeotp/TokenPersistenceTest.java @@ -142,6 +142,77 @@ public void addRemoveTokensBackupRestore() throws GeneralSecurityException, validateTokens(cur, a2); } + private void copySP(SharedPreferences copyFrom, SharedPreferences copyTo) { + // Copy to + SharedPreferences.Editor ed = copyTo.edit(); + // Copy from + SharedPreferences sp = copyFrom; + ed.clear(); + for (Map.Entry entry : sp.getAll().entrySet()) { + Object v = entry.getValue(); + String key = entry.getKey(); + if (v instanceof String) { + ed.putString(key, ((String) v)); + } + } + ed.commit(); + } + + @Test + // Test multiple restore passes + public void tokenBackupRestoreMultiPass() throws GeneralSecurityException, + IOException, Token.UnsafeUriException, Token.InvalidUriException, TokenPersistence.BadPasswordException, JSONException { + setup(null); + SharedPreferences cur = mContext.getSharedPreferences("tokenStore", android.content.Context.MODE_PRIVATE); + SharedPreferences bkp = mContext.getSharedPreferences("tokenBackup", Context.MODE_PRIVATE); + SharedPreferences bkpCopy = mContext.getSharedPreferences("tokenBackupCopy", Context.MODE_PRIVATE); + + TokenPersistence tokenBackup = new TokenPersistence(mContext); + String origPwd = "backup"; + String newPwd = "redhat"; + + tokenBackup.provision(origPwd); + Adapter a = new Adapter(mContext, this); + + for (Map.Entry entry : uris.entrySet()) { + Pair pair = Token.parse(entry.getValue()); + a.add(pair.first, pair.second); + } + + // Backup and Restore Pass 1 + copySP(bkp, bkpCopy); + // Simulate app removal + reinstall + for (int i = 0; i < numTokens; i++) { + a.delete(0); + } + cur.edit().clear().commit(); + assertEquals(0, cur.getAll().size()); + + // Setup new backup password + tokenBackup.provision(newPwd); + + // Replace SP backup copy, must be after new provision to not overwrite MK + copySP(bkpCopy, bkp); + a.restoreTokens(origPwd); + validateTokens(cur, a); + + // Pass 2: export and attempt restore again + copySP(bkp, bkpCopy); + for (int i = 0; i < numTokens; i++) { + a.delete(0); + } + cur.edit().clear().commit(); + assertEquals(0, cur.getAll().size()); + + tokenBackup.provision(newPwd); + copySP(bkpCopy, bkp); + a.restoreTokens(origPwd); + String order = cur.getString("tokenOrder", null); + assertNotNull(order); + + validateTokens(cur, a); + } + @Test // Test restoring multiple migrated tokens public void tokenCompatBackupRestore() throws GeneralSecurityException, IOException, diff --git a/mobile/src/main/java/org/fedorahosted/freeotp/TokenPersistence.java b/mobile/src/main/java/org/fedorahosted/freeotp/TokenPersistence.java index 977aa9f6..d5db83dd 100644 --- a/mobile/src/main/java/org/fedorahosted/freeotp/TokenPersistence.java +++ b/mobile/src/main/java/org/fedorahosted/freeotp/TokenPersistence.java @@ -163,6 +163,14 @@ public List restore(String pwd) throws GeneralSecurityException, throw new BadPasswordException(); } + // Overwrite the master key stored in the keystore, restored entries are then re-encrypted + KeyProtection kp = new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + .setBlockModes(KeyProperties.BLOCK_MODE_GCM) + .build(); + + mKeyStore.setEntry(MASTER, new KeyStore.SecretKeyEntry(mk.decrypt(pwd)), kp); + for (Map.Entry item : mBackups.getAll().entrySet()) { JSONObject obj; String uuid = item.getKey(); diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 18daa982..eff69e26 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -65,7 +65,7 @@ Key invalidated! The key for this token has been invalidated by the Android Key Store. This may have been due to a change in your lock screen settings. The token has been removed. Please contact your token provider. Restore from backup - Please enter backup password + Please enter backup password\n\nUpon successful restore, this password used to restore will overwrite any previously setup backup password Invalid password Cancel Restore? You will lose all previously backed up tokens!