Compare commits

..

71 Commits

Author SHA1 Message Date
akwizgran
812522a900 Bump version numbers for beta release. 2018-02-19 16:40:47 +00:00
akwizgran
98db9da4bc Merge branch '509-tap-viewfinder-to-auto-focus' into 'maintenance-0.16'
Backport: Tap viewfinder to restart auto focus

See merge request akwizgran/briar!701
2018-02-19 16:20:16 +00:00
akwizgran
eda3c964aa Merge branch '1137-stop-polling-disabled-plugins' into 'maintenance-0.16'
Backport: Don't poll disabled transport plugins

See merge request akwizgran/briar!700
2018-02-19 16:03:15 +00:00
akwizgran
68df606146 Tap viewfinder to restart auto focus. 2018-02-19 15:58:20 +00:00
akwizgran
52bd699d2d Don't poll disabled transport plugins. 2018-02-19 15:53:43 +00:00
Torsten Grote
abb8db10db Merge branch 'migration-30-31' into 'maintenance-0.16'
Beta: Migrate DB schema from version 30 to 31

See merge request akwizgran/briar!690
2018-02-18 17:58:48 +00:00
akwizgran
30edb90426 Add migration from schema 30 to 31. 2018-02-02 17:01:49 +00:00
akwizgran
ffc94b2812 Merge branch '545-remove-unnecessary-indexes' into 'maintenance-0.16'
Backport: Remove unnecessary DB indexes

See merge request akwizgran/briar!692
2018-02-02 17:00:00 +00:00
akwizgran
35a7bb4576 Merge branch '594-db-migrations' into 'maintenance-0.16'
Backport: Migrate schema when opening database

See merge request akwizgran/briar!689
2018-02-02 15:46:39 +00:00
akwizgran
2d87e34aa2 Throw meaningful exceptions for schema errors. 2018-02-02 15:34:49 +00:00
akwizgran
088564f22f Add comment. 2018-02-02 15:34:25 +00:00
akwizgran
8c8c1158f4 Apply more than one migration if suitable. 2018-02-02 15:34:09 +00:00
akwizgran
8faa456eb2 Add unit tests for migration logic. 2018-02-02 15:32:20 +00:00
akwizgran
4c61158326 Migrate database schema if a migration is available. 2018-02-02 15:31:58 +00:00
akwizgran
6792abc00a Remove unnecessary DB indexes. 2018-02-01 17:44:22 +00:00
Torsten Grote
63442aea1d Merge branch '1162-redundant-db-tasks' into 'maintenance-0.16'
Backport: Avoid queueing redundant DB tasks during sync

See merge request akwizgran/briar!685
2018-02-01 16:17:11 +00:00
akwizgran
a58443eaa8 Merge branch '1148-wrong-network-interface' into 'maintenance-0.16'
Backport: Prefer LAN addresses with longer prefixes

See merge request akwizgran/briar!684
2018-02-01 15:48:53 +00:00
akwizgran
14a9614c35 Avoid queueing redundant DB tasks during sync. 2018-02-01 15:48:15 +00:00
akwizgran
f1011b97b3 Merge branch '1143-screen-overlay-dialog' into 'maintenance-0.16'
Backport: Don't show screen overlay dialog if all overlay apps have been allowed

See merge request akwizgran/briar!683
2018-02-01 15:41:55 +00:00
akwizgran
1935b1e09a Add tests for link-local addresses. 2018-02-01 15:40:23 +00:00
akwizgran
ac9df9d5d8 Prefer LAN addresses with longer prefixes. 2018-02-01 15:40:23 +00:00
akwizgran
30a800a4d0 Remove unused argument. 2018-02-01 15:34:16 +00:00
akwizgran
69537b67a2 Simplify dialog handling, work around Android bug. 2018-02-01 15:34:16 +00:00
akwizgran
92982f98a8 Update screen overlay warning text. 2018-02-01 15:34:16 +00:00
akwizgran
ea5fa72224 Re-show dialog when activity resumes or is recreated. 2018-02-01 15:34:16 +00:00
akwizgran
5a1651d483 Set layout weight so checkbox is visible. 2018-02-01 15:34:16 +00:00
akwizgran
fcbf6dfb7f Cache the list of overlay apps. 2018-02-01 15:34:15 +00:00
akwizgran
7aebf92a6f Allow filtered taps if all overlay apps are whitelisted. 2018-02-01 15:34:10 +00:00
akwizgran
1b9f8d4f0b Merge branch '1116-samsung-back-crash' into 'maintenance-0.16'
Backport: Workaround for Samsung crash in Android 4.4

