Compare commits

..

14 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
akwizgran
0f16ac57f3 Merge branch '1267-tor-bridges' into 'master'
Tor Bridge Support

See merge request briar/briar!847
2018-07-05 14:22:39 +00:00
Torsten Grote
7ecac1867e Address review comments for Tor bridge support 2018-07-05 11:14:11 -03:00
Torsten Grote
331c09a02a Load bridges from file res/raw/bridges 2018-07-04 16:21:49 -03:00
Torsten Grote
7e05a49bda Add Android integration tests that checks if included bridges work
This also changes the way bridges are used.
Instead of using the torrc config file,
bridges are now activated via Tor's control port.
2018-07-04 15:17:28 -03:00
Torsten Grote
eac1f9ed74 MVP for bridge support 2018-07-04 15:17:28 -03:00
Torsten Grote
d16aa9e2a4 Merge branch '1334-disable-resource-shrinking' into 'master'
Disable resource shrinking for release builds

Closes #1334

See merge request briar/briar!854
2018-07-04 17:20:40 +00:00
akwizgran
cc72d146a0 Disable resource shrinking for release builds. 2018-07-04 17:02:39 +01:00
Torsten Grote
bff23480d7 Trigger external pipeline to check release builds 2018-07-04 12:47:22 -03:00
akwizgran
e435578f3b Merge branch 'gui-minor-padding-corrections' into 'master'
Fixed padding in rss_feed_import, list_item_crash, power_view

See merge request briar/briar!831
2018-07-04 13:21:18 +00:00
jRustig
33b9539a72 correction of crash report alignment 2018-07-04 14:59:34 +02:00
jRustig
8c64734ff1 Fixed padding in rss_feed_import, list_item_crash, power_view 2018-06-13 10:34:10 +02:00
63 changed files with 1166 additions and 565 deletions

View File

@@ -21,11 +21,7 @@ test:
test_reproducible:
image: briar/reproducer:latest
script:
- cd /opt/briar-reproducer
- ./reproduce.py ${CI_COMMIT_REF_NAME}
- "curl -X POST -F token=${RELEASE_CHECK_TOKEN} -F ref=master -F variables[RELEASE_TAG]=${CI_COMMIT_REF_NAME} https://code.briarproject.org/api/v4/projects/61/trigger/pipeline"
only:
- tags

View File

