Compare commits

...

3 Commits

Author SHA1 Message Date
Torsten Grote
51cf49da19 Create AccountManager to encapsulate authentication and account logic 2018-07-12 18:35:58 -03:00
Torsten Grote
9892199305 Also remind to sign-in again after app was upgraded 2018-07-09 09:55:08 -03:00
Torsten Grote
b28307002e Add an option to not show the sign-in reminder
This is done via another preference in the settings screen
and an action button attached to the notification itself
2018-07-09 09:55:08 -03:00
47 changed files with 795 additions and 530 deletions

View File

@@ -0,0 +1,39 @@
package org.briarproject.bramble.api.account;
import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
@NotNullByDefault
public interface AccountManager {
@CryptoExecutor
void createAccount(String name, String password);
AccountState getAccountState();
/**
* Returns the name of the {@link LocalAuthor} if it was just created and
* null otherwise.
*
* See {@link IdentityManager#getLocalAuthor()} for reliable retrieval.
*/
@Nullable
String getCreatedLocalAuthorName();
/**
* Validates the account password and returns true if it was valid.
*/
boolean validatePassword(String password);
/**
* Changes the password and returns true if successful, false otherwise.
*/
@CryptoExecutor
boolean changePassword(String password, String newPassword);
void deleteAccount();
}

View File

@@ -0,0 +1,12 @@
package org.briarproject.bramble.api.account;
public enum AccountState {
NO_ACCOUNT,
CREATING_ACCOUNT,
SIGNING_IN,
SIGNED_IN,
SIGNING_OUT,
SIGNED_OUT
}

View File

@@ -21,10 +21,5 @@ public interface DatabaseConfig {
@Nullable @Nullable
SecretKey getEncryptionKey(); SecretKey getEncryptionKey();
void setLocalAuthorName(String nickname);
@Nullable
String getLocalAuthorName();
long getMaxSize(); long getMaxSize();
} }

View File

@@ -0,0 +1,127 @@
package org.briarproject.bramble.account;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.account.AccountState;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.util.StringUtils;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import static java.util.logging.Level.INFO;
import static org.briarproject.bramble.api.account.AccountState.CREATING_ACCOUNT;
import static org.briarproject.bramble.api.account.AccountState.NO_ACCOUNT;
import static org.briarproject.bramble.api.account.AccountState.SIGNED_IN;
import static org.briarproject.bramble.api.account.AccountState.SIGNED_OUT;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.now;
@NotNullByDefault
public abstract class AccountManagerImpl implements AccountManager {
private final static Logger LOG =
Logger.getLogger(AccountManagerImpl.class.getSimpleName());
protected final DatabaseConfig databaseConfig;
private final CryptoComponent crypto;
@Nullable
private volatile String nickname = null;
public AccountManagerImpl(CryptoComponent crypto,
DatabaseConfig databaseConfig) {
this.crypto = crypto;
this.databaseConfig = databaseConfig;
}
protected abstract boolean storeEncryptedDatabaseKey(String hex);
@Nullable
protected abstract String getEncryptedDatabaseKey();
private boolean hasEncryptedDatabaseKey() {
return getEncryptedDatabaseKey() != null;
}
@Override
@CryptoExecutor
public void createAccount(String name, String password) {
LOG.info("Setting local author name");
this.nickname = name;
SecretKey key = crypto.generateSecretKey();
databaseConfig.setEncryptionKey(key);
String hex = encryptDatabaseKey(key, password);
storeEncryptedDatabaseKey(hex);
}
@Override
public AccountState getAccountState() {
AccountState state;
if (!databaseConfig.databaseExists() && nickname != null &&
hasEncryptedDatabaseKey()) {
state = CREATING_ACCOUNT;
} else if (!hasEncryptedDatabaseKey()) {
state = NO_ACCOUNT;
} else if (databaseConfig.getEncryptionKey() == null) {
state = SIGNED_OUT;
} else {
state = SIGNED_IN;
}
// TODO SIGNING_IN, SIGNING_OUT, DELETING_ACCOUNT
if (LOG.isLoggable(INFO)) LOG.info("Account State: " + state.name());
return state;
}
@Nullable
@Override
public String getCreatedLocalAuthorName() {
String nickname = this.nickname;
if (LOG.isLoggable(INFO))
LOG.info("Local author name has been set: " + (nickname != null));
return nickname;
}
@CryptoExecutor
private String encryptDatabaseKey(SecretKey key, String password) {
long start = now();
byte[] encrypted = crypto.encryptWithPassword(key.getBytes(), password);
logDuration(LOG, "Key derivation", start);
return StringUtils.toHexString(encrypted);
}
@Override
public boolean validatePassword(String password) {
byte[] encrypted = getEncryptedKeyAsBytes();
byte[] key = crypto.decryptWithPassword(encrypted, password);
if (key == null) {
return false;
}
databaseConfig.setEncryptionKey(new SecretKey(key));
return true;
}
@Override
@CryptoExecutor
public boolean changePassword(String password, String newPassword) {
byte[] encrypted = getEncryptedKeyAsBytes();
byte[] key = crypto.decryptWithPassword(encrypted, password);
if (key == null) {
return false;
}
String hex = encryptDatabaseKey(new SecretKey(key), newPassword);
return storeEncryptedDatabaseKey(hex);
}
private byte[] getEncryptedKeyAsBytes() {
String hex = getEncryptedDatabaseKey();
if (hex == null)
throw new IllegalStateException("Encrypted database key is null");
return StringUtils.fromHexString(hex);
}
}

View File

