Merge branch '1341-account-manager-refactoring' into 'master'

Refactor authentication and account management code

Closes #1341

See merge request briar/briar!866
This commit is contained in:
Torsten Grote
2018-08-02 11:49:15 +00:00
65 changed files with 1333 additions and 1014 deletions

View File

@@ -21,6 +21,7 @@
<method> <method>
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-api" run_configuration_type="AndroidJUnit" /> <option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-api" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-core" run_configuration_type="AndroidJUnit" /> <option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-core" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-android" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-j2se" run_configuration_type="AndroidJUnit" /> <option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-j2se" run_configuration_type="AndroidJUnit" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-core" run_configuration_type="AndroidJUnit" /> <option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-core" run_configuration_type="AndroidJUnit" />
</method> </method>

View File

@@ -34,6 +34,14 @@ dependencies {
compileOnly 'javax.annotation:jsr250-api:1.0' compileOnly 'javax.annotation:jsr250-api:1.0'
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
testImplementation 'junit:junit:4.12'
testImplementation "org.jmock:jmock:2.8.2"
testImplementation "org.jmock:jmock-junit4:2.8.2"
testImplementation "org.jmock:jmock-legacy:2.8.2"
testImplementation "org.hamcrest:hamcrest-library:1.3"
testImplementation "org.hamcrest:hamcrest-core:1.3"
androidTestImplementation project(path: ':bramble-api', configuration: 'testOutput') androidTestImplementation project(path: ':bramble-api', configuration: 'testOutput')
androidTestImplementation project(path: ':bramble-core', configuration: 'testOutput') androidTestImplementation project(path: ':bramble-core', configuration: 'testOutput')
androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test:runner:1.0.2'
@@ -43,6 +51,10 @@ dependencies {
dependencyVerification { dependencyVerification {
verify = [ verify = [
'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861',
'com.android.support.test:monitor:1.0.2:monitor-1.0.2.aar:38ef4fa98a32dc55550ff49bb36a583e178b3a9b830fcb8dcc27bfc4254bc2bc',
'com.android.support.test:runner:1.0.2:runner-1.0.2.aar:f04b9ae342975ba1cb3e4a06e13426e3e6b8a73faa45acba604493d83c9a4f00',
'com.android.support:support-annotations:27.1.1:support-annotations-27.1.1.jar:3365960206c3d2b09e845f555e7f88f8effc8d2f00b369e66c4be384029299cf',
'com.android.tools.analytics-library:protos:26.1.3:protos-26.1.3.jar:818c9f256f141d9dafec03a1aa2b94d240b2c140acfd7ee31a8b3e6c2b9479e3', 'com.android.tools.analytics-library:protos:26.1.3:protos-26.1.3.jar:818c9f256f141d9dafec03a1aa2b94d240b2c140acfd7ee31a8b3e6c2b9479e3',
'com.android.tools.analytics-library:shared:26.1.3:shared-26.1.3.jar:7110706c7ada96c8b6f5ca80c478291bc7899d46277de2c48527e045442401a3', 'com.android.tools.analytics-library:shared:26.1.3:shared-26.1.3.jar:7110706c7ada96c8b6f5ca80c478291bc7899d46277de2c48527e045442401a3',
'com.android.tools.analytics-library:tracker:26.1.3:tracker-26.1.3.jar:4155424bf2ce4872da83332579a1707252bc66cbd77c5144fdc4483d0f2e1418', 'com.android.tools.analytics-library:tracker:26.1.3:tracker-26.1.3.jar:4155424bf2ce4872da83332579a1707252bc66cbd77c5144fdc4483d0f2e1418',
@@ -91,12 +103,16 @@ dependencyVerification {
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f', 'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'javax.xml.bind:jaxb-api:2.2.12-b140109.1041:jaxb-api-2.2.12-b140109.1041.jar:b5e60cd8b7b5ff01ce4a74c5dd008f4fbd14ced3495d0b47b85cfedc182211f2', 'javax.xml.bind:jaxb-api:2.2.12-b140109.1041:jaxb-api-2.2.12-b140109.1041.jar:b5e60cd8b7b5ff01ce4a74c5dd008f4fbd14ced3495d0b47b85cfedc182211f2',
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
'net.sf.jopt-simple:jopt-simple:4.9:jopt-simple-4.9.jar:26c5856e954b5f864db76f13b86919b59c6eecf9fd930b96baa8884626baf2f5', 'net.sf.jopt-simple:jopt-simple:4.9:jopt-simple-4.9.jar:26c5856e954b5f864db76f13b86919b59c6eecf9fd930b96baa8884626baf2f5',
'net.sf.kxml:kxml2:2.3.0:kxml2-2.3.0.jar:f264dd9f79a1fde10ce5ecc53221eff24be4c9331c830b7d52f2f08a7b633de2', 'net.sf.kxml:kxml2:2.3.0:kxml2-2.3.0.jar:f264dd9f79a1fde10ce5ecc53221eff24be4c9331c830b7d52f2f08a7b633de2',
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
'org.apache.commons:commons-compress:1.12:commons-compress-1.12.jar:2c1542faf343185b7cab9c3d55c8ae5471d6d095d3887a4adefdbdf2984dc0b6', 'org.apache.commons:commons-compress:1.12:commons-compress-1.12.jar:2c1542faf343185b7cab9c3d55c8ae5471d6d095d3887a4adefdbdf2984dc0b6',
'org.apache.httpcomponents:httpclient:4.2.6:httpclient-4.2.6.jar:362e9324ee7c697e21279e20077b52737ddef3f1b2c1a7abe5ad34b465145550', 'org.apache.httpcomponents:httpclient:4.2.6:httpclient-4.2.6.jar:362e9324ee7c697e21279e20077b52737ddef3f1b2c1a7abe5ad34b465145550',
'org.apache.httpcomponents:httpcore:4.2.5:httpcore-4.2.5.jar:e5e82da4cc66c8d917bbf743e3c0752efe8522735e7fc9dbddb65bccea81cfe9', 'org.apache.httpcomponents:httpcore:4.2.5:httpcore-4.2.5.jar:e5e82da4cc66c8d917bbf743e3c0752efe8522735e7fc9dbddb65bccea81cfe9',
'org.apache.httpcomponents:httpmime:4.1:httpmime-4.1.jar:31629566148e8a47688ae43b420abc3ecd783ed15b33bebc00824bf24c9b15aa', 'org.apache.httpcomponents:httpmime:4.1:httpmime-4.1.jar:31629566148e8a47688ae43b420abc3ecd783ed15b33bebc00824bf24c9b15aa',
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca', 'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349', 'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
'org.briarproject:jtorctl:0.3:jtorctl-0.3.jar:f2939238a097898998432effe93b0334d97a787972ab3a91a8973a1d309fc864', 'org.briarproject:jtorctl:0.3:jtorctl-0.3.jar:f2939238a097898998432effe93b0334d97a787972ab3a91a8973a1d309fc864',
@@ -106,17 +122,25 @@ dependencyVerification {
'org.glassfish.jaxb:jaxb-core:2.2.11:jaxb-core-2.2.11.jar:37bcaee8ebb04362c8352a5bf6221b86967ecdab5164c696b10b9a2bb587b2aa', 'org.glassfish.jaxb:jaxb-core:2.2.11:jaxb-core-2.2.11.jar:37bcaee8ebb04362c8352a5bf6221b86967ecdab5164c696b10b9a2bb587b2aa',
'org.glassfish.jaxb:jaxb-runtime:2.2.11:jaxb-runtime-2.2.11.jar:a874f2351cfba8e2946be3002d10c18a6da8f21b52ba2acf52f2b85d5520ed70', 'org.glassfish.jaxb:jaxb-runtime:2.2.11:jaxb-runtime-2.2.11.jar:a874f2351cfba8e2946be3002d10c18a6da8f21b52ba2acf52f2b85d5520ed70',
'org.glassfish.jaxb:txw2:2.2.11:txw2-2.2.11.jar:272a3ccad45a4511351920cd2a8633c53cab8d5220c7a92954da5526bb5eafea', 'org.glassfish.jaxb:txw2:2.2.11:txw2-2.2.11.jar:272a3ccad45a4511351920cd2a8633c53cab8d5220c7a92954da5526bb5eafea',
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c',
'org.jetbrains.kotlin:kotlin-reflect:1.2.0:kotlin-reflect-1.2.0.jar:4f48a872bad6e4d9c053f4ad610d11e4012ad7e58dc19a03dd5eb811f36069dd', 'org.jetbrains.kotlin:kotlin-reflect:1.2.0:kotlin-reflect-1.2.0.jar:4f48a872bad6e4d9c053f4ad610d11e4012ad7e58dc19a03dd5eb811f36069dd',
'org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.0:kotlin-stdlib-jre7-1.2.0.jar:c7a20fb951d437797afe8980aff6c1e5a03f310c661ba58ba1d4fa90cb0f2926', 'org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.0:kotlin-stdlib-jre7-1.2.0.jar:c7a20fb951d437797afe8980aff6c1e5a03f310c661ba58ba1d4fa90cb0f2926',
'org.jetbrains.kotlin:kotlin-stdlib-jre8:1.2.0:kotlin-stdlib-jre8-1.2.0.jar:633524eee6ef1941f7cb1dab7ee3927b0a221ceee9047aeb5515f4cbb990c82a', 'org.jetbrains.kotlin:kotlin-stdlib-jre8:1.2.0:kotlin-stdlib-jre8-1.2.0.jar:633524eee6ef1941f7cb1dab7ee3927b0a221ceee9047aeb5515f4cbb990c82a',
'org.jetbrains.kotlin:kotlin-stdlib:1.2.0:kotlin-stdlib-1.2.0.jar:05cfd9f5ac0b41910703a8925f7211a495909b27a2ffdd1c5106f1689aeafcd4', 'org.jetbrains.kotlin:kotlin-stdlib:1.2.0:kotlin-stdlib-1.2.0.jar:05cfd9f5ac0b41910703a8925f7211a495909b27a2ffdd1c5106f1689aeafcd4',
'org.jetbrains.trove4j:trove4j:20160824:trove4j-20160824.jar:1917871c8deb468307a584680c87a44572f5a8b0b98c6d397fc0f5f86596dbe7', 'org.jetbrains.trove4j:trove4j:20160824:trove4j-20160824.jar:1917871c8deb468307a584680c87a44572f5a8b0b98c6d397fc0f5f86596dbe7',
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478', 'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c',
'org.jmock:jmock-legacy:2.8.2:jmock-legacy-2.8.2.jar:f2b985a5c08a9edb7f37612330c058809da3f6a6d63ce792426ebf8ff0d6d31b',
'org.jmock:jmock-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760',
'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16',
'org.jvnet.staxex:stax-ex:1.7.7:stax-ex-1.7.7.jar:a31ff7d77163c0deb09e7fee59ad35ae44c2cee2cc8552a116ccd1583d813fb4', 'org.jvnet.staxex:stax-ex:1.7.7:stax-ex-1.7.7.jar:a31ff7d77163c0deb09e7fee59ad35ae44c2cee2cc8552a116ccd1583d813fb4',
'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80',
'org.ow2.asm:asm-analysis:5.1:asm-analysis-5.1.jar:a34658f5c5de4b573eef21131cc32cc25f7b66407944f312b28ec2e56abb1fa9', 'org.ow2.asm:asm-analysis:5.1:asm-analysis-5.1.jar:a34658f5c5de4b573eef21131cc32cc25f7b66407944f312b28ec2e56abb1fa9',
'org.ow2.asm:asm-commons:5.1:asm-commons-5.1.jar:97b3786e1f55e74bddf8ad102bf50e33bbcbc1f6b7fd7b36f0bbbb25cd4981be', 'org.ow2.asm:asm-commons:5.1:asm-commons-5.1.jar:97b3786e1f55e74bddf8ad102bf50e33bbcbc1f6b7fd7b36f0bbbb25cd4981be',
'org.ow2.asm:asm-tree:5.1:asm-tree-5.1.jar:c0de2bbc4cb8297419659813ecd4ed1d077ed1dd5c1f5544cc5143e493e84c10', 'org.ow2.asm:asm-tree:5.1:asm-tree-5.1.jar:c0de2bbc4cb8297419659813ecd4ed1d077ed1dd5c1f5544cc5143e493e84c10',
'org.ow2.asm:asm-util:5.1:asm-util-5.1.jar:ee032c39ae5e3cd099148fbba9a2124f9ed613e5cb93e03ee0fa8808ce364040', 'org.ow2.asm:asm-util:5.1:asm-util-5.1.jar:ee032c39ae5e3cd099148fbba9a2124f9ed613e5cb93e03ee0fa8808ce364040',
'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220',
'org.ow2.asm:asm:5.1:asm-5.1.jar:d2da399a9967c69f0a21739256fa79d284222c223082cacadc17372244764b54', 'org.ow2.asm:asm:5.1:asm-5.1.jar:d2da399a9967c69f0a21739256fa79d284222c223082cacadc17372244764b54',
] ]
} }

View File

@@ -0,0 +1,108 @@
package org.briarproject.bramble.account;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.util.IoUtils;
import java.io.File;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
class AndroidAccountManager extends AccountManagerImpl
implements AccountManager {
private static final Logger LOG =
Logger.getLogger(AndroidAccountManager.class.getName());
private static final String PREF_DB_KEY = "key";
protected final Context appContext;
private final SharedPreferences prefs;
@Inject
AndroidAccountManager(DatabaseConfig databaseConfig,
CryptoComponent crypto, IdentityManager identityManager,
SharedPreferences prefs, Application app) {
super(databaseConfig, crypto, identityManager);
this.prefs = prefs;
appContext = app.getApplicationContext();
}
// Locking: stateChangeLock
@Override
@Nullable
protected String loadEncryptedDatabaseKey() {
String key = getDatabaseKeyFromPreferences();
if (key == null) key = super.loadEncryptedDatabaseKey();
else migrateDatabaseKeyToFile(key);
return key;
}
// Locking: stateChangeLock
@Nullable
private String getDatabaseKeyFromPreferences() {
String key = prefs.getString(PREF_DB_KEY, null);
if (key == null) LOG.info("No database key in preferences");
else LOG.info("Found database key in preferences");
return key;
}
// Locking: stateChangeLock
private void migrateDatabaseKeyToFile(String key) {
if (storeEncryptedDatabaseKey(key)) {
if (prefs.edit().remove(PREF_DB_KEY).commit())
LOG.info("Database key migrated to file");
else LOG.warning("Database key not removed from preferences");
} else {
LOG.warning("Database key not migrated to file");
}
}
@Override
public void deleteAccount() {
synchronized (stateChangeLock) {
super.deleteAccount();
SharedPreferences defaultPrefs = getDefaultSharedPreferences();
deleteAppData(prefs, defaultPrefs);
}
}
// Package access for testing
SharedPreferences getDefaultSharedPreferences() {
return PreferenceManager.getDefaultSharedPreferences(appContext);
}
// Locking: stateChangeLock
private void deleteAppData(SharedPreferences... clear) {
// Clear and commit shared preferences
for (SharedPreferences prefs : clear) {
if (!prefs.edit().clear().commit())
LOG.warning("Could not clear shared preferences");
}
// Delete files, except lib and shared_prefs directories
File dataDir = new File(appContext.getApplicationInfo().dataDir);
File[] children = dataDir.listFiles();
if (children == null) {
LOG.warning("Could not list files in app data dir");
} else {
for (File child : children) {
String name = child.getName();
if (!name.equals("lib") && !name.equals("shared_prefs")) {
IoUtils.deleteFileOrDir(child);
}
}
}
// Recreate the cache dir as some OpenGL drivers expect it to exist
if (!new File(dataDir, "cache").mkdir())
LOG.warning("Could not recreate cache dir");
}
}

View File

@@ -4,6 +4,8 @@ import org.briarproject.bramble.api.lifecycle.IoExecutor;
import java.util.List; import java.util.List;
// TODO: Create a module for this so it doesn't have to be public
public interface CircumventionProvider { public interface CircumventionProvider {
/** /**

View File

@@ -16,6 +16,8 @@ import java.util.Set;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
// TODO: Create a module for this so it doesn't need to be public
public class CircumventionProviderImpl implements CircumventionProvider { public class CircumventionProviderImpl implements CircumventionProvider {
private final static String BRIDGE_FILE_NAME = "bridges"; private final static String BRIDGE_FILE_NAME = "bridges";

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.util;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build; import android.os.Build;
import android.provider.Settings; import android.provider.Settings;
@@ -58,30 +57,6 @@ public class AndroidUtils {
&& !address.equals(FAKE_BLUETOOTH_ADDRESS); && !address.equals(FAKE_BLUETOOTH_ADDRESS);
} }
public static void deleteAppData(Context ctx, SharedPreferences... clear) {
// Clear and commit shared preferences
for (SharedPreferences prefs : clear) {
if (!prefs.edit().clear().commit())
LOG.warning("Could not clear shared preferences");
}
// Delete files, except lib and shared_prefs directories
File dataDir = new File(ctx.getApplicationInfo().dataDir);
File[] children = dataDir.listFiles();
if (children == null) {
LOG.warning("Could not list files in app data dir");
} else {
for (File child : children) {
String name = child.getName();
if (!name.equals("lib") && !name.equals("shared_prefs")) {
IoUtils.deleteFileOrDir(child);
}
}
}
// Recreate the cache dir as some OpenGL drivers expect it to exist
if (!new File(dataDir, "cache").mkdir())
LOG.warning("Could not recreate cache dir");
}
public static File getReportDir(Context ctx) { public static File getReportDir(Context ctx) {
return ctx.getDir(STORED_REPORTS, MODE_PRIVATE); return ctx.getDir(STORED_REPORTS, MODE_PRIVATE);
} }

View File

@@ -0,0 +1,162 @@
package org.briarproject.bramble.account;
import android.app.Application;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.After;
import org.junit.Before;
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.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;
public class AndroidAccountManagerTest extends BrambleMockTestCase {
private final SharedPreferences prefs =
context.mock(SharedPreferences.class, "prefs");
private final SharedPreferences defaultPrefs =
context.mock(SharedPreferences.class, "defaultPrefs");
private final DatabaseConfig databaseConfig =
context.mock(DatabaseConfig.class);
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
private final IdentityManager identityManager =
context.mock(IdentityManager.class);
private final SharedPreferences.Editor
editor = context.mock(SharedPreferences.Editor.class);
private final Application app;
private final ApplicationInfo applicationInfo;
private final String encryptedKeyHex = 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");
private final File dbDir = new File(testDir, "db");
private AndroidAccountManager accountManager;
public AndroidAccountManagerTest() {
context.setImposteriser(ClassImposteriser.INSTANCE);
app = context.mock(Application.class);
applicationInfo = new ApplicationInfo();
applicationInfo.dataDir = testDir.getAbsolutePath();
}
@Before
public void setUp() {
context.checking(new Expectations() {{
allowing(databaseConfig).getDatabaseDirectory();
will(returnValue(dbDir));
allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir));
allowing(app).getApplicationContext();
will(returnValue(app));
}});
accountManager = new AndroidAccountManager(databaseConfig, crypto,
identityManager, prefs, app) {
@Override
SharedPreferences getDefaultSharedPreferences() {
return defaultPrefs;
}
};
}
@Test
public void testDbKeyIsMigratedFromPreferencesToFile() {
context.checking(new Expectations() {{
oneOf(prefs).getString("key", null);
will(returnValue(encryptedKeyHex));
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());
assertEquals(encryptedKeyHex,
accountManager.loadEncryptedDatabaseKey());
assertTrue(keyFile.exists());
assertTrue(keyBackupFile.exists());
}
@Test
public void testDeleteAccountClearsSharedPrefsAndDeletesFiles()
throws Exception {
// Directories 'lib' and 'shared_prefs' should be spared
File libDir = new File(testDir, "lib");
File libFile = new File(libDir, "file");
File sharedPrefsDir = new File(testDir, "shared_prefs");
File sharedPrefsFile = new File(sharedPrefsDir, "file");
// Directory 'cache' should be emptied
File cacheDir = new File(testDir, "cache");
File cacheFile = new File(cacheDir, "file");
// Other directories should be deleted
File potatoDir = new File(testDir, ".potato");
File potatoFile = new File(potatoDir, "file");
context.checking(new Expectations() {{
oneOf(prefs).edit();
will(returnValue(editor));
oneOf(editor).clear();
will(returnValue(editor));
oneOf(editor).commit();
will(returnValue(true));
oneOf(defaultPrefs).edit();
will(returnValue(editor));
oneOf(editor).clear();
will(returnValue(editor));
oneOf(editor).commit();
will(returnValue(true));
oneOf(app).getApplicationInfo();
will(returnValue(applicationInfo));
}});
assertTrue(dbDir.mkdirs());
assertTrue(keyDir.mkdirs());
assertTrue(libDir.mkdirs());
assertTrue(libFile.createNewFile());
assertTrue(sharedPrefsDir.mkdirs());
assertTrue(sharedPrefsFile.createNewFile());
assertTrue(cacheDir.mkdirs());
assertTrue(cacheFile.createNewFile());
assertTrue(potatoDir.mkdirs());
assertTrue(potatoFile.createNewFile());
accountManager.deleteAccount();
assertFalse(dbDir.exists());
assertFalse(keyDir.exists());
assertTrue(libDir.exists());
assertTrue(libFile.exists());
assertTrue(sharedPrefsDir.exists());
assertTrue(sharedPrefsFile.exists());
assertTrue(cacheDir.exists());
assertFalse(cacheFile.exists());
assertFalse(potatoDir.exists());
assertFalse(potatoFile.exists());
}
@After
public void tearDown() {
deleteTestDirectory(testDir);
}
}

View File

@@ -0,0 +1,70 @@
package org.briarproject.bramble.api.account;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
@NotNullByDefault
public interface AccountManager {
/**
* Returns true if the manager has the database key. This will be false
* before {@link #createAccount(String, String)} or {@link #signIn(String)}
* has been called, and true after {@link #createAccount(String, String)}
* or {@link #signIn(String)} has returned true, until the process exits.
*/
boolean hasDatabaseKey();
/**
* Returns the database key if the manager has it. This will be null
* before {@link #createAccount(String, String)} or {@link #signIn(String)}
* has been called, and non-null after
* {@link #createAccount(String, String)} or {@link #signIn(String)} has
* returned true, until the process exits.
*/
@Nullable
SecretKey getDatabaseKey();
/**
* Returns true if the encrypted database key can be loaded from disk, and
* the database directory exists and is a directory.
*/
boolean accountExists();
/**
* Creates an identity with the given name and registers it with the
* {@link IdentityManager}. Creates a database key, encrypts it with the
* given password and stores it on disk.
* <p/>
* This method does not create the database directory, so
* {@link #accountExists()} will continue to return false until the
* database directory is created.
*/
boolean createAccount(String name, String password);
/**
* Deletes all account state from disk. {@link #accountExists()} will
* return false after this method returns.
*/
void deleteAccount();
/**
* Loads the encrypted database key from disk and decrypts it with the
* given password.
*
* @return true if the database key was successfully loaded and decrypted.
*/
boolean signIn(String password);
/**
* Loads the encrypted database key from disk, decrypts it with the old
* password, encrypts it with the new password, and stores it on disk,
* replacing the old key.
*
* @return true if the database key was successfully loaded, re-encrypted
* and stored.
*/
boolean changePassword(String oldPassword, String newPassword);
}

View File

@@ -16,7 +16,7 @@ public interface ContactManager {
/** /**
* Registers a hook to be called whenever a contact is added or removed. * Registers a hook to be called whenever a contact is added or removed.
* This method should be called before * This method should be called before
* {@link LifecycleManager#startServices(String)}. * {@link LifecycleManager#startServices(SecretKey)}.
*/ */
void registerContactHook(ContactHook hook); void registerContactHook(ContactHook hook);

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble.api.db;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
@@ -44,7 +45,8 @@ public interface DatabaseComponent {
* @throws DataTooOldException if the data uses an older schema than the * @throws DataTooOldException if the data uses an older schema than the
* current code and cannot be migrated * current code and cannot be migrated
*/ */
boolean open(@Nullable MigrationListener listener) throws DbException; boolean open(SecretKey key, @Nullable MigrationListener listener)
throws DbException;
/** /**
* Waits for any open transactions to finish and closes the database. * Waits for any open transactions to finish and closes the database.
@@ -267,7 +269,7 @@ public interface DatabaseComponent {
* Read-only. * Read-only.
*/ */
Collection<MessageId> getMessageIds(Transaction txn, GroupId g) Collection<MessageId> getMessageIds(Transaction txn, GroupId g)
throws DbException; throws DbException;
/** /**
* Returns the IDs of any messages that need to be validated. * Returns the IDs of any messages that need to be validated.
@@ -487,7 +489,7 @@ public interface DatabaseComponent {
* Removes the given transport keys from the database. * Removes the given transport keys from the database.
*/ */
void removeTransportKeys(Transaction txn, TransportId t, KeySetId k) void removeTransportKeys(Transaction txn, TransportId t, KeySetId k)
throws DbException; throws DbException;
/** /**
* Marks the given contact as verified. * Marks the given contact as verified.
@@ -534,7 +536,7 @@ public interface DatabaseComponent {
* Marks the given transport keys as usable for outgoing streams. * Marks the given transport keys as usable for outgoing streams.
*/ */
void setTransportKeysActive(Transaction txn, TransportId t, KeySetId k) void setTransportKeysActive(Transaction txn, TransportId t, KeySetId k)
throws DbException; throws DbException;
/** /**
* Stores the given transport keys, deleting any keys they have replaced. * Stores the given transport keys, deleting any keys they have replaced.

View File

@@ -1,30 +1,15 @@
package org.briarproject.bramble.api.db; package org.briarproject.bramble.api.db;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.File; import java.io.File;
import javax.annotation.Nullable;
@NotNullByDefault @NotNullByDefault
public interface DatabaseConfig { public interface DatabaseConfig {
boolean databaseExists();
File getDatabaseDirectory(); File getDatabaseDirectory();
File getDatabaseKeyDirectory(); File getDatabaseKeyDirectory();
void setEncryptionKey(SecretKey key);
@Nullable
SecretKey getEncryptionKey();
void setLocalAuthorName(String nickname);
@Nullable
String getLocalAuthorName();
long getMaxSize(); long getMaxSize();
} }

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.api.identity; package org.briarproject.bramble.api.identity;
import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author.Status; import org.briarproject.bramble.api.identity.Author.Status;
@@ -9,29 +10,40 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
public interface IdentityManager { public interface IdentityManager {
/** /**
* Stores the local pseudonym. * Creates a local identity with the given name.
*/ */
void registerLocalAuthor(LocalAuthor a) throws DbException; @CryptoExecutor
LocalAuthor createLocalAuthor(String name);
/** /**
* Returns the cached main local identity, non-blocking, or loads it from * Registers the given local identity with the manager. The identity is
* the db, blocking * not stored until {@link #storeLocalAuthor()} is called.
*/
void registerLocalAuthor(LocalAuthor a);
/**
* Stores the local identity registered with
* {@link #registerLocalAuthor(LocalAuthor)}, if any.
*/
void storeLocalAuthor() throws DbException;
/**
* Returns the cached local identity or loads it from the database.
*/ */
LocalAuthor getLocalAuthor() throws DbException; LocalAuthor getLocalAuthor() throws DbException;
/** /**
* Returns the cached main local identity, non-blocking, or loads it from * Returns the cached local identity or loads it from the database.
* the db, blocking, within the given Transaction.
*/ */
LocalAuthor getLocalAuthor(Transaction txn) throws DbException; LocalAuthor getLocalAuthor(Transaction txn) throws DbException;
/** /**
* Returns the trust-level status of the author * Returns the {@link Status} of the given author.
*/ */
Status getAuthorStatus(AuthorId a) throws DbException; Status getAuthorStatus(AuthorId a) throws DbException;
/** /**
* Returns the trust-level status of the author * Returns the {@link Status} of the given author.
*/ */
Status getAuthorStatus(Transaction txn, AuthorId a) throws DbException; Status getAuthorStatus(Transaction txn, AuthorId a) throws DbException;

View File

@@ -1,13 +1,12 @@
package org.briarproject.bramble.api.lifecycle; package org.briarproject.bramble.api.lifecycle;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Client; import org.briarproject.bramble.api.sync.Client;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import javax.annotation.Nullable;
/** /**
* Manages the lifecycle of the app, starting {@link Client Clients}, starting * Manages the lifecycle of the app, starting {@link Client Clients}, starting
* and stopping {@link Service Services}, shutting down * and stopping {@link Service Services}, shutting down
@@ -18,7 +17,7 @@ import javax.annotation.Nullable;
public interface LifecycleManager { public interface LifecycleManager {
/** /**
* The result of calling {@link #startServices(String)}. * The result of calling {@link #startServices(SecretKey)}.
*/ */
enum StartResult { enum StartResult {
ALREADY_RUNNING, ALREADY_RUNNING,
@@ -44,28 +43,27 @@ public interface LifecycleManager {
/** /**
* Registers a {@link Service} to be started and stopped. This method * Registers a {@link Service} to be started and stopped. This method
* should be called before {@link #startServices(String)}. * should be called before {@link #startServices(SecretKey)}.
*/ */
void registerService(Service s); void registerService(Service s);
/** /**
* Registers a {@link Client} to be started. This method should be called * Registers a {@link Client} to be started. This method should be called
* before {@link #startServices(String)}. * before {@link #startServices(SecretKey)}.
*/ */
void registerClient(Client c); void registerClient(Client c);
/** /**
* Registers an {@link ExecutorService} to be shut down. This method * Registers an {@link ExecutorService} to be shut down. This method
* should be called before {@link #startServices(String)}. * should be called before {@link #startServices(SecretKey)}.
*/ */
void registerForShutdown(ExecutorService e); void registerForShutdown(ExecutorService e);
/** /**
* Opens the {@link DatabaseComponent}, optionally creates a local author * Opens the {@link DatabaseComponent} using the given key and starts any
* with the provided nickname, and starts any registered * registered {@link Client Clients} and {@link Service Services}.
* {@link Client Clients} and {@link Service Services}.
*/ */
StartResult startServices(@Nullable String nickname); StartResult startServices(SecretKey dbKey);
/** /**
* Stops any registered {@link Service Services}, shuts down any * Stops any registered {@link Service Services}, shuts down any

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.api.sync; package org.briarproject.bramble.api.sync;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata; import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
@@ -35,7 +36,8 @@ public interface ValidationManager {
/** /**
* Registers the message validator for the given client. This method * Registers the message validator for the given client. This method
* should be called before {@link LifecycleManager#startServices(String)}. * should be called before
* {@link LifecycleManager#startServices(SecretKey)}.
*/ */
void registerMessageValidator(ClientId c, int majorVersion, void registerMessageValidator(ClientId c, int majorVersion,
MessageValidator v); MessageValidator v);
@@ -44,7 +46,7 @@ public interface ValidationManager {
* Registers the incoming message hook for the given client. The hook will * Registers the incoming message hook for the given client. The hook will
* be called once for each incoming message that passes validation. This * be called once for each incoming message that passes validation. This
* method should be called before * method should be called before
* {@link LifecycleManager#startServices(String)}. * {@link LifecycleManager#startServices(SecretKey)}.
*/ */
void registerIncomingMessageHook(ClientId c, int majorVersion, void registerIncomingMessageHook(ClientId c, int majorVersion,
IncomingMessageHook hook); IncomingMessageHook hook);

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble.api.versioning;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
@@ -25,7 +26,7 @@ public interface ClientVersioningManager {
/** /**
* Registers a client that will be advertised to contacts. The hook will * Registers a client that will be advertised to contacts. The hook will
* be called when the visibility of the client changes. This method should * be called when the visibility of the client changes. This method should
* be called before {@link LifecycleManager#startServices(String)}. * be called before {@link LifecycleManager#startServices(SecretKey)}.
*/ */
void registerClient(ClientId clientId, int majorVersion, int minorVersion, void registerClient(ClientId clientId, int majorVersion, int minorVersion,
ClientVersioningHook hook); ClientVersioningHook hook);

View File

@@ -0,0 +1,221 @@
package org.briarproject.bramble.account;
import org.briarproject.bramble.api.account.AccountManager;
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.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.util.IoUtils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.fromHexString;
import static org.briarproject.bramble.util.StringUtils.toHexString;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class AccountManagerImpl implements AccountManager {
private static final Logger LOG =
Logger.getLogger(AccountManagerImpl.class.getName());
private static final String DB_KEY_FILENAME = "db.key";
private static final String DB_KEY_BACKUP_FILENAME = "db.key.bak";
private final DatabaseConfig databaseConfig;
private final CryptoComponent crypto;
private final IdentityManager identityManager;
private final File dbKeyFile, dbKeyBackupFile;
final Object stateChangeLock = new Object();
@Nullable
private volatile SecretKey databaseKey = null;
@Inject
AccountManagerImpl(DatabaseConfig databaseConfig, CryptoComponent crypto,
IdentityManager identityManager) {
this.databaseConfig = databaseConfig;
this.crypto = crypto;
this.identityManager = identityManager;
File keyDir = databaseConfig.getDatabaseKeyDirectory();
dbKeyFile = new File(keyDir, DB_KEY_FILENAME);
dbKeyBackupFile = new File(keyDir, DB_KEY_BACKUP_FILENAME);
}
@Override
public boolean hasDatabaseKey() {
return databaseKey != null;
}
@Override
@Nullable
public SecretKey getDatabaseKey() {
return databaseKey;
}
// Locking: stateChangeLock
@Nullable
protected String loadEncryptedDatabaseKey() {
String key = readDbKeyFromFile(dbKeyFile);
if (key == null) {
LOG.info("No database key in primary file");
key = readDbKeyFromFile(dbKeyBackupFile);
if (key == null) LOG.info("No database key in backup file");
else LOG.warning("Found database key in backup file");
} else {
LOG.info("Found database key in primary file");
}
return key;
}
// Locking: stateChangeLock
@Nullable
private String readDbKeyFromFile(File f) {
if (!f.exists()) {
LOG.info("Key file does not exist");
return null;
}
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(
new FileInputStream(f), "UTF-8"));
String key = reader.readLine();
reader.close();
return key;
} catch (IOException e) {
logException(LOG, WARNING, e);
return null;
}
}
// Locking: stateChangeLock
protected boolean storeEncryptedDatabaseKey(String hex) {
LOG.info("Storing database key in file");
// Create the directory if necessary
if (databaseConfig.getDatabaseKeyDirectory().mkdirs())
LOG.info("Created database key directory");
// If only the backup file exists, rename it so we don't overwrite it
if (dbKeyBackupFile.exists() && !dbKeyFile.exists()) {
if (dbKeyBackupFile.renameTo(dbKeyFile))
LOG.info("Renamed old backup");
else LOG.warning("Failed to rename old backup");
}
try {
// Write to the backup file
writeDbKeyToFile(hex, dbKeyBackupFile);
LOG.info("Stored database key in backup file");
// Delete the old primary file, if it exists
if (dbKeyFile.exists()) {
if (dbKeyFile.delete()) LOG.info("Deleted primary file");
else LOG.warning("Failed to delete primary file");
}
// The backup file becomes the new primary
if (dbKeyBackupFile.renameTo(dbKeyFile)) {
LOG.info("Renamed backup file to primary");
} else {
LOG.warning("Failed to rename backup file to primary");
return false; // Don't overwrite our only copy
}
// Write a second copy to the backup file
writeDbKeyToFile(hex, dbKeyBackupFile);
LOG.info("Stored second copy of database key in backup file");
return true;
} catch (IOException e) {
logException(LOG, WARNING, e);
return false;
}
}
// Locking: stateChangeLock
private void writeDbKeyToFile(String key, File f) throws IOException {
FileOutputStream out = new FileOutputStream(f);
out.write(key.getBytes("UTF-8"));
out.flush();
out.close();
}
@Override
public boolean accountExists() {
synchronized (stateChangeLock) {
return loadEncryptedDatabaseKey() != null
&& databaseConfig.getDatabaseDirectory().isDirectory();
}
}
@Override
public boolean createAccount(String name, String password) {
synchronized (stateChangeLock) {
LocalAuthor localAuthor = identityManager.createLocalAuthor(name);
identityManager.registerLocalAuthor(localAuthor);
SecretKey key = crypto.generateSecretKey();
if (!encryptAndStoreDatabaseKey(key, password)) return false;
databaseKey = key;
return true;
}
}
// Locking: stateChangeLock
private boolean encryptAndStoreDatabaseKey(SecretKey key, String password) {
byte[] plaintext = key.getBytes();
byte[] ciphertext = crypto.encryptWithPassword(plaintext, password);
return storeEncryptedDatabaseKey(toHexString(ciphertext));
}
@Override
public void deleteAccount() {
synchronized (stateChangeLock) {
LOG.info("Deleting account");
IoUtils.deleteFileOrDir(databaseConfig.getDatabaseKeyDirectory());
IoUtils.deleteFileOrDir(databaseConfig.getDatabaseDirectory());
}
}
@Override
public boolean signIn(String password) {
synchronized (stateChangeLock) {
SecretKey key = loadAndDecryptDatabaseKey(password);
if (key == null) return false;
databaseKey = key;
return true;
}
}
// Locking: stateChangeLock
@Nullable
private SecretKey loadAndDecryptDatabaseKey(String password) {
String hex = loadEncryptedDatabaseKey();
if (hex == null) {
LOG.warning("Failed to load encrypted database key");
return null;
}
byte[] ciphertext = fromHexString(hex);
byte[] plaintext = crypto.decryptWithPassword(ciphertext, password);
if (plaintext == null) {
LOG.info("Failed to decrypt database key");
return null;
}
return new SecretKey(plaintext);
}
@Override
public boolean changePassword(String oldPassword, String newPassword) {
synchronized (stateChangeLock) {
SecretKey key = loadAndDecryptDatabaseKey(oldPassword);
return key != null && encryptAndStoreDatabaseKey(key, newPassword);
}
}
}

View File

@@ -0,0 +1,18 @@
package org.briarproject.bramble.account;
import org.briarproject.bramble.api.account.AccountManager;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class AccountModule {
@Provides
@Singleton
AccountManager provideAccountManager(AccountManagerImpl accountManager) {
return accountManager;
}
}

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble.db;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DataTooNewException; import org.briarproject.bramble.api.db.DataTooNewException;
import org.briarproject.bramble.api.db.DataTooOldException; import org.briarproject.bramble.api.db.DataTooOldException;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
@@ -48,7 +49,8 @@ interface Database<T> {
* @throws DataTooOldException if the data uses an older schema than the * @throws DataTooOldException if the data uses an older schema than the
* current code and cannot be migrated * current code and cannot be migrated
*/ */
boolean open(@Nullable MigrationListener listener) throws DbException; boolean open(SecretKey key, @Nullable MigrationListener listener)
throws DbException;
/** /**
* Prevents new transactions from starting, waits for all current * Prevents new transactions from starting, waits for all current
@@ -641,7 +643,7 @@ interface Database<T> {
* Marks the given transport keys as usable for outgoing streams. * Marks the given transport keys as usable for outgoing streams.
*/ */
void setTransportKeysActive(T txn, TransportId t, KeySetId k) void setTransportKeysActive(T txn, TransportId t, KeySetId k)
throws DbException; throws DbException;
/** /**
* Updates the transmission count and expiry time of the given message * Updates the transmission count and expiry time of the given message

View File

@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent; import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.contact.event.ContactStatusChangedEvent; import org.briarproject.bramble.api.contact.event.ContactStatusChangedEvent;
import org.briarproject.bramble.api.contact.event.ContactVerifiedEvent; import org.briarproject.bramble.api.contact.event.ContactVerifiedEvent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.ContactExistsException; import org.briarproject.bramble.api.db.ContactExistsException;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
@@ -103,9 +104,9 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public boolean open(@Nullable MigrationListener listener) public boolean open(SecretKey key, @Nullable MigrationListener listener)
throws DbException { throws DbException {
boolean reopened = db.open(listener); boolean reopened = db.open(key, listener);
shutdown.addShutdownHook(() -> { shutdown.addShutdownHook(() -> {
try { try {
close(); close();

View File

@@ -32,6 +32,9 @@ class H2Database extends JdbcDatabase {
private final DatabaseConfig config; private final DatabaseConfig config;
private final String url; private final String url;
@Nullable
private volatile SecretKey key = null;
@Inject @Inject
H2Database(DatabaseConfig config, Clock clock) { H2Database(DatabaseConfig config, Clock clock) {
super(HASH_TYPE, SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE, super(HASH_TYPE, SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE,
@@ -44,11 +47,11 @@ class H2Database extends JdbcDatabase {
} }
@Override @Override
public boolean open(@Nullable MigrationListener listener) public boolean open(SecretKey key, @Nullable MigrationListener listener)
throws DbException { throws DbException {
boolean reopen = config.databaseExists(); this.key = key;
if (!reopen) config.getDatabaseDirectory().mkdirs(); boolean reopen = !config.getDatabaseDirectory().mkdirs();
super.open("org.h2.Driver", reopen, listener); super.open("org.h2.Driver", reopen, key, listener);
return reopen; return reopen;
} }
@@ -63,7 +66,7 @@ class H2Database extends JdbcDatabase {
} }
@Override @Override
public long getFreeSpace() throws DbException { public long getFreeSpace() {
File dir = config.getDatabaseDirectory(); File dir = config.getDatabaseDirectory();
long maxSize = config.getMaxSize(); long maxSize = config.getMaxSize();
long free = dir.getFreeSpace(); long free = dir.getFreeSpace();
@@ -88,7 +91,7 @@ class H2Database extends JdbcDatabase {
@Override @Override
protected Connection createConnection() throws SQLException { protected Connection createConnection() throws SQLException {
SecretKey key = config.getEncryptionKey(); SecretKey key = this.key;
if (key == null) throw new IllegalStateException(); if (key == null) throw new IllegalStateException();
Properties props = new Properties(); Properties props = new Properties();
props.setProperty("user", "user"); props.setProperty("user", "user");

View File

@@ -33,6 +33,9 @@ class HyperSqlDatabase extends JdbcDatabase {
private final DatabaseConfig config; private final DatabaseConfig config;
private final String url; private final String url;
@Nullable
private volatile SecretKey key = null;
@Inject @Inject
HyperSqlDatabase(DatabaseConfig config, Clock clock) { HyperSqlDatabase(DatabaseConfig config, Clock clock) {
super(HASH_TYPE, SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE, super(HASH_TYPE, SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE,
@@ -46,10 +49,11 @@ class HyperSqlDatabase extends JdbcDatabase {
} }
@Override @Override
public boolean open(@Nullable MigrationListener listener) throws DbException { public boolean open(SecretKey key, @Nullable MigrationListener listener)
boolean reopen = config.databaseExists(); throws DbException {
if (!reopen) config.getDatabaseDirectory().mkdirs(); this.key = key;
super.open("org.hsqldb.jdbc.JDBCDriver", reopen, listener); boolean reopen = !config.getDatabaseDirectory().mkdirs();
super.open("org.hsqldb.jdbc.JDBCDriver", reopen, key, listener);
return reopen; return reopen;
} }
@@ -93,7 +97,7 @@ class HyperSqlDatabase extends JdbcDatabase {
@Override @Override
protected Connection createConnection() throws SQLException { protected Connection createConnection() throws SQLException {
SecretKey key = config.getEncryptionKey(); SecretKey key = this.key;
if (key == null) throw new IllegalStateException(); if (key == null) throw new IllegalStateException();
String hex = StringUtils.toHexString(key.getBytes()); String hex = StringUtils.toHexString(key.getBytes());
return DriverManager.getConnection(url + ";crypt_key=" + hex); return DriverManager.getConnection(url + ";crypt_key=" + hex);

View File

@@ -328,7 +328,7 @@ abstract class JdbcDatabase implements Database<Connection> {
this.clock = clock; this.clock = clock;
} }
protected void open(String driverClass, boolean reopen, protected void open(String driverClass, boolean reopen, SecretKey key,
@Nullable MigrationListener listener) throws DbException { @Nullable MigrationListener listener) throws DbException {
// Load the JDBC driver // Load the JDBC driver
try { try {

View File

@@ -1,10 +1,13 @@
package org.briarproject.bramble.identity; package org.briarproject.bramble.identity;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author.Status; import org.briarproject.bramble.api.identity.Author.Status;
import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
@@ -21,6 +24,8 @@ import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
import static org.briarproject.bramble.api.identity.Author.Status.UNKNOWN; import static org.briarproject.bramble.api.identity.Author.Status.UNKNOWN;
import static org.briarproject.bramble.api.identity.Author.Status.UNVERIFIED; import static org.briarproject.bramble.api.identity.Author.Status.UNVERIFIED;
import static org.briarproject.bramble.api.identity.Author.Status.VERIFIED; import static org.briarproject.bramble.api.identity.Author.Status.VERIFIED;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.now;
@ThreadSafe @ThreadSafe
@NotNullByDefault @NotNullByDefault
@@ -30,25 +35,51 @@ class IdentityManagerImpl implements IdentityManager {
Logger.getLogger(IdentityManagerImpl.class.getName()); Logger.getLogger(IdentityManagerImpl.class.getName());
private final DatabaseComponent db; private final DatabaseComponent db;
private final CryptoComponent crypto;
private final AuthorFactory authorFactory;
// The local author is immutable so we can cache it // The local author is immutable so we can cache it
@Nullable @Nullable
private volatile LocalAuthor cachedAuthor; private volatile LocalAuthor cachedAuthor;
@Inject @Inject
IdentityManagerImpl(DatabaseComponent db) { IdentityManagerImpl(DatabaseComponent db, CryptoComponent crypto,
AuthorFactory authorFactory) {
this.db = db; this.db = db;
this.crypto = crypto;
this.authorFactory = authorFactory;
} }
@Override @Override
public void registerLocalAuthor(LocalAuthor localAuthor) public LocalAuthor createLocalAuthor(String name) {
throws DbException { long start = now();
KeyPair keyPair = crypto.generateSignatureKeyPair();
byte[] publicKey = keyPair.getPublic().getEncoded();
byte[] privateKey = keyPair.getPrivate().getEncoded();
LocalAuthor localAuthor = authorFactory.createLocalAuthor(name,
publicKey, privateKey);
logDuration(LOG, "Creating local author", start);
return localAuthor;
}
@Override
public void registerLocalAuthor(LocalAuthor a) {
cachedAuthor = a;
LOG.info("Local author registered");
}
@Override
public void storeLocalAuthor() throws DbException {
LocalAuthor cached = cachedAuthor;
if (cached == null) {
LOG.info("No local author to store");
return;
}
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
db.addLocalAuthor(txn, localAuthor); db.addLocalAuthor(txn, cached);
db.commitTransaction(txn); db.commitTransaction(txn);
cachedAuthor = localAuthor; LOG.info("Local author stored");
LOG.info("Local author registered");
} finally { } finally {
db.endTransaction(txn); db.endTransaction(txn);
} }

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.lifecycle; package org.briarproject.bramble.lifecycle;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.db.DataTooNewException; import org.briarproject.bramble.api.db.DataTooNewException;
import org.briarproject.bramble.api.db.DataTooOldException; import org.briarproject.bramble.api.db.DataTooOldException;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
@@ -9,9 +8,7 @@ import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.MigrationListener; import org.briarproject.bramble.api.db.MigrationListener;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.lifecycle.Service; import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.lifecycle.ServiceException; import org.briarproject.bramble.api.lifecycle.ServiceException;
@@ -26,7 +23,6 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject; import javax.inject.Inject;
@@ -60,8 +56,6 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
private final List<Service> services; private final List<Service> services;
private final List<Client> clients; private final List<Client> clients;
private final List<ExecutorService> executors; private final List<ExecutorService> executors;
private final CryptoComponent crypto;
private final AuthorFactory authorFactory;
private final IdentityManager identityManager; private final IdentityManager identityManager;
private final Semaphore startStopSemaphore = new Semaphore(1); private final Semaphore startStopSemaphore = new Semaphore(1);
private final CountDownLatch dbLatch = new CountDownLatch(1); private final CountDownLatch dbLatch = new CountDownLatch(1);
@@ -72,12 +66,9 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
@Inject @Inject
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus, LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
CryptoComponent crypto, AuthorFactory authorFactory,
IdentityManager identityManager) { IdentityManager identityManager) {
this.db = db; this.db = db;
this.eventBus = eventBus; this.eventBus = eventBus;
this.crypto = crypto;
this.authorFactory = authorFactory;
this.identityManager = identityManager; this.identityManager = identityManager;
services = new CopyOnWriteArrayList<>(); services = new CopyOnWriteArrayList<>();
clients = new CopyOnWriteArrayList<>(); clients = new CopyOnWriteArrayList<>();
@@ -104,25 +95,8 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
executors.add(e); executors.add(e);
} }
private LocalAuthor createLocalAuthor(String nickname) {
long start = now();
KeyPair keyPair = crypto.generateSignatureKeyPair();
byte[] publicKey = keyPair.getPublic().getEncoded();
byte[] privateKey = keyPair.getPrivate().getEncoded();
LocalAuthor localAuthor = authorFactory
.createLocalAuthor(nickname, publicKey, privateKey);
logDuration(LOG, "Creating local author", start);
return localAuthor;
}
private void registerLocalAuthor(LocalAuthor author) throws DbException {
long start = now();
identityManager.registerLocalAuthor(author);
logDuration(LOG, "Registering local author", start);
}
@Override @Override
public StartResult startServices(@Nullable String nickname) { public StartResult startServices(SecretKey dbKey) {
if (!startStopSemaphore.tryAcquire()) { if (!startStopSemaphore.tryAcquire()) {
LOG.info("Already starting or stopping"); LOG.info("Already starting or stopping");
return ALREADY_RUNNING; return ALREADY_RUNNING;
@@ -131,13 +105,10 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
LOG.info("Starting services"); LOG.info("Starting services");
long start = now(); long start = now();
boolean reopened = db.open(this); boolean reopened = db.open(dbKey, this);
if (reopened) logDuration(LOG, "Reopening database", start); if (reopened) logDuration(LOG, "Reopening database", start);
else logDuration(LOG, "Creating database", start); else logDuration(LOG, "Creating database", start);
identityManager.storeLocalAuthor();
if (nickname != null) {
registerLocalAuthor(createLocalAuthor(nickname));
}
state = STARTING_SERVICES; state = STARTING_SERVICES;
dbLatch.countDown(); dbLatch.countDown();

View File

@@ -1,10 +1,5 @@
package org.briarproject.bramble.lifecycle; package org.briarproject.bramble.lifecycle;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.lifecycle.ShutdownManager; import org.briarproject.bramble.api.lifecycle.ShutdownManager;
@@ -54,11 +49,9 @@ public class LifecycleModule {
@Provides @Provides
@Singleton @Singleton
LifecycleManager provideLifecycleManager(DatabaseComponent db, LifecycleManager provideLifecycleManager(
EventBus eventBus, CryptoComponent crypto, LifecycleManagerImpl lifecycleManager) {
AuthorFactory authorFactory, IdentityManager identityManager) { return lifecycleManager;
return new LifecycleManagerImpl(db, eventBus, crypto, authorFactory,
identityManager);
} }
@Provides @Provides

View File

@@ -0,0 +1,341 @@
package org.briarproject.bramble.account;
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.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.annotation.Nullable;
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.getLocalAuthor;
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.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
public class AccountManagerImplTest extends BrambleMockTestCase {
private final DatabaseConfig databaseConfig =
context.mock(DatabaseConfig.class);
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
private final IdentityManager identityManager =
context.mock(IdentityManager.class);
private final SecretKey key = getSecretKey();
private final byte[] encryptedKey = getRandomBytes(123);
private final String encryptedKeyHex = toHexString(encryptedKey);
private final byte[] newEncryptedKey = getRandomBytes(123);
private final String newEncryptedKeyHex = toHexString(newEncryptedKey);
private final LocalAuthor localAuthor = getLocalAuthor();
private final String authorName = localAuthor.getName();
private final String password = getRandomString(10);
private final String newPassword = getRandomString(10);
private final File testDir = getTestDirectory();
private final File dbDir = new File(testDir, "db");
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");
private AccountManagerImpl accountManager;
@Before
public void setUp() {
context.checking(new Expectations() {{
allowing(databaseConfig).getDatabaseDirectory();
will(returnValue(dbDir));
allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir));
}});
accountManager =
new AccountManagerImpl(databaseConfig, crypto, identityManager);
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
}
@Test
public void testSignInReturnsFalseIfDbKeyCannotBeLoaded() {
assertFalse(accountManager.signIn(password));
assertFalse(accountManager.hasDatabaseKey());
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
}
@Test
public void testSignInReturnsFalseIfPasswordIsWrong() throws Exception {
context.checking(new Expectations() {{
oneOf(crypto).decryptWithPassword(encryptedKey, password);
will(returnValue(null));
}});
storeDatabaseKey(keyFile, encryptedKeyHex);
storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
assertFalse(accountManager.signIn(password));
assertFalse(accountManager.hasDatabaseKey());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
}
@Test
public void testSignInReturnsTrueIfPasswordIsRight() throws Exception {
context.checking(new Expectations() {{
oneOf(crypto).decryptWithPassword(encryptedKey, password);
will(returnValue(key.getBytes()));
}});
storeDatabaseKey(keyFile, encryptedKeyHex);
storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
assertTrue(accountManager.signIn(password));
assertTrue(accountManager.hasDatabaseKey());
SecretKey decrypted = accountManager.getDatabaseKey();
assertNotNull(decrypted);
assertArrayEquals(key.getBytes(), decrypted.getBytes());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
}
@Test
public void testDbKeyIsLoadedFromPrimaryFile() throws Exception {
storeDatabaseKey(keyFile, encryptedKeyHex);
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertFalse(keyBackupFile.exists());
assertEquals(encryptedKeyHex,
accountManager.loadEncryptedDatabaseKey());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertFalse(keyBackupFile.exists());
}
@Test
public void testDbKeyIsLoadedFromBackupFile() throws Exception {
storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertFalse(keyFile.exists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
assertEquals(encryptedKeyHex,
accountManager.loadEncryptedDatabaseKey());
assertFalse(keyFile.exists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
}
@Test
public void testDbKeyIsNullIfNotFound() {
assertNull(accountManager.loadEncryptedDatabaseKey());
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
}
@Test
public void testStoringDbKeyOverwritesPrimary() throws Exception {
storeDatabaseKey(keyFile, encryptedKeyHex);
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertFalse(keyBackupFile.exists());
assertTrue(accountManager.storeEncryptedDatabaseKey(
newEncryptedKeyHex));
assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyBackupFile));
}
@Test
public void testStoringDbKeyOverwritesBackup() throws Exception {
storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertFalse(keyFile.exists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
assertTrue(accountManager.storeEncryptedDatabaseKey(
newEncryptedKeyHex));
assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyBackupFile));
}
@Test
public void testAccountExistsReturnsFalseIfDbKeyCannotBeLoaded() {
assertFalse(accountManager.accountExists());
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
}
@Test
public void testAccountExistsReturnsFalseIfDbDirectoryDoesNotExist()
throws Exception {
storeDatabaseKey(keyFile, encryptedKeyHex);
storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertFalse(dbDir.exists());
assertFalse(accountManager.accountExists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
assertFalse(dbDir.exists());
}
@Test
public void testAccountExistsReturnsFalseIfDbDirectoryIsNotDirectory()
throws Exception {
storeDatabaseKey(keyFile, encryptedKeyHex);
storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertTrue(dbDir.createNewFile());
assertFalse(dbDir.isDirectory());
assertFalse(accountManager.accountExists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
assertTrue(dbDir.exists());
assertFalse(dbDir.isDirectory());
}
@Test
public void testAccountExistsReturnsTrueIfDbDirectoryIsDirectory()
throws Exception {
storeDatabaseKey(keyFile, encryptedKeyHex);
storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertTrue(dbDir.mkdirs());
assertTrue(dbDir.isDirectory());
assertTrue(accountManager.accountExists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
assertTrue(dbDir.exists());
assertTrue(dbDir.isDirectory());
}
@Test
public void testCreateAccountStoresDbKey() throws Exception {
context.checking(new Expectations() {{
oneOf(identityManager).createLocalAuthor(authorName);
will(returnValue(localAuthor));
oneOf(identityManager).registerLocalAuthor(localAuthor);
oneOf(crypto).generateSecretKey();
will(returnValue(key));
oneOf(crypto).encryptWithPassword(key.getBytes(), password);
will(returnValue(encryptedKey));
}});
assertFalse(accountManager.hasDatabaseKey());
assertTrue(accountManager.createAccount(authorName, password));
assertTrue(accountManager.hasDatabaseKey());
SecretKey dbKey = accountManager.getDatabaseKey();
assertNotNull(dbKey);
assertArrayEquals(key.getBytes(), dbKey.getBytes());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
}
@Test
public void testChangePasswordReturnsFalseIfDbKeyCannotBeLoaded() {
assertFalse(accountManager.changePassword(password, newPassword));
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
}
@Test
public void testChangePasswordReturnsFalseIfPasswordIsWrong()
throws Exception {
context.checking(new Expectations() {{
oneOf(crypto).decryptWithPassword(encryptedKey, password);
will(returnValue(null));
}});
storeDatabaseKey(keyFile, encryptedKeyHex);
storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertFalse(accountManager.changePassword(password, newPassword));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
}
@Test
public void testChangePasswordReturnsTrueIfPasswordIsRight()
throws Exception {
context.checking(new Expectations() {{
oneOf(crypto).decryptWithPassword(encryptedKey, password);
will(returnValue(key.getBytes()));
oneOf(crypto).encryptWithPassword(key.getBytes(), newPassword);
will(returnValue(newEncryptedKey));
}});
storeDatabaseKey(keyFile, encryptedKeyHex);
storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertTrue(accountManager.changePassword(password, newPassword));
assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyBackupFile));
}
private void storeDatabaseKey(File f, String hex) throws IOException {
f.getParentFile().mkdirs();
FileOutputStream out = new FileOutputStream(f);
out.write(hex.getBytes("UTF-8"));
out.flush();
out.close();
}
@Nullable
private String loadDatabaseKey(File f) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(
new FileInputStream(f), "UTF-8"));
String hex = reader.readLine();
reader.close();
return hex;
}
@After
public void tearDown() {
deleteTestDirectory(testDir);
}
}

View File

@@ -89,6 +89,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
context.mock(ShutdownManager.class); context.mock(ShutdownManager.class);
private final EventBus eventBus = context.mock(EventBus.class); private final EventBus eventBus = context.mock(EventBus.class);
private final SecretKey key = getSecretKey();
private final Object txn = new Object(); private final Object txn = new Object();
private final ClientId clientId; private final ClientId clientId;
private final int majorVersion; private final int majorVersion;
@@ -141,7 +142,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
int shutdownHandle = 12345; int shutdownHandle = 12345;
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// open() // open()
oneOf(database).open(null); oneOf(database).open(key, null);
will(returnValue(false)); will(returnValue(false));
oneOf(shutdown).addShutdownHook(with(any(Runnable.class))); oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
will(returnValue(shutdownHandle)); will(returnValue(shutdownHandle));
@@ -208,7 +209,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
assertFalse(db.open(null)); assertFalse(db.open(key, null));
Transaction transaction = db.startTransaction(false); Transaction transaction = db.startTransaction(false);
try { try {
db.addLocalAuthor(transaction, localAuthor); db.addLocalAuthor(transaction, localAuthor);
@@ -1602,7 +1603,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
MessageId messageId2 = new MessageId(getRandomId()); MessageId messageId2 = new MessageId(getRandomId());
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// open() // open()
oneOf(database).open(null); oneOf(database).open(key, null);
will(returnValue(false)); will(returnValue(false));
oneOf(shutdown).addShutdownHook(with(any(Runnable.class))); oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
will(returnValue(shutdownHandle)); will(returnValue(shutdownHandle));
@@ -1646,7 +1647,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
assertFalse(db.open(null)); assertFalse(db.open(key, null));
Transaction transaction = db.startTransaction(false); Transaction transaction = db.startTransaction(false);
try { try {
db.addLocalMessage(transaction, message, metadata, true); db.addLocalMessage(transaction, message, metadata, true);

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.db; package org.briarproject.bramble.db;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DataTooNewException; import org.briarproject.bramble.api.db.DataTooNewException;
import org.briarproject.bramble.api.db.DataTooOldException; import org.briarproject.bramble.api.db.DataTooOldException;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.db.DatabaseConfig;
@@ -26,6 +27,7 @@ import static java.util.Collections.singletonList;
import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE; import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
import static org.briarproject.bramble.db.DatabaseConstants.SCHEMA_VERSION_KEY; import static org.briarproject.bramble.db.DatabaseConstants.SCHEMA_VERSION_KEY;
import static org.briarproject.bramble.db.JdbcDatabase.CODE_SCHEMA_VERSION; import static org.briarproject.bramble.db.JdbcDatabase.CODE_SCHEMA_VERSION;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@@ -43,6 +45,7 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
protected final DatabaseConfig config = protected final DatabaseConfig config =
new TestDatabaseConfig(testDir, 1024 * 1024); new TestDatabaseConfig(testDir, 1024 * 1024);
protected final SecretKey key = getSecretKey();
protected final Clock clock = new SystemClock(); protected final Clock clock = new SystemClock();
abstract Database<Connection> createDatabase( abstract Database<Connection> createDatabase(
@@ -62,7 +65,7 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
public void testDoesNotRunMigrationsWhenCreatingDatabase() public void testDoesNotRunMigrationsWhenCreatingDatabase()
throws Exception { throws Exception {
Database<Connection> db = createDatabase(singletonList(migration)); Database<Connection> db = createDatabase(singletonList(migration));
assertFalse(db.open(null)); assertFalse(db.open(key, null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db)); assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
db.close(); db.close();
} }
@@ -72,14 +75,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
throws Exception { throws Exception {
// Open the DB for the first time // Open the DB for the first time
Database<Connection> db = createDatabase(asList(migration, migration1)); Database<Connection> db = createDatabase(asList(migration, migration1));
assertFalse(db.open(null)); assertFalse(db.open(key, null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db)); assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
// Override the data schema version // Override the data schema version
setDataSchemaVersion(db, -1); setDataSchemaVersion(db, -1);
db.close(); db.close();
// Reopen the DB - an exception should be thrown // Reopen the DB - an exception should be thrown
db = createDatabase(asList(migration, migration1)); db = createDatabase(asList(migration, migration1));
db.open(null); db.open(key, null);
} }
@Test @Test
@@ -87,12 +90,12 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
throws Exception { throws Exception {
// Open the DB for the first time // Open the DB for the first time
Database<Connection> db = createDatabase(asList(migration, migration1)); Database<Connection> db = createDatabase(asList(migration, migration1));
assertFalse(db.open(null)); assertFalse(db.open(key, null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db)); assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
db.close(); db.close();
// Reopen the DB - migrations should not be run // Reopen the DB - migrations should not be run
db = createDatabase(asList(migration, migration1)); db = createDatabase(asList(migration, migration1));
assertTrue(db.open(null)); assertTrue(db.open(key, null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db)); assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
db.close(); db.close();
} }
@@ -101,14 +104,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
public void testThrowsExceptionIfDataIsNewerThanCode() throws Exception { public void testThrowsExceptionIfDataIsNewerThanCode() throws Exception {
// Open the DB for the first time // Open the DB for the first time
Database<Connection> db = createDatabase(asList(migration, migration1)); Database<Connection> db = createDatabase(asList(migration, migration1));
assertFalse(db.open(null)); assertFalse(db.open(key, null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db)); assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
// Override the data schema version // Override the data schema version
setDataSchemaVersion(db, CODE_SCHEMA_VERSION + 1); setDataSchemaVersion(db, CODE_SCHEMA_VERSION + 1);
db.close(); db.close();
// Reopen the DB - an exception should be thrown // Reopen the DB - an exception should be thrown
db = createDatabase(asList(migration, migration1)); db = createDatabase(asList(migration, migration1));
db.open(null); db.open(key, null);
} }
@Test(expected = DataTooOldException.class) @Test(expected = DataTooOldException.class)
@@ -116,13 +119,13 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
throws Exception { throws Exception {
// Open the DB for the first time // Open the DB for the first time
Database<Connection> db = createDatabase(emptyList()); Database<Connection> db = createDatabase(emptyList());
assertFalse(db.open(null)); assertFalse(db.open(key, null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db)); assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 1); setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 1);
db.close(); db.close();
// Reopen the DB - an exception should be thrown // Reopen the DB - an exception should be thrown
db = createDatabase(emptyList()); db = createDatabase(emptyList());
db.open(null); db.open(key, null);
} }
@Test(expected = DataTooOldException.class) @Test(expected = DataTooOldException.class)
@@ -141,14 +144,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
// Open the DB for the first time // Open the DB for the first time
Database<Connection> db = createDatabase(asList(migration, migration1)); Database<Connection> db = createDatabase(asList(migration, migration1));
assertFalse(db.open(null)); assertFalse(db.open(key, null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db)); assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
// Override the data schema version // Override the data schema version
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 3); setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 3);
db.close(); db.close();
// Reopen the DB - an exception should be thrown // Reopen the DB - an exception should be thrown
db = createDatabase(asList(migration, migration1)); db = createDatabase(asList(migration, migration1));
db.open(null); db.open(key, null);
} }
@Test @Test
@@ -170,14 +173,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
// Open the DB for the first time // Open the DB for the first time
Database<Connection> db = createDatabase(asList(migration, migration1)); Database<Connection> db = createDatabase(asList(migration, migration1));
assertFalse(db.open(null)); assertFalse(db.open(key, null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db)); assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
// Override the data schema version // Override the data schema version
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 2); setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 2);
db.close(); db.close();
// Reopen the DB - the first migration should be run // Reopen the DB - the first migration should be run
db = createDatabase(asList(migration, migration1)); db = createDatabase(asList(migration, migration1));
assertTrue(db.open(null)); assertTrue(db.open(key, null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db)); assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
db.close(); db.close();
} }
@@ -202,14 +205,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
// Open the DB for the first time // Open the DB for the first time
Database<Connection> db = createDatabase(asList(migration, migration1)); Database<Connection> db = createDatabase(asList(migration, migration1));
assertFalse(db.open(null)); assertFalse(db.open(key, null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db)); assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
// Override the data schema version // Override the data schema version
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 2); setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 2);
db.close(); db.close();
// Reopen the DB - both migrations should be run // Reopen the DB - both migrations should be run
db = createDatabase(asList(migration, migration1)); db = createDatabase(asList(migration, migration1));
assertTrue(db.open(null)); assertTrue(db.open(key, null));
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db)); assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
db.close(); db.close();
} }

View File

@@ -15,6 +15,7 @@ import java.util.List;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory; import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getMean; import static org.briarproject.bramble.test.TestUtils.getMean;
import static org.briarproject.bramble.test.TestUtils.getMedian; import static org.briarproject.bramble.test.TestUtils.getMedian;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getStandardDeviation; import static org.briarproject.bramble.test.TestUtils.getStandardDeviation;
import static org.briarproject.bramble.test.UTest.Z_CRITICAL_0_01; import static org.briarproject.bramble.test.UTest.Z_CRITICAL_0_01;
@@ -71,7 +72,7 @@ public abstract class DatabasePerformanceComparisonTest
throws DbException { throws DbException {
Database<Connection> db = createDatabase(conditionA, Database<Connection> db = createDatabase(conditionA,
new TestDatabaseConfig(testDir, MAX_SIZE), new SystemClock()); new TestDatabaseConfig(testDir, MAX_SIZE), new SystemClock());
db.open(null); db.open(getSecretKey(), null);
return db; return db;
} }

View File

@@ -16,6 +16,7 @@ import java.sql.Connection;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory; import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
public abstract class DatabaseTraceTest extends DatabasePerformanceTest { public abstract class DatabaseTraceTest extends DatabasePerformanceTest {
@@ -43,7 +44,7 @@ public abstract class DatabaseTraceTest extends DatabasePerformanceTest {
private Database<Connection> openDatabase() throws DbException { private Database<Connection> openDatabase() throws DbException {
Database<Connection> db = createDatabase( Database<Connection> db = createDatabase(
new TestDatabaseConfig(testDir, MAX_SIZE), new SystemClock()); new TestDatabaseConfig(testDir, MAX_SIZE), new SystemClock());
db.open(null); db.open(getSecretKey(), null);
return db; return db;
} }

View File

@@ -9,8 +9,8 @@ import java.util.List;
public class H2MigrationTest extends DatabaseMigrationTest { public class H2MigrationTest extends DatabaseMigrationTest {
@Override @Override
Database<Connection> createDatabase(List<Migration<Connection>> migrations) Database<Connection> createDatabase(
throws Exception { List<Migration<Connection>> migrations) {
return new H2Database(config, clock) { return new H2Database(config, clock) {
@Override @Override
List<Migration<Connection>> getMigrations() { List<Migration<Connection>> getMigrations() {

View File

@@ -64,6 +64,7 @@ import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getTransportId; import static org.briarproject.bramble.test.TestUtils.getTransportId;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@@ -79,7 +80,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
private static final int ONE_MEGABYTE = 1024 * 1024; private static final int ONE_MEGABYTE = 1024 * 1024;
private static final int MAX_SIZE = 5 * ONE_MEGABYTE; private static final int MAX_SIZE = 5 * ONE_MEGABYTE;
private final File testDir = TestUtils.getTestDirectory(); private final SecretKey key = getSecretKey();
private final File testDir = getTestDirectory();
private final GroupId groupId; private final GroupId groupId;
private final ClientId clientId; private final ClientId clientId;
private final int majorVersion; private final int majorVersion;
@@ -96,7 +98,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
private final KeySetId keySetId, keySetId1; private final KeySetId keySetId, keySetId1;
private final Random random = new Random(); private final Random random = new Random();
JdbcDatabaseTest() throws Exception { JdbcDatabaseTest() {
clientId = getClientId(); clientId = getClientId();
majorVersion = 123; majorVersion = 123;
group = getGroup(clientId, majorVersion); group = getGroup(clientId, majorVersion);
@@ -1819,7 +1821,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
Database<Connection> db = createDatabase( Database<Connection> db = createDatabase(
new TestDatabaseConfig(testDir, MAX_SIZE), clock); new TestDatabaseConfig(testDir, MAX_SIZE), clock);
if (!resume) TestUtils.deleteTestDirectory(testDir); if (!resume) TestUtils.deleteTestDirectory(testDir);
db.open(null); db.open(key, null);
return db; return db;
} }

View File

@@ -13,6 +13,7 @@ import java.util.List;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory; import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getMean; import static org.briarproject.bramble.test.TestUtils.getMean;
import static org.briarproject.bramble.test.TestUtils.getMedian; import static org.briarproject.bramble.test.TestUtils.getMedian;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getStandardDeviation; import static org.briarproject.bramble.test.TestUtils.getStandardDeviation;
public abstract class SingleDatabasePerformanceTest public abstract class SingleDatabasePerformanceTest
@@ -40,7 +41,7 @@ public abstract class SingleDatabasePerformanceTest
private Database<Connection> openDatabase() throws DbException { private Database<Connection> openDatabase() throws DbException {
Database<Connection> db = createDatabase( Database<Connection> db = createDatabase(
new TestDatabaseConfig(testDir, MAX_SIZE), new SystemClock()); new TestDatabaseConfig(testDir, MAX_SIZE), new SystemClock());
db.open(null); db.open(getSecretKey(), null);
return db; return db;
} }

View File

@@ -2,15 +2,21 @@ package org.briarproject.bramble.identity;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.util.ArrayList; import java.util.ArrayList;
@@ -27,24 +33,48 @@ import static org.junit.Assert.assertEquals;
public class IdentityManagerImplTest extends BrambleMockTestCase { public class IdentityManagerImplTest extends BrambleMockTestCase {
private final IdentityManager identityManager;
private final DatabaseComponent db = context.mock(DatabaseComponent.class); private final DatabaseComponent db = context.mock(DatabaseComponent.class);
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
private final AuthorFactory authorFactory =
context.mock(AuthorFactory.class);
private final PublicKey publicKey = context.mock(PublicKey.class);
private final PrivateKey privateKey = context.mock(PrivateKey.class);
private final Transaction txn = new Transaction(null, false); private final Transaction txn = new Transaction(null, false);
private final LocalAuthor localAuthor = getLocalAuthor(); private final LocalAuthor localAuthor = getLocalAuthor();
private final Collection<LocalAuthor> localAuthors = private final Collection<LocalAuthor> localAuthors =
Collections.singletonList(localAuthor); Collections.singletonList(localAuthor);
private final String authorName = localAuthor.getName();
private final KeyPair keyPair = new KeyPair(publicKey, privateKey);
private final byte[] publicKeyBytes = localAuthor.getPublicKey();
private final byte[] privateKeyBytes = localAuthor.getPrivateKey();
private IdentityManager identityManager;
public IdentityManagerImplTest() { @Before
identityManager = new IdentityManagerImpl(db); public void setUp() {
identityManager = new IdentityManagerImpl(db, crypto, authorFactory);
} }
@Test @Test
public void testRegisterLocalAuthor() throws DbException { public void testCreateLocalAuthor() {
expectRegisterLocalAuthor(); context.checking(new Expectations() {{
identityManager.registerLocalAuthor(localAuthor); oneOf(crypto).generateSignatureKeyPair();
will(returnValue(keyPair));
oneOf(publicKey).getEncoded();
will(returnValue(publicKeyBytes));
oneOf(privateKey).getEncoded();
will(returnValue(privateKeyBytes));
oneOf(authorFactory).createLocalAuthor(authorName,
publicKeyBytes, privateKeyBytes);
will(returnValue(localAuthor));
}});
assertEquals(localAuthor,
identityManager.createLocalAuthor(authorName));
} }
private void expectRegisterLocalAuthor() throws DbException { @Test
public void testRegisterAndStoreLocalAuthor() throws DbException {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(db).startTransaction(false); oneOf(db).startTransaction(false);
will(returnValue(txn)); will(returnValue(txn));
@@ -52,6 +82,10 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
oneOf(db).commitTransaction(txn); oneOf(db).commitTransaction(txn);
oneOf(db).endTransaction(txn); oneOf(db).endTransaction(txn);
}}); }});
identityManager.registerLocalAuthor(localAuthor);
assertEquals(localAuthor, identityManager.getLocalAuthor());
identityManager.storeLocalAuthor();
} }
@Test @Test
@@ -69,7 +103,6 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testGetCachedLocalAuthor() throws DbException { public void testGetCachedLocalAuthor() throws DbException {
expectRegisterLocalAuthor();
identityManager.registerLocalAuthor(localAuthor); identityManager.registerLocalAuthor(localAuthor);
assertEquals(localAuthor, identityManager.getLocalAuthor()); assertEquals(localAuthor, identityManager.getLocalAuthor());
} }

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.test; package org.briarproject.bramble.test;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -11,7 +10,6 @@ public class TestDatabaseConfig implements DatabaseConfig {
private final File dbDir, keyDir; private final File dbDir, keyDir;
private final long maxSize; private final long maxSize;
private volatile SecretKey key = new SecretKey(new byte[SecretKey.LENGTH]);
public TestDatabaseConfig(File testDir, long maxSize) { public TestDatabaseConfig(File testDir, long maxSize) {
dbDir = new File(testDir, "db"); dbDir = new File(testDir, "db");
@@ -19,13 +17,6 @@ public class TestDatabaseConfig implements DatabaseConfig {
this.maxSize = maxSize; this.maxSize = maxSize;
} }
@Override
public boolean databaseExists() {
if (!dbDir.isDirectory()) return false;
File[] files = dbDir.listFiles();
return files != null && files.length > 0;
}
@Override @Override
public File getDatabaseDirectory() { public File getDatabaseDirectory() {
return dbDir; return dbDir;
@@ -36,26 +27,6 @@ public class TestDatabaseConfig implements DatabaseConfig {
return keyDir; return keyDir;
} }
@Override
public void setEncryptionKey(SecretKey key) {
this.key = key;
}
@Override
public SecretKey getEncryptionKey() {
return key;
}
@Override
public void setLocalAuthorName(String nickname) {
}
@Override
public String getLocalAuthorName() {
return null;
}
@Override @Override
public long getMaxSize() { public long getMaxSize() {
return maxSize; return maxSize;

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.test; package org.briarproject.bramble.test;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.lifecycle.Service; import org.briarproject.bramble.api.lifecycle.Service;
@@ -11,7 +12,6 @@ import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import javax.annotation.Nullable;
import javax.inject.Singleton; import javax.inject.Singleton;
import dagger.Module; import dagger.Module;
@@ -40,7 +40,7 @@ public class TestLifecycleModule {
} }
@Override @Override
public StartResult startServices(@Nullable String nickname) { public StartResult startServices(SecretKey dbKey) {
return StartResult.SUCCESS; return StartResult.SUCCESS;
} }
@@ -49,15 +49,15 @@ public class TestLifecycleModule {
} }
@Override @Override
public void waitForDatabase() throws InterruptedException { public void waitForDatabase() {
} }
@Override @Override
public void waitForStartup() throws InterruptedException { public void waitForStartup() {
} }
@Override @Override
public void waitForShutdown() throws InterruptedException { public void waitForShutdown() {
} }
@Override @Override

View File

@@ -0,0 +1,33 @@
package org.briarproject.bramble.account;
import android.app.Application;
import android.content.SharedPreferences;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.briar.R;
import org.briarproject.briar.android.Localizer;
import org.briarproject.briar.android.util.UiUtils;
import javax.inject.Inject;
class BriarAccountManager extends AndroidAccountManager {
@Inject
BriarAccountManager(DatabaseConfig databaseConfig, CryptoComponent crypto,
IdentityManager identityManager, SharedPreferences prefs,
Application app) {
super(databaseConfig, crypto, identityManager, prefs, app);
}
@Override
public void deleteAccount() {
synchronized (stateChangeLock) {
super.deleteAccount();
Localizer.reinitialize();
UiUtils.setTheme(appContext,
appContext.getString(R.string.pref_theme_light_value));
}
}
}

View File

@@ -0,0 +1,18 @@
package org.briarproject.bramble.account;
import org.briarproject.bramble.api.account.AccountManager;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class BriarAccountModule {
@Provides
@Singleton
AccountManager provideAccountManager(BriarAccountManager accountManager) {
return accountManager;
}
}

View File

@@ -1,16 +1,14 @@
package org.briarproject.briar.android; package org.briarproject.briar.android;
import android.content.SharedPreferences;
import org.briarproject.bramble.BrambleAndroidModule; import org.briarproject.bramble.BrambleAndroidModule;
import org.briarproject.bramble.BrambleCoreEagerSingletons; import org.briarproject.bramble.BrambleCoreEagerSingletons;
import org.briarproject.bramble.BrambleCoreModule; import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.bramble.account.BriarAccountModule;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.contact.ContactExchangeTask; import org.briarproject.bramble.api.contact.ContactExchangeTask;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.CryptoExecutor; import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator; import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
@@ -62,6 +60,7 @@ import dagger.Component;
BrambleCoreModule.class, BrambleCoreModule.class,
BriarCoreModule.class, BriarCoreModule.class,
BrambleAndroidModule.class, BrambleAndroidModule.class,
BriarAccountModule.class,
AppModule.class AppModule.class
}) })
public interface AndroidComponent public interface AndroidComponent
@@ -73,10 +72,6 @@ public interface AndroidComponent
PasswordStrengthEstimator passwordStrengthIndicator(); PasswordStrengthEstimator passwordStrengthIndicator();
CryptoComponent cryptoComponent();
DatabaseConfig databaseConfig();
@DatabaseExecutor @DatabaseExecutor
Executor databaseExecutor(); Executor databaseExecutor();
@@ -92,8 +87,6 @@ public interface AndroidComponent
AndroidNotificationManager androidNotificationManager(); AndroidNotificationManager androidNotificationManager();
SharedPreferences sharedPreferences();
ScreenFilterMonitor screenFilterMonitor(); ScreenFilterMonitor screenFilterMonitor();
ConnectionRegistry connectionRegistry(); ConnectionRegistry connectionRegistry();
@@ -151,6 +144,8 @@ public interface AndroidComponent
@IoExecutor @IoExecutor
Executor ioExecutor(); Executor ioExecutor();
AccountManager accountManager();
void inject(SignInReminderReceiver briarService); void inject(SignInReminderReceiver briarService);
void inject(BriarService briarService); void inject(BriarService briarService);

View File

@@ -1,99 +1,30 @@
package org.briarproject.briar.android; package org.briarproject.briar.android;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.File; import java.io.File;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import static java.util.logging.Level.INFO;
@NotNullByDefault @NotNullByDefault
class AndroidDatabaseConfig implements DatabaseConfig { class AndroidDatabaseConfig implements DatabaseConfig {
private static final Logger LOG =
Logger.getLogger(AndroidDatabaseConfig.class.getName());
private final File dbDir, keyDir; private final File dbDir, keyDir;
@Nullable
private volatile SecretKey key = null;
@Nullable
private volatile String nickname = null;
AndroidDatabaseConfig(File dbDir, File keyDir) { AndroidDatabaseConfig(File dbDir, File keyDir) {
this.dbDir = dbDir; this.dbDir = dbDir;
this.keyDir = keyDir; this.keyDir = keyDir;
} }
@Override
public boolean databaseExists() {
// FIXME should not run on UiThread #620
if (!dbDir.isDirectory()) {
if (LOG.isLoggable(INFO))
LOG.info(dbDir.getAbsolutePath() + " is not a directory");
return false;
}
File[] files = dbDir.listFiles();
if (LOG.isLoggable(INFO)) {
if (files == null) {
LOG.info("Could not list files in " + dbDir.getAbsolutePath());
} else {
LOG.info("Files in " + dbDir.getAbsolutePath() + ":");
for (File f : files) LOG.info(f.getName());
}
LOG.info("Database exists: " + (files != null && files.length > 0));
}
return files != null && files.length > 0;
}
@Override @Override
public File getDatabaseDirectory() { public File getDatabaseDirectory() {
if (LOG.isLoggable(INFO))
LOG.info("Database directory: " + dbDir.getAbsolutePath());
return dbDir; return dbDir;
} }
@Override @Override
public File getDatabaseKeyDirectory() { public File getDatabaseKeyDirectory() {
if (LOG.isLoggable(INFO))
LOG.info("Database key directory: " + keyDir.getAbsolutePath());
return keyDir; return keyDir;
} }
@Override
public void setEncryptionKey(SecretKey key) {
LOG.info("Setting database key");
this.key = key;
}
@Override
public void setLocalAuthorName(String nickname) {
LOG.info("Setting local author name");
this.nickname = nickname;
}
@Override
@Nullable
public String getLocalAuthorName() {
String nickname = this.nickname;
if (LOG.isLoggable(INFO))
LOG.info("Local author name has been set: " + (nickname != null));
return nickname;
}
@Override
@Nullable
public SecretKey getEncryptionKey() {
SecretKey key = this.key;
if (LOG.isLoggable(INFO))
LOG.info("Database key has been set: " + (key != null));
return key;
}
@Override @Override
public long getMaxSize() { public long getMaxSize() {
return Long.MAX_VALUE; return Long.MAX_VALUE;

View File

@@ -11,9 +11,7 @@ import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.BackoffFactory; import org.briarproject.bramble.api.plugin.BackoffFactory;
import org.briarproject.bramble.api.plugin.PluginConfig; import org.briarproject.bramble.api.plugin.PluginConfig;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory; import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
@@ -86,11 +84,7 @@ public class AppModule {
File dbDir = app.getApplicationContext().getDir("db", MODE_PRIVATE); File dbDir = app.getApplicationContext().getDir("db", MODE_PRIVATE);
File keyDir = app.getApplicationContext().getDir("key", MODE_PRIVATE); File keyDir = app.getApplicationContext().getDir("key", MODE_PRIVATE);
StrictMode.setThreadPolicy(tp); StrictMode.setThreadPolicy(tp);
@MethodsNotNullByDefault return new AndroidDatabaseConfig(dbDir, keyDir);
@ParametersNotNullByDefault
DatabaseConfig databaseConfig =
new AndroidDatabaseConfig(dbDir, keyDir);
return databaseConfig;
} }
@Provides @Provides

View File

@@ -17,7 +17,8 @@ import android.os.IBinder;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult; import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
@@ -74,12 +75,13 @@ public class BriarService extends Service {
private BroadcastReceiver receiver = null; private BroadcastReceiver receiver = null;
@Inject @Inject
protected DatabaseConfig databaseConfig; AccountManager accountManager;
// Fields that are accessed from background threads must be volatile // Fields that are accessed from background threads must be volatile
@Inject @Inject
protected volatile LifecycleManager lifecycleManager; volatile LifecycleManager lifecycleManager;
@Inject @Inject
protected volatile AndroidExecutor androidExecutor; volatile AndroidExecutor androidExecutor;
private volatile boolean started = false; private volatile boolean started = false;
@Override @Override
@@ -95,7 +97,8 @@ public class BriarService extends Service {
stopSelf(); stopSelf();
return; return;
} }
if (databaseConfig.getEncryptionKey() == null) { SecretKey dbKey = accountManager.getDatabaseKey();
if (dbKey == null) {
LOG.info("No database key"); LOG.info("No database key");
stopSelf(); stopSelf();
return; return;
@@ -138,8 +141,7 @@ public class BriarService extends Service {
startForeground(ONGOING_NOTIFICATION_ID, b.build()); startForeground(ONGOING_NOTIFICATION_ID, b.build());
// Start the services in a background thread // Start the services in a background thread
new Thread(() -> { new Thread(() -> {
String nickname = databaseConfig.getLocalAuthorName(); StartResult result = lifecycleManager.startServices(dbKey);
StartResult result = lifecycleManager.startServices(nickname);
if (result == SUCCESS) { if (result == SUCCESS) {
started = true; started = true;
} else if (result == ALREADY_RUNNING) { } else if (result == ALREADY_RUNNING) {

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ import android.content.Intent;
import android.os.IBinder; import android.os.IBinder;
import android.support.annotation.CallSuper; import android.support.annotation.CallSuper;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
@@ -33,8 +33,7 @@ public class BriarControllerImpl implements BriarController {
public static final String DOZE_ASK_AGAIN = "dozeAskAgain"; public static final String DOZE_ASK_AGAIN = "dozeAskAgain";
private final BriarServiceConnection serviceConnection; private final BriarServiceConnection serviceConnection;
private final DatabaseConfig databaseConfig; private final AccountManager accountManager;
@DatabaseExecutor
private final Executor databaseExecutor; private final Executor databaseExecutor;
private final SettingsManager settingsManager; private final SettingsManager settingsManager;
private final DozeWatchdog dozeWatchdog; private final DozeWatchdog dozeWatchdog;
@@ -44,12 +43,12 @@ public class BriarControllerImpl implements BriarController {
@Inject @Inject
BriarControllerImpl(BriarServiceConnection serviceConnection, BriarControllerImpl(BriarServiceConnection serviceConnection,
DatabaseConfig databaseConfig, AccountManager accountManager,
@DatabaseExecutor Executor databaseExecutor, @DatabaseExecutor Executor databaseExecutor,
SettingsManager settingsManager, DozeWatchdog dozeWatchdog, SettingsManager settingsManager, DozeWatchdog dozeWatchdog,
Activity activity) { Activity activity) {
this.serviceConnection = serviceConnection; this.serviceConnection = serviceConnection;
this.databaseConfig = databaseConfig; this.accountManager = accountManager;
this.databaseExecutor = databaseExecutor; this.databaseExecutor = databaseExecutor;
this.settingsManager = settingsManager; this.settingsManager = settingsManager;
this.dozeWatchdog = dozeWatchdog; this.dozeWatchdog = dozeWatchdog;
@@ -59,7 +58,7 @@ public class BriarControllerImpl implements BriarController {
@Override @Override
@CallSuper @CallSuper
public void onActivityCreate(Activity activity) { public void onActivityCreate(Activity activity) {
if (databaseConfig.getEncryptionKey() != null) startAndBindService(); if (accountManager.hasDatabaseKey()) startAndBindService();
} }
@Override @Override
@@ -84,8 +83,8 @@ public class BriarControllerImpl implements BriarController {
} }
@Override @Override
public boolean hasEncryptionKey() { public boolean accountSignedIn() {
return databaseConfig.getEncryptionKey() != null; return accountManager.hasDatabaseKey();
} }
@Override @Override

View File

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

View File

@@ -1,171 +0,0 @@
package org.briarproject.briar.android.controller;
import android.content.Context;
import android.content.SharedPreferences;
import android.support.v7.preference.PreferenceManager;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.util.AndroidUtils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
public class ConfigControllerImpl implements ConfigController {
private static final Logger LOG =
Logger.getLogger(ConfigControllerImpl.class.getName());
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 File dbKeyFile, dbKeyBackupFile;
protected final DatabaseConfig databaseConfig;
@Inject
public ConfigControllerImpl(SharedPreferences briarPrefs,
DatabaseConfig databaseConfig) {
this.briarPrefs = briarPrefs;
this.databaseConfig = databaseConfig;
File keyDir = databaseConfig.getDatabaseKeyDirectory();
dbKeyFile = new File(keyDir, DB_KEY_FILENAME);
dbKeyBackupFile = new File(keyDir, DB_KEY_BACKUP_FILENAME);
}
@Override
@Nullable
public String getEncryptedDatabaseKey() {
String key = getDatabaseKeyFromPreferences();
if (key == null) key = getDatabaseKeyFromFile();
else migrateDatabaseKeyToFile(key);
return key;
}
@Nullable
private String getDatabaseKeyFromPreferences() {
String key = briarPrefs.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;
}
@Nullable
private String getDatabaseKeyFromFile() {
String key = readDbKeyFromFile(dbKeyFile);
if (key == null) {
LOG.info("No database key in primary file");
key = readDbKeyFromFile(dbKeyBackupFile);
if (key == null) LOG.info("No database key in backup file");
else LOG.warning("Found database key in backup file");
} else {
LOG.info("Found database key in primary file");
}
return key;
}
@Nullable
private String readDbKeyFromFile(File f) {
if (!f.exists()) {
LOG.info("Key file does not exist");
return null;
}
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(
new FileInputStream(f), "UTF-8"));
String key = reader.readLine();
reader.close();
return key;
} catch (IOException e) {
logException(LOG, WARNING, e);
return null;
}
}
private void migrateDatabaseKeyToFile(String key) {
if (storeEncryptedDatabaseKey(key)) {
if (briarPrefs.edit().remove(PREF_DB_KEY).commit())
LOG.info("Database key migrated to file");
else LOG.warning("Database key not removed from preferences");
} else {
LOG.warning("Database key not migrated to file");
}
}
@Override
public boolean storeEncryptedDatabaseKey(String hex) {
LOG.info("Storing database key in file");
// Create the directory if necessary
if (databaseConfig.getDatabaseKeyDirectory().mkdirs())
LOG.info("Created database key directory");
// If only the backup file exists, rename it so we don't overwrite it
if (dbKeyBackupFile.exists() && !dbKeyFile.exists()) {
if (dbKeyBackupFile.renameTo(dbKeyFile))
LOG.info("Renamed old backup");
else LOG.warning("Failed to rename old backup");
}
try {
// Write to the backup file
writeDbKeyToFile(hex, dbKeyBackupFile);
LOG.info("Stored database key in backup file");
// Delete the old primary file, if it exists
if (dbKeyFile.exists()) {
if (dbKeyFile.delete()) LOG.info("Deleted primary file");
else LOG.warning("Failed to delete primary file");
}
// The backup file becomes the new primary
if (dbKeyBackupFile.renameTo(dbKeyFile)) {
LOG.info("Renamed backup file to primary");
} else {
LOG.warning("Failed to rename backup file to primary");
return false; // Don't overwrite our only copy
}
// Write a second copy to the backup file
writeDbKeyToFile(hex, dbKeyBackupFile);
LOG.info("Stored second copy of database key in backup file");
return true;
} catch (IOException e) {
logException(LOG, WARNING, e);
return false;
}
}
private void writeDbKeyToFile(String key, File f) throws IOException {
FileOutputStream out = new FileOutputStream(f);
out.write(key.getBytes("UTF-8"));
out.flush();
out.close();
}
@Override
public void deleteAccount(Context ctx) {
LOG.info("Deleting account");
SharedPreferences defaultPrefs =
PreferenceManager.getDefaultSharedPreferences(ctx);
AndroidUtils.deleteAppData(ctx, briarPrefs, defaultPrefs);
}
@Override
public boolean accountExists() {
String hex = getEncryptedDatabaseKey();
return hex != null && databaseConfig.databaseExists();
}
@Override
public boolean accountSignedIn() {
return databaseConfig.getEncryptionKey() != null;
}
}

View File

@@ -13,8 +13,8 @@ import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.Localizer;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BaseActivity; import org.briarproject.briar.android.activity.BaseActivity;
import org.briarproject.briar.android.controller.BriarController; import org.briarproject.briar.android.controller.BriarController;
@@ -33,6 +33,9 @@ import static org.briarproject.briar.api.android.AndroidNotificationManager.REMI
public class PasswordActivity extends BaseActivity { public class PasswordActivity extends BaseActivity {
@Inject
AccountManager accountManager;
@Inject @Inject
PasswordController passwordController; PasswordController passwordController;
@@ -50,7 +53,8 @@ public class PasswordActivity extends BaseActivity {
// fade-in after splash screen instead of default animation // fade-in after splash screen instead of default animation
overridePendingTransition(R.anim.fade_in, R.anim.fade_out); overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
if (!passwordController.accountExists()) { if (!accountManager.accountExists()) {
// TODO: Finish instead of deleting account?
deleteAccount(); deleteAccount();
return; return;
} }
@@ -87,7 +91,7 @@ public class PasswordActivity extends BaseActivity {
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
// If the user has already signed in, clean up this instance // If the user has already signed in, clean up this instance
if (briarController.hasEncryptionKey()) { if (briarController.accountSignedIn()) {
setResult(RESULT_OK); setResult(RESULT_OK);
finish(); finish();
} else { } else {
@@ -112,9 +116,7 @@ public class PasswordActivity extends BaseActivity {
} }
private void deleteAccount() { private void deleteAccount() {
passwordController.deleteAccount(this); accountManager.deleteAccount();
Localizer.reinitialize();
UiUtils.setTheme(this, getString(R.string.pref_theme_light_value));
setResult(RESULT_CANCELED); setResult(RESULT_CANCELED);
Intent i = new Intent(this, SetupActivity.class); Intent i = new Intent(this, SetupActivity.class);
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK); i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);

View File

@@ -1,18 +1,17 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.login;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.controller.ConfigController;
import org.briarproject.briar.android.controller.handler.ResultHandler; import org.briarproject.briar.android.controller.handler.ResultHandler;
@NotNullByDefault @NotNullByDefault
public interface PasswordController extends ConfigController { public interface PasswordController {
float estimatePasswordStrength(String password); float estimatePasswordStrength(String password);
void validatePassword(String password, void validatePassword(String password,
ResultHandler<Boolean> resultHandler); ResultHandler<Boolean> resultHandler);
void changePassword(String password, String newPassword, void changePassword(String oldPassword, String newPassword,
ResultHandler<Boolean> resultHandler); ResultHandler<Boolean> resultHandler);
} }

View File

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

View File

@@ -4,6 +4,7 @@ import android.annotation.TargetApi;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
@@ -25,6 +26,9 @@ public class SetupActivity extends BaseActivity
private static final String STATE_KEY_AUTHOR_NAME = "authorName"; private static final String STATE_KEY_AUTHOR_NAME = "authorName";
private static final String STATE_KEY_PASSWORD = "password"; private static final String STATE_KEY_PASSWORD = "password";
@Inject
AccountManager accountManager;
@Inject @Inject
SetupController setupController; SetupController setupController;
@@ -39,8 +43,7 @@ public class SetupActivity extends BaseActivity
setContentView(R.layout.activity_fragment_container); setContentView(R.layout.activity_fragment_container);
if (state == null) { if (state == null) {
if (setupController.accountExists()) if (accountManager.accountExists()) throw new AssertionError();
throw new AssertionError();
showInitialFragment(AuthorNameFragment.newInstance()); showInitialFragment(AuthorNameFragment.newInstance());
} else { } else {
authorName = state.getString(STATE_KEY_AUTHOR_NAME); authorName = state.getString(STATE_KEY_AUTHOR_NAME);

View File

@@ -1,13 +1,10 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.login;
import android.content.SharedPreferences;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator; import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.controller.handler.ResultHandler; import org.briarproject.briar.android.controller.handler.ResultHandler;
import org.briarproject.briar.android.controller.handler.UiResultHandler; import org.briarproject.briar.android.controller.handler.UiResultHandler;
@@ -28,12 +25,10 @@ public class SetupControllerImpl extends PasswordControllerImpl
private volatile SetupActivity setupActivity; private volatile SetupActivity setupActivity;
@Inject @Inject
SetupControllerImpl(SharedPreferences briarPrefs, SetupControllerImpl(AccountManager accountManager,
DatabaseConfig databaseConfig, @IoExecutor Executor ioExecutor,
@CryptoExecutor Executor cryptoExecutor, CryptoComponent crypto,
PasswordStrengthEstimator strengthEstimator) { PasswordStrengthEstimator strengthEstimator) {
super(briarPrefs, databaseConfig, cryptoExecutor, crypto, super(accountManager, ioExecutor, strengthEstimator);
strengthEstimator);
} }
@Override @Override
@@ -80,10 +75,11 @@ public class SetupControllerImpl extends PasswordControllerImpl
@Override @Override
public void createAccount() { public void createAccount() {
SetupActivity setupActivity = this.setupActivity; SetupActivity setupActivity = this.setupActivity;
UiResultHandler<Void> resultHandler = UiResultHandler<Boolean> resultHandler =
new UiResultHandler<Void>(setupActivity) { new UiResultHandler<Boolean>(setupActivity) {
@Override @Override
public void onResultUi(Void result) { public void onResultUi(Boolean result) {
// TODO: Show an error if result is false
if (setupActivity == null) if (setupActivity == null)
throw new IllegalStateException(); throw new IllegalStateException();
setupActivity.showApp(); setupActivity.showApp();
@@ -93,22 +89,17 @@ public class SetupControllerImpl extends PasswordControllerImpl
} }
// Package access for testing // Package access for testing
void createAccount(ResultHandler<Void> resultHandler) { void createAccount(ResultHandler<Boolean> resultHandler) {
SetupActivity setupActivity = this.setupActivity; SetupActivity setupActivity = this.setupActivity;
if (setupActivity == null) throw new IllegalStateException(); if (setupActivity == null) throw new IllegalStateException();
String authorName = setupActivity.getAuthorName(); String authorName = setupActivity.getAuthorName();
if (authorName == null) throw new IllegalStateException(); if (authorName == null) throw new IllegalStateException();
String password = setupActivity.getPassword(); String password = setupActivity.getPassword();
if (password == null) throw new IllegalStateException(); if (password == null) throw new IllegalStateException();
cryptoExecutor.execute(() -> { ioExecutor.execute(() -> {
LOG.info("Creating account"); LOG.info("Creating account");
databaseConfig.setLocalAuthorName(authorName); resultHandler.onResult(accountManager.createAccount(authorName,
SecretKey key = crypto.generateSecretKey(); password));
databaseConfig.setEncryptionKey(key);
String hex = encryptDatabaseKey(key, password);
storeEncryptedDatabaseKey(hex);
resultHandler.onResult(null);
}); });
} }
} }

View File

@@ -10,7 +10,7 @@ import android.content.SharedPreferences;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.AndroidComponent; import org.briarproject.briar.android.AndroidComponent;
import org.briarproject.briar.android.BriarApplication; import org.briarproject.briar.android.BriarApplication;
@@ -37,7 +37,7 @@ public class SignInReminderReceiver extends BroadcastReceiver {
public static final String DISMISS_REMINDER = "dismissReminder"; public static final String DISMISS_REMINDER = "dismissReminder";
@Inject @Inject
DatabaseConfig databaseConfig; AccountManager accountManager;
@Override @Override
public void onReceive(Context ctx, Intent intent) { public void onReceive(Context ctx, Intent intent) {
@@ -51,7 +51,7 @@ public class SignInReminderReceiver extends BroadcastReceiver {
if (action == null) return; if (action == null) return;
if (action.equals(ACTION_BOOT_COMPLETED) || if (action.equals(ACTION_BOOT_COMPLETED) ||
action.equals(ACTION_MY_PACKAGE_REPLACED)) { action.equals(ACTION_MY_PACKAGE_REPLACED)) {
if (databaseConfig.databaseExists()) { if (accountManager.accountExists()) {
SharedPreferences prefs = app.getDefaultSharedPreferences(); SharedPreferences prefs = app.getDefaultSharedPreferences();
if (prefs.getBoolean(NOTIFY_SIGN_IN, true)) { if (prefs.getBoolean(NOTIFY_SIGN_IN, true)) {
showSignInNotification(ctx); showSignInNotification(ctx);

View File

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

View File

@@ -7,11 +7,11 @@ import android.os.Handler;
import android.support.v7.preference.PreferenceManager; import android.support.v7.preference.PreferenceManager;
import android.transition.Fade; import android.transition.Fade;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BaseActivity; import org.briarproject.briar.android.activity.BaseActivity;
import org.briarproject.briar.android.controller.ConfigController;
import org.briarproject.briar.android.login.OpenDatabaseActivity; import org.briarproject.briar.android.login.OpenDatabaseActivity;
import org.briarproject.briar.android.login.SetupActivity; import org.briarproject.briar.android.login.SetupActivity;
@@ -27,7 +27,7 @@ public class SplashScreenActivity extends BaseActivity {
Logger.getLogger(SplashScreenActivity.class.getName()); Logger.getLogger(SplashScreenActivity.class.getName());
@Inject @Inject
protected ConfigController configController; protected AccountManager accountManager;
@Inject @Inject
protected AndroidExecutor androidExecutor; protected AndroidExecutor androidExecutor;
@@ -43,7 +43,7 @@ public class SplashScreenActivity extends BaseActivity {
setContentView(R.layout.splash); setContentView(R.layout.splash);
if (configController.accountSignedIn()) { if (accountManager.hasDatabaseKey()) {
startActivity(new Intent(this, OpenDatabaseActivity.class)); startActivity(new Intent(this, OpenDatabaseActivity.class));
finish(); finish();
} else { } else {
@@ -64,12 +64,12 @@ public class SplashScreenActivity extends BaseActivity {
LOG.info("Expired"); LOG.info("Expired");
startActivity(new Intent(this, ExpiredActivity.class)); startActivity(new Intent(this, ExpiredActivity.class));
} else { } else {
if (configController.accountExists()) { if (accountManager.accountExists()) {
LOG.info("Account exists"); LOG.info("Account exists");
startActivity(new Intent(this, OpenDatabaseActivity.class)); startActivity(new Intent(this, OpenDatabaseActivity.class));
} else { } else {
LOG.info("Account does not exist"); LOG.info("Account does not exist");
configController.deleteAccount(this); accountManager.deleteAccount();
startActivity(new Intent(this, SetupActivity.class)); startActivity(new Intent(this, SetupActivity.class));
} }
} }

View File

@@ -1,35 +0,0 @@
package org.briarproject.briar.android;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.annotation.Nullable;
import static junit.framework.Assert.assertTrue;
@NotNullByDefault
public class TestDatabaseKeyUtils {
public static void storeDatabaseKey(File f, String hex) throws IOException {
f.getParentFile().mkdirs();
FileOutputStream out = new FileOutputStream(f);
out.write(hex.getBytes("UTF-8"));
out.flush();
out.close();
}
@Nullable
public static String loadDatabaseKey(File f) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(
new FileInputStream(f), "UTF-8"));
String hex = reader.readLine();
reader.close();
return hex;
}
}

View File

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

View File

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

View File

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

View File

@@ -1,56 +1,32 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.login;
import android.content.SharedPreferences; import org.briarproject.bramble.api.account.AccountManager;
import android.content.pm.ApplicationInfo;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator; import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.ImmediateExecutor; import org.briarproject.bramble.test.ImmediateExecutor;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.jmock.lib.legacy.ClassImposteriser; import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.After;
import org.junit.Test; import org.junit.Test;
import java.io.File;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.assertTrue;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.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.getRandomString;
import static org.briarproject.bramble.util.StringUtils.toHexString;
import static org.briarproject.briar.android.TestDatabaseKeyUtils.loadDatabaseKey;
public class SetupControllerImplTest extends BrambleMockTestCase { public class SetupControllerImplTest extends BrambleMockTestCase {
private final SharedPreferences briarPrefs = private final AccountManager accountManager =
context.mock(SharedPreferences.class); context.mock(AccountManager.class);
private final DatabaseConfig databaseConfig =
context.mock(DatabaseConfig.class);
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
private final PasswordStrengthEstimator estimator = private final PasswordStrengthEstimator estimator =
context.mock(PasswordStrengthEstimator.class); context.mock(PasswordStrengthEstimator.class);
private final SetupActivity setupActivity; private final SetupActivity setupActivity;
private final Executor cryptoExecutor = new ImmediateExecutor(); private final Executor ioExecutor = new ImmediateExecutor();
private final String authorName = getRandomString(MAX_AUTHOR_NAME_LENGTH); private final String authorName = getRandomString(MAX_AUTHOR_NAME_LENGTH);
private final String password = "some.strong.pass"; private final String password = getRandomString(10);
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");
private final File keyBackupFile = new File(keyDir, "db.key.bak");
public SetupControllerImplTest() { public SetupControllerImplTest() {
context.setImposteriser(ClassImposteriser.INSTANCE); context.setImposteriser(ClassImposteriser.INSTANCE);
@@ -59,13 +35,8 @@ public class SetupControllerImplTest extends BrambleMockTestCase {
@Test @Test
@SuppressWarnings("ResultOfMethodCallIgnored") @SuppressWarnings("ResultOfMethodCallIgnored")
public void testCreateAccount() throws Exception { public void testCreateAccount() {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Allow the contents of the data directory to be logged
allowing(setupActivity).getApplicationInfo();
will(returnValue(new ApplicationInfo() {{
dataDir = testDir.getAbsolutePath();
}}));
// Set the author name and password // Set the author name and password
oneOf(setupActivity).setAuthorName(authorName); oneOf(setupActivity).setAuthorName(authorName);
oneOf(setupActivity).setPassword(password); oneOf(setupActivity).setPassword(password);
@@ -74,25 +45,13 @@ public class SetupControllerImplTest extends BrambleMockTestCase {
will(returnValue(authorName)); will(returnValue(authorName));
oneOf(setupActivity).getPassword(); oneOf(setupActivity).getPassword();
will(returnValue(password)); will(returnValue(password));
// Generate a database key // Create the account
oneOf(crypto).generateSecretKey(); oneOf(accountManager).createAccount(authorName, password);
will(returnValue(key)); will(returnValue(true));
// Attach the author name and database key to the database config
oneOf(databaseConfig).setLocalAuthorName(authorName);
oneOf(databaseConfig).setEncryptionKey(key);
// Encrypt the key with the password
oneOf(crypto).encryptWithPassword(key.getBytes(), password);
will(returnValue(encryptedKey));
// Store the encrypted key
allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir));
}}); }});
assertFalse(keyFile.exists()); SetupControllerImpl s = new SetupControllerImpl(accountManager,
assertFalse(keyBackupFile.exists()); ioExecutor, estimator);
SetupControllerImpl s = new SetupControllerImpl(briarPrefs,
databaseConfig, cryptoExecutor, crypto, estimator);
s.setSetupActivity(setupActivity); s.setSetupActivity(setupActivity);
AtomicBoolean called = new AtomicBoolean(false); AtomicBoolean called = new AtomicBoolean(false);
@@ -100,15 +59,5 @@ public class SetupControllerImplTest extends BrambleMockTestCase {
s.setPassword(password); s.setPassword(password);
s.createAccount(result -> called.set(true)); s.createAccount(result -> called.set(true));
assertTrue(called.get()); assertTrue(called.get());
assertTrue(keyFile.exists());
assertTrue(keyBackupFile.exists());
assertEquals(toHexString(encryptedKey), loadDatabaseKey(keyFile));
assertEquals(toHexString(encryptedKey), loadDatabaseKey(keyBackupFile));
}
@After
public void tearDown() {
deleteTestDirectory(testDir);
} }
} }

View File

@@ -1,5 +1,7 @@
package org.briarproject.briar.feed; package org.briarproject.briar.feed;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.contact.ContactModule; import org.briarproject.bramble.contact.ContactModule;
import org.briarproject.bramble.crypto.CryptoExecutorModule; import org.briarproject.bramble.crypto.CryptoExecutorModule;
@@ -25,6 +27,7 @@ import org.junit.Test;
import java.io.File; import java.io.File;
import java.util.Collection; import java.util.Collection;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@@ -47,8 +50,12 @@ public class FeedManagerIntegrationTest extends BriarTestCase {
component.inject(this); component.inject(this);
injectEagerSingletons(component); injectEagerSingletons(component);
IdentityManager identityManager = component.getIdentityManager();
LocalAuthor localAuthor = identityManager.createLocalAuthor("feedTest");
identityManager.registerLocalAuthor(localAuthor);
lifecycleManager = component.getLifecycleManager(); lifecycleManager = component.getLifecycleManager();
lifecycleManager.startServices("feedTest"); lifecycleManager.startServices(getSecretKey());
lifecycleManager.waitForStartup(); lifecycleManager.waitForStartup();
feedManager = component.getFeedManager(); feedManager = component.getFeedManager();

View File

@@ -1,5 +1,6 @@
package org.briarproject.briar.feed; package org.briarproject.briar.feed;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.client.ClientModule; import org.briarproject.bramble.client.ClientModule;
import org.briarproject.bramble.contact.ContactModule; import org.briarproject.bramble.contact.ContactModule;
@@ -76,6 +77,8 @@ interface FeedManagerIntegrationTestComponent {
void inject(VersioningModule.EagerSingletons init); void inject(VersioningModule.EagerSingletons init);
IdentityManager getIdentityManager();
LifecycleManager getLifecycleManager(); LifecycleManager getLifecycleManager();
FeedManager getFeedManager(); FeedManager getFeedManager();

View File

@@ -44,7 +44,6 @@ import java.io.InputStream;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH; import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
import static org.briarproject.bramble.test.TestPluginConfigModule.MAX_LATENCY; import static org.briarproject.bramble.test.TestPluginConfigModule.MAX_LATENCY;
import static org.briarproject.bramble.test.TestPluginConfigModule.TRANSPORT_ID; import static org.briarproject.bramble.test.TestPluginConfigModule.TRANSPORT_ID;
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory; import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@@ -58,8 +57,6 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
private final File bobDir = new File(testDir, "bob"); private final File bobDir = new File(testDir, "bob");
private final SecretKey master = getSecretKey(); private final SecretKey master = getSecretKey();
private final long timestamp = System.currentTimeMillis(); private final long timestamp = System.currentTimeMillis();
private final LocalAuthor aliceAuthor = getLocalAuthor();
private final LocalAuthor bobAuthor = getLocalAuthor();
private SimplexMessagingIntegrationTestComponent alice, bob; private SimplexMessagingIntegrationTestComponent alice, bob;
@@ -76,6 +73,11 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
@Test @Test
public void testWriteAndRead() throws Exception { public void testWriteAndRead() throws Exception {
// Create the identities
LocalAuthor aliceAuthor =
alice.getIdentityManager().createLocalAuthor("Alice");
LocalAuthor bobAuthor =
bob.getIdentityManager().createLocalAuthor("Bob");
// Set up the devices and get the contact IDs // Set up the devices and get the contact IDs
ContactId bobId = setUp(alice, aliceAuthor, bobAuthor, true); ContactId bobId = setUp(alice, aliceAuthor, bobAuthor, true);
ContactId aliceId = setUp(bob, bobAuthor, aliceAuthor, false); ContactId aliceId = setUp(bob, bobAuthor, aliceAuthor, false);
@@ -98,13 +100,13 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
private ContactId setUp(SimplexMessagingIntegrationTestComponent device, private ContactId setUp(SimplexMessagingIntegrationTestComponent device,
LocalAuthor local, Author remote, boolean alice) throws Exception { LocalAuthor local, Author remote, boolean alice) throws Exception {
// Start the lifecycle manager
LifecycleManager lifecycleManager = device.getLifecycleManager();
lifecycleManager.startServices(null);
lifecycleManager.waitForStartup();
// Add an identity for the user // Add an identity for the user
IdentityManager identityManager = device.getIdentityManager(); IdentityManager identityManager = device.getIdentityManager();
identityManager.registerLocalAuthor(local); identityManager.registerLocalAuthor(local);
// Start the lifecycle manager
LifecycleManager lifecycleManager = device.getLifecycleManager();
lifecycleManager.startServices(getSecretKey());
lifecycleManager.waitForStartup();
// Add the other user as a contact // Add the other user as a contact
ContactManager contactManager = device.getContactManager(); ContactManager contactManager = device.getContactManager();
return contactManager.addContact(remote, local.getId(), master, return contactManager.addContact(remote, local.getId(), master,

View File

@@ -160,8 +160,8 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone
validationWaiter = new Waiter(); validationWaiter = new Waiter();
deliveryWaiter = new Waiter(); deliveryWaiter = new Waiter();
createAndRegisterIdentities();
startLifecycles(); startLifecycles();
getDefaultIdentities();
listenToEvents(); listenToEvents();
addDefaultContacts(); addDefaultContacts();
} }
@@ -193,9 +193,9 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone
lifecycleManager0 = c0.getLifecycleManager(); lifecycleManager0 = c0.getLifecycleManager();
lifecycleManager1 = c1.getLifecycleManager(); lifecycleManager1 = c1.getLifecycleManager();
lifecycleManager2 = c2.getLifecycleManager(); lifecycleManager2 = c2.getLifecycleManager();
lifecycleManager0.startServices(AUTHOR0); lifecycleManager0.startServices(getSecretKey());
lifecycleManager1.startServices(AUTHOR1); lifecycleManager1.startServices(getSecretKey());
lifecycleManager2.startServices(AUTHOR2); lifecycleManager2.startServices(getSecretKey());
lifecycleManager0.waitForStartup(); lifecycleManager0.waitForStartup();
lifecycleManager1.waitForStartup(); lifecycleManager1.waitForStartup();
lifecycleManager2.waitForStartup(); lifecycleManager2.waitForStartup();
@@ -230,10 +230,13 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone
} }
} }
private void getDefaultIdentities() throws DbException { private void createAndRegisterIdentities() {
author0 = identityManager0.getLocalAuthor(); author0 = identityManager0.createLocalAuthor(AUTHOR0);
author1 = identityManager1.getLocalAuthor(); identityManager0.registerLocalAuthor(author0);
author2 = identityManager2.getLocalAuthor(); author1 = identityManager1.createLocalAuthor(AUTHOR1);
identityManager1.registerLocalAuthor(author1);
author2 = identityManager2.createLocalAuthor(AUTHOR2);
identityManager2.registerLocalAuthor(author2);
} }
protected void addDefaultContacts() throws Exception { protected void addDefaultContacts() throws Exception {