@@ -11,6 +11,8 @@ android {
versionCode 10011
versionName "1.0.11"
consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
compileOptions {
@@ -31,6 +33,12 @@ dependencies {
annotationProcessor 'com.google.dagger:dagger-compiler:2.0.2'
compileOnly 'javax.annotation:jsr250-api:1.0'
androidTestImplementation project(path: ':bramble-api', configuration: 'testOutput')
androidTestImplementation project(path: ':bramble-core', configuration: 'testOutput')
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestAnnotationProcessor 'com.google.dagger:dagger-compiler:2.0.2'
androidTestCompileOnly 'javax.annotation:jsr250-api:1.0'
}
dependencyVerification {

View File

@@ -0,0 +1,23 @@
package org.briarproject.bramble;
import org.briarproject.bramble.event.EventModule;
import org.briarproject.bramble.plugin.PluginModule;
import org.briarproject.bramble.plugin.tor.BridgeTest;
import org.briarproject.bramble.system.SystemModule;
import javax.inject.Singleton;
import dagger.Component;
@Singleton
@Component(modules = {
BrambleAndroidModule.class,
PluginModule.class, // needed for BackoffFactory
EventModule.class,
SystemModule.class,
})
public interface IntegrationTestComponent {
void inject(BridgeTest init);
}

View File

@@ -0,0 +1,126 @@
package org.briarproject.bramble.plugin.tor;
import android.content.Context;
import android.support.test.runner.AndroidJUnit4;
import org.briarproject.bramble.DaggerIntegrationTestComponent;
import org.briarproject.bramble.IntegrationTestComponent;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.plugin.BackoffFactory;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.logging.Logger;
import javax.inject.Inject;
import javax.net.SocketFactory;
import static android.support.test.InstrumentationRegistry.getTargetContext;
import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@RunWith(AndroidJUnit4.class)
public class BridgeTest extends BrambleTestCase {
private final static long TIMEOUT = SECONDS.toMillis(23);
private final static Logger LOG =
Logger.getLogger(BridgeTest.class.getSimpleName());
@Inject
EventBus eventBus;
@Inject
BackoffFactory backoffFactory;
@Inject
Clock clock;
private final Context appContext = getTargetContext();
private final CircumventionProvider circumventionProvider;
private final List<String> bridges;
private TorPluginFactory factory;
private volatile int currentBridge = 0;
public BridgeTest() {
super();
circumventionProvider = new CircumventionProvider() {
@Override
public boolean isTorProbablyBlocked(String countryCode) {
return true;
}
@Override
public boolean doBridgesWork(String countryCode) {
return true;
}
@Override
public List<String> getBridges() {
return singletonList(bridges.get(currentBridge));
}
};
bridges = new CircumventionProviderImpl(appContext).getBridges();
}
@Before
public void setUp() {
IntegrationTestComponent component =
DaggerIntegrationTestComponent.builder().build();
component.inject(this);
Executor ioExecutor = Executors.newCachedThreadPool();
ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(1);
LocationUtils locationUtils = () -> "US";
SocketFactory torSocketFactory = SocketFactory.getDefault();
factory = new TorPluginFactory(ioExecutor, scheduler, appContext,
locationUtils, eventBus, torSocketFactory,
backoffFactory, circumventionProvider, clock);
}
@Test
public void testBridges() throws Exception {
assertTrue(bridges.size() > 0);
for (int i = 0; i < bridges.size(); i++) {
testBridge(i);
}
}
private void testBridge(int bridge) throws Exception {
DuplexPlugin duplexPlugin =
factory.createPlugin(new TorPluginCallBack());
assertNotNull(duplexPlugin);
TorPlugin plugin = (TorPlugin) duplexPlugin;
currentBridge = bridge;
LOG.warning("Testing " + bridges.get(currentBridge));
try {
plugin.start();
long start = clock.currentTimeMillis();
while (clock.currentTimeMillis() - start < TIMEOUT) {
if (plugin.isRunning()) return;
clock.sleep(500);
}
if (!plugin.isRunning()) {
fail("Could not connect to Tor within timeout.");
}
} finally {
plugin.stop();
}
}
}

View File

@@ -0,0 +1,54 @@
package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.settings.Settings;
@NotNullByDefault
public class TorPluginCallBack implements DuplexPluginCallback {
@Override
public void incomingConnectionCreated(DuplexTransportConnection d) {
}
@Override
public void outgoingConnectionCreated(ContactId c,
DuplexTransportConnection d) {
}
@Override
public Settings getSettings() {
return new Settings();
}
@Override
public TransportProperties getLocalProperties() {
return new TransportProperties();
}
@Override
public void mergeSettings(Settings s) {
}
@Override
public void mergeLocalProperties(TransportProperties p) {
}
@Override
public void transportEnabled() {
}
@Override
public void transportDisabled() {
}
}

View File

@@ -1,11 +1,25 @@
package org.briarproject.bramble;
import android.app.Application;
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
import org.briarproject.bramble.plugin.tor.CircumventionProviderImpl;
import org.briarproject.bramble.system.AndroidSystemModule;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module(includes = {
AndroidSystemModule.class
})
public class BrambleAndroidModule {
@Provides
@Singleton
CircumventionProvider provideCircumventionProvider(Application app) {
return new CircumventionProviderImpl(app.getApplicationContext());
}
}

View File

@@ -0,0 +1,30 @@
package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import java.util.List;
public interface CircumventionProvider {
/**
* Countries where Tor is blocked, i.e. vanilla Tor connection won't work.
*
* See https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
* and https://trac.torproject.org/projects/tor/wiki/doc/OONI/censorshipwiki
*/
String[] BLOCKED = {"CN", "IR", "EG", "BY", "TR", "SY", "VE"};
/**
* Countries where vanilla bridge connection are likely to work.
* Should be a subset of {@link #BLOCKED}.
*/
String[] BRIDGES = { "EG", "BY", "TR", "SY", "VE" };
boolean isTorProbablyBlocked(String countryCode);
boolean doBridgesWork(String countryCode);
@IoExecutor
List<String> getBridges();
}

View File

@@ -0,0 +1,68 @@
package org.briarproject.bramble.plugin.tor;
import android.content.Context;
import android.content.res.Resources;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Scanner;
import java.util.Set;
import javax.annotation.Nullable;
import javax.inject.Inject;
public class CircumventionProviderImpl implements CircumventionProvider {
private final static String BRIDGE_FILE_NAME = "bridges";
private final Context ctx;
@Nullable
private volatile List<String> bridges = null;
@Inject
public CircumventionProviderImpl(Context ctx) {
this.ctx = ctx;
}
private static final Set<String> BLOCKED_IN_COUNTRIES =
new HashSet<>(Arrays.asList(BLOCKED));
private static final Set<String> BRIDGES_WORK_IN_COUNTRIES =
new HashSet<>(Arrays.asList(BRIDGES));
@Override
public boolean isTorProbablyBlocked(String countryCode) {
return BLOCKED_IN_COUNTRIES.contains(countryCode);
}
@Override
public boolean doBridgesWork(String countryCode) {
return BRIDGES_WORK_IN_COUNTRIES.contains(countryCode);
}
@Override
@IoExecutor
public List<String> getBridges() {
if (this.bridges != null) return this.bridges;
Resources res = ctx.getResources();
int resId = res.getIdentifier(BRIDGE_FILE_NAME, "raw",
ctx.getPackageName());
InputStream is = ctx.getResources().openRawResource(resId);
Scanner scanner = new Scanner(is);
List<String> bridges = new ArrayList<>();
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
if (!line.startsWith("#")) bridges.add(line);
}
scanner.close();
this.bridges = bridges;
return bridges;
}
}

View File

@@ -1,18 +0,0 @@
package org.briarproject.bramble.plugin.tor;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
class TorNetworkMetadata {
// See https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
// and https://trac.torproject.org/projects/tor/wiki/doc/OONI/censorshipwiki
// TODO: get a more complete list
private static final Set<String> BLOCKED_IN_COUNTRIES =
new HashSet<>(Arrays.asList("CN", "IR", "SY", "ZZ"));
static boolean isTorProbablyBlocked(String countryCode) {
return BLOCKED_IN_COUNTRIES.contains(countryCode);
}
}

View File

@@ -50,7 +50,9 @@ import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -119,6 +121,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private final Backoff backoff;
private final DuplexPluginCallback callback;
private final String architecture;
private final CircumventionProvider circumventionProvider;
private final int maxLatency, maxIdleTime, socketTimeout;
private final ConnectionStatus connectionStatus;
private final File torDirectory, torFile, geoIpFile, configFile;
@@ -138,7 +141,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
Context appContext, LocationUtils locationUtils,
SocketFactory torSocketFactory, Clock clock, Backoff backoff,
DuplexPluginCallback callback, String architecture,
int maxLatency, int maxIdleTime) {
CircumventionProvider circumventionProvider, int maxLatency, int maxIdleTime) {
this.ioExecutor = ioExecutor;
this.scheduler = scheduler;
this.appContext = appContext;
@@ -148,6 +151,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
this.backoff = backoff;
this.callback = callback;
this.architecture = architecture;
this.circumventionProvider = circumventionProvider;
this.maxLatency = maxLatency;
this.maxIdleTime = maxIdleTime;
if (maxIdleTime > Integer.MAX_VALUE / 2)
@@ -498,6 +502,17 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
}
private void enableBridges(boolean enable) throws IOException {
if (enable) {
Collection<String> conf = new ArrayList<>();
conf.add("UseBridges 1");
conf.addAll(circumventionProvider.getBridges());
controlConnection.setConf(conf);
} else {
controlConnection.setConf("UseBridges", "0");
}
}
@Override
public void stop() {
running = false;
@@ -662,8 +677,8 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
boolean online = net != null && net.isConnected();
boolean wifi = online && net.getType() == TYPE_WIFI;
String country = locationUtils.getCurrentCountry();
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(
country);
boolean blocked =
circumventionProvider.isTorProbablyBlocked(country);
Settings s = callback.getSettings();
int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_ALWAYS);
@@ -677,15 +692,22 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (!online) {
LOG.info("Disabling network, device is offline");
enableNetwork(false);
} else if (blocked) {
LOG.info("Disabling network, country is blocked");
enableNetwork(false);
} else if (network == PREF_TOR_NETWORK_NEVER
|| (network == PREF_TOR_NETWORK_WIFI && !wifi)) {
LOG.info("Disabling network due to data setting");
enableNetwork(false);
} else if (blocked) {
if (circumventionProvider.doBridgesWork(country)) {
LOG.info("Enabling network, using bridges");
enableBridges(true);
enableNetwork(true);
} else {
LOG.info("Disabling network, country is blocked");
enableNetwork(false);
}
} else {
LOG.info("Enabling network");
enableBridges(false);
enableNetwork(true);
}
} catch (IOException e) {

View File

@@ -43,12 +43,14 @@ public class TorPluginFactory implements DuplexPluginFactory {
private final EventBus eventBus;
private final SocketFactory torSocketFactory;
private final BackoffFactory backoffFactory;
private final CircumventionProvider circumventionProvider;
private final Clock clock;
public TorPluginFactory(Executor ioExecutor,
ScheduledExecutorService scheduler, Context appContext,
LocationUtils locationUtils, EventBus eventBus,
SocketFactory torSocketFactory, BackoffFactory backoffFactory,
CircumventionProvider circumventionProvider,
Clock clock) {
this.ioExecutor = ioExecutor;
this.scheduler = scheduler;
@@ -57,6 +59,7 @@ public class TorPluginFactory implements DuplexPluginFactory {
this.eventBus = eventBus;
this.torSocketFactory = torSocketFactory;
this.backoffFactory = backoffFactory;
this.circumventionProvider = circumventionProvider;
this.clock = clock;
}
@@ -95,7 +98,7 @@ public class TorPluginFactory implements DuplexPluginFactory {
MAX_POLLING_INTERVAL, BACKOFF_BASE);
TorPlugin plugin = new TorPlugin(ioExecutor, scheduler, appContext,
locationUtils, torSocketFactory, clock, backoff, callback,
architecture, MAX_LATENCY, MAX_IDLE_TIME);
architecture, circumventionProvider, MAX_LATENCY, MAX_IDLE_TIME);
eventBus.addListener(plugin);
return plugin;
}

View File

@@ -0,0 +1,5 @@
Bridge 131.252.210.150:8081 0E858AC201BF0F3FA3C462F64844CBFFC7297A42
Bridge 67.205.189.122:8443 12D64D5D44E20169585E7378580C0D33A872AD98
Bridge 45.32.148.146:8443 0CE016FB2462D8BF179AE71F7D702D09DEAC3F1D
Bridge 148.251.90.59:7510 019F727CA6DCA6CA5C90B55E477B7D87981E75BC
#Bridge 128.105.214.161:8081 1E326AAFB3FCB515015250D8FCCC8E37F91A153B

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
SecretKey getEncryptionKey();
void setLocalAuthorName(String nickname);
@Nullable
String getLocalAuthorName();
long getMaxSize();
}

