mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-15 20:29:52 +01:00
Compare commits
71 Commits
elliptic-c
...
beta-0.16.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
812522a900 | ||
|
|
98db9da4bc | ||
|
|
eda3c964aa | ||
|
|
68df606146 | ||
|
|
52bd699d2d | ||
|
|
abb8db10db | ||
|
|
30edb90426 | ||
|
|
ffc94b2812 | ||
|
|
35a7bb4576 | ||
|
|
2d87e34aa2 | ||
|
|
088564f22f | ||
|
|
8c8c1158f4 | ||
|
|
8faa456eb2 | ||
|
|
4c61158326 | ||
|
|
6792abc00a | ||
|
|
63442aea1d | ||
|
|
a58443eaa8 | ||
|
|
14a9614c35 | ||
|
|
f1011b97b3 | ||
|
|
1935b1e09a | ||
|
|
ac9df9d5d8 | ||
|
|
30a800a4d0 | ||
|
|
69537b67a2 | ||
|
|
92982f98a8 | ||
|
|
ea5fa72224 | ||
|
|
5a1651d483 | ||
|
|
fcbf6dfb7f | ||
|
|
7aebf92a6f | ||
|
|
1b9f8d4f0b | ||
|
|
93db4eb986 | ||
|
|
347c2f22c1 | ||
|
|
a8ea191ffb | ||
|
|
2a4c22757b | ||
|
|
28ebbbc7d1 | ||
|
|
5e7d08f05d | ||
|
|
ea005748dc | ||
|
|
b021bfab5e | ||
|
|
29cd105a1d | ||
|
|
be2e68e96c | ||
|
|
9dd3f81bb7 | ||
|
|
5d918591d4 | ||
|
|
f1c027fa4d | ||
|
|
d2d3ccf68d | ||
|
|
f4efed54d5 | ||
|
|
459538e40c | ||
|
|
183f501761 | ||
|
|
65ee5f539b | ||
|
|
604339326c | ||
|
|
0acec1343f | ||
|
|
0434756bbd | ||
|
|
e233433140 | ||
|
|
c63f285f53 | ||
|
|
0800188718 | ||
|
|
6188e48beb | ||
|
|
5726e29b56 | ||
|
|
5d70399de0 | ||
|
|
73202dde5e | ||
|
|
a98ac8233c | ||
|
|
bee3e244fc | ||
|
|
da25999a15 | ||
|
|
62049df342 | ||
|
|
024e5aa90f | ||
|
|
6d791481d5 | ||
|
|
0a807d0893 | ||
|
|
23596bbdd4 | ||
|
|
fe79954138 | ||
|
|
9902c023ca | ||
|
|
e8baee6734 | ||
|
|
a8dc029e56 | ||
|
|
74e3fee7aa | ||
|
|
05aac696b7 |
@@ -12,8 +12,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 14
|
minSdkVersion 14
|
||||||
targetSdkVersion 26
|
targetSdkVersion 26
|
||||||
versionCode 1700
|
versionCode 1618
|
||||||
versionName "0.17.0"
|
versionName "0.16.18"
|
||||||
consumerProguardFiles 'proguard-rules.txt'
|
consumerProguardFiles 'proguard-rules.txt'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +43,6 @@ dependencyVerification {
|
|||||||
'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728',
|
'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728',
|
||||||
'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',
|
||||||
'net.i2p.crypto:eddsa:0.2.0:eddsa-0.2.0.jar:a7cb1b85c16e2f0730b9204106929a1d9aaae1df728adc7041a8b8b605692140',
|
|
||||||
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
|
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
|
||||||
'org.jacoco:org.jacoco.agent:0.7.4.201502262128:org.jacoco.agent-0.7.4.201502262128-runtime.jar:e357a0f1d573c2f702a273992b1b6cb661734f66311854efb3778a888515c5b5',
|
'org.jacoco:org.jacoco.agent:0.7.4.201502262128:org.jacoco.agent-0.7.4.201502262128-runtime.jar:e357a0f1d573c2f702a273992b1b6cb661734f66311854efb3778a888515c5b5',
|
||||||
'org.jacoco:org.jacoco.agent:0.7.4.201502262128:org.jacoco.agent-0.7.4.201502262128.jar:47b4bec6df11a1118da3953da8b9fa1e7079d6fec857faa1a3cf912e53a6fd4e',
|
'org.jacoco:org.jacoco.agent:0.7.4.201502262128:org.jacoco.agent-0.7.4.201502262128.jar:47b4bec6df11a1118da3953da8b9fa1e7079d6fec857faa1a3cf912e53a6fd4e',
|
||||||
@@ -55,16 +54,16 @@ dependencyVerification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ext.torBinaryDir = 'src/main/res/raw'
|
ext.torBinaryDir = 'src/main/res/raw'
|
||||||
ext.torVersion = '0.2.9.12'
|
ext.torVersion = '0.2.9.14'
|
||||||
ext.geoipVersion = '2017-09-06'
|
ext.geoipVersion = '2017-11-06'
|
||||||
ext.torDownloadUrl = 'https://briarproject.org/build/'
|
ext.torDownloadUrl = 'https://briarproject.org/build/'
|
||||||
|
|
||||||
def torBinaries = [
|
def torBinaries = [
|
||||||
"tor_arm" : '8ed0b347ffed1d6a4d2fd14495118eb92be83e9cc06e057e15220dc288b31688',
|
"tor_arm" : '1710ea6c47b7f4c1a88bdf4858c7893837635db10e8866854eed8d61629f50e8',
|
||||||
"tor_arm_pie": '64403262511c29f462ca5e7c7621bfc3c944898364d1d5ad35a016bb8a034283',
|
"tor_arm_pie": '974e6949507db8fa2ea45231817c2c3677ed4ccf5488a2252317d744b0be1917',
|
||||||
"tor_x86" : '61e014607a2079bcf1646289c67bff6372b1aded6e1d8d83d7791efda9a4d5ab',
|
"tor_x86" : '3a5e45b3f051fcda9353b098b7086e762ffe7ba9242f7d7c8bf6523faaa8b1e9',
|
||||||
"tor_x86_pie": '18fbc98356697dd0895836ab46d5c9877d1c539193464f7db1e82a65adaaf288',
|
"tor_x86_pie": 'd1d96d8ce1a4b68accf04850185780d10cd5563d3552f7e1f040f8ca32cb4e51',
|
||||||
"geoip" : 'fe49d3adb86d3c512373101422a017dbb86c85a570524663f09dd8ce143a24f3'
|
"geoip" : '8239b98374493529a29096e45fc5877d4d6fdad0146ad8380b291f90d61484ea'
|
||||||
]
|
]
|
||||||
|
|
||||||
def downloadBinary(name) {
|
def downloadBinary(name) {
|
||||||
|
|||||||
@@ -8,8 +8,6 @@
|
|||||||
-dontwarn dagger.**
|
-dontwarn dagger.**
|
||||||
-dontnote dagger.**
|
-dontnote dagger.**
|
||||||
|
|
||||||
-keep class net.i2p.crypto.eddsa.** { *; }
|
|
||||||
|
|
||||||
-dontwarn sun.misc.Unsafe
|
-dontwarn sun.misc.Unsafe
|
||||||
-dontnote com.google.common.**
|
-dontnote com.google.common.**
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
|||||||
import org.briarproject.bramble.api.reporting.DevReporter;
|
import org.briarproject.bramble.api.reporting.DevReporter;
|
||||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
import org.briarproject.bramble.api.system.LocationUtils;
|
import org.briarproject.bramble.api.system.LocationUtils;
|
||||||
|
import org.briarproject.bramble.api.system.Scheduler;
|
||||||
import org.briarproject.bramble.plugin.droidtooth.DroidtoothPluginFactory;
|
import org.briarproject.bramble.plugin.droidtooth.DroidtoothPluginFactory;
|
||||||
import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
|
import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
|
||||||
import org.briarproject.bramble.plugin.tor.TorPluginFactory;
|
import org.briarproject.bramble.plugin.tor.TorPluginFactory;
|
||||||
@@ -22,6 +23,7 @@ import java.util.Arrays;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
|
||||||
import javax.net.SocketFactory;
|
import javax.net.SocketFactory;
|
||||||
|
|
||||||
@@ -33,6 +35,7 @@ public class AndroidPluginModule {
|
|||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
PluginConfig providePluginConfig(@IoExecutor Executor ioExecutor,
|
PluginConfig providePluginConfig(@IoExecutor Executor ioExecutor,
|
||||||
|
@Scheduler ScheduledExecutorService scheduler,
|
||||||
AndroidExecutor androidExecutor, SecureRandom random,
|
AndroidExecutor androidExecutor, SecureRandom random,
|
||||||
SocketFactory torSocketFactory, BackoffFactory backoffFactory,
|
SocketFactory torSocketFactory, BackoffFactory backoffFactory,
|
||||||
Application app, LocationUtils locationUtils, DevReporter reporter,
|
Application app, LocationUtils locationUtils, DevReporter reporter,
|
||||||
@@ -40,9 +43,9 @@ public class AndroidPluginModule {
|
|||||||
Context appContext = app.getApplicationContext();
|
Context appContext = app.getApplicationContext();
|
||||||
DuplexPluginFactory bluetooth = new DroidtoothPluginFactory(ioExecutor,
|
DuplexPluginFactory bluetooth = new DroidtoothPluginFactory(ioExecutor,
|
||||||
androidExecutor, appContext, random, eventBus, backoffFactory);
|
androidExecutor, appContext, random, eventBus, backoffFactory);
|
||||||
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, appContext,
|
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, scheduler,
|
||||||
locationUtils, reporter, eventBus, torSocketFactory,
|
appContext, locationUtils, reporter, eventBus,
|
||||||
backoffFactory);
|
torSocketFactory, backoffFactory);
|
||||||
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
|
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
|
||||||
backoffFactory, appContext);
|
backoffFactory, appContext);
|
||||||
Collection<DuplexPluginFactory> duplex =
|
Collection<DuplexPluginFactory> duplex =
|
||||||
|
|||||||
@@ -59,7 +59,12 @@ import java.util.Map.Entry;
|
|||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.zip.ZipInputStream;
|
import java.util.zip.ZipInputStream;
|
||||||
@@ -70,10 +75,15 @@ import javax.net.SocketFactory;
|
|||||||
import static android.content.Context.CONNECTIVITY_SERVICE;
|
import static android.content.Context.CONNECTIVITY_SERVICE;
|
||||||
import static android.content.Context.MODE_PRIVATE;
|
import static android.content.Context.MODE_PRIVATE;
|
||||||
import static android.content.Context.POWER_SERVICE;
|
import static android.content.Context.POWER_SERVICE;
|
||||||
|
import static android.content.Intent.ACTION_SCREEN_OFF;
|
||||||
|
import static android.content.Intent.ACTION_SCREEN_ON;
|
||||||
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
||||||
import static android.net.ConnectivityManager.TYPE_WIFI;
|
import static android.net.ConnectivityManager.TYPE_WIFI;
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
|
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
|
||||||
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
|
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
|
import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
|
||||||
@@ -102,6 +112,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
Logger.getLogger(TorPlugin.class.getName());
|
Logger.getLogger(TorPlugin.class.getName());
|
||||||
|
|
||||||
private final Executor ioExecutor;
|
private final Executor ioExecutor;
|
||||||
|
private final ScheduledExecutorService scheduler;
|
||||||
private final Context appContext;
|
private final Context appContext;
|
||||||
private final LocationUtils locationUtils;
|
private final LocationUtils locationUtils;
|
||||||
private final DevReporter reporter;
|
private final DevReporter reporter;
|
||||||
@@ -114,6 +125,9 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
private final File torDirectory, torFile, geoIpFile, configFile;
|
private final File torDirectory, torFile, geoIpFile, configFile;
|
||||||
private final File doneFile, cookieFile;
|
private final File doneFile, cookieFile;
|
||||||
private final PowerManager.WakeLock wakeLock;
|
private final PowerManager.WakeLock wakeLock;
|
||||||
|
private final Lock connectionStatusLock;
|
||||||
|
private final AtomicReference<Future<?>> connectivityCheck =
|
||||||
|
new AtomicReference<>();
|
||||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||||
|
|
||||||
private volatile boolean running = false;
|
private volatile boolean running = false;
|
||||||
@@ -122,12 +136,13 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
private volatile TorControlConnection controlConnection = null;
|
private volatile TorControlConnection controlConnection = null;
|
||||||
private volatile BroadcastReceiver networkStateReceiver = null;
|
private volatile BroadcastReceiver networkStateReceiver = null;
|
||||||
|
|
||||||
TorPlugin(Executor ioExecutor, Context appContext,
|
TorPlugin(Executor ioExecutor, ScheduledExecutorService scheduler,
|
||||||
LocationUtils locationUtils, DevReporter reporter,
|
Context appContext, LocationUtils locationUtils,
|
||||||
SocketFactory torSocketFactory, Backoff backoff,
|
DevReporter reporter, SocketFactory torSocketFactory,
|
||||||
DuplexPluginCallback callback, String architecture, int maxLatency,
|
Backoff backoff, DuplexPluginCallback callback,
|
||||||
int maxIdleTime) {
|
String architecture, int maxLatency, int maxIdleTime) {
|
||||||
this.ioExecutor = ioExecutor;
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.scheduler = scheduler;
|
||||||
this.appContext = appContext;
|
this.appContext = appContext;
|
||||||
this.locationUtils = locationUtils;
|
this.locationUtils = locationUtils;
|
||||||
this.reporter = reporter;
|
this.reporter = reporter;
|
||||||
@@ -152,6 +167,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
// This tag will prevent Huawei's powermanager from killing us.
|
// This tag will prevent Huawei's powermanager from killing us.
|
||||||
wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "LocationManagerService");
|
wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "LocationManagerService");
|
||||||
wakeLock.setReferenceCounted(false);
|
wakeLock.setReferenceCounted(false);
|
||||||
|
connectionStatusLock = new ReentrantLock();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -204,11 +220,11 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
if (LOG.isLoggable(INFO)) {
|
if (LOG.isLoggable(INFO)) {
|
||||||
Scanner stdout = new Scanner(torProcess.getInputStream());
|
Scanner stdout = new Scanner(torProcess.getInputStream());
|
||||||
Scanner stderr = new Scanner(torProcess.getErrorStream());
|
Scanner stderr = new Scanner(torProcess.getErrorStream());
|
||||||
while (stdout.hasNextLine() || stderr.hasNextLine()){
|
while (stdout.hasNextLine() || stderr.hasNextLine()) {
|
||||||
if(stdout.hasNextLine()) {
|
if (stdout.hasNextLine()) {
|
||||||
LOG.info(stdout.nextLine());
|
LOG.info(stdout.nextLine());
|
||||||
}
|
}
|
||||||
if(stderr.hasNextLine()){
|
if (stderr.hasNextLine()) {
|
||||||
LOG.info(stderr.nextLine());
|
LOG.info(stderr.nextLine());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,7 +273,11 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
// Register to receive network status events
|
// Register to receive network status events
|
||||||
networkStateReceiver = new NetworkStateReceiver();
|
networkStateReceiver = new NetworkStateReceiver();
|
||||||
IntentFilter filter = new IntentFilter(CONNECTIVITY_ACTION);
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addAction(CONNECTIVITY_ACTION);
|
||||||
|
filter.addAction(ACTION_SCREEN_ON);
|
||||||
|
filter.addAction(ACTION_SCREEN_OFF);
|
||||||
|
if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
|
||||||
appContext.registerReceiver(networkStateReceiver, filter);
|
appContext.registerReceiver(networkStateReceiver, filter);
|
||||||
// Bind a server socket to receive incoming hidden service connections
|
// Bind a server socket to receive incoming hidden service connections
|
||||||
bind();
|
bind();
|
||||||
@@ -618,6 +638,8 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
@Override
|
@Override
|
||||||
public void orConnStatus(String status, String orName) {
|
public void orConnStatus(String status, String orName) {
|
||||||
if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
|
if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
|
||||||
|
if (status.equals("CLOSED") || status.equals("FAILED"))
|
||||||
|
updateConnectionStatus(); // Check whether we've lost connectivity
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -657,7 +679,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEvent(int event, String path) {
|
public void onEvent(int event, @Nullable String path) {
|
||||||
stopWatching();
|
stopWatching();
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
}
|
}
|
||||||
@@ -677,53 +699,73 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
private void updateConnectionStatus() {
|
private void updateConnectionStatus() {
|
||||||
ioExecutor.execute(() -> {
|
ioExecutor.execute(() -> {
|
||||||
if (!running) return;
|
if (!running) return;
|
||||||
|
|
||||||
Object o = appContext.getSystemService(CONNECTIVITY_SERVICE);
|
|
||||||
ConnectivityManager cm = (ConnectivityManager) o;
|
|
||||||
NetworkInfo net = cm.getActiveNetworkInfo();
|
|
||||||
boolean online = net != null && net.isConnected();
|
|
||||||
boolean wifi = online && net.getType() == TYPE_WIFI;
|
|
||||||
String country = locationUtils.getCurrentCountry();
|
|
||||||
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(
|
|
||||||
country);
|
|
||||||
Settings s = callback.getSettings();
|
|
||||||
int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_ALWAYS);
|
|
||||||
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info("Online: " + online + ", wifi: " + wifi);
|
|
||||||
if ("".equals(country)) LOG.info("Country code unknown");
|
|
||||||
else LOG.info("Country code: " + country);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!online) {
|
connectionStatusLock.lock();
|
||||||
LOG.info("Disabling network, device is offline");
|
updateConnectionStatusLocked();
|
||||||
enableNetwork(false);
|
} finally {
|
||||||
} else if (blocked) {
|
connectionStatusLock.unlock();
|
||||||
LOG.info("Disabling network, country is blocked");
|
|
||||||
enableNetwork(false);
|
|
||||||
} else if (network == PREF_TOR_NETWORK_NEVER
|
|
||||||
|| (network == PREF_TOR_NETWORK_WIFI && !wifi)) {
|
|
||||||
LOG.info("Disabling network due to data setting");
|
|
||||||
enableNetwork(false);
|
|
||||||
} else {
|
|
||||||
LOG.info("Enabling network");
|
|
||||||
enableNetwork(true);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Locking: connectionStatusLock
|
||||||
|
private void updateConnectionStatusLocked() {
|
||||||
|
Object o = appContext.getSystemService(CONNECTIVITY_SERVICE);
|
||||||
|
ConnectivityManager cm = (ConnectivityManager) o;
|
||||||
|
NetworkInfo net = cm.getActiveNetworkInfo();
|
||||||
|
boolean online = net != null && net.isConnected();
|
||||||
|
boolean wifi = online && net.getType() == TYPE_WIFI;
|
||||||
|
String country = locationUtils.getCurrentCountry();
|
||||||
|
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(
|
||||||
|
country);
|
||||||
|
Settings s = callback.getSettings();
|
||||||
|
int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_ALWAYS);
|
||||||
|
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("Online: " + online + ", wifi: " + wifi);
|
||||||
|
if ("".equals(country)) LOG.info("Country code unknown");
|
||||||
|
else LOG.info("Country code: " + country);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!online) {
|
||||||
|
LOG.info("Disabling network, device is offline");
|
||||||
|
enableNetwork(false);
|
||||||
|
} else if (blocked) {
|
||||||
|
LOG.info("Disabling network, country is blocked");
|
||||||
|
enableNetwork(false);
|
||||||
|
} else if (network == PREF_TOR_NETWORK_NEVER
|
||||||
|
|| (network == PREF_TOR_NETWORK_WIFI && !wifi)) {
|
||||||
|
LOG.info("Disabling network due to data setting");
|
||||||
|
enableNetwork(false);
|
||||||
|
} else {
|
||||||
|
LOG.info("Enabling network");
|
||||||
|
enableNetwork(true);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleConnectionStatusUpdate() {
|
||||||
|
Future<?> newConnectivityCheck =
|
||||||
|
scheduler.schedule(this::updateConnectionStatus, 1, MINUTES);
|
||||||
|
Future<?> oldConnectivityCheck =
|
||||||
|
connectivityCheck.getAndSet(newConnectivityCheck);
|
||||||
|
if (oldConnectivityCheck != null) oldConnectivityCheck.cancel(false);
|
||||||
|
}
|
||||||
|
|
||||||
private class NetworkStateReceiver extends BroadcastReceiver {
|
private class NetworkStateReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context ctx, Intent i) {
|
public void onReceive(Context ctx, Intent i) {
|
||||||
if (!running) return;
|
if (!running) return;
|
||||||
if (CONNECTIVITY_ACTION.equals(i.getAction())) {
|
String action = i.getAction();
|
||||||
LOG.info("Detected connectivity change");
|
if (LOG.isLoggable(INFO)) LOG.info("Received broadcast " + action);
|
||||||
updateConnectionStatus();
|
updateConnectionStatus();
|
||||||
|
if (ACTION_SCREEN_ON.equals(action)
|
||||||
|
|| ACTION_SCREEN_OFF.equals(action)) {
|
||||||
|
scheduleConnectionStatusUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import org.briarproject.bramble.api.system.LocationUtils;
|
|||||||
import org.briarproject.bramble.util.AndroidUtils;
|
import org.briarproject.bramble.util.AndroidUtils;
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
@@ -36,6 +37,7 @@ public class TorPluginFactory implements DuplexPluginFactory {
|
|||||||
private static final double BACKOFF_BASE = 1.2;
|
private static final double BACKOFF_BASE = 1.2;
|
||||||
|
|
||||||
private final Executor ioExecutor;
|
private final Executor ioExecutor;
|
||||||
|
private final ScheduledExecutorService scheduler;
|
||||||
private final Context appContext;
|
private final Context appContext;
|
||||||
private final LocationUtils locationUtils;
|
private final LocationUtils locationUtils;
|
||||||
private final DevReporter reporter;
|
private final DevReporter reporter;
|
||||||
@@ -43,11 +45,13 @@ public class TorPluginFactory implements DuplexPluginFactory {
|
|||||||
private final SocketFactory torSocketFactory;
|
private final SocketFactory torSocketFactory;
|
||||||
private final BackoffFactory backoffFactory;
|
private final BackoffFactory backoffFactory;
|
||||||
|
|
||||||
public TorPluginFactory(Executor ioExecutor, Context appContext,
|
public TorPluginFactory(Executor ioExecutor,
|
||||||
|
ScheduledExecutorService scheduler, Context appContext,
|
||||||
LocationUtils locationUtils, DevReporter reporter,
|
LocationUtils locationUtils, DevReporter reporter,
|
||||||
EventBus eventBus, SocketFactory torSocketFactory,
|
EventBus eventBus, SocketFactory torSocketFactory,
|
||||||
BackoffFactory backoffFactory) {
|
BackoffFactory backoffFactory) {
|
||||||
this.ioExecutor = ioExecutor;
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.scheduler = scheduler;
|
||||||
this.appContext = appContext;
|
this.appContext = appContext;
|
||||||
this.locationUtils = locationUtils;
|
this.locationUtils = locationUtils;
|
||||||
this.reporter = reporter;
|
this.reporter = reporter;
|
||||||
@@ -89,9 +93,9 @@ public class TorPluginFactory implements DuplexPluginFactory {
|
|||||||
|
|
||||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||||
TorPlugin plugin = new TorPlugin(ioExecutor, appContext, locationUtils,
|
TorPlugin plugin = new TorPlugin(ioExecutor, scheduler, appContext,
|
||||||
reporter, torSocketFactory, backoff, callback, architecture,
|
locationUtils, reporter, torSocketFactory, backoff, callback,
|
||||||
MAX_LATENCY, MAX_IDLE_TIME);
|
architecture, MAX_LATENCY, MAX_IDLE_TIME);
|
||||||
eventBus.addListener(plugin);
|
eventBus.addListener(plugin);
|
||||||
return plugin;
|
return plugin;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,19 +12,18 @@ public interface ContactGroupFactory {
|
|||||||
/**
|
/**
|
||||||
* Creates a group that is not shared with any contacts.
|
* Creates a group that is not shared with any contacts.
|
||||||
*/
|
*/
|
||||||
Group createLocalGroup(ClientId clientId, int clientVersion);
|
Group createLocalGroup(ClientId clientId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a group for the given client to share with the given contact.
|
* Creates a group for the given client to share with the given contact.
|
||||||
*/
|
*/
|
||||||
Group createContactGroup(ClientId clientId, int clientVersion,
|
Group createContactGroup(ClientId clientId, Contact contact);
|
||||||
Contact contact);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a group for the given client to share between the given authors
|
* Creates a group for the given client to share between the given authors
|
||||||
* identified by their AuthorIds.
|
* identified by their AuthorIds.
|
||||||
*/
|
*/
|
||||||
Group createContactGroup(ClientId clientId, int clientVersion,
|
Group createContactGroup(ClientId clientId, AuthorId authorId1,
|
||||||
AuthorId authorId1, AuthorId authorId2);
|
AuthorId authorId2);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,32 +12,6 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface ContactExchangeTask {
|
public interface ContactExchangeTask {
|
||||||
|
|
||||||
/**
|
|
||||||
* The current version of the contact exchange protocol
|
|
||||||
*/
|
|
||||||
int PROTOCOL_VERSION = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Label for deriving Alice's header key from the master secret.
|
|
||||||
*/
|
|
||||||
String ALICE_KEY_LABEL =
|
|
||||||
"org.briarproject.bramble.contact/ALICE_HEADER_KEY";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Label for deriving Bob's header key from the master secret.
|
|
||||||
*/
|
|
||||||
String BOB_KEY_LABEL = "org.briarproject.bramble.contact/BOB_HEADER_KEY";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Label for deriving Alice's key binding nonce from the master secret.
|
|
||||||
*/
|
|
||||||
String ALICE_NONCE_LABEL = "org.briarproject.bramble.contact/ALICE_NONCE";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Label for deriving Bob's key binding nonce from the master secret.
|
|
||||||
*/
|
|
||||||
String BOB_NONCE_LABEL = "org.briarproject.bramble.contact/BOB_NONCE";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exchanges contact information with a remote peer.
|
* Exchanges contact information with a remote peer.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package org.briarproject.bramble.api.crypto;
|
package org.briarproject.bramble.api.crypto;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
|
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
@@ -17,98 +20,156 @@ public interface CryptoComponent {
|
|||||||
|
|
||||||
KeyParser getSignatureKeyParser();
|
KeyParser getSignatureKeyParser();
|
||||||
|
|
||||||
KeyPair generateEdKeyPair();
|
|
||||||
|
|
||||||
KeyParser getEdKeyParser();
|
|
||||||
|
|
||||||
KeyParser getMessageKeyParser();
|
KeyParser getMessageKeyParser();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Derives another secret key from the given secret key.
|
* Derives a stream header key from the given master secret.
|
||||||
*
|
* @param alice whether the key is for use by Alice or Bob.
|
||||||
* @param label a namespaced label indicating the purpose of the derived
|
|
||||||
* key, to prevent it from being repurposed or colliding with a key derived
|
|
||||||
* for another purpose
|
|
||||||
*/
|
*/
|
||||||
SecretKey deriveKey(String label, SecretKey k, byte[]... inputs);
|
SecretKey deriveHeaderKey(SecretKey master, boolean alice);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derives a message authentication code key from the given master secret.
|
||||||
|
* @param alice whether the key is for use by Alice or Bob.
|
||||||
|
*/
|
||||||
|
SecretKey deriveMacKey(SecretKey master, boolean alice);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derives a nonce from the given master secret for one of the parties to
|
||||||
|
* sign.
|
||||||
|
* @param alice whether the nonce is for use by Alice or Bob.
|
||||||
|
*/
|
||||||
|
byte[] deriveSignatureNonce(SecretKey master, boolean alice);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derives a commitment to the provided public key.
|
||||||
|
* <p/>
|
||||||
|
* Part of BQP.
|
||||||
|
*
|
||||||
|
* @param publicKey the public key
|
||||||
|
* @return the commitment to the provided public key.
|
||||||
|
*/
|
||||||
|
byte[] deriveKeyCommitment(byte[] publicKey);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Derives a common shared secret from two public keys and one of the
|
* Derives a common shared secret from two public keys and one of the
|
||||||
* corresponding private keys.
|
* corresponding private keys.
|
||||||
|
* <p/>
|
||||||
|
* Part of BQP.
|
||||||
*
|
*
|
||||||
* @param label a namespaced label indicating the purpose of this shared
|
* @param theirPublicKey the ephemeral public key of the remote party
|
||||||
* secret, to prevent it from being repurposed or colliding with a shared
|
* @param ourKeyPair our ephemeral keypair
|
||||||
* secret derived for another purpose
|
* @param alice true if ourKeyPair belongs to Alice
|
||||||
* @param theirPublicKey the public key of the remote party
|
|
||||||
* @param ourKeyPair the key pair of the local party
|
|
||||||
* @return the shared secret
|
* @return the shared secret
|
||||||
|
* @throws GeneralSecurityException
|
||||||
*/
|
*/
|
||||||
SecretKey deriveSharedSecret(String label, PublicKey theirPublicKey,
|
SecretKey deriveSharedSecret(byte[] theirPublicKey, KeyPair ourKeyPair,
|
||||||
KeyPair ourKeyPair, byte[]... inputs)
|
boolean alice) throws GeneralSecurityException;
|
||||||
throws GeneralSecurityException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signs the given byte[] with the given ECDSA private key.
|
* Derives the content of a confirmation record.
|
||||||
|
* <p/>
|
||||||
|
* Part of BQP.
|
||||||
*
|
*
|
||||||
* @param label a namespaced label indicating the purpose of this
|
* @param sharedSecret the common shared secret
|
||||||
* signature, to prevent it from being repurposed or colliding with a
|
* @param theirPayload the commit payload from the remote party
|
||||||
* signature created for another purpose
|
* @param ourPayload the commit payload we sent
|
||||||
|
* @param theirPublicKey the ephemeral public key of the remote party
|
||||||
|
* @param ourKeyPair our ephemeral keypair
|
||||||
|
* @param alice true if ourKeyPair belongs to Alice
|
||||||
|
* @param aliceRecord true if the confirmation record is for use by Alice
|
||||||
|
* @return the confirmation record
|
||||||
|
*/
|
||||||
|
byte[] deriveConfirmationRecord(SecretKey sharedSecret,
|
||||||
|
byte[] theirPayload, byte[] ourPayload,
|
||||||
|
byte[] theirPublicKey, KeyPair ourKeyPair,
|
||||||
|
boolean alice, boolean aliceRecord);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derives a master secret from the given shared secret.
|
||||||
|
* <p/>
|
||||||
|
* Part of BQP.
|
||||||
|
*
|
||||||
|
* @param sharedSecret the common shared secret
|
||||||
|
* @return the master secret
|
||||||
|
*/
|
||||||
|
SecretKey deriveMasterSecret(SecretKey sharedSecret);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derives a master secret from two public keys and one of the corresponding
|
||||||
|
* private keys.
|
||||||
|
* <p/>
|
||||||
|
* This is a helper method that calls
|
||||||
|
* deriveMasterSecret(deriveSharedSecret(theirPublicKey, ourKeyPair, alice))
|
||||||
|
*
|
||||||
|
* @param theirPublicKey the ephemeral public key of the remote party
|
||||||
|
* @param ourKeyPair our ephemeral keypair
|
||||||
|
* @param alice true if ourKeyPair belongs to Alice
|
||||||
|
* @return the shared secret
|
||||||
|
* @throws GeneralSecurityException
|
||||||
|
*/
|
||||||
|
SecretKey deriveMasterSecret(byte[] theirPublicKey, KeyPair ourKeyPair,
|
||||||
|
boolean alice) throws GeneralSecurityException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derives initial transport keys for the given transport in the given
|
||||||
|
* rotation period from the given master secret.
|
||||||
|
* @param alice whether the keys are for use by Alice or Bob.
|
||||||
|
*/
|
||||||
|
TransportKeys deriveTransportKeys(TransportId t, SecretKey master,
|
||||||
|
long rotationPeriod, boolean alice);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotates the given transport keys to the given rotation period. If the
|
||||||
|
* keys are for a future rotation period they are not rotated.
|
||||||
|
*/
|
||||||
|
TransportKeys rotateTransportKeys(TransportKeys k, long rotationPeriod);
|
||||||
|
|
||||||
|
/** Encodes the pseudo-random tag that is used to recognise a stream. */
|
||||||
|
void encodeTag(byte[] tag, SecretKey tagKey, int protocolVersion,
|
||||||
|
long streamNumber);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signs the given byte[] with the given PrivateKey.
|
||||||
|
*
|
||||||
|
* @param label A label specific to this signature
|
||||||
|
* to ensure that the signature cannot be repurposed
|
||||||
*/
|
*/
|
||||||
byte[] sign(String label, byte[] toSign, byte[] privateKey)
|
byte[] sign(String label, byte[] toSign, byte[] privateKey)
|
||||||
throws GeneralSecurityException;
|
throws GeneralSecurityException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signs the given byte[] with the given Ed25519 private key.
|
* Verifies that the given signature is valid for the signedData
|
||||||
|
* and the given publicKey.
|
||||||
*
|
*
|
||||||
* @param label A label specific to this signature
|
* @param label A label that was specific to this signature
|
||||||
* to ensure that the signature cannot be repurposed
|
* to ensure that the signature cannot be repurposed
|
||||||
*/
|
|
||||||
byte[] signEd(String label, byte[] toSign, byte[] privateKey)
|
|
||||||
throws GeneralSecurityException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies that the given signature is valid for the signed data
|
|
||||||
* and the given ECDSA public key.
|
|
||||||
*
|
|
||||||
* @param label a namespaced label indicating the purpose of this
|
|
||||||
* signature, to prevent it from being repurposed or colliding with a
|
|
||||||
* signature created for another purpose
|
|
||||||
* @return true if the signature was valid, false otherwise.
|
* @return true if the signature was valid, false otherwise.
|
||||||
*/
|
*/
|
||||||
boolean verify(String label, byte[] signedData, byte[] publicKey,
|
boolean verify(String label, byte[] signedData, byte[] publicKey,
|
||||||
byte[] signature) throws GeneralSecurityException;
|
byte[] signature) throws GeneralSecurityException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies that the given signature is valid for the signed data
|
|
||||||
* and the given Ed25519 public key.
|
|
||||||
*
|
|
||||||
* @param label A label that was specific to this signature
|
|
||||||
* to ensure that the signature cannot be repurposed
|
|
||||||
* @return true if the signature was valid, false otherwise.
|
|
||||||
*/
|
|
||||||
boolean verifyEd(String label, byte[] signedData, byte[] publicKey,
|
|
||||||
byte[] signature) throws GeneralSecurityException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the hash of the given inputs. The inputs are unambiguously
|
* Returns the hash of the given inputs. The inputs are unambiguously
|
||||||
* combined by prefixing each input with its length.
|
* combined by prefixing each input with its length.
|
||||||
*
|
*
|
||||||
* @param label a namespaced label indicating the purpose of this hash, to
|
* @param label A label specific to this hash to ensure that hashes
|
||||||
* prevent it from being repurposed or colliding with a hash created for
|
* calculated for distinct purposes don't collide.
|
||||||
* another purpose
|
|
||||||
*/
|
*/
|
||||||
byte[] hash(String label, byte[]... inputs);
|
byte[] hash(String label, byte[]... inputs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the length of hashes produced by
|
||||||
|
* the {@link CryptoComponent#hash(String, byte[]...)} method.
|
||||||
|
*/
|
||||||
|
int getHashLength();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a message authentication code with the given key over the
|
* Returns a message authentication code with the given key over the
|
||||||
* given inputs. The inputs are unambiguously combined by prefixing each
|
* given inputs. The inputs are unambiguously combined by prefixing each
|
||||||
* input with its length.
|
* input with its length.
|
||||||
*
|
|
||||||
* @param label a namespaced label indicating the purpose of this MAC, to
|
|
||||||
* prevent it from being repurposed or colliding with a MAC created for
|
|
||||||
* another purpose
|
|
||||||
*/
|
*/
|
||||||
byte[] mac(String label, SecretKey macKey, byte[]... inputs);
|
byte[] mac(SecretKey macKey, byte[]... inputs);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypts and authenticates the given plaintext so it can be written to
|
* Encrypts and authenticates the given plaintext so it can be written to
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
package org.briarproject.bramble.api.crypto;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Crypto operations for the key agreement protocol - see
|
|
||||||
* https://code.briarproject.org/akwizgran/briar-spec/blob/master/protocols/BQP.md
|
|
||||||
*/
|
|
||||||
public interface KeyAgreementCrypto {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hash label for public key commitment.
|
|
||||||
*/
|
|
||||||
String COMMIT_LABEL = "org.briarproject.bramble.keyagreement/COMMIT";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Key derivation label for confirmation record.
|
|
||||||
*/
|
|
||||||
String CONFIRMATION_KEY_LABEL =
|
|
||||||
"org.briarproject.bramble.keyagreement/CONFIRMATION_KEY";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MAC label for confirmation record.
|
|
||||||
*/
|
|
||||||
String CONFIRMATION_MAC_LABEL =
|
|
||||||
"org.briarproject.bramble.keyagreement/CONFIRMATION_MAC";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derives a commitment to the provided public key.
|
|
||||||
*
|
|
||||||
* @param publicKey the public key
|
|
||||||
* @return the commitment to the provided public key.
|
|
||||||
*/
|
|
||||||
byte[] deriveKeyCommitment(PublicKey publicKey);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derives the content of a confirmation record.
|
|
||||||
*
|
|
||||||
* @param sharedSecret the common shared secret
|
|
||||||
* @param theirPayload the key exchange payload of the remote party
|
|
||||||
* @param ourPayload the key exchange payload of the local party
|
|
||||||
* @param theirPublicKey the ephemeral public key of the remote party
|
|
||||||
* @param ourKeyPair our ephemeral key pair of the local party
|
|
||||||
* @param alice true if the local party is Alice
|
|
||||||
* @param aliceRecord true if the confirmation record is for use by Alice
|
|
||||||
* @return the confirmation record
|
|
||||||
*/
|
|
||||||
byte[] deriveConfirmationRecord(SecretKey sharedSecret,
|
|
||||||
byte[] theirPayload, byte[] ourPayload,
|
|
||||||
PublicKey theirPublicKey, KeyPair ourKeyPair,
|
|
||||||
boolean alice, boolean aliceRecord);
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package org.briarproject.bramble.api.crypto;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
|
||||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Crypto operations for the transport security protocol - see
|
|
||||||
* https://code.briarproject.org/akwizgran/briar-spec/blob/master/protocols/BTP.md
|
|
||||||
*/
|
|
||||||
public interface TransportCrypto {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derives initial transport keys for the given transport in the given
|
|
||||||
* rotation period from the given master secret.
|
|
||||||
*
|
|
||||||
* @param alice whether the keys are for use by Alice or Bob.
|
|
||||||
*/
|
|
||||||
TransportKeys deriveTransportKeys(TransportId t, SecretKey master,
|
|
||||||
long rotationPeriod, boolean alice);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rotates the given transport keys to the given rotation period. If the
|
|
||||||
* keys are for the given period or any later period they are not rotated.
|
|
||||||
*/
|
|
||||||
TransportKeys rotateTransportKeys(TransportKeys k, long rotationPeriod);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes the pseudo-random tag that is used to recognise a stream.
|
|
||||||
*/
|
|
||||||
void encodeTag(byte[] tag, SecretKey tagKey, int protocolVersion,
|
|
||||||
long streamNumber);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package org.briarproject.bramble.api.db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when the database uses a newer schema than the current code.
|
||||||
|
*/
|
||||||
|
public class DataTooNewException extends DbException {
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package org.briarproject.bramble.api.db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when the database uses an older schema than the current code and
|
||||||
|
* cannot be migrated.
|
||||||
|
*/
|
||||||
|
public class DataTooOldException extends DbException {
|
||||||
|
}
|
||||||
@@ -37,6 +37,11 @@ public interface DatabaseComponent {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the database and returns true if the database already existed.
|
* Opens the database and returns true if the database already existed.
|
||||||
|
*
|
||||||
|
* @throws DataTooNewException if the data uses a newer schema than the
|
||||||
|
* current code
|
||||||
|
* @throws DataTooOldException if the data uses an older schema than the
|
||||||
|
* current code and cannot be migrated
|
||||||
*/
|
*/
|
||||||
boolean open() throws DbException;
|
boolean open() throws DbException;
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public class AuthorId extends UniqueId {
|
|||||||
/**
|
/**
|
||||||
* Label for hashing authors to calculate their identities.
|
* Label for hashing authors to calculate their identities.
|
||||||
*/
|
*/
|
||||||
public static final String LABEL = "org.briarproject.bramble/AUTHOR_ID";
|
public static final String LABEL = "org.briarproject.bramble.AUTHOR_ID";
|
||||||
|
|
||||||
public AuthorId(byte[] id) {
|
public AuthorId(byte[] id) {
|
||||||
super(id);
|
super(id);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ public interface KeyAgreementConstants {
|
|||||||
/**
|
/**
|
||||||
* The current version of the BQP protocol.
|
* The current version of the BQP protocol.
|
||||||
*/
|
*/
|
||||||
byte PROTOCOL_VERSION = 3;
|
byte PROTOCOL_VERSION = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The length of the record header in bytes.
|
* The length of the record header in bytes.
|
||||||
@@ -22,10 +22,7 @@ public interface KeyAgreementConstants {
|
|||||||
*/
|
*/
|
||||||
int COMMIT_LENGTH = 16;
|
int COMMIT_LENGTH = 16;
|
||||||
|
|
||||||
/**
|
long CONNECTION_TIMEOUT = 20 * 1000; // Milliseconds
|
||||||
* The connection timeout in milliseconds.
|
|
||||||
*/
|
|
||||||
long CONNECTION_TIMEOUT = 20 * 1000;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The transport identifier for Bluetooth.
|
* The transport identifier for Bluetooth.
|
||||||
@@ -36,16 +33,4 @@ public interface KeyAgreementConstants {
|
|||||||
* The transport identifier for LAN.
|
* The transport identifier for LAN.
|
||||||
*/
|
*/
|
||||||
int TRANSPORT_ID_LAN = 1;
|
int TRANSPORT_ID_LAN = 1;
|
||||||
|
|
||||||
/**
|
|
||||||
* Label for deriving the shared secret.
|
|
||||||
*/
|
|
||||||
String SHARED_SECRET_LABEL =
|
|
||||||
"org.briarproject.bramble.keyagreement/SHARED_SECRET";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Label for deriving the master secret.
|
|
||||||
*/
|
|
||||||
String MASTER_SECRET_LABEL =
|
|
||||||
"org.briarproject.bramble.keyagreement/MASTER_SECRET";
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package org.briarproject.bramble.api.keyagreement;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages tasks for conducting key agreements with remote peers.
|
||||||
|
*/
|
||||||
|
@NotNullByDefault
|
||||||
|
public interface KeyAgreementTaskFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current key agreement task.
|
||||||
|
*/
|
||||||
|
KeyAgreementTask createTask();
|
||||||
|
}
|
||||||
@@ -17,11 +17,6 @@ public interface TransportPropertyManager {
|
|||||||
*/
|
*/
|
||||||
ClientId CLIENT_ID = new ClientId("org.briarproject.briar.properties");
|
ClientId CLIENT_ID = new ClientId("org.briarproject.briar.properties");
|
||||||
|
|
||||||
/**
|
|
||||||
* The current version of the transport property client.
|
|
||||||
*/
|
|
||||||
int CLIENT_VERSION = 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the given properties received while adding a contact - they will
|
* Stores the given properties received while adding a contact - they will
|
||||||
* be superseded by any properties synced from the contact.
|
* be superseded by any properties synced from the contact.
|
||||||
|
|||||||
@@ -36,8 +36,4 @@ public class ClientId implements Comparable<ClientId> {
|
|||||||
return id.hashCode();
|
return id.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
public interface GroupFactory {
|
public interface GroupFactory {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a group with the given client ID, client version and descriptor.
|
* Creates a group with the given client ID and descriptor.
|
||||||
*/
|
*/
|
||||||
Group createGroup(ClientId c, int clientVersion, byte[] descriptor);
|
Group createGroup(ClientId c, byte[] descriptor);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public class GroupId extends UniqueId {
|
|||||||
/**
|
/**
|
||||||
* Label for hashing groups to calculate their identifiers.
|
* Label for hashing groups to calculate their identifiers.
|
||||||
*/
|
*/
|
||||||
public static final String LABEL = "org.briarproject.bramble/GROUP_ID";
|
public static final String LABEL = "org.briarproject.bramble.GROUP_ID";
|
||||||
|
|
||||||
public GroupId(byte[] id) {
|
public GroupId(byte[] id) {
|
||||||
super(id);
|
super(id);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public class MessageId extends UniqueId {
|
|||||||
/**
|
/**
|
||||||
* Label for hashing messages to calculate their identifiers.
|
* Label for hashing messages to calculate their identifiers.
|
||||||
*/
|
*/
|
||||||
public static final String LABEL = "org.briarproject.bramble/MESSAGE_ID";
|
public static final String LABEL = "org.briarproject.bramble.MESSAGE_ID";
|
||||||
|
|
||||||
public MessageId(byte[] id) {
|
public MessageId(byte[] id) {
|
||||||
super(id);
|
super(id);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ public interface TransportConstants {
|
|||||||
/**
|
/**
|
||||||
* The current version of the transport protocol.
|
* The current version of the transport protocol.
|
||||||
*/
|
*/
|
||||||
int PROTOCOL_VERSION = 4;
|
int PROTOCOL_VERSION = 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The length of the pseudo-random tag in bytes.
|
* The length of the pseudo-random tag in bytes.
|
||||||
@@ -80,32 +80,4 @@ public interface TransportConstants {
|
|||||||
* The size of the reordering window.
|
* The size of the reordering window.
|
||||||
*/
|
*/
|
||||||
int REORDERING_WINDOW_SIZE = 32;
|
int REORDERING_WINDOW_SIZE = 32;
|
||||||
|
|
||||||
/**
|
|
||||||
* Label for deriving Alice's initial tag key from the master secret.
|
|
||||||
*/
|
|
||||||
String ALICE_TAG_LABEL = "org.briarproject.bramble.transport/ALICE_TAG_KEY";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Label for deriving Bob's initial tag key from the master secret.
|
|
||||||
*/
|
|
||||||
String BOB_TAG_LABEL = "org.briarproject.bramble.transport/BOB_TAG_KEY";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Label for deriving Alice's initial header key from the master secret.
|
|
||||||
*/
|
|
||||||
String ALICE_HEADER_LABEL =
|
|
||||||
"org.briarproject.bramble.transport/ALICE_HEADER_KEY";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Label for deriving Bob's initial header key from the master secret.
|
|
||||||
*/
|
|
||||||
String BOB_HEADER_LABEL =
|
|
||||||
"org.briarproject.bramble.transport/BOB_HEADER_KEY";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Label for deriving the next period's key in key rotation.
|
|
||||||
*/
|
|
||||||
String ROTATE_LABEL = "org.briarproject.bramble.transport/ROTATE";
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,27 @@ package org.briarproject.bramble.test;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.UniqueId;
|
import org.briarproject.bramble.api.UniqueId;
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
|
import org.briarproject.bramble.api.sync.ClientId;
|
||||||
|
import org.briarproject.bramble.api.sync.Group;
|
||||||
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.bramble.util.IoUtils;
|
import org.briarproject.bramble.util.IoUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||||
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
||||||
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||||
|
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
|
|
||||||
public class TestUtils {
|
public class TestUtils {
|
||||||
|
|
||||||
private static final AtomicInteger nextTestDir =
|
private static final AtomicInteger nextTestDir =
|
||||||
@@ -38,4 +53,50 @@ public class TestUtils {
|
|||||||
return new SecretKey(getRandomBytes(SecretKey.LENGTH));
|
return new SecretKey(getRandomBytes(SecretKey.LENGTH));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static LocalAuthor getLocalAuthor() {
|
||||||
|
return getLocalAuthor(1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LocalAuthor getLocalAuthor(int nameLength) {
|
||||||
|
AuthorId id = new AuthorId(getRandomId());
|
||||||
|
String name = getRandomString(nameLength);
|
||||||
|
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||||
|
byte[] privateKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||||
|
long created = System.currentTimeMillis();
|
||||||
|
return new LocalAuthor(id, name, publicKey, privateKey, created);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Author getAuthor() {
|
||||||
|
return getAuthor(1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Author getAuthor(int nameLength) {
|
||||||
|
AuthorId id = new AuthorId(getRandomId());
|
||||||
|
String name = getRandomString(nameLength);
|
||||||
|
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||||
|
return new Author(id, name, publicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Group getGroup(ClientId clientId) {
|
||||||
|
int descriptorLength = 1 + random.nextInt(MAX_GROUP_DESCRIPTOR_LENGTH);
|
||||||
|
return getGroup(clientId, descriptorLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Group getGroup(ClientId clientId, int descriptorLength) {
|
||||||
|
GroupId groupId = new GroupId(getRandomId());
|
||||||
|
byte[] descriptor = getRandomBytes(descriptorLength);
|
||||||
|
return new Group(groupId, clientId, descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Message getMessage(GroupId groupId) {
|
||||||
|
int bodyLength = 1 + random.nextInt(MAX_MESSAGE_BODY_LENGTH);
|
||||||
|
return getMessage(groupId, MESSAGE_HEADER_LENGTH + bodyLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Message getMessage(GroupId groupId, int rawLength) {
|
||||||
|
MessageId id = new MessageId(getRandomId());
|
||||||
|
byte[] raw = getRandomBytes(rawLength);
|
||||||
|
long timestamp = System.currentTimeMillis();
|
||||||
|
return new Message(id, groupId, timestamp, raw);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,21 +9,18 @@ apply plugin: 'witness'
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation project(path: ':bramble-api', configuration: 'default')
|
implementation project(path: ':bramble-api', configuration: 'default')
|
||||||
implementation 'com.madgag.spongycastle:core:1.58.0.0'
|
implementation 'com.madgag.spongycastle:core:1.58.0.0'
|
||||||
implementation 'com.h2database:h2:1.4.192' // The last version that supports Java 1.6
|
implementation 'com.h2database:h2:1.4.192' // This is the last version that supports Java 1.6
|
||||||
implementation 'org.bitlet:weupnp:0.1.4'
|
implementation 'org.bitlet:weupnp:0.1.4'
|
||||||
implementation 'net.i2p.crypto:eddsa:0.2.0'
|
|
||||||
|
|
||||||
apt 'com.google.dagger:dagger-compiler:2.0.2'
|
apt 'com.google.dagger:dagger-compiler:2.0.2'
|
||||||
|
|
||||||
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
|
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
|
||||||
testImplementation 'org.hsqldb:hsqldb:2.3.5' // The last version that supports Java 1.6
|
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
testImplementation "org.jmock:jmock:2.8.2"
|
testImplementation "org.jmock:jmock:2.8.2"
|
||||||
testImplementation "org.jmock:jmock-junit4:2.8.2"
|
testImplementation "org.jmock:jmock-junit4:2.8.2"
|
||||||
testImplementation "org.jmock:jmock-legacy:2.8.2"
|
testImplementation "org.jmock:jmock-legacy:2.8.2"
|
||||||
testImplementation "org.hamcrest:hamcrest-library:1.3"
|
testImplementation "org.hamcrest:hamcrest-library:1.3"
|
||||||
testImplementation "org.hamcrest:hamcrest-core:1.3"
|
testImplementation "org.hamcrest:hamcrest-core:1.3"
|
||||||
testImplementation "org.whispersystems:curve25519-java:0.4.1"
|
|
||||||
|
|
||||||
testApt 'com.google.dagger:dagger-compiler:2.0.2'
|
testApt 'com.google.dagger:dagger-compiler:2.0.2'
|
||||||
}
|
}
|
||||||
@@ -40,21 +37,18 @@ dependencyVerification {
|
|||||||
'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728',
|
'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728',
|
||||||
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||||
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
|
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
|
||||||
'net.i2p.crypto:eddsa:0.2.0:eddsa-0.2.0.jar:a7cb1b85c16e2f0730b9204106929a1d9aaae1df728adc7041a8b8b605692140',
|
|
||||||
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
|
'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.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
|
||||||
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
|
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
|
||||||
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
|
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
|
||||||
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
|
'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.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c',
|
||||||
'org.hsqldb:hsqldb:2.3.5:hsqldb-2.3.5.jar:6676a6977ac98997a80f827ddbd3fe8ca1e0853dad1492512135fd1a222ccfad',
|
|
||||||
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c',
|
'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-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-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760',
|
||||||
'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16',
|
'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16',
|
||||||
'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80',
|
'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80',
|
||||||
'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220',
|
'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220',
|
||||||
'org.whispersystems:curve25519-java:0.4.1:curve25519-java-0.4.1.jar:7dd659d8822c06c3aea1a47f18fac9e5761e29cab8100030b877db445005f03e',
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,25 +32,23 @@ class ContactGroupFactoryImpl implements ContactGroupFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Group createLocalGroup(ClientId clientId, int clientVersion) {
|
public Group createLocalGroup(ClientId clientId) {
|
||||||
return groupFactory.createGroup(clientId, clientVersion,
|
return groupFactory.createGroup(clientId, LOCAL_GROUP_DESCRIPTOR);
|
||||||
LOCAL_GROUP_DESCRIPTOR);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Group createContactGroup(ClientId clientId, int clientVersion,
|
public Group createContactGroup(ClientId clientId, Contact contact) {
|
||||||
Contact contact) {
|
|
||||||
AuthorId local = contact.getLocalAuthorId();
|
AuthorId local = contact.getLocalAuthorId();
|
||||||
AuthorId remote = contact.getAuthor().getId();
|
AuthorId remote = contact.getAuthor().getId();
|
||||||
byte[] descriptor = createGroupDescriptor(local, remote);
|
byte[] descriptor = createGroupDescriptor(local, remote);
|
||||||
return groupFactory.createGroup(clientId, clientVersion, descriptor);
|
return groupFactory.createGroup(clientId, descriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Group createContactGroup(ClientId clientId, int clientVersion,
|
public Group createContactGroup(ClientId clientId, AuthorId authorId1,
|
||||||
AuthorId authorId1, AuthorId authorId2) {
|
AuthorId authorId2) {
|
||||||
byte[] descriptor = createGroupDescriptor(authorId1, authorId2);
|
byte[] descriptor = createGroupDescriptor(authorId1, authorId2);
|
||||||
return groupFactory.createGroup(clientId, clientVersion, descriptor);
|
return groupFactory.createGroup(clientId, descriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] createGroupDescriptor(AuthorId local, AuthorId remote) {
|
private byte[] createGroupDescriptor(AuthorId local, AuthorId remote) {
|
||||||
|
|||||||
@@ -141,10 +141,8 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Derive the header keys for the transport streams
|
// Derive the header keys for the transport streams
|
||||||
SecretKey aliceHeaderKey = crypto.deriveKey(ALICE_KEY_LABEL,
|
SecretKey aliceHeaderKey = crypto.deriveHeaderKey(masterSecret, true);
|
||||||
masterSecret, new byte[] {PROTOCOL_VERSION});
|
SecretKey bobHeaderKey = crypto.deriveHeaderKey(masterSecret, false);
|
||||||
SecretKey bobHeaderKey = crypto.deriveKey(BOB_KEY_LABEL, masterSecret,
|
|
||||||
new byte[] {PROTOCOL_VERSION});
|
|
||||||
|
|
||||||
// Create the readers
|
// Create the readers
|
||||||
InputStream streamReader =
|
InputStream streamReader =
|
||||||
@@ -158,10 +156,8 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
BdfWriter w = bdfWriterFactory.createWriter(streamWriter);
|
BdfWriter w = bdfWriterFactory.createWriter(streamWriter);
|
||||||
|
|
||||||
// Derive the nonces to be signed
|
// Derive the nonces to be signed
|
||||||
byte[] aliceNonce = crypto.mac(ALICE_NONCE_LABEL, masterSecret,
|
byte[] aliceNonce = crypto.deriveSignatureNonce(masterSecret, true);
|
||||||
new byte[] {PROTOCOL_VERSION});
|
byte[] bobNonce = crypto.deriveSignatureNonce(masterSecret, false);
|
||||||
byte[] bobNonce = crypto.mac(BOB_NONCE_LABEL, masterSecret,
|
|
||||||
new byte[] {PROTOCOL_VERSION});
|
|
||||||
|
|
||||||
// Exchange pseudonyms, signed nonces, and timestamps
|
// Exchange pseudonyms, signed nonces, and timestamps
|
||||||
long localTimestamp = clock.currentTimeMillis();
|
long localTimestamp = clock.currentTimeMillis();
|
||||||
@@ -200,8 +196,8 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Add the contact
|
// Add the contact
|
||||||
ContactId contactId = addContact(remoteAuthor, timestamp,
|
ContactId contactId = addContact(remoteAuthor, masterSecret,
|
||||||
remoteProperties);
|
timestamp, alice, remoteProperties);
|
||||||
// Reuse the connection as a transport connection
|
// Reuse the connection as a transport connection
|
||||||
connectionManager.manageOutgoingConnection(contactId, transportId,
|
connectionManager.manageOutgoingConnection(contactId, transportId,
|
||||||
conn);
|
conn);
|
||||||
@@ -298,15 +294,15 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
return remote;
|
return remote;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ContactId addContact(Author remoteAuthor, long timestamp,
|
private ContactId addContact(Author remoteAuthor, SecretKey master,
|
||||||
|
long timestamp, boolean alice,
|
||||||
Map<TransportId, TransportProperties> remoteProperties)
|
Map<TransportId, TransportProperties> remoteProperties)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
ContactId contactId;
|
ContactId contactId;
|
||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
contactId = contactManager.addContact(txn, remoteAuthor,
|
contactId = contactManager.addContact(txn, remoteAuthor,
|
||||||
localAuthor.getId(), masterSecret, timestamp, alice,
|
localAuthor.getId(), master, timestamp, alice, true, true);
|
||||||
true, true);
|
|
||||||
transportPropertyManager.addRemoteProperties(txn, contactId,
|
transportPropertyManager.addRemoteProperties(txn, contactId,
|
||||||
remoteProperties);
|
remoteProperties);
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
@@ -316,7 +312,8 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
return contactId;
|
return contactId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tryToClose(DuplexTransportConnection conn, boolean exception) {
|
private void tryToClose(DuplexTransportConnection conn,
|
||||||
|
boolean exception) {
|
||||||
try {
|
try {
|
||||||
LOG.info("Closing connection");
|
LOG.info("Closing connection");
|
||||||
conn.getReader().dispose(exception, true);
|
conn.getReader().dispose(exception, true);
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
package org.briarproject.bramble.crypto;
|
package org.briarproject.bramble.crypto;
|
||||||
|
|
||||||
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
|
|
||||||
import net.i2p.crypto.eddsa.EdDSAPublicKey;
|
|
||||||
import net.i2p.crypto.eddsa.KeyPairGenerator;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
import org.briarproject.bramble.api.crypto.KeyParser;
|
||||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
||||||
|
import org.briarproject.bramble.api.transport.IncomingKeys;
|
||||||
|
import org.briarproject.bramble.api.transport.OutgoingKeys;
|
||||||
|
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||||
import org.briarproject.bramble.util.ByteUtils;
|
import org.briarproject.bramble.util.ByteUtils;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
|
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
|
||||||
@@ -26,6 +26,7 @@ import org.spongycastle.crypto.params.ECPrivateKeyParameters;
|
|||||||
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
||||||
import org.spongycastle.crypto.params.KeyParameter;
|
import org.spongycastle.crypto.params.KeyParameter;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.Provider;
|
import java.security.Provider;
|
||||||
@@ -39,8 +40,14 @@ import java.util.logging.Logger;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
|
||||||
|
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||||
import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS;
|
import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS;
|
||||||
|
import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
|
||||||
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
|
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
|
||||||
|
import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
|
||||||
|
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
|
||||||
|
import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
||||||
|
|
||||||
class CryptoComponentImpl implements CryptoComponent {
|
class CryptoComponentImpl implements CryptoComponent {
|
||||||
|
|
||||||
@@ -49,19 +56,49 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
|
|
||||||
private static final int AGREEMENT_KEY_PAIR_BITS = 256;
|
private static final int AGREEMENT_KEY_PAIR_BITS = 256;
|
||||||
private static final int SIGNATURE_KEY_PAIR_BITS = 256;
|
private static final int SIGNATURE_KEY_PAIR_BITS = 256;
|
||||||
private static final int ED_KEY_PAIR_BITS = 256;
|
|
||||||
private static final int STORAGE_IV_BYTES = 24; // 196 bits
|
private static final int STORAGE_IV_BYTES = 24; // 196 bits
|
||||||
private static final int PBKDF_SALT_BYTES = 32; // 256 bits
|
private static final int PBKDF_SALT_BYTES = 32; // 256 bits
|
||||||
private static final int PBKDF_TARGET_MILLIS = 500;
|
private static final int PBKDF_TARGET_MILLIS = 500;
|
||||||
private static final int PBKDF_SAMPLES = 30;
|
private static final int PBKDF_SAMPLES = 30;
|
||||||
|
private static final int HASH_SIZE = 256 / 8;
|
||||||
|
|
||||||
|
private static byte[] ascii(String s) {
|
||||||
|
return s.getBytes(Charset.forName("US-ASCII"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// KDF labels for contact exchange stream header key derivation
|
||||||
|
private static final byte[] A_INVITE = ascii("ALICE_INVITATION_KEY");
|
||||||
|
private static final byte[] B_INVITE = ascii("BOB_INVITATION_KEY");
|
||||||
|
// KDF labels for contact exchange signature nonce derivation
|
||||||
|
private static final byte[] A_SIG_NONCE = ascii("ALICE_SIGNATURE_NONCE");
|
||||||
|
private static final byte[] B_SIG_NONCE = ascii("BOB_SIGNATURE_NONCE");
|
||||||
|
// Hash label for BQP public key commitment derivation
|
||||||
|
private static final String COMMIT =
|
||||||
|
"org.briarproject.bramble.COMMIT";
|
||||||
|
// Hash label for shared secret derivation
|
||||||
|
private static final String SHARED_SECRET =
|
||||||
|
"org.briarproject.bramble.SHARED_SECRET";
|
||||||
|
// KDF label for BQP confirmation key derivation
|
||||||
|
private static final byte[] CONFIRMATION_KEY = ascii("CONFIRMATION_KEY");
|
||||||
|
// KDF label for master key derivation
|
||||||
|
private static final byte[] MASTER_KEY = ascii("MASTER_KEY");
|
||||||
|
// KDF labels for tag key derivation
|
||||||
|
private static final byte[] A_TAG = ascii("ALICE_TAG_KEY");
|
||||||
|
private static final byte[] B_TAG = ascii("BOB_TAG_KEY");
|
||||||
|
// KDF labels for header key derivation
|
||||||
|
private static final byte[] A_HEADER = ascii("ALICE_HEADER_KEY");
|
||||||
|
private static final byte[] B_HEADER = ascii("BOB_HEADER_KEY");
|
||||||
|
// KDF labels for MAC key derivation
|
||||||
|
private static final byte[] A_MAC = ascii("ALICE_MAC_KEY");
|
||||||
|
private static final byte[] B_MAC = ascii("BOB_MAC_KEY");
|
||||||
|
// KDF label for key rotation
|
||||||
|
private static final byte[] ROTATE = ascii("ROTATE");
|
||||||
|
|
||||||
private final SecureRandom secureRandom;
|
private final SecureRandom secureRandom;
|
||||||
private final ECKeyPairGenerator agreementKeyPairGenerator;
|
private final ECKeyPairGenerator agreementKeyPairGenerator;
|
||||||
private final ECKeyPairGenerator signatureKeyPairGenerator;
|
private final ECKeyPairGenerator signatureKeyPairGenerator;
|
||||||
private final KeyParser agreementKeyParser, signatureKeyParser;
|
private final KeyParser agreementKeyParser, signatureKeyParser;
|
||||||
private final MessageEncrypter messageEncrypter;
|
private final MessageEncrypter messageEncrypter;
|
||||||
private final KeyPairGenerator edKeyPairGenerator;
|
|
||||||
private final KeyParser edKeyParser;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
CryptoComponentImpl(SecureRandomProvider secureRandomProvider) {
|
CryptoComponentImpl(SecureRandomProvider secureRandomProvider) {
|
||||||
@@ -95,9 +132,6 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
signatureKeyParser = new Sec1KeyParser(PARAMETERS,
|
signatureKeyParser = new Sec1KeyParser(PARAMETERS,
|
||||||
SIGNATURE_KEY_PAIR_BITS);
|
SIGNATURE_KEY_PAIR_BITS);
|
||||||
messageEncrypter = new MessageEncrypter(secureRandom);
|
messageEncrypter = new MessageEncrypter(secureRandom);
|
||||||
edKeyPairGenerator = new KeyPairGenerator();
|
|
||||||
edKeyPairGenerator.initialize(ED_KEY_PAIR_BITS, secureRandom);
|
|
||||||
edKeyParser = new EdKeyParser();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
|
// Based on https://android-developers.googleblog.com/2013/08/some-securerandom-thoughts.html
|
||||||
@@ -156,21 +190,6 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
return secret;
|
return secret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeyPair generateEdKeyPair() {
|
|
||||||
java.security.KeyPair keyPair = edKeyPairGenerator.generateKeyPair();
|
|
||||||
EdDSAPublicKey edPublicKey = (EdDSAPublicKey) keyPair.getPublic();
|
|
||||||
PublicKey publicKey = new EdPublicKey(edPublicKey.getAbyte());
|
|
||||||
EdDSAPrivateKey edPrivateKey = (EdDSAPrivateKey) keyPair.getPrivate();
|
|
||||||
PrivateKey privateKey = new EdPrivateKey(edPrivateKey.getSeed());
|
|
||||||
return new KeyPair(publicKey, privateKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeyParser getEdKeyParser() {
|
|
||||||
return edKeyParser;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyPair generateAgreementKeyPair() {
|
public KeyPair generateAgreementKeyPair() {
|
||||||
AsymmetricCipherKeyPair keyPair =
|
AsymmetricCipherKeyPair keyPair =
|
||||||
@@ -219,63 +238,197 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SecretKey deriveKey(String label, SecretKey k, byte[]... inputs) {
|
public SecretKey deriveHeaderKey(SecretKey master,
|
||||||
byte[] mac = mac(label, k, inputs);
|
boolean alice) {
|
||||||
if (mac.length != SecretKey.LENGTH) throw new IllegalStateException();
|
return new SecretKey(macKdf(master, alice ? A_INVITE : B_INVITE));
|
||||||
return new SecretKey(mac);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SecretKey deriveSharedSecret(String label, PublicKey theirPublicKey,
|
public SecretKey deriveMacKey(SecretKey master, boolean alice) {
|
||||||
KeyPair ourKeyPair, byte[]... inputs)
|
return new SecretKey(macKdf(master, alice ? A_MAC : B_MAC));
|
||||||
throws GeneralSecurityException {
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] deriveSignatureNonce(SecretKey master,
|
||||||
|
boolean alice) {
|
||||||
|
return macKdf(master, alice ? A_SIG_NONCE : B_SIG_NONCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] deriveKeyCommitment(byte[] publicKey) {
|
||||||
|
byte[] hash = hash(COMMIT, publicKey);
|
||||||
|
// The output is the first COMMIT_LENGTH bytes of the hash
|
||||||
|
byte[] commitment = new byte[COMMIT_LENGTH];
|
||||||
|
System.arraycopy(hash, 0, commitment, 0, COMMIT_LENGTH);
|
||||||
|
return commitment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecretKey deriveSharedSecret(byte[] theirPublicKey,
|
||||||
|
KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException {
|
||||||
PrivateKey ourPriv = ourKeyPair.getPrivate();
|
PrivateKey ourPriv = ourKeyPair.getPrivate();
|
||||||
byte[][] hashInputs = new byte[inputs.length + 1][];
|
PublicKey theirPub = agreementKeyParser.parsePublicKey(theirPublicKey);
|
||||||
hashInputs[0] = performRawKeyAgreement(ourPriv, theirPublicKey);
|
byte[] raw = performRawKeyAgreement(ourPriv, theirPub);
|
||||||
System.arraycopy(inputs, 0, hashInputs, 1, inputs.length);
|
byte[] alicePub, bobPub;
|
||||||
byte[] hash = hash(label, hashInputs);
|
if (alice) {
|
||||||
if (hash.length != SecretKey.LENGTH) throw new IllegalStateException();
|
alicePub = ourKeyPair.getPublic().getEncoded();
|
||||||
return new SecretKey(hash);
|
bobPub = theirPublicKey;
|
||||||
|
} else {
|
||||||
|
alicePub = theirPublicKey;
|
||||||
|
bobPub = ourKeyPair.getPublic().getEncoded();
|
||||||
|
}
|
||||||
|
return new SecretKey(hash(SHARED_SECRET, raw, alicePub, bobPub));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] deriveConfirmationRecord(SecretKey sharedSecret,
|
||||||
|
byte[] theirPayload, byte[] ourPayload, byte[] theirPublicKey,
|
||||||
|
KeyPair ourKeyPair, boolean alice, boolean aliceRecord) {
|
||||||
|
SecretKey ck = new SecretKey(macKdf(sharedSecret, CONFIRMATION_KEY));
|
||||||
|
byte[] alicePayload, alicePub, bobPayload, bobPub;
|
||||||
|
if (alice) {
|
||||||
|
alicePayload = ourPayload;
|
||||||
|
alicePub = ourKeyPair.getPublic().getEncoded();
|
||||||
|
bobPayload = theirPayload;
|
||||||
|
bobPub = theirPublicKey;
|
||||||
|
} else {
|
||||||
|
alicePayload = theirPayload;
|
||||||
|
alicePub = theirPublicKey;
|
||||||
|
bobPayload = ourPayload;
|
||||||
|
bobPub = ourKeyPair.getPublic().getEncoded();
|
||||||
|
}
|
||||||
|
if (aliceRecord)
|
||||||
|
return macKdf(ck, alicePayload, alicePub, bobPayload, bobPub);
|
||||||
|
else
|
||||||
|
return macKdf(ck, bobPayload, bobPub, alicePayload, alicePub);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecretKey deriveMasterSecret(SecretKey sharedSecret) {
|
||||||
|
return new SecretKey(macKdf(sharedSecret, MASTER_KEY));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecretKey deriveMasterSecret(byte[] theirPublicKey,
|
||||||
|
KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException {
|
||||||
|
return deriveMasterSecret(deriveSharedSecret(
|
||||||
|
theirPublicKey, ourKeyPair, alice));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransportKeys deriveTransportKeys(TransportId t,
|
||||||
|
SecretKey master, long rotationPeriod, boolean alice) {
|
||||||
|
// Keys for the previous period are derived from the master secret
|
||||||
|
SecretKey inTagPrev = deriveTagKey(master, t, !alice);
|
||||||
|
SecretKey inHeaderPrev = deriveHeaderKey(master, t, !alice);
|
||||||
|
SecretKey outTagPrev = deriveTagKey(master, t, alice);
|
||||||
|
SecretKey outHeaderPrev = deriveHeaderKey(master, t, alice);
|
||||||
|
// Derive the keys for the current and next periods
|
||||||
|
SecretKey inTagCurr = rotateKey(inTagPrev, rotationPeriod);
|
||||||
|
SecretKey inHeaderCurr = rotateKey(inHeaderPrev, rotationPeriod);
|
||||||
|
SecretKey inTagNext = rotateKey(inTagCurr, rotationPeriod + 1);
|
||||||
|
SecretKey inHeaderNext = rotateKey(inHeaderCurr, rotationPeriod + 1);
|
||||||
|
SecretKey outTagCurr = rotateKey(outTagPrev, rotationPeriod);
|
||||||
|
SecretKey outHeaderCurr = rotateKey(outHeaderPrev, rotationPeriod);
|
||||||
|
// Initialise the reordering windows and stream counters
|
||||||
|
IncomingKeys inPrev = new IncomingKeys(inTagPrev, inHeaderPrev,
|
||||||
|
rotationPeriod - 1);
|
||||||
|
IncomingKeys inCurr = new IncomingKeys(inTagCurr, inHeaderCurr,
|
||||||
|
rotationPeriod);
|
||||||
|
IncomingKeys inNext = new IncomingKeys(inTagNext, inHeaderNext,
|
||||||
|
rotationPeriod + 1);
|
||||||
|
OutgoingKeys outCurr = new OutgoingKeys(outTagCurr, outHeaderCurr,
|
||||||
|
rotationPeriod);
|
||||||
|
// Collect and return the keys
|
||||||
|
return new TransportKeys(t, inPrev, inCurr, inNext, outCurr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransportKeys rotateTransportKeys(TransportKeys k,
|
||||||
|
long rotationPeriod) {
|
||||||
|
if (k.getRotationPeriod() >= rotationPeriod) return k;
|
||||||
|
IncomingKeys inPrev = k.getPreviousIncomingKeys();
|
||||||
|
IncomingKeys inCurr = k.getCurrentIncomingKeys();
|
||||||
|
IncomingKeys inNext = k.getNextIncomingKeys();
|
||||||
|
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
|
||||||
|
long startPeriod = outCurr.getRotationPeriod();
|
||||||
|
// Rotate the keys
|
||||||
|
for (long p = startPeriod + 1; p <= rotationPeriod; p++) {
|
||||||
|
inPrev = inCurr;
|
||||||
|
inCurr = inNext;
|
||||||
|
SecretKey inNextTag = rotateKey(inNext.getTagKey(), p + 1);
|
||||||
|
SecretKey inNextHeader = rotateKey(inNext.getHeaderKey(), p + 1);
|
||||||
|
inNext = new IncomingKeys(inNextTag, inNextHeader, p + 1);
|
||||||
|
SecretKey outCurrTag = rotateKey(outCurr.getTagKey(), p);
|
||||||
|
SecretKey outCurrHeader = rotateKey(outCurr.getHeaderKey(), p);
|
||||||
|
outCurr = new OutgoingKeys(outCurrTag, outCurrHeader, p);
|
||||||
|
}
|
||||||
|
// Collect and return the keys
|
||||||
|
return new TransportKeys(k.getTransportId(), inPrev, inCurr, inNext,
|
||||||
|
outCurr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SecretKey rotateKey(SecretKey k, long rotationPeriod) {
|
||||||
|
byte[] period = new byte[INT_64_BYTES];
|
||||||
|
ByteUtils.writeUint64(rotationPeriod, period, 0);
|
||||||
|
return new SecretKey(macKdf(k, ROTATE, period));
|
||||||
|
}
|
||||||
|
|
||||||
|
private SecretKey deriveTagKey(SecretKey master, TransportId t,
|
||||||
|
boolean alice) {
|
||||||
|
byte[] id = StringUtils.toUtf8(t.getString());
|
||||||
|
return new SecretKey(macKdf(master, alice ? A_TAG : B_TAG, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
private SecretKey deriveHeaderKey(SecretKey master, TransportId t,
|
||||||
|
boolean alice) {
|
||||||
|
byte[] id = StringUtils.toUtf8(t.getString());
|
||||||
|
return new SecretKey(macKdf(master, alice ? A_HEADER : B_HEADER, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void encodeTag(byte[] tag, SecretKey tagKey, int protocolVersion,
|
||||||
|
long streamNumber) {
|
||||||
|
if (tag.length < TAG_LENGTH) throw new IllegalArgumentException();
|
||||||
|
if (protocolVersion < 0 || protocolVersion > MAX_16_BIT_UNSIGNED)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
if (streamNumber < 0 || streamNumber > MAX_32_BIT_UNSIGNED)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
// Initialise the PRF
|
||||||
|
Digest prf = new Blake2sDigest(tagKey.getBytes());
|
||||||
|
// The output of the PRF must be long enough to use as a tag
|
||||||
|
int macLength = prf.getDigestSize();
|
||||||
|
if (macLength < TAG_LENGTH) throw new IllegalStateException();
|
||||||
|
// The input is the protocol version as a 16-bit integer, followed by
|
||||||
|
// the stream number as a 64-bit integer
|
||||||
|
byte[] protocolVersionBytes = new byte[INT_16_BYTES];
|
||||||
|
ByteUtils.writeUint16(protocolVersion, protocolVersionBytes, 0);
|
||||||
|
prf.update(protocolVersionBytes, 0, protocolVersionBytes.length);
|
||||||
|
byte[] streamNumberBytes = new byte[INT_64_BYTES];
|
||||||
|
ByteUtils.writeUint64(streamNumber, streamNumberBytes, 0);
|
||||||
|
prf.update(streamNumberBytes, 0, streamNumberBytes.length);
|
||||||
|
byte[] mac = new byte[macLength];
|
||||||
|
prf.doFinal(mac, 0);
|
||||||
|
// The output is the first TAG_LENGTH bytes of the MAC
|
||||||
|
System.arraycopy(mac, 0, tag, 0, TAG_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] sign(String label, byte[] toSign, byte[] privateKey)
|
public byte[] sign(String label, byte[] toSign, byte[] privateKey)
|
||||||
throws GeneralSecurityException {
|
throws GeneralSecurityException {
|
||||||
return sign(new SignatureImpl(secureRandom), signatureKeyParser, label,
|
Signature signature = new SignatureImpl(secureRandom);
|
||||||
toSign, privateKey);
|
KeyParser keyParser = getSignatureKeyParser();
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] signEd(String label, byte[] toSign, byte[] privateKey)
|
|
||||||
throws GeneralSecurityException {
|
|
||||||
return sign(new EdSignature(), edKeyParser, label, toSign, privateKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] sign(Signature sig, KeyParser keyParser, String label,
|
|
||||||
byte[] toSign, byte[] privateKey) throws GeneralSecurityException {
|
|
||||||
PrivateKey key = keyParser.parsePrivateKey(privateKey);
|
PrivateKey key = keyParser.parsePrivateKey(privateKey);
|
||||||
sig.initSign(key);
|
signature.initSign(key);
|
||||||
updateSignature(sig, label, toSign);
|
updateSignature(signature, label, toSign);
|
||||||
return sig.sign();
|
return signature.sign();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean verify(String label, byte[] signedData, byte[] publicKey,
|
public boolean verify(String label, byte[] signedData, byte[] publicKey,
|
||||||
byte[] signature) throws GeneralSecurityException {
|
byte[] signature) throws GeneralSecurityException {
|
||||||
return verify(new SignatureImpl(secureRandom), signatureKeyParser,
|
Signature sig = new SignatureImpl(secureRandom);
|
||||||
label, signedData, publicKey, signature);
|
KeyParser keyParser = getSignatureKeyParser();
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean verifyEd(String label, byte[] signedData, byte[] publicKey,
|
|
||||||
byte[] signature) throws GeneralSecurityException {
|
|
||||||
return verify(new EdSignature(), edKeyParser, label, signedData,
|
|
||||||
publicKey, signature);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean verify(Signature sig, KeyParser keyParser, String label,
|
|
||||||
byte[] signedData, byte[] publicKey, byte[] signature)
|
|
||||||
throws GeneralSecurityException {
|
|
||||||
PublicKey key = keyParser.parsePublicKey(publicKey);
|
PublicKey key = keyParser.parsePublicKey(publicKey);
|
||||||
sig.initVerify(key);
|
sig.initVerify(key);
|
||||||
updateSignature(sig, label, signedData);
|
updateSignature(sig, label, signedData);
|
||||||
@@ -283,7 +436,7 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateSignature(Signature signature, String label,
|
private void updateSignature(Signature signature, String label,
|
||||||
byte[] toSign) throws GeneralSecurityException {
|
byte[] toSign) {
|
||||||
byte[] labelBytes = StringUtils.toUtf8(label);
|
byte[] labelBytes = StringUtils.toUtf8(label);
|
||||||
byte[] length = new byte[INT_32_BYTES];
|
byte[] length = new byte[INT_32_BYTES];
|
||||||
ByteUtils.writeUint32(labelBytes.length, length, 0);
|
ByteUtils.writeUint32(labelBytes.length, length, 0);
|
||||||
@@ -313,13 +466,14 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] mac(String label, SecretKey macKey, byte[]... inputs) {
|
public int getHashLength() {
|
||||||
byte[] labelBytes = StringUtils.toUtf8(label);
|
return HASH_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] mac(SecretKey macKey, byte[]... inputs) {
|
||||||
Digest mac = new Blake2sDigest(macKey.getBytes());
|
Digest mac = new Blake2sDigest(macKey.getBytes());
|
||||||
byte[] length = new byte[INT_32_BYTES];
|
byte[] length = new byte[INT_32_BYTES];
|
||||||
ByteUtils.writeUint32(labelBytes.length, length, 0);
|
|
||||||
mac.update(length, 0, length.length);
|
|
||||||
mac.update(labelBytes, 0, labelBytes.length);
|
|
||||||
for (byte[] input : inputs) {
|
for (byte[] input : inputs) {
|
||||||
ByteUtils.writeUint32(input.length, length, 0);
|
ByteUtils.writeUint32(input.length, length, 0);
|
||||||
mac.update(length, 0, length.length);
|
mac.update(length, 0, length.length);
|
||||||
@@ -411,6 +565,30 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
return AsciiArmour.wrap(b, lineLength);
|
return AsciiArmour.wrap(b, lineLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Key derivation function based on a pseudo-random function - see
|
||||||
|
// NIST SP 800-108, section 5.1
|
||||||
|
private byte[] macKdf(SecretKey key, byte[]... inputs) {
|
||||||
|
// Initialise the PRF
|
||||||
|
Digest prf = new Blake2sDigest(key.getBytes());
|
||||||
|
// The output of the PRF must be long enough to use as a key
|
||||||
|
int macLength = prf.getDigestSize();
|
||||||
|
if (macLength < SecretKey.LENGTH) throw new IllegalStateException();
|
||||||
|
// Calculate the PRF over the concatenated length-prefixed inputs
|
||||||
|
byte[] length = new byte[INT_32_BYTES];
|
||||||
|
for (byte[] input : inputs) {
|
||||||
|
ByteUtils.writeUint32(input.length, length, 0);
|
||||||
|
prf.update(length, 0, length.length);
|
||||||
|
prf.update(input, 0, input.length);
|
||||||
|
}
|
||||||
|
byte[] mac = new byte[macLength];
|
||||||
|
prf.doFinal(mac, 0);
|
||||||
|
// The output is the first SecretKey.LENGTH bytes of the MAC
|
||||||
|
if (mac.length == SecretKey.LENGTH) return mac;
|
||||||
|
byte[] truncated = new byte[SecretKey.LENGTH];
|
||||||
|
System.arraycopy(mac, 0, truncated, 0, truncated.length);
|
||||||
|
return truncated;
|
||||||
|
}
|
||||||
|
|
||||||
// Password-based key derivation function - see PKCS#5 v2.1, section 5.2
|
// Password-based key derivation function - see PKCS#5 v2.1, section 5.2
|
||||||
private byte[] pbkdf2(String password, byte[] salt, int iterations) {
|
private byte[] pbkdf2(String password, byte[] salt, int iterations) {
|
||||||
byte[] utf8 = StringUtils.toUtf8(password);
|
byte[] utf8 = StringUtils.toUtf8(password);
|
||||||
|
|||||||
@@ -3,11 +3,9 @@ package org.briarproject.bramble.crypto;
|
|||||||
import org.briarproject.bramble.TimeLoggingExecutor;
|
import org.briarproject.bramble.TimeLoggingExecutor;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
||||||
import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
|
|
||||||
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
|
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
|
||||||
import org.briarproject.bramble.api.crypto.StreamDecrypterFactory;
|
import org.briarproject.bramble.api.crypto.StreamDecrypterFactory;
|
||||||
import org.briarproject.bramble.api.crypto.StreamEncrypterFactory;
|
import org.briarproject.bramble.api.crypto.StreamEncrypterFactory;
|
||||||
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
||||||
|
|
||||||
@@ -76,12 +74,6 @@ public class CryptoModule {
|
|||||||
return new PasswordStrengthEstimatorImpl();
|
return new PasswordStrengthEstimatorImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
TransportCrypto provideTransportCrypto(
|
|
||||||
TransportCryptoImpl transportCrypto) {
|
|
||||||
return transportCrypto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
StreamDecrypterFactory provideStreamDecrypterFactory(
|
StreamDecrypterFactory provideStreamDecrypterFactory(
|
||||||
Provider<AuthenticatedCipher> cipherProvider) {
|
Provider<AuthenticatedCipher> cipherProvider) {
|
||||||
@@ -89,17 +81,9 @@ public class CryptoModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
StreamEncrypterFactory provideStreamEncrypterFactory(
|
StreamEncrypterFactory provideStreamEncrypterFactory(CryptoComponent crypto,
|
||||||
CryptoComponent crypto, TransportCrypto transportCrypto,
|
|
||||||
Provider<AuthenticatedCipher> cipherProvider) {
|
Provider<AuthenticatedCipher> cipherProvider) {
|
||||||
return new StreamEncrypterFactoryImpl(crypto, transportCrypto,
|
return new StreamEncrypterFactoryImpl(crypto, cipherProvider);
|
||||||
cipherProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
KeyAgreementCrypto provideKeyAgreementCrypto(
|
|
||||||
KeyAgreementCryptoImpl keyAgreementCrypto) {
|
|
||||||
return keyAgreementCrypto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
package org.briarproject.bramble.crypto;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
|
||||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
|
||||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
class EdKeyParser implements KeyParser {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PublicKey parsePublicKey(byte[] encodedKey)
|
|
||||||
throws GeneralSecurityException {
|
|
||||||
if (encodedKey.length != 32) throw new GeneralSecurityException();
|
|
||||||
return new EdPublicKey(encodedKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PrivateKey parsePrivateKey(byte[] encodedKey)
|
|
||||||
throws GeneralSecurityException {
|
|
||||||
if (encodedKey.length != 32) throw new GeneralSecurityException();
|
|
||||||
return new EdPrivateKey(encodedKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package org.briarproject.bramble.crypto;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.Bytes;
|
|
||||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
class EdPrivateKey extends Bytes implements PrivateKey {
|
|
||||||
|
|
||||||
EdPrivateKey(byte[] bytes) {
|
|
||||||
super(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getEncoded() {
|
|
||||||
return getBytes();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package org.briarproject.bramble.crypto;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.Bytes;
|
|
||||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
class EdPublicKey extends Bytes implements PublicKey {
|
|
||||||
|
|
||||||
EdPublicKey(byte[] bytes) {
|
|
||||||
super(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getEncoded() {
|
|
||||||
return getBytes();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
package org.briarproject.bramble.crypto;
|
|
||||||
|
|
||||||
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
|
|
||||||
import net.i2p.crypto.eddsa.EdDSAPublicKey;
|
|
||||||
import net.i2p.crypto.eddsa.EdDSASecurityProvider;
|
|
||||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec;
|
|
||||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
|
|
||||||
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
|
|
||||||
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
|
||||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.Provider;
|
|
||||||
|
|
||||||
import static net.i2p.crypto.eddsa.EdDSAEngine.SIGNATURE_ALGORITHM;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
class EdSignature implements Signature {
|
|
||||||
|
|
||||||
private static final Provider PROVIDER = new EdDSASecurityProvider();
|
|
||||||
|
|
||||||
private static final EdDSANamedCurveSpec CURVE_SPEC =
|
|
||||||
EdDSANamedCurveTable.getByName("Ed25519");
|
|
||||||
|
|
||||||
private final java.security.Signature signature;
|
|
||||||
|
|
||||||
EdSignature() {
|
|
||||||
try {
|
|
||||||
signature = java.security.Signature
|
|
||||||
.getInstance(SIGNATURE_ALGORITHM, PROVIDER);
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initSign(PrivateKey k) throws GeneralSecurityException {
|
|
||||||
if (!(k instanceof EdPrivateKey))
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
EdDSAPrivateKey privateKey = new EdDSAPrivateKey(
|
|
||||||
new EdDSAPrivateKeySpec(k.getEncoded(), CURVE_SPEC));
|
|
||||||
signature.initSign(privateKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initVerify(PublicKey k) throws GeneralSecurityException {
|
|
||||||
if (!(k instanceof EdPublicKey))
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
EdDSAPublicKey publicKey = new EdDSAPublicKey(
|
|
||||||
new EdDSAPublicKeySpec(k.getEncoded(), CURVE_SPEC));
|
|
||||||
signature.initVerify(publicKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update(byte b) throws GeneralSecurityException {
|
|
||||||
signature.update(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update(byte[] b) throws GeneralSecurityException {
|
|
||||||
signature.update(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update(byte[] b, int off, int len)
|
|
||||||
throws GeneralSecurityException {
|
|
||||||
signature.update(b, off, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] sign() throws GeneralSecurityException {
|
|
||||||
return signature.sign();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean verify(byte[] sig) throws GeneralSecurityException {
|
|
||||||
return signature.verify(sig);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
package org.briarproject.bramble.crypto;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
|
||||||
import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
|
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
|
||||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
|
|
||||||
|
|
||||||
class KeyAgreementCryptoImpl implements KeyAgreementCrypto {
|
|
||||||
|
|
||||||
private final CryptoComponent crypto;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
KeyAgreementCryptoImpl(CryptoComponent crypto) {
|
|
||||||
this.crypto = crypto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] deriveKeyCommitment(PublicKey publicKey) {
|
|
||||||
byte[] hash = crypto.hash(COMMIT_LABEL, publicKey.getEncoded());
|
|
||||||
// The output is the first COMMIT_LENGTH bytes of the hash
|
|
||||||
byte[] commitment = new byte[COMMIT_LENGTH];
|
|
||||||
System.arraycopy(hash, 0, commitment, 0, COMMIT_LENGTH);
|
|
||||||
return commitment;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] deriveConfirmationRecord(SecretKey sharedSecret,
|
|
||||||
byte[] theirPayload, byte[] ourPayload, PublicKey theirPublicKey,
|
|
||||||
KeyPair ourKeyPair, boolean alice, boolean aliceRecord) {
|
|
||||||
SecretKey ck = crypto.deriveKey(CONFIRMATION_KEY_LABEL, sharedSecret);
|
|
||||||
byte[] alicePayload, alicePub, bobPayload, bobPub;
|
|
||||||
if (alice) {
|
|
||||||
alicePayload = ourPayload;
|
|
||||||
alicePub = ourKeyPair.getPublic().getEncoded();
|
|
||||||
bobPayload = theirPayload;
|
|
||||||
bobPub = theirPublicKey.getEncoded();
|
|
||||||
} else {
|
|
||||||
alicePayload = theirPayload;
|
|
||||||
alicePub = theirPublicKey.getEncoded();
|
|
||||||
bobPayload = ourPayload;
|
|
||||||
bobPub = ourKeyPair.getPublic().getEncoded();
|
|
||||||
}
|
|
||||||
if (aliceRecord) {
|
|
||||||
return crypto.mac(CONFIRMATION_MAC_LABEL, ck, alicePayload,
|
|
||||||
alicePub, bobPayload, bobPub);
|
|
||||||
} else {
|
|
||||||
return crypto.mac(CONFIRMATION_MAC_LABEL, ck, bobPayload, bobPub,
|
|
||||||
alicePayload, alicePub);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -22,25 +22,25 @@ interface Signature {
|
|||||||
/**
|
/**
|
||||||
* @see {@link java.security.Signature#update(byte)}
|
* @see {@link java.security.Signature#update(byte)}
|
||||||
*/
|
*/
|
||||||
void update(byte b) throws GeneralSecurityException;
|
void update(byte b);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see {@link java.security.Signature#update(byte[])}
|
* @see {@link java.security.Signature#update(byte[])}
|
||||||
*/
|
*/
|
||||||
void update(byte[] b) throws GeneralSecurityException;
|
void update(byte[] b);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see {@link java.security.Signature#update(byte[], int, int)}
|
* @see {@link java.security.Signature#update(byte[], int, int)}
|
||||||
*/
|
*/
|
||||||
void update(byte[] b, int off, int len) throws GeneralSecurityException;
|
void update(byte[] b, int off, int len);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see {@link java.security.Signature#sign()}
|
* @see {@link java.security.Signature#sign()}
|
||||||
*/
|
*/
|
||||||
byte[] sign() throws GeneralSecurityException;
|
byte[] sign();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see {@link java.security.Signature#verify(byte[])}
|
* @see {@link java.security.Signature#verify(byte[])}
|
||||||
*/
|
*/
|
||||||
boolean verify(byte[] signature) throws GeneralSecurityException;
|
boolean verify(byte[] signature);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import org.briarproject.bramble.api.crypto.CryptoComponent;
|
|||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.api.crypto.StreamEncrypter;
|
import org.briarproject.bramble.api.crypto.StreamEncrypter;
|
||||||
import org.briarproject.bramble.api.crypto.StreamEncrypterFactory;
|
import org.briarproject.bramble.api.crypto.StreamEncrypterFactory;
|
||||||
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.transport.StreamContext;
|
import org.briarproject.bramble.api.transport.StreamContext;
|
||||||
|
|
||||||
@@ -23,15 +22,12 @@ import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENG
|
|||||||
class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
|
class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
|
||||||
|
|
||||||
private final CryptoComponent crypto;
|
private final CryptoComponent crypto;
|
||||||
private final TransportCrypto transportCrypto;
|
|
||||||
private final Provider<AuthenticatedCipher> cipherProvider;
|
private final Provider<AuthenticatedCipher> cipherProvider;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
StreamEncrypterFactoryImpl(CryptoComponent crypto,
|
StreamEncrypterFactoryImpl(CryptoComponent crypto,
|
||||||
TransportCrypto transportCrypto,
|
|
||||||
Provider<AuthenticatedCipher> cipherProvider) {
|
Provider<AuthenticatedCipher> cipherProvider) {
|
||||||
this.crypto = crypto;
|
this.crypto = crypto;
|
||||||
this.transportCrypto = transportCrypto;
|
|
||||||
this.cipherProvider = cipherProvider;
|
this.cipherProvider = cipherProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,8 +37,7 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
|
|||||||
AuthenticatedCipher cipher = cipherProvider.get();
|
AuthenticatedCipher cipher = cipherProvider.get();
|
||||||
long streamNumber = ctx.getStreamNumber();
|
long streamNumber = ctx.getStreamNumber();
|
||||||
byte[] tag = new byte[TAG_LENGTH];
|
byte[] tag = new byte[TAG_LENGTH];
|
||||||
transportCrypto.encodeTag(tag, ctx.getTagKey(), PROTOCOL_VERSION,
|
crypto.encodeTag(tag, ctx.getTagKey(), PROTOCOL_VERSION, streamNumber);
|
||||||
streamNumber);
|
|
||||||
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
|
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
|
||||||
crypto.getSecureRandom().nextBytes(streamHeaderNonce);
|
crypto.getSecureRandom().nextBytes(streamHeaderNonce);
|
||||||
SecretKey frameKey = crypto.generateSecretKey();
|
SecretKey frameKey = crypto.generateSecretKey();
|
||||||
|
|||||||
@@ -1,135 +0,0 @@
|
|||||||
package org.briarproject.bramble.crypto;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
|
||||||
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
|
||||||
import org.briarproject.bramble.api.transport.IncomingKeys;
|
|
||||||
import org.briarproject.bramble.api.transport.OutgoingKeys;
|
|
||||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
|
||||||
import org.briarproject.bramble.util.ByteUtils;
|
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
|
||||||
import org.spongycastle.crypto.Digest;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.ALICE_HEADER_LABEL;
|
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.ALICE_TAG_LABEL;
|
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.BOB_HEADER_LABEL;
|
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.BOB_TAG_LABEL;
|
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.ROTATE_LABEL;
|
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
|
||||||
import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
|
|
||||||
import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
|
|
||||||
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
|
|
||||||
import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
|
||||||
|
|
||||||
class TransportCryptoImpl implements TransportCrypto {
|
|
||||||
|
|
||||||
private final CryptoComponent crypto;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
TransportCryptoImpl(CryptoComponent crypto) {
|
|
||||||
this.crypto = crypto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TransportKeys deriveTransportKeys(TransportId t,
|
|
||||||
SecretKey master, long rotationPeriod, boolean alice) {
|
|
||||||
// Keys for the previous period are derived from the master secret
|
|
||||||
SecretKey inTagPrev = deriveTagKey(master, t, !alice);
|
|
||||||
SecretKey inHeaderPrev = deriveHeaderKey(master, t, !alice);
|
|
||||||
SecretKey outTagPrev = deriveTagKey(master, t, alice);
|
|
||||||
SecretKey outHeaderPrev = deriveHeaderKey(master, t, alice);
|
|
||||||
// Derive the keys for the current and next periods
|
|
||||||
SecretKey inTagCurr = rotateKey(inTagPrev, rotationPeriod);
|
|
||||||
SecretKey inHeaderCurr = rotateKey(inHeaderPrev, rotationPeriod);
|
|
||||||
SecretKey inTagNext = rotateKey(inTagCurr, rotationPeriod + 1);
|
|
||||||
SecretKey inHeaderNext = rotateKey(inHeaderCurr, rotationPeriod + 1);
|
|
||||||
SecretKey outTagCurr = rotateKey(outTagPrev, rotationPeriod);
|
|
||||||
SecretKey outHeaderCurr = rotateKey(outHeaderPrev, rotationPeriod);
|
|
||||||
// Initialise the reordering windows and stream counters
|
|
||||||
IncomingKeys inPrev = new IncomingKeys(inTagPrev, inHeaderPrev,
|
|
||||||
rotationPeriod - 1);
|
|
||||||
IncomingKeys inCurr = new IncomingKeys(inTagCurr, inHeaderCurr,
|
|
||||||
rotationPeriod);
|
|
||||||
IncomingKeys inNext = new IncomingKeys(inTagNext, inHeaderNext,
|
|
||||||
rotationPeriod + 1);
|
|
||||||
OutgoingKeys outCurr = new OutgoingKeys(outTagCurr, outHeaderCurr,
|
|
||||||
rotationPeriod);
|
|
||||||
// Collect and return the keys
|
|
||||||
return new TransportKeys(t, inPrev, inCurr, inNext, outCurr);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TransportKeys rotateTransportKeys(TransportKeys k,
|
|
||||||
long rotationPeriod) {
|
|
||||||
if (k.getRotationPeriod() >= rotationPeriod) return k;
|
|
||||||
IncomingKeys inPrev = k.getPreviousIncomingKeys();
|
|
||||||
IncomingKeys inCurr = k.getCurrentIncomingKeys();
|
|
||||||
IncomingKeys inNext = k.getNextIncomingKeys();
|
|
||||||
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
|
|
||||||
long startPeriod = outCurr.getRotationPeriod();
|
|
||||||
// Rotate the keys
|
|
||||||
for (long p = startPeriod + 1; p <= rotationPeriod; p++) {
|
|
||||||
inPrev = inCurr;
|
|
||||||
inCurr = inNext;
|
|
||||||
SecretKey inNextTag = rotateKey(inNext.getTagKey(), p + 1);
|
|
||||||
SecretKey inNextHeader = rotateKey(inNext.getHeaderKey(), p + 1);
|
|
||||||
inNext = new IncomingKeys(inNextTag, inNextHeader, p + 1);
|
|
||||||
SecretKey outCurrTag = rotateKey(outCurr.getTagKey(), p);
|
|
||||||
SecretKey outCurrHeader = rotateKey(outCurr.getHeaderKey(), p);
|
|
||||||
outCurr = new OutgoingKeys(outCurrTag, outCurrHeader, p);
|
|
||||||
}
|
|
||||||
// Collect and return the keys
|
|
||||||
return new TransportKeys(k.getTransportId(), inPrev, inCurr, inNext,
|
|
||||||
outCurr);
|
|
||||||
}
|
|
||||||
|
|
||||||
private SecretKey rotateKey(SecretKey k, long rotationPeriod) {
|
|
||||||
byte[] period = new byte[INT_64_BYTES];
|
|
||||||
ByteUtils.writeUint64(rotationPeriod, period, 0);
|
|
||||||
return crypto.deriveKey(ROTATE_LABEL, k, period);
|
|
||||||
}
|
|
||||||
|
|
||||||
private SecretKey deriveTagKey(SecretKey master, TransportId t,
|
|
||||||
boolean alice) {
|
|
||||||
String label = alice ? ALICE_TAG_LABEL : BOB_TAG_LABEL;
|
|
||||||
byte[] id = StringUtils.toUtf8(t.getString());
|
|
||||||
return crypto.deriveKey(label, master, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private SecretKey deriveHeaderKey(SecretKey master, TransportId t,
|
|
||||||
boolean alice) {
|
|
||||||
String label = alice ? ALICE_HEADER_LABEL : BOB_HEADER_LABEL;
|
|
||||||
byte[] id = StringUtils.toUtf8(t.getString());
|
|
||||||
return crypto.deriveKey(label, master, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void encodeTag(byte[] tag, SecretKey tagKey, int protocolVersion,
|
|
||||||
long streamNumber) {
|
|
||||||
if (tag.length < TAG_LENGTH) throw new IllegalArgumentException();
|
|
||||||
if (protocolVersion < 0 || protocolVersion > MAX_16_BIT_UNSIGNED)
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
if (streamNumber < 0 || streamNumber > MAX_32_BIT_UNSIGNED)
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
// Initialise the PRF
|
|
||||||
Digest prf = new Blake2sDigest(tagKey.getBytes());
|
|
||||||
// The output of the PRF must be long enough to use as a tag
|
|
||||||
int macLength = prf.getDigestSize();
|
|
||||||
if (macLength < TAG_LENGTH) throw new IllegalStateException();
|
|
||||||
// The input is the protocol version as a 16-bit integer, followed by
|
|
||||||
// the stream number as a 64-bit integer
|
|
||||||
byte[] protocolVersionBytes = new byte[INT_16_BYTES];
|
|
||||||
ByteUtils.writeUint16(protocolVersion, protocolVersionBytes, 0);
|
|
||||||
prf.update(protocolVersionBytes, 0, protocolVersionBytes.length);
|
|
||||||
byte[] streamNumberBytes = new byte[INT_64_BYTES];
|
|
||||||
ByteUtils.writeUint64(streamNumber, streamNumberBytes, 0);
|
|
||||||
prf.update(streamNumberBytes, 0, streamNumberBytes.length);
|
|
||||||
byte[] mac = new byte[macLength];
|
|
||||||
prf.doFinal(mac, 0);
|
|
||||||
// The output is the first TAG_LENGTH bytes of the MAC
|
|
||||||
System.arraycopy(mac, 0, tag, 0, TAG_LENGTH);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,8 @@ 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.db.DataTooNewException;
|
||||||
|
import org.briarproject.bramble.api.db.DataTooOldException;
|
||||||
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.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
@@ -37,6 +39,11 @@ interface Database<T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the database and returns true if the database already existed.
|
* Opens the database and returns true if the database already existed.
|
||||||
|
*
|
||||||
|
* @throws DataTooNewException if the data uses a newer schema than the
|
||||||
|
* current code
|
||||||
|
* @throws DataTooOldException if the data uses an older schema than the
|
||||||
|
* current code and cannot be migrated
|
||||||
*/
|
*/
|
||||||
boolean open() throws DbException;
|
boolean open() throws DbException;
|
||||||
|
|
||||||
|
|||||||
@@ -23,10 +23,4 @@ interface DatabaseConstants {
|
|||||||
*/
|
*/
|
||||||
String SCHEMA_VERSION_KEY = "schemaVersion";
|
String SCHEMA_VERSION_KEY = "schemaVersion";
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@link Settings} key under which the minimum supported database
|
|
||||||
* schema version is stored.
|
|
||||||
*/
|
|
||||||
String MIN_SCHEMA_VERSION_KEY = "minSchemaVersion";
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,23 +22,21 @@ import javax.inject.Inject;
|
|||||||
class H2Database extends JdbcDatabase {
|
class H2Database extends JdbcDatabase {
|
||||||
|
|
||||||
private static final String HASH_TYPE = "BINARY(32)";
|
private static final String HASH_TYPE = "BINARY(32)";
|
||||||
private static final String SECRET_TYPE = "BINARY(32)";
|
|
||||||
private static final String BINARY_TYPE = "BINARY";
|
private static final String BINARY_TYPE = "BINARY";
|
||||||
private static final String COUNTER_TYPE = "INT NOT NULL AUTO_INCREMENT";
|
private static final String COUNTER_TYPE = "INT NOT NULL AUTO_INCREMENT";
|
||||||
private static final String STRING_TYPE = "VARCHAR";
|
private static final String SECRET_TYPE = "BINARY(32)";
|
||||||
|
|
||||||
private final DatabaseConfig config;
|
private final DatabaseConfig config;
|
||||||
private final String url;
|
private final String url;
|
||||||
|
|
||||||
@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, BINARY_TYPE, COUNTER_TYPE, SECRET_TYPE, clock);
|
||||||
clock);
|
|
||||||
this.config = config;
|
this.config = config;
|
||||||
File dir = config.getDatabaseDirectory();
|
File dir = config.getDatabaseDirectory();
|
||||||
String path = new File(dir, "db").getAbsolutePath();
|
String path = new File(dir, "db").getAbsolutePath();
|
||||||
url = "jdbc:h2:split:" + path + ";CIPHER=AES;MULTI_THREADED=1"
|
url = "jdbc:h2:split:" + path + ";CIPHER=AES;MULTI_THREADED=1"
|
||||||
+ ";WRITE_DELAY=0";
|
+ ";WRITE_DELAY=0;DB_CLOSE_ON_EXIT=false";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
package org.briarproject.bramble.db;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseConfig;
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.DriverManager;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.sql.Statement;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains all the HSQLDB-specific code for the database.
|
|
||||||
*/
|
|
||||||
@NotNullByDefault
|
|
||||||
class HyperSqlDatabase extends JdbcDatabase {
|
|
||||||
|
|
||||||
private static final String HASH_TYPE = "BINARY(32)";
|
|
||||||
private static final String SECRET_TYPE = "BINARY(32)";
|
|
||||||
private static final String BINARY_TYPE = "BINARY";
|
|
||||||
private static final String COUNTER_TYPE =
|
|
||||||
"INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY(START WITH 1)";
|
|
||||||
private static final String STRING_TYPE = "VARCHAR";
|
|
||||||
|
|
||||||
private final DatabaseConfig config;
|
|
||||||
private final String url;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
HyperSqlDatabase(DatabaseConfig config, Clock clock) {
|
|
||||||
super(HASH_TYPE, SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE,
|
|
||||||
clock);
|
|
||||||
this.config = config;
|
|
||||||
File dir = config.getDatabaseDirectory();
|
|
||||||
String path = new File(dir, "db").getAbsolutePath();
|
|
||||||
url = "jdbc:hsqldb:file:" + path
|
|
||||||
+ ";sql.enforce_size=false;allow_empty_batch=true"
|
|
||||||
+ ";encrypt_lobs=true;crypt_type=AES";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean open() throws DbException {
|
|
||||||
boolean reopen = config.databaseExists();
|
|
||||||
if (!reopen) config.getDatabaseDirectory().mkdirs();
|
|
||||||
super.open("org.hsqldb.jdbc.JDBCDriver", reopen);
|
|
||||||
return reopen;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws DbException {
|
|
||||||
try {
|
|
||||||
super.closeAllConnections();
|
|
||||||
Connection c = createConnection();
|
|
||||||
Statement s = c.createStatement();
|
|
||||||
s.executeQuery("SHUTDOWN");
|
|
||||||
s.close();
|
|
||||||
c.close();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
throw new DbException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getFreeSpace() throws DbException {
|
|
||||||
File dir = config.getDatabaseDirectory();
|
|
||||||
long maxSize = config.getMaxSize();
|
|
||||||
long free = dir.getFreeSpace();
|
|
||||||
long used = getDiskSpace(dir);
|
|
||||||
long quota = maxSize - used;
|
|
||||||
return Math.min(free, quota);
|
|
||||||
}
|
|
||||||
|
|
||||||
private long getDiskSpace(File f) {
|
|
||||||
if (f.isDirectory()) {
|
|
||||||
long total = 0;
|
|
||||||
File[] children = f.listFiles();
|
|
||||||
if (children != null)
|
|
||||||
for (File child : children) total += getDiskSpace(child);
|
|
||||||
return total;
|
|
||||||
} else if (f.isFile()) {
|
|
||||||
return f.length();
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Connection createConnection() throws SQLException {
|
|
||||||
SecretKey key = config.getEncryptionKey();
|
|
||||||
if (key == null) throw new IllegalStateException();
|
|
||||||
String hex = StringUtils.toHexString(key.getBytes());
|
|
||||||
return DriverManager.getConnection(url + ";crypt_key=" + hex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,8 @@ 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.crypto.SecretKey;
|
||||||
|
import org.briarproject.bramble.api.db.DataTooNewException;
|
||||||
|
import org.briarproject.bramble.api.db.DataTooOldException;
|
||||||
import org.briarproject.bramble.api.db.DbClosedException;
|
import org.briarproject.bramble.api.db.DbClosedException;
|
||||||
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;
|
||||||
@@ -47,6 +49,7 @@ import java.util.logging.Logger;
|
|||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
|
import static org.briarproject.bramble.api.db.Metadata.REMOVE;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||||
@@ -57,7 +60,6 @@ import static org.briarproject.bramble.api.sync.ValidationManager.State.INVALID;
|
|||||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.PENDING;
|
import static org.briarproject.bramble.api.sync.ValidationManager.State.PENDING;
|
||||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
|
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
|
||||||
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.MIN_SCHEMA_VERSION_KEY;
|
|
||||||
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.ExponentialBackoff.calculateExpiry;
|
import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
|
||||||
|
|
||||||
@@ -68,32 +70,32 @@ import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
abstract class JdbcDatabase implements Database<Connection> {
|
abstract class JdbcDatabase implements Database<Connection> {
|
||||||
|
|
||||||
private static final int SCHEMA_VERSION = 31;
|
// Package access for testing
|
||||||
private static final int MIN_SCHEMA_VERSION = 31;
|
static final int CODE_SCHEMA_VERSION = 31;
|
||||||
|
|
||||||
private static final String CREATE_SETTINGS =
|
private static final String CREATE_SETTINGS =
|
||||||
"CREATE TABLE settings"
|
"CREATE TABLE settings"
|
||||||
+ " (namespace _STRING NOT NULL,"
|
+ " (namespace VARCHAR NOT NULL,"
|
||||||
+ " settingKey _STRING NOT NULL,"
|
+ " key VARCHAR NOT NULL,"
|
||||||
+ " value _STRING NOT NULL,"
|
+ " value VARCHAR NOT NULL,"
|
||||||
+ " PRIMARY KEY (namespace, settingKey))";
|
+ " PRIMARY KEY (namespace, key))";
|
||||||
|
|
||||||
private static final String CREATE_LOCAL_AUTHORS =
|
private static final String CREATE_LOCAL_AUTHORS =
|
||||||
"CREATE TABLE localAuthors"
|
"CREATE TABLE localAuthors"
|
||||||
+ " (authorId _HASH NOT NULL,"
|
+ " (authorId HASH NOT NULL,"
|
||||||
+ " name _STRING NOT NULL,"
|
+ " name VARCHAR NOT NULL,"
|
||||||
+ " publicKey _BINARY NOT NULL,"
|
+ " publicKey BINARY NOT NULL,"
|
||||||
+ " privateKey _BINARY NOT NULL,"
|
+ " privateKey BINARY NOT NULL,"
|
||||||
+ " created BIGINT NOT NULL,"
|
+ " created BIGINT NOT NULL,"
|
||||||
+ " PRIMARY KEY (authorId))";
|
+ " PRIMARY KEY (authorId))";
|
||||||
|
|
||||||
private static final String CREATE_CONTACTS =
|
private static final String CREATE_CONTACTS =
|
||||||
"CREATE TABLE contacts"
|
"CREATE TABLE contacts"
|
||||||
+ " (contactId _COUNTER,"
|
+ " (contactId COUNTER,"
|
||||||
+ " authorId _HASH NOT NULL,"
|
+ " authorId HASH NOT NULL,"
|
||||||
+ " name _STRING NOT NULL,"
|
+ " name VARCHAR NOT NULL,"
|
||||||
+ " publicKey _BINARY NOT NULL,"
|
+ " publicKey BINARY NOT NULL,"
|
||||||
+ " localAuthorId _HASH NOT NULL,"
|
+ " localAuthorId HASH NOT NULL,"
|
||||||
+ " verified BOOLEAN NOT NULL,"
|
+ " verified BOOLEAN NOT NULL,"
|
||||||
+ " active BOOLEAN NOT NULL,"
|
+ " active BOOLEAN NOT NULL,"
|
||||||
+ " PRIMARY KEY (contactId),"
|
+ " PRIMARY KEY (contactId),"
|
||||||
@@ -103,17 +105,17 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
|
|
||||||
private static final String CREATE_GROUPS =
|
private static final String CREATE_GROUPS =
|
||||||
"CREATE TABLE groups"
|
"CREATE TABLE groups"
|
||||||
+ " (groupId _HASH NOT NULL,"
|
+ " (groupId HASH NOT NULL,"
|
||||||
+ " clientId _STRING NOT NULL,"
|
+ " clientId VARCHAR NOT NULL,"
|
||||||
+ " descriptor _BINARY NOT NULL,"
|
+ " descriptor BINARY NOT NULL,"
|
||||||
+ " PRIMARY KEY (groupId))";
|
+ " PRIMARY KEY (groupId))";
|
||||||
|
|
||||||
private static final String CREATE_GROUP_METADATA =
|
private static final String CREATE_GROUP_METADATA =
|
||||||
"CREATE TABLE groupMetadata"
|
"CREATE TABLE groupMetadata"
|
||||||
+ " (groupId _HASH NOT NULL,"
|
+ " (groupId HASH NOT NULL,"
|
||||||
+ " metaKey _STRING NOT NULL,"
|
+ " key VARCHAR NOT NULL,"
|
||||||
+ " value _BINARY NOT NULL,"
|
+ " value BINARY NOT NULL,"
|
||||||
+ " PRIMARY KEY (groupId, metaKey),"
|
+ " PRIMARY KEY (groupId, key),"
|
||||||
+ " FOREIGN KEY (groupId)"
|
+ " FOREIGN KEY (groupId)"
|
||||||
+ " REFERENCES groups (groupId)"
|
+ " REFERENCES groups (groupId)"
|
||||||
+ " ON DELETE CASCADE)";
|
+ " ON DELETE CASCADE)";
|
||||||
@@ -121,7 +123,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
private static final String CREATE_GROUP_VISIBILITIES =
|
private static final String CREATE_GROUP_VISIBILITIES =
|
||||||
"CREATE TABLE groupVisibilities"
|
"CREATE TABLE groupVisibilities"
|
||||||
+ " (contactId INT NOT NULL,"
|
+ " (contactId INT NOT NULL,"
|
||||||
+ " groupId _HASH NOT NULL,"
|
+ " groupId HASH NOT NULL,"
|
||||||
+ " shared BOOLEAN NOT NULL,"
|
+ " shared BOOLEAN NOT NULL,"
|
||||||
+ " PRIMARY KEY (contactId, groupId),"
|
+ " PRIMARY KEY (contactId, groupId),"
|
||||||
+ " FOREIGN KEY (contactId)"
|
+ " FOREIGN KEY (contactId)"
|
||||||
@@ -133,8 +135,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
|
|
||||||
private static final String CREATE_MESSAGES =
|
private static final String CREATE_MESSAGES =
|
||||||
"CREATE TABLE messages"
|
"CREATE TABLE messages"
|
||||||
+ " (messageId _HASH NOT NULL,"
|
+ " (messageId HASH NOT NULL,"
|
||||||
+ " groupId _HASH NOT NULL,"
|
+ " groupId HASH NOT NULL,"
|
||||||
+ " timestamp BIGINT NOT NULL,"
|
+ " timestamp BIGINT NOT NULL,"
|
||||||
+ " state INT NOT NULL,"
|
+ " state INT NOT NULL,"
|
||||||
+ " shared BOOLEAN NOT NULL,"
|
+ " shared BOOLEAN NOT NULL,"
|
||||||
@@ -147,19 +149,24 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
|
|
||||||
private static final String CREATE_MESSAGE_METADATA =
|
private static final String CREATE_MESSAGE_METADATA =
|
||||||
"CREATE TABLE messageMetadata"
|
"CREATE TABLE messageMetadata"
|
||||||
+ " (messageId _HASH NOT NULL,"
|
+ " (messageId HASH NOT NULL,"
|
||||||
+ " metaKey _STRING NOT NULL,"
|
+ " groupId HASH NOT NULL," // Denormalised
|
||||||
+ " value _BINARY NOT NULL,"
|
+ " state INT NOT NULL," // Denormalised
|
||||||
+ " PRIMARY KEY (messageId, metaKey),"
|
+ " key VARCHAR NOT NULL,"
|
||||||
|
+ " value BINARY NOT NULL,"
|
||||||
|
+ " PRIMARY KEY (messageId, key),"
|
||||||
+ " FOREIGN KEY (messageId)"
|
+ " FOREIGN KEY (messageId)"
|
||||||
+ " REFERENCES messages (messageId)"
|
+ " REFERENCES messages (messageId)"
|
||||||
|
+ " ON DELETE CASCADE,"
|
||||||
|
+ " FOREIGN KEY (groupId)"
|
||||||
|
+ " REFERENCES groups (groupId)"
|
||||||
+ " ON DELETE CASCADE)";
|
+ " ON DELETE CASCADE)";
|
||||||
|
|
||||||
private static final String CREATE_MESSAGE_DEPENDENCIES =
|
private static final String CREATE_MESSAGE_DEPENDENCIES =
|
||||||
"CREATE TABLE messageDependencies"
|
"CREATE TABLE messageDependencies"
|
||||||
+ " (groupId _HASH NOT NULL,"
|
+ " (groupId HASH NOT NULL,"
|
||||||
+ " messageId _HASH NOT NULL,"
|
+ " messageId HASH NOT NULL,"
|
||||||
+ " dependencyId _HASH NOT NULL," // Not a foreign key
|
+ " dependencyId HASH NOT NULL," // Not a foreign key
|
||||||
+ " FOREIGN KEY (groupId)"
|
+ " FOREIGN KEY (groupId)"
|
||||||
+ " REFERENCES groups (groupId)"
|
+ " REFERENCES groups (groupId)"
|
||||||
+ " ON DELETE CASCADE,"
|
+ " ON DELETE CASCADE,"
|
||||||
@@ -169,7 +176,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
|
|
||||||
private static final String CREATE_OFFERS =
|
private static final String CREATE_OFFERS =
|
||||||
"CREATE TABLE offers"
|
"CREATE TABLE offers"
|
||||||
+ " (messageId _HASH NOT NULL," // Not a foreign key
|
+ " (messageId HASH NOT NULL," // Not a foreign key
|
||||||
+ " contactId INT NOT NULL,"
|
+ " contactId INT NOT NULL,"
|
||||||
+ " PRIMARY KEY (messageId, contactId),"
|
+ " PRIMARY KEY (messageId, contactId),"
|
||||||
+ " FOREIGN KEY (contactId)"
|
+ " FOREIGN KEY (contactId)"
|
||||||
@@ -178,7 +185,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
|
|
||||||
private static final String CREATE_STATUSES =
|
private static final String CREATE_STATUSES =
|
||||||
"CREATE TABLE statuses"
|
"CREATE TABLE statuses"
|
||||||
+ " (messageId _HASH NOT NULL,"
|
+ " (messageId HASH NOT NULL,"
|
||||||
+ " contactId INT NOT NULL,"
|
+ " contactId INT NOT NULL,"
|
||||||
+ " ack BOOLEAN NOT NULL,"
|
+ " ack BOOLEAN NOT NULL,"
|
||||||
+ " seen BOOLEAN NOT NULL,"
|
+ " seen BOOLEAN NOT NULL,"
|
||||||
@@ -195,20 +202,20 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
|
|
||||||
private static final String CREATE_TRANSPORTS =
|
private static final String CREATE_TRANSPORTS =
|
||||||
"CREATE TABLE transports"
|
"CREATE TABLE transports"
|
||||||
+ " (transportId _STRING NOT NULL,"
|
+ " (transportId VARCHAR NOT NULL,"
|
||||||
+ " maxLatency INT NOT NULL,"
|
+ " maxLatency INT NOT NULL,"
|
||||||
+ " PRIMARY KEY (transportId))";
|
+ " PRIMARY KEY (transportId))";
|
||||||
|
|
||||||
private static final String CREATE_INCOMING_KEYS =
|
private static final String CREATE_INCOMING_KEYS =
|
||||||
"CREATE TABLE incomingKeys"
|
"CREATE TABLE incomingKeys"
|
||||||
+ " (contactId INT NOT NULL,"
|
+ " (contactId INT NOT NULL,"
|
||||||
+ " transportId _STRING NOT NULL,"
|
+ " transportId VARCHAR NOT NULL,"
|
||||||
+ " rotationPeriod BIGINT NOT NULL,"
|
+ " period BIGINT NOT NULL,"
|
||||||
+ " tagKey _SECRET NOT NULL,"
|
+ " tagKey SECRET NOT NULL,"
|
||||||
+ " headerKey _SECRET NOT NULL,"
|
+ " headerKey SECRET NOT NULL,"
|
||||||
+ " base BIGINT NOT NULL,"
|
+ " base BIGINT NOT NULL,"
|
||||||
+ " bitmap _BINARY NOT NULL,"
|
+ " bitmap BINARY NOT NULL,"
|
||||||
+ " PRIMARY KEY (contactId, transportId, rotationPeriod),"
|
+ " PRIMARY KEY (contactId, transportId, period),"
|
||||||
+ " FOREIGN KEY (contactId)"
|
+ " FOREIGN KEY (contactId)"
|
||||||
+ " REFERENCES contacts (contactId)"
|
+ " REFERENCES contacts (contactId)"
|
||||||
+ " ON DELETE CASCADE,"
|
+ " ON DELETE CASCADE,"
|
||||||
@@ -219,10 +226,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
private static final String CREATE_OUTGOING_KEYS =
|
private static final String CREATE_OUTGOING_KEYS =
|
||||||
"CREATE TABLE outgoingKeys"
|
"CREATE TABLE outgoingKeys"
|
||||||
+ " (contactId INT NOT NULL,"
|
+ " (contactId INT NOT NULL,"
|
||||||
+ " transportId _STRING NOT NULL,"
|
+ " transportId VARCHAR NOT NULL,"
|
||||||
+ " rotationPeriod BIGINT NOT NULL,"
|
+ " period BIGINT NOT NULL,"
|
||||||
+ " tagKey _SECRET NOT NULL,"
|
+ " tagKey SECRET NOT NULL,"
|
||||||
+ " headerKey _SECRET NOT NULL,"
|
+ " headerKey SECRET NOT NULL,"
|
||||||
+ " stream BIGINT NOT NULL,"
|
+ " stream BIGINT NOT NULL,"
|
||||||
+ " PRIMARY KEY (contactId, transportId),"
|
+ " PRIMARY KEY (contactId, transportId),"
|
||||||
+ " FOREIGN KEY (contactId)"
|
+ " FOREIGN KEY (contactId)"
|
||||||
@@ -236,32 +243,19 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
"CREATE INDEX IF NOT EXISTS contactsByAuthorId"
|
"CREATE INDEX IF NOT EXISTS contactsByAuthorId"
|
||||||
+ " ON contacts (authorId)";
|
+ " ON contacts (authorId)";
|
||||||
|
|
||||||
private static final String INDEX_MESSAGES_BY_GROUP_ID =
|
|
||||||
"CREATE INDEX IF NOT EXISTS messagesByGroupId"
|
|
||||||
+ " ON messages (groupId)";
|
|
||||||
|
|
||||||
private static final String INDEX_OFFERS_BY_CONTACT_ID =
|
|
||||||
"CREATE INDEX IF NOT EXISTS offersByContactId"
|
|
||||||
+ " ON offers (contactId)";
|
|
||||||
|
|
||||||
private static final String INDEX_GROUPS_BY_CLIENT_ID =
|
private static final String INDEX_GROUPS_BY_CLIENT_ID =
|
||||||
"CREATE INDEX IF NOT EXISTS groupsByClientId"
|
"CREATE INDEX IF NOT EXISTS groupsByClientId"
|
||||||
+ " ON groups (clientId)";
|
+ " ON groups (clientId)";
|
||||||
|
|
||||||
private static final String INDEX_MESSAGE_METADATA_BY_MESSAGE_ID =
|
private static final String INDEX_MESSAGE_METADATA_BY_GROUP_ID_STATE =
|
||||||
"CREATE INDEX IF NOT EXISTS messageMetadataByMessageId"
|
"CREATE INDEX IF NOT EXISTS messageMetadataByGroupIdState"
|
||||||
+ " ON messageMetadata (messageId)";
|
+ " ON messageMetadata (groupId, state)";
|
||||||
|
|
||||||
private static final String INDEX_GROUP_METADATA_BY_GROUP_ID =
|
|
||||||
"CREATE INDEX IF NOT EXISTS groupMetadataByGroupId"
|
|
||||||
+ " ON groupMetadata (groupId)";
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(JdbcDatabase.class.getName());
|
Logger.getLogger(JdbcDatabase.class.getName());
|
||||||
|
|
||||||
// Different database libraries use different names for certain types
|
// Different database libraries use different names for certain types
|
||||||
private final String hashType, secretType, binaryType;
|
private final String hashType, binaryType, counterType, secretType;
|
||||||
private final String counterType, stringType;
|
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
|
|
||||||
// Locking: connectionsLock
|
// Locking: connectionsLock
|
||||||
@@ -276,13 +270,12 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
private final Lock connectionsLock = new ReentrantLock();
|
private final Lock connectionsLock = new ReentrantLock();
|
||||||
private final Condition connectionsChanged = connectionsLock.newCondition();
|
private final Condition connectionsChanged = connectionsLock.newCondition();
|
||||||
|
|
||||||
JdbcDatabase(String hashType, String secretType, String binaryType,
|
JdbcDatabase(String hashType, String binaryType, String counterType,
|
||||||
String counterType, String stringType, Clock clock) {
|
String secretType, Clock clock) {
|
||||||
this.hashType = hashType;
|
this.hashType = hashType;
|
||||||
this.secretType = secretType;
|
|
||||||
this.binaryType = binaryType;
|
this.binaryType = binaryType;
|
||||||
this.counterType = counterType;
|
this.counterType = counterType;
|
||||||
this.stringType = stringType;
|
this.secretType = secretType;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,10 +290,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
Connection txn = startTransaction();
|
Connection txn = startTransaction();
|
||||||
try {
|
try {
|
||||||
if (reopen) {
|
if (reopen) {
|
||||||
if (!checkSchemaVersion(txn)) throw new DbException();
|
checkSchemaVersion(txn);
|
||||||
} else {
|
} else {
|
||||||
createTables(txn);
|
createTables(txn);
|
||||||
storeSchemaVersion(txn);
|
storeSchemaVersion(txn, CODE_SCHEMA_VERSION);
|
||||||
}
|
}
|
||||||
createIndexes(txn);
|
createIndexes(txn);
|
||||||
commitTransaction(txn);
|
commitTransaction(txn);
|
||||||
@@ -310,19 +303,49 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkSchemaVersion(Connection txn) throws DbException {
|
/**
|
||||||
|
* Compares the schema version stored in the database with the schema
|
||||||
|
* version used by the current code and applies any suitable migrations to
|
||||||
|
* the data if necessary.
|
||||||
|
*
|
||||||
|
* @throws DataTooNewException if the data uses a newer schema than the
|
||||||
|
* current code
|
||||||
|
* @throws DataTooOldException if the data uses an older schema than the
|
||||||
|
* current code and cannot be migrated
|
||||||
|
*/
|
||||||
|
private void checkSchemaVersion(Connection txn) throws DbException {
|
||||||
Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
|
Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
|
||||||
int schemaVersion = s.getInt(SCHEMA_VERSION_KEY, -1);
|
int dataSchemaVersion = s.getInt(SCHEMA_VERSION_KEY, -1);
|
||||||
if (schemaVersion == SCHEMA_VERSION) return true;
|
if (dataSchemaVersion == -1) throw new DbException();
|
||||||
if (schemaVersion < MIN_SCHEMA_VERSION) return false;
|
if (dataSchemaVersion == CODE_SCHEMA_VERSION) return;
|
||||||
int minSchemaVersion = s.getInt(MIN_SCHEMA_VERSION_KEY, -1);
|
if (CODE_SCHEMA_VERSION < dataSchemaVersion)
|
||||||
return SCHEMA_VERSION >= minSchemaVersion;
|
throw new DataTooNewException();
|
||||||
|
// Apply any suitable migrations in order
|
||||||
|
for (Migration<Connection> m : getMigrations()) {
|
||||||
|
int start = m.getStartVersion(), end = m.getEndVersion();
|
||||||
|
if (start == dataSchemaVersion) {
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Migrating from schema " + start + " to " + end);
|
||||||
|
// Apply the migration
|
||||||
|
m.migrate(txn);
|
||||||
|
// Store the new schema version
|
||||||
|
storeSchemaVersion(txn, end);
|
||||||
|
dataSchemaVersion = end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dataSchemaVersion != CODE_SCHEMA_VERSION)
|
||||||
|
throw new DataTooOldException();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void storeSchemaVersion(Connection txn) throws DbException {
|
// Package access for testing
|
||||||
|
List<Migration<Connection>> getMigrations() {
|
||||||
|
return Collections.singletonList(new Migration30_31());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void storeSchemaVersion(Connection txn, int version)
|
||||||
|
throws DbException {
|
||||||
Settings s = new Settings();
|
Settings s = new Settings();
|
||||||
s.putInt(SCHEMA_VERSION_KEY, SCHEMA_VERSION);
|
s.putInt(SCHEMA_VERSION_KEY, version);
|
||||||
s.putInt(MIN_SCHEMA_VERSION_KEY, MIN_SCHEMA_VERSION);
|
|
||||||
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
|
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,11 +395,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
try {
|
try {
|
||||||
s = txn.createStatement();
|
s = txn.createStatement();
|
||||||
s.executeUpdate(INDEX_CONTACTS_BY_AUTHOR_ID);
|
s.executeUpdate(INDEX_CONTACTS_BY_AUTHOR_ID);
|
||||||
s.executeUpdate(INDEX_MESSAGES_BY_GROUP_ID);
|
|
||||||
s.executeUpdate(INDEX_OFFERS_BY_CONTACT_ID);
|
|
||||||
s.executeUpdate(INDEX_GROUPS_BY_CLIENT_ID);
|
s.executeUpdate(INDEX_GROUPS_BY_CLIENT_ID);
|
||||||
s.executeUpdate(INDEX_MESSAGE_METADATA_BY_MESSAGE_ID);
|
s.executeUpdate(INDEX_MESSAGE_METADATA_BY_GROUP_ID_STATE);
|
||||||
s.executeUpdate(INDEX_GROUP_METADATA_BY_GROUP_ID);
|
|
||||||
s.close();
|
s.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(s);
|
tryToClose(s);
|
||||||
@@ -385,17 +405,16 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String insertTypeNames(String s) {
|
private String insertTypeNames(String s) {
|
||||||
s = s.replaceAll("_HASH", hashType);
|
s = s.replaceAll("HASH", hashType);
|
||||||
s = s.replaceAll("_SECRET", secretType);
|
s = s.replaceAll("BINARY", binaryType);
|
||||||
s = s.replaceAll("_BINARY", binaryType);
|
s = s.replaceAll("COUNTER", counterType);
|
||||||
s = s.replaceAll("_COUNTER", counterType);
|
s = s.replaceAll("SECRET", secretType);
|
||||||
s = s.replaceAll("_STRING", stringType);
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Connection startTransaction() throws DbException {
|
public Connection startTransaction() throws DbException {
|
||||||
Connection txn;
|
Connection txn = null;
|
||||||
connectionsLock.lock();
|
connectionsLock.lock();
|
||||||
try {
|
try {
|
||||||
if (closed) throw new DbClosedException();
|
if (closed) throw new DbClosedException();
|
||||||
@@ -723,7 +742,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
try {
|
try {
|
||||||
// Store the incoming keys
|
// Store the incoming keys
|
||||||
String sql = "INSERT INTO incomingKeys (contactId, transportId,"
|
String sql = "INSERT INTO incomingKeys (contactId, transportId,"
|
||||||
+ " rotationPeriod, tagKey, headerKey, base, bitmap)"
|
+ " period, tagKey, headerKey, base, bitmap)"
|
||||||
+ " VALUES (?, ?, ?, ?, ?, ?, ?)";
|
+ " VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, c.getInt());
|
ps.setInt(1, c.getInt());
|
||||||
@@ -758,8 +777,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (rows != 1) throw new DbStateException();
|
if (rows != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
// Store the outgoing keys
|
// Store the outgoing keys
|
||||||
sql = "INSERT INTO outgoingKeys (contactId, transportId,"
|
sql = "INSERT INTO outgoingKeys (contactId, transportId, period,"
|
||||||
+ " rotationPeriod, tagKey, headerKey, stream)"
|
+ " tagKey, headerKey, stream)"
|
||||||
+ " VALUES (?, ?, ?, ?, ?, ?)";
|
+ " VALUES (?, ?, ?, ?, ?, ?)";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, c.getInt());
|
ps.setInt(1, c.getInt());
|
||||||
@@ -1334,16 +1353,13 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
try {
|
try {
|
||||||
// Retrieve the message IDs for each query term and intersect
|
// Retrieve the message IDs for each query term and intersect
|
||||||
Set<MessageId> intersection = null;
|
Set<MessageId> intersection = null;
|
||||||
String sql = "SELECT m.messageId"
|
String sql = "SELECT messageId FROM messageMetadata"
|
||||||
+ " FROM messages AS m"
|
+ " WHERE groupId = ? AND state = ?"
|
||||||
+ " JOIN messageMetadata AS md"
|
+ " AND key = ? AND value = ?";
|
||||||
+ " ON m.messageId = md.messageId"
|
|
||||||
+ " WHERE state = ? AND groupId = ?"
|
|
||||||
+ " AND metaKey = ? AND value = ?";
|
|
||||||
for (Entry<String, byte[]> e : query.entrySet()) {
|
for (Entry<String, byte[]> e : query.entrySet()) {
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, DELIVERED.getValue());
|
ps.setBytes(1, g.getBytes());
|
||||||
ps.setBytes(2, g.getBytes());
|
ps.setInt(2, DELIVERED.getValue());
|
||||||
ps.setString(3, e.getKey());
|
ps.setString(3, e.getKey());
|
||||||
ps.setBytes(4, e.getValue());
|
ps.setBytes(4, e.getValue());
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
@@ -1371,25 +1387,20 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT m.messageId, metaKey, value"
|
String sql = "SELECT messageId, key, value"
|
||||||
+ " FROM messages AS m"
|
+ " FROM messageMetadata"
|
||||||
+ " JOIN messageMetadata AS md"
|
+ " WHERE groupId = ? AND state = ?";
|
||||||
+ " ON m.messageId = md.messageId"
|
|
||||||
+ " WHERE state = ? AND groupId = ?"
|
|
||||||
+ " ORDER BY m.messageId";
|
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, DELIVERED.getValue());
|
ps.setBytes(1, g.getBytes());
|
||||||
ps.setBytes(2, g.getBytes());
|
ps.setInt(2, DELIVERED.getValue());
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
Map<MessageId, Metadata> all = new HashMap<>();
|
Map<MessageId, Metadata> all = new HashMap<>();
|
||||||
Metadata metadata = null;
|
|
||||||
MessageId lastMessageId = null;
|
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
MessageId messageId = new MessageId(rs.getBytes(1));
|
MessageId messageId = new MessageId(rs.getBytes(1));
|
||||||
if (lastMessageId == null || !messageId.equals(lastMessageId)) {
|
Metadata metadata = all.get(messageId);
|
||||||
|
if (metadata == null) {
|
||||||
metadata = new Metadata();
|
metadata = new Metadata();
|
||||||
all.put(messageId, metadata);
|
all.put(messageId, metadata);
|
||||||
lastMessageId = messageId;
|
|
||||||
}
|
}
|
||||||
metadata.put(rs.getString(2), rs.getBytes(3));
|
metadata.put(rs.getString(2), rs.getBytes(3));
|
||||||
}
|
}
|
||||||
@@ -1421,7 +1432,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT metaKey, value FROM groupMetadata"
|
String sql = "SELECT key, value FROM groupMetadata"
|
||||||
+ " WHERE groupId = ?";
|
+ " WHERE groupId = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setBytes(1, g.getBytes());
|
ps.setBytes(1, g.getBytes());
|
||||||
@@ -1444,10 +1455,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT metaKey, value FROM messageMetadata AS md"
|
String sql = "SELECT key, value FROM messageMetadata"
|
||||||
+ " JOIN messages AS m"
|
+ " WHERE state = ? AND messageId = ?";
|
||||||
+ " ON m.messageId = md.messageId"
|
|
||||||
+ " WHERE m.state = ? AND md.messageId = ?";
|
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, DELIVERED.getValue());
|
ps.setInt(1, DELIVERED.getValue());
|
||||||
ps.setBytes(2, m.getBytes());
|
ps.setBytes(2, m.getBytes());
|
||||||
@@ -1470,11 +1479,9 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT metaKey, value FROM messageMetadata AS md"
|
String sql = "SELECT key, value FROM messageMetadata"
|
||||||
+ " JOIN messages AS m"
|
+ " WHERE (state = ? OR state = ?)"
|
||||||
+ " ON m.messageId = md.messageId"
|
+ " AND messageId = ?";
|
||||||
+ " WHERE (m.state = ? OR m.state = ?)"
|
|
||||||
+ " AND md.messageId = ?";
|
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, DELIVERED.getValue());
|
ps.setInt(1, DELIVERED.getValue());
|
||||||
ps.setInt(2, PENDING.getValue());
|
ps.setInt(2, PENDING.getValue());
|
||||||
@@ -1908,8 +1915,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT settingKey, value FROM settings"
|
String sql = "SELECT key, value FROM settings WHERE namespace = ?";
|
||||||
+ " WHERE namespace = ?";
|
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setString(1, namespace);
|
ps.setString(1, namespace);
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
@@ -1932,11 +1938,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
// Retrieve the incoming keys
|
// Retrieve the incoming keys
|
||||||
String sql = "SELECT rotationPeriod, tagKey, headerKey,"
|
String sql = "SELECT period, tagKey, headerKey, base, bitmap"
|
||||||
+ " base, bitmap"
|
|
||||||
+ " FROM incomingKeys"
|
+ " FROM incomingKeys"
|
||||||
+ " WHERE transportId = ?"
|
+ " WHERE transportId = ?"
|
||||||
+ " ORDER BY contactId, rotationPeriod";
|
+ " ORDER BY contactId, period";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setString(1, t.getString());
|
ps.setString(1, t.getString());
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
@@ -1953,10 +1958,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
rs.close();
|
rs.close();
|
||||||
ps.close();
|
ps.close();
|
||||||
// Retrieve the outgoing keys in the same order
|
// Retrieve the outgoing keys in the same order
|
||||||
sql = "SELECT contactId, rotationPeriod, tagKey, headerKey, stream"
|
sql = "SELECT contactId, period, tagKey, headerKey, stream"
|
||||||
+ " FROM outgoingKeys"
|
+ " FROM outgoingKeys"
|
||||||
+ " WHERE transportId = ?"
|
+ " WHERE transportId = ?"
|
||||||
+ " ORDER BY contactId, rotationPeriod";
|
+ " ORDER BY contactId, period";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setString(1, t.getString());
|
ps.setString(1, t.getString());
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
@@ -1993,8 +1998,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
try {
|
try {
|
||||||
String sql = "UPDATE outgoingKeys SET stream = stream + 1"
|
String sql = "UPDATE outgoingKeys SET stream = stream + 1"
|
||||||
+ " WHERE contactId = ? AND transportId = ?"
|
+ " WHERE contactId = ? AND transportId = ? AND period = ?";
|
||||||
+ " AND rotationPeriod = ?";
|
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, c.getInt());
|
ps.setInt(1, c.getInt());
|
||||||
ps.setString(2, t.getString());
|
ps.setString(2, t.getString());
|
||||||
@@ -2051,7 +2055,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
int[] batchAffected = ps.executeBatch();
|
int[] batchAffected = ps.executeBatch();
|
||||||
if (batchAffected.length != requested.size())
|
if (batchAffected.length != requested.size())
|
||||||
throw new DbStateException();
|
throw new DbStateException();
|
||||||
for (int rows: batchAffected) {
|
for (int rows : batchAffected) {
|
||||||
if (rows < 0) throw new DbStateException();
|
if (rows < 0) throw new DbStateException();
|
||||||
if (rows > 1) throw new DbStateException();
|
if (rows > 1) throw new DbStateException();
|
||||||
}
|
}
|
||||||
@@ -2065,30 +2069,97 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
@Override
|
@Override
|
||||||
public void mergeGroupMetadata(Connection txn, GroupId g, Metadata meta)
|
public void mergeGroupMetadata(Connection txn, GroupId g, Metadata meta)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
mergeMetadata(txn, g.getBytes(), meta, "groupMetadata", "groupId");
|
PreparedStatement ps = null;
|
||||||
|
try {
|
||||||
|
Map<String, byte[]> added = removeOrUpdateMetadata(txn,
|
||||||
|
g.getBytes(), meta, "groupMetadata", "groupId");
|
||||||
|
if (added.isEmpty()) return;
|
||||||
|
// Insert any keys that don't already exist
|
||||||
|
String sql = "INSERT INTO groupMetadata (groupId, key, value)"
|
||||||
|
+ " VALUES (?, ?, ?)";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, g.getBytes());
|
||||||
|
for (Entry<String, byte[]> e : added.entrySet()) {
|
||||||
|
ps.setString(2, e.getKey());
|
||||||
|
ps.setBytes(3, e.getValue());
|
||||||
|
ps.addBatch();
|
||||||
|
}
|
||||||
|
int[] batchAffected = ps.executeBatch();
|
||||||
|
if (batchAffected.length != added.size())
|
||||||
|
throw new DbStateException();
|
||||||
|
for (int rows : batchAffected)
|
||||||
|
if (rows != 1) throw new DbStateException();
|
||||||
|
ps.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
tryToClose(ps);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mergeMessageMetadata(Connection txn, MessageId m, Metadata meta)
|
public void mergeMessageMetadata(Connection txn, MessageId m,
|
||||||
throws DbException {
|
Metadata meta) throws DbException {
|
||||||
mergeMetadata(txn, m.getBytes(), meta, "messageMetadata", "messageId");
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
Map<String, byte[]> added = removeOrUpdateMetadata(txn,
|
||||||
|
m.getBytes(), meta, "messageMetadata", "messageId");
|
||||||
|
if (added.isEmpty()) return;
|
||||||
|
// Get the group ID and message state for the denormalised columns
|
||||||
|
String sql = "SELECT groupId, state FROM messages"
|
||||||
|
+ " WHERE messageId = ?";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, m.getBytes());
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
if (!rs.next()) throw new DbStateException();
|
||||||
|
GroupId g = new GroupId(rs.getBytes(1));
|
||||||
|
State state = State.fromValue(rs.getInt(2));
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
// Insert any keys that don't already exist
|
||||||
|
sql = "INSERT INTO messageMetadata"
|
||||||
|
+ " (messageId, groupId, state, key, value)"
|
||||||
|
+ " VALUES (?, ?, ?, ?, ?)";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, m.getBytes());
|
||||||
|
ps.setBytes(2, g.getBytes());
|
||||||
|
ps.setInt(3, state.getValue());
|
||||||
|
for (Entry<String, byte[]> e : added.entrySet()) {
|
||||||
|
ps.setString(4, e.getKey());
|
||||||
|
ps.setBytes(5, e.getValue());
|
||||||
|
ps.addBatch();
|
||||||
|
}
|
||||||
|
int[] batchAffected = ps.executeBatch();
|
||||||
|
if (batchAffected.length != added.size())
|
||||||
|
throw new DbStateException();
|
||||||
|
for (int rows : batchAffected)
|
||||||
|
if (rows != 1) throw new DbStateException();
|
||||||
|
ps.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
tryToClose(rs);
|
||||||
|
tryToClose(ps);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mergeMetadata(Connection txn, byte[] id, Metadata meta,
|
// Removes or updates any existing entries, returns any entries that
|
||||||
String tableName, String columnName) throws DbException {
|
// need to be added
|
||||||
|
private Map<String, byte[]> removeOrUpdateMetadata(Connection txn,
|
||||||
|
byte[] id, Metadata meta, String tableName, String columnName)
|
||||||
|
throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
try {
|
try {
|
||||||
// Determine which keys are being removed
|
// Determine which keys are being removed
|
||||||
List<String> removed = new ArrayList<>();
|
List<String> removed = new ArrayList<>();
|
||||||
Map<String, byte[]> retained = new HashMap<>();
|
Map<String, byte[]> notRemoved = new HashMap<>();
|
||||||
for (Entry<String, byte[]> e : meta.entrySet()) {
|
for (Entry<String, byte[]> e : meta.entrySet()) {
|
||||||
if (e.getValue() == REMOVE) removed.add(e.getKey());
|
if (e.getValue() == REMOVE) removed.add(e.getKey());
|
||||||
else retained.put(e.getKey(), e.getValue());
|
else notRemoved.put(e.getKey(), e.getValue());
|
||||||
}
|
}
|
||||||
// Delete any keys that are being removed
|
// Delete any keys that are being removed
|
||||||
if (!removed.isEmpty()) {
|
if (!removed.isEmpty()) {
|
||||||
String sql = "DELETE FROM " + tableName
|
String sql = "DELETE FROM " + tableName
|
||||||
+ " WHERE " + columnName + " = ? AND metaKey = ?";
|
+ " WHERE " + columnName + " = ? AND key = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setBytes(1, id);
|
ps.setBytes(1, id);
|
||||||
for (String key : removed) {
|
for (String key : removed) {
|
||||||
@@ -2104,45 +2175,33 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
ps.close();
|
ps.close();
|
||||||
}
|
}
|
||||||
if (retained.isEmpty()) return;
|
if (notRemoved.isEmpty()) return Collections.emptyMap();
|
||||||
// Update any keys that already exist
|
// Update any keys that already exist
|
||||||
String sql = "UPDATE " + tableName + " SET value = ?"
|
String sql = "UPDATE " + tableName + " SET value = ?"
|
||||||
+ " WHERE " + columnName + " = ? AND metaKey = ?";
|
+ " WHERE " + columnName + " = ? AND key = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setBytes(2, id);
|
ps.setBytes(2, id);
|
||||||
for (Entry<String, byte[]> e : retained.entrySet()) {
|
for (Entry<String, byte[]> e : notRemoved.entrySet()) {
|
||||||
ps.setBytes(1, e.getValue());
|
ps.setBytes(1, e.getValue());
|
||||||
ps.setString(3, e.getKey());
|
ps.setString(3, e.getKey());
|
||||||
ps.addBatch();
|
ps.addBatch();
|
||||||
}
|
}
|
||||||
int[] batchAffected = ps.executeBatch();
|
int[] batchAffected = ps.executeBatch();
|
||||||
if (batchAffected.length != retained.size())
|
if (batchAffected.length != notRemoved.size())
|
||||||
throw new DbStateException();
|
throw new DbStateException();
|
||||||
for (int rows : batchAffected) {
|
for (int rows : batchAffected) {
|
||||||
if (rows < 0) throw new DbStateException();
|
if (rows < 0) throw new DbStateException();
|
||||||
if (rows > 1) throw new DbStateException();
|
if (rows > 1) throw new DbStateException();
|
||||||
}
|
}
|
||||||
// Insert any keys that don't already exist
|
|
||||||
sql = "INSERT INTO " + tableName
|
|
||||||
+ " (" + columnName + ", metaKey, value)"
|
|
||||||
+ " VALUES (?, ?, ?)";
|
|
||||||
ps = txn.prepareStatement(sql);
|
|
||||||
ps.setBytes(1, id);
|
|
||||||
int updateIndex = 0, inserted = 0;
|
|
||||||
for (Entry<String, byte[]> e : retained.entrySet()) {
|
|
||||||
if (batchAffected[updateIndex] == 0) {
|
|
||||||
ps.setString(2, e.getKey());
|
|
||||||
ps.setBytes(3, e.getValue());
|
|
||||||
ps.addBatch();
|
|
||||||
inserted++;
|
|
||||||
}
|
|
||||||
updateIndex++;
|
|
||||||
}
|
|
||||||
batchAffected = ps.executeBatch();
|
|
||||||
if (batchAffected.length != inserted) throw new DbStateException();
|
|
||||||
for (int rows : batchAffected)
|
|
||||||
if (rows != 1) throw new DbStateException();
|
|
||||||
ps.close();
|
ps.close();
|
||||||
|
// Are there any keys that don't already exist?
|
||||||
|
Map<String, byte[]> added = new HashMap<>();
|
||||||
|
int updateIndex = 0;
|
||||||
|
for (Entry<String, byte[]> e : notRemoved.entrySet()) {
|
||||||
|
if (batchAffected[updateIndex++] == 0)
|
||||||
|
added.put(e.getKey(), e.getValue());
|
||||||
|
}
|
||||||
|
return added;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
@@ -2156,7 +2215,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
try {
|
try {
|
||||||
// Update any settings that already exist
|
// Update any settings that already exist
|
||||||
String sql = "UPDATE settings SET value = ?"
|
String sql = "UPDATE settings SET value = ?"
|
||||||
+ " WHERE namespace = ? AND settingKey = ?";
|
+ " WHERE namespace = ? AND key = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
for (Entry<String, String> e : s.entrySet()) {
|
for (Entry<String, String> e : s.entrySet()) {
|
||||||
ps.setString(1, e.getValue());
|
ps.setString(1, e.getValue());
|
||||||
@@ -2171,7 +2230,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (rows > 1) throw new DbStateException();
|
if (rows > 1) throw new DbStateException();
|
||||||
}
|
}
|
||||||
// Insert any settings that don't already exist
|
// Insert any settings that don't already exist
|
||||||
sql = "INSERT INTO settings (namespace, settingKey, value)"
|
sql = "INSERT INTO settings (namespace, key, value)"
|
||||||
+ " VALUES (?, ?, ?)";
|
+ " VALUES (?, ?, ?)";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
int updateIndex = 0, inserted = 0;
|
int updateIndex = 0, inserted = 0;
|
||||||
@@ -2495,7 +2554,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setMessageShared(Connection txn, MessageId m) throws DbException {
|
public void setMessageShared(Connection txn, MessageId m)
|
||||||
|
throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
try {
|
try {
|
||||||
String sql = "UPDATE messages SET shared = TRUE"
|
String sql = "UPDATE messages SET shared = TRUE"
|
||||||
@@ -2523,6 +2583,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
int affected = ps.executeUpdate();
|
int affected = ps.executeUpdate();
|
||||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
|
// Update denormalised column in messageMetadata
|
||||||
|
sql = "UPDATE messageMetadata SET state = ? WHERE messageId = ?";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setInt(1, state.getValue());
|
||||||
|
ps.setBytes(2, m.getBytes());
|
||||||
|
affected = ps.executeUpdate();
|
||||||
|
if (affected < 0) throw new DbStateException();
|
||||||
|
ps.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(ps);
|
tryToClose(ps);
|
||||||
throw new DbException(e);
|
throw new DbException(e);
|
||||||
@@ -2535,8 +2603,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
try {
|
try {
|
||||||
String sql = "UPDATE incomingKeys SET base = ?, bitmap = ?"
|
String sql = "UPDATE incomingKeys SET base = ?, bitmap = ?"
|
||||||
+ " WHERE contactId = ? AND transportId = ?"
|
+ " WHERE contactId = ? AND transportId = ? AND period = ?";
|
||||||
+ " AND rotationPeriod = ?";
|
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setLong(1, base);
|
ps.setLong(1, base);
|
||||||
ps.setBytes(2, bitmap);
|
ps.setBytes(2, bitmap);
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package org.briarproject.bramble.db;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
|
||||||
|
interface Migration<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the schema version from which this migration starts.
|
||||||
|
*/
|
||||||
|
int getStartVersion();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the schema version at which this migration ends.
|
||||||
|
*/
|
||||||
|
int getEndVersion();
|
||||||
|
|
||||||
|
void migrate(T txn) throws DbException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package org.briarproject.bramble.db;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
|
||||||
|
class Migration30_31 implements Migration<Connection> {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
Logger.getLogger(Migration30_31.class.getName());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getStartVersion() {
|
||||||
|
return 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getEndVersion() {
|
||||||
|
return 31;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void migrate(Connection txn) throws DbException {
|
||||||
|
Statement s = null;
|
||||||
|
try {
|
||||||
|
s = txn.createStatement();
|
||||||
|
// Add groupId column
|
||||||
|
s.execute("ALTER TABLE messageMetadata"
|
||||||
|
+ " ADD COLUMN groupId BINARY(32) AFTER messageId");
|
||||||
|
// Populate groupId column
|
||||||
|
s.execute("UPDATE messageMetadata AS mm SET groupId ="
|
||||||
|
+ " (SELECT groupId FROM messages AS m"
|
||||||
|
+ " WHERE mm.messageId = m.messageId)");
|
||||||
|
// Add not null constraint now column has been populated
|
||||||
|
s.execute("ALTER TABLE messageMetadata"
|
||||||
|
+ " ALTER COLUMN groupId"
|
||||||
|
+ " SET NOT NULL");
|
||||||
|
// Add foreign key constraint
|
||||||
|
s.execute("ALTER TABLE messageMetadata"
|
||||||
|
+ " ADD CONSTRAINT groupIdForeignKey"
|
||||||
|
+ " FOREIGN KEY (groupId)"
|
||||||
|
+ " REFERENCES groups (groupId)"
|
||||||
|
+ " ON DELETE CASCADE");
|
||||||
|
// Add state column
|
||||||
|
s.execute("ALTER TABLE messageMetadata"
|
||||||
|
+ " ADD COLUMN state INT AFTER groupId");
|
||||||
|
// Populate state column
|
||||||
|
s.execute("UPDATE messageMetadata AS mm SET state ="
|
||||||
|
+ " (SELECT state FROM messages AS m"
|
||||||
|
+ " WHERE mm.messageId = m.messageId)");
|
||||||
|
// Add not null constraint now column has been populated
|
||||||
|
s.execute("ALTER TABLE messageMetadata"
|
||||||
|
+ " ALTER COLUMN state"
|
||||||
|
+ " SET NOT NULL");
|
||||||
|
} catch (SQLException e) {
|
||||||
|
tryToClose(s);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryToClose(@Nullable Statement s) {
|
||||||
|
try {
|
||||||
|
if (s != null) s.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package org.briarproject.bramble.keyagreement;
|
package org.briarproject.bramble.keyagreement;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||||
@@ -46,7 +46,7 @@ class KeyAgreementConnector {
|
|||||||
|
|
||||||
private final Callbacks callbacks;
|
private final Callbacks callbacks;
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
private final KeyAgreementCrypto keyAgreementCrypto;
|
private final CryptoComponent crypto;
|
||||||
private final PluginManager pluginManager;
|
private final PluginManager pluginManager;
|
||||||
private final CompletionService<KeyAgreementConnection> connect;
|
private final CompletionService<KeyAgreementConnection> connect;
|
||||||
|
|
||||||
@@ -58,11 +58,11 @@ class KeyAgreementConnector {
|
|||||||
private volatile boolean alice = false;
|
private volatile boolean alice = false;
|
||||||
|
|
||||||
KeyAgreementConnector(Callbacks callbacks, Clock clock,
|
KeyAgreementConnector(Callbacks callbacks, Clock clock,
|
||||||
KeyAgreementCrypto keyAgreementCrypto, PluginManager pluginManager,
|
CryptoComponent crypto, PluginManager pluginManager,
|
||||||
Executor ioExecutor) {
|
Executor ioExecutor) {
|
||||||
this.callbacks = callbacks;
|
this.callbacks = callbacks;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
this.keyAgreementCrypto = keyAgreementCrypto;
|
this.crypto = crypto;
|
||||||
this.pluginManager = pluginManager;
|
this.pluginManager = pluginManager;
|
||||||
connect = new ExecutorCompletionService<>(ioExecutor);
|
connect = new ExecutorCompletionService<>(ioExecutor);
|
||||||
}
|
}
|
||||||
@@ -70,8 +70,8 @@ class KeyAgreementConnector {
|
|||||||
public Payload listen(KeyPair localKeyPair) {
|
public Payload listen(KeyPair localKeyPair) {
|
||||||
LOG.info("Starting BQP listeners");
|
LOG.info("Starting BQP listeners");
|
||||||
// Derive commitment
|
// Derive commitment
|
||||||
byte[] commitment = keyAgreementCrypto.deriveKeyCommitment(
|
byte[] commitment = crypto.deriveKeyCommitment(
|
||||||
localKeyPair.getPublic());
|
localKeyPair.getPublic().getEncoded());
|
||||||
// Start all listeners and collect their descriptors
|
// Start all listeners and collect their descriptors
|
||||||
List<TransportDescriptor> descriptors = new ArrayList<>();
|
List<TransportDescriptor> descriptors = new ArrayList<>();
|
||||||
for (DuplexPlugin plugin : pluginManager.getKeyAgreementPlugins()) {
|
for (DuplexPlugin plugin : pluginManager.getKeyAgreementPlugins()) {
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
package org.briarproject.bramble.keyagreement;
|
package org.briarproject.bramble.keyagreement;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory;
|
||||||
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
||||||
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
@@ -13,9 +22,13 @@ import dagger.Provides;
|
|||||||
public class KeyAgreementModule {
|
public class KeyAgreementModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
KeyAgreementTask provideKeyAgreementTask(
|
@Singleton
|
||||||
KeyAgreementTaskImpl keyAgreementTask) {
|
KeyAgreementTaskFactory provideKeyAgreementTaskFactory(Clock clock,
|
||||||
return keyAgreementTask;
|
CryptoComponent crypto, EventBus eventBus,
|
||||||
|
@IoExecutor Executor ioExecutor, PayloadEncoder payloadEncoder,
|
||||||
|
PluginManager pluginManager) {
|
||||||
|
return new KeyAgreementTaskFactoryImpl(clock, crypto, eventBus,
|
||||||
|
ioExecutor, payloadEncoder, pluginManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
package org.briarproject.bramble.keyagreement;
|
package org.briarproject.bramble.keyagreement;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
|
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
|
||||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.api.keyagreement.Payload;
|
import org.briarproject.bramble.api.keyagreement.Payload;
|
||||||
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
||||||
@@ -14,10 +11,6 @@ import java.io.IOException;
|
|||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.MASTER_SECRET_LABEL;
|
|
||||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
|
|
||||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.SHARED_SECRET_LABEL;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of the BQP protocol.
|
* Implementation of the BQP protocol.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -64,7 +57,6 @@ class KeyAgreementProtocol {
|
|||||||
|
|
||||||
private final Callbacks callbacks;
|
private final Callbacks callbacks;
|
||||||
private final CryptoComponent crypto;
|
private final CryptoComponent crypto;
|
||||||
private final KeyAgreementCrypto keyAgreementCrypto;
|
|
||||||
private final PayloadEncoder payloadEncoder;
|
private final PayloadEncoder payloadEncoder;
|
||||||
private final KeyAgreementTransport transport;
|
private final KeyAgreementTransport transport;
|
||||||
private final Payload theirPayload, ourPayload;
|
private final Payload theirPayload, ourPayload;
|
||||||
@@ -72,13 +64,11 @@ class KeyAgreementProtocol {
|
|||||||
private final boolean alice;
|
private final boolean alice;
|
||||||
|
|
||||||
KeyAgreementProtocol(Callbacks callbacks, CryptoComponent crypto,
|
KeyAgreementProtocol(Callbacks callbacks, CryptoComponent crypto,
|
||||||
KeyAgreementCrypto keyAgreementCrypto,
|
|
||||||
PayloadEncoder payloadEncoder, KeyAgreementTransport transport,
|
PayloadEncoder payloadEncoder, KeyAgreementTransport transport,
|
||||||
Payload theirPayload, Payload ourPayload, KeyPair ourKeyPair,
|
Payload theirPayload, Payload ourPayload, KeyPair ourKeyPair,
|
||||||
boolean alice) {
|
boolean alice) {
|
||||||
this.callbacks = callbacks;
|
this.callbacks = callbacks;
|
||||||
this.crypto = crypto;
|
this.crypto = crypto;
|
||||||
this.keyAgreementCrypto = keyAgreementCrypto;
|
|
||||||
this.payloadEncoder = payloadEncoder;
|
this.payloadEncoder = payloadEncoder;
|
||||||
this.transport = transport;
|
this.transport = transport;
|
||||||
this.theirPayload = theirPayload;
|
this.theirPayload = theirPayload;
|
||||||
@@ -96,7 +86,7 @@ class KeyAgreementProtocol {
|
|||||||
*/
|
*/
|
||||||
SecretKey perform() throws AbortException, IOException {
|
SecretKey perform() throws AbortException, IOException {
|
||||||
try {
|
try {
|
||||||
PublicKey theirPublicKey;
|
byte[] theirPublicKey;
|
||||||
if (alice) {
|
if (alice) {
|
||||||
sendKey();
|
sendKey();
|
||||||
// Alice waits here until Bob obtains her payload.
|
// Alice waits here until Bob obtains her payload.
|
||||||
@@ -114,7 +104,7 @@ class KeyAgreementProtocol {
|
|||||||
receiveConfirm(s, theirPublicKey);
|
receiveConfirm(s, theirPublicKey);
|
||||||
sendConfirm(s, theirPublicKey);
|
sendConfirm(s, theirPublicKey);
|
||||||
}
|
}
|
||||||
return crypto.deriveKey(MASTER_SECRET_LABEL, s);
|
return crypto.deriveMasterSecret(s);
|
||||||
} catch (AbortException e) {
|
} catch (AbortException e) {
|
||||||
sendAbort(e.getCause() != null);
|
sendAbort(e.getCause() != null);
|
||||||
throw e;
|
throw e;
|
||||||
@@ -125,41 +115,27 @@ class KeyAgreementProtocol {
|
|||||||
transport.sendKey(ourKeyPair.getPublic().getEncoded());
|
transport.sendKey(ourKeyPair.getPublic().getEncoded());
|
||||||
}
|
}
|
||||||
|
|
||||||
private PublicKey receiveKey() throws AbortException {
|
private byte[] receiveKey() throws AbortException {
|
||||||
byte[] publicKeyBytes = transport.receiveKey();
|
byte[] publicKey = transport.receiveKey();
|
||||||
callbacks.initialRecordReceived();
|
callbacks.initialRecordReceived();
|
||||||
KeyParser keyParser = crypto.getAgreementKeyParser();
|
byte[] expected = crypto.deriveKeyCommitment(publicKey);
|
||||||
try {
|
if (!Arrays.equals(expected, theirPayload.getCommitment()))
|
||||||
PublicKey publicKey = keyParser.parsePublicKey(publicKeyBytes);
|
|
||||||
byte[] expected = keyAgreementCrypto.deriveKeyCommitment(publicKey);
|
|
||||||
if (!Arrays.equals(expected, theirPayload.getCommitment()))
|
|
||||||
throw new AbortException();
|
|
||||||
return publicKey;
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
throw new AbortException();
|
throw new AbortException();
|
||||||
}
|
return publicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SecretKey deriveSharedSecret(PublicKey theirPublicKey)
|
private SecretKey deriveSharedSecret(byte[] theirPublicKey)
|
||||||
throws AbortException {
|
throws AbortException {
|
||||||
try {
|
try {
|
||||||
byte[] ourPublicKeyBytes = ourKeyPair.getPublic().getEncoded();
|
return crypto.deriveSharedSecret(theirPublicKey, ourKeyPair, alice);
|
||||||
byte[] theirPublicKeyBytes = theirPublicKey.getEncoded();
|
|
||||||
byte[][] inputs = {
|
|
||||||
new byte[] {PROTOCOL_VERSION},
|
|
||||||
alice ? ourPublicKeyBytes : theirPublicKeyBytes,
|
|
||||||
alice ? theirPublicKeyBytes : ourPublicKeyBytes
|
|
||||||
};
|
|
||||||
return crypto.deriveSharedSecret(SHARED_SECRET_LABEL,
|
|
||||||
theirPublicKey, ourKeyPair, inputs);
|
|
||||||
} catch (GeneralSecurityException e) {
|
} catch (GeneralSecurityException e) {
|
||||||
throw new AbortException(e);
|
throw new AbortException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendConfirm(SecretKey s, PublicKey theirPublicKey)
|
private void sendConfirm(SecretKey s, byte[] theirPublicKey)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
byte[] confirm = keyAgreementCrypto.deriveConfirmationRecord(s,
|
byte[] confirm = crypto.deriveConfirmationRecord(s,
|
||||||
payloadEncoder.encode(theirPayload),
|
payloadEncoder.encode(theirPayload),
|
||||||
payloadEncoder.encode(ourPayload),
|
payloadEncoder.encode(ourPayload),
|
||||||
theirPublicKey, ourKeyPair,
|
theirPublicKey, ourKeyPair,
|
||||||
@@ -167,10 +143,10 @@ class KeyAgreementProtocol {
|
|||||||
transport.sendConfirm(confirm);
|
transport.sendConfirm(confirm);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void receiveConfirm(SecretKey s, PublicKey theirPublicKey)
|
private void receiveConfirm(SecretKey s, byte[] theirPublicKey)
|
||||||
throws AbortException {
|
throws AbortException {
|
||||||
byte[] confirm = transport.receiveConfirm();
|
byte[] confirm = transport.receiveConfirm();
|
||||||
byte[] expected = keyAgreementCrypto.deriveConfirmationRecord(s,
|
byte[] expected = crypto.deriveConfirmationRecord(s,
|
||||||
payloadEncoder.encode(theirPayload),
|
payloadEncoder.encode(theirPayload),
|
||||||
payloadEncoder.encode(ourPayload),
|
payloadEncoder.encode(ourPayload),
|
||||||
theirPublicKey, ourKeyPair,
|
theirPublicKey, ourKeyPair,
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package org.briarproject.bramble.keyagreement;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
|
||||||
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory;
|
||||||
|
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
class KeyAgreementTaskFactoryImpl implements KeyAgreementTaskFactory {
|
||||||
|
|
||||||
|
private final Clock clock;
|
||||||
|
private final CryptoComponent crypto;
|
||||||
|
private final EventBus eventBus;
|
||||||
|
private final Executor ioExecutor;
|
||||||
|
private final PayloadEncoder payloadEncoder;
|
||||||
|
private final PluginManager pluginManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
KeyAgreementTaskFactoryImpl(Clock clock, CryptoComponent crypto,
|
||||||
|
EventBus eventBus, @IoExecutor Executor ioExecutor,
|
||||||
|
PayloadEncoder payloadEncoder, PluginManager pluginManager) {
|
||||||
|
this.clock = clock;
|
||||||
|
this.crypto = crypto;
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.payloadEncoder = payloadEncoder;
|
||||||
|
this.pluginManager = pluginManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyAgreementTask createTask() {
|
||||||
|
return new KeyAgreementTaskImpl(clock, crypto, eventBus, payloadEncoder,
|
||||||
|
pluginManager, ioExecutor);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.briarproject.bramble.keyagreement;
|
package org.briarproject.bramble.keyagreement;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
|
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
@@ -15,7 +14,6 @@ import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFinishedEvent
|
|||||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent;
|
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent;
|
||||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStartedEvent;
|
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStartedEvent;
|
||||||
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent;
|
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
|
||||||
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.bramble.api.plugin.PluginManager;
|
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||||
@@ -25,8 +23,6 @@ import java.io.IOException;
|
|||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@@ -39,7 +35,6 @@ class KeyAgreementTaskImpl extends Thread implements
|
|||||||
Logger.getLogger(KeyAgreementTaskImpl.class.getName());
|
Logger.getLogger(KeyAgreementTaskImpl.class.getName());
|
||||||
|
|
||||||
private final CryptoComponent crypto;
|
private final CryptoComponent crypto;
|
||||||
private final KeyAgreementCrypto keyAgreementCrypto;
|
|
||||||
private final EventBus eventBus;
|
private final EventBus eventBus;
|
||||||
private final PayloadEncoder payloadEncoder;
|
private final PayloadEncoder payloadEncoder;
|
||||||
private final KeyPair localKeyPair;
|
private final KeyPair localKeyPair;
|
||||||
@@ -48,17 +43,14 @@ class KeyAgreementTaskImpl extends Thread implements
|
|||||||
private Payload localPayload;
|
private Payload localPayload;
|
||||||
private Payload remotePayload;
|
private Payload remotePayload;
|
||||||
|
|
||||||
@Inject
|
|
||||||
KeyAgreementTaskImpl(Clock clock, CryptoComponent crypto,
|
KeyAgreementTaskImpl(Clock clock, CryptoComponent crypto,
|
||||||
KeyAgreementCrypto keyAgreementCrypto, EventBus eventBus,
|
EventBus eventBus, PayloadEncoder payloadEncoder,
|
||||||
PayloadEncoder payloadEncoder, PluginManager pluginManager,
|
PluginManager pluginManager, Executor ioExecutor) {
|
||||||
@IoExecutor Executor ioExecutor) {
|
|
||||||
this.crypto = crypto;
|
this.crypto = crypto;
|
||||||
this.keyAgreementCrypto = keyAgreementCrypto;
|
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
this.payloadEncoder = payloadEncoder;
|
this.payloadEncoder = payloadEncoder;
|
||||||
localKeyPair = crypto.generateAgreementKeyPair();
|
localKeyPair = crypto.generateAgreementKeyPair();
|
||||||
connector = new KeyAgreementConnector(this, clock, keyAgreementCrypto,
|
connector = new KeyAgreementConnector(this, clock, crypto,
|
||||||
pluginManager, ioExecutor);
|
pluginManager, ioExecutor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,8 +100,8 @@ class KeyAgreementTaskImpl extends Thread implements
|
|||||||
// Run BQP protocol over the connection
|
// Run BQP protocol over the connection
|
||||||
LOG.info("Starting BQP protocol");
|
LOG.info("Starting BQP protocol");
|
||||||
KeyAgreementProtocol protocol = new KeyAgreementProtocol(this, crypto,
|
KeyAgreementProtocol protocol = new KeyAgreementProtocol(this, crypto,
|
||||||
keyAgreementCrypto, payloadEncoder, transport, remotePayload,
|
payloadEncoder, transport, remotePayload, localPayload,
|
||||||
localPayload, localKeyPair, alice);
|
localKeyPair, alice);
|
||||||
try {
|
try {
|
||||||
SecretKey master = protocol.perform();
|
SecretKey master = protocol.perform();
|
||||||
KeyAgreementResult result =
|
KeyAgreementResult result =
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
|||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
|
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
|
||||||
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
|
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
|
||||||
|
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent;
|
||||||
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent;
|
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent;
|
||||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
@@ -25,6 +26,7 @@ import java.security.SecureRandom;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
@@ -50,7 +52,7 @@ class Poller implements EventListener {
|
|||||||
private final SecureRandom random;
|
private final SecureRandom random;
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
private final Lock lock;
|
private final Lock lock;
|
||||||
private final Map<TransportId, PollTask> tasks; // Locking: lock
|
private final Map<TransportId, ScheduledPollTask> tasks; // Locking: lock
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
Poller(@IoExecutor Executor ioExecutor,
|
Poller(@IoExecutor Executor ioExecutor,
|
||||||
@@ -93,6 +95,10 @@ class Poller implements EventListener {
|
|||||||
TransportEnabledEvent t = (TransportEnabledEvent) e;
|
TransportEnabledEvent t = (TransportEnabledEvent) e;
|
||||||
// Poll the newly enabled transport
|
// Poll the newly enabled transport
|
||||||
pollNow(t.getTransportId());
|
pollNow(t.getTransportId());
|
||||||
|
} else if (e instanceof TransportDisabledEvent) {
|
||||||
|
TransportDisabledEvent t = (TransportDisabledEvent) e;
|
||||||
|
// Cancel polling for the disabled transport
|
||||||
|
cancel(t.getTransportId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,18 +157,31 @@ class Poller implements EventListener {
|
|||||||
TransportId t = p.getId();
|
TransportId t = p.getId();
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
PollTask scheduled = tasks.get(t);
|
ScheduledPollTask scheduled = tasks.get(t);
|
||||||
if (scheduled == null || due < scheduled.due) {
|
if (scheduled == null || due < scheduled.task.due) {
|
||||||
|
// If a later task exists, cancel it. If it's already started
|
||||||
|
// it will abort safely when it finds it's been replaced
|
||||||
|
if (scheduled != null) scheduled.future.cancel(false);
|
||||||
PollTask task = new PollTask(p, due, randomiseNext);
|
PollTask task = new PollTask(p, due, randomiseNext);
|
||||||
tasks.put(t, task);
|
Future future = scheduler.schedule(
|
||||||
scheduler.schedule(
|
|
||||||
() -> ioExecutor.execute(task), delay, MILLISECONDS);
|
() -> ioExecutor.execute(task), delay, MILLISECONDS);
|
||||||
|
tasks.put(t, new ScheduledPollTask(task, future));
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void cancel(TransportId t) {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
ScheduledPollTask scheduled = tasks.remove(t);
|
||||||
|
if (scheduled != null) scheduled.future.cancel(false);
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@IoExecutor
|
@IoExecutor
|
||||||
private void poll(Plugin p) {
|
private void poll(Plugin p) {
|
||||||
TransportId t = p.getId();
|
TransportId t = p.getId();
|
||||||
@@ -170,6 +189,17 @@ class Poller implements EventListener {
|
|||||||
p.poll(connectionRegistry.getConnectedContacts(t));
|
p.poll(connectionRegistry.getConnectedContacts(t));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ScheduledPollTask {
|
||||||
|
|
||||||
|
private final PollTask task;
|
||||||
|
private final Future future;
|
||||||
|
|
||||||
|
private ScheduledPollTask(PollTask task, Future future) {
|
||||||
|
this.task = task;
|
||||||
|
this.future = future;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class PollTask implements Runnable {
|
private class PollTask implements Runnable {
|
||||||
|
|
||||||
private final Plugin plugin;
|
private final Plugin plugin;
|
||||||
@@ -188,7 +218,9 @@ class Poller implements EventListener {
|
|||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
TransportId t = plugin.getId();
|
TransportId t = plugin.getId();
|
||||||
if (tasks.get(t) != this) return; // Replaced by another task
|
ScheduledPollTask scheduled = tasks.get(t);
|
||||||
|
if (scheduled != null && scheduled.task != this)
|
||||||
|
return; // Replaced by another task
|
||||||
tasks.remove(t);
|
tasks.remove(t);
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import java.net.SocketAddress;
|
|||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
@@ -44,6 +44,9 @@ class LanTcpPlugin extends TcpPlugin {
|
|||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(LanTcpPlugin.class.getName());
|
Logger.getLogger(LanTcpPlugin.class.getName());
|
||||||
|
|
||||||
|
private static final LanAddressComparator ADDRESS_COMPARATOR =
|
||||||
|
new LanAddressComparator();
|
||||||
|
|
||||||
private static final int MAX_ADDRESSES = 4;
|
private static final int MAX_ADDRESSES = 4;
|
||||||
private static final String SEPARATOR = ",";
|
private static final String SEPARATOR = ",";
|
||||||
|
|
||||||
@@ -63,19 +66,18 @@ class LanTcpPlugin extends TcpPlugin {
|
|||||||
TransportProperties p = callback.getLocalProperties();
|
TransportProperties p = callback.getLocalProperties();
|
||||||
String oldIpPorts = p.get(PROP_IP_PORTS);
|
String oldIpPorts = p.get(PROP_IP_PORTS);
|
||||||
List<InetSocketAddress> olds = parseSocketAddresses(oldIpPorts);
|
List<InetSocketAddress> olds = parseSocketAddresses(oldIpPorts);
|
||||||
List<InetSocketAddress> locals = new LinkedList<>();
|
List<InetSocketAddress> locals = new ArrayList<>();
|
||||||
for (InetAddress local : getLocalIpAddresses()) {
|
for (InetAddress local : getLocalIpAddresses()) {
|
||||||
if (isAcceptableAddress(local)) {
|
if (isAcceptableAddress(local)) {
|
||||||
// If this is the old address, try to use the same port
|
// If this is the old address, try to use the same port
|
||||||
for (InetSocketAddress old : olds) {
|
for (InetSocketAddress old : olds) {
|
||||||
if (old.getAddress().equals(local)) {
|
if (old.getAddress().equals(local))
|
||||||
int port = old.getPort();
|
locals.add(new InetSocketAddress(local, old.getPort()));
|
||||||
locals.add(0, new InetSocketAddress(local, port));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
locals.add(new InetSocketAddress(local, 0));
|
locals.add(new InetSocketAddress(local, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Collections.sort(locals, ADDRESS_COMPARATOR);
|
||||||
return locals;
|
return locals;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,17 +155,39 @@ class LanTcpPlugin extends TcpPlugin {
|
|||||||
// Package access for testing
|
// Package access for testing
|
||||||
boolean addressesAreOnSameLan(byte[] localIp, byte[] remoteIp) {
|
boolean addressesAreOnSameLan(byte[] localIp, byte[] remoteIp) {
|
||||||
// 10.0.0.0/8
|
// 10.0.0.0/8
|
||||||
if (localIp[0] == 10) return remoteIp[0] == 10;
|
if (isPrefix10(localIp)) return isPrefix10(remoteIp);
|
||||||
// 172.16.0.0/12
|
// 172.16.0.0/12
|
||||||
if (localIp[0] == (byte) 172 && (localIp[1] & 0xF0) == 16)
|
if (isPrefix172(localIp)) return isPrefix172(remoteIp);
|
||||||
return remoteIp[0] == (byte) 172 && (remoteIp[1] & 0xF0) == 16;
|
|
||||||
// 192.168.0.0/16
|
// 192.168.0.0/16
|
||||||
if (localIp[0] == (byte) 192 && localIp[1] == (byte) 168)
|
if (isPrefix192(localIp)) return isPrefix192(remoteIp);
|
||||||
return remoteIp[0] == (byte) 192 && remoteIp[1] == (byte) 168;
|
|
||||||
// Unrecognised prefix - may be compatible
|
// Unrecognised prefix - may be compatible
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isPrefix10(byte[] ipv4) {
|
||||||
|
return ipv4[0] == 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isPrefix172(byte[] ipv4) {
|
||||||
|
return ipv4[0] == (byte) 172 && (ipv4[1] & 0xF0) == 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isPrefix192(byte[] ipv4) {
|
||||||
|
return ipv4[0] == (byte) 192 && ipv4[1] == (byte) 168;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the prefix length for an RFC 1918 address, or 0 for any other
|
||||||
|
// address
|
||||||
|
private static int getRfc1918PrefixLength(InetAddress addr) {
|
||||||
|
if (!(addr instanceof Inet4Address)) return 0;
|
||||||
|
if (!addr.isSiteLocalAddress()) return 0;
|
||||||
|
byte[] ipv4 = addr.getAddress();
|
||||||
|
if (isPrefix10(ipv4)) return 8;
|
||||||
|
if (isPrefix172(ipv4)) return 12;
|
||||||
|
if (isPrefix192(ipv4)) return 16;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsKeyAgreement() {
|
public boolean supportsKeyAgreement() {
|
||||||
return true;
|
return true;
|
||||||
@@ -278,4 +302,19 @@ class LanTcpPlugin extends TcpPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class LanAddressComparator implements Comparator<InetSocketAddress> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(InetSocketAddress a, InetSocketAddress b) {
|
||||||
|
// Prefer addresses with non-zero ports
|
||||||
|
int aPort = a.getPort(), bPort = b.getPort();
|
||||||
|
if (aPort > 0 && bPort == 0) return -1;
|
||||||
|
if (aPort == 0 && bPort > 0) return 1;
|
||||||
|
// Prefer addresses with longer RFC 1918 prefixes
|
||||||
|
int aPrefix = getRfc1918PrefixLength(a.getAddress());
|
||||||
|
int bPrefix = getRfc1918PrefixLength(b.getAddress());
|
||||||
|
return bPrefix - aPrefix;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,12 +58,12 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
this.metadataParser = metadataParser;
|
this.metadataParser = metadataParser;
|
||||||
this.contactGroupFactory = contactGroupFactory;
|
this.contactGroupFactory = contactGroupFactory;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
|
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID);
|
||||||
CLIENT_VERSION);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createLocalState(Transaction txn) throws DbException {
|
public void createLocalState(Transaction txn) throws DbException {
|
||||||
|
if (db.containsGroup(txn, localGroup.getId())) return;
|
||||||
db.addGroup(txn, localGroup);
|
db.addGroup(txn, localGroup);
|
||||||
// Ensure we've set things up for any pre-existing contacts
|
// Ensure we've set things up for any pre-existing contacts
|
||||||
for (Contact c : db.getContacts(txn)) addingContact(txn, c);
|
for (Contact c : db.getContacts(txn)) addingContact(txn, c);
|
||||||
@@ -130,7 +130,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
public Map<TransportId, TransportProperties> getLocalProperties()
|
public Map<TransportId, TransportProperties> getLocalProperties()
|
||||||
throws DbException {
|
throws DbException {
|
||||||
Map<TransportId, TransportProperties> local;
|
Map<TransportId, TransportProperties> local;
|
||||||
Transaction txn = db.startTransaction(true);
|
// TODO: Transaction can be read-only when code is simplified
|
||||||
|
Transaction txn = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
local = getLocalProperties(txn);
|
local = getLocalProperties(txn);
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
@@ -165,7 +166,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
throws DbException {
|
throws DbException {
|
||||||
try {
|
try {
|
||||||
TransportProperties p = null;
|
TransportProperties p = null;
|
||||||
Transaction txn = db.startTransaction(true);
|
// TODO: Transaction can be read-only when code is simplified
|
||||||
|
Transaction txn = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
// Find the latest local update
|
// Find the latest local update
|
||||||
LatestUpdate latest = findLatest(txn, localGroup.getId(), t,
|
LatestUpdate latest = findLatest(txn, localGroup.getId(), t,
|
||||||
@@ -191,7 +193,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
public Map<ContactId, TransportProperties> getRemoteProperties(
|
public Map<ContactId, TransportProperties> getRemoteProperties(
|
||||||
TransportId t) throws DbException {
|
TransportId t) throws DbException {
|
||||||
Map<ContactId, TransportProperties> remote = new HashMap<>();
|
Map<ContactId, TransportProperties> remote = new HashMap<>();
|
||||||
Transaction txn = db.startTransaction(true);
|
// TODO: Transaction can be read-only when code is simplified
|
||||||
|
Transaction txn = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
for (Contact c : db.getContacts(txn))
|
for (Contact c : db.getContacts(txn))
|
||||||
remote.put(c.getId(), getRemoteProperties(txn, c, t));
|
remote.put(c.getId(), getRemoteProperties(txn, c, t));
|
||||||
@@ -225,7 +228,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
public TransportProperties getRemoteProperties(ContactId c, TransportId t)
|
public TransportProperties getRemoteProperties(ContactId c, TransportId t)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
TransportProperties p;
|
TransportProperties p;
|
||||||
Transaction txn = db.startTransaction(true);
|
// TODO: Transaction can be read-only when code is simplified
|
||||||
|
Transaction txn = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
p = getRemoteProperties(txn, db.getContact(txn, c), t);
|
p = getRemoteProperties(txn, db.getContact(txn, c), t);
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
@@ -288,8 +292,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Group getContactGroup(Contact c) {
|
private Group getContactGroup(Contact c) {
|
||||||
return contactGroupFactory.createContactGroup(CLIENT_ID,
|
return contactGroupFactory.createContactGroup(CLIENT_ID, c);
|
||||||
CLIENT_VERSION, c);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void storeMessage(Transaction txn, GroupId g, TransportId t,
|
private void storeMessage(Transaction txn, GroupId g, TransportId t,
|
||||||
@@ -316,6 +319,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
|
|
||||||
private Map<TransportId, LatestUpdate> findLatestLocal(Transaction txn)
|
private Map<TransportId, LatestUpdate> findLatestLocal(Transaction txn)
|
||||||
throws DbException, FormatException {
|
throws DbException, FormatException {
|
||||||
|
// TODO: This can be simplified before 1.0
|
||||||
Map<TransportId, LatestUpdate> latestUpdates = new HashMap<>();
|
Map<TransportId, LatestUpdate> latestUpdates = new HashMap<>();
|
||||||
Map<MessageId, BdfDictionary> metadata = clientHelper
|
Map<MessageId, BdfDictionary> metadata = clientHelper
|
||||||
.getMessageMetadataAsDictionary(txn, localGroup.getId());
|
.getMessageMetadataAsDictionary(txn, localGroup.getId());
|
||||||
@@ -323,7 +327,17 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
BdfDictionary meta = e.getValue();
|
BdfDictionary meta = e.getValue();
|
||||||
TransportId t = new TransportId(meta.getString("transportId"));
|
TransportId t = new TransportId(meta.getString("transportId"));
|
||||||
long version = meta.getLong("version");
|
long version = meta.getLong("version");
|
||||||
latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
|
LatestUpdate latest = latestUpdates.get(t);
|
||||||
|
if (latest == null) {
|
||||||
|
latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
|
||||||
|
} else if (version > latest.version) {
|
||||||
|
// This update is newer - delete the previous one
|
||||||
|
db.removeMessage(txn, latest.messageId);
|
||||||
|
latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
|
||||||
|
} else {
|
||||||
|
// We've already found a newer update - delete this one
|
||||||
|
db.removeMessage(txn, e.getKey());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return latestUpdates;
|
return latestUpdates;
|
||||||
}
|
}
|
||||||
@@ -331,16 +345,38 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
@Nullable
|
@Nullable
|
||||||
private LatestUpdate findLatest(Transaction txn, GroupId g, TransportId t,
|
private LatestUpdate findLatest(Transaction txn, GroupId g, TransportId t,
|
||||||
boolean local) throws DbException, FormatException {
|
boolean local) throws DbException, FormatException {
|
||||||
|
// TODO: This can be simplified before 1.0
|
||||||
|
LatestUpdate latest = null;
|
||||||
Map<MessageId, BdfDictionary> metadata =
|
Map<MessageId, BdfDictionary> metadata =
|
||||||
clientHelper.getMessageMetadataAsDictionary(txn, g);
|
clientHelper.getMessageMetadataAsDictionary(txn, g);
|
||||||
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
|
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
|
||||||
BdfDictionary meta = e.getValue();
|
BdfDictionary meta = e.getValue();
|
||||||
if (meta.getString("transportId").equals(t.getString())
|
if (meta.getString("transportId").equals(t.getString())
|
||||||
&& meta.getBoolean("local") == local) {
|
&& meta.getBoolean("local") == local) {
|
||||||
return new LatestUpdate(e.getKey(), meta.getLong("version"));
|
long version = meta.getLong("version");
|
||||||
|
if (latest == null) {
|
||||||
|
latest = new LatestUpdate(e.getKey(), version);
|
||||||
|
} else if (version > latest.version) {
|
||||||
|
// This update is newer - delete the previous one
|
||||||
|
if (local) {
|
||||||
|
db.removeMessage(txn, latest.messageId);
|
||||||
|
} else {
|
||||||
|
db.deleteMessage(txn, latest.messageId);
|
||||||
|
db.deleteMessageMetadata(txn, latest.messageId);
|
||||||
|
}
|
||||||
|
latest = new LatestUpdate(e.getKey(), version);
|
||||||
|
} else {
|
||||||
|
// We've already found a newer update - delete this one
|
||||||
|
if (local) {
|
||||||
|
db.removeMessage(txn, e.getKey());
|
||||||
|
} else {
|
||||||
|
db.deleteMessage(txn, e.getKey());
|
||||||
|
db.deleteMessageMetadata(txn, e.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return latest;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TransportProperties parseProperties(BdfList message)
|
private TransportProperties parseProperties(BdfList message)
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import java.util.Collection;
|
|||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
@@ -54,7 +55,8 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(DuplexOutgoingSession.class.getName());
|
Logger.getLogger(DuplexOutgoingSession.class.getName());
|
||||||
|
|
||||||
private static final ThrowingRunnable<IOException> CLOSE = () -> {};
|
private static final ThrowingRunnable<IOException> CLOSE = () -> {
|
||||||
|
};
|
||||||
|
|
||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
private final Executor dbExecutor;
|
private final Executor dbExecutor;
|
||||||
@@ -65,6 +67,12 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
private final RecordWriter recordWriter;
|
private final RecordWriter recordWriter;
|
||||||
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
|
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
|
||||||
|
|
||||||
|
private final AtomicBoolean generateAckQueued = new AtomicBoolean(false);
|
||||||
|
private final AtomicBoolean generateBatchQueued = new AtomicBoolean(false);
|
||||||
|
private final AtomicBoolean generateOfferQueued = new AtomicBoolean(false);
|
||||||
|
private final AtomicBoolean generateRequestQueued =
|
||||||
|
new AtomicBoolean(false);
|
||||||
|
|
||||||
private volatile boolean interrupted = false;
|
private volatile boolean interrupted = false;
|
||||||
|
|
||||||
DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
|
DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
|
||||||
@@ -87,10 +95,10 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
eventBus.addListener(this);
|
eventBus.addListener(this);
|
||||||
try {
|
try {
|
||||||
// Start a query for each type of record
|
// Start a query for each type of record
|
||||||
dbExecutor.execute(new GenerateAck());
|
generateAck();
|
||||||
dbExecutor.execute(new GenerateBatch());
|
generateBatch();
|
||||||
dbExecutor.execute(new GenerateOffer());
|
generateOffer();
|
||||||
dbExecutor.execute(new GenerateRequest());
|
generateRequest();
|
||||||
long now = clock.currentTimeMillis();
|
long now = clock.currentTimeMillis();
|
||||||
long nextKeepalive = now + maxIdleTime;
|
long nextKeepalive = now + maxIdleTime;
|
||||||
long nextRetxQuery = now + RETX_QUERY_INTERVAL;
|
long nextRetxQuery = now + RETX_QUERY_INTERVAL;
|
||||||
@@ -115,8 +123,8 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
now = clock.currentTimeMillis();
|
now = clock.currentTimeMillis();
|
||||||
if (now >= nextRetxQuery) {
|
if (now >= nextRetxQuery) {
|
||||||
// Check for retransmittable records
|
// Check for retransmittable records
|
||||||
dbExecutor.execute(new GenerateBatch());
|
generateBatch();
|
||||||
dbExecutor.execute(new GenerateOffer());
|
generateOffer();
|
||||||
nextRetxQuery = now + RETX_QUERY_INTERVAL;
|
nextRetxQuery = now + RETX_QUERY_INTERVAL;
|
||||||
}
|
}
|
||||||
if (now >= nextKeepalive) {
|
if (now >= nextKeepalive) {
|
||||||
@@ -142,6 +150,26 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void generateAck() {
|
||||||
|
if (generateAckQueued.compareAndSet(false, true))
|
||||||
|
dbExecutor.execute(new GenerateAck());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void generateBatch() {
|
||||||
|
if (generateBatchQueued.compareAndSet(false, true))
|
||||||
|
dbExecutor.execute(new GenerateBatch());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void generateOffer() {
|
||||||
|
if (generateOfferQueued.compareAndSet(false, true))
|
||||||
|
dbExecutor.execute(new GenerateOffer());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void generateRequest() {
|
||||||
|
if (generateRequestQueued.compareAndSet(false, true))
|
||||||
|
dbExecutor.execute(new GenerateRequest());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void interrupt() {
|
public void interrupt() {
|
||||||
interrupted = true;
|
interrupted = true;
|
||||||
@@ -154,20 +182,20 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
||||||
if (c.getContactId().equals(contactId)) interrupt();
|
if (c.getContactId().equals(contactId)) interrupt();
|
||||||
} else if (e instanceof MessageSharedEvent) {
|
} else if (e instanceof MessageSharedEvent) {
|
||||||
dbExecutor.execute(new GenerateOffer());
|
generateOffer();
|
||||||
} else if (e instanceof GroupVisibilityUpdatedEvent) {
|
} else if (e instanceof GroupVisibilityUpdatedEvent) {
|
||||||
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
|
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
|
||||||
if (g.getAffectedContacts().contains(contactId))
|
if (g.getAffectedContacts().contains(contactId))
|
||||||
dbExecutor.execute(new GenerateOffer());
|
generateOffer();
|
||||||
} else if (e instanceof MessageRequestedEvent) {
|
} else if (e instanceof MessageRequestedEvent) {
|
||||||
if (((MessageRequestedEvent) e).getContactId().equals(contactId))
|
if (((MessageRequestedEvent) e).getContactId().equals(contactId))
|
||||||
dbExecutor.execute(new GenerateBatch());
|
generateBatch();
|
||||||
} else if (e instanceof MessageToAckEvent) {
|
} else if (e instanceof MessageToAckEvent) {
|
||||||
if (((MessageToAckEvent) e).getContactId().equals(contactId))
|
if (((MessageToAckEvent) e).getContactId().equals(contactId))
|
||||||
dbExecutor.execute(new GenerateAck());
|
generateAck();
|
||||||
} else if (e instanceof MessageToRequestEvent) {
|
} else if (e instanceof MessageToRequestEvent) {
|
||||||
if (((MessageToRequestEvent) e).getContactId().equals(contactId))
|
if (((MessageToRequestEvent) e).getContactId().equals(contactId))
|
||||||
dbExecutor.execute(new GenerateRequest());
|
generateRequest();
|
||||||
} else if (e instanceof ShutdownEvent) {
|
} else if (e instanceof ShutdownEvent) {
|
||||||
interrupt();
|
interrupt();
|
||||||
}
|
}
|
||||||
@@ -179,6 +207,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (interrupted) return;
|
if (interrupted) return;
|
||||||
|
if (!generateAckQueued.getAndSet(false)) throw new AssertionError();
|
||||||
try {
|
try {
|
||||||
Ack a;
|
Ack a;
|
||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
@@ -212,7 +241,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
if (interrupted) return;
|
if (interrupted) return;
|
||||||
recordWriter.writeAck(ack);
|
recordWriter.writeAck(ack);
|
||||||
LOG.info("Sent ack");
|
LOG.info("Sent ack");
|
||||||
dbExecutor.execute(new GenerateAck());
|
generateAck();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,6 +251,8 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (interrupted) return;
|
if (interrupted) return;
|
||||||
|
if (!generateBatchQueued.getAndSet(false))
|
||||||
|
throw new AssertionError();
|
||||||
try {
|
try {
|
||||||
Collection<byte[]> b;
|
Collection<byte[]> b;
|
||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
@@ -256,7 +287,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
if (interrupted) return;
|
if (interrupted) return;
|
||||||
for (byte[] raw : batch) recordWriter.writeMessage(raw);
|
for (byte[] raw : batch) recordWriter.writeMessage(raw);
|
||||||
LOG.info("Sent batch");
|
LOG.info("Sent batch");
|
||||||
dbExecutor.execute(new GenerateBatch());
|
generateBatch();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,6 +297,8 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (interrupted) return;
|
if (interrupted) return;
|
||||||
|
if (!generateOfferQueued.getAndSet(false))
|
||||||
|
throw new AssertionError();
|
||||||
try {
|
try {
|
||||||
Offer o;
|
Offer o;
|
||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
@@ -300,7 +333,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
if (interrupted) return;
|
if (interrupted) return;
|
||||||
recordWriter.writeOffer(offer);
|
recordWriter.writeOffer(offer);
|
||||||
LOG.info("Sent offer");
|
LOG.info("Sent offer");
|
||||||
dbExecutor.execute(new GenerateOffer());
|
generateOffer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,6 +343,8 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (interrupted) return;
|
if (interrupted) return;
|
||||||
|
if (!generateRequestQueued.getAndSet(false))
|
||||||
|
throw new AssertionError();
|
||||||
try {
|
try {
|
||||||
Request r;
|
Request r;
|
||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
@@ -343,7 +378,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
if (interrupted) return;
|
if (interrupted) return;
|
||||||
recordWriter.writeRequest(request);
|
recordWriter.writeRequest(request);
|
||||||
LOG.info("Sent request");
|
LOG.info("Sent request");
|
||||||
dbExecutor.execute(new GenerateRequest());
|
generateRequest();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,16 +6,11 @@ import org.briarproject.bramble.api.sync.ClientId;
|
|||||||
import org.briarproject.bramble.api.sync.Group;
|
import org.briarproject.bramble.api.sync.Group;
|
||||||
import org.briarproject.bramble.api.sync.GroupFactory;
|
import org.briarproject.bramble.api.sync.GroupFactory;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.util.ByteUtils;
|
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.sync.GroupId.LABEL;
|
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
|
|
||||||
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class GroupFactoryImpl implements GroupFactory {
|
class GroupFactoryImpl implements GroupFactory {
|
||||||
@@ -28,12 +23,9 @@ class GroupFactoryImpl implements GroupFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Group createGroup(ClientId c, int clientVersion, byte[] descriptor) {
|
public Group createGroup(ClientId c, byte[] descriptor) {
|
||||||
byte[] clientVersionBytes = new byte[INT_32_BYTES];
|
byte[] hash = crypto.hash(GroupId.LABEL,
|
||||||
ByteUtils.writeUint32(clientVersion, clientVersionBytes, 0);
|
StringUtils.toUtf8(c.getString()), descriptor);
|
||||||
byte[] hash = crypto.hash(LABEL, new byte[] {PROTOCOL_VERSION},
|
|
||||||
StringUtils.toUtf8(c.getString()), clientVersionBytes,
|
|
||||||
descriptor);
|
|
||||||
return new Group(new GroupId(hash), c, descriptor);
|
return new Group(new GroupId(hash), c, descriptor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,10 +12,8 @@ import org.briarproject.bramble.util.ByteUtils;
|
|||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.sync.MessageId.LABEL;
|
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
@@ -34,9 +32,9 @@ class MessageFactoryImpl implements MessageFactory {
|
|||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
byte[] timeBytes = new byte[ByteUtils.INT_64_BYTES];
|
byte[] timeBytes = new byte[ByteUtils.INT_64_BYTES];
|
||||||
ByteUtils.writeUint64(timestamp, timeBytes, 0);
|
ByteUtils.writeUint64(timestamp, timeBytes, 0);
|
||||||
byte[] hash = crypto.hash(LABEL, new byte[] {PROTOCOL_VERSION},
|
byte[] idHash =
|
||||||
g.getBytes(), timeBytes, body);
|
crypto.hash(MessageId.LABEL, g.getBytes(), timeBytes, body);
|
||||||
MessageId id = new MessageId(hash);
|
MessageId id = new MessageId(idHash);
|
||||||
byte[] raw = new byte[MESSAGE_HEADER_LENGTH + body.length];
|
byte[] raw = new byte[MESSAGE_HEADER_LENGTH + body.length];
|
||||||
System.arraycopy(g.getBytes(), 0, raw, 0, UniqueId.LENGTH);
|
System.arraycopy(g.getBytes(), 0, raw, 0, UniqueId.LENGTH);
|
||||||
ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH);
|
ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package org.briarproject.bramble.transport;
|
package org.briarproject.bramble.transport;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
@@ -20,18 +20,17 @@ class TransportKeyManagerFactoryImpl implements
|
|||||||
TransportKeyManagerFactory {
|
TransportKeyManagerFactory {
|
||||||
|
|
||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
private final TransportCrypto transportCrypto;
|
private final CryptoComponent crypto;
|
||||||
private final Executor dbExecutor;
|
private final Executor dbExecutor;
|
||||||
private final ScheduledExecutorService scheduler;
|
private final ScheduledExecutorService scheduler;
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
TransportKeyManagerFactoryImpl(DatabaseComponent db,
|
TransportKeyManagerFactoryImpl(DatabaseComponent db, CryptoComponent crypto,
|
||||||
TransportCrypto transportCrypto,
|
|
||||||
@DatabaseExecutor Executor dbExecutor,
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
@Scheduler ScheduledExecutorService scheduler, Clock clock) {
|
@Scheduler ScheduledExecutorService scheduler, Clock clock) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.transportCrypto = transportCrypto;
|
this.crypto = crypto;
|
||||||
this.dbExecutor = dbExecutor;
|
this.dbExecutor = dbExecutor;
|
||||||
this.scheduler = scheduler;
|
this.scheduler = scheduler;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
@@ -40,8 +39,8 @@ class TransportKeyManagerFactoryImpl implements
|
|||||||
@Override
|
@Override
|
||||||
public TransportKeyManager createTransportKeyManager(
|
public TransportKeyManager createTransportKeyManager(
|
||||||
TransportId transportId, long maxLatency) {
|
TransportId transportId, long maxLatency) {
|
||||||
return new TransportKeyManagerImpl(db, transportCrypto, dbExecutor,
|
return new TransportKeyManagerImpl(db, crypto, dbExecutor, scheduler,
|
||||||
scheduler, clock, transportId, maxLatency);
|
clock, transportId, maxLatency);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ package org.briarproject.bramble.transport;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.Bytes;
|
import org.briarproject.bramble.api.Bytes;
|
||||||
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.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
|
||||||
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;
|
||||||
@@ -41,7 +41,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
|||||||
Logger.getLogger(TransportKeyManagerImpl.class.getName());
|
Logger.getLogger(TransportKeyManagerImpl.class.getName());
|
||||||
|
|
||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
private final TransportCrypto transportCrypto;
|
private final CryptoComponent crypto;
|
||||||
private final Executor dbExecutor;
|
private final Executor dbExecutor;
|
||||||
private final ScheduledExecutorService scheduler;
|
private final ScheduledExecutorService scheduler;
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
@@ -54,12 +54,11 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
|||||||
private final Map<ContactId, MutableOutgoingKeys> outContexts;
|
private final Map<ContactId, MutableOutgoingKeys> outContexts;
|
||||||
private final Map<ContactId, MutableTransportKeys> keys;
|
private final Map<ContactId, MutableTransportKeys> keys;
|
||||||
|
|
||||||
TransportKeyManagerImpl(DatabaseComponent db,
|
TransportKeyManagerImpl(DatabaseComponent db, CryptoComponent crypto,
|
||||||
TransportCrypto transportCrypto, Executor dbExecutor,
|
Executor dbExecutor, @Scheduler ScheduledExecutorService scheduler,
|
||||||
@Scheduler ScheduledExecutorService scheduler, Clock clock,
|
Clock clock, TransportId transportId, long maxLatency) {
|
||||||
TransportId transportId, long maxLatency) {
|
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.transportCrypto = transportCrypto;
|
this.crypto = crypto;
|
||||||
this.dbExecutor = dbExecutor;
|
this.dbExecutor = dbExecutor;
|
||||||
this.scheduler = scheduler;
|
this.scheduler = scheduler;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
@@ -100,8 +99,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
|||||||
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
|
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
|
||||||
ContactId c = e.getKey();
|
ContactId c = e.getKey();
|
||||||
TransportKeys k = e.getValue();
|
TransportKeys k = e.getValue();
|
||||||
TransportKeys k1 =
|
TransportKeys k1 = crypto.rotateTransportKeys(k, rotationPeriod);
|
||||||
transportCrypto.rotateTransportKeys(k, rotationPeriod);
|
|
||||||
if (k1.getRotationPeriod() > k.getRotationPeriod())
|
if (k1.getRotationPeriod() > k.getRotationPeriod())
|
||||||
rotationResult.rotated.put(c, k1);
|
rotationResult.rotated.put(c, k1);
|
||||||
rotationResult.current.put(c, k1);
|
rotationResult.current.put(c, k1);
|
||||||
@@ -129,7 +127,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
|||||||
for (long streamNumber : inKeys.getWindow().getUnseen()) {
|
for (long streamNumber : inKeys.getWindow().getUnseen()) {
|
||||||
TagContext tagCtx = new TagContext(c, inKeys, streamNumber);
|
TagContext tagCtx = new TagContext(c, inKeys, streamNumber);
|
||||||
byte[] tag = new byte[TAG_LENGTH];
|
byte[] tag = new byte[TAG_LENGTH];
|
||||||
transportCrypto.encodeTag(tag, inKeys.getTagKey(), PROTOCOL_VERSION,
|
crypto.encodeTag(tag, inKeys.getTagKey(), PROTOCOL_VERSION,
|
||||||
streamNumber);
|
streamNumber);
|
||||||
inContexts.put(new Bytes(tag), tagCtx);
|
inContexts.put(new Bytes(tag), tagCtx);
|
||||||
}
|
}
|
||||||
@@ -164,11 +162,11 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
|||||||
// Work out what rotation period the timestamp belongs to
|
// Work out what rotation period the timestamp belongs to
|
||||||
long rotationPeriod = timestamp / rotationPeriodLength;
|
long rotationPeriod = timestamp / rotationPeriodLength;
|
||||||
// Derive the transport keys
|
// Derive the transport keys
|
||||||
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
TransportKeys k = crypto.deriveTransportKeys(transportId, master,
|
||||||
master, rotationPeriod, alice);
|
rotationPeriod, alice);
|
||||||
// Rotate the keys to the current rotation period if necessary
|
// Rotate the keys to the current rotation period if necessary
|
||||||
rotationPeriod = clock.currentTimeMillis() / rotationPeriodLength;
|
rotationPeriod = clock.currentTimeMillis() / rotationPeriodLength;
|
||||||
k = transportCrypto.rotateTransportKeys(k, rotationPeriod);
|
k = crypto.rotateTransportKeys(k, rotationPeriod);
|
||||||
// Initialise mutable state for the contact
|
// Initialise mutable state for the contact
|
||||||
addKeys(c, new MutableTransportKeys(k));
|
addKeys(c, new MutableTransportKeys(k));
|
||||||
// Write the keys back to the DB
|
// Write the keys back to the DB
|
||||||
@@ -236,8 +234,8 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
|||||||
// Add tags for any stream numbers added to the window
|
// Add tags for any stream numbers added to the window
|
||||||
for (long streamNumber : change.getAdded()) {
|
for (long streamNumber : change.getAdded()) {
|
||||||
byte[] addTag = new byte[TAG_LENGTH];
|
byte[] addTag = new byte[TAG_LENGTH];
|
||||||
transportCrypto.encodeTag(addTag, inKeys.getTagKey(),
|
crypto.encodeTag(addTag, inKeys.getTagKey(), PROTOCOL_VERSION,
|
||||||
PROTOCOL_VERSION, streamNumber);
|
streamNumber);
|
||||||
inContexts.put(new Bytes(addTag), new TagContext(
|
inContexts.put(new Bytes(addTag), new TagContext(
|
||||||
tagCtx.contactId, inKeys, streamNumber));
|
tagCtx.contactId, inKeys, streamNumber));
|
||||||
}
|
}
|
||||||
@@ -245,7 +243,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
|||||||
for (long streamNumber : change.getRemoved()) {
|
for (long streamNumber : change.getRemoved()) {
|
||||||
if (streamNumber == tagCtx.streamNumber) continue;
|
if (streamNumber == tagCtx.streamNumber) continue;
|
||||||
byte[] removeTag = new byte[TAG_LENGTH];
|
byte[] removeTag = new byte[TAG_LENGTH];
|
||||||
transportCrypto.encodeTag(removeTag, inKeys.getTagKey(),
|
crypto.encodeTag(removeTag, inKeys.getTagKey(),
|
||||||
PROTOCOL_VERSION, streamNumber);
|
PROTOCOL_VERSION, streamNumber);
|
||||||
inContexts.remove(new Bytes(removeTag));
|
inContexts.remove(new Bytes(removeTag));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
package org.briarproject.bramble.crypto;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
|
||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
|
|
||||||
public class EcdsaSignatureTest extends SignatureTest {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected KeyPair generateKeyPair() {
|
|
||||||
return crypto.generateSignatureKeyPair();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected byte[] sign(String label, byte[] toSign, byte[] privateKey)
|
|
||||||
throws GeneralSecurityException {
|
|
||||||
return crypto.sign(label, toSign, privateKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean verify(String label, byte[] signedData, byte[] publicKey,
|
|
||||||
byte[] signature) throws GeneralSecurityException {
|
|
||||||
return crypto.verify(label, signedData, publicKey, signature);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package org.briarproject.bramble.crypto;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
|
||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
|
|
||||||
public class EdSignatureTest extends SignatureTest {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected KeyPair generateKeyPair() {
|
|
||||||
return crypto.generateEdKeyPair();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected byte[] sign(String label, byte[] toSign, byte[] privateKey)
|
|
||||||
throws GeneralSecurityException {
|
|
||||||
return crypto.signEd(label, toSign, privateKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean verify(String label, byte[] signedData, byte[] publicKey,
|
|
||||||
byte[] signature) throws GeneralSecurityException {
|
|
||||||
return crypto.verifyEd(label, signedData, publicKey, signature);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +1,16 @@
|
|||||||
package org.briarproject.bramble.crypto;
|
package org.briarproject.bramble.crypto;
|
||||||
|
|
||||||
import net.i2p.crypto.eddsa.EdDSASecurityProvider;
|
|
||||||
import net.i2p.crypto.eddsa.KeyPairGenerator;
|
|
||||||
|
|
||||||
import org.spongycastle.asn1.sec.SECNamedCurves;
|
import org.spongycastle.asn1.sec.SECNamedCurves;
|
||||||
import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves;
|
import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves;
|
||||||
import org.spongycastle.asn1.x9.X9ECParameters;
|
import org.spongycastle.asn1.x9.X9ECParameters;
|
||||||
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
|
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
|
||||||
import org.spongycastle.crypto.BasicAgreement;
|
|
||||||
import org.spongycastle.crypto.Digest;
|
import org.spongycastle.crypto.Digest;
|
||||||
import org.spongycastle.crypto.agreement.ECDHBasicAgreement;
|
|
||||||
import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
|
import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
|
||||||
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
|
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
|
||||||
import org.spongycastle.crypto.params.ECDomainParameters;
|
import org.spongycastle.crypto.params.ECDomainParameters;
|
||||||
import org.spongycastle.crypto.params.ECKeyGenerationParameters;
|
import org.spongycastle.crypto.params.ECKeyGenerationParameters;
|
||||||
|
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
|
||||||
|
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
||||||
import org.spongycastle.crypto.params.ParametersWithRandom;
|
import org.spongycastle.crypto.params.ParametersWithRandom;
|
||||||
import org.spongycastle.crypto.signers.DSADigestSigner;
|
import org.spongycastle.crypto.signers.DSADigestSigner;
|
||||||
import org.spongycastle.crypto.signers.DSAKCalculator;
|
import org.spongycastle.crypto.signers.DSAKCalculator;
|
||||||
@@ -22,23 +19,14 @@ import org.spongycastle.crypto.signers.HMacDSAKCalculator;
|
|||||||
import org.spongycastle.math.ec.ECCurve;
|
import org.spongycastle.math.ec.ECCurve;
|
||||||
import org.spongycastle.math.ec.ECPoint;
|
import org.spongycastle.math.ec.ECPoint;
|
||||||
import org.spongycastle.math.ec.MontgomeryLadderMultiplier;
|
import org.spongycastle.math.ec.MontgomeryLadderMultiplier;
|
||||||
import org.whispersystems.curve25519.Curve25519;
|
|
||||||
import org.whispersystems.curve25519.Curve25519KeyPair;
|
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.security.Provider;
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.Signature;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static net.i2p.crypto.eddsa.EdDSAEngine.SIGNATURE_ALGORITHM;
|
|
||||||
import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS;
|
|
||||||
|
|
||||||
// Not a JUnit test
|
// Not a JUnit test
|
||||||
public class EllipticCurvePerformanceTest {
|
public class EllipticCurvePerformanceTest {
|
||||||
|
|
||||||
@@ -49,9 +37,8 @@ public class EllipticCurvePerformanceTest {
|
|||||||
"secp256k1", "secp256r1", "secp384r1", "secp521r1");
|
"secp256k1", "secp256r1", "secp384r1", "secp521r1");
|
||||||
private static final List<String> BRAINPOOL_NAMES = Arrays.asList(
|
private static final List<String> BRAINPOOL_NAMES = Arrays.asList(
|
||||||
"brainpoolp256r1", "brainpoolp384r1", "brainpoolp512r1");
|
"brainpoolp256r1", "brainpoolp384r1", "brainpoolp512r1");
|
||||||
private static final Provider ED_PROVIDER = new EdDSASecurityProvider();
|
|
||||||
|
|
||||||
public static void main(String[] args) throws GeneralSecurityException {
|
public static void main(String[] args) {
|
||||||
for (String name : SEC_NAMES) {
|
for (String name : SEC_NAMES) {
|
||||||
ECDomainParameters params =
|
ECDomainParameters params =
|
||||||
convertParams(SECNamedCurves.getByName(name));
|
convertParams(SECNamedCurves.getByName(name));
|
||||||
@@ -64,32 +51,43 @@ public class EllipticCurvePerformanceTest {
|
|||||||
runTest(name + " default", params);
|
runTest(name + " default", params);
|
||||||
runTest(name + " constant", constantTime(params));
|
runTest(name + " constant", constantTime(params));
|
||||||
}
|
}
|
||||||
runTest("ours", PARAMETERS);
|
runTest("ours", EllipticCurveConstants.PARAMETERS);
|
||||||
runCurve25519Test();
|
|
||||||
runEd25519Test();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void runTest(String name, ECDomainParameters params) {
|
private static void runTest(String name, ECDomainParameters params) {
|
||||||
// Generate two key pairs using the given parameters
|
// Generate two key pairs using the given parameters
|
||||||
|
ECKeyGenerationParameters generatorParams =
|
||||||
|
new ECKeyGenerationParameters(params, random);
|
||||||
ECKeyPairGenerator generator = new ECKeyPairGenerator();
|
ECKeyPairGenerator generator = new ECKeyPairGenerator();
|
||||||
generator.init(new ECKeyGenerationParameters(params, random));
|
generator.init(generatorParams);
|
||||||
AsymmetricCipherKeyPair keyPair1 = generator.generateKeyPair();
|
AsymmetricCipherKeyPair keyPair1 = generator.generateKeyPair();
|
||||||
|
ECPublicKeyParameters public1 =
|
||||||
|
(ECPublicKeyParameters) keyPair1.getPublic();
|
||||||
|
ECPrivateKeyParameters private1 =
|
||||||
|
(ECPrivateKeyParameters) keyPair1.getPrivate();
|
||||||
AsymmetricCipherKeyPair keyPair2 = generator.generateKeyPair();
|
AsymmetricCipherKeyPair keyPair2 = generator.generateKeyPair();
|
||||||
// Time some ECDH and ECDHC key agreements
|
ECPublicKeyParameters public2 =
|
||||||
long agreementMedian = runAgreementTest(keyPair1, keyPair2, false);
|
(ECPublicKeyParameters) keyPair2.getPublic();
|
||||||
long agreementWithCofactorMedian =
|
// Time some ECDH key agreements
|
||||||
runAgreementTest(keyPair1, keyPair2, true);
|
|
||||||
// Time some signatures
|
|
||||||
List<Long> samples = new ArrayList<>();
|
List<Long> samples = new ArrayList<>();
|
||||||
|
for (int i = 0; i < SAMPLES; i++) {
|
||||||
|
ECDHCBasicAgreement agreement = new ECDHCBasicAgreement();
|
||||||
|
long start = System.nanoTime();
|
||||||
|
agreement.init(private1);
|
||||||
|
agreement.calculateAgreement(public2);
|
||||||
|
samples.add(System.nanoTime() - start);
|
||||||
|
}
|
||||||
|
long agreementMedian = median(samples);
|
||||||
|
// Time some signatures
|
||||||
List<byte[]> signatures = new ArrayList<>();
|
List<byte[]> signatures = new ArrayList<>();
|
||||||
|
samples.clear();
|
||||||
for (int i = 0; i < SAMPLES; i++) {
|
for (int i = 0; i < SAMPLES; i++) {
|
||||||
Digest digest = new Blake2sDigest();
|
Digest digest = new Blake2sDigest();
|
||||||
DSAKCalculator calculator = new HMacDSAKCalculator(digest);
|
DSAKCalculator calculator = new HMacDSAKCalculator(digest);
|
||||||
DSADigestSigner signer = new DSADigestSigner(new ECDSASigner(
|
DSADigestSigner signer = new DSADigestSigner(new ECDSASigner(
|
||||||
calculator), digest);
|
calculator), digest);
|
||||||
long start = System.nanoTime();
|
long start = System.nanoTime();
|
||||||
signer.init(true,
|
signer.init(true, new ParametersWithRandom(private1, random));
|
||||||
new ParametersWithRandom(keyPair1.getPrivate(), random));
|
|
||||||
signer.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN);
|
signer.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN);
|
||||||
signatures.add(signer.generateSignature());
|
signatures.add(signer.generateSignature());
|
||||||
samples.add(System.nanoTime() - start);
|
samples.add(System.nanoTime() - start);
|
||||||
@@ -103,83 +101,17 @@ public class EllipticCurvePerformanceTest {
|
|||||||
DSADigestSigner signer = new DSADigestSigner(new ECDSASigner(
|
DSADigestSigner signer = new DSADigestSigner(new ECDSASigner(
|
||||||
calculator), digest);
|
calculator), digest);
|
||||||
long start = System.nanoTime();
|
long start = System.nanoTime();
|
||||||
signer.init(false, keyPair1.getPublic());
|
signer.init(false, public1);
|
||||||
signer.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN);
|
signer.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN);
|
||||||
if (!signer.verifySignature(signatures.get(i)))
|
if (!signer.verifySignature(signatures.get(i)))
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
samples.add(System.nanoTime() - start);
|
samples.add(System.nanoTime() - start);
|
||||||
}
|
}
|
||||||
long verificationMedian = median(samples);
|
long verificationMedian = median(samples);
|
||||||
System.out.println(String.format("%s: %,d %,d %,d %,d", name,
|
System.out.println(name + ": "
|
||||||
agreementMedian, agreementWithCofactorMedian,
|
+ agreementMedian + " "
|
||||||
signatureMedian, verificationMedian));
|
+ signatureMedian + " "
|
||||||
}
|
+ verificationMedian);
|
||||||
|
|
||||||
private static long runAgreementTest(AsymmetricCipherKeyPair keyPair1,
|
|
||||||
AsymmetricCipherKeyPair keyPair2, boolean withCofactor) {
|
|
||||||
List<Long> samples = new ArrayList<>();
|
|
||||||
for (int i = 0; i < SAMPLES; i++) {
|
|
||||||
BasicAgreement agreement = createAgreement(withCofactor);
|
|
||||||
long start = System.nanoTime();
|
|
||||||
agreement.init(keyPair1.getPrivate());
|
|
||||||
agreement.calculateAgreement(keyPair2.getPublic());
|
|
||||||
samples.add(System.nanoTime() - start);
|
|
||||||
}
|
|
||||||
return median(samples);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BasicAgreement createAgreement(boolean withCofactor) {
|
|
||||||
if (withCofactor) return new ECDHCBasicAgreement();
|
|
||||||
else return new ECDHBasicAgreement();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void runCurve25519Test() {
|
|
||||||
Curve25519 curve25519 = Curve25519.getInstance("java");
|
|
||||||
Curve25519KeyPair keyPair1 = curve25519.generateKeyPair();
|
|
||||||
Curve25519KeyPair keyPair2 = curve25519.generateKeyPair();
|
|
||||||
// Time some key agreements
|
|
||||||
List<Long> samples = new ArrayList<>();
|
|
||||||
for (int i = 0; i < SAMPLES; i++) {
|
|
||||||
long start = System.nanoTime();
|
|
||||||
curve25519.calculateAgreement(keyPair1.getPublicKey(),
|
|
||||||
keyPair2.getPrivateKey());
|
|
||||||
samples.add(System.nanoTime() - start);
|
|
||||||
}
|
|
||||||
long agreementMedian = median(samples);
|
|
||||||
System.out.println(String.format("Curve25519: %,d - - -",
|
|
||||||
agreementMedian));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void runEd25519Test() throws GeneralSecurityException {
|
|
||||||
KeyPair keyPair = new KeyPairGenerator().generateKeyPair();
|
|
||||||
// Time some signatures
|
|
||||||
List<Long> samples = new ArrayList<>();
|
|
||||||
List<byte[]> signatures = new ArrayList<>();
|
|
||||||
for (int i = 0; i < SAMPLES; i++) {
|
|
||||||
Signature signature =
|
|
||||||
Signature.getInstance(SIGNATURE_ALGORITHM, ED_PROVIDER);
|
|
||||||
long start = System.nanoTime();
|
|
||||||
signature.initSign(keyPair.getPrivate(), random);
|
|
||||||
signature.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN);
|
|
||||||
signatures.add(signature.sign());
|
|
||||||
samples.add(System.nanoTime() - start);
|
|
||||||
}
|
|
||||||
long signatureMedian = median(samples);
|
|
||||||
// Time some signature verifications
|
|
||||||
samples.clear();
|
|
||||||
for (int i = 0; i < SAMPLES; i++) {
|
|
||||||
Signature signature =
|
|
||||||
Signature.getInstance(SIGNATURE_ALGORITHM, ED_PROVIDER);
|
|
||||||
long start = System.nanoTime();
|
|
||||||
signature.initVerify(keyPair.getPublic());
|
|
||||||
signature.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN);
|
|
||||||
if (!signature.verify(signatures.get(i)))
|
|
||||||
throw new AssertionError();
|
|
||||||
samples.add(System.nanoTime() - start);
|
|
||||||
}
|
|
||||||
long verificationMedian = median(samples);
|
|
||||||
System.out.println(String.format("Ed25519: - - %,d %,d",
|
|
||||||
signatureMedian, verificationMedian));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static long median(List<Long> list) {
|
private static long median(List<Long> list) {
|
||||||
|
|||||||
@@ -3,32 +3,40 @@ package org.briarproject.bramble.crypto;
|
|||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.SHARED_SECRET_LABEL;
|
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
|
||||||
public class KeyAgreementTest extends BrambleTestCase {
|
public class KeyAgreementTest extends BrambleTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeriveSharedSecret() throws Exception {
|
public void testDeriveMasterSecret() throws Exception {
|
||||||
CryptoComponent crypto =
|
SecureRandomProvider
|
||||||
new CryptoComponentImpl(new TestSecureRandomProvider());
|
secureRandomProvider = new TestSecureRandomProvider();
|
||||||
|
CryptoComponent crypto = new CryptoComponentImpl(secureRandomProvider);
|
||||||
KeyPair aPair = crypto.generateAgreementKeyPair();
|
KeyPair aPair = crypto.generateAgreementKeyPair();
|
||||||
|
byte[] aPub = aPair.getPublic().getEncoded();
|
||||||
KeyPair bPair = crypto.generateAgreementKeyPair();
|
KeyPair bPair = crypto.generateAgreementKeyPair();
|
||||||
Random random = new Random();
|
byte[] bPub = bPair.getPublic().getEncoded();
|
||||||
byte[][] inputs = new byte[random.nextInt(10) + 1][];
|
SecretKey aMaster = crypto.deriveMasterSecret(aPub, bPair, true);
|
||||||
for (int i = 0; i < inputs.length; i++)
|
SecretKey bMaster = crypto.deriveMasterSecret(bPub, aPair, false);
|
||||||
inputs[i] = getRandomBytes(random.nextInt(256));
|
assertArrayEquals(aMaster.getBytes(), bMaster.getBytes());
|
||||||
SecretKey aShared = crypto.deriveSharedSecret(SHARED_SECRET_LABEL,
|
}
|
||||||
bPair.getPublic(), aPair, inputs);
|
|
||||||
SecretKey bShared = crypto.deriveSharedSecret(SHARED_SECRET_LABEL,
|
@Test
|
||||||
aPair.getPublic(), bPair, inputs);
|
public void testDeriveSharedSecret() throws Exception {
|
||||||
|
SecureRandomProvider
|
||||||
|
secureRandomProvider = new TestSecureRandomProvider();
|
||||||
|
CryptoComponent crypto = new CryptoComponentImpl(secureRandomProvider);
|
||||||
|
KeyPair aPair = crypto.generateAgreementKeyPair();
|
||||||
|
byte[] aPub = aPair.getPublic().getEncoded();
|
||||||
|
KeyPair bPair = crypto.generateAgreementKeyPair();
|
||||||
|
byte[] bPub = bPair.getPublic().getEncoded();
|
||||||
|
SecretKey aShared = crypto.deriveSharedSecret(bPub, aPair, true);
|
||||||
|
SecretKey bShared = crypto.deriveSharedSecret(aPub, bPair, false);
|
||||||
assertArrayEquals(aShared.getBytes(), bShared.getBytes());
|
assertArrayEquals(aShared.getBytes(), bShared.getBytes());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ package org.briarproject.bramble.crypto;
|
|||||||
import org.briarproject.bramble.api.Bytes;
|
import org.briarproject.bramble.api.Bytes;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
||||||
|
import org.briarproject.bramble.test.TestUtils;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -16,34 +16,35 @@ import java.util.HashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public class KeyDerivationTest extends BrambleTestCase {
|
public class KeyDerivationTest extends BrambleTestCase {
|
||||||
|
|
||||||
private final CryptoComponent crypto =
|
|
||||||
new CryptoComponentImpl(new TestSecureRandomProvider());
|
|
||||||
private final TransportCrypto transportCrypto =
|
|
||||||
new TransportCryptoImpl(crypto);
|
|
||||||
private final TransportId transportId = new TransportId("id");
|
private final TransportId transportId = new TransportId("id");
|
||||||
private final SecretKey master = getSecretKey();
|
private final CryptoComponent crypto;
|
||||||
|
private final SecretKey master;
|
||||||
|
|
||||||
|
public KeyDerivationTest() {
|
||||||
|
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
|
||||||
|
master = TestUtils.getSecretKey();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testKeysAreDistinct() {
|
public void testKeysAreDistinct() {
|
||||||
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
TransportKeys k = crypto.deriveTransportKeys(transportId, master,
|
||||||
master, 123, true);
|
123, true);
|
||||||
assertAllDifferent(k);
|
assertAllDifferent(k);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCurrentKeysMatchCurrentKeysOfContact() {
|
public void testCurrentKeysMatchCurrentKeysOfContact() {
|
||||||
// Start in rotation period 123
|
// Start in rotation period 123
|
||||||
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
|
TransportKeys kA = crypto.deriveTransportKeys(transportId, master,
|
||||||
master, 123, true);
|
123, true);
|
||||||
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
|
TransportKeys kB = crypto.deriveTransportKeys(transportId, master,
|
||||||
master, 123, false);
|
123, false);
|
||||||
// Alice's incoming keys should equal Bob's outgoing keys
|
// Alice's incoming keys should equal Bob's outgoing keys
|
||||||
assertArrayEquals(kA.getCurrentIncomingKeys().getTagKey().getBytes(),
|
assertArrayEquals(kA.getCurrentIncomingKeys().getTagKey().getBytes(),
|
||||||
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
|
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
|
||||||
@@ -55,8 +56,8 @@ public class KeyDerivationTest extends BrambleTestCase {
|
|||||||
assertArrayEquals(kA.getCurrentOutgoingKeys().getHeaderKey().getBytes(),
|
assertArrayEquals(kA.getCurrentOutgoingKeys().getHeaderKey().getBytes(),
|
||||||
kB.getCurrentIncomingKeys().getHeaderKey().getBytes());
|
kB.getCurrentIncomingKeys().getHeaderKey().getBytes());
|
||||||
// Rotate into the future
|
// Rotate into the future
|
||||||
kA = transportCrypto.rotateTransportKeys(kA, 456);
|
kA = crypto.rotateTransportKeys(kA, 456);
|
||||||
kB = transportCrypto.rotateTransportKeys(kB, 456);
|
kB = crypto.rotateTransportKeys(kB, 456);
|
||||||
// Alice's incoming keys should equal Bob's outgoing keys
|
// Alice's incoming keys should equal Bob's outgoing keys
|
||||||
assertArrayEquals(kA.getCurrentIncomingKeys().getTagKey().getBytes(),
|
assertArrayEquals(kA.getCurrentIncomingKeys().getTagKey().getBytes(),
|
||||||
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
|
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
|
||||||
@@ -72,23 +73,22 @@ public class KeyDerivationTest extends BrambleTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testPreviousKeysMatchPreviousKeysOfContact() {
|
public void testPreviousKeysMatchPreviousKeysOfContact() {
|
||||||
// Start in rotation period 123
|
// Start in rotation period 123
|
||||||
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
|
TransportKeys kA = crypto.deriveTransportKeys(transportId, master,
|
||||||
master, 123, true);
|
123, true);
|
||||||
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
|
TransportKeys kB = crypto.deriveTransportKeys(transportId, master,
|
||||||
master, 123, false);
|
123, false);
|
||||||
// Compare Alice's previous keys in period 456 with Bob's current keys
|
// Compare Alice's previous keys in period 456 with Bob's current keys
|
||||||
// in period 455
|
// in period 455
|
||||||
kA = transportCrypto.rotateTransportKeys(kA, 456);
|
kA = crypto.rotateTransportKeys(kA, 456);
|
||||||
kB = transportCrypto.rotateTransportKeys(kB, 455);
|
kB = crypto.rotateTransportKeys(kB, 455);
|
||||||
// Alice's previous incoming keys should equal Bob's outgoing keys
|
// Alice's previous incoming keys should equal Bob's outgoing keys
|
||||||
assertArrayEquals(kA.getPreviousIncomingKeys().getTagKey().getBytes(),
|
assertArrayEquals(kA.getPreviousIncomingKeys().getTagKey().getBytes(),
|
||||||
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
|
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
|
||||||
assertArrayEquals(
|
assertArrayEquals(kA.getPreviousIncomingKeys().getHeaderKey().getBytes(),
|
||||||
kA.getPreviousIncomingKeys().getHeaderKey().getBytes(),
|
|
||||||
kB.getCurrentOutgoingKeys().getHeaderKey().getBytes());
|
kB.getCurrentOutgoingKeys().getHeaderKey().getBytes());
|
||||||
// Compare Alice's current keys in period 456 with Bob's previous keys
|
// Compare Alice's current keys in period 456 with Bob's previous keys
|
||||||
// in period 457
|
// in period 457
|
||||||
kB = transportCrypto.rotateTransportKeys(kB, 457);
|
kB = crypto.rotateTransportKeys(kB, 457);
|
||||||
// Alice's outgoing keys should equal Bob's previous incoming keys
|
// Alice's outgoing keys should equal Bob's previous incoming keys
|
||||||
assertArrayEquals(kA.getCurrentOutgoingKeys().getTagKey().getBytes(),
|
assertArrayEquals(kA.getCurrentOutgoingKeys().getTagKey().getBytes(),
|
||||||
kB.getPreviousIncomingKeys().getTagKey().getBytes());
|
kB.getPreviousIncomingKeys().getTagKey().getBytes());
|
||||||
@@ -99,14 +99,14 @@ public class KeyDerivationTest extends BrambleTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testNextKeysMatchNextKeysOfContact() {
|
public void testNextKeysMatchNextKeysOfContact() {
|
||||||
// Start in rotation period 123
|
// Start in rotation period 123
|
||||||
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
|
TransportKeys kA = crypto.deriveTransportKeys(transportId, master,
|
||||||
master, 123, true);
|
123, true);
|
||||||
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
|
TransportKeys kB = crypto.deriveTransportKeys(transportId, master,
|
||||||
master, 123, false);
|
123, false);
|
||||||
// Compare Alice's current keys in period 456 with Bob's next keys in
|
// Compare Alice's current keys in period 456 with Bob's next keys in
|
||||||
// period 455
|
// period 455
|
||||||
kA = transportCrypto.rotateTransportKeys(kA, 456);
|
kA = crypto.rotateTransportKeys(kA, 456);
|
||||||
kB = transportCrypto.rotateTransportKeys(kB, 455);
|
kB = crypto.rotateTransportKeys(kB, 455);
|
||||||
// Alice's outgoing keys should equal Bob's next incoming keys
|
// Alice's outgoing keys should equal Bob's next incoming keys
|
||||||
assertArrayEquals(kA.getCurrentOutgoingKeys().getTagKey().getBytes(),
|
assertArrayEquals(kA.getCurrentOutgoingKeys().getTagKey().getBytes(),
|
||||||
kB.getNextIncomingKeys().getTagKey().getBytes());
|
kB.getNextIncomingKeys().getTagKey().getBytes());
|
||||||
@@ -114,7 +114,7 @@ public class KeyDerivationTest extends BrambleTestCase {
|
|||||||
kB.getNextIncomingKeys().getHeaderKey().getBytes());
|
kB.getNextIncomingKeys().getHeaderKey().getBytes());
|
||||||
// Compare Alice's next keys in period 456 with Bob's current keys
|
// Compare Alice's next keys in period 456 with Bob's current keys
|
||||||
// in period 457
|
// in period 457
|
||||||
kB = transportCrypto.rotateTransportKeys(kB, 457);
|
kB = crypto.rotateTransportKeys(kB, 457);
|
||||||
// Alice's next incoming keys should equal Bob's outgoing keys
|
// Alice's next incoming keys should equal Bob's outgoing keys
|
||||||
assertArrayEquals(kA.getNextIncomingKeys().getTagKey().getBytes(),
|
assertArrayEquals(kA.getNextIncomingKeys().getTagKey().getBytes(),
|
||||||
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
|
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
|
||||||
@@ -124,12 +124,12 @@ public class KeyDerivationTest extends BrambleTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMasterKeyAffectsOutput() {
|
public void testMasterKeyAffectsOutput() {
|
||||||
SecretKey master1 = getSecretKey();
|
SecretKey master1 = TestUtils.getSecretKey();
|
||||||
assertFalse(Arrays.equals(master.getBytes(), master1.getBytes()));
|
assertFalse(Arrays.equals(master.getBytes(), master1.getBytes()));
|
||||||
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
TransportKeys k = crypto.deriveTransportKeys(transportId, master,
|
||||||
master, 123, true);
|
123, true);
|
||||||
TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId,
|
TransportKeys k1 = crypto.deriveTransportKeys(transportId, master1,
|
||||||
master1, 123, true);
|
123, true);
|
||||||
assertAllDifferent(k, k1);
|
assertAllDifferent(k, k1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,10 +137,10 @@ public class KeyDerivationTest extends BrambleTestCase {
|
|||||||
public void testTransportIdAffectsOutput() {
|
public void testTransportIdAffectsOutput() {
|
||||||
TransportId transportId1 = new TransportId("id1");
|
TransportId transportId1 = new TransportId("id1");
|
||||||
assertFalse(transportId.getString().equals(transportId1.getString()));
|
assertFalse(transportId.getString().equals(transportId1.getString()));
|
||||||
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
TransportKeys k = crypto.deriveTransportKeys(transportId, master,
|
||||||
master, 123, true);
|
123, true);
|
||||||
TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId1,
|
TransportKeys k1 = crypto.deriveTransportKeys(transportId1, master,
|
||||||
master, 123, true);
|
123, true);
|
||||||
assertAllDifferent(k, k1);
|
assertAllDifferent(k, k1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,49 +4,42 @@ import org.briarproject.bramble.api.crypto.CryptoComponent;
|
|||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
||||||
|
import org.briarproject.bramble.test.TestUtils;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
|
||||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
|
||||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
|
||||||
public class MacTest extends BrambleTestCase {
|
public class MacTest extends BrambleTestCase {
|
||||||
|
|
||||||
private final CryptoComponent crypto =
|
private final CryptoComponent crypto;
|
||||||
new CryptoComponentImpl(new TestSecureRandomProvider());
|
|
||||||
|
|
||||||
private final SecretKey key1 = getSecretKey(), key2 = getSecretKey();
|
private final SecretKey k = TestUtils.getSecretKey();
|
||||||
private final String label1 = getRandomString(123);
|
private final byte[] inputBytes = TestUtils.getRandomBytes(123);
|
||||||
private final String label2 = getRandomString(123);
|
private final byte[] inputBytes1 = TestUtils.getRandomBytes(234);
|
||||||
private final byte[] input1 = getRandomBytes(123);
|
private final byte[] inputBytes2 = new byte[0];
|
||||||
private final byte[] input2 = getRandomBytes(234);
|
|
||||||
private final byte[] input3 = new byte[0];
|
public MacTest() {
|
||||||
|
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIdenticalKeysAndInputsProduceIdenticalMacs() {
|
public void testIdenticalKeysAndInputsProduceIdenticalMacs() {
|
||||||
// Calculate the MAC twice - the results should be identical
|
// Calculate the MAC twice - the results should be identical
|
||||||
byte[] mac = crypto.mac(label1, key1, input1, input2, input3);
|
byte[] mac = crypto.mac(k, inputBytes, inputBytes1, inputBytes2);
|
||||||
byte[] mac1 = crypto.mac(label1, key1, input1, input2, input3);
|
byte[] mac1 = crypto.mac(k, inputBytes, inputBytes1, inputBytes2);
|
||||||
assertArrayEquals(mac, mac1);
|
assertArrayEquals(mac, mac1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDifferentLabelsProduceDifferentMacs() {
|
|
||||||
// Calculate the MAC with each label - the results should be different
|
|
||||||
byte[] mac = crypto.mac(label1, key1, input1, input2, input3);
|
|
||||||
byte[] mac1 = crypto.mac(label2, key1, input1, input2, input3);
|
|
||||||
assertFalse(Arrays.equals(mac, mac1));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDifferentKeysProduceDifferentMacs() {
|
public void testDifferentKeysProduceDifferentMacs() {
|
||||||
|
// Generate second random key
|
||||||
|
SecretKey k1 = TestUtils.getSecretKey();
|
||||||
// Calculate the MAC with each key - the results should be different
|
// Calculate the MAC with each key - the results should be different
|
||||||
byte[] mac = crypto.mac(label1, key1, input1, input2, input3);
|
byte[] mac = crypto.mac(k, inputBytes, inputBytes1, inputBytes2);
|
||||||
byte[] mac1 = crypto.mac(label1, key2, input1, input2, input3);
|
byte[] mac1 = crypto.mac(k1, inputBytes, inputBytes1, inputBytes2);
|
||||||
assertFalse(Arrays.equals(mac, mac1));
|
assertFalse(Arrays.equals(mac, mac1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,8 +47,8 @@ public class MacTest extends BrambleTestCase {
|
|||||||
public void testDifferentInputsProduceDifferentMacs() {
|
public void testDifferentInputsProduceDifferentMacs() {
|
||||||
// Calculate the MAC with the inputs in different orders - the results
|
// Calculate the MAC with the inputs in different orders - the results
|
||||||
// should be different
|
// should be different
|
||||||
byte[] mac = crypto.mac(label1, key1, input1, input2, input3);
|
byte[] mac = crypto.mac(k, inputBytes, inputBytes1, inputBytes2);
|
||||||
byte[] mac1 = crypto.mac(label1, key1, input3, input2, input1);
|
byte[] mac1 = crypto.mac(k, inputBytes2, inputBytes1, inputBytes);
|
||||||
assertFalse(Arrays.equals(mac, mac1));
|
assertFalse(Arrays.equals(mac, mac1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,32 +8,23 @@ import org.briarproject.bramble.test.TestUtils;
|
|||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public abstract class SignatureTest extends BrambleTestCase {
|
public class SignatureTest extends BrambleTestCase {
|
||||||
|
|
||||||
protected final CryptoComponent crypto;
|
private final CryptoComponent crypto;
|
||||||
|
|
||||||
private final byte[] publicKey, privateKey;
|
private final byte[] publicKey, privateKey;
|
||||||
private final String label = StringUtils.getRandomString(42);
|
private final String label = StringUtils.getRandomString(42);
|
||||||
private final byte[] inputBytes = TestUtils.getRandomBytes(123);
|
private final byte[] inputBytes = TestUtils.getRandomBytes(123);
|
||||||
|
|
||||||
protected abstract KeyPair generateKeyPair();
|
public SignatureTest() {
|
||||||
|
|
||||||
protected abstract byte[] sign(String label, byte[] toSign,
|
|
||||||
byte[] privateKey) throws GeneralSecurityException;
|
|
||||||
|
|
||||||
protected abstract boolean verify(String label, byte[] signedData,
|
|
||||||
byte[] publicKey, byte[] signature) throws GeneralSecurityException;
|
|
||||||
|
|
||||||
SignatureTest() {
|
|
||||||
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
|
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
|
||||||
KeyPair k = generateKeyPair();
|
KeyPair k = crypto.generateSignatureKeyPair();
|
||||||
publicKey = k.getPublic().getEncoded();
|
publicKey = k.getPublic().getEncoded();
|
||||||
privateKey = k.getPrivate().getEncoded();
|
privateKey = k.getPrivate().getEncoded();
|
||||||
}
|
}
|
||||||
@@ -42,19 +33,19 @@ public abstract class SignatureTest extends BrambleTestCase {
|
|||||||
public void testIdenticalKeysAndInputsProduceIdenticalSignatures()
|
public void testIdenticalKeysAndInputsProduceIdenticalSignatures()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
// Calculate the Signature twice - the results should be identical
|
// Calculate the Signature twice - the results should be identical
|
||||||
byte[] sig1 = sign(label, inputBytes, privateKey);
|
byte[] sig1 = crypto.sign(label, inputBytes, privateKey);
|
||||||
byte[] sig2 = sign(label, inputBytes, privateKey);
|
byte[] sig2 = crypto.sign(label, inputBytes, privateKey);
|
||||||
assertArrayEquals(sig1, sig2);
|
assertArrayEquals(sig1, sig2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDifferentKeysProduceDifferentSignatures() throws Exception {
|
public void testDifferentKeysProduceDifferentSignatures() throws Exception {
|
||||||
// Generate second private key
|
// Generate second private key
|
||||||
KeyPair k2 = generateKeyPair();
|
KeyPair k2 = crypto.generateSignatureKeyPair();
|
||||||
byte[] privateKey2 = k2.getPrivate().getEncoded();
|
byte[] privateKey2 = k2.getPrivate().getEncoded();
|
||||||
// Calculate the signature with each key
|
// Calculate the signature with each key
|
||||||
byte[] sig1 = sign(label, inputBytes, privateKey);
|
byte[] sig1 = crypto.sign(label, inputBytes, privateKey);
|
||||||
byte[] sig2 = sign(label, inputBytes, privateKey2);
|
byte[] sig2 = crypto.sign(label, inputBytes, privateKey2);
|
||||||
assertFalse(Arrays.equals(sig1, sig2));
|
assertFalse(Arrays.equals(sig1, sig2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,8 +56,8 @@ public abstract class SignatureTest extends BrambleTestCase {
|
|||||||
byte[] inputBytes2 = TestUtils.getRandomBytes(123);
|
byte[] inputBytes2 = TestUtils.getRandomBytes(123);
|
||||||
// Calculate the signature with different inputs
|
// Calculate the signature with different inputs
|
||||||
// the results should be different
|
// the results should be different
|
||||||
byte[] sig1 = sign(label, inputBytes, privateKey);
|
byte[] sig1 = crypto.sign(label, inputBytes, privateKey);
|
||||||
byte[] sig2 = sign(label, inputBytes2, privateKey);
|
byte[] sig2 = crypto.sign(label, inputBytes2, privateKey);
|
||||||
assertFalse(Arrays.equals(sig1, sig2));
|
assertFalse(Arrays.equals(sig1, sig2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,25 +68,25 @@ public abstract class SignatureTest extends BrambleTestCase {
|
|||||||
String label2 = StringUtils.getRandomString(42);
|
String label2 = StringUtils.getRandomString(42);
|
||||||
// Calculate the signature with different inputs
|
// Calculate the signature with different inputs
|
||||||
// the results should be different
|
// the results should be different
|
||||||
byte[] sig1 = sign(label, inputBytes, privateKey);
|
byte[] sig1 = crypto.sign(label, inputBytes, privateKey);
|
||||||
byte[] sig2 = sign(label2, inputBytes, privateKey);
|
byte[] sig2 = crypto.sign(label2, inputBytes, privateKey);
|
||||||
assertFalse(Arrays.equals(sig1, sig2));
|
assertFalse(Arrays.equals(sig1, sig2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSignatureVerification() throws Exception {
|
public void testSignatureVerification() throws Exception {
|
||||||
byte[] sig = sign(label, inputBytes, privateKey);
|
byte[] sig = crypto.sign(label, inputBytes, privateKey);
|
||||||
assertTrue(verify(label, inputBytes, publicKey, sig));
|
assertTrue(crypto.verify(label, inputBytes, publicKey, sig));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDifferentKeyFailsVerification() throws Exception {
|
public void testDifferentKeyFailsVerification() throws Exception {
|
||||||
// Generate second private key
|
// Generate second private key
|
||||||
KeyPair k2 = generateKeyPair();
|
KeyPair k2 = crypto.generateSignatureKeyPair();
|
||||||
byte[] privateKey2 = k2.getPrivate().getEncoded();
|
byte[] privateKey2 = k2.getPrivate().getEncoded();
|
||||||
// calculate the signature with different key, should fail to verify
|
// calculate the signature with different key, should fail to verify
|
||||||
byte[] sig = sign(label, inputBytes, privateKey2);
|
byte[] sig = crypto.sign(label, inputBytes, privateKey2);
|
||||||
assertFalse(verify(label, inputBytes, publicKey, sig));
|
assertFalse(crypto.verify(label, inputBytes, publicKey, sig));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -103,8 +94,8 @@ public abstract class SignatureTest extends BrambleTestCase {
|
|||||||
// Generate a second input
|
// Generate a second input
|
||||||
byte[] inputBytes2 = TestUtils.getRandomBytes(123);
|
byte[] inputBytes2 = TestUtils.getRandomBytes(123);
|
||||||
// calculate the signature with different input, should fail to verify
|
// calculate the signature with different input, should fail to verify
|
||||||
byte[] sig = sign(label, inputBytes, privateKey);
|
byte[] sig = crypto.sign(label, inputBytes, privateKey);
|
||||||
assertFalse(verify(label, inputBytes2, publicKey, sig));
|
assertFalse(crypto.verify(label, inputBytes2, publicKey, sig));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -112,8 +103,8 @@ public abstract class SignatureTest extends BrambleTestCase {
|
|||||||
// Generate a second label
|
// Generate a second label
|
||||||
String label2 = StringUtils.getRandomString(42);
|
String label2 = StringUtils.getRandomString(42);
|
||||||
// calculate the signature with different label, should fail to verify
|
// calculate the signature with different label, should fail to verify
|
||||||
byte[] sig = sign(label, inputBytes, privateKey);
|
byte[] sig = crypto.sign(label, inputBytes, privateKey);
|
||||||
assertFalse(verify(label2, inputBytes, publicKey, sig));
|
assertFalse(crypto.verify(label2, inputBytes, publicKey, sig));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ package org.briarproject.bramble.crypto;
|
|||||||
import org.briarproject.bramble.api.Bytes;
|
import org.briarproject.bramble.api.Bytes;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
||||||
|
import org.briarproject.bramble.test.TestUtils;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@@ -13,25 +14,25 @@ import java.util.Set;
|
|||||||
import static junit.framework.TestCase.assertTrue;
|
import static junit.framework.TestCase.assertTrue;
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
|
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
|
||||||
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.TestUtils.getSecretKey;
|
|
||||||
|
|
||||||
public class TagEncodingTest extends BrambleMockTestCase {
|
public class TagEncodingTest extends BrambleTestCase {
|
||||||
|
|
||||||
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
private final CryptoComponent crypto;
|
||||||
|
private final SecretKey tagKey;
|
||||||
private final TransportCrypto transportCrypto =
|
|
||||||
new TransportCryptoImpl(crypto);
|
|
||||||
private final SecretKey tagKey = getSecretKey();
|
|
||||||
private final long streamNumber = 1234567890;
|
private final long streamNumber = 1234567890;
|
||||||
|
|
||||||
|
public TagEncodingTest() {
|
||||||
|
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
|
||||||
|
tagKey = TestUtils.getSecretKey();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testKeyAffectsTag() throws Exception {
|
public void testKeyAffectsTag() throws Exception {
|
||||||
Set<Bytes> set = new HashSet<>();
|
Set<Bytes> set = new HashSet<>();
|
||||||
for (int i = 0; i < 100; i++) {
|
for (int i = 0; i < 100; i++) {
|
||||||
byte[] tag = new byte[TAG_LENGTH];
|
byte[] tag = new byte[TAG_LENGTH];
|
||||||
SecretKey tagKey = getSecretKey();
|
SecretKey tagKey = TestUtils.getSecretKey();
|
||||||
transportCrypto.encodeTag(tag, tagKey, PROTOCOL_VERSION,
|
crypto.encodeTag(tag, tagKey, PROTOCOL_VERSION, streamNumber);
|
||||||
streamNumber);
|
|
||||||
assertTrue(set.add(new Bytes(tag)));
|
assertTrue(set.add(new Bytes(tag)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,8 +42,7 @@ public class TagEncodingTest extends BrambleMockTestCase {
|
|||||||
Set<Bytes> set = new HashSet<>();
|
Set<Bytes> set = new HashSet<>();
|
||||||
for (int i = 0; i < 100; i++) {
|
for (int i = 0; i < 100; i++) {
|
||||||
byte[] tag = new byte[TAG_LENGTH];
|
byte[] tag = new byte[TAG_LENGTH];
|
||||||
transportCrypto.encodeTag(tag, tagKey, PROTOCOL_VERSION + i,
|
crypto.encodeTag(tag, tagKey, PROTOCOL_VERSION + i, streamNumber);
|
||||||
streamNumber);
|
|
||||||
assertTrue(set.add(new Bytes(tag)));
|
assertTrue(set.add(new Bytes(tag)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,8 +52,7 @@ public class TagEncodingTest extends BrambleMockTestCase {
|
|||||||
Set<Bytes> set = new HashSet<>();
|
Set<Bytes> set = new HashSet<>();
|
||||||
for (int i = 0; i < 100; i++) {
|
for (int i = 0; i < 100; i++) {
|
||||||
byte[] tag = new byte[TAG_LENGTH];
|
byte[] tag = new byte[TAG_LENGTH];
|
||||||
transportCrypto.encodeTag(tag, tagKey, PROTOCOL_VERSION,
|
crypto.encodeTag(tag, tagKey, PROTOCOL_VERSION, streamNumber + i);
|
||||||
streamNumber + i);
|
|
||||||
assertTrue(set.add(new Bytes(tag)));
|
assertTrue(set.add(new Bytes(tag)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,380 +0,0 @@
|
|||||||
package org.briarproject.bramble.db;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
|
||||||
import org.briarproject.bramble.test.TestUtils;
|
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.PreparedStatement;
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.sql.Statement;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static java.sql.Types.BINARY;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.junit.Assert.assertNull;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
|
|
||||||
public abstract class BasicDatabaseTest extends BrambleTestCase {
|
|
||||||
|
|
||||||
private static final int BATCH_SIZE = 100;
|
|
||||||
|
|
||||||
private final File testDir = TestUtils.getTestDirectory();
|
|
||||||
private final File db = new File(testDir, "db");
|
|
||||||
|
|
||||||
protected abstract String getBinaryType();
|
|
||||||
|
|
||||||
protected abstract String getDriverName();
|
|
||||||
|
|
||||||
protected abstract Connection openConnection(File db, boolean encrypt)
|
|
||||||
throws SQLException;
|
|
||||||
|
|
||||||
protected abstract void shutdownDatabase(File db, boolean encrypt)
|
|
||||||
throws SQLException;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
testDir.mkdirs();
|
|
||||||
Class.forName(getDriverName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testInsertUpdateAndDelete() throws Exception {
|
|
||||||
Connection connection = openConnection(db, false);
|
|
||||||
try {
|
|
||||||
// Create the table
|
|
||||||
createTable(connection);
|
|
||||||
// Generate an ID and two names
|
|
||||||
byte[] id = TestUtils.getRandomId();
|
|
||||||
String oldName = StringUtils.getRandomString(50);
|
|
||||||
String newName = StringUtils.getRandomString(50);
|
|
||||||
// Insert the ID and old name into the table
|
|
||||||
insertRow(connection, id, oldName);
|
|
||||||
// Check that the old name can be retrieved using the ID
|
|
||||||
assertTrue(rowExists(connection, id));
|
|
||||||
assertEquals(oldName, getName(connection, id));
|
|
||||||
// Update the name
|
|
||||||
updateRow(connection, id, newName);
|
|
||||||
// Check that the new name can be retrieved using the ID
|
|
||||||
assertTrue(rowExists(connection, id));
|
|
||||||
assertEquals(newName, getName(connection, id));
|
|
||||||
// Delete the row from the table
|
|
||||||
assertTrue(deleteRow(connection, id));
|
|
||||||
// Check that the row no longer exists
|
|
||||||
assertFalse(rowExists(connection, id));
|
|
||||||
// Deleting the row again should have no effect
|
|
||||||
assertFalse(deleteRow(connection, id));
|
|
||||||
} finally {
|
|
||||||
connection.close();
|
|
||||||
shutdownDatabase(db, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBatchInsertUpdateAndDelete() throws Exception {
|
|
||||||
Connection connection = openConnection(db, false);
|
|
||||||
try {
|
|
||||||
// Create the table
|
|
||||||
createTable(connection);
|
|
||||||
// Generate some IDs and two sets of names
|
|
||||||
byte[][] ids = new byte[BATCH_SIZE][];
|
|
||||||
String[] oldNames = new String[BATCH_SIZE];
|
|
||||||
String[] newNames = new String[BATCH_SIZE];
|
|
||||||
for (int i = 0; i < BATCH_SIZE; i++) {
|
|
||||||
ids[i] = TestUtils.getRandomId();
|
|
||||||
oldNames[i] = StringUtils.getRandomString(50);
|
|
||||||
newNames[i] = StringUtils.getRandomString(50);
|
|
||||||
}
|
|
||||||
// Insert the IDs and old names into the table as a batch
|
|
||||||
insertBatch(connection, ids, oldNames);
|
|
||||||
// Update the names as a batch
|
|
||||||
updateBatch(connection, ids, newNames);
|
|
||||||
// Check that the new names can be retrieved using the IDs
|
|
||||||
for (int i = 0; i < BATCH_SIZE; i++) {
|
|
||||||
assertTrue(rowExists(connection, ids[i]));
|
|
||||||
assertEquals(newNames[i], getName(connection, ids[i]));
|
|
||||||
}
|
|
||||||
// Delete the rows as a batch
|
|
||||||
boolean[] deleted = deleteBatch(connection, ids);
|
|
||||||
// Check that the rows no longer exist
|
|
||||||
for (int i = 0; i < BATCH_SIZE; i++) {
|
|
||||||
assertTrue(deleted[i]);
|
|
||||||
assertFalse(rowExists(connection, ids[i]));
|
|
||||||
}
|
|
||||||
// Deleting the rows again should have no effect
|
|
||||||
deleted = deleteBatch(connection, ids);
|
|
||||||
for (int i = 0; i < BATCH_SIZE; i++) assertFalse(deleted[i]);
|
|
||||||
} finally {
|
|
||||||
connection.close();
|
|
||||||
shutdownDatabase(db, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSortOrder() throws Exception {
|
|
||||||
byte[] first = new byte[] {
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0
|
|
||||||
};
|
|
||||||
byte[] second = new byte[] {
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 127
|
|
||||||
};
|
|
||||||
byte[] third = new byte[] {
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, (byte) 255
|
|
||||||
};
|
|
||||||
Connection connection = openConnection(db, false);
|
|
||||||
try {
|
|
||||||
// Create the table
|
|
||||||
createTable(connection);
|
|
||||||
// Insert the rows
|
|
||||||
insertRow(connection, first, "first");
|
|
||||||
insertRow(connection, second, "second");
|
|
||||||
insertRow(connection, third, "third");
|
|
||||||
insertRow(connection, null, "null");
|
|
||||||
// Check the ordering of the < operator: null is not comparable
|
|
||||||
assertNull(getPredecessor(connection, first));
|
|
||||||
assertEquals("first", getPredecessor(connection, second));
|
|
||||||
assertEquals("second", getPredecessor(connection, third));
|
|
||||||
assertNull(getPredecessor(connection, null));
|
|
||||||
// Check the ordering of ORDER BY: nulls come first
|
|
||||||
List<String> names = getNames(connection);
|
|
||||||
assertEquals(4, names.size());
|
|
||||||
assertEquals("null", names.get(0));
|
|
||||||
assertEquals("first", names.get(1));
|
|
||||||
assertEquals("second", names.get(2));
|
|
||||||
assertEquals("third", names.get(3));
|
|
||||||
} finally {
|
|
||||||
connection.close();
|
|
||||||
shutdownDatabase(db, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDataIsFoundWithoutEncryption() throws Exception {
|
|
||||||
testEncryption(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDataIsNotFoundWithEncryption() throws Exception {
|
|
||||||
testEncryption(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void testEncryption(boolean encrypt) throws Exception {
|
|
||||||
byte[] sequence = new byte[] {'a', 'b', 'c', 'd', 'e', 'f', 'g'};
|
|
||||||
Connection connection = openConnection(db, encrypt);
|
|
||||||
try {
|
|
||||||
createTable(connection);
|
|
||||||
insertRow(connection, sequence, "abcdefg");
|
|
||||||
} finally {
|
|
||||||
connection.close();
|
|
||||||
shutdownDatabase(db, encrypt);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (findSequence(testDir, sequence) == encrypt) fail();
|
|
||||||
} finally {
|
|
||||||
shutdownDatabase(db, encrypt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createTable(Connection connection) throws SQLException {
|
|
||||||
Statement s = connection.createStatement();
|
|
||||||
String sql = "CREATE TABLE foo (uniqueId " + getBinaryType() + ","
|
|
||||||
+ " name VARCHAR(100) NOT NULL)";
|
|
||||||
s.executeUpdate(sql);
|
|
||||||
s.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void insertRow(Connection connection, byte[] id, String name)
|
|
||||||
throws SQLException {
|
|
||||||
String sql = "INSERT INTO foo (uniqueId, name) VALUES (?, ?)";
|
|
||||||
PreparedStatement ps = connection.prepareStatement(sql);
|
|
||||||
if (id == null) ps.setNull(1, BINARY);
|
|
||||||
else ps.setBytes(1, id);
|
|
||||||
ps.setString(2, name);
|
|
||||||
int affected = ps.executeUpdate();
|
|
||||||
assertEquals(1, affected);
|
|
||||||
ps.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean rowExists(Connection connection, byte[] id)
|
|
||||||
throws SQLException {
|
|
||||||
assertNotNull(id);
|
|
||||||
String sql = "SELECT * FROM foo WHERE uniqueID = ?";
|
|
||||||
PreparedStatement ps = connection.prepareStatement(sql);
|
|
||||||
ps.setBytes(1, id);
|
|
||||||
ResultSet rs = ps.executeQuery();
|
|
||||||
boolean found = rs.next();
|
|
||||||
assertFalse(rs.next());
|
|
||||||
rs.close();
|
|
||||||
ps.close();
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getName(Connection connection, byte[] id)
|
|
||||||
throws SQLException {
|
|
||||||
assertNotNull(id);
|
|
||||||
String sql = "SELECT name FROM foo WHERE uniqueID = ?";
|
|
||||||
PreparedStatement ps = connection.prepareStatement(sql);
|
|
||||||
ps.setBytes(1, id);
|
|
||||||
ResultSet rs = ps.executeQuery();
|
|
||||||
assertTrue(rs.next());
|
|
||||||
String name = rs.getString(1);
|
|
||||||
assertFalse(rs.next());
|
|
||||||
rs.close();
|
|
||||||
ps.close();
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateRow(Connection connection, byte[] id, String name)
|
|
||||||
throws SQLException {
|
|
||||||
String sql = "UPDATE foo SET name = ? WHERE uniqueId = ?";
|
|
||||||
PreparedStatement ps = connection.prepareStatement(sql);
|
|
||||||
if (id == null) ps.setNull(2, BINARY);
|
|
||||||
else ps.setBytes(2, id);
|
|
||||||
ps.setString(1, name);
|
|
||||||
assertEquals(1, ps.executeUpdate());
|
|
||||||
ps.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean deleteRow(Connection connection, byte[] id)
|
|
||||||
throws SQLException {
|
|
||||||
String sql = "DELETE FROM foo WHERE uniqueId = ?";
|
|
||||||
PreparedStatement ps = connection.prepareStatement(sql);
|
|
||||||
if (id == null) ps.setNull(1, BINARY);
|
|
||||||
else ps.setBytes(1, id);
|
|
||||||
int affected = ps.executeUpdate();
|
|
||||||
ps.close();
|
|
||||||
return affected == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void insertBatch(Connection connection, byte[][] ids,
|
|
||||||
String[] names) throws SQLException {
|
|
||||||
assertEquals(ids.length, names.length);
|
|
||||||
String sql = "INSERT INTO foo (uniqueId, name) VALUES (?, ?)";
|
|
||||||
PreparedStatement ps = connection.prepareStatement(sql);
|
|
||||||
for (int i = 0; i < ids.length; i++) {
|
|
||||||
if (ids[i] == null) ps.setNull(1, BINARY);
|
|
||||||
else ps.setBytes(1, ids[i]);
|
|
||||||
ps.setString(2, names[i]);
|
|
||||||
ps.addBatch();
|
|
||||||
}
|
|
||||||
int[] batchAffected = ps.executeBatch();
|
|
||||||
assertEquals(ids.length, batchAffected.length);
|
|
||||||
for (int affected : batchAffected) assertEquals(1, affected);
|
|
||||||
ps.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateBatch(Connection connection, byte[][] ids,
|
|
||||||
String[] names) throws SQLException {
|
|
||||||
assertEquals(ids.length, names.length);
|
|
||||||
String sql = "UPDATE foo SET name = ? WHERE uniqueId = ?";
|
|
||||||
PreparedStatement ps = connection.prepareStatement(sql);
|
|
||||||
for (int i = 0; i < ids.length; i++) {
|
|
||||||
if (ids[i] == null) ps.setNull(2, BINARY);
|
|
||||||
else ps.setBytes(2, ids[i]);
|
|
||||||
ps.setString(1, names[i]);
|
|
||||||
ps.addBatch();
|
|
||||||
}
|
|
||||||
int[] batchAffected = ps.executeBatch();
|
|
||||||
assertEquals(ids.length, batchAffected.length);
|
|
||||||
for (int affected : batchAffected) assertEquals(1, affected);
|
|
||||||
ps.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean[] deleteBatch(Connection connection, byte[][] ids)
|
|
||||||
throws SQLException {
|
|
||||||
String sql = "DELETE FROM foo WHERE uniqueId = ?";
|
|
||||||
PreparedStatement ps = connection.prepareStatement(sql);
|
|
||||||
for (byte[] id : ids) {
|
|
||||||
if (id == null) ps.setNull(1, BINARY);
|
|
||||||
else ps.setBytes(1, id);
|
|
||||||
ps.addBatch();
|
|
||||||
}
|
|
||||||
int[] batchAffected = ps.executeBatch();
|
|
||||||
assertEquals(ids.length, batchAffected.length);
|
|
||||||
boolean[] ret = new boolean[ids.length];
|
|
||||||
for (int i = 0; i < batchAffected.length; i++)
|
|
||||||
ret[i] = batchAffected[i] == 1;
|
|
||||||
ps.close();
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getPredecessor(Connection connection, byte[] id)
|
|
||||||
throws SQLException {
|
|
||||||
String sql = "SELECT name FROM foo WHERE uniqueId < ?"
|
|
||||||
+ " ORDER BY uniqueId DESC";
|
|
||||||
PreparedStatement ps = connection.prepareStatement(sql);
|
|
||||||
ps.setBytes(1, id);
|
|
||||||
ps.setMaxRows(1);
|
|
||||||
ResultSet rs = ps.executeQuery();
|
|
||||||
String name = rs.next() ? rs.getString(1) : null;
|
|
||||||
assertFalse(rs.next());
|
|
||||||
rs.close();
|
|
||||||
ps.close();
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> getNames(Connection connection) throws SQLException {
|
|
||||||
String sql = "SELECT name FROM foo ORDER BY uniqueId NULLS FIRST";
|
|
||||||
List<String> names = new ArrayList<>();
|
|
||||||
PreparedStatement ps = connection.prepareStatement(sql);
|
|
||||||
ResultSet rs = ps.executeQuery();
|
|
||||||
while (rs.next()) names.add(rs.getString(1));
|
|
||||||
rs.close();
|
|
||||||
ps.close();
|
|
||||||
return names;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean findSequence(File f, byte[] sequence) throws IOException {
|
|
||||||
if (f.isDirectory()) {
|
|
||||||
File[] children = f.listFiles();
|
|
||||||
if (children != null)
|
|
||||||
for (File child : children)
|
|
||||||
if (findSequence(child, sequence)) return true;
|
|
||||||
return false;
|
|
||||||
} else if (f.isFile()) {
|
|
||||||
FileInputStream in = new FileInputStream(f);
|
|
||||||
try {
|
|
||||||
int offset = 0;
|
|
||||||
while (true) {
|
|
||||||
int read = in.read();
|
|
||||||
if (read == -1) return false;
|
|
||||||
if (((byte) read) == sequence[offset]) {
|
|
||||||
offset++;
|
|
||||||
if (offset == sequence.length) return true;
|
|
||||||
} else {
|
|
||||||
offset = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
in.close();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown() throws Exception {
|
|
||||||
TestUtils.deleteTestDirectory(testDir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +1,345 @@
|
|||||||
package org.briarproject.bramble.db;
|
package org.briarproject.bramble.db;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
import org.briarproject.bramble.test.TestUtils;
|
import org.briarproject.bramble.test.TestUtils;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.DriverManager;
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.Properties;
|
import java.sql.Statement;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class BasicH2Test extends BasicDatabaseTest {
|
import static java.sql.Types.BINARY;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
private final SecretKey key = TestUtils.getSecretKey();
|
public class BasicH2Test extends BrambleTestCase {
|
||||||
|
|
||||||
@Override
|
private static final String CREATE_TABLE =
|
||||||
protected String getBinaryType() {
|
"CREATE TABLE foo (uniqueId BINARY(32), name VARCHAR NOT NULL)";
|
||||||
return "BINARY(32)";
|
private static final int BATCH_SIZE = 100;
|
||||||
|
|
||||||
|
private final File testDir = TestUtils.getTestDirectory();
|
||||||
|
private final File db = new File(testDir, "db");
|
||||||
|
private final String url = "jdbc:h2:" + db.getAbsolutePath();
|
||||||
|
|
||||||
|
private Connection connection = null;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
testDir.mkdirs();
|
||||||
|
Class.forName("org.h2.Driver");
|
||||||
|
connection = DriverManager.getConnection(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Test
|
||||||
protected String getDriverName() {
|
public void testInsertUpdateAndDelete() throws Exception {
|
||||||
return "org.h2.Driver";
|
// Create the table
|
||||||
|
createTable(connection);
|
||||||
|
// Generate an ID and two names
|
||||||
|
byte[] id = TestUtils.getRandomId();
|
||||||
|
String oldName = StringUtils.getRandomString(50);
|
||||||
|
String newName = StringUtils.getRandomString(50);
|
||||||
|
// Insert the ID and old name into the table
|
||||||
|
insertRow(id, oldName);
|
||||||
|
// Check that the old name can be retrieved using the ID
|
||||||
|
assertTrue(rowExists(id));
|
||||||
|
assertEquals(oldName, getName(id));
|
||||||
|
// Update the name
|
||||||
|
updateRow(id, newName);
|
||||||
|
// Check that the new name can be retrieved using the ID
|
||||||
|
assertTrue(rowExists(id));
|
||||||
|
assertEquals(newName, getName(id));
|
||||||
|
// Delete the row from the table
|
||||||
|
assertTrue(deleteRow(id));
|
||||||
|
// Check that the row no longer exists
|
||||||
|
assertFalse(rowExists(id));
|
||||||
|
// Deleting the row again should have no effect
|
||||||
|
assertFalse(deleteRow(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Test
|
||||||
protected Connection openConnection(File db, boolean encrypt)
|
public void testBatchInsertUpdateAndDelete() throws Exception {
|
||||||
throws SQLException {
|
// Create the table
|
||||||
String url = "jdbc:h2:split:" + db.getAbsolutePath();
|
createTable(connection);
|
||||||
Properties props = new Properties();
|
// Generate some IDs and two sets of names
|
||||||
props.setProperty("user", "user");
|
byte[][] ids = new byte[BATCH_SIZE][];
|
||||||
if (encrypt) {
|
String[] oldNames = new String[BATCH_SIZE];
|
||||||
url += ";CIPHER=AES";
|
String[] newNames = new String[BATCH_SIZE];
|
||||||
String hex = StringUtils.toHexString(key.getBytes());
|
for (int i = 0; i < BATCH_SIZE; i++) {
|
||||||
props.setProperty("password", hex + " password");
|
ids[i] = TestUtils.getRandomId();
|
||||||
|
oldNames[i] = StringUtils.getRandomString(50);
|
||||||
|
newNames[i] = StringUtils.getRandomString(50);
|
||||||
}
|
}
|
||||||
return DriverManager.getConnection(url, props);
|
// Insert the IDs and old names into the table as a batch
|
||||||
|
insertBatch(ids, oldNames);
|
||||||
|
// Update the names as a batch
|
||||||
|
updateBatch(ids, newNames);
|
||||||
|
// Check that the new names can be retrieved using the IDs
|
||||||
|
for (int i = 0; i < BATCH_SIZE; i++) {
|
||||||
|
assertTrue(rowExists(ids[i]));
|
||||||
|
assertEquals(newNames[i], getName(ids[i]));
|
||||||
|
}
|
||||||
|
// Delete the rows as a batch
|
||||||
|
boolean[] deleted = deleteBatch(ids);
|
||||||
|
// Check that the rows no longer exist
|
||||||
|
for (int i = 0; i < BATCH_SIZE; i++) {
|
||||||
|
assertTrue(deleted[i]);
|
||||||
|
assertFalse(rowExists(ids[i]));
|
||||||
|
}
|
||||||
|
// Deleting the rows again should have no effect
|
||||||
|
deleted = deleteBatch(ids);
|
||||||
|
for (int i = 0; i < BATCH_SIZE; i++) assertFalse(deleted[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Test
|
||||||
protected void shutdownDatabase(File db, boolean encrypt)
|
public void testSortOrder() throws Exception {
|
||||||
throws SQLException {
|
byte[] first = new byte[] {
|
||||||
// The DB is closed automatically when the connection is closed
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0
|
||||||
|
};
|
||||||
|
byte[] second = new byte[] {
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 127
|
||||||
|
};
|
||||||
|
byte[] third = new byte[] {
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, (byte) 255
|
||||||
|
};
|
||||||
|
// Create the table
|
||||||
|
createTable(connection);
|
||||||
|
// Insert the rows
|
||||||
|
insertRow(first, "first");
|
||||||
|
insertRow(second, "second");
|
||||||
|
insertRow(third, "third");
|
||||||
|
insertRow(null, "null");
|
||||||
|
// Check the ordering of the < operator: the null ID is not comparable
|
||||||
|
assertNull(getPredecessor(first));
|
||||||
|
assertEquals("first", getPredecessor(second));
|
||||||
|
assertEquals("second", getPredecessor(third));
|
||||||
|
assertNull(getPredecessor(null));
|
||||||
|
// Check the ordering of ORDER BY: nulls come first
|
||||||
|
List<String> names = getNames();
|
||||||
|
assertEquals(4, names.size());
|
||||||
|
assertEquals("null", names.get(0));
|
||||||
|
assertEquals("first", names.get(1));
|
||||||
|
assertEquals("second", names.get(2));
|
||||||
|
assertEquals("third", names.get(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createTable(Connection connection) throws SQLException {
|
||||||
|
try {
|
||||||
|
Statement s = connection.createStatement();
|
||||||
|
s.executeUpdate(CREATE_TABLE);
|
||||||
|
s.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
connection.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertRow(byte[] id, String name) throws SQLException {
|
||||||
|
String sql = "INSERT INTO foo (uniqueId, name) VALUES (?, ?)";
|
||||||
|
try {
|
||||||
|
PreparedStatement ps = connection.prepareStatement(sql);
|
||||||
|
if (id == null) ps.setNull(1, BINARY);
|
||||||
|
else ps.setBytes(1, id);
|
||||||
|
ps.setString(2, name);
|
||||||
|
int affected = ps.executeUpdate();
|
||||||
|
assertEquals(1, affected);
|
||||||
|
ps.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
connection.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean rowExists(byte[] id) throws SQLException {
|
||||||
|
assertNotNull(id);
|
||||||
|
String sql = "SELECT NULL FROM foo WHERE uniqueID = ?";
|
||||||
|
try {
|
||||||
|
PreparedStatement ps = connection.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, id);
|
||||||
|
ResultSet rs = ps.executeQuery();
|
||||||
|
boolean found = rs.next();
|
||||||
|
assertFalse(rs.next());
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
return found;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
connection.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getName(byte[] id) throws SQLException {
|
||||||
|
assertNotNull(id);
|
||||||
|
String sql = "SELECT name FROM foo WHERE uniqueID = ?";
|
||||||
|
try {
|
||||||
|
PreparedStatement ps = connection.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, id);
|
||||||
|
ResultSet rs = ps.executeQuery();
|
||||||
|
assertTrue(rs.next());
|
||||||
|
String name = rs.getString(1);
|
||||||
|
assertFalse(rs.next());
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
return name;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
connection.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateRow(byte[] id, String name) throws SQLException {
|
||||||
|
String sql = "UPDATE foo SET name = ? WHERE uniqueId = ?";
|
||||||
|
try {
|
||||||
|
PreparedStatement ps = connection.prepareStatement(sql);
|
||||||
|
if (id == null) ps.setNull(2, BINARY);
|
||||||
|
else ps.setBytes(2, id);
|
||||||
|
ps.setString(1, name);
|
||||||
|
assertEquals(1, ps.executeUpdate());
|
||||||
|
ps.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
connection.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean deleteRow(byte[] id) throws SQLException {
|
||||||
|
String sql = "DELETE FROM foo WHERE uniqueId = ?";
|
||||||
|
try {
|
||||||
|
PreparedStatement ps = connection.prepareStatement(sql);
|
||||||
|
if (id == null) ps.setNull(1, BINARY);
|
||||||
|
else ps.setBytes(1, id);
|
||||||
|
int affected = ps.executeUpdate();
|
||||||
|
ps.close();
|
||||||
|
return affected == 1;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
connection.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertBatch(byte[][] ids, String[] names) throws SQLException {
|
||||||
|
assertEquals(ids.length, names.length);
|
||||||
|
String sql = "INSERT INTO foo (uniqueId, name) VALUES (?, ?)";
|
||||||
|
try {
|
||||||
|
PreparedStatement ps = connection.prepareStatement(sql);
|
||||||
|
for (int i = 0; i < ids.length; i++) {
|
||||||
|
if (ids[i] == null) ps.setNull(1, BINARY);
|
||||||
|
else ps.setBytes(1, ids[i]);
|
||||||
|
ps.setString(2, names[i]);
|
||||||
|
ps.addBatch();
|
||||||
|
}
|
||||||
|
int[] batchAffected = ps.executeBatch();
|
||||||
|
assertEquals(ids.length, batchAffected.length);
|
||||||
|
for (int affected : batchAffected) assertEquals(1, affected);
|
||||||
|
ps.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
connection.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBatch(byte[][] ids, String[] names) throws SQLException {
|
||||||
|
assertEquals(ids.length, names.length);
|
||||||
|
String sql = "UPDATE foo SET name = ? WHERE uniqueId = ?";
|
||||||
|
try {
|
||||||
|
PreparedStatement ps = connection.prepareStatement(sql);
|
||||||
|
for (int i = 0; i < ids.length; i++) {
|
||||||
|
if (ids[i] == null) ps.setNull(2, BINARY);
|
||||||
|
else ps.setBytes(2, ids[i]);
|
||||||
|
ps.setString(1, names[i]);
|
||||||
|
ps.addBatch();
|
||||||
|
}
|
||||||
|
int[] batchAffected = ps.executeBatch();
|
||||||
|
assertEquals(ids.length, batchAffected.length);
|
||||||
|
for (int affected : batchAffected) assertEquals(1, affected);
|
||||||
|
ps.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
connection.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean[] deleteBatch(byte[][] ids) throws SQLException {
|
||||||
|
String sql = "DELETE FROM foo WHERE uniqueId = ?";
|
||||||
|
try {
|
||||||
|
PreparedStatement ps = connection.prepareStatement(sql);
|
||||||
|
for (byte[] id : ids) {
|
||||||
|
if (id == null) ps.setNull(1, BINARY);
|
||||||
|
else ps.setBytes(1, id);
|
||||||
|
ps.addBatch();
|
||||||
|
}
|
||||||
|
int[] batchAffected = ps.executeBatch();
|
||||||
|
assertEquals(ids.length, batchAffected.length);
|
||||||
|
boolean[] ret = new boolean[ids.length];
|
||||||
|
for (int i = 0; i < batchAffected.length; i++)
|
||||||
|
ret[i] = batchAffected[i] == 1;
|
||||||
|
ps.close();
|
||||||
|
return ret;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
connection.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPredecessor(byte[] id) throws SQLException {
|
||||||
|
String sql = "SELECT name FROM foo WHERE uniqueId < ?"
|
||||||
|
+ " ORDER BY uniqueId DESC LIMIT ?";
|
||||||
|
try {
|
||||||
|
PreparedStatement ps = connection.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, id);
|
||||||
|
ps.setInt(2, 1);
|
||||||
|
ResultSet rs = ps.executeQuery();
|
||||||
|
String name = rs.next() ? rs.getString(1) : null;
|
||||||
|
assertFalse(rs.next());
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
return name;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
connection.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getNames() throws SQLException {
|
||||||
|
String sql = "SELECT name FROM foo ORDER BY uniqueId";
|
||||||
|
List<String> names = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
PreparedStatement ps = connection.prepareStatement(sql);
|
||||||
|
ResultSet rs = ps.executeQuery();
|
||||||
|
while (rs.next()) names.add(rs.getString(1));
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
return names;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
connection.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() throws Exception {
|
||||||
|
if (connection != null) connection.close();
|
||||||
|
TestUtils.deleteTestDirectory(testDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
package org.briarproject.bramble.db;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
|
||||||
import org.briarproject.bramble.test.TestUtils;
|
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.DriverManager;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.sql.Statement;
|
|
||||||
|
|
||||||
public class BasicHyperSqlTest extends BasicDatabaseTest {
|
|
||||||
|
|
||||||
private final SecretKey key = TestUtils.getSecretKey();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getBinaryType() {
|
|
||||||
return "BINARY(32)";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getDriverName() {
|
|
||||||
return "org.hsqldb.jdbc.JDBCDriver";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Connection openConnection(File db, boolean encrypt)
|
|
||||||
throws SQLException {
|
|
||||||
String url = "jdbc:hsqldb:file:" + db.getAbsolutePath() +
|
|
||||||
";sql.enforce_size=false;allow_empty_batch=true";
|
|
||||||
if (encrypt) {
|
|
||||||
String hex = StringUtils.toHexString(key.getBytes());
|
|
||||||
url += ";encrypt_lobs=true;crypt_type=AES;crypt_key=" + hex;
|
|
||||||
}
|
|
||||||
return DriverManager.getConnection(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void shutdownDatabase(File db, boolean encrypt)
|
|
||||||
throws SQLException {
|
|
||||||
Connection c = openConnection(db, encrypt);
|
|
||||||
Statement s = c.createStatement();
|
|
||||||
s.executeQuery("SHUTDOWN");
|
|
||||||
s.close();
|
|
||||||
c.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
package org.briarproject.bramble.db;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.DataTooNewException;
|
||||||
|
import org.briarproject.bramble.api.db.DataTooOldException;
|
||||||
|
import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.settings.Settings;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.briarproject.bramble.system.SystemClock;
|
||||||
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
|
import org.briarproject.bramble.test.TestDatabaseConfig;
|
||||||
|
import org.briarproject.bramble.test.TestUtils;
|
||||||
|
import org.jmock.Expectations;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
|
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.JdbcDatabase.CODE_SCHEMA_VERSION;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
|
private final File testDir = TestUtils.getTestDirectory();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private final Migration<Connection> migration =
|
||||||
|
context.mock(Migration.class, "migration");
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private final Migration<Connection> migration1 =
|
||||||
|
context.mock(Migration.class, "migration1");
|
||||||
|
|
||||||
|
protected final DatabaseConfig config =
|
||||||
|
new TestDatabaseConfig(testDir, 1024 * 1024);
|
||||||
|
protected final Clock clock = new SystemClock();
|
||||||
|
|
||||||
|
abstract Database<Connection> createDatabase(
|
||||||
|
List<Migration<Connection>> migrations) throws Exception;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
assertTrue(testDir.mkdirs());
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
TestUtils.deleteTestDirectory(testDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDoesNotRunMigrationsWhenCreatingDatabase()
|
||||||
|
throws Exception {
|
||||||
|
Database<Connection> db = createDatabase(singletonList(migration));
|
||||||
|
assertFalse(db.open());
|
||||||
|
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = DbException.class)
|
||||||
|
public void testThrowsExceptionIfDataSchemaVersionIsMissing()
|
||||||
|
throws Exception {
|
||||||
|
// Open the DB for the first time
|
||||||
|
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||||
|
assertFalse(db.open());
|
||||||
|
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||||
|
// Override the data schema version
|
||||||
|
setDataSchemaVersion(db, -1);
|
||||||
|
db.close();
|
||||||
|
// Reopen the DB - an exception should be thrown
|
||||||
|
db = createDatabase(asList(migration, migration1));
|
||||||
|
db.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDoesNotRunMigrationsIfSchemaVersionsMatch()
|
||||||
|
throws Exception {
|
||||||
|
// Open the DB for the first time
|
||||||
|
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||||
|
assertFalse(db.open());
|
||||||
|
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||||
|
db.close();
|
||||||
|
// Reopen the DB - migrations should not be run
|
||||||
|
db = createDatabase(asList(migration, migration1));
|
||||||
|
assertTrue(db.open());
|
||||||
|
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = DataTooNewException.class)
|
||||||
|
public void testThrowsExceptionIfDataIsNewerThanCode() throws Exception {
|
||||||
|
// Open the DB for the first time
|
||||||
|
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||||
|
assertFalse(db.open());
|
||||||
|
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||||
|
// Override the data schema version
|
||||||
|
setDataSchemaVersion(db, CODE_SCHEMA_VERSION + 1);
|
||||||
|
db.close();
|
||||||
|
// Reopen the DB - an exception should be thrown
|
||||||
|
db = createDatabase(asList(migration, migration1));
|
||||||
|
db.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = DataTooOldException.class)
|
||||||
|
public void testThrowsExceptionIfCodeIsNewerThanDataAndNoMigrations()
|
||||||
|
throws Exception {
|
||||||
|
// Open the DB for the first time
|
||||||
|
Database<Connection> db = createDatabase(emptyList());
|
||||||
|
assertFalse(db.open());
|
||||||
|
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||||
|
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 1);
|
||||||
|
db.close();
|
||||||
|
// Reopen the DB - an exception should be thrown
|
||||||
|
db = createDatabase(emptyList());
|
||||||
|
db.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = DataTooOldException.class)
|
||||||
|
public void testThrowsExceptionIfCodeIsNewerThanDataAndNoSuitableMigration()
|
||||||
|
throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(migration).getStartVersion();
|
||||||
|
will(returnValue(CODE_SCHEMA_VERSION - 2));
|
||||||
|
oneOf(migration).getEndVersion();
|
||||||
|
will(returnValue(CODE_SCHEMA_VERSION - 1));
|
||||||
|
oneOf(migration1).getStartVersion();
|
||||||
|
will(returnValue(CODE_SCHEMA_VERSION - 1));
|
||||||
|
oneOf(migration1).getEndVersion();
|
||||||
|
will(returnValue(CODE_SCHEMA_VERSION));
|
||||||
|
}});
|
||||||
|
|
||||||
|
// Open the DB for the first time
|
||||||
|
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||||
|
assertFalse(db.open());
|
||||||
|
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||||
|
// Override the data schema version
|
||||||
|
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 3);
|
||||||
|
db.close();
|
||||||
|
// Reopen the DB - an exception should be thrown
|
||||||
|
db = createDatabase(asList(migration, migration1));
|
||||||
|
db.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRunsMigrationIfCodeIsNewerThanDataAndSuitableMigration()
|
||||||
|
throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
// First migration should be run, increasing schema version by 2
|
||||||
|
oneOf(migration).getStartVersion();
|
||||||
|
will(returnValue(CODE_SCHEMA_VERSION - 2));
|
||||||
|
oneOf(migration).getEndVersion();
|
||||||
|
will(returnValue(CODE_SCHEMA_VERSION));
|
||||||
|
oneOf(migration).migrate(with(any(Connection.class)));
|
||||||
|
// Second migration is not suitable and should be skipped
|
||||||
|
oneOf(migration1).getStartVersion();
|
||||||
|
will(returnValue(CODE_SCHEMA_VERSION - 1));
|
||||||
|
oneOf(migration1).getEndVersion();
|
||||||
|
will(returnValue(CODE_SCHEMA_VERSION));
|
||||||
|
}});
|
||||||
|
|
||||||
|
// Open the DB for the first time
|
||||||
|
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||||
|
assertFalse(db.open());
|
||||||
|
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||||
|
// Override the data schema version
|
||||||
|
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 2);
|
||||||
|
db.close();
|
||||||
|
// Reopen the DB - the first migration should be run
|
||||||
|
db = createDatabase(asList(migration, migration1));
|
||||||
|
assertTrue(db.open());
|
||||||
|
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRunsMigrationsIfCodeIsNewerThanDataAndSuitableMigrations()
|
||||||
|
throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
// First migration should be run, incrementing schema version
|
||||||
|
oneOf(migration).getStartVersion();
|
||||||
|
will(returnValue(CODE_SCHEMA_VERSION - 2));
|
||||||
|
oneOf(migration).getEndVersion();
|
||||||
|
will(returnValue(CODE_SCHEMA_VERSION - 1));
|
||||||
|
oneOf(migration).migrate(with(any(Connection.class)));
|
||||||
|
// Second migration should be run, incrementing schema version again
|
||||||
|
oneOf(migration1).getStartVersion();
|
||||||
|
will(returnValue(CODE_SCHEMA_VERSION - 1));
|
||||||
|
oneOf(migration1).getEndVersion();
|
||||||
|
will(returnValue(CODE_SCHEMA_VERSION));
|
||||||
|
oneOf(migration1).migrate(with(any(Connection.class)));
|
||||||
|
}});
|
||||||
|
|
||||||
|
// Open the DB for the first time
|
||||||
|
Database<Connection> db = createDatabase(asList(migration, migration1));
|
||||||
|
assertFalse(db.open());
|
||||||
|
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||||
|
// Override the data schema version
|
||||||
|
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 2);
|
||||||
|
db.close();
|
||||||
|
// Reopen the DB - both migrations should be run
|
||||||
|
db = createDatabase(asList(migration, migration1));
|
||||||
|
assertTrue(db.open());
|
||||||
|
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getDataSchemaVersion(Database<Connection> db)
|
||||||
|
throws Exception {
|
||||||
|
Connection txn = db.startTransaction();
|
||||||
|
Settings s = db.getSettings(txn, DB_SETTINGS_NAMESPACE);
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
return s.getInt(SCHEMA_VERSION_KEY, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setDataSchemaVersion(Database<Connection> db, int version)
|
||||||
|
throws Exception {
|
||||||
|
Settings s = new Settings();
|
||||||
|
s.putInt(SCHEMA_VERSION_KEY, version);
|
||||||
|
Connection txn = db.startTransaction();
|
||||||
|
db.mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
|||||||
|
package org.briarproject.bramble.db;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public class H2MigrationTest extends DatabaseMigrationTest {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Database<Connection> createDatabase(List<Migration<Connection>> migrations)
|
||||||
|
throws Exception {
|
||||||
|
return new H2Database(config, clock) {
|
||||||
|
@Override
|
||||||
|
List<Migration<Connection>> getMigrations() {
|
||||||
|
return migrations;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package org.briarproject.bramble.db;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseConfig;
|
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
|
||||||
|
|
||||||
public class HyperSqlDatabaseTest extends JdbcDatabaseTest {
|
|
||||||
|
|
||||||
public HyperSqlDatabaseTest() throws Exception {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected JdbcDatabase createDatabase(DatabaseConfig config, Clock clock) {
|
|
||||||
return new HyperSqlDatabase(config, clock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,280 @@
|
|||||||
|
package org.briarproject.bramble.db;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.Metadata;
|
||||||
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.bramble.api.sync.ValidationManager.State;
|
||||||
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
|
import org.briarproject.bramble.test.TestUtils;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import static junit.framework.TestCase.assertNotNull;
|
||||||
|
import static junit.framework.TestCase.assertTrue;
|
||||||
|
import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED;
|
||||||
|
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class Migration30_31Test extends BrambleTestCase {
|
||||||
|
|
||||||
|
private static final String CREATE_GROUPS_STUB =
|
||||||
|
"CREATE TABLE groups"
|
||||||
|
+ " (groupID BINARY(32) NOT NULL,"
|
||||||
|
+ " PRIMARY KEY (groupId))";
|
||||||
|
|
||||||
|
private static final String CREATE_MESSAGES =
|
||||||
|
"CREATE TABLE messages"
|
||||||
|
+ " (messageId BINARY(32) NOT NULL,"
|
||||||
|
+ " groupId BINARY(32) NOT NULL,"
|
||||||
|
+ " timestamp BIGINT NOT NULL,"
|
||||||
|
+ " state INT NOT NULL,"
|
||||||
|
+ " shared BOOLEAN NOT NULL,"
|
||||||
|
+ " length INT NOT NULL,"
|
||||||
|
+ " raw BLOB," // Null if message has been deleted
|
||||||
|
+ " PRIMARY KEY (messageId),"
|
||||||
|
+ " FOREIGN KEY (groupId)"
|
||||||
|
+ " REFERENCES groups (groupId)"
|
||||||
|
+ " ON DELETE CASCADE)";
|
||||||
|
|
||||||
|
private static final String CREATE_MESSAGE_METADATA_30 =
|
||||||
|
"CREATE TABLE messageMetadata"
|
||||||
|
+ " (messageId BINARY(32) NOT NULL,"
|
||||||
|
+ " key VARCHAR NOT NULL,"
|
||||||
|
+ " value BINARY NOT NULL,"
|
||||||
|
+ " PRIMARY KEY (messageId, key),"
|
||||||
|
+ " FOREIGN KEY (messageId)"
|
||||||
|
+ " REFERENCES messages (messageId)"
|
||||||
|
+ " ON DELETE CASCADE)";
|
||||||
|
|
||||||
|
private final File testDir = TestUtils.getTestDirectory();
|
||||||
|
private final File db = new File(testDir, "db");
|
||||||
|
private final String url = "jdbc:h2:" + db.getAbsolutePath();
|
||||||
|
private final GroupId groupId = new GroupId(getRandomId());
|
||||||
|
private final GroupId groupId1 = new GroupId(getRandomId());
|
||||||
|
private final Message message = TestUtils.getMessage(groupId);
|
||||||
|
private final Message message1 = TestUtils.getMessage(groupId1);
|
||||||
|
private final Metadata meta = new Metadata(), meta1 = new Metadata();
|
||||||
|
|
||||||
|
private Connection connection = null;
|
||||||
|
|
||||||
|
public Migration30_31Test() {
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
meta.put(getRandomString(123 + i), getRandomBytes(123 + i));
|
||||||
|
meta1.put(getRandomString(123 + i), getRandomBytes(123 + i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
assertTrue(testDir.mkdirs());
|
||||||
|
Class.forName("org.h2.Driver");
|
||||||
|
connection = DriverManager.getConnection(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() throws Exception {
|
||||||
|
if (connection != null) connection.close();
|
||||||
|
TestUtils.deleteTestDirectory(testDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMigration() throws Exception {
|
||||||
|
try {
|
||||||
|
Statement s = connection.createStatement();
|
||||||
|
s.execute(CREATE_GROUPS_STUB);
|
||||||
|
s.execute(CREATE_MESSAGES);
|
||||||
|
s.execute(CREATE_MESSAGE_METADATA_30);
|
||||||
|
s.close();
|
||||||
|
|
||||||
|
addGroup(groupId);
|
||||||
|
addMessage(message, DELIVERED, true);
|
||||||
|
addMessageMetadata30(message, meta);
|
||||||
|
assertMetadataEquals(meta, getMessageMetadata(message.getId()));
|
||||||
|
|
||||||
|
addGroup(groupId1);
|
||||||
|
addMessage(message1, UNKNOWN, false);
|
||||||
|
addMessageMetadata30(message1, meta1);
|
||||||
|
assertMetadataEquals(meta1, getMessageMetadata(message1.getId()));
|
||||||
|
|
||||||
|
new Migration30_31().migrate(connection);
|
||||||
|
|
||||||
|
assertMetadataEquals(meta, getMessageMetadata(message.getId()));
|
||||||
|
for (String key : meta.keySet()) {
|
||||||
|
GroupId g = getMessageMetadataGroupId31(message.getId(), key);
|
||||||
|
assertEquals(groupId, g);
|
||||||
|
State state = getMessageMetadataState31(message.getId(), key);
|
||||||
|
assertEquals(DELIVERED, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertMetadataEquals(meta1, getMessageMetadata(message1.getId()));
|
||||||
|
for (String key : meta1.keySet()) {
|
||||||
|
GroupId g = getMessageMetadataGroupId31(message1.getId(), key);
|
||||||
|
assertEquals(groupId1, g);
|
||||||
|
State state = getMessageMetadataState31(message1.getId(), key);
|
||||||
|
assertEquals(UNKNOWN, state);
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
connection.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addGroup(GroupId g) throws SQLException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
try {
|
||||||
|
String sql = "INSERT INTO groups (groupId) VALUES (?)";
|
||||||
|
ps = connection.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, g.getBytes());
|
||||||
|
int affected = ps.executeUpdate();
|
||||||
|
if (affected != 1) throw new DbStateException();
|
||||||
|
ps.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
if (ps != null) ps.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addMessage(Message m, State state, boolean shared)
|
||||||
|
throws SQLException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
try {
|
||||||
|
String sql = "INSERT INTO messages (messageId, groupId, timestamp,"
|
||||||
|
+ " state, shared, length, raw)"
|
||||||
|
+ " VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||||
|
ps = connection.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, m.getId().getBytes());
|
||||||
|
ps.setBytes(2, m.getGroupId().getBytes());
|
||||||
|
ps.setLong(3, m.getTimestamp());
|
||||||
|
ps.setInt(4, state.getValue());
|
||||||
|
ps.setBoolean(5, shared);
|
||||||
|
byte[] raw = m.getRaw();
|
||||||
|
ps.setInt(6, raw.length);
|
||||||
|
ps.setBytes(7, raw);
|
||||||
|
int affected = ps.executeUpdate();
|
||||||
|
if (affected != 1) throw new DbStateException();
|
||||||
|
ps.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
if (ps != null) ps.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addMessageMetadata30(Message m, Metadata meta)
|
||||||
|
throws SQLException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
try {
|
||||||
|
String sql = "INSERT INTO messageMetadata"
|
||||||
|
+ " (messageId, key, value)"
|
||||||
|
+ " VALUES (?, ?, ?)";
|
||||||
|
ps = connection.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, m.getId().getBytes());
|
||||||
|
for (Entry<String, byte[]> e : meta.entrySet()) {
|
||||||
|
ps.setString(2, e.getKey());
|
||||||
|
ps.setBytes(3, e.getValue());
|
||||||
|
ps.addBatch();
|
||||||
|
}
|
||||||
|
int[] batchAffected = ps.executeBatch();
|
||||||
|
if (batchAffected.length != meta.size())
|
||||||
|
throw new DbStateException();
|
||||||
|
for (int rows : batchAffected)
|
||||||
|
if (rows != 1) throw new DbStateException();
|
||||||
|
ps.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
if (ps != null) ps.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Metadata getMessageMetadata(MessageId m) throws SQLException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
String sql = "SELECT key, value FROM messageMetadata"
|
||||||
|
+ " WHERE messageId = ?";
|
||||||
|
ps = connection.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, m.getBytes());
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
Metadata meta = new Metadata();
|
||||||
|
while (rs.next()) meta.put(rs.getString(1), rs.getBytes(2));
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
return meta;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
if (rs != null) rs.close();
|
||||||
|
if (ps != null) ps.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private GroupId getMessageMetadataGroupId31(MessageId m, String key)
|
||||||
|
throws SQLException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
String sql = "SELECT groupId FROM messageMetadata"
|
||||||
|
+ " WHERE messageId = ? AND key = ?";
|
||||||
|
ps = connection.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, m.getBytes());
|
||||||
|
ps.setString(2, key);
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
if (!rs.next()) throw new DbStateException();
|
||||||
|
GroupId g = new GroupId(rs.getBytes(1));
|
||||||
|
if (rs.next()) throw new DbStateException();
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
return g;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
if (rs != null) rs.close();
|
||||||
|
if (ps != null) ps.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private State getMessageMetadataState31(MessageId m, String key)
|
||||||
|
throws SQLException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
String sql = "SELECT state FROM messageMetadata"
|
||||||
|
+ " WHERE messageId = ? AND key = ?";
|
||||||
|
ps = connection.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, m.getBytes());
|
||||||
|
ps.setString(2, key);
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
if (!rs.next()) throw new DbStateException();
|
||||||
|
State state = State.fromValue(rs.getInt(1));
|
||||||
|
if (rs.next()) throw new DbStateException();
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
return state;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
if (rs != null) rs.close();
|
||||||
|
if (ps != null) ps.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertMetadataEquals(Metadata expected, Metadata actual) {
|
||||||
|
assertEquals(expected.size(), actual.size());
|
||||||
|
for (Entry<String, byte[]> e : expected.entrySet()) {
|
||||||
|
byte[] value = actual.get(e.getKey());
|
||||||
|
assertNotNull(value);
|
||||||
|
assertArrayEquals(e.getValue(), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ import static org.junit.Assert.assertFalse;
|
|||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
public class H2TransactionIsolationTest extends BrambleTestCase {
|
public class TransactionIsolationTest extends BrambleTestCase {
|
||||||
|
|
||||||
private static final String DROP_TABLE = "DROP TABLE foo IF EXISTS";
|
private static final String DROP_TABLE = "DROP TABLE foo IF EXISTS";
|
||||||
private static final String CREATE_TABLE = "CREATE TABLE foo"
|
private static final String CREATE_TABLE = "CREATE TABLE foo"
|
||||||
@@ -34,9 +34,9 @@ public class H2TransactionIsolationTest extends BrambleTestCase {
|
|||||||
|
|
||||||
private final File testDir = TestUtils.getTestDirectory();
|
private final File testDir = TestUtils.getTestDirectory();
|
||||||
private final File db = new File(testDir, "db");
|
private final File db = new File(testDir, "db");
|
||||||
private final String withMvcc = "jdbc:h2:split:" + db.getAbsolutePath()
|
private final String withMvcc = "jdbc:h2:" + db.getAbsolutePath()
|
||||||
+ ";MV_STORE=TRUE;MVCC=TRUE";
|
+ ";MV_STORE=TRUE;MVCC=TRUE";
|
||||||
private final String withoutMvcc = "jdbc:h2:split:" + db.getAbsolutePath()
|
private final String withoutMvcc = "jdbc:h2:" + db.getAbsolutePath()
|
||||||
+ ";MV_STORE=FALSE;MVCC=FALSE;LOCK_MODE=1";
|
+ ";MV_STORE=FALSE;MVCC=FALSE;LOCK_MODE=1";
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
package org.briarproject.bramble.keyagreement;
|
package org.briarproject.bramble.keyagreement;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
|
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
|
||||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.api.keyagreement.Payload;
|
import org.briarproject.bramble.api.keyagreement.Payload;
|
||||||
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
|
import org.briarproject.bramble.test.TestUtils;
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
import org.jmock.auto.Mock;
|
import org.jmock.auto.Mock;
|
||||||
import org.jmock.integration.junit4.JUnitRuleMockery;
|
import org.jmock.integration.junit4.JUnitRuleMockery;
|
||||||
@@ -17,11 +16,6 @@ import org.junit.Rule;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
|
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
|
||||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.MASTER_SECRET_LABEL;
|
|
||||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
|
|
||||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.SHARED_SECRET_LABEL;
|
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
|
||||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
@@ -34,33 +28,34 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
|
|||||||
setImposteriser(ClassImposteriser.INSTANCE);
|
setImposteriser(ClassImposteriser.INSTANCE);
|
||||||
}};
|
}};
|
||||||
|
|
||||||
private final PublicKey alicePubKey =
|
private static final byte[] ALICE_PUBKEY = TestUtils.getRandomBytes(32);
|
||||||
context.mock(PublicKey.class, "alice");
|
private static final byte[] ALICE_COMMIT =
|
||||||
private final byte[] alicePubKeyBytes = getRandomBytes(32);
|
TestUtils.getRandomBytes(COMMIT_LENGTH);
|
||||||
private final byte[] aliceCommit = getRandomBytes(COMMIT_LENGTH);
|
private static final byte[] ALICE_PAYLOAD =
|
||||||
private final byte[] alicePayload = getRandomBytes(COMMIT_LENGTH + 8);
|
TestUtils.getRandomBytes(COMMIT_LENGTH + 8);
|
||||||
private final byte[] aliceConfirm = getRandomBytes(SecretKey.LENGTH);
|
|
||||||
|
|
||||||
private final PublicKey bobPubKey = context.mock(PublicKey.class, "bob");
|
private static final byte[] BOB_PUBKEY = TestUtils.getRandomBytes(32);
|
||||||
private final byte[] bobPubKeyBytes = getRandomBytes(32);
|
private static final byte[] BOB_COMMIT =
|
||||||
private final byte[] bobCommit = getRandomBytes(COMMIT_LENGTH);
|
TestUtils.getRandomBytes(COMMIT_LENGTH);
|
||||||
private final byte[] bobPayload = getRandomBytes(COMMIT_LENGTH + 19);
|
private static final byte[] BOB_PAYLOAD =
|
||||||
private final byte[] bobConfirm = getRandomBytes(SecretKey.LENGTH);
|
TestUtils.getRandomBytes(COMMIT_LENGTH + 19);
|
||||||
|
|
||||||
private final PublicKey badPubKey = context.mock(PublicKey.class, "bad");
|
private static final byte[] ALICE_CONFIRM =
|
||||||
private final byte[] badPubKeyBytes = getRandomBytes(32);
|
TestUtils.getRandomBytes(SecretKey.LENGTH);
|
||||||
private final byte[] badCommit = getRandomBytes(COMMIT_LENGTH);
|
private static final byte[] BOB_CONFIRM =
|
||||||
private final byte[] badConfirm = getRandomBytes(SecretKey.LENGTH);
|
TestUtils.getRandomBytes(SecretKey.LENGTH);
|
||||||
|
|
||||||
|
private static final byte[] BAD_PUBKEY = TestUtils.getRandomBytes(32);
|
||||||
|
private static final byte[] BAD_COMMIT =
|
||||||
|
TestUtils.getRandomBytes(COMMIT_LENGTH);
|
||||||
|
private static final byte[] BAD_CONFIRM =
|
||||||
|
TestUtils.getRandomBytes(SecretKey.LENGTH);
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
KeyAgreementProtocol.Callbacks callbacks;
|
KeyAgreementProtocol.Callbacks callbacks;
|
||||||
@Mock
|
@Mock
|
||||||
CryptoComponent crypto;
|
CryptoComponent crypto;
|
||||||
@Mock
|
@Mock
|
||||||
KeyAgreementCrypto keyAgreementCrypto;
|
|
||||||
@Mock
|
|
||||||
KeyParser keyParser;
|
|
||||||
@Mock
|
|
||||||
PayloadEncoder payloadEncoder;
|
PayloadEncoder payloadEncoder;
|
||||||
@Mock
|
@Mock
|
||||||
KeyAgreementTransport transport;
|
KeyAgreementTransport transport;
|
||||||
@@ -70,72 +65,60 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testAliceProtocol() throws Exception {
|
public void testAliceProtocol() throws Exception {
|
||||||
// set up
|
// set up
|
||||||
Payload theirPayload = new Payload(bobCommit, null);
|
Payload theirPayload = new Payload(BOB_COMMIT, null);
|
||||||
Payload ourPayload = new Payload(aliceCommit, null);
|
Payload ourPayload = new Payload(ALICE_COMMIT, null);
|
||||||
KeyPair ourKeyPair = new KeyPair(ourPubKey, null);
|
KeyPair ourKeyPair = new KeyPair(ourPubKey, null);
|
||||||
SecretKey sharedSecret = getSecretKey();
|
SecretKey sharedSecret = TestUtils.getSecretKey();
|
||||||
SecretKey masterSecret = getSecretKey();
|
SecretKey masterSecret = TestUtils.getSecretKey();
|
||||||
|
|
||||||
KeyAgreementProtocol protocol = new KeyAgreementProtocol(callbacks,
|
KeyAgreementProtocol protocol =
|
||||||
crypto, keyAgreementCrypto, payloadEncoder, transport,
|
new KeyAgreementProtocol(callbacks, crypto, payloadEncoder,
|
||||||
theirPayload, ourPayload, ourKeyPair, true);
|
transport, theirPayload, ourPayload, ourKeyPair, true);
|
||||||
|
|
||||||
// expectations
|
// expectations
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// Helpers
|
// Helpers
|
||||||
allowing(payloadEncoder).encode(ourPayload);
|
allowing(payloadEncoder).encode(ourPayload);
|
||||||
will(returnValue(alicePayload));
|
will(returnValue(ALICE_PAYLOAD));
|
||||||
allowing(payloadEncoder).encode(theirPayload);
|
allowing(payloadEncoder).encode(theirPayload);
|
||||||
will(returnValue(bobPayload));
|
will(returnValue(BOB_PAYLOAD));
|
||||||
allowing(ourPubKey).getEncoded();
|
allowing(ourPubKey).getEncoded();
|
||||||
will(returnValue(alicePubKeyBytes));
|
will(returnValue(ALICE_PUBKEY));
|
||||||
allowing(crypto).getAgreementKeyParser();
|
|
||||||
will(returnValue(keyParser));
|
|
||||||
allowing(alicePubKey).getEncoded();
|
|
||||||
will(returnValue(alicePubKeyBytes));
|
|
||||||
allowing(bobPubKey).getEncoded();
|
|
||||||
will(returnValue(bobPubKeyBytes));
|
|
||||||
|
|
||||||
// Alice sends her public key
|
// Alice sends her public key
|
||||||
oneOf(transport).sendKey(alicePubKeyBytes);
|
oneOf(transport).sendKey(ALICE_PUBKEY);
|
||||||
|
|
||||||
// Alice receives Bob's public key
|
// Alice receives Bob's public key
|
||||||
oneOf(callbacks).connectionWaiting();
|
oneOf(callbacks).connectionWaiting();
|
||||||
oneOf(transport).receiveKey();
|
oneOf(transport).receiveKey();
|
||||||
will(returnValue(bobPubKeyBytes));
|
will(returnValue(BOB_PUBKEY));
|
||||||
oneOf(callbacks).initialRecordReceived();
|
oneOf(callbacks).initialRecordReceived();
|
||||||
oneOf(keyParser).parsePublicKey(bobPubKeyBytes);
|
|
||||||
will(returnValue(bobPubKey));
|
|
||||||
|
|
||||||
// Alice verifies Bob's public key
|
// Alice verifies Bob's public key
|
||||||
oneOf(keyAgreementCrypto).deriveKeyCommitment(bobPubKey);
|
oneOf(crypto).deriveKeyCommitment(BOB_PUBKEY);
|
||||||
will(returnValue(bobCommit));
|
will(returnValue(BOB_COMMIT));
|
||||||
|
|
||||||
// Alice computes shared secret
|
// Alice computes shared secret
|
||||||
oneOf(crypto).deriveSharedSecret(SHARED_SECRET_LABEL, bobPubKey,
|
oneOf(crypto).deriveSharedSecret(BOB_PUBKEY, ourKeyPair, true);
|
||||||
ourKeyPair, new byte[] {PROTOCOL_VERSION},
|
|
||||||
alicePubKeyBytes, bobPubKeyBytes);
|
|
||||||
will(returnValue(sharedSecret));
|
will(returnValue(sharedSecret));
|
||||||
|
|
||||||
// Alice sends her confirmation record
|
// Alice sends her confirmation record
|
||||||
oneOf(keyAgreementCrypto).deriveConfirmationRecord(sharedSecret,
|
oneOf(crypto).deriveConfirmationRecord(sharedSecret, BOB_PAYLOAD,
|
||||||
bobPayload, alicePayload, bobPubKey, ourKeyPair,
|
ALICE_PAYLOAD, BOB_PUBKEY, ourKeyPair, true, true);
|
||||||
true, true);
|
will(returnValue(ALICE_CONFIRM));
|
||||||
will(returnValue(aliceConfirm));
|
oneOf(transport).sendConfirm(ALICE_CONFIRM);
|
||||||
oneOf(transport).sendConfirm(aliceConfirm);
|
|
||||||
|
|
||||||
// Alice receives Bob's confirmation record
|
// Alice receives Bob's confirmation record
|
||||||
oneOf(transport).receiveConfirm();
|
oneOf(transport).receiveConfirm();
|
||||||
will(returnValue(bobConfirm));
|
will(returnValue(BOB_CONFIRM));
|
||||||
|
|
||||||
// Alice verifies Bob's confirmation record
|
// Alice verifies Bob's confirmation record
|
||||||
oneOf(keyAgreementCrypto).deriveConfirmationRecord(sharedSecret,
|
oneOf(crypto).deriveConfirmationRecord(sharedSecret, BOB_PAYLOAD,
|
||||||
bobPayload, alicePayload, bobPubKey, ourKeyPair,
|
ALICE_PAYLOAD, BOB_PUBKEY, ourKeyPair, true, false);
|
||||||
true, false);
|
will(returnValue(BOB_CONFIRM));
|
||||||
will(returnValue(bobConfirm));
|
|
||||||
|
|
||||||
// Alice computes master secret
|
// Alice computes master secret
|
||||||
oneOf(crypto).deriveKey(MASTER_SECRET_LABEL, sharedSecret);
|
oneOf(crypto).deriveMasterSecret(sharedSecret);
|
||||||
will(returnValue(masterSecret));
|
will(returnValue(masterSecret));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
@@ -146,71 +129,59 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testBobProtocol() throws Exception {
|
public void testBobProtocol() throws Exception {
|
||||||
// set up
|
// set up
|
||||||
Payload theirPayload = new Payload(aliceCommit, null);
|
Payload theirPayload = new Payload(ALICE_COMMIT, null);
|
||||||
Payload ourPayload = new Payload(bobCommit, null);
|
Payload ourPayload = new Payload(BOB_COMMIT, null);
|
||||||
KeyPair ourKeyPair = new KeyPair(ourPubKey, null);
|
KeyPair ourKeyPair = new KeyPair(ourPubKey, null);
|
||||||
SecretKey sharedSecret = getSecretKey();
|
SecretKey sharedSecret = TestUtils.getSecretKey();
|
||||||
SecretKey masterSecret = getSecretKey();
|
SecretKey masterSecret = TestUtils.getSecretKey();
|
||||||
|
|
||||||
KeyAgreementProtocol protocol = new KeyAgreementProtocol(callbacks,
|
KeyAgreementProtocol protocol =
|
||||||
crypto, keyAgreementCrypto, payloadEncoder, transport,
|
new KeyAgreementProtocol(callbacks, crypto, payloadEncoder,
|
||||||
theirPayload, ourPayload, ourKeyPair, false);
|
transport, theirPayload, ourPayload, ourKeyPair, false);
|
||||||
|
|
||||||
// expectations
|
// expectations
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// Helpers
|
// Helpers
|
||||||
allowing(payloadEncoder).encode(ourPayload);
|
allowing(payloadEncoder).encode(ourPayload);
|
||||||
will(returnValue(bobPayload));
|
will(returnValue(BOB_PAYLOAD));
|
||||||
allowing(payloadEncoder).encode(theirPayload);
|
allowing(payloadEncoder).encode(theirPayload);
|
||||||
will(returnValue(alicePayload));
|
will(returnValue(ALICE_PAYLOAD));
|
||||||
allowing(ourPubKey).getEncoded();
|
allowing(ourPubKey).getEncoded();
|
||||||
will(returnValue(bobPubKeyBytes));
|
will(returnValue(BOB_PUBKEY));
|
||||||
allowing(crypto).getAgreementKeyParser();
|
|
||||||
will(returnValue(keyParser));
|
|
||||||
allowing(alicePubKey).getEncoded();
|
|
||||||
will(returnValue(alicePubKeyBytes));
|
|
||||||
allowing(bobPubKey).getEncoded();
|
|
||||||
will(returnValue(bobPubKeyBytes));
|
|
||||||
|
|
||||||
// Bob receives Alice's public key
|
// Bob receives Alice's public key
|
||||||
oneOf(transport).receiveKey();
|
oneOf(transport).receiveKey();
|
||||||
will(returnValue(alicePubKeyBytes));
|
will(returnValue(ALICE_PUBKEY));
|
||||||
oneOf(callbacks).initialRecordReceived();
|
oneOf(callbacks).initialRecordReceived();
|
||||||
oneOf(keyParser).parsePublicKey(alicePubKeyBytes);
|
|
||||||
will(returnValue(alicePubKey));
|
|
||||||
|
|
||||||
// Bob verifies Alice's public key
|
// Bob verifies Alice's public key
|
||||||
oneOf(keyAgreementCrypto).deriveKeyCommitment(alicePubKey);
|
oneOf(crypto).deriveKeyCommitment(ALICE_PUBKEY);
|
||||||
will(returnValue(aliceCommit));
|
will(returnValue(ALICE_COMMIT));
|
||||||
|
|
||||||
// Bob sends his public key
|
// Bob sends his public key
|
||||||
oneOf(transport).sendKey(bobPubKeyBytes);
|
oneOf(transport).sendKey(BOB_PUBKEY);
|
||||||
|
|
||||||
// Bob computes shared secret
|
// Bob computes shared secret
|
||||||
oneOf(crypto).deriveSharedSecret(SHARED_SECRET_LABEL, alicePubKey,
|
oneOf(crypto).deriveSharedSecret(ALICE_PUBKEY, ourKeyPair, false);
|
||||||
ourKeyPair, new byte[] {PROTOCOL_VERSION},
|
|
||||||
alicePubKeyBytes, bobPubKeyBytes);
|
|
||||||
will(returnValue(sharedSecret));
|
will(returnValue(sharedSecret));
|
||||||
|
|
||||||
// Bob receives Alices's confirmation record
|
// Bob receives Alices's confirmation record
|
||||||
oneOf(transport).receiveConfirm();
|
oneOf(transport).receiveConfirm();
|
||||||
will(returnValue(aliceConfirm));
|
will(returnValue(ALICE_CONFIRM));
|
||||||
|
|
||||||
// Bob verifies Alice's confirmation record
|
// Bob verifies Alice's confirmation record
|
||||||
oneOf(keyAgreementCrypto).deriveConfirmationRecord(sharedSecret,
|
oneOf(crypto).deriveConfirmationRecord(sharedSecret, ALICE_PAYLOAD,
|
||||||
alicePayload, bobPayload, alicePubKey, ourKeyPair,
|
BOB_PAYLOAD, ALICE_PUBKEY, ourKeyPair, false, true);
|
||||||
false, true);
|
will(returnValue(ALICE_CONFIRM));
|
||||||
will(returnValue(aliceConfirm));
|
|
||||||
|
|
||||||
// Bob sends his confirmation record
|
// Bob sends his confirmation record
|
||||||
oneOf(keyAgreementCrypto).deriveConfirmationRecord(sharedSecret,
|
oneOf(crypto).deriveConfirmationRecord(sharedSecret, ALICE_PAYLOAD,
|
||||||
alicePayload, bobPayload, alicePubKey, ourKeyPair,
|
BOB_PAYLOAD, ALICE_PUBKEY, ourKeyPair, false, false);
|
||||||
false, false);
|
will(returnValue(BOB_CONFIRM));
|
||||||
will(returnValue(bobConfirm));
|
oneOf(transport).sendConfirm(BOB_CONFIRM);
|
||||||
oneOf(transport).sendConfirm(bobConfirm);
|
|
||||||
|
|
||||||
// Bob computes master secret
|
// Bob computes master secret
|
||||||
oneOf(crypto).deriveKey(MASTER_SECRET_LABEL, sharedSecret);
|
oneOf(crypto).deriveMasterSecret(sharedSecret);
|
||||||
will(returnValue(masterSecret));
|
will(returnValue(masterSecret));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
@@ -221,44 +192,38 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
|
|||||||
@Test(expected = AbortException.class)
|
@Test(expected = AbortException.class)
|
||||||
public void testAliceProtocolAbortOnBadKey() throws Exception {
|
public void testAliceProtocolAbortOnBadKey() throws Exception {
|
||||||
// set up
|
// set up
|
||||||
Payload theirPayload = new Payload(bobCommit, null);
|
Payload theirPayload = new Payload(BOB_COMMIT, null);
|
||||||
Payload ourPayload = new Payload(aliceCommit, null);
|
Payload ourPayload = new Payload(ALICE_COMMIT, null);
|
||||||
KeyPair ourKeyPair = new KeyPair(ourPubKey, null);
|
KeyPair ourKeyPair = new KeyPair(ourPubKey, null);
|
||||||
|
|
||||||
KeyAgreementProtocol protocol = new KeyAgreementProtocol(callbacks,
|
KeyAgreementProtocol protocol =
|
||||||
crypto, keyAgreementCrypto, payloadEncoder, transport,
|
new KeyAgreementProtocol(callbacks, crypto, payloadEncoder,
|
||||||
theirPayload, ourPayload, ourKeyPair, true);
|
transport, theirPayload, ourPayload, ourKeyPair, true);
|
||||||
|
|
||||||
// expectations
|
// expectations
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// Helpers
|
// Helpers
|
||||||
allowing(ourPubKey).getEncoded();
|
allowing(ourPubKey).getEncoded();
|
||||||
will(returnValue(alicePubKeyBytes));
|
will(returnValue(ALICE_PUBKEY));
|
||||||
allowing(crypto).getAgreementKeyParser();
|
|
||||||
will(returnValue(keyParser));
|
|
||||||
|
|
||||||
// Alice sends her public key
|
// Alice sends her public key
|
||||||
oneOf(transport).sendKey(alicePubKeyBytes);
|
oneOf(transport).sendKey(ALICE_PUBKEY);
|
||||||
|
|
||||||
// Alice receives a bad public key
|
// Alice receives a bad public key
|
||||||
oneOf(callbacks).connectionWaiting();
|
oneOf(callbacks).connectionWaiting();
|
||||||
oneOf(transport).receiveKey();
|
oneOf(transport).receiveKey();
|
||||||
will(returnValue(badPubKeyBytes));
|
will(returnValue(BAD_PUBKEY));
|
||||||
oneOf(callbacks).initialRecordReceived();
|
oneOf(callbacks).initialRecordReceived();
|
||||||
oneOf(keyParser).parsePublicKey(badPubKeyBytes);
|
|
||||||
will(returnValue(badPubKey));
|
|
||||||
|
|
||||||
// Alice verifies Bob's public key
|
// Alice verifies Bob's public key
|
||||||
oneOf(keyAgreementCrypto).deriveKeyCommitment(badPubKey);
|
oneOf(crypto).deriveKeyCommitment(BAD_PUBKEY);
|
||||||
will(returnValue(badCommit));
|
will(returnValue(BAD_COMMIT));
|
||||||
|
|
||||||
// Alice aborts
|
// Alice aborts
|
||||||
oneOf(transport).sendAbort(false);
|
oneOf(transport).sendAbort(false);
|
||||||
|
|
||||||
// Alice never computes shared secret
|
// Alice never computes shared secret
|
||||||
never(crypto).deriveSharedSecret(SHARED_SECRET_LABEL, badPubKey,
|
never(crypto).deriveSharedSecret(BAD_PUBKEY, ourKeyPair, true);
|
||||||
ourKeyPair, new byte[] {PROTOCOL_VERSION},
|
|
||||||
alicePubKeyBytes, bobPubKeyBytes);
|
|
||||||
}});
|
}});
|
||||||
|
|
||||||
// execute
|
// execute
|
||||||
@@ -268,38 +233,34 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
|
|||||||
@Test(expected = AbortException.class)
|
@Test(expected = AbortException.class)
|
||||||
public void testBobProtocolAbortOnBadKey() throws Exception {
|
public void testBobProtocolAbortOnBadKey() throws Exception {
|
||||||
// set up
|
// set up
|
||||||
Payload theirPayload = new Payload(aliceCommit, null);
|
Payload theirPayload = new Payload(ALICE_COMMIT, null);
|
||||||
Payload ourPayload = new Payload(bobCommit, null);
|
Payload ourPayload = new Payload(BOB_COMMIT, null);
|
||||||
KeyPair ourKeyPair = new KeyPair(ourPubKey, null);
|
KeyPair ourKeyPair = new KeyPair(ourPubKey, null);
|
||||||
|
|
||||||
KeyAgreementProtocol protocol = new KeyAgreementProtocol(callbacks,
|
KeyAgreementProtocol protocol =
|
||||||
crypto, keyAgreementCrypto, payloadEncoder, transport,
|
new KeyAgreementProtocol(callbacks, crypto, payloadEncoder,
|
||||||
theirPayload, ourPayload, ourKeyPair, false);
|
transport, theirPayload, ourPayload, ourKeyPair, false);
|
||||||
|
|
||||||
// expectations
|
// expectations
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// Helpers
|
// Helpers
|
||||||
allowing(ourPubKey).getEncoded();
|
allowing(ourPubKey).getEncoded();
|
||||||
will(returnValue(bobPubKeyBytes));
|
will(returnValue(BOB_PUBKEY));
|
||||||
allowing(crypto).getAgreementKeyParser();
|
|
||||||
will(returnValue(keyParser));
|
|
||||||
|
|
||||||
// Bob receives a bad public key
|
// Bob receives a bad public key
|
||||||
oneOf(transport).receiveKey();
|
oneOf(transport).receiveKey();
|
||||||
will(returnValue(badPubKeyBytes));
|
will(returnValue(BAD_PUBKEY));
|
||||||
oneOf(callbacks).initialRecordReceived();
|
oneOf(callbacks).initialRecordReceived();
|
||||||
oneOf(keyParser).parsePublicKey(badPubKeyBytes);
|
|
||||||
will(returnValue(badPubKey));
|
|
||||||
|
|
||||||
// Bob verifies Alice's public key
|
// Bob verifies Alice's public key
|
||||||
oneOf(keyAgreementCrypto).deriveKeyCommitment(badPubKey);
|
oneOf(crypto).deriveKeyCommitment(BAD_PUBKEY);
|
||||||
will(returnValue(badCommit));
|
will(returnValue(BAD_COMMIT));
|
||||||
|
|
||||||
// Bob aborts
|
// Bob aborts
|
||||||
oneOf(transport).sendAbort(false);
|
oneOf(transport).sendAbort(false);
|
||||||
|
|
||||||
// Bob never sends his public key
|
// Bob never sends his public key
|
||||||
never(transport).sendKey(bobPubKeyBytes);
|
never(transport).sendKey(BOB_PUBKEY);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
// execute
|
// execute
|
||||||
@@ -309,72 +270,62 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
|
|||||||
@Test(expected = AbortException.class)
|
@Test(expected = AbortException.class)
|
||||||
public void testAliceProtocolAbortOnBadConfirm() throws Exception {
|
public void testAliceProtocolAbortOnBadConfirm() throws Exception {
|
||||||
// set up
|
// set up
|
||||||
Payload theirPayload = new Payload(bobCommit, null);
|
Payload theirPayload = new Payload(BOB_COMMIT, null);
|
||||||
Payload ourPayload = new Payload(aliceCommit, null);
|
Payload ourPayload = new Payload(ALICE_COMMIT, null);
|
||||||
KeyPair ourKeyPair = new KeyPair(ourPubKey, null);
|
KeyPair ourKeyPair = new KeyPair(ourPubKey, null);
|
||||||
SecretKey sharedSecret = getSecretKey();
|
SecretKey sharedSecret = TestUtils.getSecretKey();
|
||||||
|
|
||||||
KeyAgreementProtocol protocol = new KeyAgreementProtocol(callbacks,
|
KeyAgreementProtocol protocol =
|
||||||
crypto, keyAgreementCrypto, payloadEncoder, transport,
|
new KeyAgreementProtocol(callbacks, crypto, payloadEncoder,
|
||||||
theirPayload, ourPayload, ourKeyPair, true);
|
transport, theirPayload, ourPayload, ourKeyPair, true);
|
||||||
|
|
||||||
// expectations
|
// expectations
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// Helpers
|
// Helpers
|
||||||
allowing(payloadEncoder).encode(ourPayload);
|
allowing(payloadEncoder).encode(ourPayload);
|
||||||
will(returnValue(alicePayload));
|
will(returnValue(ALICE_PAYLOAD));
|
||||||
allowing(payloadEncoder).encode(theirPayload);
|
allowing(payloadEncoder).encode(theirPayload);
|
||||||
will(returnValue(bobPayload));
|
will(returnValue(BOB_PAYLOAD));
|
||||||
allowing(ourPubKey).getEncoded();
|
allowing(ourPubKey).getEncoded();
|
||||||
will(returnValue(alicePubKeyBytes));
|
will(returnValue(ALICE_PUBKEY));
|
||||||
allowing(crypto).getAgreementKeyParser();
|
|
||||||
will(returnValue(keyParser));
|
|
||||||
allowing(bobPubKey).getEncoded();
|
|
||||||
will(returnValue(bobPubKeyBytes));
|
|
||||||
|
|
||||||
// Alice sends her public key
|
// Alice sends her public key
|
||||||
oneOf(transport).sendKey(alicePubKeyBytes);
|
oneOf(transport).sendKey(ALICE_PUBKEY);
|
||||||
|
|
||||||
// Alice receives Bob's public key
|
// Alice receives Bob's public key
|
||||||
oneOf(callbacks).connectionWaiting();
|
oneOf(callbacks).connectionWaiting();
|
||||||
oneOf(transport).receiveKey();
|
oneOf(transport).receiveKey();
|
||||||
will(returnValue(bobPubKeyBytes));
|
will(returnValue(BOB_PUBKEY));
|
||||||
oneOf(callbacks).initialRecordReceived();
|
oneOf(callbacks).initialRecordReceived();
|
||||||
oneOf(keyParser).parsePublicKey(bobPubKeyBytes);
|
|
||||||
will(returnValue(bobPubKey));
|
|
||||||
|
|
||||||
// Alice verifies Bob's public key
|
// Alice verifies Bob's public key
|
||||||
oneOf(keyAgreementCrypto).deriveKeyCommitment(bobPubKey);
|
oneOf(crypto).deriveKeyCommitment(BOB_PUBKEY);
|
||||||
will(returnValue(bobCommit));
|
will(returnValue(BOB_COMMIT));
|
||||||
|
|
||||||
// Alice computes shared secret
|
// Alice computes shared secret
|
||||||
oneOf(crypto).deriveSharedSecret(SHARED_SECRET_LABEL, bobPubKey,
|
oneOf(crypto).deriveSharedSecret(BOB_PUBKEY, ourKeyPair, true);
|
||||||
ourKeyPair, new byte[] {PROTOCOL_VERSION},
|
|
||||||
alicePubKeyBytes, bobPubKeyBytes);
|
|
||||||
will(returnValue(sharedSecret));
|
will(returnValue(sharedSecret));
|
||||||
|
|
||||||
// Alice sends her confirmation record
|
// Alice sends her confirmation record
|
||||||
oneOf(keyAgreementCrypto).deriveConfirmationRecord(sharedSecret,
|
oneOf(crypto).deriveConfirmationRecord(sharedSecret, BOB_PAYLOAD,
|
||||||
bobPayload, alicePayload, bobPubKey, ourKeyPair,
|
ALICE_PAYLOAD, BOB_PUBKEY, ourKeyPair, true, true);
|
||||||
true, true);
|
will(returnValue(ALICE_CONFIRM));
|
||||||
will(returnValue(aliceConfirm));
|
oneOf(transport).sendConfirm(ALICE_CONFIRM);
|
||||||
oneOf(transport).sendConfirm(aliceConfirm);
|
|
||||||
|
|
||||||
// Alice receives a bad confirmation record
|
// Alice receives a bad confirmation record
|
||||||
oneOf(transport).receiveConfirm();
|
oneOf(transport).receiveConfirm();
|
||||||
will(returnValue(badConfirm));
|
will(returnValue(BAD_CONFIRM));
|
||||||
|
|
||||||
// Alice verifies Bob's confirmation record
|
// Alice verifies Bob's confirmation record
|
||||||
oneOf(keyAgreementCrypto).deriveConfirmationRecord(sharedSecret,
|
oneOf(crypto).deriveConfirmationRecord(sharedSecret, BOB_PAYLOAD,
|
||||||
bobPayload, alicePayload, bobPubKey, ourKeyPair,
|
ALICE_PAYLOAD, BOB_PUBKEY, ourKeyPair, true, false);
|
||||||
true, false);
|
will(returnValue(BOB_CONFIRM));
|
||||||
will(returnValue(bobConfirm));
|
|
||||||
|
|
||||||
// Alice aborts
|
// Alice aborts
|
||||||
oneOf(transport).sendAbort(false);
|
oneOf(transport).sendAbort(false);
|
||||||
|
|
||||||
// Alice never computes master secret
|
// Alice never computes master secret
|
||||||
never(crypto).deriveKey(MASTER_SECRET_LABEL, sharedSecret);
|
never(crypto).deriveMasterSecret(sharedSecret);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
// execute
|
// execute
|
||||||
@@ -384,66 +335,56 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
|
|||||||
@Test(expected = AbortException.class)
|
@Test(expected = AbortException.class)
|
||||||
public void testBobProtocolAbortOnBadConfirm() throws Exception {
|
public void testBobProtocolAbortOnBadConfirm() throws Exception {
|
||||||
// set up
|
// set up
|
||||||
Payload theirPayload = new Payload(aliceCommit, null);
|
Payload theirPayload = new Payload(ALICE_COMMIT, null);
|
||||||
Payload ourPayload = new Payload(bobCommit, null);
|
Payload ourPayload = new Payload(BOB_COMMIT, null);
|
||||||
KeyPair ourKeyPair = new KeyPair(ourPubKey, null);
|
KeyPair ourKeyPair = new KeyPair(ourPubKey, null);
|
||||||
SecretKey sharedSecret = getSecretKey();
|
SecretKey sharedSecret = TestUtils.getSecretKey();
|
||||||
|
|
||||||
KeyAgreementProtocol protocol = new KeyAgreementProtocol(callbacks,
|
KeyAgreementProtocol protocol =
|
||||||
crypto, keyAgreementCrypto, payloadEncoder, transport,
|
new KeyAgreementProtocol(callbacks, crypto, payloadEncoder,
|
||||||
theirPayload, ourPayload, ourKeyPair, false);
|
transport, theirPayload, ourPayload, ourKeyPair, false);
|
||||||
|
|
||||||
// expectations
|
// expectations
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// Helpers
|
// Helpers
|
||||||
allowing(payloadEncoder).encode(ourPayload);
|
allowing(payloadEncoder).encode(ourPayload);
|
||||||
will(returnValue(bobPayload));
|
will(returnValue(BOB_PAYLOAD));
|
||||||
allowing(payloadEncoder).encode(theirPayload);
|
allowing(payloadEncoder).encode(theirPayload);
|
||||||
will(returnValue(alicePayload));
|
will(returnValue(ALICE_PAYLOAD));
|
||||||
allowing(ourPubKey).getEncoded();
|
allowing(ourPubKey).getEncoded();
|
||||||
will(returnValue(bobPubKeyBytes));
|
will(returnValue(BOB_PUBKEY));
|
||||||
allowing(crypto).getAgreementKeyParser();
|
|
||||||
will(returnValue(keyParser));
|
|
||||||
allowing(alicePubKey).getEncoded();
|
|
||||||
will(returnValue(alicePubKeyBytes));
|
|
||||||
|
|
||||||
// Bob receives Alice's public key
|
// Bob receives Alice's public key
|
||||||
oneOf(transport).receiveKey();
|
oneOf(transport).receiveKey();
|
||||||
will(returnValue(alicePubKeyBytes));
|
will(returnValue(ALICE_PUBKEY));
|
||||||
oneOf(callbacks).initialRecordReceived();
|
oneOf(callbacks).initialRecordReceived();
|
||||||
oneOf(keyParser).parsePublicKey(alicePubKeyBytes);
|
|
||||||
will(returnValue(alicePubKey));
|
|
||||||
|
|
||||||
// Bob verifies Alice's public key
|
// Bob verifies Alice's public key
|
||||||
oneOf(keyAgreementCrypto).deriveKeyCommitment(alicePubKey);
|
oneOf(crypto).deriveKeyCommitment(ALICE_PUBKEY);
|
||||||
will(returnValue(aliceCommit));
|
will(returnValue(ALICE_COMMIT));
|
||||||
|
|
||||||
// Bob sends his public key
|
// Bob sends his public key
|
||||||
oneOf(transport).sendKey(bobPubKeyBytes);
|
oneOf(transport).sendKey(BOB_PUBKEY);
|
||||||
|
|
||||||
// Bob computes shared secret
|
// Bob computes shared secret
|
||||||
oneOf(crypto).deriveSharedSecret(SHARED_SECRET_LABEL, alicePubKey,
|
oneOf(crypto).deriveSharedSecret(ALICE_PUBKEY, ourKeyPair, false);
|
||||||
ourKeyPair, new byte[] {PROTOCOL_VERSION},
|
|
||||||
alicePubKeyBytes, bobPubKeyBytes);
|
|
||||||
will(returnValue(sharedSecret));
|
will(returnValue(sharedSecret));
|
||||||
|
|
||||||
// Bob receives a bad confirmation record
|
// Bob receives a bad confirmation record
|
||||||
oneOf(transport).receiveConfirm();
|
oneOf(transport).receiveConfirm();
|
||||||
will(returnValue(badConfirm));
|
will(returnValue(BAD_CONFIRM));
|
||||||
|
|
||||||
// Bob verifies Alice's confirmation record
|
// Bob verifies Alice's confirmation record
|
||||||
oneOf(keyAgreementCrypto).deriveConfirmationRecord(sharedSecret,
|
oneOf(crypto).deriveConfirmationRecord(sharedSecret, ALICE_PAYLOAD,
|
||||||
alicePayload, bobPayload, alicePubKey, ourKeyPair,
|
BOB_PAYLOAD, ALICE_PUBKEY, ourKeyPair, false, true);
|
||||||
false, true);
|
will(returnValue(ALICE_CONFIRM));
|
||||||
will(returnValue(aliceConfirm));
|
|
||||||
|
|
||||||
// Bob aborts
|
// Bob aborts
|
||||||
oneOf(transport).sendAbort(false);
|
oneOf(transport).sendAbort(false);
|
||||||
|
|
||||||
// Bob never sends his confirmation record
|
// Bob never sends his confirmation record
|
||||||
never(keyAgreementCrypto).deriveConfirmationRecord(sharedSecret,
|
never(crypto).deriveConfirmationRecord(sharedSecret, ALICE_PAYLOAD,
|
||||||
alicePayload, bobPayload, alicePubKey, ourKeyPair,
|
BOB_PAYLOAD, ALICE_PUBKEY, ourKeyPair, false, false);
|
||||||
false, false);
|
|
||||||
}});
|
}});
|
||||||
|
|
||||||
// execute
|
// execute
|
||||||
|
|||||||
@@ -12,14 +12,14 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
|||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
|
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
|
||||||
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
|
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
|
||||||
|
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent;
|
||||||
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent;
|
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent;
|
||||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.briarproject.bramble.test.ImmediateExecutor;
|
import org.briarproject.bramble.test.ImmediateExecutor;
|
||||||
import org.briarproject.bramble.test.RunAction;
|
import org.briarproject.bramble.test.RunAction;
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
import org.jmock.Mockery;
|
|
||||||
import org.jmock.lib.legacy.ClassImposteriser;
|
import org.jmock.lib.legacy.ClassImposteriser;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
@@ -29,30 +29,37 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
|
||||||
public class PollerTest extends BrambleTestCase {
|
public class PollerTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
|
private final ScheduledExecutorService scheduler =
|
||||||
|
context.mock(ScheduledExecutorService.class);
|
||||||
|
private final ConnectionManager connectionManager =
|
||||||
|
context.mock(ConnectionManager.class);
|
||||||
|
private final ConnectionRegistry connectionRegistry =
|
||||||
|
context.mock(ConnectionRegistry.class);
|
||||||
|
private final PluginManager pluginManager =
|
||||||
|
context.mock(PluginManager.class);
|
||||||
|
private final Clock clock = context.mock(Clock.class);
|
||||||
|
private final ScheduledFuture future = context.mock(ScheduledFuture.class);
|
||||||
|
private final SecureRandom random;
|
||||||
|
|
||||||
|
private final Executor ioExecutor = new ImmediateExecutor();
|
||||||
|
private final TransportId transportId = new TransportId("id");
|
||||||
private final ContactId contactId = new ContactId(234);
|
private final ContactId contactId = new ContactId(234);
|
||||||
private final int pollingInterval = 60 * 1000;
|
private final int pollingInterval = 60 * 1000;
|
||||||
private final long now = System.currentTimeMillis();
|
private final long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
public PollerTest() {
|
||||||
|
context.setImposteriser(ClassImposteriser.INSTANCE);
|
||||||
|
random = context.mock(SecureRandom.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testConnectOnContactStatusChanged() throws Exception {
|
public void testConnectOnContactStatusChanged() throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
|
||||||
Executor ioExecutor = new ImmediateExecutor();
|
|
||||||
ScheduledExecutorService scheduler =
|
|
||||||
context.mock(ScheduledExecutorService.class);
|
|
||||||
ConnectionManager connectionManager =
|
|
||||||
context.mock(ConnectionManager.class);
|
|
||||||
ConnectionRegistry connectionRegistry =
|
|
||||||
context.mock(ConnectionRegistry.class);
|
|
||||||
PluginManager pluginManager = context.mock(PluginManager.class);
|
|
||||||
SecureRandom random = context.mock(SecureRandom.class);
|
|
||||||
Clock clock = context.mock(Clock.class);
|
|
||||||
|
|
||||||
// Two simplex plugins: one supports polling, the other doesn't
|
// Two simplex plugins: one supports polling, the other doesn't
|
||||||
SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class);
|
SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class);
|
||||||
SimplexPlugin simplexPlugin1 =
|
SimplexPlugin simplexPlugin1 =
|
||||||
@@ -120,28 +127,12 @@ public class PollerTest extends BrambleTestCase {
|
|||||||
connectionRegistry, pluginManager, random, clock);
|
connectionRegistry, pluginManager, random, clock);
|
||||||
|
|
||||||
p.eventOccurred(new ContactStatusChangedEvent(contactId, true));
|
p.eventOccurred(new ContactStatusChangedEvent(contactId, true));
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRescheduleAndReconnectOnConnectionClosed()
|
public void testRescheduleAndReconnectOnConnectionClosed()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Mockery context = new Mockery();
|
|
||||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
|
||||||
Executor ioExecutor = new ImmediateExecutor();
|
|
||||||
ScheduledExecutorService scheduler =
|
|
||||||
context.mock(ScheduledExecutorService.class);
|
|
||||||
ConnectionManager connectionManager =
|
|
||||||
context.mock(ConnectionManager.class);
|
|
||||||
ConnectionRegistry connectionRegistry =
|
|
||||||
context.mock(ConnectionRegistry.class);
|
|
||||||
PluginManager pluginManager = context.mock(PluginManager.class);
|
|
||||||
SecureRandom random = context.mock(SecureRandom.class);
|
|
||||||
Clock clock = context.mock(Clock.class);
|
|
||||||
|
|
||||||
DuplexPlugin plugin = context.mock(DuplexPlugin.class);
|
DuplexPlugin plugin = context.mock(DuplexPlugin.class);
|
||||||
TransportId transportId = new TransportId("id");
|
|
||||||
DuplexTransportConnection duplexConnection =
|
DuplexTransportConnection duplexConnection =
|
||||||
context.mock(DuplexTransportConnection.class);
|
context.mock(DuplexTransportConnection.class);
|
||||||
|
|
||||||
@@ -168,6 +159,7 @@ public class PollerTest extends BrambleTestCase {
|
|||||||
will(returnValue(now));
|
will(returnValue(now));
|
||||||
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
||||||
with((long) pollingInterval), with(MILLISECONDS));
|
with((long) pollingInterval), with(MILLISECONDS));
|
||||||
|
will(returnValue(future));
|
||||||
// connectToContact()
|
// connectToContact()
|
||||||
// Check whether the contact is already connected
|
// Check whether the contact is already connected
|
||||||
oneOf(connectionRegistry).isConnected(contactId, transportId);
|
oneOf(connectionRegistry).isConnected(contactId, transportId);
|
||||||
@@ -185,28 +177,12 @@ public class PollerTest extends BrambleTestCase {
|
|||||||
|
|
||||||
p.eventOccurred(new ConnectionClosedEvent(contactId, transportId,
|
p.eventOccurred(new ConnectionClosedEvent(contactId, transportId,
|
||||||
false));
|
false));
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRescheduleOnConnectionOpened() throws Exception {
|
public void testRescheduleOnConnectionOpened() throws Exception {
|
||||||
Mockery context = new Mockery();
|
Plugin plugin = context.mock(Plugin.class);
|
||||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
|
||||||
Executor ioExecutor = new ImmediateExecutor();
|
|
||||||
ScheduledExecutorService scheduler =
|
|
||||||
context.mock(ScheduledExecutorService.class);
|
|
||||||
ConnectionManager connectionManager =
|
|
||||||
context.mock(ConnectionManager.class);
|
|
||||||
ConnectionRegistry connectionRegistry =
|
|
||||||
context.mock(ConnectionRegistry.class);
|
|
||||||
PluginManager pluginManager = context.mock(PluginManager.class);
|
|
||||||
SecureRandom random = context.mock(SecureRandom.class);
|
|
||||||
Clock clock = context.mock(Clock.class);
|
|
||||||
|
|
||||||
DuplexPlugin plugin = context.mock(DuplexPlugin.class);
|
|
||||||
TransportId transportId = new TransportId("id");
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
allowing(plugin).getId();
|
allowing(plugin).getId();
|
||||||
@@ -224,6 +200,7 @@ public class PollerTest extends BrambleTestCase {
|
|||||||
will(returnValue(now));
|
will(returnValue(now));
|
||||||
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
||||||
with((long) pollingInterval), with(MILLISECONDS));
|
with((long) pollingInterval), with(MILLISECONDS));
|
||||||
|
will(returnValue(future));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
Poller p = new Poller(ioExecutor, scheduler, connectionManager,
|
Poller p = new Poller(ioExecutor, scheduler, connectionManager,
|
||||||
@@ -231,27 +208,11 @@ public class PollerTest extends BrambleTestCase {
|
|||||||
|
|
||||||
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
|
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
|
||||||
false));
|
false));
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRescheduleDoesNotReplaceEarlierTask() throws Exception {
|
public void testRescheduleDoesNotReplaceEarlierTask() throws Exception {
|
||||||
Mockery context = new Mockery();
|
Plugin plugin = context.mock(Plugin.class);
|
||||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
|
||||||
Executor ioExecutor = new ImmediateExecutor();
|
|
||||||
ScheduledExecutorService scheduler =
|
|
||||||
context.mock(ScheduledExecutorService.class);
|
|
||||||
ConnectionManager connectionManager =
|
|
||||||
context.mock(ConnectionManager.class);
|
|
||||||
ConnectionRegistry connectionRegistry =
|
|
||||||
context.mock(ConnectionRegistry.class);
|
|
||||||
PluginManager pluginManager = context.mock(PluginManager.class);
|
|
||||||
SecureRandom random = context.mock(SecureRandom.class);
|
|
||||||
Clock clock = context.mock(Clock.class);
|
|
||||||
|
|
||||||
DuplexPlugin plugin = context.mock(DuplexPlugin.class);
|
|
||||||
TransportId transportId = new TransportId("id");
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
allowing(plugin).getId();
|
allowing(plugin).getId();
|
||||||
@@ -270,6 +231,7 @@ public class PollerTest extends BrambleTestCase {
|
|||||||
will(returnValue(now));
|
will(returnValue(now));
|
||||||
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
||||||
with((long) pollingInterval), with(MILLISECONDS));
|
with((long) pollingInterval), with(MILLISECONDS));
|
||||||
|
will(returnValue(future));
|
||||||
// Second event
|
// Second event
|
||||||
// Get the plugin
|
// Get the plugin
|
||||||
oneOf(pluginManager).getPlugin(transportId);
|
oneOf(pluginManager).getPlugin(transportId);
|
||||||
@@ -291,27 +253,59 @@ public class PollerTest extends BrambleTestCase {
|
|||||||
false));
|
false));
|
||||||
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
|
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
|
||||||
false));
|
false));
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPollOnTransportEnabled() throws Exception {
|
public void testRescheduleReplacesLaterTask() throws Exception {
|
||||||
Mockery context = new Mockery();
|
Plugin plugin = context.mock(Plugin.class);
|
||||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
|
||||||
Executor ioExecutor = new ImmediateExecutor();
|
context.checking(new Expectations() {{
|
||||||
ScheduledExecutorService scheduler =
|
allowing(plugin).getId();
|
||||||
context.mock(ScheduledExecutorService.class);
|
will(returnValue(transportId));
|
||||||
ConnectionManager connectionManager =
|
// First event
|
||||||
context.mock(ConnectionManager.class);
|
// Get the plugin
|
||||||
ConnectionRegistry connectionRegistry =
|
oneOf(pluginManager).getPlugin(transportId);
|
||||||
context.mock(ConnectionRegistry.class);
|
will(returnValue(plugin));
|
||||||
PluginManager pluginManager = context.mock(PluginManager.class);
|
// The plugin supports polling
|
||||||
SecureRandom random = context.mock(SecureRandom.class);
|
oneOf(plugin).shouldPoll();
|
||||||
Clock clock = context.mock(Clock.class);
|
will(returnValue(true));
|
||||||
|
// Schedule the next poll
|
||||||
|
oneOf(plugin).getPollingInterval();
|
||||||
|
will(returnValue(pollingInterval));
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(now));
|
||||||
|
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
||||||
|
with((long) pollingInterval), with(MILLISECONDS));
|
||||||
|
will(returnValue(future));
|
||||||
|
// Second event
|
||||||
|
// Get the plugin
|
||||||
|
oneOf(pluginManager).getPlugin(transportId);
|
||||||
|
will(returnValue(plugin));
|
||||||
|
// The plugin supports polling
|
||||||
|
oneOf(plugin).shouldPoll();
|
||||||
|
will(returnValue(true));
|
||||||
|
// Replace the previously scheduled task, due later
|
||||||
|
oneOf(plugin).getPollingInterval();
|
||||||
|
will(returnValue(pollingInterval - 2));
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(now + 1));
|
||||||
|
oneOf(future).cancel(false);
|
||||||
|
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
||||||
|
with((long) pollingInterval - 2), with(MILLISECONDS));
|
||||||
|
}});
|
||||||
|
|
||||||
|
Poller p = new Poller(ioExecutor, scheduler, connectionManager,
|
||||||
|
connectionRegistry, pluginManager, random, clock);
|
||||||
|
|
||||||
|
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
|
||||||
|
false));
|
||||||
|
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
|
||||||
|
false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPollsOnTransportEnabled() throws Exception {
|
||||||
Plugin plugin = context.mock(Plugin.class);
|
Plugin plugin = context.mock(Plugin.class);
|
||||||
TransportId transportId = new TransportId("id");
|
|
||||||
List<ContactId> connected = Collections.singletonList(contactId);
|
List<ContactId> connected = Collections.singletonList(contactId);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
@@ -328,6 +322,7 @@ public class PollerTest extends BrambleTestCase {
|
|||||||
will(returnValue(now));
|
will(returnValue(now));
|
||||||
oneOf(scheduler).schedule(with(any(Runnable.class)), with(0L),
|
oneOf(scheduler).schedule(with(any(Runnable.class)), with(0L),
|
||||||
with(MILLISECONDS));
|
with(MILLISECONDS));
|
||||||
|
will(returnValue(future));
|
||||||
will(new RunAction());
|
will(new RunAction());
|
||||||
// Running the polling task schedules the next polling task
|
// Running the polling task schedules the next polling task
|
||||||
oneOf(plugin).getPollingInterval();
|
oneOf(plugin).getPollingInterval();
|
||||||
@@ -338,6 +333,7 @@ public class PollerTest extends BrambleTestCase {
|
|||||||
will(returnValue(now));
|
will(returnValue(now));
|
||||||
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
oneOf(scheduler).schedule(with(any(Runnable.class)),
|
||||||
with((long) (pollingInterval * 0.5)), with(MILLISECONDS));
|
with((long) (pollingInterval * 0.5)), with(MILLISECONDS));
|
||||||
|
will(returnValue(future));
|
||||||
// Poll the plugin
|
// Poll the plugin
|
||||||
oneOf(connectionRegistry).getConnectedContacts(transportId);
|
oneOf(connectionRegistry).getConnectedContacts(transportId);
|
||||||
will(returnValue(connected));
|
will(returnValue(connected));
|
||||||
@@ -348,7 +344,36 @@ public class PollerTest extends BrambleTestCase {
|
|||||||
connectionRegistry, pluginManager, random, clock);
|
connectionRegistry, pluginManager, random, clock);
|
||||||
|
|
||||||
p.eventOccurred(new TransportEnabledEvent(transportId));
|
p.eventOccurred(new TransportEnabledEvent(transportId));
|
||||||
|
}
|
||||||
|
|
||||||
context.assertIsSatisfied();
|
@Test
|
||||||
|
public void testCancelsPollingOnTransportDisabled() throws Exception {
|
||||||
|
Plugin plugin = context.mock(Plugin.class);
|
||||||
|
List<ContactId> connected = Collections.singletonList(contactId);
|
||||||
|
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
allowing(plugin).getId();
|
||||||
|
will(returnValue(transportId));
|
||||||
|
// Get the plugin
|
||||||
|
oneOf(pluginManager).getPlugin(transportId);
|
||||||
|
will(returnValue(plugin));
|
||||||
|
// The plugin supports polling
|
||||||
|
oneOf(plugin).shouldPoll();
|
||||||
|
will(returnValue(true));
|
||||||
|
// Schedule a polling task immediately
|
||||||
|
oneOf(clock).currentTimeMillis();
|
||||||
|
will(returnValue(now));
|
||||||
|
oneOf(scheduler).schedule(with(any(Runnable.class)), with(0L),
|
||||||
|
with(MILLISECONDS));
|
||||||
|
will(returnValue(future));
|
||||||
|
// The plugin is disabled before the task runs - cancel the task
|
||||||
|
oneOf(future).cancel(false);
|
||||||
|
}});
|
||||||
|
|
||||||
|
Poller p = new Poller(ioExecutor, scheduler, connectionManager,
|
||||||
|
connectionRegistry, pluginManager, random, clock);
|
||||||
|
|
||||||
|
p.eventOccurred(new TransportEnabledEvent(transportId));
|
||||||
|
p.eventOccurred(new TransportDisabledEvent(transportId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
|||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
import org.briarproject.bramble.api.settings.Settings;
|
import org.briarproject.bramble.api.settings.Settings;
|
||||||
|
import org.briarproject.bramble.plugin.tcp.LanTcpPlugin.LanAddressComparator;
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ import java.net.NetworkInterface;
|
|||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.Hashtable;
|
import java.util.Hashtable;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
@@ -56,6 +58,9 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
|||||||
// Local and remote in 192.168.0.0/16 should return true
|
// Local and remote in 192.168.0.0/16 should return true
|
||||||
assertTrue(plugin.addressesAreOnSameLan(makeAddress(192, 168, 0, 0),
|
assertTrue(plugin.addressesAreOnSameLan(makeAddress(192, 168, 0, 0),
|
||||||
makeAddress(192, 168, 255, 255)));
|
makeAddress(192, 168, 255, 255)));
|
||||||
|
// Local and remote in 169.254.0.0/16 (link-local) should return true
|
||||||
|
assertTrue(plugin.addressesAreOnSameLan(makeAddress(169, 254, 0, 0),
|
||||||
|
makeAddress(169, 254, 255, 255)));
|
||||||
// Local and remote in different recognised prefixes should return false
|
// Local and remote in different recognised prefixes should return false
|
||||||
assertFalse(plugin.addressesAreOnSameLan(makeAddress(10, 0, 0, 0),
|
assertFalse(plugin.addressesAreOnSameLan(makeAddress(10, 0, 0, 0),
|
||||||
makeAddress(172, 31, 255, 255)));
|
makeAddress(172, 31, 255, 255)));
|
||||||
@@ -150,14 +155,17 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
|||||||
int port = ss.getLocalPort();
|
int port = ss.getLocalPort();
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
AtomicBoolean error = new AtomicBoolean(false);
|
AtomicBoolean error = new AtomicBoolean(false);
|
||||||
new Thread(() -> {
|
new Thread() {
|
||||||
try {
|
@Override
|
||||||
ss.accept();
|
public void run() {
|
||||||
latch.countDown();
|
try {
|
||||||
} catch (IOException e) {
|
ss.accept();
|
||||||
error.set(true);
|
latch.countDown();
|
||||||
|
} catch (IOException e) {
|
||||||
|
error.set(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}).start();
|
}.start();
|
||||||
// Tell the plugin about the port
|
// Tell the plugin about the port
|
||||||
TransportProperties p = new TransportProperties();
|
TransportProperties p = new TransportProperties();
|
||||||
p.put("ipPorts", addrString + ":" + port);
|
p.put("ipPorts", addrString + ":" + port);
|
||||||
@@ -240,14 +248,17 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
|||||||
ss.bind(new InetSocketAddress(addrString, 0), 10);
|
ss.bind(new InetSocketAddress(addrString, 0), 10);
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
AtomicBoolean error = new AtomicBoolean(false);
|
AtomicBoolean error = new AtomicBoolean(false);
|
||||||
new Thread(() -> {
|
new Thread() {
|
||||||
try {
|
@Override
|
||||||
ss.accept();
|
public void run() {
|
||||||
latch.countDown();
|
try {
|
||||||
} catch (IOException e) {
|
ss.accept();
|
||||||
error.set(true);
|
latch.countDown();
|
||||||
|
} catch (IOException e) {
|
||||||
|
error.set(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}).start();
|
}.start();
|
||||||
// Tell the plugin about the port
|
// Tell the plugin about the port
|
||||||
BdfList descriptor = new BdfList();
|
BdfList descriptor = new BdfList();
|
||||||
descriptor.add(TRANSPORT_ID_LAN);
|
descriptor.add(TRANSPORT_ID_LAN);
|
||||||
@@ -269,6 +280,57 @@ public class LanTcpPluginTest extends BrambleTestCase {
|
|||||||
plugin.stop();
|
plugin.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testComparatorPrefersNonZeroPorts() throws Exception {
|
||||||
|
Comparator<InetSocketAddress> comparator = new LanAddressComparator();
|
||||||
|
InetSocketAddress nonZero = new InetSocketAddress("1.2.3.4", 1234);
|
||||||
|
InetSocketAddress zero = new InetSocketAddress("1.2.3.4", 0);
|
||||||
|
|
||||||
|
assertEquals(0, comparator.compare(nonZero, nonZero));
|
||||||
|
assertTrue(comparator.compare(nonZero, zero) < 0);
|
||||||
|
|
||||||
|
assertTrue(comparator.compare(zero, nonZero) > 0);
|
||||||
|
assertEquals(0, comparator.compare(zero, zero));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testComparatorPrefersLongerPrefixes() throws Exception {
|
||||||
|
Comparator<InetSocketAddress> comparator = new LanAddressComparator();
|
||||||
|
InetSocketAddress prefix192 = new InetSocketAddress("192.168.0.1", 0);
|
||||||
|
InetSocketAddress prefix172 = new InetSocketAddress("172.16.0.1", 0);
|
||||||
|
InetSocketAddress prefix10 = new InetSocketAddress("10.0.0.1", 0);
|
||||||
|
|
||||||
|
assertEquals(0, comparator.compare(prefix192, prefix192));
|
||||||
|
assertTrue(comparator.compare(prefix192, prefix172) < 0);
|
||||||
|
assertTrue(comparator.compare(prefix192, prefix10) < 0);
|
||||||
|
|
||||||
|
assertTrue(comparator.compare(prefix172, prefix192) > 0);
|
||||||
|
assertEquals(0, comparator.compare(prefix172, prefix172));
|
||||||
|
assertTrue(comparator.compare(prefix172, prefix10) < 0);
|
||||||
|
|
||||||
|
assertTrue(comparator.compare(prefix10, prefix192) > 0);
|
||||||
|
assertTrue(comparator.compare(prefix10, prefix172) > 0);
|
||||||
|
assertEquals(0, comparator.compare(prefix10, prefix10));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testComparatorPrefersSiteLocalToLinkLocal() throws Exception {
|
||||||
|
Comparator<InetSocketAddress> comparator = new LanAddressComparator();
|
||||||
|
InetSocketAddress prefix192 = new InetSocketAddress("192.168.0.1", 0);
|
||||||
|
InetSocketAddress prefix172 = new InetSocketAddress("172.16.0.1", 0);
|
||||||
|
InetSocketAddress prefix10 = new InetSocketAddress("10.0.0.1", 0);
|
||||||
|
InetSocketAddress linkLocal = new InetSocketAddress("169.254.0.1", 0);
|
||||||
|
|
||||||
|
assertTrue(comparator.compare(prefix192, linkLocal) < 0);
|
||||||
|
assertTrue(comparator.compare(prefix172, linkLocal) < 0);
|
||||||
|
assertTrue(comparator.compare(prefix10, linkLocal) < 0);
|
||||||
|
|
||||||
|
assertTrue(comparator.compare(linkLocal, prefix192) > 0);
|
||||||
|
assertTrue(comparator.compare(linkLocal, prefix172) > 0);
|
||||||
|
assertTrue(comparator.compare(linkLocal, prefix10) > 0);
|
||||||
|
assertEquals(0, comparator.compare(linkLocal, linkLocal));
|
||||||
|
}
|
||||||
|
|
||||||
private boolean systemHasLocalIpv4Address() throws Exception {
|
private boolean systemHasLocalIpv4Address() throws Exception {
|
||||||
for (NetworkInterface i : Collections.list(
|
for (NetworkInterface i : Collections.list(
|
||||||
NetworkInterface.getNetworkInterfaces())) {
|
NetworkInterface.getNetworkInterfaces())) {
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ import java.util.Map;
|
|||||||
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.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||||
import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_ID;
|
import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_ID;
|
||||||
import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_VERSION;
|
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||||
@@ -79,8 +78,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
private TransportPropertyManagerImpl createInstance() {
|
private TransportPropertyManagerImpl createInstance() {
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID,
|
oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID);
|
||||||
CLIENT_VERSION);
|
|
||||||
will(returnValue(localGroup));
|
will(returnValue(localGroup));
|
||||||
}});
|
}});
|
||||||
return new TransportPropertyManagerImpl(db, clientHelper,
|
return new TransportPropertyManagerImpl(db, clientHelper,
|
||||||
@@ -96,18 +94,18 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
Group contactGroup1 = getGroup(), contactGroup2 = getGroup();
|
Group contactGroup1 = getGroup(), contactGroup2 = getGroup();
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(db).containsGroup(txn, localGroup.getId());
|
||||||
|
will(returnValue(false));
|
||||||
oneOf(db).addGroup(txn, localGroup);
|
oneOf(db).addGroup(txn, localGroup);
|
||||||
oneOf(db).getContacts(txn);
|
oneOf(db).getContacts(txn);
|
||||||
will(returnValue(contacts));
|
will(returnValue(contacts));
|
||||||
// The first contact's group has already been set up
|
// The first contact's group has already been set up
|
||||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact1);
|
||||||
CLIENT_VERSION, contact1);
|
|
||||||
will(returnValue(contactGroup1));
|
will(returnValue(contactGroup1));
|
||||||
oneOf(db).containsGroup(txn, contactGroup1.getId());
|
oneOf(db).containsGroup(txn, contactGroup1.getId());
|
||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
// The second contact's group hasn't been set up
|
// The second contact's group hasn't been set up
|
||||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact2);
|
||||||
CLIENT_VERSION, contact2);
|
|
||||||
will(returnValue(contactGroup2));
|
will(returnValue(contactGroup2));
|
||||||
oneOf(db).containsGroup(txn, contactGroup2.getId());
|
oneOf(db).containsGroup(txn, contactGroup2.getId());
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
@@ -127,15 +125,28 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreatesGroupWhenAddingContact() throws Exception {
|
public void testDoesNotCreateGroupsAtStartupIfAlreadyCreated()
|
||||||
|
throws Exception {
|
||||||
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(db).containsGroup(txn, localGroup.getId());
|
||||||
|
will(returnValue(true));
|
||||||
|
}});
|
||||||
|
|
||||||
|
TransportPropertyManagerImpl t = createInstance();
|
||||||
|
t.createLocalState(txn);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreatesContactGroupWhenAddingContact() throws Exception {
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
Contact contact = getContact(true);
|
Contact contact = getContact(true);
|
||||||
Group contactGroup = getGroup();
|
Group contactGroup = getGroup();
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// Create the group and share it with the contact
|
// Create the group and share it with the contact
|
||||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
|
||||||
CLIENT_VERSION, contact);
|
|
||||||
will(returnValue(contactGroup));
|
will(returnValue(contactGroup));
|
||||||
oneOf(db).containsGroup(txn, contactGroup.getId());
|
oneOf(db).containsGroup(txn, contactGroup.getId());
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
@@ -161,8 +172,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
Group contactGroup = getGroup();
|
Group contactGroup = getGroup();
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
|
||||||
CLIENT_VERSION, contact);
|
|
||||||
will(returnValue(contactGroup));
|
will(returnValue(contactGroup));
|
||||||
oneOf(db).removeGroup(txn, contactGroup);
|
oneOf(db).removeGroup(txn, contactGroup);
|
||||||
}});
|
}});
|
||||||
@@ -221,7 +231,6 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
long timestamp = 123456789;
|
long timestamp = 123456789;
|
||||||
Message message = getMessage(contactGroupId, timestamp);
|
Message message = getMessage(contactGroupId, timestamp);
|
||||||
Metadata meta = new Metadata();
|
Metadata meta = new Metadata();
|
||||||
// Version 4 is being delivered
|
|
||||||
BdfDictionary metaDictionary = BdfDictionary.of(
|
BdfDictionary metaDictionary = BdfDictionary.of(
|
||||||
new BdfEntry("transportId", "foo"),
|
new BdfEntry("transportId", "foo"),
|
||||||
new BdfEntry("version", 4),
|
new BdfEntry("version", 4),
|
||||||
@@ -229,7 +238,19 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
);
|
);
|
||||||
Map<MessageId, BdfDictionary> messageMetadata =
|
Map<MessageId, BdfDictionary> messageMetadata =
|
||||||
new LinkedHashMap<>();
|
new LinkedHashMap<>();
|
||||||
// An older remote update for the same transport should be deleted
|
// Old remote updates for the same transport should be deleted
|
||||||
|
MessageId fooVersion2 = new MessageId(getRandomId());
|
||||||
|
messageMetadata.put(fooVersion2, BdfDictionary.of(
|
||||||
|
new BdfEntry("transportId", "foo"),
|
||||||
|
new BdfEntry("version", 2),
|
||||||
|
new BdfEntry("local", false)
|
||||||
|
));
|
||||||
|
MessageId fooVersion1 = new MessageId(getRandomId());
|
||||||
|
messageMetadata.put(fooVersion1, BdfDictionary.of(
|
||||||
|
new BdfEntry("transportId", "foo"),
|
||||||
|
new BdfEntry("version", 1),
|
||||||
|
new BdfEntry("local", false)
|
||||||
|
));
|
||||||
MessageId fooVersion3 = new MessageId(getRandomId());
|
MessageId fooVersion3 = new MessageId(getRandomId());
|
||||||
messageMetadata.put(fooVersion3, BdfDictionary.of(
|
messageMetadata.put(fooVersion3, BdfDictionary.of(
|
||||||
new BdfEntry("transportId", "foo"),
|
new BdfEntry("transportId", "foo"),
|
||||||
@@ -243,7 +264,11 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
contactGroupId);
|
contactGroupId);
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
// The previous update (version 3) should be deleted
|
// Versions 1-3 should be deleted
|
||||||
|
oneOf(db).deleteMessage(txn, fooVersion1);
|
||||||
|
oneOf(db).deleteMessageMetadata(txn, fooVersion1);
|
||||||
|
oneOf(db).deleteMessage(txn, fooVersion2);
|
||||||
|
oneOf(db).deleteMessageMetadata(txn, fooVersion2);
|
||||||
oneOf(db).deleteMessage(txn, fooVersion3);
|
oneOf(db).deleteMessage(txn, fooVersion3);
|
||||||
oneOf(db).deleteMessageMetadata(txn, fooVersion3);
|
oneOf(db).deleteMessageMetadata(txn, fooVersion3);
|
||||||
}});
|
}});
|
||||||
@@ -259,7 +284,6 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
long timestamp = 123456789;
|
long timestamp = 123456789;
|
||||||
Message message = getMessage(contactGroupId, timestamp);
|
Message message = getMessage(contactGroupId, timestamp);
|
||||||
Metadata meta = new Metadata();
|
Metadata meta = new Metadata();
|
||||||
// Version 3 is being delivered
|
|
||||||
BdfDictionary metaDictionary = BdfDictionary.of(
|
BdfDictionary metaDictionary = BdfDictionary.of(
|
||||||
new BdfEntry("transportId", "foo"),
|
new BdfEntry("transportId", "foo"),
|
||||||
new BdfEntry("version", 3),
|
new BdfEntry("version", 3),
|
||||||
@@ -267,6 +291,19 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
);
|
);
|
||||||
Map<MessageId, BdfDictionary> messageMetadata =
|
Map<MessageId, BdfDictionary> messageMetadata =
|
||||||
new LinkedHashMap<>();
|
new LinkedHashMap<>();
|
||||||
|
// Old remote updates for the same transport should be deleted
|
||||||
|
MessageId fooVersion2 = new MessageId(getRandomId());
|
||||||
|
messageMetadata.put(fooVersion2, BdfDictionary.of(
|
||||||
|
new BdfEntry("transportId", "foo"),
|
||||||
|
new BdfEntry("version", 2),
|
||||||
|
new BdfEntry("local", false)
|
||||||
|
));
|
||||||
|
MessageId fooVersion1 = new MessageId(getRandomId());
|
||||||
|
messageMetadata.put(fooVersion1, BdfDictionary.of(
|
||||||
|
new BdfEntry("transportId", "foo"),
|
||||||
|
new BdfEntry("version", 1),
|
||||||
|
new BdfEntry("local", false)
|
||||||
|
));
|
||||||
// A newer remote update for the same transport should not be deleted
|
// A newer remote update for the same transport should not be deleted
|
||||||
MessageId fooVersion4 = new MessageId(getRandomId());
|
MessageId fooVersion4 = new MessageId(getRandomId());
|
||||||
messageMetadata.put(fooVersion4, BdfDictionary.of(
|
messageMetadata.put(fooVersion4, BdfDictionary.of(
|
||||||
@@ -281,6 +318,11 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
contactGroupId);
|
contactGroupId);
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
|
// Versions 1 and 2 should be deleted, version 4 should not
|
||||||
|
oneOf(db).deleteMessage(txn, fooVersion1);
|
||||||
|
oneOf(db).deleteMessageMetadata(txn, fooVersion1);
|
||||||
|
oneOf(db).deleteMessage(txn, fooVersion2);
|
||||||
|
oneOf(db).deleteMessageMetadata(txn, fooVersion2);
|
||||||
// The update being delivered (version 3) should be deleted
|
// The update being delivered (version 3) should be deleted
|
||||||
oneOf(db).deleteMessage(txn, message.getId());
|
oneOf(db).deleteMessage(txn, message.getId());
|
||||||
oneOf(db).deleteMessageMetadata(txn, message.getId());
|
oneOf(db).deleteMessageMetadata(txn, message.getId());
|
||||||
@@ -303,8 +345,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(db).getContact(txn, contact.getId());
|
oneOf(db).getContact(txn, contact.getId());
|
||||||
will(returnValue(contact));
|
will(returnValue(contact));
|
||||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
|
||||||
CLIENT_VERSION, contact);
|
|
||||||
will(returnValue(contactGroup));
|
will(returnValue(contactGroup));
|
||||||
}});
|
}});
|
||||||
expectStoreMessage(txn, contactGroup.getId(), "foo", fooPropertiesDict,
|
expectStoreMessage(txn, contactGroup.getId(), "foo", fooPropertiesDict,
|
||||||
@@ -318,7 +359,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReturnsLatestLocalProperties() throws Exception {
|
public void testReturnsLatestLocalProperties() throws Exception {
|
||||||
Transaction txn = new Transaction(null, true);
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
expectGetLocalProperties(txn);
|
expectGetLocalProperties(txn);
|
||||||
|
|
||||||
@@ -332,7 +373,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testReturnsEmptyPropertiesIfNoLocalPropertiesAreFound()
|
public void testReturnsEmptyPropertiesIfNoLocalPropertiesAreFound()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Transaction txn = new Transaction(null, true);
|
Transaction txn = new Transaction(null, false);
|
||||||
Map<MessageId, BdfDictionary> messageMetadata =
|
Map<MessageId, BdfDictionary> messageMetadata =
|
||||||
new LinkedHashMap<>();
|
new LinkedHashMap<>();
|
||||||
// A local update for another transport should be ignored
|
// A local update for another transport should be ignored
|
||||||
@@ -344,7 +385,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
));
|
));
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(db).startTransaction(true);
|
oneOf(db).startTransaction(false);
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
localGroup.getId());
|
localGroup.getId());
|
||||||
@@ -359,7 +400,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReturnsLocalProperties() throws Exception {
|
public void testReturnsLocalProperties() throws Exception {
|
||||||
Transaction txn = new Transaction(null, true);
|
Transaction txn = new Transaction(null, false);
|
||||||
Map<MessageId, BdfDictionary> messageMetadata =
|
Map<MessageId, BdfDictionary> messageMetadata =
|
||||||
new LinkedHashMap<>();
|
new LinkedHashMap<>();
|
||||||
// A local update for another transport should be ignored
|
// A local update for another transport should be ignored
|
||||||
@@ -379,7 +420,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
BdfList fooUpdate = BdfList.of("foo", 1, fooPropertiesDict);
|
BdfList fooUpdate = BdfList.of("foo", 1, fooPropertiesDict);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(db).startTransaction(true);
|
oneOf(db).startTransaction(false);
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
localGroup.getId());
|
localGroup.getId());
|
||||||
@@ -398,7 +439,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testReturnsRemotePropertiesOrEmptyProperties()
|
public void testReturnsRemotePropertiesOrEmptyProperties()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
Transaction txn = new Transaction(null, true);
|
Transaction txn = new Transaction(null, false);
|
||||||
Contact contact1 = getContact(false);
|
Contact contact1 = getContact(false);
|
||||||
Contact contact2 = getContact(true);
|
Contact contact2 = getContact(true);
|
||||||
Contact contact3 = getContact(true);
|
Contact contact3 = getContact(true);
|
||||||
@@ -432,21 +473,19 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
BdfList fooUpdate = BdfList.of("foo", 1, fooPropertiesDict);
|
BdfList fooUpdate = BdfList.of("foo", 1, fooPropertiesDict);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(db).startTransaction(true);
|
oneOf(db).startTransaction(false);
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
oneOf(db).getContacts(txn);
|
oneOf(db).getContacts(txn);
|
||||||
will(returnValue(contacts));
|
will(returnValue(contacts));
|
||||||
// First contact: skipped because not active
|
// First contact: skipped because not active
|
||||||
// Second contact: no updates
|
// Second contact: no updates
|
||||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact2);
|
||||||
CLIENT_VERSION, contact2);
|
|
||||||
will(returnValue(contactGroup2));
|
will(returnValue(contactGroup2));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
contactGroup2.getId());
|
contactGroup2.getId());
|
||||||
will(returnValue(Collections.emptyMap()));
|
will(returnValue(Collections.emptyMap()));
|
||||||
// Third contact: returns an update
|
// Third contact: returns an update
|
||||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact3);
|
||||||
CLIENT_VERSION, contact3);
|
|
||||||
will(returnValue(contactGroup3));
|
will(returnValue(contactGroup3));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
contactGroup3.getId());
|
contactGroup3.getId());
|
||||||
@@ -516,8 +555,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
// Store the new properties in each contact's group, version 1
|
// Store the new properties in each contact's group, version 1
|
||||||
oneOf(db).getContacts(txn);
|
oneOf(db).getContacts(txn);
|
||||||
will(returnValue(Collections.singletonList(contact)));
|
will(returnValue(Collections.singletonList(contact)));
|
||||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
|
||||||
CLIENT_VERSION, contact);
|
|
||||||
will(returnValue(contactGroup));
|
will(returnValue(contactGroup));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
contactGroup.getId());
|
contactGroup.getId());
|
||||||
@@ -569,8 +607,7 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
// Store the merged properties in each contact's group, version 2
|
// Store the merged properties in each contact's group, version 2
|
||||||
oneOf(db).getContacts(txn);
|
oneOf(db).getContacts(txn);
|
||||||
will(returnValue(Collections.singletonList(contact)));
|
will(returnValue(Collections.singletonList(contact)));
|
||||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
|
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
|
||||||
CLIENT_VERSION, contact);
|
|
||||||
will(returnValue(contactGroup));
|
will(returnValue(contactGroup));
|
||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
contactGroup.getId());
|
contactGroup.getId());
|
||||||
@@ -617,14 +654,28 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
return new Message(messageId, g, timestamp, raw);
|
return new Message(messageId, g, timestamp, raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void expectGetLocalProperties(Transaction txn) throws Exception {
|
private void expectGetLocalProperties(Transaction txn)
|
||||||
Map<MessageId, BdfDictionary> messageMetadata = new LinkedHashMap<>();
|
throws Exception {
|
||||||
// The latest update for transport "foo" should be returned
|
Map<MessageId, BdfDictionary> messageMetadata =
|
||||||
|
new LinkedHashMap<>();
|
||||||
|
// The only update for transport "foo" should be returned
|
||||||
MessageId fooVersion999 = new MessageId(getRandomId());
|
MessageId fooVersion999 = new MessageId(getRandomId());
|
||||||
messageMetadata.put(fooVersion999, BdfDictionary.of(
|
messageMetadata.put(fooVersion999, BdfDictionary.of(
|
||||||
new BdfEntry("transportId", "foo"),
|
new BdfEntry("transportId", "foo"),
|
||||||
new BdfEntry("version", 999)
|
new BdfEntry("version", 999)
|
||||||
));
|
));
|
||||||
|
// An old update for transport "bar" should be deleted
|
||||||
|
MessageId barVersion2 = new MessageId(getRandomId());
|
||||||
|
messageMetadata.put(barVersion2, BdfDictionary.of(
|
||||||
|
new BdfEntry("transportId", "bar"),
|
||||||
|
new BdfEntry("version", 2)
|
||||||
|
));
|
||||||
|
// An even older update for transport "bar" should be deleted
|
||||||
|
MessageId barVersion1 = new MessageId(getRandomId());
|
||||||
|
messageMetadata.put(barVersion1, BdfDictionary.of(
|
||||||
|
new BdfEntry("transportId", "bar"),
|
||||||
|
new BdfEntry("version", 1)
|
||||||
|
));
|
||||||
// The latest update for transport "bar" should be returned
|
// The latest update for transport "bar" should be returned
|
||||||
MessageId barVersion3 = new MessageId(getRandomId());
|
MessageId barVersion3 = new MessageId(getRandomId());
|
||||||
messageMetadata.put(barVersion3, BdfDictionary.of(
|
messageMetadata.put(barVersion3, BdfDictionary.of(
|
||||||
@@ -639,6 +690,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||||
localGroup.getId());
|
localGroup.getId());
|
||||||
will(returnValue(messageMetadata));
|
will(returnValue(messageMetadata));
|
||||||
|
oneOf(db).removeMessage(txn, barVersion1);
|
||||||
|
oneOf(db).removeMessage(txn, barVersion2);
|
||||||
// Retrieve and parse the latest local properties
|
// Retrieve and parse the latest local properties
|
||||||
oneOf(clientHelper).getMessageAsList(txn, fooVersion999);
|
oneOf(clientHelper).getMessageAsList(txn, fooVersion999);
|
||||||
will(returnValue(fooUpdate));
|
will(returnValue(fooUpdate));
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package org.briarproject.bramble.sync;
|
package org.briarproject.bramble.sync;
|
||||||
|
|
||||||
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.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.sync.Ack;
|
import org.briarproject.bramble.api.sync.Ack;
|
||||||
import org.briarproject.bramble.api.sync.ClientId;
|
import org.briarproject.bramble.api.sync.ClientId;
|
||||||
@@ -22,6 +22,7 @@ import org.briarproject.bramble.api.transport.StreamReaderFactory;
|
|||||||
import org.briarproject.bramble.api.transport.StreamWriterFactory;
|
import org.briarproject.bramble.api.transport.StreamWriterFactory;
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
import org.briarproject.bramble.test.TestUtils;
|
import org.briarproject.bramble.test.TestUtils;
|
||||||
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
@@ -36,7 +37,6 @@ import javax.inject.Inject;
|
|||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
|
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
|
||||||
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.util.StringUtils.getRandomString;
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
@@ -57,7 +57,7 @@ public class SyncIntegrationTest extends BrambleTestCase {
|
|||||||
@Inject
|
@Inject
|
||||||
RecordWriterFactory recordWriterFactory;
|
RecordWriterFactory recordWriterFactory;
|
||||||
@Inject
|
@Inject
|
||||||
TransportCrypto transportCrypto;
|
CryptoComponent crypto;
|
||||||
|
|
||||||
private final ContactId contactId;
|
private final ContactId contactId;
|
||||||
private final TransportId transportId;
|
private final TransportId transportId;
|
||||||
@@ -79,11 +79,9 @@ public class SyncIntegrationTest extends BrambleTestCase {
|
|||||||
headerKey = TestUtils.getSecretKey();
|
headerKey = TestUtils.getSecretKey();
|
||||||
streamNumber = 123;
|
streamNumber = 123;
|
||||||
// Create a group
|
// Create a group
|
||||||
ClientId clientId = new ClientId(getRandomString(123));
|
ClientId clientId = new ClientId(StringUtils.getRandomString(5));
|
||||||
int clientVersion = 1234567890;
|
|
||||||
byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
|
byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
|
||||||
Group group = groupFactory.createGroup(clientId, clientVersion,
|
Group group = groupFactory.createGroup(clientId, descriptor);
|
||||||
descriptor);
|
|
||||||
// Add two messages to the group
|
// Add two messages to the group
|
||||||
long timestamp = System.currentTimeMillis();
|
long timestamp = System.currentTimeMillis();
|
||||||
byte[] body = "Hello world".getBytes("UTF-8");
|
byte[] body = "Hello world".getBytes("UTF-8");
|
||||||
@@ -119,8 +117,7 @@ public class SyncIntegrationTest extends BrambleTestCase {
|
|||||||
private void read(byte[] connectionData) throws Exception {
|
private void read(byte[] connectionData) throws Exception {
|
||||||
// Calculate the expected tag
|
// Calculate the expected tag
|
||||||
byte[] expectedTag = new byte[TAG_LENGTH];
|
byte[] expectedTag = new byte[TAG_LENGTH];
|
||||||
transportCrypto.encodeTag(expectedTag, tagKey, PROTOCOL_VERSION,
|
crypto.encodeTag(expectedTag, tagKey, PROTOCOL_VERSION, streamNumber);
|
||||||
streamNumber);
|
|
||||||
|
|
||||||
// Read the tag
|
// Read the tag
|
||||||
InputStream in = new ByteArrayInputStream(connectionData);
|
InputStream in = new ByteArrayInputStream(connectionData);
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package org.briarproject.bramble.transport;
|
package org.briarproject.bramble.transport;
|
||||||
|
|
||||||
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.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
@@ -11,11 +11,12 @@ import org.briarproject.bramble.api.transport.IncomingKeys;
|
|||||||
import org.briarproject.bramble.api.transport.OutgoingKeys;
|
import org.briarproject.bramble.api.transport.OutgoingKeys;
|
||||||
import org.briarproject.bramble.api.transport.StreamContext;
|
import org.briarproject.bramble.api.transport.StreamContext;
|
||||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
import org.briarproject.bramble.test.RunAction;
|
import org.briarproject.bramble.test.RunAction;
|
||||||
import org.briarproject.bramble.test.TestUtils;
|
import org.briarproject.bramble.test.TestUtils;
|
||||||
import org.hamcrest.Description;
|
import org.hamcrest.Description;
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
|
import org.jmock.Mockery;
|
||||||
import org.jmock.api.Action;
|
import org.jmock.api.Action;
|
||||||
import org.jmock.api.Invocation;
|
import org.jmock.api.Invocation;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -40,15 +41,7 @@ import static org.junit.Assert.assertEquals;
|
|||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
||||||
public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
public class TransportKeyManagerImplTest extends BrambleTestCase {
|
||||||
|
|
||||||
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
|
||||||
private final TransportCrypto transportCrypto =
|
|
||||||
context.mock(TransportCrypto.class);
|
|
||||||
private final Executor dbExecutor = context.mock(Executor.class);
|
|
||||||
private final ScheduledExecutorService scheduler =
|
|
||||||
context.mock(ScheduledExecutorService.class);
|
|
||||||
private final Clock clock = context.mock(Clock.class);
|
|
||||||
|
|
||||||
private final TransportId transportId = new TransportId("id");
|
private final TransportId transportId = new TransportId("id");
|
||||||
private final long maxLatency = 30 * 1000; // 30 seconds
|
private final long maxLatency = 30 * 1000; // 30 seconds
|
||||||
@@ -62,6 +55,14 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testKeysAreRotatedAtStartup() throws Exception {
|
public void testKeysAreRotatedAtStartup() throws Exception {
|
||||||
|
Mockery context = new Mockery();
|
||||||
|
DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||||
|
CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||||
|
Executor dbExecutor = context.mock(Executor.class);
|
||||||
|
ScheduledExecutorService scheduler =
|
||||||
|
context.mock(ScheduledExecutorService.class);
|
||||||
|
Clock clock = context.mock(Clock.class);
|
||||||
|
|
||||||
Map<ContactId, TransportKeys> loaded = new LinkedHashMap<>();
|
Map<ContactId, TransportKeys> loaded = new LinkedHashMap<>();
|
||||||
TransportKeys shouldRotate = createTransportKeys(900, 0);
|
TransportKeys shouldRotate = createTransportKeys(900, 0);
|
||||||
TransportKeys shouldNotRotate = createTransportKeys(1000, 0);
|
TransportKeys shouldNotRotate = createTransportKeys(1000, 0);
|
||||||
@@ -78,15 +79,14 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(db).getTransportKeys(txn, transportId);
|
oneOf(db).getTransportKeys(txn, transportId);
|
||||||
will(returnValue(loaded));
|
will(returnValue(loaded));
|
||||||
// Rotate the transport keys
|
// Rotate the transport keys
|
||||||
oneOf(transportCrypto).rotateTransportKeys(shouldRotate, 1000);
|
oneOf(crypto).rotateTransportKeys(shouldRotate, 1000);
|
||||||
will(returnValue(rotated));
|
will(returnValue(rotated));
|
||||||
oneOf(transportCrypto).rotateTransportKeys(shouldNotRotate, 1000);
|
oneOf(crypto).rotateTransportKeys(shouldNotRotate, 1000);
|
||||||
will(returnValue(shouldNotRotate));
|
will(returnValue(shouldNotRotate));
|
||||||
// Encode the tags (3 sets per contact)
|
// Encode the tags (3 sets per contact)
|
||||||
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||||
exactly(6).of(transportCrypto).encodeTag(
|
exactly(6).of(crypto).encodeTag(with(any(byte[].class)),
|
||||||
with(any(byte[].class)), with(tagKey),
|
with(tagKey), with(PROTOCOL_VERSION), with(i));
|
||||||
with(PROTOCOL_VERSION), with(i));
|
|
||||||
will(new EncodeTagAction());
|
will(new EncodeTagAction());
|
||||||
}
|
}
|
||||||
// Save the keys that were rotated
|
// Save the keys that were rotated
|
||||||
@@ -97,124 +97,161 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
with(rotationPeriodLength - 1), with(MILLISECONDS));
|
with(rotationPeriodLength - 1), with(MILLISECONDS));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
|
TransportKeyManager
|
||||||
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
transportKeyManager = new TransportKeyManagerImpl(db,
|
||||||
maxLatency);
|
crypto, dbExecutor, scheduler, clock, transportId, maxLatency);
|
||||||
transportKeyManager.start(txn);
|
transportKeyManager.start(txn);
|
||||||
|
|
||||||
|
context.assertIsSatisfied();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testKeysAreRotatedWhenAddingContact() throws Exception {
|
public void testKeysAreRotatedWhenAddingContact() throws Exception {
|
||||||
boolean alice = random.nextBoolean();
|
Mockery context = new Mockery();
|
||||||
|
DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||||
|
CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||||
|
Executor dbExecutor = context.mock(Executor.class);
|
||||||
|
ScheduledExecutorService scheduler =
|
||||||
|
context.mock(ScheduledExecutorService.class);
|
||||||
|
Clock clock = context.mock(Clock.class);
|
||||||
|
|
||||||
|
boolean alice = true;
|
||||||
TransportKeys transportKeys = createTransportKeys(999, 0);
|
TransportKeys transportKeys = createTransportKeys(999, 0);
|
||||||
TransportKeys rotated = createTransportKeys(1000, 0);
|
TransportKeys rotated = createTransportKeys(1000, 0);
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
|
oneOf(crypto).deriveTransportKeys(transportId, masterKey, 999,
|
||||||
999, alice);
|
alice);
|
||||||
will(returnValue(transportKeys));
|
will(returnValue(transportKeys));
|
||||||
// Get the current time (1 ms after start of rotation period 1000)
|
// Get the current time (1 ms after start of rotation period 1000)
|
||||||
oneOf(clock).currentTimeMillis();
|
oneOf(clock).currentTimeMillis();
|
||||||
will(returnValue(rotationPeriodLength * 1000 + 1));
|
will(returnValue(rotationPeriodLength * 1000 + 1));
|
||||||
// Rotate the transport keys
|
// Rotate the transport keys
|
||||||
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
|
oneOf(crypto).rotateTransportKeys(transportKeys, 1000);
|
||||||
will(returnValue(rotated));
|
will(returnValue(rotated));
|
||||||
// Encode the tags (3 sets)
|
// Encode the tags (3 sets)
|
||||||
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||||
exactly(3).of(transportCrypto).encodeTag(
|
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
|
||||||
with(any(byte[].class)), with(tagKey),
|
with(tagKey), with(PROTOCOL_VERSION), with(i));
|
||||||
with(PROTOCOL_VERSION), with(i));
|
|
||||||
will(new EncodeTagAction());
|
will(new EncodeTagAction());
|
||||||
}
|
}
|
||||||
// Save the keys
|
// Save the keys
|
||||||
oneOf(db).addTransportKeys(txn, contactId, rotated);
|
oneOf(db).addTransportKeys(txn, contactId, rotated);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
|
TransportKeyManager
|
||||||
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
transportKeyManager = new TransportKeyManagerImpl(db,
|
||||||
maxLatency);
|
crypto, dbExecutor, scheduler, clock, transportId, maxLatency);
|
||||||
// The timestamp is 1 ms before the start of rotation period 1000
|
// The timestamp is 1 ms before the start of rotation period 1000
|
||||||
long timestamp = rotationPeriodLength * 1000 - 1;
|
long timestamp = rotationPeriodLength * 1000 - 1;
|
||||||
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
|
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
|
||||||
alice);
|
alice);
|
||||||
|
|
||||||
|
context.assertIsSatisfied();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testOutgoingStreamContextIsNullIfContactIsNotFound()
|
public void testOutgoingStreamContextIsNullIfContactIsNotFound()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
Mockery context = new Mockery();
|
||||||
|
DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||||
|
CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||||
|
Executor dbExecutor = context.mock(Executor.class);
|
||||||
|
ScheduledExecutorService scheduler =
|
||||||
|
context.mock(ScheduledExecutorService.class);
|
||||||
|
Clock clock = context.mock(Clock.class);
|
||||||
|
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
|
TransportKeyManager
|
||||||
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
transportKeyManager = new TransportKeyManagerImpl(db,
|
||||||
maxLatency);
|
crypto, dbExecutor, scheduler, clock, transportId, maxLatency);
|
||||||
assertNull(transportKeyManager.getStreamContext(txn, contactId));
|
assertNull(transportKeyManager.getStreamContext(txn, contactId));
|
||||||
|
|
||||||
|
context.assertIsSatisfied();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testOutgoingStreamContextIsNullIfStreamCounterIsExhausted()
|
public void testOutgoingStreamContextIsNullIfStreamCounterIsExhausted()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
boolean alice = random.nextBoolean();
|
Mockery context = new Mockery();
|
||||||
|
DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||||
|
CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||||
|
Executor dbExecutor = context.mock(Executor.class);
|
||||||
|
ScheduledExecutorService scheduler =
|
||||||
|
context.mock(ScheduledExecutorService.class);
|
||||||
|
Clock clock = context.mock(Clock.class);
|
||||||
|
|
||||||
|
boolean alice = true;
|
||||||
// The stream counter has been exhausted
|
// The stream counter has been exhausted
|
||||||
TransportKeys transportKeys = createTransportKeys(1000,
|
TransportKeys transportKeys = createTransportKeys(1000,
|
||||||
MAX_32_BIT_UNSIGNED + 1);
|
MAX_32_BIT_UNSIGNED + 1);
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
|
oneOf(crypto).deriveTransportKeys(transportId, masterKey, 1000,
|
||||||
1000, alice);
|
alice);
|
||||||
will(returnValue(transportKeys));
|
will(returnValue(transportKeys));
|
||||||
// Get the current time (the start of rotation period 1000)
|
// Get the current time (the start of rotation period 1000)
|
||||||
oneOf(clock).currentTimeMillis();
|
oneOf(clock).currentTimeMillis();
|
||||||
will(returnValue(rotationPeriodLength * 1000));
|
will(returnValue(rotationPeriodLength * 1000));
|
||||||
// Encode the tags (3 sets)
|
// Encode the tags (3 sets)
|
||||||
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||||
exactly(3).of(transportCrypto).encodeTag(
|
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
|
||||||
with(any(byte[].class)), with(tagKey),
|
with(tagKey), with(PROTOCOL_VERSION), with(i));
|
||||||
with(PROTOCOL_VERSION), with(i));
|
|
||||||
will(new EncodeTagAction());
|
will(new EncodeTagAction());
|
||||||
}
|
}
|
||||||
// Rotate the transport keys (the keys are unaffected)
|
// Rotate the transport keys (the keys are unaffected)
|
||||||
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
|
oneOf(crypto).rotateTransportKeys(transportKeys, 1000);
|
||||||
will(returnValue(transportKeys));
|
will(returnValue(transportKeys));
|
||||||
// Save the keys
|
// Save the keys
|
||||||
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
|
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
|
TransportKeyManager
|
||||||
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
transportKeyManager = new TransportKeyManagerImpl(db,
|
||||||
maxLatency);
|
crypto, dbExecutor, scheduler, clock, transportId, maxLatency);
|
||||||
// The timestamp is at the start of rotation period 1000
|
// The timestamp is at the start of rotation period 1000
|
||||||
long timestamp = rotationPeriodLength * 1000;
|
long timestamp = rotationPeriodLength * 1000;
|
||||||
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
|
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
|
||||||
alice);
|
alice);
|
||||||
assertNull(transportKeyManager.getStreamContext(txn, contactId));
|
assertNull(transportKeyManager.getStreamContext(txn, contactId));
|
||||||
|
|
||||||
|
context.assertIsSatisfied();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testOutgoingStreamCounterIsIncremented() throws Exception {
|
public void testOutgoingStreamCounterIsIncremented() throws Exception {
|
||||||
boolean alice = random.nextBoolean();
|
Mockery context = new Mockery();
|
||||||
|
DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||||
|
CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||||
|
Executor dbExecutor = context.mock(Executor.class);
|
||||||
|
ScheduledExecutorService scheduler =
|
||||||
|
context.mock(ScheduledExecutorService.class);
|
||||||
|
Clock clock = context.mock(Clock.class);
|
||||||
|
|
||||||
|
boolean alice = true;
|
||||||
// The stream counter can be used one more time before being exhausted
|
// The stream counter can be used one more time before being exhausted
|
||||||
TransportKeys transportKeys = createTransportKeys(1000,
|
TransportKeys transportKeys = createTransportKeys(1000,
|
||||||
MAX_32_BIT_UNSIGNED);
|
MAX_32_BIT_UNSIGNED);
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
|
oneOf(crypto).deriveTransportKeys(transportId, masterKey, 1000,
|
||||||
1000, alice);
|
alice);
|
||||||
will(returnValue(transportKeys));
|
will(returnValue(transportKeys));
|
||||||
// Get the current time (the start of rotation period 1000)
|
// Get the current time (the start of rotation period 1000)
|
||||||
oneOf(clock).currentTimeMillis();
|
oneOf(clock).currentTimeMillis();
|
||||||
will(returnValue(rotationPeriodLength * 1000));
|
will(returnValue(rotationPeriodLength * 1000));
|
||||||
// Encode the tags (3 sets)
|
// Encode the tags (3 sets)
|
||||||
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||||
exactly(3).of(transportCrypto).encodeTag(
|
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
|
||||||
with(any(byte[].class)), with(tagKey),
|
with(tagKey), with(PROTOCOL_VERSION), with(i));
|
||||||
with(PROTOCOL_VERSION), with(i));
|
|
||||||
will(new EncodeTagAction());
|
will(new EncodeTagAction());
|
||||||
}
|
}
|
||||||
// Rotate the transport keys (the keys are unaffected)
|
// Rotate the transport keys (the keys are unaffected)
|
||||||
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
|
oneOf(crypto).rotateTransportKeys(transportKeys, 1000);
|
||||||
will(returnValue(transportKeys));
|
will(returnValue(transportKeys));
|
||||||
// Save the keys
|
// Save the keys
|
||||||
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
|
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
|
||||||
@@ -222,9 +259,9 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(db).incrementStreamCounter(txn, contactId, transportId, 1000);
|
oneOf(db).incrementStreamCounter(txn, contactId, transportId, 1000);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
|
TransportKeyManager
|
||||||
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
transportKeyManager = new TransportKeyManagerImpl(db,
|
||||||
maxLatency);
|
crypto, dbExecutor, scheduler, clock, transportId, maxLatency);
|
||||||
// The timestamp is at the start of rotation period 1000
|
// The timestamp is at the start of rotation period 1000
|
||||||
long timestamp = rotationPeriodLength * 1000;
|
long timestamp = rotationPeriodLength * 1000;
|
||||||
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
|
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
|
||||||
@@ -240,76 +277,94 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
assertEquals(MAX_32_BIT_UNSIGNED, ctx.getStreamNumber());
|
assertEquals(MAX_32_BIT_UNSIGNED, ctx.getStreamNumber());
|
||||||
// The second request should return null, the counter is exhausted
|
// The second request should return null, the counter is exhausted
|
||||||
assertNull(transportKeyManager.getStreamContext(txn, contactId));
|
assertNull(transportKeyManager.getStreamContext(txn, contactId));
|
||||||
|
|
||||||
|
context.assertIsSatisfied();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIncomingStreamContextIsNullIfTagIsNotFound()
|
public void testIncomingStreamContextIsNullIfTagIsNotFound()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
boolean alice = random.nextBoolean();
|
Mockery context = new Mockery();
|
||||||
|
DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||||
|
CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||||
|
Executor dbExecutor = context.mock(Executor.class);
|
||||||
|
ScheduledExecutorService scheduler =
|
||||||
|
context.mock(ScheduledExecutorService.class);
|
||||||
|
Clock clock = context.mock(Clock.class);
|
||||||
|
|
||||||
|
boolean alice = true;
|
||||||
TransportKeys transportKeys = createTransportKeys(1000, 0);
|
TransportKeys transportKeys = createTransportKeys(1000, 0);
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
|
oneOf(crypto).deriveTransportKeys(transportId, masterKey, 1000,
|
||||||
1000, alice);
|
alice);
|
||||||
will(returnValue(transportKeys));
|
will(returnValue(transportKeys));
|
||||||
// Get the current time (the start of rotation period 1000)
|
// Get the current time (the start of rotation period 1000)
|
||||||
oneOf(clock).currentTimeMillis();
|
oneOf(clock).currentTimeMillis();
|
||||||
will(returnValue(rotationPeriodLength * 1000));
|
will(returnValue(rotationPeriodLength * 1000));
|
||||||
// Encode the tags (3 sets)
|
// Encode the tags (3 sets)
|
||||||
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||||
exactly(3).of(transportCrypto).encodeTag(
|
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
|
||||||
with(any(byte[].class)), with(tagKey),
|
with(tagKey), with(PROTOCOL_VERSION), with(i));
|
||||||
with(PROTOCOL_VERSION), with(i));
|
|
||||||
will(new EncodeTagAction());
|
will(new EncodeTagAction());
|
||||||
}
|
}
|
||||||
// Rotate the transport keys (the keys are unaffected)
|
// Rotate the transport keys (the keys are unaffected)
|
||||||
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
|
oneOf(crypto).rotateTransportKeys(transportKeys, 1000);
|
||||||
will(returnValue(transportKeys));
|
will(returnValue(transportKeys));
|
||||||
// Save the keys
|
// Save the keys
|
||||||
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
|
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
|
TransportKeyManager
|
||||||
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
transportKeyManager = new TransportKeyManagerImpl(db,
|
||||||
maxLatency);
|
crypto, dbExecutor, scheduler, clock, transportId, maxLatency);
|
||||||
// The timestamp is at the start of rotation period 1000
|
// The timestamp is at the start of rotation period 1000
|
||||||
long timestamp = rotationPeriodLength * 1000;
|
long timestamp = rotationPeriodLength * 1000;
|
||||||
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
|
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
|
||||||
alice);
|
alice);
|
||||||
assertNull(transportKeyManager.getStreamContext(txn,
|
assertNull(transportKeyManager.getStreamContext(txn,
|
||||||
new byte[TAG_LENGTH]));
|
new byte[TAG_LENGTH]));
|
||||||
|
|
||||||
|
context.assertIsSatisfied();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTagIsNotRecognisedTwice() throws Exception {
|
public void testTagIsNotRecognisedTwice() throws Exception {
|
||||||
boolean alice = random.nextBoolean();
|
Mockery context = new Mockery();
|
||||||
|
DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||||
|
CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||||
|
Executor dbExecutor = context.mock(Executor.class);
|
||||||
|
ScheduledExecutorService scheduler =
|
||||||
|
context.mock(ScheduledExecutorService.class);
|
||||||
|
Clock clock = context.mock(Clock.class);
|
||||||
|
|
||||||
|
boolean alice = true;
|
||||||
TransportKeys transportKeys = createTransportKeys(1000, 0);
|
TransportKeys transportKeys = createTransportKeys(1000, 0);
|
||||||
// Keep a copy of the tags
|
// Keep a copy of the tags
|
||||||
List<byte[]> tags = new ArrayList<>();
|
List<byte[]> tags = new ArrayList<>();
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
|
oneOf(crypto).deriveTransportKeys(transportId, masterKey, 1000,
|
||||||
1000, alice);
|
alice);
|
||||||
will(returnValue(transportKeys));
|
will(returnValue(transportKeys));
|
||||||
// Get the current time (the start of rotation period 1000)
|
// Get the current time (the start of rotation period 1000)
|
||||||
oneOf(clock).currentTimeMillis();
|
oneOf(clock).currentTimeMillis();
|
||||||
will(returnValue(rotationPeriodLength * 1000));
|
will(returnValue(rotationPeriodLength * 1000));
|
||||||
// Encode the tags (3 sets)
|
// Encode the tags (3 sets)
|
||||||
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||||
exactly(3).of(transportCrypto).encodeTag(
|
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
|
||||||
with(any(byte[].class)), with(tagKey),
|
with(tagKey), with(PROTOCOL_VERSION), with(i));
|
||||||
with(PROTOCOL_VERSION), with(i));
|
|
||||||
will(new EncodeTagAction(tags));
|
will(new EncodeTagAction(tags));
|
||||||
}
|
}
|
||||||
// Rotate the transport keys (the keys are unaffected)
|
// Rotate the transport keys (the keys are unaffected)
|
||||||
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
|
oneOf(crypto).rotateTransportKeys(transportKeys, 1000);
|
||||||
will(returnValue(transportKeys));
|
will(returnValue(transportKeys));
|
||||||
// Save the keys
|
// Save the keys
|
||||||
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
|
oneOf(db).addTransportKeys(txn, contactId, transportKeys);
|
||||||
// Encode a new tag after sliding the window
|
// Encode a new tag after sliding the window
|
||||||
oneOf(transportCrypto).encodeTag(with(any(byte[].class)),
|
oneOf(crypto).encodeTag(with(any(byte[].class)),
|
||||||
with(tagKey), with(PROTOCOL_VERSION),
|
with(tagKey), with(PROTOCOL_VERSION),
|
||||||
with((long) REORDERING_WINDOW_SIZE));
|
with((long) REORDERING_WINDOW_SIZE));
|
||||||
will(new EncodeTagAction(tags));
|
will(new EncodeTagAction(tags));
|
||||||
@@ -318,9 +373,9 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
1, new byte[REORDERING_WINDOW_SIZE / 8]);
|
1, new byte[REORDERING_WINDOW_SIZE / 8]);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
|
TransportKeyManager
|
||||||
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
transportKeyManager = new TransportKeyManagerImpl(db,
|
||||||
maxLatency);
|
crypto, dbExecutor, scheduler, clock, transportId, maxLatency);
|
||||||
// The timestamp is at the start of rotation period 1000
|
// The timestamp is at the start of rotation period 1000
|
||||||
long timestamp = rotationPeriodLength * 1000;
|
long timestamp = rotationPeriodLength * 1000;
|
||||||
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
|
transportKeyManager.addContact(txn, contactId, masterKey, timestamp,
|
||||||
@@ -340,10 +395,20 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
assertEquals(REORDERING_WINDOW_SIZE * 3 + 1, tags.size());
|
assertEquals(REORDERING_WINDOW_SIZE * 3 + 1, tags.size());
|
||||||
// The second request should return null, the tag has already been used
|
// The second request should return null, the tag has already been used
|
||||||
assertNull(transportKeyManager.getStreamContext(txn, tag));
|
assertNull(transportKeyManager.getStreamContext(txn, tag));
|
||||||
|
|
||||||
|
context.assertIsSatisfied();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testKeysAreRotatedToCurrentPeriod() throws Exception {
|
public void testKeysAreRotatedToCurrentPeriod() throws Exception {
|
||||||
|
Mockery context = new Mockery();
|
||||||
|
DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||||
|
CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||||
|
Executor dbExecutor = context.mock(Executor.class);
|
||||||
|
ScheduledExecutorService scheduler =
|
||||||
|
context.mock(ScheduledExecutorService.class);
|
||||||
|
Clock clock = context.mock(Clock.class);
|
||||||
|
|
||||||
TransportKeys transportKeys = createTransportKeys(1000, 0);
|
TransportKeys transportKeys = createTransportKeys(1000, 0);
|
||||||
Map<ContactId, TransportKeys> loaded =
|
Map<ContactId, TransportKeys> loaded =
|
||||||
Collections.singletonMap(contactId, transportKeys);
|
Collections.singletonMap(contactId, transportKeys);
|
||||||
@@ -359,13 +424,12 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(db).getTransportKeys(txn, transportId);
|
oneOf(db).getTransportKeys(txn, transportId);
|
||||||
will(returnValue(loaded));
|
will(returnValue(loaded));
|
||||||
// Rotate the transport keys (the keys are unaffected)
|
// Rotate the transport keys (the keys are unaffected)
|
||||||
oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
|
oneOf(crypto).rotateTransportKeys(transportKeys, 1000);
|
||||||
will(returnValue(transportKeys));
|
will(returnValue(transportKeys));
|
||||||
// Encode the tags (3 sets)
|
// Encode the tags (3 sets)
|
||||||
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||||
exactly(3).of(transportCrypto).encodeTag(
|
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
|
||||||
with(any(byte[].class)), with(tagKey),
|
with(tagKey), with(PROTOCOL_VERSION), with(i));
|
||||||
with(PROTOCOL_VERSION), with(i));
|
|
||||||
will(new EncodeTagAction());
|
will(new EncodeTagAction());
|
||||||
}
|
}
|
||||||
// Schedule key rotation at the start of the next rotation period
|
// Schedule key rotation at the start of the next rotation period
|
||||||
@@ -381,14 +445,13 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(clock).currentTimeMillis();
|
oneOf(clock).currentTimeMillis();
|
||||||
will(returnValue(rotationPeriodLength * 1001));
|
will(returnValue(rotationPeriodLength * 1001));
|
||||||
// Rotate the transport keys
|
// Rotate the transport keys
|
||||||
oneOf(transportCrypto).rotateTransportKeys(
|
oneOf(crypto).rotateTransportKeys(with(any(TransportKeys.class)),
|
||||||
with(any(TransportKeys.class)), with(1001L));
|
with(1001L));
|
||||||
will(returnValue(rotated));
|
will(returnValue(rotated));
|
||||||
// Encode the tags (3 sets)
|
// Encode the tags (3 sets)
|
||||||
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
|
||||||
exactly(3).of(transportCrypto).encodeTag(
|
exactly(3).of(crypto).encodeTag(with(any(byte[].class)),
|
||||||
with(any(byte[].class)), with(tagKey),
|
with(tagKey), with(PROTOCOL_VERSION), with(i));
|
||||||
with(PROTOCOL_VERSION), with(i));
|
|
||||||
will(new EncodeTagAction());
|
will(new EncodeTagAction());
|
||||||
}
|
}
|
||||||
// Save the keys that were rotated
|
// Save the keys that were rotated
|
||||||
@@ -402,10 +465,12 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(db).endTransaction(txn1);
|
oneOf(db).endTransaction(txn1);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
|
TransportKeyManager
|
||||||
db, transportCrypto, dbExecutor, scheduler, clock, transportId,
|
transportKeyManager = new TransportKeyManagerImpl(db,
|
||||||
maxLatency);
|
crypto, dbExecutor, scheduler, clock, transportId, maxLatency);
|
||||||
transportKeyManager.start(txn);
|
transportKeyManager.start(txn);
|
||||||
|
|
||||||
|
context.assertIsSatisfied();
|
||||||
}
|
}
|
||||||
|
|
||||||
private TransportKeys createTransportKeys(long rotationPeriod,
|
private TransportKeys createTransportKeys(long rotationPeriod,
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ dependencyVerification {
|
|||||||
'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728',
|
'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728',
|
||||||
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||||
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
|
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
|
||||||
'net.i2p.crypto:eddsa:0.2.0:eddsa-0.2.0.jar:a7cb1b85c16e2f0730b9204106929a1d9aaae1df728adc7041a8b8b605692140',
|
|
||||||
'net.java.dev.jna:jna-platform:4.4.0:jna-platform-4.4.0.jar:e9dda9e884fc107eb6367710540789a12dfa8ad28be9326b22ca6e352e325499',
|
'net.java.dev.jna:jna-platform:4.4.0:jna-platform-4.4.0.jar:e9dda9e884fc107eb6367710540789a12dfa8ad28be9326b22ca6e352e325499',
|
||||||
'net.java.dev.jna:jna:4.4.0:jna-4.4.0.jar:c4dadeeecaa90c8847902082aee5eb107fcf59c5d0e63a17fcaf273c0e2d2bd1',
|
'net.java.dev.jna:jna:4.4.0:jna-4.4.0.jar:c4dadeeecaa90c8847902082aee5eb107fcf59c5d0e63a17fcaf273c0e2d2bd1',
|
||||||
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
|
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'witness'
|
apply plugin: 'witness'
|
||||||
|
|
||||||
|
configurations.all {
|
||||||
|
resolutionStrategy.preferProjectModules()
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(path: ':briar-core', configuration: 'default')
|
implementation project(path: ':briar-core', configuration: 'default')
|
||||||
implementation project(path: ':bramble-core', configuration: 'default')
|
implementation project(path: ':bramble-core', configuration: 'default')
|
||||||
@@ -108,7 +112,6 @@ dependencyVerification {
|
|||||||
'nekohtml:xercesMinimal:1.9.6.2:xercesMinimal-1.9.6.2.jar:95b8b357d19f63797dd7d67622fd3f18374d64acbc6584faba1c7759a31e8438',
|
'nekohtml:xercesMinimal:1.9.6.2:xercesMinimal-1.9.6.2.jar:95b8b357d19f63797dd7d67622fd3f18374d64acbc6584faba1c7759a31e8438',
|
||||||
'net.bytebuddy:byte-buddy-agent:1.6.14:byte-buddy-agent-1.6.14.jar:c141a2d6809c3eeff4a43d25992826abccebdd4b793af3e7a5f346e88ae73a33',
|
'net.bytebuddy:byte-buddy-agent:1.6.14:byte-buddy-agent-1.6.14.jar:c141a2d6809c3eeff4a43d25992826abccebdd4b793af3e7a5f346e88ae73a33',
|
||||||
'net.bytebuddy:byte-buddy:1.6.14:byte-buddy-1.6.14.jar:917758b3c651e278a15a029ba1d42dbf802d8b0e1fe2aa4b81c5750c64f461c1',
|
'net.bytebuddy:byte-buddy:1.6.14:byte-buddy-1.6.14.jar:917758b3c651e278a15a029ba1d42dbf802d8b0e1fe2aa4b81c5750c64f461c1',
|
||||||
'net.i2p.crypto:eddsa:0.2.0:eddsa-0.2.0.jar:a7cb1b85c16e2f0730b9204106929a1d9aaae1df728adc7041a8b8b605692140',
|
|
||||||
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
|
'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.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
|
||||||
'org.apache.maven.wagon:wagon-file:1.0-beta-6:wagon-file-1.0-beta-6.jar:7298feeb36ff14dd933c38e62585fb9973fea32fb3c4bc5379428cb1aac5dd3c',
|
'org.apache.maven.wagon:wagon-file:1.0-beta-6:wagon-file-1.0-beta-6.jar:7298feeb36ff14dd933c38e62585fb9973fea32fb3c4bc5379428cb1aac5dd3c',
|
||||||
@@ -186,19 +189,19 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 14
|
minSdkVersion 14
|
||||||
targetSdkVersion 26
|
targetSdkVersion 26
|
||||||
versionCode 1700
|
versionCode 1618
|
||||||
versionName "0.17.0"
|
versionName "0.16.18"
|
||||||
applicationId "org.briarproject.briar.android"
|
applicationId "org.briarproject.briar.beta"
|
||||||
resValue "string", "app_package", "org.briarproject.briar.android"
|
resValue "string", "app_package", "org.briarproject.briar.beta"
|
||||||
resValue "string", "app_name", "Briar"
|
resValue "string", "app_name", "Briar Beta"
|
||||||
buildConfigField "String", "GitHash", "\"${getGitHash()}\""
|
buildConfigField "String", "GitHash", "\"${getGitHash()}\""
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
debug {
|
debug {
|
||||||
applicationIdSuffix ".debug"
|
applicationIdSuffix ".debug"
|
||||||
resValue "string", "app_package", "org.briarproject.briar.android.debug"
|
resValue "string", "app_package", "org.briarproject.briar.beta.debug"
|
||||||
resValue "string", "app_name", "Briar Debug"
|
resValue "string", "app_name", "Briar Beta Debug"
|
||||||
shrinkResources false
|
shrinkResources false
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
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;
|
||||||
@@ -12,7 +14,7 @@ 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;
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory;
|
||||||
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
||||||
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
@@ -89,6 +91,8 @@ public interface AndroidComponent
|
|||||||
|
|
||||||
AndroidNotificationManager androidNotificationManager();
|
AndroidNotificationManager androidNotificationManager();
|
||||||
|
|
||||||
|
SharedPreferences sharedPreferences();
|
||||||
|
|
||||||
ScreenFilterMonitor screenFilterMonitor();
|
ScreenFilterMonitor screenFilterMonitor();
|
||||||
|
|
||||||
ConnectionRegistry connectionRegistry();
|
ConnectionRegistry connectionRegistry();
|
||||||
@@ -125,7 +129,7 @@ public interface AndroidComponent
|
|||||||
|
|
||||||
ContactExchangeTask contactExchangeTask();
|
ContactExchangeTask contactExchangeTask();
|
||||||
|
|
||||||
KeyAgreementTask keyAgreementTask();
|
KeyAgreementTaskFactory keyAgreementTaskFactory();
|
||||||
|
|
||||||
PayloadEncoder payloadEncoder();
|
PayloadEncoder payloadEncoder();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.briar.android;
|
package org.briarproject.briar.android;
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||||
@@ -157,6 +158,11 @@ public class AppModule {
|
|||||||
return devConfig;
|
return devConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
SharedPreferences provideSharedPreferences(Application app) {
|
||||||
|
return app.getSharedPreferences("db", MODE_PRIVATE);
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
ReferenceManager provideReferenceManager() {
|
ReferenceManager provideReferenceManager() {
|
||||||
@@ -174,8 +180,11 @@ public class AppModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
@Singleton
|
||||||
ScreenFilterMonitor provideScreenFilterMonitor(
|
ScreenFilterMonitor provideScreenFilterMonitor(
|
||||||
|
LifecycleManager lifecycleManager,
|
||||||
ScreenFilterMonitorImpl screenFilterMonitor) {
|
ScreenFilterMonitorImpl screenFilterMonitor) {
|
||||||
|
lifecycleManager.registerService(screenFilterMonitor);
|
||||||
return screenFilterMonitor;
|
return screenFilterMonitor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ package org.briarproject.briar.android;
|
|||||||
*/
|
*/
|
||||||
public interface BriarApplication {
|
public interface BriarApplication {
|
||||||
|
|
||||||
// This build expires on 31 December 2018
|
// This build expires on 30 April 2018
|
||||||
long EXPIRY_DATE = 1546214400 * 1000L;
|
long EXPIRY_DATE = 1525046400 * 1000L;
|
||||||
|
|
||||||
AndroidComponent getApplicationComponent();
|
AndroidComponent getApplicationComponent();
|
||||||
|
|
||||||
|
|||||||
@@ -124,21 +124,24 @@ public class BriarService extends Service {
|
|||||||
b.setPriority(PRIORITY_MIN);
|
b.setPriority(PRIORITY_MIN);
|
||||||
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();
|
@Override
|
||||||
StartResult result = lifecycleManager.startServices(nickname);
|
public void run() {
|
||||||
if (result == SUCCESS) {
|
String nickname = databaseConfig.getLocalAuthorName();
|
||||||
started = true;
|
StartResult result = lifecycleManager.startServices(nickname);
|
||||||
} else if (result == ALREADY_RUNNING) {
|
if (result == SUCCESS) {
|
||||||
LOG.info("Already running");
|
started = true;
|
||||||
stopSelf();
|
} else if (result == ALREADY_RUNNING) {
|
||||||
} else {
|
LOG.info("Already running");
|
||||||
if (LOG.isLoggable(WARNING))
|
stopSelf();
|
||||||
LOG.warning("Startup failed: " + result);
|
} else {
|
||||||
showStartupFailureNotification(result);
|
if (LOG.isLoggable(WARNING))
|
||||||
stopSelf();
|
LOG.warning("Startup failed: " + result);
|
||||||
|
showStartupFailureNotification(result);
|
||||||
|
stopSelf();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}).start();
|
}.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showStartupFailureNotification(StartResult result) {
|
private void showStartupFailureNotification(StartResult result) {
|
||||||
@@ -184,9 +187,12 @@ public class BriarService extends Service {
|
|||||||
LOG.info("Destroyed");
|
LOG.info("Destroyed");
|
||||||
stopForeground(true);
|
stopForeground(true);
|
||||||
// Stop the services in a background thread
|
// Stop the services in a background thread
|
||||||
new Thread(() -> {
|
new Thread() {
|
||||||
if (started) lifecycleManager.stopServices();
|
@Override
|
||||||
}).start();
|
public void run() {
|
||||||
|
if (started) lifecycleManager.stopServices();
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,13 +1,22 @@
|
|||||||
package org.briarproject.briar.android;
|
package org.briarproject.briar.android;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.PackageManager.NameNotFoundException;
|
import android.content.pm.PackageManager.NameNotFoundException;
|
||||||
import android.content.pm.Signature;
|
import android.content.pm.Signature;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.lifecycle.Service;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.ServiceException;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
||||||
|
|
||||||
@@ -16,23 +25,33 @@ import java.io.InputStream;
|
|||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
|
import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
|
||||||
|
import static android.content.Intent.ACTION_PACKAGE_ADDED;
|
||||||
|
import static android.content.Intent.ACTION_PACKAGE_CHANGED;
|
||||||
|
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
|
||||||
|
import static android.content.Intent.ACTION_PACKAGE_REPLACED;
|
||||||
import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
|
import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
|
||||||
import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
|
import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
|
||||||
|
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
|
||||||
import static android.content.pm.PackageManager.GET_PERMISSIONS;
|
import static android.content.pm.PackageManager.GET_PERMISSIONS;
|
||||||
import static android.content.pm.PackageManager.GET_SIGNATURES;
|
import static android.content.pm.PackageManager.GET_SIGNATURES;
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class ScreenFilterMonitorImpl implements ScreenFilterMonitor {
|
class ScreenFilterMonitorImpl implements ScreenFilterMonitor, Service {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(ScreenFilterMonitorImpl.class.getName());
|
Logger.getLogger(ScreenFilterMonitorImpl.class.getName());
|
||||||
@@ -56,54 +75,93 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor {
|
|||||||
"82BA35E003C1B4B10DD244A8EE24FFFD333872AB5221985EDAB0FC0D" +
|
"82BA35E003C1B4B10DD244A8EE24FFFD333872AB5221985EDAB0FC0D" +
|
||||||
"0B145B6AA192858E79020103";
|
"0B145B6AA192858E79020103";
|
||||||
|
|
||||||
|
private static final String PREF_KEY_ALLOWED = "allowedOverlayApps";
|
||||||
|
|
||||||
private final PackageManager pm;
|
private final PackageManager pm;
|
||||||
|
private final Application app;
|
||||||
|
private final AndroidExecutor androidExecutor;
|
||||||
|
private final SharedPreferences prefs;
|
||||||
|
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
// UiThread
|
||||||
|
@Nullable
|
||||||
|
private BroadcastReceiver receiver = null;
|
||||||
|
|
||||||
|
// UiThread
|
||||||
|
@Nullable
|
||||||
|
private Collection<AppDetails> cachedApps = null;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ScreenFilterMonitorImpl(Application app) {
|
ScreenFilterMonitorImpl(Application app, AndroidExecutor androidExecutor,
|
||||||
|
SharedPreferences prefs) {
|
||||||
pm = app.getPackageManager();
|
pm = app.getPackageManager();
|
||||||
|
this.app = app;
|
||||||
|
this.androidExecutor = androidExecutor;
|
||||||
|
this.prefs = prefs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@UiThread
|
@UiThread
|
||||||
public Set<String> getApps() {
|
public Collection<AppDetails> getApps() {
|
||||||
Set<String> screenFilterApps = new TreeSet<>();
|
if (cachedApps != null) return cachedApps;
|
||||||
|
Set<String> allowed = prefs.getStringSet(PREF_KEY_ALLOWED,
|
||||||
|
Collections.emptySet());
|
||||||
|
List<AppDetails> apps = new ArrayList<>();
|
||||||
List<PackageInfo> packageInfos =
|
List<PackageInfo> packageInfos =
|
||||||
pm.getInstalledPackages(GET_PERMISSIONS);
|
pm.getInstalledPackages(GET_PERMISSIONS);
|
||||||
for (PackageInfo packageInfo : packageInfos) {
|
for (PackageInfo packageInfo : packageInfos) {
|
||||||
if (isOverlayApp(packageInfo)) {
|
if (!allowed.contains(packageInfo.packageName)
|
||||||
String name = pkgToString(packageInfo);
|
&& isOverlayApp(packageInfo)) {
|
||||||
if (name != null) {
|
String name = getAppName(packageInfo);
|
||||||
screenFilterApps.add(name);
|
apps.add(new AppDetails(name, packageInfo.packageName));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return screenFilterApps;
|
Collections.sort(apps, (a, b) -> a.name.compareTo(b.name));
|
||||||
|
apps = Collections.unmodifiableList(apps);
|
||||||
|
cachedApps = apps;
|
||||||
|
return apps;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetches the application name for a given package.
|
@Override
|
||||||
@Nullable
|
@UiThread
|
||||||
private String pkgToString(PackageInfo pkgInfo) {
|
public void allowApps(Collection<String> packageNames) {
|
||||||
|
cachedApps = null;
|
||||||
|
Set<String> allowed = prefs.getStringSet(PREF_KEY_ALLOWED,
|
||||||
|
Collections.emptySet());
|
||||||
|
Set<String> merged = new HashSet<>(allowed);
|
||||||
|
merged.addAll(packageNames);
|
||||||
|
prefs.edit().putStringSet(PREF_KEY_ALLOWED, merged).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the application name for a given package, or the package name
|
||||||
|
// if no application name is available
|
||||||
|
private String getAppName(PackageInfo pkgInfo) {
|
||||||
CharSequence seq = pm.getApplicationLabel(pkgInfo.applicationInfo);
|
CharSequence seq = pm.getApplicationLabel(pkgInfo.applicationInfo);
|
||||||
if (seq != null) {
|
return seq == null ? pkgInfo.packageName : seq.toString();
|
||||||
return seq.toString();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if an installed package is a user app using the permission.
|
// Checks if an installed package is a user app using the permission.
|
||||||
private boolean isOverlayApp(PackageInfo packageInfo) {
|
private boolean isOverlayApp(PackageInfo packageInfo) {
|
||||||
int mask = FLAG_SYSTEM | FLAG_UPDATED_SYSTEM_APP;
|
int mask = FLAG_SYSTEM | FLAG_UPDATED_SYSTEM_APP;
|
||||||
// Ignore system apps
|
// Ignore system apps
|
||||||
if ((packageInfo.applicationInfo.flags & mask) != 0) {
|
if ((packageInfo.applicationInfo.flags & mask) != 0) return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Ignore Play Services, it's effectively a system app
|
// Ignore Play Services, it's effectively a system app
|
||||||
if (isPlayServices(packageInfo.packageName)) {
|
if (isPlayServices(packageInfo.packageName)) return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Get permissions
|
// Get permissions
|
||||||
String[] requestedPermissions = packageInfo.requestedPermissions;
|
String[] requestedPermissions = packageInfo.requestedPermissions;
|
||||||
if (requestedPermissions != null) {
|
if (requestedPermissions == null) return false;
|
||||||
|
if (SDK_INT >= 16 && SDK_INT < 23) {
|
||||||
|
// Check whether the permission has been requested and granted
|
||||||
|
int[] flags = packageInfo.requestedPermissionsFlags;
|
||||||
|
for (int i = 0; i < requestedPermissions.length; i++) {
|
||||||
|
if (requestedPermissions[i].equals(SYSTEM_ALERT_WINDOW)) {
|
||||||
|
// 'flags' may be null on Robolectric
|
||||||
|
return flags == null ||
|
||||||
|
(flags[i] & REQUESTED_PERMISSION_GRANTED) != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Check whether the permission has been requested
|
||||||
for (String requestedPermission : requestedPermissions) {
|
for (String requestedPermission : requestedPermissions) {
|
||||||
if (requestedPermission.equals(SYSTEM_ALERT_WINDOW)) {
|
if (requestedPermission.equals(SYSTEM_ALERT_WINDOW)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -113,6 +171,7 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("PackageManagerGetSignatures")
|
||||||
private boolean isPlayServices(String pkg) {
|
private boolean isPlayServices(String pkg) {
|
||||||
if (!PLAY_SERVICES_PACKAGE.equals(pkg)) return false;
|
if (!PLAY_SERVICES_PACKAGE.equals(pkg)) return false;
|
||||||
try {
|
try {
|
||||||
@@ -135,4 +194,36 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startService() throws ServiceException {
|
||||||
|
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||||
|
androidExecutor.runOnUiThread(() -> {
|
||||||
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addAction(ACTION_PACKAGE_ADDED);
|
||||||
|
filter.addAction(ACTION_PACKAGE_CHANGED);
|
||||||
|
filter.addAction(ACTION_PACKAGE_REMOVED);
|
||||||
|
filter.addAction(ACTION_PACKAGE_REPLACED);
|
||||||
|
filter.addDataScheme("package");
|
||||||
|
receiver = new PackageBroadcastReceiver();
|
||||||
|
app.registerReceiver(receiver, filter);
|
||||||
|
cachedApps = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopService() throws ServiceException {
|
||||||
|
androidExecutor.runOnUiThread(() -> {
|
||||||
|
if (receiver != null) app.unregisterReceiver(receiver);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PackageBroadcastReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@UiThread
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
cachedApps = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public interface TestingConstants {
|
|||||||
* Whether this is a beta build. This should be set to false for final
|
* Whether this is a beta build. This should be set to false for final
|
||||||
* release builds.
|
* release builds.
|
||||||
*/
|
*/
|
||||||
boolean IS_BETA_BUILD = false;
|
boolean IS_BETA_BUILD = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default log level. Disable logging for final release builds.
|
* Default log level. Disable logging for final release builds.
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import org.briarproject.briar.android.forum.CreateForumActivity;
|
|||||||
import org.briarproject.briar.android.forum.ForumActivity;
|
import org.briarproject.briar.android.forum.ForumActivity;
|
||||||
import org.briarproject.briar.android.forum.ForumListFragment;
|
import org.briarproject.briar.android.forum.ForumListFragment;
|
||||||
import org.briarproject.briar.android.forum.ForumModule;
|
import org.briarproject.briar.android.forum.ForumModule;
|
||||||
|
import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
|
||||||
import org.briarproject.briar.android.introduction.ContactChooserFragment;
|
import org.briarproject.briar.android.introduction.ContactChooserFragment;
|
||||||
import org.briarproject.briar.android.introduction.IntroductionActivity;
|
import org.briarproject.briar.android.introduction.IntroductionActivity;
|
||||||
import org.briarproject.briar.android.introduction.IntroductionMessageFragment;
|
import org.briarproject.briar.android.introduction.IntroductionMessageFragment;
|
||||||
@@ -152,7 +153,9 @@ public interface ActivityComponent {
|
|||||||
|
|
||||||
// Fragments
|
// Fragments
|
||||||
void inject(AuthorNameFragment fragment);
|
void inject(AuthorNameFragment fragment);
|
||||||
|
|
||||||
void inject(PasswordFragment fragment);
|
void inject(PasswordFragment fragment);
|
||||||
|
|
||||||
void inject(DozeFragment fragment);
|
void inject(DozeFragment fragment);
|
||||||
|
|
||||||
void inject(ContactListFragment fragment);
|
void inject(ContactListFragment fragment);
|
||||||
@@ -189,4 +192,5 @@ public interface ActivityComponent {
|
|||||||
|
|
||||||
void inject(SettingsFragment fragment);
|
void inject(SettingsFragment fragment);
|
||||||
|
|
||||||
|
void inject(ScreenFilterDialogFragment fragment);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.briarproject.briar.android.activity;
|
package org.briarproject.briar.android.activity;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.SharedPreferences;
|
|
||||||
|
|
||||||
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;
|
||||||
@@ -19,7 +18,6 @@ import org.briarproject.briar.android.navdrawer.NavDrawerControllerImpl;
|
|||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
|
||||||
import static android.content.Context.MODE_PRIVATE;
|
|
||||||
import static org.briarproject.briar.android.BriarService.BriarServiceConnection;
|
import static org.briarproject.briar.android.BriarService.BriarServiceConnection;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@@ -57,12 +55,6 @@ public class ActivityModule {
|
|||||||
return configController;
|
return configController;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ActivityScope
|
|
||||||
@Provides
|
|
||||||
SharedPreferences provideSharedPreferences(Activity activity) {
|
|
||||||
return activity.getSharedPreferences("db", MODE_PRIVATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ActivityScope
|
@ActivityScope
|
||||||
@Provides
|
@Provides
|
||||||
PasswordController providePasswordController(
|
PasswordController providePasswordController(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.os.Bundle;
|
|||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.support.annotation.LayoutRes;
|
import android.support.annotation.LayoutRes;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
import android.support.v4.app.FragmentManager;
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
@@ -21,13 +22,15 @@ import org.briarproject.briar.android.controller.ActivityLifecycleController;
|
|||||||
import org.briarproject.briar.android.forum.ForumModule;
|
import org.briarproject.briar.android.forum.ForumModule;
|
||||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||||
import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
|
import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
|
||||||
|
import org.briarproject.briar.android.util.UiUtils;
|
||||||
import org.briarproject.briar.android.widget.TapSafeFrameLayout;
|
import org.briarproject.briar.android.widget.TapSafeFrameLayout;
|
||||||
import org.briarproject.briar.android.widget.TapSafeFrameLayout.OnTapFilteredListener;
|
import org.briarproject.briar.android.widget.TapSafeFrameLayout.OnTapFilteredListener;
|
||||||
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
||||||
|
import org.briarproject.briar.api.android.ScreenFilterMonitor.AppDetails;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -48,7 +51,10 @@ public abstract class BaseActivity extends AppCompatActivity
|
|||||||
private final List<ActivityLifecycleController> lifecycleControllers =
|
private final List<ActivityLifecycleController> lifecycleControllers =
|
||||||
new ArrayList<>();
|
new ArrayList<>();
|
||||||
private boolean destroyed = false;
|
private boolean destroyed = false;
|
||||||
private ScreenFilterDialogFragment dialogFrag;
|
|
||||||
|
@Nullable
|
||||||
|
private Toolbar toolbar = null;
|
||||||
|
private boolean searchedForToolbar = false;
|
||||||
|
|
||||||
public abstract void injectActivity(ActivityComponent component);
|
public abstract void injectActivity(ActivityComponent component);
|
||||||
|
|
||||||
@@ -57,8 +63,8 @@ public abstract class BaseActivity extends AppCompatActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle state) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(state);
|
||||||
|
|
||||||
if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE);
|
if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE);
|
||||||
|
|
||||||
@@ -97,6 +103,16 @@ public abstract class BaseActivity extends AppCompatActivity
|
|||||||
for (ActivityLifecycleController alc : lifecycleControllers) {
|
for (ActivityLifecycleController alc : lifecycleControllers) {
|
||||||
alc.onActivityStart();
|
alc.onActivityStart();
|
||||||
}
|
}
|
||||||
|
protectToolbar();
|
||||||
|
ScreenFilterDialogFragment f = findDialogFragment();
|
||||||
|
if (f != null) f.setDismissListener(this::protectToolbar);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private ScreenFilterDialogFragment findDialogFragment() {
|
||||||
|
Fragment f = getSupportFragmentManager().findFragmentByTag(
|
||||||
|
ScreenFilterDialogFragment.TAG);
|
||||||
|
return (ScreenFilterDialogFragment) f;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -107,15 +123,6 @@ public abstract class BaseActivity extends AppCompatActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPause() {
|
|
||||||
super.onPause();
|
|
||||||
if (dialogFrag != null) {
|
|
||||||
dialogFrag.dismiss();
|
|
||||||
dialogFrag = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void showInitialFragment(BaseFragment f) {
|
protected void showInitialFragment(BaseFragment f) {
|
||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager().beginTransaction()
|
||||||
.replace(R.id.fragmentContainer, f, f.getUniqueTag())
|
.replace(R.id.fragmentContainer, f, f.getUniqueTag())
|
||||||
@@ -132,16 +139,27 @@ public abstract class BaseActivity extends AppCompatActivity
|
|||||||
.commit();
|
.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showScreenFilterWarning() {
|
private boolean showScreenFilterWarning() {
|
||||||
if (dialogFrag != null && dialogFrag.isVisible()) return;
|
// If the dialog is already visible, filter the tap
|
||||||
Set<String> apps = screenFilterMonitor.getApps();
|
ScreenFilterDialogFragment f = findDialogFragment();
|
||||||
if (apps.isEmpty()) return;
|
if (f != null && f.isVisible()) return false;
|
||||||
dialogFrag =
|
Collection<AppDetails> apps = screenFilterMonitor.getApps();
|
||||||
ScreenFilterDialogFragment.newInstance(new ArrayList<>(apps));
|
// If all overlay apps have been allowed, allow the tap
|
||||||
dialogFrag.setCancelable(false);
|
if (apps.isEmpty()) return true;
|
||||||
// Show dialog unless onSaveInstanceState() has been called, see #1112
|
// Show dialog unless onSaveInstanceState() has been called, see #1112
|
||||||
FragmentManager fm = getSupportFragmentManager();
|
FragmentManager fm = getSupportFragmentManager();
|
||||||
if (!fm.isStateSaved()) dialogFrag.show(fm, dialogFrag.getTag());
|
if (!fm.isStateSaved()) {
|
||||||
|
// Create dialog
|
||||||
|
f = ScreenFilterDialogFragment.newInstance(apps);
|
||||||
|
// When dialog is dismissed, update protection of toolbar
|
||||||
|
f.setDismissListener(this::protectToolbar);
|
||||||
|
// Hide soft keyboard when (re)showing dialog
|
||||||
|
View focus = getCurrentFocus();
|
||||||
|
if (focus != null) hideSoftKeyboard(focus);
|
||||||
|
f.show(fm, ScreenFilterDialogFragment.TAG);
|
||||||
|
}
|
||||||
|
// Filter the tap
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -195,15 +213,25 @@ public abstract class BaseActivity extends AppCompatActivity
|
|||||||
* is outside the wrapper.
|
* is outside the wrapper.
|
||||||
*/
|
*/
|
||||||
private void protectToolbar() {
|
private void protectToolbar() {
|
||||||
View decorView = getWindow().getDecorView();
|
findToolbar();
|
||||||
if (decorView instanceof ViewGroup) {
|
if (toolbar != null) {
|
||||||
Toolbar toolbar = findToolbar((ViewGroup) decorView);
|
boolean filter = !screenFilterMonitor.getApps().isEmpty();
|
||||||
if (toolbar != null) toolbar.setFilterTouchesWhenObscured(true);
|
UiUtils.setFilterTouchesWhenObscured(toolbar, filter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void findToolbar() {
|
||||||
|
if (searchedForToolbar) return;
|
||||||
|
View decorView = getWindow().getDecorView();
|
||||||
|
if (decorView instanceof ViewGroup)
|
||||||
|
toolbar = findToolbar((ViewGroup) decorView);
|
||||||
|
searchedForToolbar = true;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private Toolbar findToolbar(ViewGroup vg) {
|
private Toolbar findToolbar(ViewGroup vg) {
|
||||||
|
// Views inside tap-safe layouts are already protected
|
||||||
|
if (vg instanceof TapSafeFrameLayout) return null;
|
||||||
for (int i = 0, len = vg.getChildCount(); i < len; i++) {
|
for (int i = 0, len = vg.getChildCount(); i < len; i++) {
|
||||||
View child = vg.getChildAt(i);
|
View child = vg.getChildAt(i);
|
||||||
if (child instanceof Toolbar) return (Toolbar) child;
|
if (child instanceof Toolbar) return (Toolbar) child;
|
||||||
@@ -223,23 +251,20 @@ public abstract class BaseActivity extends AppCompatActivity
|
|||||||
@Override
|
@Override
|
||||||
public void setContentView(View v) {
|
public void setContentView(View v) {
|
||||||
super.setContentView(makeTapSafeWrapper(v));
|
super.setContentView(makeTapSafeWrapper(v));
|
||||||
protectToolbar();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setContentView(View v, LayoutParams layoutParams) {
|
public void setContentView(View v, LayoutParams layoutParams) {
|
||||||
super.setContentView(makeTapSafeWrapper(v), layoutParams);
|
super.setContentView(makeTapSafeWrapper(v), layoutParams);
|
||||||
protectToolbar();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addContentView(View v, LayoutParams layoutParams) {
|
public void addContentView(View v, LayoutParams layoutParams) {
|
||||||
super.addContentView(makeTapSafeWrapper(v), layoutParams);
|
super.addContentView(makeTapSafeWrapper(v), layoutParams);
|
||||||
protectToolbar();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTapFiltered() {
|
public boolean shouldAllowTap() {
|
||||||
showScreenFilterWarning();
|
return showScreenFilterWarning();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,11 +27,11 @@ import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
|
|||||||
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
|
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
|
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
|
||||||
import static android.os.Build.MANUFACTURER;
|
|
||||||
import static android.os.Build.VERSION.SDK_INT;
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING;
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.getDozeWhitelistingIntent;
|
import static org.briarproject.briar.android.util.UiUtils.getDozeWhitelistingIntent;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
|
||||||
|
|
||||||
@SuppressLint("Registered")
|
@SuppressLint("Registered")
|
||||||
public abstract class BriarActivity extends BaseActivity {
|
public abstract class BriarActivity extends BaseActivity {
|
||||||
@@ -80,7 +80,7 @@ public abstract class BriarActivity extends BaseActivity {
|
|||||||
public void setSceneTransitionAnimation() {
|
public void setSceneTransitionAnimation() {
|
||||||
if (SDK_INT < 21) return;
|
if (SDK_INT < 21) return;
|
||||||
// workaround for #1007
|
// workaround for #1007
|
||||||
if (SDK_INT == 24 && MANUFACTURER.equalsIgnoreCase("Samsung")) {
|
if (isSamsung7()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Transition slide = new Slide(Gravity.RIGHT);
|
Transition slide = new Slide(Gravity.RIGHT);
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import static org.briarproject.briar.android.blog.BasePostFragment.POST_ID;
|
|||||||
import static org.briarproject.briar.android.util.UiUtils.TEASER_LENGTH;
|
import static org.briarproject.briar.android.util.UiUtils.TEASER_LENGTH;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.getSpanned;
|
import static org.briarproject.briar.android.util.UiUtils.getSpanned;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.getTeaser;
|
import static org.briarproject.briar.android.util.UiUtils.getTeaser;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.makeLinksClickable;
|
import static org.briarproject.briar.android.util.UiUtils.makeLinksClickable;
|
||||||
import static org.briarproject.briar.api.blog.MessageType.POST;
|
import static org.briarproject.briar.api.blog.MessageType.POST;
|
||||||
|
|
||||||
@@ -130,7 +131,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
|||||||
i.putExtra(GROUP_ID, item.getGroupId().getBytes());
|
i.putExtra(GROUP_ID, item.getGroupId().getBytes());
|
||||||
i.putExtra(POST_ID, item.getId().getBytes());
|
i.putExtra(POST_ID, item.getId().getBytes());
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= 23) {
|
if (Build.VERSION.SDK_INT >= 23 && !isSamsung7()) {
|
||||||
ActivityOptionsCompat options =
|
ActivityOptionsCompat options =
|
||||||
makeSceneTransitionAnimation((Activity) ctx, layout,
|
makeSceneTransitionAnimation((Activity) ctx, layout,
|
||||||
getTransitionName(item.getId()));
|
getTransitionName(item.getId()));
|
||||||
@@ -138,6 +139,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
|||||||
options.toBundle());
|
options.toBundle());
|
||||||
} else {
|
} else {
|
||||||
// work-around for android bug #224270
|
// work-around for android bug #224270
|
||||||
|
// work-around for Samsung Android 7 bug #1007
|
||||||
ctx.startActivity(i);
|
ctx.startActivity(i);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -123,22 +123,25 @@ public class BriarControllerImpl implements BriarController {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void signOut(ResultHandler<Void> eventHandler) {
|
public void signOut(ResultHandler<Void> eventHandler) {
|
||||||
new Thread(() -> {
|
new Thread() {
|
||||||
try {
|
@Override
|
||||||
// Wait for the service to finish starting up
|
public void run() {
|
||||||
IBinder binder = serviceConnection.waitForBinder();
|
try {
|
||||||
BriarService service =
|
// Wait for the service to finish starting up
|
||||||
((BriarService.BriarBinder) binder).getService();
|
IBinder binder = serviceConnection.waitForBinder();
|
||||||
service.waitForStartup();
|
BriarService service =
|
||||||
// Shut down the service and wait for it to shut down
|
((BriarService.BriarBinder) binder).getService();
|
||||||
LOG.info("Shutting down service");
|
service.waitForStartup();
|
||||||
service.shutdown();
|
// Shut down the service and wait for it to shut down
|
||||||
service.waitForShutdown();
|
LOG.info("Shutting down service");
|
||||||
} catch (InterruptedException e) {
|
service.shutdown();
|
||||||
LOG.warning("Interrupted while waiting for service");
|
service.waitForShutdown();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LOG.warning("Interrupted while waiting for service");
|
||||||
|
}
|
||||||
|
eventHandler.onResult(null);
|
||||||
}
|
}
|
||||||
eventHandler.onResult(null);
|
}.start();
|
||||||
}).start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void unbindService() {
|
private void unbindService() {
|
||||||
|
|||||||
@@ -1,40 +1,106 @@
|
|||||||
package org.briarproject.briar.android.fragment;
|
package org.briarproject.briar.android.fragment;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Activity;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.DialogFragment;
|
import android.support.v4.app.DialogFragment;
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.activity.BaseActivity;
|
||||||
|
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
||||||
|
import org.briarproject.briar.api.android.ScreenFilterMonitor.AppDetails;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
@NotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
|
@ParametersNotNullByDefault
|
||||||
public class ScreenFilterDialogFragment extends DialogFragment {
|
public class ScreenFilterDialogFragment extends DialogFragment {
|
||||||
|
|
||||||
|
public static final String TAG = ScreenFilterDialogFragment.class.getName();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ScreenFilterMonitor screenFilterMonitor;
|
||||||
|
|
||||||
|
DismissListener dismissListener = null;
|
||||||
|
|
||||||
public static ScreenFilterDialogFragment newInstance(
|
public static ScreenFilterDialogFragment newInstance(
|
||||||
ArrayList<String> apps) {
|
Collection<AppDetails> apps) {
|
||||||
ScreenFilterDialogFragment frag = new ScreenFilterDialogFragment();
|
ScreenFilterDialogFragment frag = new ScreenFilterDialogFragment();
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putStringArrayList("apps", apps);
|
ArrayList<String> appNames = new ArrayList<>();
|
||||||
|
for (AppDetails a : apps) appNames.add(a.name);
|
||||||
|
args.putStringArrayList("appNames", appNames);
|
||||||
|
ArrayList<String> packageNames = new ArrayList<>();
|
||||||
|
for (AppDetails a : apps) packageNames.add(a.packageName);
|
||||||
|
args.putStringArrayList("packageNames", packageNames);
|
||||||
frag.setArguments(args);
|
frag.setArguments(args);
|
||||||
return frag;
|
return frag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDismissListener(DismissListener dismissListener) {
|
||||||
|
this.dismissListener = dismissListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
Activity activity = getActivity();
|
||||||
|
if (activity == null) throw new IllegalStateException();
|
||||||
|
((BaseActivity) activity).getActivityComponent().inject(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(),
|
Activity activity = getActivity();
|
||||||
|
if (activity == null) throw new IllegalStateException();
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(activity,
|
||||||
R.style.BriarDialogThemeNoFilter);
|
R.style.BriarDialogThemeNoFilter);
|
||||||
builder.setTitle(R.string.screen_filter_title);
|
builder.setTitle(R.string.screen_filter_title);
|
||||||
ArrayList<String> apps = getArguments().getStringArrayList("apps");
|
Bundle args = getArguments();
|
||||||
builder.setMessage(getString(R.string.screen_filter_body,
|
if (args == null) throw new IllegalStateException();
|
||||||
TextUtils.join("\n", apps)));
|
ArrayList<String> appNames = args.getStringArrayList("appNames");
|
||||||
builder.setNeutralButton(R.string.continue_button,
|
ArrayList<String> packageNames =
|
||||||
(dialog, which) -> dialog.dismiss());
|
args.getStringArrayList("packageNames");
|
||||||
|
if (appNames == null || packageNames == null)
|
||||||
|
throw new IllegalStateException();
|
||||||
|
LayoutInflater inflater = activity.getLayoutInflater();
|
||||||
|
// See https://stackoverflow.com/a/24720976/6314875
|
||||||
|
@SuppressLint("InflateParams")
|
||||||
|
View dialogView = inflater.inflate(R.layout.dialog_screen_filter, null);
|
||||||
|
builder.setView(dialogView);
|
||||||
|
TextView message = dialogView.findViewById(R.id.screen_filter_message);
|
||||||
|
message.setText(getString(R.string.screen_filter_body,
|
||||||
|
TextUtils.join("\n", appNames)));
|
||||||
|
CheckBox allow = dialogView.findViewById(R.id.screen_filter_checkbox);
|
||||||
|
builder.setNeutralButton(R.string.continue_button, (dialog, which) -> {
|
||||||
|
if (allow.isChecked()) screenFilterMonitor.allowApps(packageNames);
|
||||||
|
dialog.dismiss();
|
||||||
|
});
|
||||||
|
builder.setCancelable(false);
|
||||||
return builder.create();
|
return builder.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDismiss(DialogInterface dialog) {
|
||||||
|
super.onDismiss(dialog);
|
||||||
|
if (dismissListener != null) dismissListener.onDialogDismissed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface DismissListener {
|
||||||
|
void onDialogDismissed();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import android.util.AttributeSet;
|
|||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import android.view.SurfaceHolder;
|
import android.view.SurfaceHolder;
|
||||||
import android.view.SurfaceView;
|
import android.view.SurfaceView;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
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;
|
||||||
@@ -38,18 +39,21 @@ import static java.util.logging.Level.WARNING;
|
|||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
||||||
AutoFocusCallback {
|
AutoFocusCallback, View.OnClickListener {
|
||||||
|
|
||||||
private static final int AUTO_FOCUS_RETRY_DELAY = 5000; // Milliseconds
|
private static final int AUTO_FOCUS_RETRY_DELAY = 5000; // Milliseconds
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(CameraView.class.getName());
|
Logger.getLogger(CameraView.class.getName());
|
||||||
|
|
||||||
|
private final Runnable autoFocusRetry = this::retryAutoFocus;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private Camera camera = null;
|
private Camera camera = null;
|
||||||
private PreviewConsumer previewConsumer = null;
|
private PreviewConsumer previewConsumer = null;
|
||||||
private Surface surface = null;
|
private Surface surface = null;
|
||||||
private int displayOrientation = 0, surfaceWidth = 0, surfaceHeight = 0;
|
private int displayOrientation = 0, surfaceWidth = 0, surfaceHeight = 0;
|
||||||
private boolean previewStarted = false, autoFocus = false;
|
private boolean previewStarted = false;
|
||||||
|
private boolean autoFocusSupported = false, autoFocusRunning = false;
|
||||||
|
|
||||||
public CameraView(Context context) {
|
public CameraView(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
@@ -74,6 +78,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
super.onAttachedToWindow();
|
super.onAttachedToWindow();
|
||||||
setKeepScreenOn(true);
|
setKeepScreenOn(true);
|
||||||
getHolder().addCallback(this);
|
getHolder().addCallback(this);
|
||||||
|
setOnClickListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -157,27 +162,41 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
@UiThread
|
@UiThread
|
||||||
private void startConsumer() throws CameraException {
|
private void startConsumer() throws CameraException {
|
||||||
if (camera == null) throw new CameraException("Camera is null");
|
if (camera == null) throw new CameraException("Camera is null");
|
||||||
if (autoFocus) {
|
startAutoFocus();
|
||||||
|
previewConsumer.start(camera);
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
private void startAutoFocus() throws CameraException {
|
||||||
|
if (camera != null && autoFocusSupported && !autoFocusRunning) {
|
||||||
try {
|
try {
|
||||||
|
removeCallbacks(autoFocusRetry);
|
||||||
camera.autoFocus(this);
|
camera.autoFocus(this);
|
||||||
|
autoFocusRunning = true;
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
throw new CameraException(e);
|
throw new CameraException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
previewConsumer.start(camera);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void stopConsumer() throws CameraException {
|
private void stopConsumer() throws CameraException {
|
||||||
if (camera == null) throw new CameraException("Camera is null");
|
if (camera == null) throw new CameraException("Camera is null");
|
||||||
if (autoFocus) {
|
cancelAutoFocus();
|
||||||
|
previewConsumer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
private void cancelAutoFocus() throws CameraException {
|
||||||
|
if (camera != null && autoFocusSupported && autoFocusRunning) {
|
||||||
try {
|
try {
|
||||||
|
removeCallbacks(autoFocusRetry);
|
||||||
camera.cancelAutoFocus();
|
camera.cancelAutoFocus();
|
||||||
|
autoFocusRunning = false;
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
throw new CameraException(e);
|
throw new CameraException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
previewConsumer.stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
@@ -325,7 +344,7 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void enableAutoFocus(String focusMode) {
|
private void enableAutoFocus(String focusMode) {
|
||||||
autoFocus = FOCUS_MODE_AUTO.equals(focusMode) ||
|
autoFocusSupported = FOCUS_MODE_AUTO.equals(focusMode) ||
|
||||||
FOCUS_MODE_MACRO.equals(focusMode);
|
FOCUS_MODE_MACRO.equals(focusMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,16 +446,23 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAutoFocus(boolean success, Camera camera) {
|
public void onAutoFocus(boolean success, Camera camera) {
|
||||||
LOG.info("Auto focus succeeded: " + success);
|
if (LOG.isLoggable(INFO))
|
||||||
postDelayed(this::retryAutoFocus, AUTO_FOCUS_RETRY_DELAY);
|
LOG.info("Auto focus succeeded: " + success);
|
||||||
|
autoFocusRunning = false;
|
||||||
|
postDelayed(autoFocusRetry, AUTO_FOCUS_RETRY_DELAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void retryAutoFocus() {
|
private void retryAutoFocus() {
|
||||||
try {
|
try {
|
||||||
if (camera != null) camera.autoFocus(this);
|
startAutoFocus();
|
||||||
} catch (RuntimeException e) {
|
} catch (CameraException e) {
|
||||||
LOG.log(WARNING, "Error retrying auto focus", e);
|
LOG.log(WARNING, e.toString(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
retryAutoFocus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -24,6 +24,7 @@ import com.google.zxing.Result;
|
|||||||
import org.briarproject.bramble.api.event.Event;
|
import org.briarproject.bramble.api.event.Event;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
|
||||||
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory;
|
||||||
import org.briarproject.bramble.api.keyagreement.Payload;
|
import org.briarproject.bramble.api.keyagreement.Payload;
|
||||||
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
||||||
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
||||||
@@ -47,7 +48,6 @@ import java.util.logging.Logger;
|
|||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Provider;
|
|
||||||
|
|
||||||
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
|
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
|
||||||
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
|
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
|
||||||
@@ -68,7 +68,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
|||||||
private static final Logger LOG = Logger.getLogger(TAG);
|
private static final Logger LOG = Logger.getLogger(TAG);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
Provider<KeyAgreementTask> keyAgreementTaskProvider;
|
KeyAgreementTaskFactory keyAgreementTaskFactory;
|
||||||
@Inject
|
@Inject
|
||||||
PayloadEncoder payloadEncoder;
|
PayloadEncoder payloadEncoder;
|
||||||
@Inject
|
@Inject
|
||||||
@@ -187,7 +187,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
|||||||
@UiThread
|
@UiThread
|
||||||
private void startListening() {
|
private void startListening() {
|
||||||
KeyAgreementTask oldTask = task;
|
KeyAgreementTask oldTask = task;
|
||||||
KeyAgreementTask newTask = keyAgreementTaskProvider.get();
|
KeyAgreementTask newTask = keyAgreementTaskFactory.createTask();
|
||||||
task = newTask;
|
task = newTask;
|
||||||
ioExecutor.execute(() -> {
|
ioExecutor.execute(() -> {
|
||||||
if (oldTask != null) oldTask.stopListening();
|
if (oldTask != null) oldTask.stopListening();
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import android.widget.Toast;
|
|||||||
|
|
||||||
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.BriarActivity;
|
||||||
import org.briarproject.briar.android.controller.handler.UiResultHandler;
|
import org.briarproject.briar.android.controller.handler.UiResultHandler;
|
||||||
import org.briarproject.briar.android.util.UiUtils;
|
import org.briarproject.briar.android.util.UiUtils;
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ import static android.view.View.INVISIBLE;
|
|||||||
import static android.view.View.VISIBLE;
|
import static android.view.View.VISIBLE;
|
||||||
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
|
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
|
||||||
|
|
||||||
public class ChangePasswordActivity extends BaseActivity
|
public class ChangePasswordActivity extends BriarActivity
|
||||||
implements OnClickListener, OnEditorActionListener {
|
implements OnClickListener, OnEditorActionListener {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ import java.util.logging.Logger;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static android.os.Build.MANUFACTURER;
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
import static android.support.v4.app.FragmentManager.POP_BACK_STACK_INCLUSIVE;
|
import static android.support.v4.app.FragmentManager.POP_BACK_STACK_INCLUSIVE;
|
||||||
import static android.support.v4.view.GravityCompat.START;
|
import static android.support.v4.view.GravityCompat.START;
|
||||||
import static android.support.v4.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED;
|
import static android.support.v4.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED;
|
||||||
@@ -215,10 +217,15 @@ public class NavDrawerActivity extends BriarActivity implements
|
|||||||
} else if (getSupportFragmentManager().getBackStackEntryCount() == 0 &&
|
} else if (getSupportFragmentManager().getBackStackEntryCount() == 0 &&
|
||||||
getSupportFragmentManager()
|
getSupportFragmentManager()
|
||||||
.findFragmentByTag(ContactListFragment.TAG) != null) {
|
.findFragmentByTag(ContactListFragment.TAG) != null) {
|
||||||
Intent i = new Intent(Intent.ACTION_MAIN);
|
if (SDK_INT == 19 && MANUFACTURER.equalsIgnoreCase("Samsung")) {
|
||||||
i.addCategory(Intent.CATEGORY_HOME);
|
// workaround for #1116 causes splash screen to show again
|
||||||
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
super.onBackPressed();
|
||||||
startActivity(i);
|
} else {
|
||||||
|
Intent i = new Intent(Intent.ACTION_MAIN);
|
||||||
|
i.addCategory(Intent.CATEGORY_HOME);
|
||||||
|
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
startActivity(i);
|
||||||
|
}
|
||||||
} else if (getSupportFragmentManager().getBackStackEntryCount() == 0 &&
|
} else if (getSupportFragmentManager().getBackStackEntryCount() == 0 &&
|
||||||
getSupportFragmentManager()
|
getSupportFragmentManager()
|
||||||
.findFragmentByTag(ContactListFragment.TAG) == null) {
|
.findFragmentByTag(ContactListFragment.TAG) == null) {
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ import javax.inject.Inject;
|
|||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.briar.android.BriarApplication.EXPIRY_DATE;
|
import static org.briarproject.briar.android.BriarApplication.EXPIRY_DATE;
|
||||||
import static org.briarproject.briar.android.TestingConstants.IS_BETA_BUILD;
|
|
||||||
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
|
||||||
import static org.briarproject.briar.android.controller.BriarControllerImpl.DOZE_ASK_AGAIN;
|
import static org.briarproject.briar.android.controller.BriarControllerImpl.DOZE_ASK_AGAIN;
|
||||||
import static org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning.NO;
|
import static org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning.NO;
|
||||||
import static org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning.SHOW;
|
import static org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning.SHOW;
|
||||||
@@ -46,7 +44,7 @@ public class NavDrawerControllerImpl extends DbControllerImpl
|
|||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(NavDrawerControllerImpl.class.getName());
|
Logger.getLogger(NavDrawerControllerImpl.class.getName());
|
||||||
private static final String EXPIRY_DATE_WARNING = "expiryDateWarning";
|
private static final String EXPIRY_DATE_WARNING = "expiryDateWarning";
|
||||||
private static final String EXPIRY_SHOW_UPDATE = "expiryShowUpdate";
|
private static final String EXPIRY_SHOW_UPDATE = "expiryShowUpdateAgain";
|
||||||
|
|
||||||
private final PluginManager pluginManager;
|
private final PluginManager pluginManager;
|
||||||
private final SettingsManager settingsManager;
|
private final SettingsManager settingsManager;
|
||||||
@@ -108,10 +106,6 @@ public class NavDrawerControllerImpl extends DbControllerImpl
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showExpiryWarning(ResultHandler<ExpiryWarning> handler) {
|
public void showExpiryWarning(ResultHandler<ExpiryWarning> handler) {
|
||||||
if (!IS_DEBUG_BUILD && !IS_BETA_BUILD) {
|
|
||||||
handler.onResult(NO);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
runOnDbThread(() -> {
|
runOnDbThread(() -> {
|
||||||
try {
|
try {
|
||||||
Settings settings =
|
Settings settings =
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user