See merge request akwizgran/briar!682
2018-02-01 11:00:28 +00:00
Torsten Grote
93db4eb986 Workaround for Samsung crash in Android 4.4
Closes #1116
2018-02-01 10:41:48 +00:00
akwizgran
347c2f22c1 Bump version numbers for beta release. 2018-01-29 16:48:21 +00:00
Torsten Grote
a8ea191ffb Merge branch '1007-samsung-transition-npe-fix' into 'maintenance-0.16'
Backport: Another attempt at fixing an infamous Samsung activity transition NPE

See merge request akwizgran/briar!678
2018-01-29 14:53:46 +00:00
Torsten Grote
2a4c22757b Another attempt at fixing an infamous Samsung activity transition NPE 2018-01-29 12:36:21 -02:00
Torsten Grote
28ebbbc7d1 Backport translation updates
New translations: br, nl, he, sv, cs, ja
2018-01-29 10:45:12 -02:00
akwizgran
5e7d08f05d Merge branch 'change-password-activity' into 'maintenance-0.16'
Backport: ChangePasswordActivity should extend BriarActivity

See merge request akwizgran/briar!673
2018-01-23 17:36:18 +00:00
akwizgran
ea005748dc Merge branch 'tor-plugin-detect-connectivity-loss' into 'maintenance-0.16'
Backport: Tor plugin should detect connectivity loss

See merge request akwizgran/briar!672
2018-01-23 17:29:28 +00:00
akwizgran
b021bfab5e ChangePasswordActivity should extend BriarActivity. 2018-01-23 17:22:43 +00:00
akwizgran
29cd105a1d Use scheduler service to schedule connectivity checks. 2018-01-23 17:16:59 +00:00
akwizgran
be2e68e96c Listen for a wider range of connectivity-related events. 2018-01-23 17:15:53 +00:00
akwizgran
9dd3f81bb7 Use Tor's OR connection events to detect lost connectivity. 2018-01-23 17:15:53 +00:00
akwizgran
5d918591d4 Merge branch '1145-avoid-unnecessary-db-queries' into 'maintenance-0.16'
Backport: Avoid unnecessary DB queries when starting clients

See merge request akwizgran/briar!669
2018-01-16 15:33:14 +00:00
akwizgran
f1c027fa4d Avoid unnecessary DB queries when starting clients. 2018-01-16 15:23:31 +00:00
akwizgran
d2d3ccf68d Merge branch 'prefer-project-modules' into 'maintenance-0.16'
Backport: Prefer project modules over prebuilt dependencies

See merge request akwizgran/briar!668
2018-01-12 17:55:05 +00:00
akwizgran
f4efed54d5 Prefer project modules over prebuilt dependencies. 2018-01-12 17:35:59 +00:00
akwizgran
459538e40c Bump version numbers for beta release. 2017-12-22 14:43:03 +00:00
akwizgran
183f501761 Merge branch '1132-upgrade-tor-0.2.9.14' into 'maintenance-0.16'
Beta: Upgrade Tor to 0.2.9.14, GeoIP to 2017-11-06

See merge request akwizgran/briar!657
2017-12-22 14:10:52 +00:00
akwizgran
65ee5f539b Upgrade Tor to 0.2.9.14, GeoIP to 2017-11-06. 2017-12-22 13:52:45 +00:00
akwizgran
604339326c Merge branch '1129-send-on-ctrl-enter' into 'maintenance-0.16'
Beta: Send message on ctrl + enter

See merge request akwizgran/briar!656
2017-12-22 11:49:55 +00:00
sbkaf
0acec1343f send message on ctrl + enter 2017-12-22 11:32:15 +00:00
akwizgran
0434756bbd Merge branch '1133-extend-expiry-period' into 'maintenance-0.16'
Extend expiry date, show extension notification

See merge request akwizgran/briar!655
2017-12-22 11:23:40 +00:00
akwizgran
e233433140 Extend expiry date, show extension notification. 2017-12-22 10:58:11 +00:00
akwizgran
c63f285f53 Bumped version numbers for beta release. 2017-12-07 14:13:11 +00:00
akwizgran
0800188718 Merge branch '1112-screen-filter-crash' into 'maintenance-0.16'
Beta: Don't show screen filter dialog after onSaveInstanceState().

See merge request !650
2017-12-07 13:29:27 +00:00
akwizgran
6188e48beb Don't show screen filter dialog after onSaveInstanceState(). 2017-12-07 13:07:07 +00:00
akwizgran
5726e29b56 Merge branch '1088-huawei-whitelisting' into 'maintenance-0.16'
Beta: Add button for Huawei's power manager to setup wizard

See merge request !648
2017-12-07 13:05:34 +00:00
Torsten Grote
5d70399de0 Add button for Huawei's power manager to setup wizard 2017-12-05 15:26:14 -02:00
akwizgran
73202dde5e Merge branch '1127-notification-channels' into 'maintenance-0.16'
Beta: Use channels for all notifications