View File

@@ -12,7 +12,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Annotation for injecting the executor for long-running IO tasks. Also used
* for annotating methods that should run on the UI executor.
* for annotating methods that should run on the IO executor.
* <p>
* The contract of this executor is that tasks may be run concurrently, and
* submitting a task will never block. Tasks may run indefinitely. Tasks

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;
}
@Override
public void setLocalAuthorName(String nickname) {
}
@Override
public String getLocalAuthorName() {
return null;
}
@Override
public long getMaxSize() {
return maxSize;

View File

@@ -261,7 +261,7 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
release {
shrinkResources true
shrinkResources false
minifyEnabled true
crunchPngs false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'

View File

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

View File

@@ -5,6 +5,7 @@ import android.content.SharedPreferences;
import org.briarproject.bramble.BrambleAndroidModule;
import org.briarproject.bramble.BrambleCoreEagerSingletons;
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.ContactManager;
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.briar.BriarCoreEagerSingletons;
import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.login.SignInReminderReceiver;
import org.briarproject.briar.android.reporting.BriarReportSender;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.DozeWatchdog;
@@ -76,6 +78,8 @@ public interface AndroidComponent
DatabaseConfig databaseConfig();
AccountManager accountManager();
@DatabaseExecutor
Executor databaseExecutor();
@@ -150,7 +154,7 @@ public interface AndroidComponent
@IoExecutor
Executor ioExecutor();
void inject(BootReceiver briarService);
void inject(SignInReminderReceiver briarService);
void inject(BriarService briarService);

View File

@@ -21,8 +21,6 @@ class AndroidDatabaseConfig implements DatabaseConfig {
@Nullable
private volatile SecretKey key = null;
@Nullable
private volatile String nickname = null;
AndroidDatabaseConfig(File dbDir, File keyDir) {
this.dbDir = dbDir;
@@ -70,21 +68,6 @@ class AndroidDatabaseConfig implements DatabaseConfig {
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
@Nullable
public SecretKey getEncryptionKey() {

View File

@@ -5,6 +5,7 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.os.StrictMode;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.db.DatabaseConfig;
@@ -25,9 +26,11 @@ import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.Scheduler;
import org.briarproject.bramble.plugin.bluetooth.AndroidBluetoothPluginFactory;
import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
import org.briarproject.bramble.plugin.tor.TorPluginFactory;
import org.briarproject.bramble.util.AndroidUtils;
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.DozeWatchdog;
import org.briarproject.briar.api.android.ReferenceManager;
@@ -93,20 +96,27 @@ public class AppModule {
return databaseConfig;
}
@Provides
@Singleton
AccountManager provideAccountManager(
AndroidAccountManagerImpl androidAccountManager) {
return androidAccountManager;
}
@Provides
PluginConfig providePluginConfig(@IoExecutor Executor ioExecutor,
@Scheduler ScheduledExecutorService scheduler,
AndroidExecutor androidExecutor, SecureRandom random,
SocketFactory torSocketFactory, BackoffFactory backoffFactory,
Application app, LocationUtils locationUtils, EventBus eventBus,
Clock clock) {
CircumventionProvider circumventionProvider, Clock clock) {
Context appContext = app.getApplicationContext();
DuplexPluginFactory bluetooth =
new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor,
appContext, random, eventBus, backoffFactory);
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, scheduler,
appContext, locationUtils, eventBus, torSocketFactory,
backoffFactory, clock);
backoffFactory, circumventionProvider, clock);
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
scheduler, backoffFactory, appContext);
Collection<DuplexPluginFactory> duplex = asList(bluetooth, tor, lan);
@@ -162,6 +172,7 @@ public class AppModule {
@Provides
SharedPreferences provideSharedPreferences(Application app) {
// FIXME unify this with getDefaultSharedPreferences()
return app.getSharedPreferences("db", MODE_PRIVATE);
}

View File

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

View File

@@ -77,11 +77,12 @@ public class BriarApplicationImpl extends Application
private final CachingLogHandler logHandler = new CachingLogHandler();
private AndroidComponent applicationComponent;
private volatile SharedPreferences prefs;
@Override
protected void attachBaseContext(Context base) {
SharedPreferences prefs =
PreferenceManager.getDefaultSharedPreferences(base);
if (prefs == null)
prefs = PreferenceManager.getDefaultSharedPreferences(base);
// Loading the language needs to be done here.
Localizer.initialize(prefs);
super.attachBaseContext(
@@ -156,4 +157,9 @@ public class BriarApplicationImpl extends Application
public AndroidComponent getApplicationComponent() {
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.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.StartResult;
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 java.util.logging.Level.INFO;
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.SUCCESS;
import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_CHANNEL_ID;
@@ -75,7 +78,7 @@ public class BriarService extends Service {
private BroadcastReceiver receiver = null;
@Inject
protected DatabaseConfig databaseConfig;
protected AccountManager accountManager;
// Fields that are accessed from background threads must be volatile
@Inject
protected volatile LifecycleManager lifecycleManager;
@@ -96,7 +99,8 @@ public class BriarService extends Service {
stopSelf();
return;
}
if (databaseConfig.getEncryptionKey() == null) {
AccountState accountState = accountManager.getAccountState();
if (accountState != SIGNED_IN && accountState != CREATING_ACCOUNT) {
LOG.info("No database key");
stopSelf();
return;
@@ -141,7 +145,7 @@ public class BriarService extends Service {
nm.cancel(REMINDER_NOTIFICATION_ID);
// Start the services in a background thread
new Thread(() -> {
String nickname = databaseConfig.getLocalAuthorName();
String nickname = accountManager.getCreatedLocalAuthorName();
StartResult result = lifecycleManager.startServices(nickname);
if (result == SUCCESS) {
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.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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.util.AndroidUtils;
import org.briarproject.briar.android.BriarApplication;
import java.io.BufferedReader;
import java.io.File;
@@ -20,27 +21,30 @@ import javax.annotation.Nullable;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.AndroidUtils.deleteAppData;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
public class ConfigControllerImpl implements ConfigController {
public class AndroidAccountManagerImpl extends AccountManagerImpl {
private static final Logger LOG =
Logger.getLogger(ConfigControllerImpl.class.getName());
private final static Logger LOG =
Logger.getLogger(AndroidAccountManagerImpl.class.getSimpleName());
private static final String PREF_DB_KEY = "key";
private static final String DB_KEY_FILENAME = "db.key";
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;
protected final DatabaseConfig databaseConfig;
@Inject
public ConfigControllerImpl(SharedPreferences briarPrefs,
DatabaseConfig databaseConfig) {
this.briarPrefs = briarPrefs;
this.databaseConfig = databaseConfig;
public AndroidAccountManagerImpl(CryptoComponent crypto,
DatabaseConfig databaseConfig, Application app,
SharedPreferences dbPrefs) {
super(crypto, databaseConfig);
this.app = (BriarApplication) app;
this.dbPrefs = dbPrefs;
File keyDir = databaseConfig.getDatabaseKeyDirectory();
dbKeyFile = new File(keyDir, DB_KEY_FILENAME);
dbKeyBackupFile = new File(keyDir, DB_KEY_BACKUP_FILENAME);
@@ -48,7 +52,7 @@ public class ConfigControllerImpl implements ConfigController {
@Override
@Nullable
public String getEncryptedDatabaseKey() {
protected String getEncryptedDatabaseKey() {
String key = getDatabaseKeyFromPreferences();
if (key == null) key = getDatabaseKeyFromFile();
else migrateDatabaseKeyToFile(key);
@@ -57,7 +61,7 @@ public class ConfigControllerImpl implements ConfigController {
@Nullable
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");
else LOG.info("Found database key in preferences");
return key;
@@ -97,7 +101,7 @@ public class ConfigControllerImpl implements ConfigController {
private void migrateDatabaseKeyToFile(String 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");
else LOG.warning("Database key not removed from preferences");
} else {
@@ -106,7 +110,7 @@ public class ConfigControllerImpl implements ConfigController {
}
@Override
public boolean storeEncryptedDatabaseKey(String hex) {
protected boolean storeEncryptedDatabaseKey(String hex) {
LOG.info("Storing database key in file");
// Create the directory if necessary
if (databaseConfig.getDatabaseKeyDirectory().mkdirs())
@@ -151,21 +155,10 @@ public class ConfigControllerImpl implements ConfigController {
}
@Override
public void deleteAccount(Context ctx) {
public void deleteAccount() {
LOG.info("Deleting account");
SharedPreferences defaultPrefs =
PreferenceManager.getDefaultSharedPreferences(ctx);
AndroidUtils.deleteAppData(ctx, briarPrefs, defaultPrefs);
SharedPreferences defaultPrefs = app.getDefaultSharedPreferences();
deleteAppData(app.getApplicationContext(), dbPrefs, 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.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.content.Intent;
@@ -13,8 +13,8 @@ import android.widget.ProgressBar;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
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.login.PowerView.OnCheckedChangedListener;
import org.briarproject.briar.android.util.UiUtils;
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;

View File

@@ -1,4 +1,4 @@
package org.briarproject.briar.android.login;
package org.briarproject.briar.android.account;
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.IBinder;
@@ -15,6 +15,7 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.login.StrengthMeter;
import org.briarproject.briar.android.util.UiUtils;
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.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.content.Intent;
@@ -10,6 +10,7 @@ import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BaseActivity;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import org.briarproject.briar.android.login.OpenDatabaseActivity;
import javax.annotation.Nullable;
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.briar.android.login.PasswordController;
@NotNullByDefault
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 org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.CryptoExecutor;
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.briar.android.controller.handler.ResultHandler;
import org.briarproject.briar.android.controller.handler.UiResultHandler;
import org.briarproject.briar.android.login.PasswordControllerImpl;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
@@ -28,11 +27,10 @@ public class SetupControllerImpl extends PasswordControllerImpl
private volatile SetupActivity setupActivity;
@Inject
SetupControllerImpl(SharedPreferences briarPrefs,
DatabaseConfig databaseConfig,
SetupControllerImpl(AccountManager accountManager,
@CryptoExecutor Executor cryptoExecutor, CryptoComponent crypto,
PasswordStrengthEstimator strengthEstimator) {
super(briarPrefs, databaseConfig, cryptoExecutor, crypto,
super(accountManager, cryptoExecutor, crypto,
strengthEstimator);
}
@@ -102,11 +100,7 @@ public class SetupControllerImpl extends PasswordControllerImpl
if (password == null) throw new IllegalStateException();
cryptoExecutor.execute(() -> {
LOG.info("Creating account");
databaseConfig.setLocalAuthorName(authorName);
SecretKey key = crypto.generateSecretKey();
databaseConfig.setEncryptionKey(key);
String hex = encryptDatabaseKey(key, password);
storeEncryptedDatabaseKey(hex);
accountManager.createAccount(authorName, password);
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.TextWatcher;
@@ -17,7 +17,7 @@ import javax.inject.Inject;
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 {
@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.KeyAgreementActivity;
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.DozeFragment;
import org.briarproject.briar.android.account.DozeFragment;
import org.briarproject.briar.android.login.OpenDatabaseActivity;
import org.briarproject.briar.android.login.PasswordActivity;
import org.briarproject.briar.android.login.PasswordFragment;
import org.briarproject.briar.android.login.SetupActivity;
import org.briarproject.briar.android.account.PasswordFragment;
import org.briarproject.briar.android.account.SetupActivity;
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
import org.briarproject.briar.android.panic.PanicPreferencesActivity;
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.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.DbControllerImpl;
import org.briarproject.briar.android.login.PasswordController;
import org.briarproject.briar.android.login.PasswordControllerImpl;
import org.briarproject.briar.android.login.SetupController;
import org.briarproject.briar.android.login.SetupControllerImpl;
import org.briarproject.briar.android.account.SetupController;
import org.briarproject.briar.android.account.SetupControllerImpl;
import org.briarproject.briar.android.navdrawer.NavDrawerController;
import org.briarproject.briar.android.navdrawer.NavDrawerControllerImpl;
@@ -48,13 +46,6 @@ public class ActivityModule {
return setupController;
}
@ActivityScope
@Provides
ConfigController provideConfigController(
ConfigControllerImpl configController) {
return configController;
}
@ActivityScope
@Provides
PasswordController providePasswordController(

View File

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

View File

@@ -6,7 +6,7 @@ public interface BriarController extends ActivityLifecycleController {
void startAndBindService();
boolean hasEncryptionKey();
boolean signedIn();
/**
* 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.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.DbException;
import org.briarproject.bramble.api.settings.Settings;
@@ -21,6 +22,8 @@ import java.util.logging.Logger;
import javax.inject.Inject;
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.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
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";
private final BriarServiceConnection serviceConnection;
private final DatabaseConfig databaseConfig;
private final AccountManager accountManager;
@DatabaseExecutor
private final Executor databaseExecutor;
private final SettingsManager settingsManager;
@@ -44,12 +47,12 @@ public class BriarControllerImpl implements BriarController {
@Inject
BriarControllerImpl(BriarServiceConnection serviceConnection,
DatabaseConfig databaseConfig,
AccountManager accountManager,
@DatabaseExecutor Executor databaseExecutor,
SettingsManager settingsManager, DozeWatchdog dozeWatchdog,
Activity activity) {
this.serviceConnection = serviceConnection;
this.databaseConfig = databaseConfig;
this.accountManager = accountManager;
this.databaseExecutor = databaseExecutor;
this.settingsManager = settingsManager;
this.dozeWatchdog = dozeWatchdog;
@@ -59,7 +62,7 @@ public class BriarControllerImpl implements BriarController {
@Override
@CallSuper
public void onActivityCreate(Activity activity) {
if (databaseConfig.getEncryptionKey() != null) startAndBindService();
if (signedIn()) startAndBindService();
}
@Override
@@ -84,8 +87,9 @@ public class BriarControllerImpl implements BriarController {
}
@Override
public boolean hasEncryptionKey() {
return databaseConfig.getEncryptionKey() != null;
public boolean signedIn() {
AccountState state = accountManager.getAccountState();
return state == CREATING_ACCOUNT || state == SIGNED_IN;
}
@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.android.Localizer;
import org.briarproject.briar.android.account.SetupActivity;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BaseActivity;
import org.briarproject.briar.android.controller.BriarController;
@@ -85,7 +86,7 @@ public class PasswordActivity extends BaseActivity {
public void onStart() {
super.onStart();
// If the user has already signed in, clean up this instance
if (briarController.hasEncryptionKey()) {
if (briarController.signedIn()) {
setResult(RESULT_OK);
finish();
}
@@ -105,7 +106,7 @@ public class PasswordActivity extends BaseActivity {
}
private void deleteAccount() {
passwordController.deleteAccount(this);
passwordController.deleteAccount();
Localizer.reinitialize();
UiUtils.setTheme(this, getString(R.string.pref_theme_light_value));
setResult(RESULT_CANCELED);

View File

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

View File

@@ -1,47 +1,41 @@
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.CryptoExecutor;
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.util.StringUtils;
import org.briarproject.briar.android.controller.ConfigControllerImpl;
import org.briarproject.briar.android.controller.handler.ResultHandler;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.bramble.api.account.AccountState.NO_ACCOUNT;
@NotNullByDefault
public class PasswordControllerImpl extends ConfigControllerImpl
implements PasswordController {
private static final Logger LOG =
Logger.getLogger(PasswordControllerImpl.class.getName());
public class PasswordControllerImpl implements PasswordController {
protected final AccountManager accountManager;
protected final Executor cryptoExecutor;
protected final CryptoComponent crypto;
private final PasswordStrengthEstimator strengthEstimator;
@Inject
PasswordControllerImpl(SharedPreferences briarPrefs,
DatabaseConfig databaseConfig,
public PasswordControllerImpl(AccountManager accountManager,
@CryptoExecutor Executor cryptoExecutor, CryptoComponent crypto,
PasswordStrengthEstimator strengthEstimator) {
super(briarPrefs, databaseConfig);
this.accountManager = accountManager;
this.cryptoExecutor = cryptoExecutor;
this.crypto = crypto;
this.strengthEstimator = strengthEstimator;
}
@Override
public boolean accountExists() {
return accountManager.getAccountState() != NO_ACCOUNT;
}
@Override
public float estimatePasswordStrength(String password) {
return strengthEstimator.estimateStrength(password);
@@ -50,46 +44,25 @@ public class PasswordControllerImpl extends ConfigControllerImpl
@Override
public void validatePassword(String password,
ResultHandler<Boolean> resultHandler) {
byte[] encrypted = getEncryptedKey();
cryptoExecutor.execute(() -> {
byte[] key = crypto.decryptWithPassword(encrypted, password);
if (key == null) {
resultHandler.onResult(false);
} else {
databaseConfig.setEncryptionKey(new SecretKey(key));
resultHandler.onResult(true);
}
boolean result = accountManager.validatePassword(password);
resultHandler.onResult(result);
});
}
@Override
public void changePassword(String password, String newPassword,
ResultHandler<Boolean> resultHandler) {
byte[] encrypted = getEncryptedKey();
cryptoExecutor.execute(() -> {
byte[] key = crypto.decryptWithPassword(encrypted, password);
if (key == null) {
resultHandler.onResult(false);
} else {
String hex =
encryptDatabaseKey(new SecretKey(key), newPassword);
resultHandler.onResult(storeEncryptedDatabaseKey(hex));
}
boolean result =
accountManager.changePassword(password, newPassword);
resultHandler.onResult(result);
});
}
private byte[] getEncryptedKey() {
String hex = getEncryptedDatabaseKey();
if (hex == null)
throw new IllegalStateException("Encrypted database key is null");
return StringUtils.fromHexString(hex);
@Override
public void deleteAccount() {
accountManager.deleteAccount();
}
@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.NotificationManager;
@@ -6,45 +6,57 @@ import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.support.v4.app.NotificationCompat;
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.android.AndroidComponent;
import org.briarproject.briar.android.BriarApplication;
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
import org.briarproject.briar.android.settings.SettingsActivity;
import javax.inject.Inject;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.content.Context.NOTIFICATION_SERVICE;
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_NEW_TASK;
import static android.os.Build.VERSION.SDK_INT;
import static android.support.v4.app.NotificationCompat.PRIORITY_LOW;
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.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_NOTIFICATION_ID;
public class BootReceiver extends BroadcastReceiver {
public class SignInReminderReceiver extends BroadcastReceiver {
@Inject
DatabaseConfig databaseConfig;
AccountManager accountManager;
@Override
public void onReceive(Context ctx, Intent intent) {
if (!FEATURE_FLAG_SIGN_IN_REMINDER) return;
AndroidComponent applicationComponent =
((BriarApplication) ctx.getApplicationContext())
.getApplicationComponent();
BriarApplication app = (BriarApplication) ctx.getApplicationContext();
AndroidComponent applicationComponent = app.getApplicationComponent();
applicationComponent.inject(this);
String action = intent.getAction();
if (action != null && action.equals(ACTION_BOOT_COMPLETED)) {
if (databaseConfig.databaseExists()) {
showSignInNotification(ctx);
if (action == null) return;
if (action.equals(ACTION_BOOT_COMPLETED) ||
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.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);
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);
b.setContentIntent(PendingIntent.getActivity(ctx, 0, i, 0));

View File

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

View File

@@ -1,19 +1,43 @@
package org.briarproject.briar.android.settings;
import android.app.NotificationManager;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.view.MenuItem;
import org.briarproject.briar.R;
import org.briarproject.briar.android.BriarApplication;
import org.briarproject.briar.android.activity.ActivityComponent;
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 static final String NO_NOTIFY_SIGN_IN = "noNotifySignIn";
@Override
public void onCreate(Bundle 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();
if (actionBar != null) {
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.now;
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.activity.RequestCodes.REQUEST_RINGTONE;
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 TOR_NAMESPACE = TorConstants.ID.getString();
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 =
Logger.getLogger(SettingsFragment.class.getName());
@@ -143,6 +145,8 @@ public class SettingsFragment extends PreferenceFragmentCompat
(ListPreference) findPreference("pref_key_theme");
enableBluetooth = (ListPreference) findPreference("pref_key_bluetooth");
torNetwork = (ListPreference) findPreference("pref_key_tor_network");
CheckBoxPreference notifySignIn =
(CheckBoxPreference) findPreference(NOTIFY_SIGN_IN);
notifyPrivateMessages = (CheckBoxPreference) findPreference(
"pref_key_notify_private_messages");
notifyGroupMessages = (CheckBoxPreference) findPreference(
@@ -199,6 +203,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
);
} else {
theme.setVisible(FEATURE_FLAG_DARK_THEME);
notifySignIn.setVisible(FEATURE_FLAG_SIGN_IN_REMINDER);
findPreference("pref_key_explode").setVisible(false);
findPreference("pref_key_test_data").setVisible(false);
@@ -346,7 +351,9 @@ public class SettingsFragment extends PreferenceFragmentCompat
}
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);
torNetwork.setEnabled(enabled);
notifyPrivateMessages.setEnabled(enabled);

View File

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

View File

@@ -15,7 +15,9 @@
android:layout_weight="1"
android:gravity="top"
android:hint="@string/blogs_rss_feeds_import_hint"
android:inputType="textUri"/>
android:inputType="textUri"
android:paddingLeft="@dimen/margin_large"
android:paddingRight="@dimen/margin_large"/>
<Button
android:id="@+id/importButton"

View File

@@ -12,6 +12,8 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:paddingLeft="@dimen/margin_large"
android:paddingRight="@dimen/margin_large"
android:textSize="@dimen/text_size_medium"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"

View File

@@ -71,6 +71,7 @@
<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_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_text">Touch to open Briar.</string>
<plurals name="private_message_notification_text">
@@ -373,6 +374,8 @@
<!-- Settings Notifications -->
<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_summary">Show 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: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
android:defaultValue="true"
android:key="pref_key_notify_private_messages"

View File

@@ -23,13 +23,14 @@ public class TestBriarApplication extends Application
Logger.getLogger(TestBriarApplication.class.getName());
private AndroidComponent applicationComponent;
private SharedPreferences prefs;
@Override
public void onCreate() {
super.onCreate();
LOG.info("Created");
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs = PreferenceManager.getDefaultSharedPreferences(this);
Localizer.initialize(prefs);
applicationComponent = DaggerAndroidComponent.builder()
.appModule(new AppModule(this))
@@ -51,4 +52,9 @@ public class TestBriarApplication extends Application
public AndroidComponent getApplicationComponent() {
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.view.View;
@@ -7,6 +7,7 @@ import android.widget.EditText;
import org.briarproject.briar.R;
import org.briarproject.briar.android.TestBriarApplication;
import org.briarproject.briar.android.login.StrengthMeter;
import org.junit.Before;
import org.junit.Test;
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.widget.EditText;
import org.briarproject.briar.R;
import org.briarproject.briar.android.TestBriarApplication;
import org.briarproject.briar.android.account.SetupActivity;
import org.junit.Assert;
import org.junit.Before;
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 org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.CryptoComponent;
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.test.BrambleMockTestCase;
import org.briarproject.bramble.test.ImmediateExecutor;
@@ -18,22 +17,17 @@ import java.io.File;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
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;
public class SetupControllerImplTest extends BrambleMockTestCase {
private final SharedPreferences briarPrefs =
context.mock(SharedPreferences.class);
private final AccountManager accountManager =
context.mock(AccountManager.class);
private final DatabaseConfig databaseConfig =
context.mock(DatabaseConfig.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 password = "some.strong.pass";
private final byte[] encryptedKey = getRandomBytes(123);
private final SecretKey key = getSecretKey();
private final File testDir = getTestDirectory();
private final File keyDir = new File(testDir, "key");
private final File keyFile = new File(keyDir, "db.key");
@@ -74,25 +66,15 @@ public class SetupControllerImplTest extends BrambleMockTestCase {
will(returnValue(authorName));
oneOf(setupActivity).getPassword();
will(returnValue(password));
// Generate a database key
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));
oneOf(accountManager).createAccount(authorName, password);
}});
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
SetupControllerImpl s = new SetupControllerImpl(briarPrefs,
databaseConfig, cryptoExecutor, crypto, estimator);
SetupControllerImpl s =
new SetupControllerImpl(accountManager, cryptoExecutor, crypto,
estimator);
s.setSetupActivity(setupActivity);
AtomicBoolean called = new AtomicBoolean(false);
@@ -100,11 +82,6 @@ public class SetupControllerImplTest extends BrambleMockTestCase {
s.setPassword(password);
s.createAccount(result -> called.set(true));
assertTrue(called.get());
assertTrue(keyFile.exists());
assertTrue(keyBackupFile.exists());
assertEquals(toHexString(encryptedKey), loadDatabaseKey(keyFile));
assertEquals(toHexString(encryptedKey), loadDatabaseKey(keyBackupFile));
}
@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(
BriarControllerImpl briarController) {
BriarController c = Mockito.mock(BriarController.class);
Mockito.when(c.hasEncryptionKey()).thenReturn(true);
Mockito.when(c.signedIn()).thenReturn(true);
return c;
}

View File

@@ -1,37 +1,23 @@
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.PasswordStrengthEstimator;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.ImmediateExecutor;
import org.jmock.Expectations;
import org.junit.After;
import org.junit.Test;
import java.io.File;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
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 {
private final SharedPreferences briarPrefs =
context.mock(SharedPreferences.class);
private final DatabaseConfig databaseConfig =
context.mock(DatabaseConfig.class);
private final AccountManager accountManager =
context.mock(AccountManager.class);
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
private final PasswordStrengthEstimator estimator =
context.mock(PasswordStrengthEstimator.class);
@@ -40,85 +26,37 @@ public class PasswordControllerImplTest extends BrambleMockTestCase {
private final String oldPassword = "some.old.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
public void testChangePasswordReturnsTrue() throws Exception {
public void testChangePasswordReturnsTrue() {
context.checking(new Expectations() {{
// Look up the encrypted DB key
oneOf(briarPrefs).getString("key", null);
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));
oneOf(accountManager).changePassword(oldPassword, newPassword);
will(returnValue(true));
}});
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
storeDatabaseKey(keyFile, toHexString(oldEncryptedKey));
storeDatabaseKey(keyBackupFile, toHexString(oldEncryptedKey));
PasswordControllerImpl p = new PasswordControllerImpl(briarPrefs,
databaseConfig, cryptoExecutor, crypto, estimator);
PasswordControllerImpl p =
new PasswordControllerImpl(accountManager, cryptoExecutor,
crypto, estimator);
AtomicBoolean capturedResult = new AtomicBoolean(false);
p.changePassword(oldPassword, newPassword, capturedResult::set);
assertTrue(capturedResult.get());
assertTrue(keyFile.exists());
assertTrue(keyBackupFile.exists());
assertEquals(toHexString(newEncryptedKey), loadDatabaseKey(keyFile));
assertEquals(toHexString(newEncryptedKey),
loadDatabaseKey(keyBackupFile));
}
@Test
public void testChangePasswordReturnsFalseIfOldPasswordIsWrong()
throws Exception {
public void testChangePasswordReturnsFalseIfOldPasswordIsWrong() {
context.checking(new Expectations() {{
// Look up the encrypted DB key
oneOf(briarPrefs).getString("key", null);
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));
oneOf(accountManager).changePassword(oldPassword, newPassword);
will(returnValue(false));
}});
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
storeDatabaseKey(keyFile, toHexString(oldEncryptedKey));
storeDatabaseKey(keyBackupFile, toHexString(oldEncryptedKey));
PasswordControllerImpl p = new PasswordControllerImpl(briarPrefs,
databaseConfig, cryptoExecutor, crypto, estimator);
PasswordControllerImpl p =
new PasswordControllerImpl(accountManager, cryptoExecutor,
crypto, estimator);
AtomicBoolean capturedResult = new AtomicBoolean(true);
p.changePassword(oldPassword, newPassword, capturedResult::set);
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);
}
}