diff --git a/bramble-android/src/main/java/org/briarproject/bramble/account/AndroidAccountManager.java b/bramble-android/src/main/java/org/briarproject/bramble/account/AndroidAccountManager.java index d792672c4e6be4d54405d4c56d8bf1992b94d803..22c279ecaae41d9199928d4d8a75fa7a7639e7ad 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/account/AndroidAccountManager.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/account/AndroidAccountManager.java @@ -38,12 +38,15 @@ class AndroidAccountManager extends AccountManagerImpl @Override @Nullable protected String loadEncryptedDatabaseKey() { - String key = getDatabaseKeyFromPreferences(); - if (key == null) key = super.loadEncryptedDatabaseKey(); - else migrateDatabaseKeyToFile(key); - return key; + synchronized (stateChangeLock) { + String key = getDatabaseKeyFromPreferences(); + if (key == null) key = super.loadEncryptedDatabaseKey(); + else migrateDatabaseKeyToFile(key); + return key; + } } + // Locking: stateChangeLock @Nullable private String getDatabaseKeyFromPreferences() { String key = prefs.getString(PREF_DB_KEY, null); @@ -52,6 +55,7 @@ class AndroidAccountManager extends AccountManagerImpl return key; } + // Locking: stateChangeLock private void migrateDatabaseKeyToFile(String key) { if (storeEncryptedDatabaseKey(key)) { if (prefs.edit().remove(PREF_DB_KEY).commit()) @@ -64,12 +68,15 @@ class AndroidAccountManager extends AccountManagerImpl @Override public void deleteAccount() { - super.deleteAccount(); - SharedPreferences defaultPrefs = - PreferenceManager.getDefaultSharedPreferences(appContext); - deleteAppData(prefs, defaultPrefs); + synchronized (stateChangeLock) { + super.deleteAccount(); + SharedPreferences defaultPrefs = + PreferenceManager.getDefaultSharedPreferences(appContext); + deleteAppData(prefs, defaultPrefs); + } } + // Locking: stateChangeLock private void deleteAppData(SharedPreferences... clear) { // Clear and commit shared preferences for (SharedPreferences prefs : clear) { diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/account/AccountManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/account/AccountManager.java index 15d53122030da56191d5dd7b1c863645c183134a..e2784c145815baaa818a64bc2e1997f36783c216 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/account/AccountManager.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/account/AccountManager.java @@ -8,18 +8,58 @@ import javax.annotation.Nullable; @NotNullByDefault public interface AccountManager { + /** + * Returns true if the manager has the database key. This will be false + * before {@link #createAccount(String)} or {@link #signIn(String)} has + * been called, and true after {@link #createAccount(String)} or + * {@link #signIn(String)} has returned, until the process exits. + */ boolean hasDatabaseKey(); + /** + * Returns the database key if the manager has it. This will be null + * before {@link #createAccount(String)} or {@link #signIn(String)} has + * been called, and non-null after {@link #createAccount(String)} or + * {@link #signIn(String)} has returned, until the process exits. + */ @Nullable SecretKey getDatabaseKey(); + /** + * Returns true if the encrypted database key can be loaded from disk and + * the database directory exists. + */ boolean accountExists(); + /** + * Creates a database key, encrypts it with the given password and stores + * it on disk. This method does not create the database directory, so + * {@link #accountExists()} will continue to return false until the + * database directory is created. + */ boolean createAccount(String password); + /** + * Deletes all account state from disk. {@link #accountExists()} will + * return false after this method returns. + */ void deleteAccount(); + /** + * Loads the encrypted database key from disk and decrypts it with the + * given password. + * + * @return true if the database key was successfully loaded and decrypted. + */ boolean signIn(String password); + /** + * Loads the encrypted database key from disk, decrypts it with the old + * password, encrypts it with the new password, and stores it on disk, + * replacing the old key. + * + * @return true if the database key was successfully loaded, re-encrypted + * and stored. + */ boolean changePassword(String oldPassword, String newPassword); } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/account/AccountManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/account/AccountManagerImpl.java index 4d076fd2e8f6e26d9c39e274ffab4bfecf2e3e41..6162b3e42765189573ca222be25af18e2e509206 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/account/AccountManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/account/AccountManagerImpl.java @@ -38,6 +38,8 @@ class AccountManagerImpl implements AccountManager { private final CryptoComponent crypto; private final File dbKeyFile, dbKeyBackupFile; + protected final Object stateChangeLock = new Object(); + @Nullable private volatile SecretKey databaseKey = null; @@ -63,18 +65,21 @@ class AccountManagerImpl implements AccountManager { @Nullable protected String loadEncryptedDatabaseKey() { - String key = readDbKeyFromFile(dbKeyFile); - if (key == null) { - LOG.info("No database key in primary file"); - key = readDbKeyFromFile(dbKeyBackupFile); - if (key == null) LOG.info("No database key in backup file"); - else LOG.warning("Found database key in backup file"); - } else { - LOG.info("Found database key in primary file"); + synchronized (stateChangeLock) { + String key = readDbKeyFromFile(dbKeyFile); + if (key == null) { + LOG.info("No database key in primary file"); + key = readDbKeyFromFile(dbKeyBackupFile); + if (key == null) LOG.info("No database key in backup file"); + else LOG.warning("Found database key in backup file"); + } else { + LOG.info("Found database key in primary file"); + } + return key; } - return key; } + // Locking: stateChangeLock @Nullable private String readDbKeyFromFile(File f) { if (!f.exists()) { @@ -93,6 +98,7 @@ class AccountManagerImpl implements AccountManager { } } + // Locking: stateChangeLock protected boolean storeEncryptedDatabaseKey(String hex) { LOG.info("Storing database key in file"); // Create the directory if necessary @@ -130,6 +136,7 @@ class AccountManagerImpl implements AccountManager { } } + // Locking: stateChangeLock private void writeDbKeyToFile(String key, File f) throws IOException { FileOutputStream out = new FileOutputStream(f); out.write(key.getBytes("UTF-8")); @@ -139,18 +146,23 @@ class AccountManagerImpl implements AccountManager { @Override public boolean accountExists() { - return loadEncryptedDatabaseKey() != null - && databaseConfig.getDatabaseDirectory().isDirectory(); + synchronized (stateChangeLock) { + return loadEncryptedDatabaseKey() != null + && databaseConfig.getDatabaseDirectory().isDirectory(); + } } @Override public boolean createAccount(String password) { - SecretKey key = crypto.generateSecretKey(); - if (!encryptAndStoreDatabaseKey(key, password)) return false; - databaseKey = key; - return true; + synchronized (stateChangeLock) { + SecretKey key = crypto.generateSecretKey(); + if (!encryptAndStoreDatabaseKey(key, password)) return false; + databaseKey = key; + return true; + } } + // Locking: stateChangeLock private boolean encryptAndStoreDatabaseKey(SecretKey key, String password) { byte[] plaintext = key.getBytes(); byte[] ciphertext = crypto.encryptWithPassword(plaintext, password); @@ -159,19 +171,24 @@ class AccountManagerImpl implements AccountManager { @Override public void deleteAccount() { - LOG.info("Deleting account"); - IoUtils.deleteFileOrDir(databaseConfig.getDatabaseKeyDirectory()); - IoUtils.deleteFileOrDir(databaseConfig.getDatabaseDirectory()); + synchronized (stateChangeLock) { + LOG.info("Deleting account"); + IoUtils.deleteFileOrDir(databaseConfig.getDatabaseKeyDirectory()); + IoUtils.deleteFileOrDir(databaseConfig.getDatabaseDirectory()); + } } @Override public boolean signIn(String password) { - SecretKey key = loadAndDecryptDatabaseKey(password); - if (key == null) return false; - databaseKey = key; - return true; + synchronized (stateChangeLock) { + SecretKey key = loadAndDecryptDatabaseKey(password); + if (key == null) return false; + databaseKey = key; + return true; + } } + // Locking: stateChangeLock @Nullable private SecretKey loadAndDecryptDatabaseKey(String password) { String hex = loadEncryptedDatabaseKey(); @@ -190,7 +207,9 @@ class AccountManagerImpl implements AccountManager { @Override public boolean changePassword(String oldPassword, String newPassword) { - SecretKey key = loadAndDecryptDatabaseKey(oldPassword); - return key != null && encryptAndStoreDatabaseKey(key, newPassword); + synchronized (stateChangeLock) { + SecretKey key = loadAndDecryptDatabaseKey(oldPassword); + return key != null && encryptAndStoreDatabaseKey(key, newPassword); + } } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordActivity.java index 537d00d08edc2b3ff10a88ee1b7fc57a42351a6a..762744e8ae360c09b5adf9cc1247c1b278ff81d7 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/login/PasswordActivity.java @@ -53,6 +53,7 @@ public class PasswordActivity extends BaseActivity { overridePendingTransition(R.anim.fade_in, R.anim.fade_out); if (!accountManager.accountExists()) { + // TODO: Finish instead of deleting account? deleteAccount(); return; }