@@ -46,16 +46,6 @@ public class TestDatabaseConfig implements DatabaseConfig {
return key; return key;
} }
@Override
public void setLocalAuthorName(String nickname) {
}
@Override
public String getLocalAuthorName() {
return null;
}
@Override @Override
public long getMaxSize() { public long getMaxSize() {
return maxSize; return maxSize;

View File

@@ -26,10 +26,11 @@
android:theme="@style/BriarTheme"> android:theme="@style/BriarTheme">
<receiver <receiver
android:name="org.briarproject.briar.android.BootReceiver" android:name="org.briarproject.briar.android.login.SignInReminderReceiver"
android:exported="false"> android:exported="false">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
</intent-filter> </intent-filter>
</receiver> </receiver>
@@ -69,7 +70,7 @@
</activity> </activity>
<activity <activity
android:name="org.briarproject.briar.android.login.SetupActivity" android:name="org.briarproject.briar.android.account.SetupActivity"
android:label="@string/setup_title" android:label="@string/setup_title"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
</activity> </activity>

View File

@@ -5,6 +5,7 @@ import android.content.SharedPreferences;
import org.briarproject.bramble.BrambleAndroidModule; import org.briarproject.bramble.BrambleAndroidModule;
import org.briarproject.bramble.BrambleCoreEagerSingletons; import org.briarproject.bramble.BrambleCoreEagerSingletons;
import org.briarproject.bramble.BrambleCoreModule; import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.contact.ContactExchangeTask; import org.briarproject.bramble.api.contact.ContactExchangeTask;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
@@ -26,6 +27,7 @@ import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.BriarCoreEagerSingletons; import org.briarproject.briar.BriarCoreEagerSingletons;
import org.briarproject.briar.BriarCoreModule; import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.login.SignInReminderReceiver;
import org.briarproject.briar.android.reporting.BriarReportSender; import org.briarproject.briar.android.reporting.BriarReportSender;
import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.DozeWatchdog; import org.briarproject.briar.api.android.DozeWatchdog;
@@ -76,6 +78,8 @@ public interface AndroidComponent
DatabaseConfig databaseConfig(); DatabaseConfig databaseConfig();
AccountManager accountManager();
@DatabaseExecutor @DatabaseExecutor
Executor databaseExecutor(); Executor databaseExecutor();
@@ -150,7 +154,7 @@ public interface AndroidComponent
@IoExecutor @IoExecutor
Executor ioExecutor(); Executor ioExecutor();
void inject(BootReceiver briarService); void inject(SignInReminderReceiver briarService);
void inject(BriarService briarService); void inject(BriarService briarService);

View File

@@ -21,8 +21,6 @@ class AndroidDatabaseConfig implements DatabaseConfig {
@Nullable @Nullable
private volatile SecretKey key = null; private volatile SecretKey key = null;
@Nullable
private volatile String nickname = null;
AndroidDatabaseConfig(File dbDir, File keyDir) { AndroidDatabaseConfig(File dbDir, File keyDir) {
this.dbDir = dbDir; this.dbDir = dbDir;
@@ -70,21 +68,6 @@ class AndroidDatabaseConfig implements DatabaseConfig {
this.key = key; this.key = key;
} }
@Override
public void setLocalAuthorName(String nickname) {
LOG.info("Setting local author name");
this.nickname = nickname;
}
@Override
@Nullable
public String getLocalAuthorName() {
String nickname = this.nickname;
if (LOG.isLoggable(INFO))
LOG.info("Local author name has been set: " + (nickname != null));
return nickname;
}
@Override @Override
@Nullable @Nullable
public SecretKey getEncryptionKey() { public SecretKey getEncryptionKey() {

View File

@@ -5,6 +5,7 @@ import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.StrictMode; import android.os.StrictMode;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.db.DatabaseConfig;
@@ -29,6 +30,7 @@ import org.briarproject.bramble.plugin.tor.CircumventionProvider;
import org.briarproject.bramble.plugin.tor.TorPluginFactory; import org.briarproject.bramble.plugin.tor.TorPluginFactory;
import org.briarproject.bramble.util.AndroidUtils; import org.briarproject.bramble.util.AndroidUtils;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.android.account.AndroidAccountManagerImpl;
import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.DozeWatchdog; import org.briarproject.briar.api.android.DozeWatchdog;
import org.briarproject.briar.api.android.ReferenceManager; import org.briarproject.briar.api.android.ReferenceManager;
@@ -94,6 +96,13 @@ public class AppModule {
return databaseConfig; return databaseConfig;
} }
@Provides
@Singleton
AccountManager provideAccountManager(
AndroidAccountManagerImpl androidAccountManager) {
return androidAccountManager;
}
@Provides @Provides
PluginConfig providePluginConfig(@IoExecutor Executor ioExecutor, PluginConfig providePluginConfig(@IoExecutor Executor ioExecutor,
@Scheduler ScheduledExecutorService scheduler, @Scheduler ScheduledExecutorService scheduler,
@@ -163,6 +172,7 @@ public class AppModule {
@Provides @Provides
SharedPreferences provideSharedPreferences(Application app) { SharedPreferences provideSharedPreferences(Application app) {
// FIXME unify this with getDefaultSharedPreferences()
return app.getSharedPreferences("db", MODE_PRIVATE); return app.getSharedPreferences("db", MODE_PRIVATE);
} }

View File

@@ -1,5 +1,8 @@
package org.briarproject.briar.android; package org.briarproject.briar.android;
import android.content.Context;
import android.content.SharedPreferences;
import java.util.Collection; import java.util.Collection;
import java.util.logging.LogRecord; import java.util.logging.LogRecord;
@@ -12,4 +15,8 @@ public interface BriarApplication {
Collection<LogRecord> getRecentLogRecords(); Collection<LogRecord> getRecentLogRecords();
AndroidComponent getApplicationComponent(); AndroidComponent getApplicationComponent();
Context getApplicationContext();
SharedPreferences getDefaultSharedPreferences();
} }

View File

@@ -77,11 +77,12 @@ public class BriarApplicationImpl extends Application
private final CachingLogHandler logHandler = new CachingLogHandler(); private final CachingLogHandler logHandler = new CachingLogHandler();
private AndroidComponent applicationComponent; private AndroidComponent applicationComponent;
private volatile SharedPreferences prefs;
@Override @Override
protected void attachBaseContext(Context base) { protected void attachBaseContext(Context base) {
SharedPreferences prefs = if (prefs == null)
PreferenceManager.getDefaultSharedPreferences(base); prefs = PreferenceManager.getDefaultSharedPreferences(base);
// Loading the language needs to be done here. // Loading the language needs to be done here.
Localizer.initialize(prefs); Localizer.initialize(prefs);
super.attachBaseContext( super.attachBaseContext(
@@ -156,4 +157,9 @@ public class BriarApplicationImpl extends Application
public AndroidComponent getApplicationComponent() { public AndroidComponent getApplicationComponent() {
return applicationComponent; return applicationComponent;
} }
@Override
public SharedPreferences getDefaultSharedPreferences() {
return prefs;
}
} }

View File

@@ -17,7 +17,8 @@ import android.os.IBinder;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.account.AccountState;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult; import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
@@ -48,6 +49,8 @@ import static android.support.v4.app.NotificationCompat.PRIORITY_MIN;
import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET; import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.account.AccountState.CREATING_ACCOUNT;
import static org.briarproject.bramble.api.account.AccountState.SIGNED_IN;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_CHANNEL_ID; import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_CHANNEL_ID;
@@ -75,7 +78,7 @@ public class BriarService extends Service {
private BroadcastReceiver receiver = null; private BroadcastReceiver receiver = null;
@Inject @Inject
protected DatabaseConfig databaseConfig; protected AccountManager accountManager;
// Fields that are accessed from background threads must be volatile // Fields that are accessed from background threads must be volatile
@Inject @Inject
protected volatile LifecycleManager lifecycleManager; protected volatile LifecycleManager lifecycleManager;
@@ -96,7 +99,8 @@ public class BriarService extends Service {
stopSelf(); stopSelf();
return; return;
} }
if (databaseConfig.getEncryptionKey() == null) { AccountState accountState = accountManager.getAccountState();
if (accountState != SIGNED_IN && accountState != CREATING_ACCOUNT) {
LOG.info("No database key"); LOG.info("No database key");
stopSelf(); stopSelf();
return; return;
@@ -141,7 +145,7 @@ public class BriarService extends Service {
nm.cancel(REMINDER_NOTIFICATION_ID); nm.cancel(REMINDER_NOTIFICATION_ID);
// Start the services in a background thread // Start the services in a background thread
new Thread(() -> { new Thread(() -> {
String nickname = databaseConfig.getLocalAuthorName(); String nickname = accountManager.getCreatedLocalAuthorName();
StartResult result = lifecycleManager.startServices(nickname); StartResult result = lifecycleManager.startServices(nickname);
if (result == SUCCESS) { if (result == SUCCESS) {
started = true; started = true;

View File

@@ -1,12 +1,13 @@
package org.briarproject.briar.android.controller; package org.briarproject.briar.android.account;
import android.content.Context; import android.app.Application;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.support.v7.preference.PreferenceManager;
import org.briarproject.bramble.account.AccountManagerImpl;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.util.AndroidUtils; import org.briarproject.briar.android.BriarApplication;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
@@ -20,27 +21,30 @@ import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.AndroidUtils.deleteAppData;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault @NotNullByDefault
public class ConfigControllerImpl implements ConfigController { public class AndroidAccountManagerImpl extends AccountManagerImpl {
private static final Logger LOG = private final static Logger LOG =
Logger.getLogger(ConfigControllerImpl.class.getName()); Logger.getLogger(AndroidAccountManagerImpl.class.getSimpleName());
private static final String PREF_DB_KEY = "key"; private static final String PREF_DB_KEY = "key";
private static final String DB_KEY_FILENAME = "db.key"; private static final String DB_KEY_FILENAME = "db.key";
private static final String DB_KEY_BACKUP_FILENAME = "db.key.bak"; private static final String DB_KEY_BACKUP_FILENAME = "db.key.bak";
private final SharedPreferences briarPrefs; private final BriarApplication app;
private final SharedPreferences dbPrefs;
private final File dbKeyFile, dbKeyBackupFile; private final File dbKeyFile, dbKeyBackupFile;
protected final DatabaseConfig databaseConfig;
@Inject @Inject
public ConfigControllerImpl(SharedPreferences briarPrefs, public AndroidAccountManagerImpl(CryptoComponent crypto,
DatabaseConfig databaseConfig) { DatabaseConfig databaseConfig, Application app,
this.briarPrefs = briarPrefs; SharedPreferences dbPrefs) {
this.databaseConfig = databaseConfig; super(crypto, databaseConfig);
this.app = (BriarApplication) app;
this.dbPrefs = dbPrefs;
File keyDir = databaseConfig.getDatabaseKeyDirectory(); File keyDir = databaseConfig.getDatabaseKeyDirectory();
dbKeyFile = new File(keyDir, DB_KEY_FILENAME); dbKeyFile = new File(keyDir, DB_KEY_FILENAME);
dbKeyBackupFile = new File(keyDir, DB_KEY_BACKUP_FILENAME); dbKeyBackupFile = new File(keyDir, DB_KEY_BACKUP_FILENAME);
@@ -48,7 +52,7 @@ public class ConfigControllerImpl implements ConfigController {
@Override @Override
@Nullable @Nullable
public String getEncryptedDatabaseKey() { protected String getEncryptedDatabaseKey() {
String key = getDatabaseKeyFromPreferences(); String key = getDatabaseKeyFromPreferences();
if (key == null) key = getDatabaseKeyFromFile(); if (key == null) key = getDatabaseKeyFromFile();
else migrateDatabaseKeyToFile(key); else migrateDatabaseKeyToFile(key);
@@ -57,7 +61,7 @@ public class ConfigControllerImpl implements ConfigController {
@Nullable @Nullable
private String getDatabaseKeyFromPreferences() { private String getDatabaseKeyFromPreferences() {
String key = briarPrefs.getString(PREF_DB_KEY, null); String key = dbPrefs.getString(PREF_DB_KEY, null);
if (key == null) LOG.info("No database key in preferences"); if (key == null) LOG.info("No database key in preferences");
else LOG.info("Found database key in preferences"); else LOG.info("Found database key in preferences");
return key; return key;
@@ -97,7 +101,7 @@ public class ConfigControllerImpl implements ConfigController {
private void migrateDatabaseKeyToFile(String key) { private void migrateDatabaseKeyToFile(String key) {
if (storeEncryptedDatabaseKey(key)) { if (storeEncryptedDatabaseKey(key)) {
if (briarPrefs.edit().remove(PREF_DB_KEY).commit()) if (dbPrefs.edit().remove(PREF_DB_KEY).commit())
LOG.info("Database key migrated to file"); LOG.info("Database key migrated to file");
else LOG.warning("Database key not removed from preferences"); else LOG.warning("Database key not removed from preferences");
} else { } else {
@@ -106,7 +110,7 @@ public class ConfigControllerImpl implements ConfigController {
} }
@Override @Override
public boolean storeEncryptedDatabaseKey(String hex) { protected boolean storeEncryptedDatabaseKey(String hex) {
LOG.info("Storing database key in file"); LOG.info("Storing database key in file");
// Create the directory if necessary // Create the directory if necessary
if (databaseConfig.getDatabaseKeyDirectory().mkdirs()) if (databaseConfig.getDatabaseKeyDirectory().mkdirs())
@@ -151,21 +155,10 @@ public class ConfigControllerImpl implements ConfigController {
} }
@Override @Override
public void deleteAccount(Context ctx) { public void deleteAccount() {
LOG.info("Deleting account"); LOG.info("Deleting account");
SharedPreferences defaultPrefs = SharedPreferences defaultPrefs = app.getDefaultSharedPreferences();
PreferenceManager.getDefaultSharedPreferences(ctx); deleteAppData(app.getApplicationContext(), dbPrefs, defaultPrefs);
AndroidUtils.deleteAppData(ctx, briarPrefs, defaultPrefs);
} }
@Override
public boolean accountExists() {
String hex = getEncryptedDatabaseKey();
return hex != null && databaseConfig.databaseExists();
}
@Override
public boolean accountSignedIn() {
return databaseConfig.getEncryptionKey() != null;
}
} }

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.account;
import android.os.Bundle; import android.os.Bundle;
import android.support.design.widget.TextInputEditText; import android.support.design.widget.TextInputEditText;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.account;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Intent; import android.content.Intent;
@@ -13,8 +13,8 @@ import android.widget.ProgressBar;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.account.PowerView.OnCheckedChangedListener;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.login.PowerView.OnCheckedChangedListener;
import org.briarproject.briar.android.util.UiUtils; import org.briarproject.briar.android.util.UiUtils;
import static android.view.View.INVISIBLE; import static android.view.View.INVISIBLE;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.account;
import android.content.Context; import android.content.Context;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.account;
import android.content.Context; import android.content.Context;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.account;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
@@ -15,6 +15,7 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.login.StrengthMeter;
import org.briarproject.briar.android.util.UiUtils; import org.briarproject.briar.android.util.UiUtils;
import javax.annotation.Nullable; import javax.annotation.Nullable;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.account;
import android.content.Context; import android.content.Context;
import android.os.Parcel; import android.os.Parcel;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.account;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Intent; import android.content.Intent;
@@ -10,6 +10,7 @@ import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BaseActivity; import org.briarproject.briar.android.activity.BaseActivity;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener; import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import org.briarproject.briar.android.login.OpenDatabaseActivity;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;

View File

@@ -1,6 +1,7 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.account;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.login.PasswordController;
@NotNullByDefault @NotNullByDefault
public interface SetupController extends PasswordController { public interface SetupController extends PasswordController {

View File

@@ -1,16 +1,15 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.account;
import android.content.SharedPreferences;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.CryptoExecutor; import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator; import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.controller.handler.ResultHandler; import org.briarproject.briar.android.controller.handler.ResultHandler;
import org.briarproject.briar.android.controller.handler.UiResultHandler; import org.briarproject.briar.android.controller.handler.UiResultHandler;
import org.briarproject.briar.android.login.PasswordControllerImpl;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -28,11 +27,10 @@ public class SetupControllerImpl extends PasswordControllerImpl
private volatile SetupActivity setupActivity; private volatile SetupActivity setupActivity;
@Inject @Inject
SetupControllerImpl(SharedPreferences briarPrefs, SetupControllerImpl(AccountManager accountManager,
DatabaseConfig databaseConfig,
@CryptoExecutor Executor cryptoExecutor, CryptoComponent crypto, @CryptoExecutor Executor cryptoExecutor, CryptoComponent crypto,
PasswordStrengthEstimator strengthEstimator) { PasswordStrengthEstimator strengthEstimator) {
super(briarPrefs, databaseConfig, cryptoExecutor, crypto, super(accountManager, cryptoExecutor, crypto,
strengthEstimator); strengthEstimator);
} }
@@ -102,11 +100,7 @@ public class SetupControllerImpl extends PasswordControllerImpl
if (password == null) throw new IllegalStateException(); if (password == null) throw new IllegalStateException();
cryptoExecutor.execute(() -> { cryptoExecutor.execute(() -> {
LOG.info("Creating account"); LOG.info("Creating account");
databaseConfig.setLocalAuthorName(authorName); accountManager.createAccount(authorName, password);
SecretKey key = crypto.generateSecretKey();
databaseConfig.setEncryptionKey(key);
String hex = encryptDatabaseKey(key, password);
storeEncryptedDatabaseKey(hex);
resultHandler.onResult(null); resultHandler.onResult(null);
}); });
} }

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.account;
import android.text.Editable; import android.text.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
@@ -17,7 +17,7 @@ import javax.inject.Inject;
import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog; import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
abstract class SetupFragment extends BaseFragment implements TextWatcher, public abstract class SetupFragment extends BaseFragment implements TextWatcher,
OnEditorActionListener, OnClickListener { OnEditorActionListener, OnClickListener {
@Inject @Inject

View File

@@ -30,13 +30,13 @@ import org.briarproject.briar.android.keyagreement.ContactExchangeActivity;
import org.briarproject.briar.android.keyagreement.IntroFragment; import org.briarproject.briar.android.keyagreement.IntroFragment;
import org.briarproject.briar.android.keyagreement.KeyAgreementActivity; import org.briarproject.briar.android.keyagreement.KeyAgreementActivity;
import org.briarproject.briar.android.keyagreement.KeyAgreementFragment; import org.briarproject.briar.android.keyagreement.KeyAgreementFragment;
import org.briarproject.briar.android.login.AuthorNameFragment; import org.briarproject.briar.android.account.AuthorNameFragment;
import org.briarproject.briar.android.login.ChangePasswordActivity; import org.briarproject.briar.android.login.ChangePasswordActivity;
import org.briarproject.briar.android.login.DozeFragment; import org.briarproject.briar.android.account.DozeFragment;
import org.briarproject.briar.android.login.OpenDatabaseActivity; import org.briarproject.briar.android.login.OpenDatabaseActivity;
import org.briarproject.briar.android.login.PasswordActivity; import org.briarproject.briar.android.login.PasswordActivity;
import org.briarproject.briar.android.login.PasswordFragment; import org.briarproject.briar.android.account.PasswordFragment;
import org.briarproject.briar.android.login.SetupActivity; import org.briarproject.briar.android.account.SetupActivity;
import org.briarproject.briar.android.navdrawer.NavDrawerActivity; import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
import org.briarproject.briar.android.panic.PanicPreferencesActivity; import org.briarproject.briar.android.panic.PanicPreferencesActivity;
import org.briarproject.briar.android.panic.PanicResponderActivity; import org.briarproject.briar.android.panic.PanicResponderActivity;

View File

@@ -4,14 +4,12 @@ import android.app.Activity;
import org.briarproject.briar.android.controller.BriarController; import org.briarproject.briar.android.controller.BriarController;
import org.briarproject.briar.android.controller.BriarControllerImpl; import org.briarproject.briar.android.controller.BriarControllerImpl;
import org.briarproject.briar.android.controller.ConfigController;
import org.briarproject.briar.android.controller.ConfigControllerImpl;
import org.briarproject.briar.android.controller.DbController; import org.briarproject.briar.android.controller.DbController;
import org.briarproject.briar.android.controller.DbControllerImpl; import org.briarproject.briar.android.controller.DbControllerImpl;
import org.briarproject.briar.android.login.PasswordController; import org.briarproject.briar.android.login.PasswordController;
import org.briarproject.briar.android.login.PasswordControllerImpl; import org.briarproject.briar.android.login.PasswordControllerImpl;
import org.briarproject.briar.android.login.SetupController; import org.briarproject.briar.android.account.SetupController;
import org.briarproject.briar.android.login.SetupControllerImpl; import org.briarproject.briar.android.account.SetupControllerImpl;
import org.briarproject.briar.android.navdrawer.NavDrawerController; import org.briarproject.briar.android.navdrawer.NavDrawerController;
import org.briarproject.briar.android.navdrawer.NavDrawerControllerImpl; import org.briarproject.briar.android.navdrawer.NavDrawerControllerImpl;
@@ -48,13 +46,6 @@ public class ActivityModule {
return setupController; return setupController;
} }
@ActivityScope
@Provides
ConfigController provideConfigController(
ConfigControllerImpl configController) {
return configController;
}
@ActivityScope @ActivityScope
@Provides @Provides
PasswordController providePasswordController( PasswordController providePasswordController(

View File

@@ -61,7 +61,7 @@ public abstract class BriarActivity extends BaseActivity {
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
if (!briarController.hasEncryptionKey() && !isFinishing()) { if (!briarController.signedIn() && !isFinishing()) {
Intent i = new Intent(this, PasswordActivity.class); Intent i = new Intent(this, PasswordActivity.class);
startActivityForResult(i, REQUEST_PASSWORD); startActivityForResult(i, REQUEST_PASSWORD);
} else if (SDK_INT >= 23) { } else if (SDK_INT >= 23) {
@@ -138,7 +138,7 @@ public abstract class BriarActivity extends BaseActivity {
} }
protected void signOut(boolean removeFromRecentApps) { protected void signOut(boolean removeFromRecentApps) {
if (briarController.hasEncryptionKey()) { if (briarController.signedIn()) {
// Don't use UiResultHandler because we want the result even if // Don't use UiResultHandler because we want the result even if
// this activity has been destroyed // this activity has been destroyed
briarController.signOut(result -> runOnUiThread( briarController.signOut(result -> runOnUiThread(

View File

@@ -6,7 +6,7 @@ public interface BriarController extends ActivityLifecycleController {
void startAndBindService(); void startAndBindService();
boolean hasEncryptionKey(); boolean signedIn();
/** /**
* Returns true via the handler when the app has dozed * Returns true via the handler when the app has dozed

View File

@@ -5,7 +5,8 @@ import android.content.Intent;
import android.os.IBinder; import android.os.IBinder;
import android.support.annotation.CallSuper; import android.support.annotation.CallSuper;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.account.AccountState;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
@@ -21,6 +22,8 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.account.AccountState.CREATING_ACCOUNT;
import static org.briarproject.bramble.api.account.AccountState.SIGNED_IN;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE; import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting; import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting;
@@ -33,7 +36,7 @@ public class BriarControllerImpl implements BriarController {
public static final String DOZE_ASK_AGAIN = "dozeAskAgain"; public static final String DOZE_ASK_AGAIN = "dozeAskAgain";
private final BriarServiceConnection serviceConnection; private final BriarServiceConnection serviceConnection;
private final DatabaseConfig databaseConfig; private final AccountManager accountManager;
@DatabaseExecutor @DatabaseExecutor
private final Executor databaseExecutor; private final Executor databaseExecutor;
private final SettingsManager settingsManager; private final SettingsManager settingsManager;
@@ -44,12 +47,12 @@ public class BriarControllerImpl implements BriarController {
@Inject @Inject
BriarControllerImpl(BriarServiceConnection serviceConnection, BriarControllerImpl(BriarServiceConnection serviceConnection,
DatabaseConfig databaseConfig, AccountManager accountManager,
@DatabaseExecutor Executor databaseExecutor, @DatabaseExecutor Executor databaseExecutor,
SettingsManager settingsManager, DozeWatchdog dozeWatchdog, SettingsManager settingsManager, DozeWatchdog dozeWatchdog,
Activity activity) { Activity activity) {
this.serviceConnection = serviceConnection; this.serviceConnection = serviceConnection;
this.databaseConfig = databaseConfig; this.accountManager = accountManager;
this.databaseExecutor = databaseExecutor; this.databaseExecutor = databaseExecutor;
this.settingsManager = settingsManager; this.settingsManager = settingsManager;
this.dozeWatchdog = dozeWatchdog; this.dozeWatchdog = dozeWatchdog;
@@ -59,7 +62,7 @@ public class BriarControllerImpl implements BriarController {
@Override @Override
@CallSuper @CallSuper
public void onActivityCreate(Activity activity) { public void onActivityCreate(Activity activity) {
if (databaseConfig.getEncryptionKey() != null) startAndBindService(); if (signedIn()) startAndBindService();
} }
@Override @Override
@@ -84,8 +87,9 @@ public class BriarControllerImpl implements BriarController {
} }
@Override @Override
public boolean hasEncryptionKey() { public boolean signedIn() {
return databaseConfig.getEncryptionKey() != null; AccountState state = accountManager.getAccountState();
return state == CREATING_ACCOUNT || state == SIGNED_IN;
} }
@Override @Override

View File

@@ -1,23 +0,0 @@
package org.briarproject.briar.android.controller;
import android.content.Context;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
@NotNullByDefault
public interface ConfigController {
@Nullable
String getEncryptedDatabaseKey();
boolean storeEncryptedDatabaseKey(String hex);
void deleteAccount(Context ctx);
boolean accountExists();
boolean accountSignedIn();
}

View File

@@ -14,6 +14,7 @@ import android.widget.ProgressBar;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.Localizer; import org.briarproject.briar.android.Localizer;
import org.briarproject.briar.android.account.SetupActivity;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BaseActivity; import org.briarproject.briar.android.activity.BaseActivity;
import org.briarproject.briar.android.controller.BriarController; import org.briarproject.briar.android.controller.BriarController;
@@ -85,7 +86,7 @@ public class PasswordActivity extends BaseActivity {
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
// If the user has already signed in, clean up this instance // If the user has already signed in, clean up this instance
if (briarController.hasEncryptionKey()) { if (briarController.signedIn()) {
setResult(RESULT_OK); setResult(RESULT_OK);
finish(); finish();
} }
@@ -105,7 +106,7 @@ public class PasswordActivity extends BaseActivity {
} }
private void deleteAccount() { private void deleteAccount() {
passwordController.deleteAccount(this); passwordController.deleteAccount();
Localizer.reinitialize(); Localizer.reinitialize();
UiUtils.setTheme(this, getString(R.string.pref_theme_light_value)); UiUtils.setTheme(this, getString(R.string.pref_theme_light_value));
setResult(RESULT_CANCELED); setResult(RESULT_CANCELED);

View File

@@ -1,11 +1,12 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.login;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.controller.ConfigController;
import org.briarproject.briar.android.controller.handler.ResultHandler; import org.briarproject.briar.android.controller.handler.ResultHandler;
@NotNullByDefault @NotNullByDefault
public interface PasswordController extends ConfigController { public interface PasswordController {
boolean accountExists();
float estimatePasswordStrength(String password); float estimatePasswordStrength(String password);
@@ -15,4 +16,6 @@ public interface PasswordController extends ConfigController {
void changePassword(String password, String newPassword, void changePassword(String password, String newPassword,
ResultHandler<Boolean> resultHandler); ResultHandler<Boolean> resultHandler);
void deleteAccount();
} }

View File

@@ -1,47 +1,41 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.login;
import android.content.SharedPreferences; import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.CryptoExecutor; import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator; import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.android.controller.ConfigControllerImpl;
import org.briarproject.briar.android.controller.handler.ResultHandler; import org.briarproject.briar.android.controller.handler.ResultHandler;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.api.account.AccountState.NO_ACCOUNT;
import static org.briarproject.bramble.util.LogUtils.now;
@NotNullByDefault @NotNullByDefault
public class PasswordControllerImpl extends ConfigControllerImpl public class PasswordControllerImpl implements PasswordController {
implements PasswordController {
private static final Logger LOG =
Logger.getLogger(PasswordControllerImpl.class.getName());
protected final AccountManager accountManager;
protected final Executor cryptoExecutor; protected final Executor cryptoExecutor;
protected final CryptoComponent crypto; protected final CryptoComponent crypto;
private final PasswordStrengthEstimator strengthEstimator; private final PasswordStrengthEstimator strengthEstimator;
@Inject @Inject
PasswordControllerImpl(SharedPreferences briarPrefs, public PasswordControllerImpl(AccountManager accountManager,
DatabaseConfig databaseConfig,
@CryptoExecutor Executor cryptoExecutor, CryptoComponent crypto, @CryptoExecutor Executor cryptoExecutor, CryptoComponent crypto,
PasswordStrengthEstimator strengthEstimator) { PasswordStrengthEstimator strengthEstimator) {
super(briarPrefs, databaseConfig); this.accountManager = accountManager;
this.cryptoExecutor = cryptoExecutor; this.cryptoExecutor = cryptoExecutor;
this.crypto = crypto; this.crypto = crypto;
this.strengthEstimator = strengthEstimator; this.strengthEstimator = strengthEstimator;
} }
@Override
public boolean accountExists() {
return accountManager.getAccountState() != NO_ACCOUNT;
}
@Override @Override
public float estimatePasswordStrength(String password) { public float estimatePasswordStrength(String password) {
return strengthEstimator.estimateStrength(password); return strengthEstimator.estimateStrength(password);
@@ -50,46 +44,25 @@ public class PasswordControllerImpl extends ConfigControllerImpl
@Override @Override
public void validatePassword(String password, public void validatePassword(String password,
ResultHandler<Boolean> resultHandler) { ResultHandler<Boolean> resultHandler) {
byte[] encrypted = getEncryptedKey();
cryptoExecutor.execute(() -> { cryptoExecutor.execute(() -> {
byte[] key = crypto.decryptWithPassword(encrypted, password); boolean result = accountManager.validatePassword(password);
if (key == null) { resultHandler.onResult(result);
resultHandler.onResult(false);
} else {
databaseConfig.setEncryptionKey(new SecretKey(key));
resultHandler.onResult(true);
}
}); });
} }
@Override @Override
public void changePassword(String password, String newPassword, public void changePassword(String password, String newPassword,
ResultHandler<Boolean> resultHandler) { ResultHandler<Boolean> resultHandler) {
byte[] encrypted = getEncryptedKey();
cryptoExecutor.execute(() -> { cryptoExecutor.execute(() -> {
byte[] key = crypto.decryptWithPassword(encrypted, password); boolean result =
if (key == null) { accountManager.changePassword(password, newPassword);
resultHandler.onResult(false); resultHandler.onResult(result);
} else {
String hex =
encryptDatabaseKey(new SecretKey(key), newPassword);
resultHandler.onResult(storeEncryptedDatabaseKey(hex));
}
}); });
} }
private byte[] getEncryptedKey() { @Override
String hex = getEncryptedDatabaseKey(); public void deleteAccount() {
if (hex == null) accountManager.deleteAccount();
throw new IllegalStateException("Encrypted database key is null");
return StringUtils.fromHexString(hex);
} }
@CryptoExecutor
String encryptDatabaseKey(SecretKey key, String password) {
long start = now();
byte[] encrypted = crypto.encryptWithPassword(key.getBytes(), password);
logDuration(LOG, "Key derivation", start);
return StringUtils.toHexString(encrypted);
}
} }

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android; package org.briarproject.briar.android.login;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
@@ -6,45 +6,57 @@ import android.app.PendingIntent;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.AndroidComponent;
import org.briarproject.briar.android.BriarApplication;
import org.briarproject.briar.android.navdrawer.NavDrawerActivity; import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
import org.briarproject.briar.android.settings.SettingsActivity;
import javax.inject.Inject; import javax.inject.Inject;
import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.content.Context.NOTIFICATION_SERVICE; import static android.content.Context.NOTIFICATION_SERVICE;
import static android.content.Intent.ACTION_BOOT_COMPLETED; import static android.content.Intent.ACTION_BOOT_COMPLETED;
import static android.content.Intent.ACTION_MY_PACKAGE_REPLACED;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static android.support.v4.app.NotificationCompat.PRIORITY_LOW; import static android.support.v4.app.NotificationCompat.PRIORITY_LOW;
import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET; import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET;
import static org.briarproject.bramble.api.account.AccountState.NO_ACCOUNT;
import static org.briarproject.briar.android.TestingConstants.FEATURE_FLAG_SIGN_IN_REMINDER; import static org.briarproject.briar.android.TestingConstants.FEATURE_FLAG_SIGN_IN_REMINDER;
import static org.briarproject.briar.android.settings.SettingsActivity.NO_NOTIFY_SIGN_IN;
import static org.briarproject.briar.android.settings.SettingsFragment.NOTIFY_SIGN_IN;
import static org.briarproject.briar.api.android.AndroidNotificationManager.REMINDER_CHANNEL_ID; import static org.briarproject.briar.api.android.AndroidNotificationManager.REMINDER_CHANNEL_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.REMINDER_NOTIFICATION_ID; import static org.briarproject.briar.api.android.AndroidNotificationManager.REMINDER_NOTIFICATION_ID;
public class BootReceiver extends BroadcastReceiver { public class SignInReminderReceiver extends BroadcastReceiver {
@Inject @Inject
DatabaseConfig databaseConfig; AccountManager accountManager;
@Override @Override
public void onReceive(Context ctx, Intent intent) { public void onReceive(Context ctx, Intent intent) {
if (!FEATURE_FLAG_SIGN_IN_REMINDER) return; if (!FEATURE_FLAG_SIGN_IN_REMINDER) return;
AndroidComponent applicationComponent = BriarApplication app = (BriarApplication) ctx.getApplicationContext();
((BriarApplication) ctx.getApplicationContext()) AndroidComponent applicationComponent = app.getApplicationComponent();
.getApplicationComponent();
applicationComponent.inject(this); applicationComponent.inject(this);
String action = intent.getAction(); String action = intent.getAction();
if (action != null && action.equals(ACTION_BOOT_COMPLETED)) { if (action == null) return;
if (databaseConfig.databaseExists()) { if (action.equals(ACTION_BOOT_COMPLETED) ||
showSignInNotification(ctx); action.equals(ACTION_MY_PACKAGE_REPLACED)) {
if (accountManager.getAccountState() != NO_ACCOUNT) {
SharedPreferences prefs = app.getDefaultSharedPreferences();
if (prefs.getBoolean(NOTIFY_SIGN_IN, true)) {
showSignInNotification(ctx);
}
} }
} }
} }
@@ -73,6 +85,14 @@ public class BootReceiver extends BroadcastReceiver {
b.setWhen(0); // Don't show the time b.setWhen(0); // Don't show the time
b.setPriority(PRIORITY_LOW); b.setPriority(PRIORITY_LOW);
// Add a 'Do not show sign-in reminder' action
String actionTitle =
ctx.getString(R.string.reminder_notification_do_not_show_again);
Intent i1 = new Intent(ctx, SettingsActivity.class);
i1.setAction(NO_NOTIFY_SIGN_IN);
PendingIntent actionIntent = PendingIntent.getActivity(ctx, 0, i1, 0);
b.addAction(0, actionTitle, actionIntent);
Intent i = new Intent(ctx, NavDrawerActivity.class); Intent i = new Intent(ctx, NavDrawerActivity.class);
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP); i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);
b.setContentIntent(PendingIntent.getActivity(ctx, 0, i, 0)); b.setContentIntent(PendingIntent.getActivity(ctx, 0, i, 0));

View File

@@ -7,10 +7,10 @@ import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.preference.PreferenceManager; import android.support.v7.preference.PreferenceManager;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity; import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.controller.ConfigController;
import org.iilab.IilabEngineeringRSA2048Pin; import org.iilab.IilabEngineeringRSA2048Pin;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -33,7 +33,7 @@ public class PanicResponderActivity extends BriarActivity {
Logger.getLogger(PanicResponderActivity.class.getName()); Logger.getLogger(PanicResponderActivity.class.getName());
@Inject @Inject
protected ConfigController configController; protected AccountManager accountManager;
@Inject @Inject
protected AndroidExecutor androidExecutor; protected AndroidExecutor androidExecutor;
@@ -94,7 +94,7 @@ public class PanicResponderActivity extends BriarActivity {
private void deleteAllData() { private void deleteAllData() {
androidExecutor.runOnBackgroundThread(() -> { androidExecutor.runOnBackgroundThread(() -> {
configController.deleteAccount(PanicResponderActivity.this); accountManager.deleteAccount();
// TODO somehow delete/shred the database more thoroughly // TODO somehow delete/shred the database more thoroughly
PanicResponder.deleteAllAppData(PanicResponderActivity.this); PanicResponder.deleteAllAppData(PanicResponderActivity.this);

View File

@@ -1,19 +1,43 @@
package org.briarproject.briar.android.settings; package org.briarproject.briar.android.settings;
import android.app.NotificationManager;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBar;
import android.view.MenuItem; import android.view.MenuItem;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.BriarApplication;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity; import org.briarproject.briar.android.activity.BriarActivity;
import static org.briarproject.briar.android.settings.SettingsFragment.NOTIFY_SIGN_IN;
import static org.briarproject.briar.api.android.AndroidNotificationManager.REMINDER_NOTIFICATION_ID;
public class SettingsActivity extends BriarActivity { public class SettingsActivity extends BriarActivity {
public static final String NO_NOTIFY_SIGN_IN = "noNotifySignIn";
@Override @Override
public void onCreate(Bundle bundle) { public void onCreate(Bundle bundle) {
super.onCreate(bundle); super.onCreate(bundle);
// Maybe turn off sign-in reminder
Intent intent = getIntent();
if (intent != null && NO_NOTIFY_SIGN_IN.equals(intent.getAction())) {
// Turn it off
BriarApplication app = (BriarApplication) getApplication();
SharedPreferences prefs = app.getDefaultSharedPreferences();
prefs.edit().putBoolean(NOTIFY_SIGN_IN, false).apply();
// Remove sign-in reminder notification
NotificationManager nm = (NotificationManager)
getSystemService(NOTIFICATION_SERVICE);
if (nm != null) nm.cancel(REMINDER_NOTIFICATION_ID);
// Finish this activity again
finish();
}
ActionBar actionBar = getSupportActionBar(); ActionBar actionBar = getSupportActionBar();
if (actionBar != null) { if (actionBar != null) {
actionBar.setHomeButtonEnabled(true); actionBar.setHomeButtonEnabled(true);

View File

@@ -71,6 +71,7 @@ import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.android.TestingConstants.FEATURE_FLAG_DARK_THEME; import static org.briarproject.briar.android.TestingConstants.FEATURE_FLAG_DARK_THEME;
import static org.briarproject.briar.android.TestingConstants.FEATURE_FLAG_SIGN_IN_REMINDER;
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_RINGTONE; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_RINGTONE;
import static org.briarproject.briar.android.navdrawer.NavDrawerActivity.INTENT_SIGN_OUT; import static org.briarproject.briar.android.navdrawer.NavDrawerActivity.INTENT_SIGN_OUT;
@@ -97,6 +98,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
public static final String BT_NAMESPACE = BluetoothConstants.ID.getString(); public static final String BT_NAMESPACE = BluetoothConstants.ID.getString();
public static final String TOR_NAMESPACE = TorConstants.ID.getString(); public static final String TOR_NAMESPACE = TorConstants.ID.getString();
public static final String LANGUAGE = "pref_key_language"; public static final String LANGUAGE = "pref_key_language";
public static final String NOTIFY_SIGN_IN = "pref_key_notify_sign_in";
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(SettingsFragment.class.getName()); Logger.getLogger(SettingsFragment.class.getName());
@@ -143,6 +145,8 @@ public class SettingsFragment extends PreferenceFragmentCompat
(ListPreference) findPreference("pref_key_theme"); (ListPreference) findPreference("pref_key_theme");
enableBluetooth = (ListPreference) findPreference("pref_key_bluetooth"); enableBluetooth = (ListPreference) findPreference("pref_key_bluetooth");
torNetwork = (ListPreference) findPreference("pref_key_tor_network"); torNetwork = (ListPreference) findPreference("pref_key_tor_network");
CheckBoxPreference notifySignIn =
(CheckBoxPreference) findPreference(NOTIFY_SIGN_IN);
notifyPrivateMessages = (CheckBoxPreference) findPreference( notifyPrivateMessages = (CheckBoxPreference) findPreference(
"pref_key_notify_private_messages"); "pref_key_notify_private_messages");
notifyGroupMessages = (CheckBoxPreference) findPreference( notifyGroupMessages = (CheckBoxPreference) findPreference(
@@ -199,6 +203,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
); );
} else { } else {
theme.setVisible(FEATURE_FLAG_DARK_THEME); theme.setVisible(FEATURE_FLAG_DARK_THEME);
notifySignIn.setVisible(FEATURE_FLAG_SIGN_IN_REMINDER);
findPreference("pref_key_explode").setVisible(false); findPreference("pref_key_explode").setVisible(false);
findPreference("pref_key_test_data").setVisible(false); findPreference("pref_key_test_data").setVisible(false);
@@ -346,7 +351,9 @@ public class SettingsFragment extends PreferenceFragmentCompat
} }
private void setSettingsEnabled(boolean enabled) { private void setSettingsEnabled(boolean enabled) {
// theme not needed here, because handled by SharedPreferences // preferences not needed here, because handled by SharedPreferences:
// - pref_key_theme
// - pref_key_notify_sign_in
enableBluetooth.setEnabled(enabled); enableBluetooth.setEnabled(enabled);
torNetwork.setEnabled(enabled); torNetwork.setEnabled(enabled);
notifyPrivateMessages.setEnabled(enabled); notifyPrivateMessages.setEnabled(enabled);

View File

@@ -7,18 +7,20 @@ import android.os.Handler;
import android.support.v7.preference.PreferenceManager; import android.support.v7.preference.PreferenceManager;
import android.transition.Fade; import android.transition.Fade;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.account.SetupActivity;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BaseActivity; import org.briarproject.briar.android.activity.BaseActivity;
import org.briarproject.briar.android.controller.ConfigController;
import org.briarproject.briar.android.login.OpenDatabaseActivity; import org.briarproject.briar.android.login.OpenDatabaseActivity;
import org.briarproject.briar.android.login.SetupActivity;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.bramble.api.account.AccountState.NO_ACCOUNT;
import static org.briarproject.bramble.api.account.AccountState.SIGNED_IN;
import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE; import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE;
public class SplashScreenActivity extends BaseActivity { public class SplashScreenActivity extends BaseActivity {
@@ -27,7 +29,7 @@ public class SplashScreenActivity extends BaseActivity {
Logger.getLogger(SplashScreenActivity.class.getName()); Logger.getLogger(SplashScreenActivity.class.getName());
@Inject @Inject
protected ConfigController configController; AccountManager accountManager;
@Inject @Inject
protected AndroidExecutor androidExecutor; protected AndroidExecutor androidExecutor;
@@ -43,7 +45,7 @@ public class SplashScreenActivity extends BaseActivity {
setContentView(R.layout.splash); setContentView(R.layout.splash);
if (configController.accountSignedIn()) { if (accountManager.getAccountState() == SIGNED_IN) {
startActivity(new Intent(this, OpenDatabaseActivity.class)); startActivity(new Intent(this, OpenDatabaseActivity.class));
finish(); finish();
} else { } else {
@@ -64,12 +66,12 @@ public class SplashScreenActivity extends BaseActivity {
LOG.info("Expired"); LOG.info("Expired");
startActivity(new Intent(this, ExpiredActivity.class)); startActivity(new Intent(this, ExpiredActivity.class));
} else { } else {
if (configController.accountExists()) { if (accountManager.getAccountState() != NO_ACCOUNT) {
LOG.info("Account exists"); LOG.info("Account exists");
startActivity(new Intent(this, OpenDatabaseActivity.class)); startActivity(new Intent(this, OpenDatabaseActivity.class));
} else { } else {
LOG.info("Account does not exist"); LOG.info("Account does not exist");
configController.deleteAccount(this); accountManager.deleteAccount();
startActivity(new Intent(this, SetupActivity.class)); startActivity(new Intent(this, SetupActivity.class));
} }
} }

View File

@@ -71,6 +71,7 @@
<string name="reminder_notification_title">Signed out of Briar</string> <string name="reminder_notification_title">Signed out of Briar</string>
<string name="reminder_notification_text">Tap to sign back in or swipe to dismiss.</string> <string name="reminder_notification_text">Tap to sign back in or swipe to dismiss.</string>
<string name="reminder_notification_channel_title">Briar Sign-in Reminder</string> <string name="reminder_notification_channel_title">Briar Sign-in Reminder</string>
<string name="reminder_notification_do_not_show_again">Don\'t show again</string>
<string name="ongoing_notification_title">Signed into Briar</string> <string name="ongoing_notification_title">Signed into Briar</string>
<string name="ongoing_notification_text">Touch to open Briar.</string> <string name="ongoing_notification_text">Touch to open Briar.</string>
<plurals name="private_message_notification_text"> <plurals name="private_message_notification_text">
@@ -373,6 +374,8 @@
<!-- Settings Notifications --> <!-- Settings Notifications -->
<string name="notification_settings_title">Notifications</string> <string name="notification_settings_title">Notifications</string>
<string name="notify_sign_in_title">Remind me to sign in</string>
<string name="notify_sign_in_summary">Show a reminder when the phone starts or the app got updated</string>
<string name="notify_private_messages_setting_title">Private messages</string> <string name="notify_private_messages_setting_title">Private messages</string>
<string name="notify_private_messages_setting_summary">Show alerts for private messages</string> <string name="notify_private_messages_setting_summary">Show alerts for private messages</string>
<string name="notify_private_messages_setting_summary_26">Configure alerts for private messages</string> <string name="notify_private_messages_setting_summary_26">Configure alerts for private messages</string>

View File

@@ -81,6 +81,12 @@
android:layout="@layout/preferences_category" android:layout="@layout/preferences_category"
android:title="@string/notification_settings_title"> android:title="@string/notification_settings_title">
<CheckBoxPreference
android:defaultValue="true"
android:key="pref_key_notify_sign_in"
android:summary="@string/notify_sign_in_summary"
android:title="@string/notify_sign_in_title"/>
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="true" android:defaultValue="true"
android:key="pref_key_notify_private_messages" android:key="pref_key_notify_private_messages"

View File

@@ -23,13 +23,14 @@ public class TestBriarApplication extends Application
Logger.getLogger(TestBriarApplication.class.getName()); Logger.getLogger(TestBriarApplication.class.getName());
private AndroidComponent applicationComponent; private AndroidComponent applicationComponent;
private SharedPreferences prefs;
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
LOG.info("Created"); LOG.info("Created");
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); prefs = PreferenceManager.getDefaultSharedPreferences(this);
Localizer.initialize(prefs); Localizer.initialize(prefs);
applicationComponent = DaggerAndroidComponent.builder() applicationComponent = DaggerAndroidComponent.builder()
.appModule(new AppModule(this)) .appModule(new AppModule(this))
@@ -51,4 +52,9 @@ public class TestBriarApplication extends Application
public AndroidComponent getApplicationComponent() { public AndroidComponent getApplicationComponent() {
return applicationComponent; return applicationComponent;
} }
@Override
public SharedPreferences getDefaultSharedPreferences() {
return prefs;
}
} }

View File

@@ -0,0 +1,368 @@
package org.briarproject.briar.android.account;
import android.app.Application;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.junit.After;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.briarproject.bramble.util.StringUtils.toHexString;
import static org.briarproject.briar.android.TestDatabaseKeyUtils.loadDatabaseKey;
import static org.briarproject.briar.android.TestDatabaseKeyUtils.storeDatabaseKey;
import static org.hamcrest.Matchers.samePropertyValuesAs;
public class AndroidAccountManagerImplTest extends BrambleMockTestCase {
private final CryptoComponent cryptoComponent =
context.mock(CryptoComponent.class);
private final SharedPreferences prefs =
context.mock(SharedPreferences.class);
private final DatabaseConfig databaseConfig =
context.mock(DatabaseConfig.class);
private final Editor editor = context.mock(Editor.class);
private final String authorName = getRandomString(MAX_AUTHOR_NAME_LENGTH);
private final String password = "some.strong.pass";
private final String oldPassword = "some.old.pass";
private final String newPassword = "some.new.pass";
private final SecretKey key = getSecretKey();
private final byte[] keyBytes = key.getBytes();
private final byte[] encryptedKey = getRandomBytes(123);
private final String encryptedKeyHex = toHexString(encryptedKey);
private final String oldEncryptedKeyHex = toHexString(getRandomBytes(123));
private final byte[] oldEncryptedKey = getRandomBytes(123);
private final byte[] newEncryptedKey = getRandomBytes(123);
private final File testDir = getTestDirectory();
private final File keyDir = new File(testDir, "key");
private final File keyFile = new File(keyDir, "db.key");
private final File keyBackupFile = new File(keyDir, "db.key.bak");
@Test
public void createAccount() throws IOException {
context.checking(new Expectations() {{
// Generate a database key
oneOf(cryptoComponent).generateSecretKey();
will(returnValue(key));
// Attach the author name and database key to the database config
oneOf(databaseConfig).setEncryptionKey(key);
// Encrypt the key with the password
oneOf(cryptoComponent)
.encryptWithPassword(key.getBytes(), password);
will(returnValue(encryptedKey));
// Store the encrypted key
allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir));
}});
getAndroidAccountManagerImpl().createAccount(authorName, password);
assertTrue(keyFile.exists());
assertTrue(keyBackupFile.exists());
assertEquals(toHexString(encryptedKey), loadDatabaseKey(keyFile));
assertEquals(toHexString(encryptedKey), loadDatabaseKey(keyBackupFile));
}
@Test
public void testChangePasswordReturnsTrue() throws Exception {
context.checking(new Expectations() {{
// Look up the encrypted DB key
oneOf(prefs).getString("key", null);
will(returnValue(null));
allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir));
// Decrypt and re-encrypt the key
oneOf(cryptoComponent)
.decryptWithPassword(oldEncryptedKey, oldPassword);
will(returnValue(key.getBytes()));
oneOf(cryptoComponent)
.encryptWithPassword(key.getBytes(), newPassword);
will(returnValue(newEncryptedKey));
}});
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
storeDatabaseKey(keyFile, toHexString(oldEncryptedKey));
storeDatabaseKey(keyBackupFile, toHexString(oldEncryptedKey));
AndroidAccountManagerImpl accountManager =
getAndroidAccountManagerImpl();
assertTrue(accountManager.changePassword(oldPassword, newPassword));
assertTrue(keyFile.exists());
assertTrue(keyBackupFile.exists());
assertEquals(toHexString(newEncryptedKey), loadDatabaseKey(keyFile));
assertEquals(toHexString(newEncryptedKey),
loadDatabaseKey(keyBackupFile));
}
@Test
public void testChangePasswordReturnsFalseIfOldPasswordIsWrong()
throws Exception {
context.checking(new Expectations() {{
// Look up the encrypted DB key
oneOf(prefs).getString("key", null);
will(returnValue(null));
allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir));
// Try to decrypt the key - the password is wrong
oneOf(cryptoComponent)
.decryptWithPassword(oldEncryptedKey, oldPassword);
will(returnValue(null));
}});
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
storeDatabaseKey(keyFile, toHexString(oldEncryptedKey));
storeDatabaseKey(keyBackupFile, toHexString(oldEncryptedKey));
AndroidAccountManagerImpl accountManager =
getAndroidAccountManagerImpl();
assertFalse(accountManager.changePassword(oldPassword, newPassword));
assertTrue(keyFile.exists());
assertTrue(keyBackupFile.exists());
assertEquals(toHexString(oldEncryptedKey), loadDatabaseKey(keyFile));
assertEquals(toHexString(oldEncryptedKey),
loadDatabaseKey(keyBackupFile));
}
@Test
public void testValidatePasswordReturnsTrue() throws Exception {
context.checking(new Expectations() {{
// Look up the encrypted DB key
oneOf(prefs).getString("key", null);
will(returnValue(null));
allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir));
// Decrypt the key
oneOf(cryptoComponent)
.decryptWithPassword(encryptedKey, password);
will(returnValue(keyBytes));
oneOf(databaseConfig)
.setEncryptionKey(with(samePropertyValuesAs(key)));
}});
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
storeDatabaseKey(keyFile, toHexString(encryptedKey));
storeDatabaseKey(keyBackupFile, toHexString(encryptedKey));
AndroidAccountManagerImpl accountManager =
getAndroidAccountManagerImpl();
assertTrue(accountManager.validatePassword(password));
}
@Test
public void testValidatePasswordReturnsFalseIfPasswordIsWrong()
throws Exception {
context.checking(new Expectations() {{
// Look up the encrypted DB key
oneOf(prefs).getString("key", null);
will(returnValue(null));
allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir));
// Decrypt the key
oneOf(cryptoComponent)
.decryptWithPassword(encryptedKey, password);
will(returnValue(null));
}});
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
storeDatabaseKey(keyFile, toHexString(encryptedKey));
storeDatabaseKey(keyBackupFile, toHexString(encryptedKey));
AndroidAccountManagerImpl accountManager =
getAndroidAccountManagerImpl();
assertFalse(accountManager.validatePassword(password));
}
@Test
public void testDbKeyIsMigratedFromPreferencesToFile() throws Exception {
context.checking(new Expectations() {{
oneOf(prefs).getString("key", null);
will(returnValue(encryptedKeyHex));
allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir));
oneOf(prefs).edit();
will(returnValue(editor));
oneOf(editor).remove("key");
will(returnValue(editor));
oneOf(editor).commit();
will(returnValue(true));
}});
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
AndroidAccountManagerImpl c = getAndroidAccountManagerImpl();
assertEquals(encryptedKeyHex, c.getEncryptedDatabaseKey());
assertTrue(keyFile.exists());
assertTrue(keyBackupFile.exists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
}
@Test
public void testDbKeyIsLoadedFromPrimaryFile() throws Exception {
context.checking(new Expectations() {{
oneOf(prefs).getString("key", null);
will(returnValue(null));
allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir));
}});
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
storeDatabaseKey(keyFile, encryptedKeyHex);
assertTrue(keyFile.exists());
assertFalse(keyBackupFile.exists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
AndroidAccountManagerImpl c = getAndroidAccountManagerImpl();
assertEquals(encryptedKeyHex, c.getEncryptedDatabaseKey());
assertTrue(keyFile.exists());
assertFalse(keyBackupFile.exists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
}
@Test
public void testDbKeyIsLoadedFromBackupFile() throws Exception {
context.checking(new Expectations() {{
oneOf(prefs).getString("key", null);
will(returnValue(null));
allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir));
}});
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertFalse(keyFile.exists());
assertTrue(keyBackupFile.exists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
AndroidAccountManagerImpl c = getAndroidAccountManagerImpl();
assertEquals(encryptedKeyHex, c.getEncryptedDatabaseKey());
assertFalse(keyFile.exists());
assertTrue(keyBackupFile.exists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
}
@Test
public void testDbKeyIsNullIfNotFound() {
context.checking(new Expectations() {{
oneOf(prefs).getString("key", null);
will(returnValue(null));
allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir));
}});
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
AndroidAccountManagerImpl c = getAndroidAccountManagerImpl();
assertNull(c.getEncryptedDatabaseKey());
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
}
@Test
public void testStoringDbKeyOverwritesPrimary() throws Exception {
context.checking(new Expectations() {{
allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir));
}});
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
storeDatabaseKey(keyFile, oldEncryptedKeyHex);
assertTrue(keyFile.exists());
assertFalse(keyBackupFile.exists());
assertEquals(oldEncryptedKeyHex, loadDatabaseKey(keyFile));
AndroidAccountManagerImpl c = getAndroidAccountManagerImpl();
assertTrue(c.storeEncryptedDatabaseKey(encryptedKeyHex));
assertTrue(keyFile.exists());
assertTrue(keyBackupFile.exists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
}
@Test
public void testStoringDbKeyOverwritesBackup() throws Exception {
context.checking(new Expectations() {{
allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir));
}});
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
storeDatabaseKey(keyBackupFile, oldEncryptedKeyHex);
assertFalse(keyFile.exists());
assertTrue(keyBackupFile.exists());
assertEquals(oldEncryptedKeyHex, loadDatabaseKey(keyBackupFile));
AndroidAccountManagerImpl c = getAndroidAccountManagerImpl();
assertTrue(c.storeEncryptedDatabaseKey(encryptedKeyHex));
assertTrue(keyFile.exists());
assertTrue(keyBackupFile.exists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
}
@After
public void tearDown() {
deleteTestDirectory(testDir);
}
private AndroidAccountManagerImpl getAndroidAccountManagerImpl() {
// app is only needed for deleting account
Application app = null;
//noinspection ConstantConditions
return new AndroidAccountManagerImpl(cryptoComponent, databaseConfig,
app, prefs);
}
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.account;
import android.support.design.widget.TextInputLayout; import android.support.design.widget.TextInputLayout;
import android.view.View; import android.view.View;
@@ -7,6 +7,7 @@ import android.widget.EditText;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.TestBriarApplication; import org.briarproject.briar.android.TestBriarApplication;
import org.briarproject.briar.android.login.StrengthMeter;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;

View File

@@ -1,10 +1,11 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.account;
import android.support.design.widget.TextInputLayout; import android.support.design.widget.TextInputLayout;
import android.widget.EditText; import android.widget.EditText;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.TestBriarApplication; import org.briarproject.briar.android.TestBriarApplication;
import org.briarproject.briar.android.account.SetupActivity;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;

View File

@@ -1,11 +1,10 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.account;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator; import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.ImmediateExecutor; import org.briarproject.bramble.test.ImmediateExecutor;
@@ -18,22 +17,17 @@ import java.io.File;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.assertTrue;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory; import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory; import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.briarproject.bramble.util.StringUtils.toHexString;
import static org.briarproject.briar.android.TestDatabaseKeyUtils.loadDatabaseKey;
public class SetupControllerImplTest extends BrambleMockTestCase { public class SetupControllerImplTest extends BrambleMockTestCase {
private final SharedPreferences briarPrefs = private final AccountManager accountManager =
context.mock(SharedPreferences.class); context.mock(AccountManager.class);
private final DatabaseConfig databaseConfig = private final DatabaseConfig databaseConfig =
context.mock(DatabaseConfig.class); context.mock(DatabaseConfig.class);
private final CryptoComponent crypto = context.mock(CryptoComponent.class); private final CryptoComponent crypto = context.mock(CryptoComponent.class);
@@ -45,8 +39,6 @@ public class SetupControllerImplTest extends BrambleMockTestCase {
private final String authorName = getRandomString(MAX_AUTHOR_NAME_LENGTH); private final String authorName = getRandomString(MAX_AUTHOR_NAME_LENGTH);
private final String password = "some.strong.pass"; private final String password = "some.strong.pass";
private final byte[] encryptedKey = getRandomBytes(123);
private final SecretKey key = getSecretKey();
private final File testDir = getTestDirectory(); private final File testDir = getTestDirectory();
private final File keyDir = new File(testDir, "key"); private final File keyDir = new File(testDir, "key");
private final File keyFile = new File(keyDir, "db.key"); private final File keyFile = new File(keyDir, "db.key");
@@ -74,25 +66,15 @@ public class SetupControllerImplTest extends BrambleMockTestCase {
will(returnValue(authorName)); will(returnValue(authorName));
oneOf(setupActivity).getPassword(); oneOf(setupActivity).getPassword();
will(returnValue(password)); will(returnValue(password));
// Generate a database key oneOf(accountManager).createAccount(authorName, password);
oneOf(crypto).generateSecretKey();
will(returnValue(key));
// Attach the author name and database key to the database config
oneOf(databaseConfig).setLocalAuthorName(authorName);
oneOf(databaseConfig).setEncryptionKey(key);
// Encrypt the key with the password
oneOf(crypto).encryptWithPassword(key.getBytes(), password);
will(returnValue(encryptedKey));
// Store the encrypted key
allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir));
}}); }});
assertFalse(keyFile.exists()); assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists()); assertFalse(keyBackupFile.exists());
SetupControllerImpl s = new SetupControllerImpl(briarPrefs, SetupControllerImpl s =
databaseConfig, cryptoExecutor, crypto, estimator); new SetupControllerImpl(accountManager, cryptoExecutor, crypto,
estimator);
s.setSetupActivity(setupActivity); s.setSetupActivity(setupActivity);
AtomicBoolean called = new AtomicBoolean(false); AtomicBoolean called = new AtomicBoolean(false);
@@ -100,11 +82,6 @@ public class SetupControllerImplTest extends BrambleMockTestCase {
s.setPassword(password); s.setPassword(password);
s.createAccount(result -> called.set(true)); s.createAccount(result -> called.set(true));
assertTrue(called.get()); assertTrue(called.get());
assertTrue(keyFile.exists());
assertTrue(keyBackupFile.exists());
assertEquals(toHexString(encryptedKey), loadDatabaseKey(keyFile));
assertEquals(toHexString(encryptedKey), loadDatabaseKey(keyBackupFile));
} }
@After @After

View File

@@ -1,205 +0,0 @@
package org.briarproject.briar.android.controller;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.junit.After;
import org.junit.Test;
import java.io.File;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.briarproject.bramble.util.StringUtils.toHexString;
import static org.briarproject.briar.android.TestDatabaseKeyUtils.loadDatabaseKey;
import static org.briarproject.briar.android.TestDatabaseKeyUtils.storeDatabaseKey;
public class ConfigControllerImplTest extends BrambleMockTestCase {
private final SharedPreferences prefs =
context.mock(SharedPreferences.class);
private final DatabaseConfig databaseConfig =
context.mock(DatabaseConfig.class);
private final Editor editor = context.mock(Editor.class);
private final byte[] encryptedKey = getRandomBytes(123);
private final String encryptedKeyHex = toHexString(encryptedKey);
private final String oldEncryptedKeyHex = toHexString(getRandomBytes(123));
private final File testDir = getTestDirectory();
private final File keyDir = new File(testDir, "key");
private final File keyFile = new File(keyDir, "db.key");
private final File keyBackupFile = new File(keyDir, "db.key.bak");
@Test
public void testDbKeyIsMigratedFromPreferencesToFile() throws Exception {
context.checking(new Expectations() {{
oneOf(prefs).getString("key", null);
will(returnValue(encryptedKeyHex));
allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir));
oneOf(prefs).edit();
will(returnValue(editor));
oneOf(editor).remove("key");
will(returnValue(editor));
oneOf(editor).commit();
will(returnValue(true));
}});
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
ConfigControllerImpl c = new ConfigControllerImpl(prefs,
databaseConfig);
assertEquals(encryptedKeyHex, c.getEncryptedDatabaseKey());
assertTrue(keyFile.exists());
assertTrue(keyBackupFile.exists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
}
@Test
public void testDbKeyIsLoadedFromPrimaryFile() throws Exception {
context.checking(new Expectations() {{
oneOf(prefs).getString("key", null);
will(returnValue(null));
allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir));
}});
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
storeDatabaseKey(keyFile, encryptedKeyHex);
assertTrue(keyFile.exists());
assertFalse(keyBackupFile.exists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
ConfigControllerImpl c = new ConfigControllerImpl(prefs,
databaseConfig);
assertEquals(encryptedKeyHex, c.getEncryptedDatabaseKey());
assertTrue(keyFile.exists());
assertFalse(keyBackupFile.exists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
}
@Test
public void testDbKeyIsLoadedFromBackupFile() throws Exception {
context.checking(new Expectations() {{
oneOf(prefs).getString("key", null);
will(returnValue(null));
allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir));
}});
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertFalse(keyFile.exists());
assertTrue(keyBackupFile.exists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
ConfigControllerImpl c = new ConfigControllerImpl(prefs,
databaseConfig);
assertEquals(encryptedKeyHex, c.getEncryptedDatabaseKey());
assertFalse(keyFile.exists());
assertTrue(keyBackupFile.exists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
}
@Test
public void testDbKeyIsNullIfNotFound() {
context.checking(new Expectations() {{
oneOf(prefs).getString("key", null);
will(returnValue(null));
allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir));
}});
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
ConfigControllerImpl c = new ConfigControllerImpl(prefs,
databaseConfig);
assertNull(c.getEncryptedDatabaseKey());
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
}
@Test
public void testStoringDbKeyOverwritesPrimary() throws Exception {
context.checking(new Expectations() {{
allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir));
}});
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
storeDatabaseKey(keyFile, oldEncryptedKeyHex);
assertTrue(keyFile.exists());
assertFalse(keyBackupFile.exists());
assertEquals(oldEncryptedKeyHex, loadDatabaseKey(keyFile));
ConfigController c = new ConfigControllerImpl(prefs,
databaseConfig);
assertTrue(c.storeEncryptedDatabaseKey(encryptedKeyHex));
assertTrue(keyFile.exists());
assertTrue(keyBackupFile.exists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
}
@Test
public void testStoringDbKeyOverwritesBackup() throws Exception {
context.checking(new Expectations() {{
allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir));
}});
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
storeDatabaseKey(keyBackupFile, oldEncryptedKeyHex);
assertFalse(keyFile.exists());
assertTrue(keyBackupFile.exists());
assertEquals(oldEncryptedKeyHex, loadDatabaseKey(keyBackupFile));
ConfigController c = new ConfigControllerImpl(prefs,
databaseConfig);
assertTrue(c.storeEncryptedDatabaseKey(encryptedKeyHex));
assertTrue(keyFile.exists());
assertTrue(keyBackupFile.exists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
}
@After
public void tearDown() {
deleteTestDirectory(testDir);
}
}

View File

@@ -45,7 +45,7 @@ public class TestForumActivity extends ForumActivity {
protected BriarController provideBriarController( protected BriarController provideBriarController(
BriarControllerImpl briarController) { BriarControllerImpl briarController) {
BriarController c = Mockito.mock(BriarController.class); BriarController c = Mockito.mock(BriarController.class);
Mockito.when(c.hasEncryptionKey()).thenReturn(true); Mockito.when(c.signedIn()).thenReturn(true);
return c; return c;
} }

View File

@@ -1,37 +1,23 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.login;
import android.content.SharedPreferences; import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator; import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.ImmediateExecutor; import org.briarproject.bramble.test.ImmediateExecutor;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.junit.After;
import org.junit.Test; import org.junit.Test;
import java.io.File;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.assertTrue;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.briarproject.bramble.util.StringUtils.toHexString;
import static org.briarproject.briar.android.TestDatabaseKeyUtils.loadDatabaseKey;
import static org.briarproject.briar.android.TestDatabaseKeyUtils.storeDatabaseKey;
public class PasswordControllerImplTest extends BrambleMockTestCase { public class PasswordControllerImplTest extends BrambleMockTestCase {
private final SharedPreferences briarPrefs = private final AccountManager accountManager =
context.mock(SharedPreferences.class); context.mock(AccountManager.class);
private final DatabaseConfig databaseConfig =
context.mock(DatabaseConfig.class);
private final CryptoComponent crypto = context.mock(CryptoComponent.class); private final CryptoComponent crypto = context.mock(CryptoComponent.class);
private final PasswordStrengthEstimator estimator = private final PasswordStrengthEstimator estimator =
context.mock(PasswordStrengthEstimator.class); context.mock(PasswordStrengthEstimator.class);
@@ -40,85 +26,37 @@ public class PasswordControllerImplTest extends BrambleMockTestCase {
private final String oldPassword = "some.old.pass"; private final String oldPassword = "some.old.pass";
private final String newPassword = "some.new.pass"; private final String newPassword = "some.new.pass";
private final byte[] oldEncryptedKey = getRandomBytes(123);
private final byte[] newEncryptedKey = getRandomBytes(123);
private final byte[] key = getSecretKey().getBytes();
private final File testDir = getTestDirectory();
private final File keyDir = new File(testDir, "key");
private final File keyFile = new File(keyDir, "db.key");
private final File keyBackupFile = new File(keyDir, "db.key.bak");
@Test @Test
public void testChangePasswordReturnsTrue() throws Exception { public void testChangePasswordReturnsTrue() {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Look up the encrypted DB key oneOf(accountManager).changePassword(oldPassword, newPassword);
oneOf(briarPrefs).getString("key", null); will(returnValue(true));
will(returnValue(null));
allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir));
// Decrypt and re-encrypt the key
oneOf(crypto).decryptWithPassword(oldEncryptedKey, oldPassword);
will(returnValue(key));
oneOf(crypto).encryptWithPassword(key, newPassword);
will(returnValue(newEncryptedKey));
}}); }});
assertFalse(keyFile.exists()); PasswordControllerImpl p =
assertFalse(keyBackupFile.exists()); new PasswordControllerImpl(accountManager, cryptoExecutor,
crypto, estimator);
storeDatabaseKey(keyFile, toHexString(oldEncryptedKey));
storeDatabaseKey(keyBackupFile, toHexString(oldEncryptedKey));
PasswordControllerImpl p = new PasswordControllerImpl(briarPrefs,
databaseConfig, cryptoExecutor, crypto, estimator);
AtomicBoolean capturedResult = new AtomicBoolean(false); AtomicBoolean capturedResult = new AtomicBoolean(false);
p.changePassword(oldPassword, newPassword, capturedResult::set); p.changePassword(oldPassword, newPassword, capturedResult::set);
assertTrue(capturedResult.get()); assertTrue(capturedResult.get());
assertTrue(keyFile.exists());
assertTrue(keyBackupFile.exists());
assertEquals(toHexString(newEncryptedKey), loadDatabaseKey(keyFile));
assertEquals(toHexString(newEncryptedKey),
loadDatabaseKey(keyBackupFile));
} }
@Test @Test
public void testChangePasswordReturnsFalseIfOldPasswordIsWrong() public void testChangePasswordReturnsFalseIfOldPasswordIsWrong() {
throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Look up the encrypted DB key oneOf(accountManager).changePassword(oldPassword, newPassword);
oneOf(briarPrefs).getString("key", null); will(returnValue(false));
will(returnValue(null));
allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir));
// Try to decrypt the key - the password is wrong
oneOf(crypto).decryptWithPassword(oldEncryptedKey, oldPassword);
will(returnValue(null));
}}); }});
assertFalse(keyFile.exists()); PasswordControllerImpl p =
assertFalse(keyBackupFile.exists()); new PasswordControllerImpl(accountManager, cryptoExecutor,
crypto, estimator);
storeDatabaseKey(keyFile, toHexString(oldEncryptedKey));
storeDatabaseKey(keyBackupFile, toHexString(oldEncryptedKey));
PasswordControllerImpl p = new PasswordControllerImpl(briarPrefs,
databaseConfig, cryptoExecutor, crypto, estimator);
AtomicBoolean capturedResult = new AtomicBoolean(true); AtomicBoolean capturedResult = new AtomicBoolean(true);
p.changePassword(oldPassword, newPassword, capturedResult::set); p.changePassword(oldPassword, newPassword, capturedResult::set);
assertFalse(capturedResult.get()); assertFalse(capturedResult.get());
assertTrue(keyFile.exists());
assertTrue(keyBackupFile.exists());
assertEquals(toHexString(oldEncryptedKey), loadDatabaseKey(keyFile));
assertEquals(toHexString(oldEncryptedKey),
loadDatabaseKey(keyBackupFile));
} }
@After
public void tearDown() {
deleteTestDirectory(testDir);
}
} }