See merge request !647
2017-12-05 17:03:37 +00:00
akwizgran
a98ac8233c Sort order of channel IDs affects UI of Settings app. 2017-12-05 16:49:31 +00:00
akwizgran
bee3e244fc Use channels for all notifications. 2017-12-05 16:49:31 +00:00
akwizgran
da25999a15 Merge branch '1120-crash-removing-shutdown-hook' into 'maintenance-0.16'
Beta: Don't remove shutdown hook when closing DB

See merge request !645
2017-12-05 14:58:56 +00:00
akwizgran
62049df342 Don't remove shutdown hook when closing DB. 2017-12-05 14:46:07 +00:00
akwizgran
024e5aa90f Bumped version numbers for beta release. 2017-12-04 14:43:27 +00:00
akwizgran
6d791481d5 Merge branch '1007-samsung-transition-npe-beta' into 'maintenance-0.16'
Beta: Don't set scene transition for Samsung devices running Android 7.0

See merge request !641
2017-12-04 14:35:39 +00:00
Torsten Grote
0a807d0893 Don't set scene transition for Samsung devices running Android 7.0 2017-12-04 10:58:20 -02:00
akwizgran
23596bbdd4 Merge branch origin/maintenance-0.16 into maintenance-0.16 2017-12-01 17:19:42 +00:00
Torsten Grote
fe79954138 Merge branch 'briar-beta-app-name' into 'maintenance-0.16'
Change app name for beta debug builds

See merge request !636
2017-12-01 16:43:45 +00:00
akwizgran
9902c023ca Bump version number for beta release. 2017-12-01 16:30:18 +00:00
akwizgran
e8baee6734 Specify 7 characters for Git revision.
(cherry picked from commit f0d8532)
2017-12-01 16:29:45 +00:00
akwizgran
a8dc029e56 Change app name for beta debug builds. 2017-12-01 16:21:20 +00:00
akwizgran
74e3fee7aa Merge branch '1124-notification-channel-crash-beta' into 'maintenance-0.16'
Beta: Use NotificationChannel for foreground service to avoid crash on Android 8.1

See merge request !635
2017-12-01 16:00:53 +00:00
Torsten Grote
05aac696b7 Use NotificationChannel for foreground service to avoid crash on Android 8.1
This also seems to address #1075 at least on an emulator
2017-12-01 13:47:02 -02:00
171 changed files with 7206 additions and 5092 deletions

View File

@@ -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) {

View File

@@ -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.**

View File

@@ -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 =

View File

@@ -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();
} }
} }
} }

View File

@@ -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;
} }

View File

@@ -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);
} }

View File

@@ -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.
*/ */

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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";
} }

View File

@@ -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();
}

View File

@@ -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.

View File

@@ -36,8 +36,4 @@ public class ClientId implements Comparable<ClientId> {
return id.hashCode(); return id.hashCode();
} }
@Override
public String toString() {
return id;
}
} }

View File

@@ -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);
} }

View File

@@ -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);

View File

@@ -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);

View File

@@ -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";
} }

View File

@@ -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);
}
} }

View File

@@ -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',
] ]
} }

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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);

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
} }

View File

@@ -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();

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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";
} }

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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);
}
}
}

View File

@@ -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()) {

View File

@@ -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

View File

@@ -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,

View File

@@ -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);
}
}

View File

@@ -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 =

View File

@@ -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();

View File

@@ -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;
}
}
} }

View File

@@ -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)

View File

@@ -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();
} }
} }
} }

View File

@@ -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);
} }
} }

View File

@@ -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);

View File

@@ -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);
} }
} }

View File

@@ -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));
} }

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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) {

View File

@@ -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());
} }
} }

View File

@@ -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);
} }

View File

@@ -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));
} }

View File

@@ -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));
} }
} }

View File

@@ -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)));
} }
} }

View File

@@ -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);
}
}

View File

@@ -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);
} }
} }

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
};
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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));
} }
} }

View File

@@ -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())) {

View File

@@ -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));

View File

@@ -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);

View File

@@ -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,

View File

@@ -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',

View File

@@ -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'

View File

@@ -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();

View File

@@ -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;
} }

View File

@@ -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();

View File

@@ -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

View File

@@ -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;
}
}
} }

View File

@@ -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.

View File

@@ -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);
} }

View File

@@ -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(

View File

@@ -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();
} }
} }

View File

@@ -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);

View File

@@ -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);
} }
}); });

View File

@@ -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() {

View File

@@ -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();
}
} }

View File

@@ -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();
}
} }

View File

@@ -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();

View File

@@ -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

View File

@@ -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) {

View File

@@ -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