Compare commits

..

115 Commits

Author SHA1 Message Date
akwizgran
7f8e96a654 Bump version numbers for beta release. 2018-03-07 17:10:28 +00:00
akwizgran
84e040605b Don't reuse the same ConnectionChooser every time.
This is a fix for a backporting mistake.
2018-03-07 16:47:08 +00:00
akwizgran
b0aa1517e5 Merge branch '283-key-exchange-connections' into 'maintenance-0.16'
Backport: Refactor key agreement connection choosing

See merge request akwizgran/briar!725
2018-03-07 14:06:09 +00:00
akwizgran
2ac9f567dc Merge branch '1164-store-bluetooth-properties' into 'maintenance-0.16'
Backport: Store Bluetooth address and UUID at first startup

See merge request akwizgran/briar!724
2018-03-07 14:05:08 +00:00
akwizgran
792cfd7d6f Merge branch '790-ask-before-turning-on-bluetooth' into 'maintenance-0.16'
Backport: Ask before turning on Bluetooth to add a contact

See merge request akwizgran/briar!723
2018-03-07 14:04:09 +00:00
Torsten Grote
2112d4fa7d Backport: Update translations 2018-03-07 09:57:42 -03:00
akwizgran
31ca04e070 Merge branch '1001-bluetooth-connects-to-contacts' into 'maintenance-0.16'
Backport: Don't make Bluetooth connections when configured not to

See merge request akwizgran/briar!722
2018-03-07 12:36:17 +00:00
akwizgran
9693a5cb93 Refactor key agreement connection choosing. 2018-03-07 12:27:05 +00:00
akwizgran
82266345ae Merge branch 'bluetooth-refactoring' into 'maintenance-0.16'
Backport: Factor shared Bluetooth code into superclass

See merge request akwizgran/briar!721
2018-03-07 12:24:38 +00:00
akwizgran
0942fe6053 Merge branch 'transport-indicators-no-buttons' into 'maintenance-0.16'
Backport: Prevent transport indicators from looking like buttons

See merge request akwizgran/briar!720
2018-03-07 12:16:16 +00:00
akwizgran
4a1f58705d Address review comments. 2018-03-07 12:10:31 +00:00
akwizgran
cfe0d9a656 Don't set running = true until properties have been loaded. 2018-03-07 12:10:31 +00:00
akwizgran
3cf61e7b3d Store Bluetooth address and UUID at first startup. 2018-03-07 12:10:31 +00:00
akwizgran
7bb7f8ad5b Fix import of wrong Immutable annotation. 2018-03-07 12:09:37 +00:00
akwizgran
fc50bb1c6c Ask before turning on Bluetooth to add a contact. 2018-03-07 12:09:37 +00:00
akwizgran
19be4d6edf Remove unnecessary executor calls. 2018-03-07 12:08:56 +00:00
akwizgran
b2e4de91a4 Don't make Bluetooth connections when configured not to. 2018-03-07 12:08:56 +00:00
akwizgran
9b184fe1d9 Merge branch '1174-link-click-crash' into 'maintenance-0.16'
Backport: Get unwrapped context when clicking links to prevent crash on Android 4

See merge request akwizgran/briar!719
2018-03-07 12:00:27 +00:00
akwizgran
f4ddc01641 Factor shared Bluetooth code into superclass. 2018-03-07 11:56:52 +00:00
akwizgran
08b63201d9 Merge branch 'fix-intro-fragment' into 'maintenance-0.16'
Backport: Fix uncentered intro fragment

See merge request akwizgran/briar!718
2018-03-07 11:52:22 +00:00
Torsten Grote
1c41181f1c Prevent transport indicators from looking like buttons 2018-03-07 11:50:18 +00:00
Torsten Grote
246b330b36 Passing in reference to FragmentManager when clicking links to prevent crash on Android 4 2018-03-07 11:39:24 +00:00
akwizgran
fd3e74cefc Merge branch '1168-startup-status-screen' into 'maintenance-0.16'
Backport: Show status message while opening and migrating DB

See merge request akwizgran/briar!717
2018-03-07 11:31:24 +00:00
goapunk
ef12191ec8 fix uncentered intro fragment
Signed-off-by: goapunk <noobie@goapunks.net>
2018-03-07 11:25:18 +00:00
akwizgran
a9fc310762 Merge branch '1176-startup-failure-crash' into 'maintenance-0.16'
Backport: Inject StartupFailureActivity to prevent NPE

See merge request akwizgran/briar!716
2018-03-07 11:14:49 +00:00
akwizgran
0a70c2d44d Add more lifecycle states, merge lifecycle events. 2018-03-07 11:07:28 +00:00
Torsten Grote
af1fc6f095 Start NavDrawerActivity only after database was opened and services started 2018-03-07 11:07:27 +00:00
Torsten Grote
21956f2627 Show a status screen when opening the database or applying migrations 2018-03-07 11:07:24 +00:00
akwizgran
55db6e524a Merge branch '346-qr-code-optimisations' into 'maintenance-0.16'
Backport: Improve QR code scanning on phones with high res cameras and slow CPUs

See merge request akwizgran/briar!715
2018-03-07 11:06:56 +00:00
Torsten Grote
dac3de24e7 Do not show splash screen when signed in 2018-03-07 11:05:17 +00:00
akwizgran
f93f41893e Inject StartupFailureActivity to prevent NPE. 2018-03-07 11:00:31 +00:00
akwizgran
7dacb43e01 Don't stop camera view when QR code is scanned. 2018-03-07 10:54:27 +00:00
akwizgran
6a962bad24 Use ConstraintLayout for intro fragment. 2018-03-07 10:47:37 +00:00
akwizgran
489c0154e9 Add javadoc links. 2018-03-07 10:47:37 +00:00
akwizgran
85dc99da72 Crop camera preview before looking for QR code. 2018-03-07 10:47:35 +00:00
akwizgran
ec808fd9f7 Add landscape layout for QR code fragment. 2018-03-07 10:45:49 +00:00
Torsten Grote
4c661cd4bb Merge branch '1154-fix-notification-light' into 'maintenance-0.16'
Backport: Fix notification light

See merge request akwizgran/briar!713
2018-03-06 18:17:46 +00:00
Torsten Grote
6324fb72a5 Fix notification light 2018-03-06 15:04:50 -03:00
akwizgran
d3aebc4aba Merge branch '1136-startup-failure-ux' into 'maintenance-0.16'
Backport: Improve UX for startup failures

See merge request akwizgran/briar!707
2018-02-28 10:26:48 +00:00
Torsten Grote
65c0e110c5 Improve UX for startup failures
Show a proper error message when database is too new or too old.
2018-02-26 14:49:01 -03:00
Torsten Grote
67aeb40d34 Backport ErrorFragment 2018-02-26 14:49:00 -03:00
akwizgran
8280b2e3b8 Inject StartupFailureActivity to prevent NPE. 2018-02-26 14:49:00 -03:00
akwizgran
4e0b9145c1 Merge branch '542-retransmission' into 'maintenance-0.16'
Backport: Don't poll for retransmission

See merge request akwizgran/briar!703
2018-02-22 12:45:48 +00:00
akwizgran
0ad4f2f39b Don't poll for retransmission. 2018-02-22 12:36:33 +00:00
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
242 changed files with 7060 additions and 8707 deletions

View File

@@ -1,23 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="H2 Performance Test" type="AndroidJUnit" factoryName="Android JUnit">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="bramble-core" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" value="org.briarproject.bramble.db" />
<option name="MAIN_CLASS_NAME" value="org.briarproject.bramble.db.H2DatabasePerformanceTest" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<envs />
<patterns />
<method />
</configuration>
</component>

View File

@@ -1,23 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="HyperSQL Performance Test" type="AndroidJUnit" factoryName="Android JUnit">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<module name="bramble-core" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" value="org.briarproject.bramble.db" />
<option name="MAIN_CLASS_NAME" value="org.briarproject.bramble.db.HyperSqlDatabasePerformanceTest" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<envs />
<patterns />
<method />
</configuration>
</component>

View File

@@ -12,8 +12,8 @@ android {
defaultConfig {
minSdkVersion 14
targetSdkVersion 26
versionCode 1700
versionName "0.17.0"
versionCode 1619
versionName "0.16.19"
consumerProguardFiles 'proguard-rules.txt'
}
@@ -43,7 +43,6 @@ dependencyVerification {
'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.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.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',

View File

@@ -8,10 +8,6 @@
-dontwarn dagger.**
-dontnote dagger.**
-keep class net.i2p.crypto.eddsa.** { *; }
-keep class org.whispersystems.curve25519.** { *; }
-dontwarn sun.misc.Unsafe
-dontnote com.google.common.**

View File

@@ -6,7 +6,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
@@ -20,15 +19,10 @@ import javax.annotation.Nullable;
import static android.content.Context.CONNECTIVITY_SERVICE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.wifi.WifiManager.EXTRA_WIFI_STATE;
import static java.util.logging.Level.INFO;
import static org.briarproject.bramble.util.AndroidUtils.logNetworkState;
@NotNullByDefault
class AndroidLanTcpPlugin extends LanTcpPlugin {
private static final String WIFI_AP_STATE_ACTION =
"android.net.wifi.WIFI_AP_STATE_CHANGED";
private static final Logger LOG =
Logger.getLogger(AndroidLanTcpPlugin.class.getName());
@@ -50,11 +44,8 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
running = true;
// Register to receive network status events
networkStateReceiver = new NetworkStateReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(CONNECTIVITY_ACTION);
filter.addAction(WIFI_AP_STATE_ACTION);
IntentFilter filter = new IntentFilter(CONNECTIVITY_ACTION);
appContext.registerReceiver(networkStateReceiver, filter);
if (LOG.isLoggable(INFO)) logNetworkState(appContext, LOG);
}
@Override
@@ -70,27 +61,10 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
@Override
public void onReceive(Context ctx, Intent i) {
if (!running) return;
if (LOG.isLoggable(INFO)) {
if (CONNECTIVITY_ACTION.equals(i.getAction())) {
LOG.info("Connectivity change");
Bundle extras = i.getExtras();
if (extras != null) {
LOG.info("Extras:");
for (String key : extras.keySet())
LOG.info("\t" + key + ": " + extras.get(key));
}
} else if (WIFI_AP_STATE_ACTION.equals(i.getAction())) {
int state = i.getIntExtra(EXTRA_WIFI_STATE, 0);
if (state == 13) LOG.info("Wifi AP enabled");
else LOG.info("Wifi AP state " + state);
}
logNetworkState(appContext, LOG);
}
Object o = ctx.getSystemService(CONNECTIVITY_SERVICE);
ConnectivityManager cm = (ConnectivityManager) o;
NetworkInfo net = cm.getActiveNetworkInfo();
if (net != null && net.getType() == TYPE_WIFI
&& net.isConnected()) {
if (net != null && net.getType() == TYPE_WIFI && net.isConnected()) {
LOG.info("Connected to Wi-Fi");
if (socket == null || socket.isClosed()) bind();
} else {

View File

@@ -716,7 +716,8 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
boolean online = net != null && net.isConnected();
boolean wifi = online && net.getType() == TYPE_WIFI;
String country = locationUtils.getCurrentCountry();
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(country);
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(
country);
Settings s = callback.getSettings();
int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_ALWAYS);

View File

@@ -1,41 +1,18 @@
package org.briarproject.bramble.util;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.provider.Settings;
import java.io.File;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import static android.content.Context.CONNECTIVITY_SERVICE;
import static android.content.Context.MODE_PRIVATE;
import static android.content.Context.WIFI_SERVICE;
import static android.os.Build.VERSION.SDK_INT;
import static java.net.NetworkInterface.getNetworkInterfaces;
import static java.util.Collections.list;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.StringUtils.ipToString;
import static org.briarproject.bramble.util.StringUtils.toHexString;
@SuppressLint("HardwareIds")
public class AndroidUtils {
// Fake Bluetooth address returned by BluetoothAdapter on API 23 and later
@@ -46,7 +23,7 @@ public class AndroidUtils {
@SuppressWarnings("deprecation")
public static Collection<String> getSupportedArchitectures() {
List<String> abis = new ArrayList<>();
if (SDK_INT >= 21) {
if (Build.VERSION.SDK_INT >= 21) {
abis.addAll(Arrays.asList(Build.SUPPORTED_ABIS));
} else {
abis.add(Build.CPU_ABI);
@@ -90,123 +67,4 @@ public class AndroidUtils {
public static File getReportDir(Context ctx) {
return ctx.getDir(STORED_REPORTS, MODE_PRIVATE);
}
public static void logNetworkState(Context ctx, Logger logger) {
if (!logger.isLoggable(INFO)) return;
Object o = ctx.getSystemService(CONNECTIVITY_SERVICE);
if (o == null) throw new AssertionError();
ConnectivityManager cm = (ConnectivityManager) o;
o = ctx.getApplicationContext().getSystemService(WIFI_SERVICE);
if (o == null) throw new AssertionError();
WifiManager wm = (WifiManager) o;
StringBuilder s = new StringBuilder();
logWifiInfo(s, wm.getConnectionInfo());
logNetworkInfo(s, cm.getActiveNetworkInfo(), true);
if (SDK_INT >= 21) {
for (Network network : cm.getAllNetworks())
logNetworkInfo(s, cm.getNetworkInfo(network), false);
} else {
for (NetworkInfo info : cm.getAllNetworkInfo())
logNetworkInfo(s, info, false);
}
try {
for (NetworkInterface iface : list(getNetworkInterfaces()))
logNetworkInterface(s, iface);
} catch (SocketException e) {
logger.log(WARNING, e.toString(), e);
}
logger.log(INFO, s.toString());
}
private static void logWifiInfo(StringBuilder s, @Nullable WifiInfo info) {
if (info == null) {
s.append("Wifi info: null\n");
return;
}
s.append("Wifi info:\n");
s.append("\tSSID: ").append(info.getSSID()).append("\n");
s.append("\tBSSID: ").append(info.getBSSID()).append("\n");
s.append("\tMAC address: ").append(info.getMacAddress()).append("\n");
s.append("\tIP address: ")
.append(ipToString(info.getIpAddress())).append("\n");
s.append("\tSupplicant state: ")
.append(info.getSupplicantState()).append("\n");
s.append("\tNetwork ID: ").append(info.getNetworkId()).append("\n");
s.append("\tLink speed: ").append(info.getLinkSpeed()).append("\n");
s.append("\tRSSI: ").append(info.getRssi()).append("\n");
if (info.getHiddenSSID()) s.append("\tHidden SSID\n");
if (SDK_INT >= 21)
s.append("\tFrequency: ").append(info.getFrequency()).append("\n");
}
private static void logNetworkInfo(StringBuilder s,
@Nullable NetworkInfo info, boolean active) {
if (info == null) {
if (active) s.append("Active network info: null\n");
else s.append("Network info: null\n");
return;
}
if (active) s.append("Active network info:\n");
else s.append("Network info:\n");
s.append("\tType: ").append(info.getTypeName())
.append(" (").append(info.getType()).append(")\n");
s.append("\tSubtype: ").append(info.getSubtypeName())
.append(" (").append(info.getSubtype()).append(")\n");
s.append("\tState: ").append(info.getState()).append("\n");
s.append("\tDetailed state: ")
.append(info.getDetailedState()).append("\n");
s.append("\tReason: ").append(info.getReason()).append("\n");
s.append("\tExtra info: ").append(info.getExtraInfo()).append("\n");
if (info.isAvailable()) s.append("\tAvailable\n");
if (info.isConnected()) s.append("\tConnected\n");
if (info.isConnectedOrConnecting())
s.append("\tConnected or connecting\n");
if (info.isFailover()) s.append("\tFailover\n");
if (info.isRoaming()) s.append("\tRoaming\n");
}
private static void logNetworkInterface(StringBuilder s,
NetworkInterface iface) throws SocketException {
s.append("Network interface:\n");
s.append("\tName: ").append(iface.getName()).append("\n");
s.append("\tDisplay name: ")
.append(iface.getDisplayName()).append("\n");
s.append("\tHardware address: ")
.append(hexOrNull(iface.getHardwareAddress())).append("\n");
if (iface.isLoopback()) s.append("\tLoopback\n");
if (iface.isPointToPoint()) s.append("\tPoint-to-point\n");
if (iface.isVirtual()) s.append("\tVirtual\n");
if (iface.isUp()) s.append("\tUp\n");
if (SDK_INT >= 19)
s.append("\tIndex: ").append(iface.getIndex()).append("\n");
for (InterfaceAddress addr : iface.getInterfaceAddresses()) {
s.append("\tInterface address:\n");
logInetAddress(s, addr.getAddress());
s.append("\t\tPrefix length: ")
.append(addr.getNetworkPrefixLength()).append("\n");
}
}
private static void logInetAddress(StringBuilder s, InetAddress addr) {
s.append("\t\tAddress: ")
.append(hexOrNull(addr.getAddress())).append("\n");
s.append("\t\tHost address: ")
.append(addr.getHostAddress()).append("\n");
if (addr.isLoopbackAddress()) s.append("\t\tLoopback\n");
if (addr.isLinkLocalAddress()) s.append("\t\tLink-local\n");
if (addr.isSiteLocalAddress()) s.append("\t\tSite-local\n");
if (addr.isAnyLocalAddress()) s.append("\t\tAny local (wildcard)\n");
if (addr.isMCNodeLocal()) s.append("\t\tMulticast node-local\n");
if (addr.isMCLinkLocal()) s.append("\t\tMulticast link-local\n");
if (addr.isMCSiteLocal()) s.append("\t\tMulticast site-local\n");
if (addr.isMCOrgLocal()) s.append("\t\tMulticast org-local\n");
if (addr.isMCGlobal()) s.append("\t\tMulticast global\n");
}
@Nullable
private static String hexOrNull(@Nullable byte[] b) {
return b == null ? null : toHexString(b);
}
}

View File

@@ -1,101 +0,0 @@
package org.briarproject.bramble.api;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import javax.annotation.concurrent.NotThreadSafe;
@NotThreadSafe
@NotNullByDefault
public class Multiset<T> {
private final Map<T, Integer> map = new HashMap<>();
private int total = 0;
/**
* Returns how many items the multiset contains in total.
*/
public int getTotal() {
return total;
}
/**
* Returns how many unique items the multiset contains.
*/
public int getUnique() {
return map.size();
}
/**
* Returns how many of the given item the multiset contains.
*/
public int getCount(T t) {
Integer count = map.get(t);
return count == null ? 0 : count;
}
/**
* Adds the given item to the multiset and returns how many of the item
* the multiset now contains.
*/
public int add(T t) {
Integer count = map.get(t);
if (count == null) count = 0;
map.put(t, count + 1);
total++;
return count + 1;
}
/**
* Removes the given item from the multiset and returns how many of the
* item the multiset now contains.
* @throws NoSuchElementException if the item is not in the multiset.
*/
public int remove(T t) {
Integer count = map.get(t);
if (count == null) throw new NoSuchElementException();
if (count == 1) map.remove(t);
else map.put(t, count - 1);
total--;
return count - 1;
}
/**
* Removes all occurrences of the given item from the multiset.
*/
public int removeAll(T t) {
Integer count = map.remove(t);
if (count == null) return 0;
total -= count;
return count;
}
/**
* Returns true if the multiset contains any occurrences of the given item.
*/
public boolean contains(T t) {
return map.containsKey(t);
}
/**
* Removes all items from the multiset.
*/
public void clear() {
map.clear();
total = 0;
}
/**
* Returns the set of unique items the multiset contains. The returned set
* is unmodifiable.
*/
public Set<T> keySet() {
return Collections.unmodifiableSet(map.keySet());
}
}

View File

@@ -1,9 +0,0 @@
package org.briarproject.bramble.api;
import java.io.IOException;
/**
* An exception that indicates an unrecoverable version mismatch.
*/
public class UnsupportedVersionException extends IOException {
}

View File

@@ -5,7 +5,6 @@ import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
@@ -94,13 +93,10 @@ public interface ClientHelper {
BdfList toList(Message m) throws FormatException;
BdfList toList(Author a);
byte[] sign(String label, BdfList toSign, byte[] privateKey)
throws FormatException, GeneralSecurityException;
void verifySignature(String label, byte[] sig, byte[] publicKey,
BdfList signed) throws FormatException, GeneralSecurityException;
Author parseAndValidateAuthor(BdfList author) throws FormatException;
}

View File

@@ -12,19 +12,18 @@ public interface ContactGroupFactory {
/**
* 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.
*/
Group createContactGroup(ClientId clientId, int clientVersion,
Contact contact);
Group createContactGroup(ClientId clientId, Contact contact);
/**
* Creates a group for the given client to share between the given authors
* identified by their AuthorIds.
*/
Group createContactGroup(ClientId clientId, int clientVersion,
AuthorId authorId1, AuthorId authorId2);
Group createContactGroup(ClientId clientId, AuthorId authorId1,
AuthorId authorId2);
}

View File

@@ -12,32 +12,6 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
@NotNullByDefault
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.
*/

View File

@@ -1,13 +1,11 @@
package org.briarproject.bramble.api.crypto;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.transport.TransportKeys;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import javax.annotation.Nullable;
@NotNullByDefault
public interface CryptoComponent {
SecretKey generateSecretKey();
@@ -25,46 +23,127 @@ public interface CryptoComponent {
KeyParser getMessageKeyParser();
/**
* Derives another secret key from the given secret key.
*
* @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
* Derives a stream header key from the given master secret.
* @param alice whether the key is for use by Alice or Bob.
*/
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
* corresponding private keys.
* <p/>
* Part of BQP.
*
* @param label a namespaced label indicating the purpose of this shared
* secret, to prevent it from being repurposed or colliding with a shared
* secret derived for another purpose
* @param theirPublicKey the public key of the remote party
* @param ourKeyPair the key pair of the local party
* @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 deriveSharedSecret(String label, PublicKey theirPublicKey,
KeyPair ourKeyPair, byte[]... inputs)
throws GeneralSecurityException;
SecretKey deriveSharedSecret(byte[] theirPublicKey, KeyPair ourKeyPair,
boolean alice) throws GeneralSecurityException;
/**
* Signs the given byte[] with the given private key.
* Derives the content of a confirmation record.
* <p/>
* Part of BQP.
*
* @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
* @param sharedSecret the common shared secret
* @param theirPayload the commit payload from the remote party
* @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)
throws GeneralSecurityException;
/**
* Verifies that the given signature is valid for the signed data
* and the given public key.
* Verifies that the given signature is valid for the signedData
* and the given publicKey.
*
* @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
* @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 verify(String label, byte[] signedData, byte[] publicKey,
@@ -74,22 +153,23 @@ public interface CryptoComponent {
* Returns the hash of the given inputs. The inputs are unambiguously
* combined by prefixing each input with its length.
*
* @param label a namespaced label indicating the purpose of this hash, to
* prevent it from being repurposed or colliding with a hash created for
* another purpose
* @param label A label specific to this hash to ensure that hashes
* calculated for distinct purposes don't collide.
*/
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
* given inputs. The inputs are unambiguously combined by prefixing each
* 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
@@ -105,7 +185,6 @@ public interface CryptoComponent {
* given password. Returns null if the ciphertext cannot be decrypted and
* authenticated (for example, if the password is wrong).
*/
@Nullable
byte[] decryptWithPassword(byte[] ciphertext, String password);
/**

View File

@@ -1,19 +0,0 @@
package org.briarproject.bramble.api.crypto;
public interface CryptoConstants {
/**
* The maximum length of an agreement public key in bytes.
*/
int MAX_AGREEMENT_PUBLIC_KEY_BYTES = 32;
/**
* The maximum length of a signature public key in bytes.
*/
int MAX_SIGNATURE_PUBLIC_KEY_BYTES = 32;
/**
* The maximum length of a signature in bytes.
*/
int MAX_SIGNATURE_BYTES = 64;
}

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,11 @@
package org.briarproject.bramble.api.data;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.IOException;
@NotNullByDefault
public interface ObjectReader<T> {
T readObject(BdfReader r) throws IOException;
}

View File

@@ -259,30 +259,31 @@ public interface DatabaseComponent {
Collection<LocalAuthor> getLocalAuthors(Transaction txn) throws DbException;
/**
* Returns the IDs of any messages that need to be validated.
* Returns the IDs of any messages that need to be validated by the given
* client.
* <p/>
* Read-only.
*/
Collection<MessageId> getMessagesToValidate(Transaction txn)
Collection<MessageId> getMessagesToValidate(Transaction txn, ClientId c)
throws DbException;
/**
* Returns the IDs of any messages that are pending delivery due to
* dependencies on other messages.
* Returns the IDs of any messages that are valid but pending delivery due
* to dependencies on other messages for the given client.
* <p/>
* Read-only.
*/
Collection<MessageId> getPendingMessages(Transaction txn)
Collection<MessageId> getPendingMessages(Transaction txn, ClientId c)
throws DbException;
/**
* Returns the IDs of any messages that have shared dependents but have
* not yet been shared themselves.
* Returns the IDs of any messages from the given client
* that have a shared dependent, but are still not shared themselves.
* <p/>
* Read-only.
*/
Collection<MessageId> getMessagesToShare(Transaction txn)
throws DbException;
Collection<MessageId> getMessagesToShare(Transaction txn,
ClientId c) throws DbException;
/**
* Returns the message with the given ID, in serialised form, or null if

View File

@@ -1,13 +1,11 @@
package org.briarproject.bramble.api.identity;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.util.StringUtils;
import java.io.UnsupportedEncodingException;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
/**
* A pseudonym for a user.
*/
@@ -19,25 +17,20 @@ public class Author {
NONE, ANONYMOUS, UNKNOWN, UNVERIFIED, VERIFIED, OURSELVES
}
/**
* The current version of the author structure.
*/
public static final int FORMAT_VERSION = 1;
private final AuthorId id;
private final int formatVersion;
private final String name;
private final byte[] publicKey;
public Author(AuthorId id, int formatVersion, String name,
byte[] publicKey) {
int nameLength = StringUtils.toUtf8(name).length;
if (nameLength == 0 || nameLength > MAX_AUTHOR_NAME_LENGTH)
throw new IllegalArgumentException();
if (publicKey.length == 0 || publicKey.length > MAX_PUBLIC_KEY_LENGTH)
public Author(AuthorId id, String name, byte[] publicKey) {
int length;
try {
length = name.getBytes("UTF-8").length;
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
if (length == 0 || length > AuthorConstants.MAX_AUTHOR_NAME_LENGTH)
throw new IllegalArgumentException();
this.id = id;
this.formatVersion = formatVersion;
this.name = name;
this.publicKey = publicKey;
}
@@ -49,13 +42,6 @@ public class Author {
return id;
}
/**
* Returns the version of the author structure used to create the author.
*/
public int getFormatVersion() {
return formatVersion;
}
/**
* Returns the author's name.
*/

View File

@@ -1,8 +1,5 @@
package org.briarproject.bramble.api.identity;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_BYTES;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_PUBLIC_KEY_BYTES;
public interface AuthorConstants {
/**
@@ -11,14 +8,26 @@ public interface AuthorConstants {
int MAX_AUTHOR_NAME_LENGTH = 50;
/**
* The maximum length of a public key in bytes. This applies to the
* signature algorithm used by the current {@link Author format version}.
* The maximum length of a public key in bytes.
* <p>
* Public keys use SEC1 format: 0x04 x y, where x and y are unsigned
* big-endian integers.
* <p>
* For a 256-bit elliptic curve, the maximum length is 2 * 256 / 8 + 1.
*/
int MAX_PUBLIC_KEY_LENGTH = MAX_SIGNATURE_PUBLIC_KEY_BYTES;
int MAX_PUBLIC_KEY_LENGTH = 65;
/**
* The maximum length of a signature in bytes. This applies to the
* signature algorithm used by the current {@link Author format version}.
* The maximum length of a signature in bytes.
* <p>
* A signature is an ASN.1 DER sequence containing two integers, r and s.
* The format is 0x30 len1 0x02 len2 r 0x02 len3 s, where len1 is
* len(0x02 len2 r 0x02 len3 s) as a DER length, len2 is len(r) as a DER
* length, len3 is len(s) as a DER length, and r and s are signed
* big-endian integers of minimal length.
* <p>
* For a 256-bit elliptic curve, the lengths are one byte each, so the
* maximum length is 2 * 256 / 8 + 8.
*/
int MAX_SIGNATURE_LENGTH = MAX_SIGNATURE_BYTES;
int MAX_SIGNATURE_LENGTH = 72;
}

View File

@@ -5,27 +5,8 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
public interface AuthorFactory {
/**
* Creates an author with the current format version and the given name and
* public key.
*/
Author createAuthor(String name, byte[] publicKey);
/**
* Creates an author with the given format version, name and public key.
*/
Author createAuthor(int formatVersion, String name, byte[] publicKey);
/**
* Creates a local author with the current format version and the given
* name and keys.
*/
LocalAuthor createLocalAuthor(String name, byte[] publicKey,
byte[] privateKey);
/**
* Creates a local author with the given format version, name and keys.
*/
LocalAuthor createLocalAuthor(int formatVersion, String name,
byte[] publicKey, byte[] privateKey);
}

View File

@@ -16,7 +16,7 @@ public class AuthorId extends UniqueId {
/**
* 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) {
super(id);

View File

@@ -14,9 +14,9 @@ public class LocalAuthor extends Author {
private final byte[] privateKey;
private final long created;
public LocalAuthor(AuthorId id, int formatVersion, String name,
byte[] publicKey, byte[] privateKey, long created) {
super(id, formatVersion, name, publicKey);
public LocalAuthor(AuthorId id, String name, byte[] publicKey,
byte[] privateKey, long created) {
super(id, name, publicKey);
this.privateKey = privateKey;
this.created = created;
}

View File

@@ -3,9 +3,9 @@ package org.briarproject.bramble.api.keyagreement;
public interface KeyAgreementConstants {
/**
* The current version of the BQP protocol. Version number 89 is reserved.
* The current version of the BQP protocol.
*/
byte PROTOCOL_VERSION = 4;
byte PROTOCOL_VERSION = 2;
/**
* The length of the record header in bytes.
@@ -22,10 +22,7 @@ public interface KeyAgreementConstants {
*/
int COMMIT_LENGTH = 16;
/**
* The connection timeout in milliseconds.
*/
long CONNECTION_TIMEOUT = 20 * 1000;
long CONNECTION_TIMEOUT = 20 * 1000; // Milliseconds
/**
* The transport identifier for Bluetooth.
@@ -36,16 +33,4 @@ public interface KeyAgreementConstants {
* The transport identifier for LAN.
*/
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");
/**
* The current version of the transport property client.
*/
int CLIENT_VERSION = 0;
/**
* Stores the given properties received while adding a contact - they will
* be superseded by any properties synced from the contact.

View File

@@ -36,8 +36,4 @@ public class ClientId implements Comparable<ClientId> {
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 {
/**
* 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.
*/
public static final String LABEL = "org.briarproject.bramble/GROUP_ID";
public static final String LABEL = "org.briarproject.bramble.GROUP_ID";
public GroupId(byte[] id) {
super(id);

View File

@@ -16,7 +16,7 @@ public class MessageId extends UniqueId {
/**
* 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) {
super(id);

View File

@@ -7,7 +7,7 @@ public interface TransportConstants {
/**
* The current version of the transport protocol.
*/
int PROTOCOL_VERSION = 4;
int PROTOCOL_VERSION = 3;
/**
* The length of the pseudo-random tag in bytes.
@@ -80,32 +80,4 @@ public interface TransportConstants {
* The size of the reordering window.
*/
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

@@ -146,14 +146,6 @@ public class StringUtils {
return s.toString();
}
public static String ipToString(int ip) {
int ip1 = ip & 0xFF;
int ip2 = (ip >> 8) & 0xFF;
int ip3 = (ip >> 16) & 0xFF;
int ip4 = (ip >> 24) & 0xFF;
return ip1 + "." + ip2 + "." + ip3 + "." + ip4;
}
public static String getRandomString(int length) {
char[] c = new char[length];
for (int i = 0; i < length; i++)

View File

@@ -13,14 +13,9 @@ import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.util.IoUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
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;
@@ -68,8 +63,7 @@ public class TestUtils {
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
byte[] privateKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
long created = System.currentTimeMillis();
return new LocalAuthor(id, FORMAT_VERSION, name, publicKey, privateKey,
created);
return new LocalAuthor(id, name, publicKey, privateKey, created);
}
public static Author getAuthor() {
@@ -80,7 +74,7 @@ public class TestUtils {
AuthorId id = new AuthorId(getRandomId());
String name = getRandomString(nameLength);
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
return new Author(id, FORMAT_VERSION, name, publicKey);
return new Author(id, name, publicKey);
}
public static Group getGroup(ClientId clientId) {
@@ -105,38 +99,4 @@ public class TestUtils {
long timestamp = System.currentTimeMillis();
return new Message(id, groupId, timestamp, raw);
}
public static double getMedian(Collection<? extends Number> samples) {
int size = samples.size();
if (size == 0) throw new IllegalArgumentException();
List<Double> sorted = new ArrayList<>(size);
for (Number n : samples) sorted.add(n.doubleValue());
Collections.sort(sorted);
if (size % 2 == 1) return sorted.get(size / 2);
double low = sorted.get(size / 2 - 1), high = sorted.get(size / 2);
return (low + high) / 2;
}
public static double getMean(Collection<? extends Number> samples) {
if (samples.isEmpty()) throw new IllegalArgumentException();
double sum = 0;
for (Number n : samples) sum += n.doubleValue();
return sum / samples.size();
}
public static double getVariance(Collection<? extends Number> samples) {
if (samples.size() < 2) throw new IllegalArgumentException();
double mean = getMean(samples);
double sumSquareDiff = 0;
for (Number n : samples) {
double diff = n.doubleValue() - mean;
sumSquareDiff += diff * diff;
}
return sumSquareDiff / (samples.size() - 1);
}
public static double getStandardDeviation(
Collection<? extends Number> samples) {
return Math.sqrt(getVariance(samples));
}
}

View File

@@ -9,15 +9,12 @@ apply plugin: 'witness'
dependencies {
implementation project(path: ':bramble-api', configuration: 'default')
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 'net.i2p.crypto:eddsa:0.2.0'
implementation 'org.whispersystems:curve25519-java:0.4.1'
apt 'com.google.dagger:dagger-compiler:2.0.2'
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 "org.jmock:jmock:2.8.2"
testImplementation "org.jmock:jmock-junit4:2.8.2"
@@ -40,21 +37,18 @@ dependencyVerification {
'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',
'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:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
'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.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
'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-legacy:2.8.2:jmock-legacy-2.8.2.jar:f2b985a5c08a9edb7f37612330c058809da3f6a6d63ce792426ebf8ff0d6d31b',
'org.jmock:jmock-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760',
'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16',
'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80',
'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

@@ -15,8 +15,6 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
@@ -34,12 +32,7 @@ import java.util.Map.Entry;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
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.MESSAGE_HEADER_LENGTH;
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
@Immutable
@NotNullByDefault
@@ -58,14 +51,12 @@ class ClientHelperImpl implements ClientHelper {
private final MetadataParser metadataParser;
private final MetadataEncoder metadataEncoder;
private final CryptoComponent crypto;
private final AuthorFactory authorFactory;
@Inject
ClientHelperImpl(DatabaseComponent db, MessageFactory messageFactory,
BdfReaderFactory bdfReaderFactory,
BdfWriterFactory bdfWriterFactory, MetadataParser metadataParser,
MetadataEncoder metadataEncoder, CryptoComponent crypto,
AuthorFactory authorFactory) {
MetadataEncoder metadataEncoder, CryptoComponent crypto) {
this.db = db;
this.messageFactory = messageFactory;
this.bdfReaderFactory = bdfReaderFactory;
@@ -73,7 +64,6 @@ class ClientHelperImpl implements ClientHelper {
this.metadataParser = metadataParser;
this.metadataEncoder = metadataEncoder;
this.crypto = crypto;
this.authorFactory = authorFactory;
}
@Override
@@ -351,11 +341,6 @@ class ClientHelperImpl implements ClientHelper {
raw.length - MESSAGE_HEADER_LENGTH);
}
@Override
public BdfList toList(Author a) {
return BdfList.of(a.getFormatVersion(), a.getName(), a.getPublicKey());
}
@Override
public byte[] sign(String label, BdfList toSign, byte[] privateKey)
throws FormatException, GeneralSecurityException {
@@ -370,16 +355,4 @@ class ClientHelperImpl implements ClientHelper {
}
}
@Override
public Author parseAndValidateAuthor(BdfList author)
throws FormatException {
checkSize(author, 3);
int formatVersion = author.getLong(0).intValue();
if (formatVersion != FORMAT_VERSION) throw new FormatException();
String name = author.getString(1);
checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH);
byte[] publicKey = author.getRaw(2);
checkLength(publicKey, 1, MAX_PUBLIC_KEY_LENGTH);
return authorFactory.createAuthor(formatVersion, name, publicKey);
}
}

View File

@@ -2,6 +2,14 @@ package org.briarproject.bramble.client;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.client.ContactGroupFactory;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.data.BdfReaderFactory;
import org.briarproject.bramble.api.data.BdfWriterFactory;
import org.briarproject.bramble.api.data.MetadataEncoder;
import org.briarproject.bramble.api.data.MetadataParser;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.sync.GroupFactory;
import org.briarproject.bramble.api.sync.MessageFactory;
import dagger.Module;
import dagger.Provides;
@@ -10,14 +18,19 @@ import dagger.Provides;
public class ClientModule {
@Provides
ClientHelper provideClientHelper(ClientHelperImpl clientHelper) {
return clientHelper;
ClientHelper provideClientHelper(DatabaseComponent db,
MessageFactory messageFactory, BdfReaderFactory bdfReaderFactory,
BdfWriterFactory bdfWriterFactory, MetadataParser metadataParser,
MetadataEncoder metadataEncoder, CryptoComponent cryptoComponent) {
return new ClientHelperImpl(db, messageFactory, bdfReaderFactory,
bdfWriterFactory, metadataParser, metadataEncoder,
cryptoComponent);
}
@Provides
ContactGroupFactory provideContactGroupFactory(
ContactGroupFactoryImpl contactGroupFactory) {
return contactGroupFactory;
ContactGroupFactory provideContactGroupFactory(GroupFactory groupFactory,
ClientHelper clientHelper) {
return new ContactGroupFactoryImpl(groupFactory, clientHelper);
}
}

View File

@@ -32,25 +32,23 @@ class ContactGroupFactoryImpl implements ContactGroupFactory {
}
@Override
public Group createLocalGroup(ClientId clientId, int clientVersion) {
return groupFactory.createGroup(clientId, clientVersion,
LOCAL_GROUP_DESCRIPTOR);
public Group createLocalGroup(ClientId clientId) {
return groupFactory.createGroup(clientId, LOCAL_GROUP_DESCRIPTOR);
}
@Override
public Group createContactGroup(ClientId clientId, int clientVersion,
Contact contact) {
public Group createContactGroup(ClientId clientId, Contact contact) {
AuthorId local = contact.getLocalAuthorId();
AuthorId remote = contact.getAuthor().getId();
byte[] descriptor = createGroupDescriptor(local, remote);
return groupFactory.createGroup(clientId, clientVersion, descriptor);
return groupFactory.createGroup(clientId, descriptor);
}
@Override
public Group createContactGroup(ClientId clientId, int clientVersion,
AuthorId authorId1, AuthorId authorId2) {
public Group createContactGroup(ClientId clientId, AuthorId authorId1,
AuthorId authorId2) {
byte[] descriptor = createGroupDescriptor(authorId1, authorId2);
return groupFactory.createGroup(clientId, clientVersion, descriptor);
return groupFactory.createGroup(clientId, descriptor);
}
private byte[] createGroupDescriptor(AuthorId local, AuthorId remote) {

View File

@@ -43,7 +43,6 @@ import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
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_SIGNATURE_LENGTH;
@@ -142,10 +141,8 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
}
// Derive the header keys for the transport streams
SecretKey aliceHeaderKey = crypto.deriveKey(ALICE_KEY_LABEL,
masterSecret, new byte[] {PROTOCOL_VERSION});
SecretKey bobHeaderKey = crypto.deriveKey(BOB_KEY_LABEL, masterSecret,
new byte[] {PROTOCOL_VERSION});
SecretKey aliceHeaderKey = crypto.deriveHeaderKey(masterSecret, true);
SecretKey bobHeaderKey = crypto.deriveHeaderKey(masterSecret, false);
// Create the readers
InputStream streamReader =
@@ -159,10 +156,8 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
BdfWriter w = bdfWriterFactory.createWriter(streamWriter);
// Derive the nonces to be signed
byte[] aliceNonce = crypto.mac(ALICE_NONCE_LABEL, masterSecret,
new byte[] {PROTOCOL_VERSION});
byte[] bobNonce = crypto.mac(BOB_NONCE_LABEL, masterSecret,
new byte[] {PROTOCOL_VERSION});
byte[] aliceNonce = crypto.deriveSignatureNonce(masterSecret, true);
byte[] bobNonce = crypto.deriveSignatureNonce(masterSecret, false);
// Exchange pseudonyms, signed nonces, and timestamps
long localTimestamp = clock.currentTimeMillis();
@@ -201,8 +196,8 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
try {
// Add the contact
ContactId contactId = addContact(remoteAuthor, timestamp,
remoteProperties);
ContactId contactId = addContact(remoteAuthor, masterSecret,
timestamp, alice, remoteProperties);
// Reuse the connection as a transport connection
connectionManager.manageOutgoingConnection(contactId, transportId,
conn);
@@ -228,7 +223,6 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
// Write the name, public key and signature
w.writeListStart();
w.writeLong(localAuthor.getFormatVersion());
w.writeString(localAuthor.getName());
w.writeRaw(localAuthor.getPublicKey());
w.writeRaw(sig);
@@ -238,16 +232,11 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
private Author receivePseudonym(BdfReader r, byte[] nonce)
throws GeneralSecurityException, IOException {
// Read the format version, name, public key and signature
// Read the name, public key and signature
r.readListStart();
int formatVersion = (int) r.readLong();
if (formatVersion != FORMAT_VERSION) throw new FormatException();
String name = r.readString(MAX_AUTHOR_NAME_LENGTH);
if (name.isEmpty()) throw new FormatException();
byte[] publicKey = r.readRaw(MAX_PUBLIC_KEY_LENGTH);
if (publicKey.length == 0) throw new FormatException();
byte[] sig = r.readRaw(MAX_SIGNATURE_LENGTH);
if (sig.length == 0) throw new FormatException();
r.readListEnd();
LOG.info("Received pseudonym");
// Verify the signature
@@ -256,7 +245,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
LOG.info("Invalid signature");
throw new GeneralSecurityException();
}
return authorFactory.createAuthor(formatVersion, name, publicKey);
return authorFactory.createAuthor(name, publicKey);
}
private void sendTimestamp(BdfWriter w, long timestamp)
@@ -305,15 +294,15 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
return remote;
}
private ContactId addContact(Author remoteAuthor, long timestamp,
private ContactId addContact(Author remoteAuthor, SecretKey master,
long timestamp, boolean alice,
Map<TransportId, TransportProperties> remoteProperties)
throws DbException {
ContactId contactId;
Transaction txn = db.startTransaction(false);
try {
contactId = contactManager.addContact(txn, remoteAuthor,
localAuthor.getId(), masterSecret, timestamp, alice,
true, true);
localAuthor.getId(), master, timestamp, alice, true, true);
transportPropertyManager.addRemoteProperties(txn, contactId,
remoteProperties);
db.commitTransaction(txn);
@@ -323,7 +312,8 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
return contactId;
}
private void tryToClose(DuplexTransportConnection conn, boolean exception) {
private void tryToClose(DuplexTransportConnection conn,
boolean exception) {
try {
LOG.info("Closing connection");
conn.getReader().dispose(exception, true);

View File

@@ -0,0 +1,547 @@
package org.briarproject.bramble.crypto;
/*
The BLAKE2 cryptographic hash function was designed by Jean-
Philippe Aumasson, Samuel Neves, Zooko Wilcox-O'Hearn, and Christian
Winnerlein.
Reference Implementation and Description can be found at: https://blake2.net/
RFC: https://tools.ietf.org/html/rfc7693
This implementation does not support the Tree Hashing Mode.
For unkeyed hashing, developers adapting BLAKE2 to ASN.1 - based
message formats SHOULD use the OID tree at x = 1.3.6.1.4.1.1722.12.2.
Algorithm | Target | Collision | Hash | Hash ASN.1 |
Identifier | Arch | Security | nn | OID Suffix |
---------------+--------+-----------+------+------------+
id-blake2s128 | 32-bit | 2**64 | 16 | x.2.4 |
id-blake2s160 | 32-bit | 2**80 | 20 | x.2.5 |
id-blake2s224 | 32-bit | 2**112 | 28 | x.2.7 |
id-blake2s256 | 32-bit | 2**128 | 32 | x.2.8 |
---------------+--------+-----------+------+------------+
Based on the BouncyCastle implementation of BLAKE2b. License:
Copyright (c) 2000 - 2015 The Legion of the Bouncy Castle Inc.
(http://www.bouncycastle.org)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
import org.spongycastle.crypto.ExtendedDigest;
import org.spongycastle.util.Arrays;
/**
* Implementation of the cryptographic hash function BLAKE2s.
* <p/>
* BLAKE2s offers a built-in keying mechanism to be used directly
* for authentication ("Prefix-MAC") rather than a HMAC construction.
* <p/>
* BLAKE2s offers a built-in support for a salt for randomized hashing
* and a personal string for defining a unique hash function for each application.
* <p/>
* BLAKE2s is optimized for 32-bit platforms and produces digests of any size
* between 1 and 32 bytes.
*/
public class Blake2sDigest implements ExtendedDigest {
/** BLAKE2s Initialization Vector **/
private static final int blake2s_IV[] =
// Produced from the square root of primes 2, 3, 5, 7, 11, 13, 17, 19.
// The same as SHA-256 IV.
{
0x6a09e667, 0xbb67ae85, 0x3c6ef372,
0xa54ff53a, 0x510e527f, 0x9b05688c,
0x1f83d9ab, 0x5be0cd19
};
/** Message word permutations **/
private static final byte[][] blake2s_sigma =
{
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
{ 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 },
{ 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 },
{ 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 },
{ 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 },
{ 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 },
{ 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 },
{ 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 },
{ 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 },
{ 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 }
};
private static final int ROUNDS = 10; // to use for Catenas H'
private static final int BLOCK_LENGTH_BYTES = 64;// bytes
// General parameters:
private int digestLength = 32; // 1- 32 bytes
private int keyLength = 0; // 0 - 32 bytes for keyed hashing for MAC
private byte[] salt = null;
private byte[] personalization = null;
private byte[] key = null;
// Tree hashing parameters:
// Because this class does not implement the Tree Hashing Mode,
// these parameters can be treated as constants (see init() function)
/*
* private int fanout = 1; // 0-255
* private int depth = 1; // 1 - 255
* private int leafLength= 0;
* private long nodeOffset = 0L;
* private int nodeDepth = 0;
* private int innerHashLength = 0;
*/
/**
* Whenever this buffer overflows, it will be processed in the compress()
* function. For performance issues, long messages will not use this buffer.
*/
private byte[] buffer = null;
/** Position of last inserted byte **/
private int bufferPos = 0;// a value from 0 up to BLOCK_LENGTH_BYTES
/** Internal state, in the BLAKE2 paper it is called v **/
private int[] internalState = new int[16];
/** State vector, in the BLAKE2 paper it is called h **/
private int[] chainValue = null;
// counter (counts bytes): Length up to 2^64 are supported
/** holds least significant bits of counter **/
private int t0 = 0;
/** holds most significant bits of counter **/
private int t1 = 0;
/** finalization flag, for last block: ~0 **/
private int f0 = 0;
// For Tree Hashing Mode, not used here:
// private long f1 = 0L; // finalization flag, for last node: ~0L
/**
* BLAKE2s-256 for hashing.
*/
public Blake2sDigest() {
this(256);
}
public Blake2sDigest(Blake2sDigest digest) {
this.bufferPos = digest.bufferPos;
this.buffer = Arrays.clone(digest.buffer);
this.keyLength = digest.keyLength;
this.key = Arrays.clone(digest.key);
this.digestLength = digest.digestLength;
this.chainValue = Arrays.clone(digest.chainValue);
this.personalization = Arrays.clone(digest.personalization);
}
/**
* BLAKE2s for hashing.
*
* @param digestBits the desired digest length in bits. Must be one of
* [128, 160, 224, 256].
*/
public Blake2sDigest(int digestBits) {
if (digestBits != 128 && digestBits != 160 &&
digestBits != 224 && digestBits != 256) {
throw new IllegalArgumentException(
"BLAKE2s digest restricted to one of [128, 160, 224, 256]");
}
buffer = new byte[BLOCK_LENGTH_BYTES];
keyLength = 0;
digestLength = digestBits / 8;
init();
}
/**
* BLAKE2s for authentication ("Prefix-MAC mode").
* <p/>
* After calling the doFinal() method, the key will remain to be used for
* further computations of this instance. The key can be overwritten using
* the clearKey() method.
*
* @param key a key up to 32 bytes or null
*/
public Blake2sDigest(byte[] key) {
buffer = new byte[BLOCK_LENGTH_BYTES];
if (key != null) {
if (key.length > 32) {
throw new IllegalArgumentException(
"Keys > 32 are not supported");
}
this.key = new byte[key.length];
System.arraycopy(key, 0, this.key, 0, key.length);
keyLength = key.length;
System.arraycopy(key, 0, buffer, 0, key.length);
bufferPos = BLOCK_LENGTH_BYTES; // zero padding
}
digestLength = 32;
init();
}
/**
* BLAKE2s with key, required digest length, salt and personalization.
* <p/>
* After calling the doFinal() method, the key, the salt and the personal
* string will remain and might be used for further computations with this
* instance. The key can be overwritten using the clearKey() method, the
* salt (pepper) can be overwritten using the clearSalt() method.
*
* @param key a key up to 32 bytes or null
* @param digestBytes from 1 up to 32 bytes
* @param salt 8 bytes or null
* @param personalization 8 bytes or null
*/
public Blake2sDigest(byte[] key, int digestBytes, byte[] salt,
byte[] personalization) {
buffer = new byte[BLOCK_LENGTH_BYTES];
if (digestBytes < 1 || digestBytes > 32) {
throw new IllegalArgumentException(
"Invalid digest length (required: 1 - 32)");
}
digestLength = digestBytes;
if (salt != null) {
if (salt.length != 8) {
throw new IllegalArgumentException(
"Salt length must be exactly 8 bytes");
}
this.salt = new byte[8];
System.arraycopy(salt, 0, this.salt, 0, salt.length);
}
if (personalization != null) {
if (personalization.length != 8) {
throw new IllegalArgumentException(
"Personalization length must be exactly 8 bytes");
}
this.personalization = new byte[8];
System.arraycopy(personalization, 0, this.personalization, 0,
personalization.length);
}
if (key != null) {
if (key.length > 32) {
throw new IllegalArgumentException(
"Keys > 32 bytes are not supported");
}
this.key = new byte[key.length];
System.arraycopy(key, 0, this.key, 0, key.length);
keyLength = key.length;
System.arraycopy(key, 0, buffer, 0, key.length);
bufferPos = BLOCK_LENGTH_BYTES; // zero padding
}
init();
}
// initialize chainValue
private void init() {
if (chainValue == null) {
chainValue = new int[8];
chainValue[0] = blake2s_IV[0]
^ (digestLength | (keyLength << 8) | 0x1010000);
// 0x1010000 = ((fanout << 16) | (depth << 24));
// with fanout = 1; depth = 0;
chainValue[1] = blake2s_IV[1];// ^ leafLength; with leafLength = 0;
chainValue[2] = blake2s_IV[2];// ^ nodeOffset; with nodeOffset = 0;
chainValue[3] = blake2s_IV[3];// ^ ( (nodeOffset << 32) |
// (nodeDepth << 16) | (innerHashLength << 24) );
// with nodeDepth = 0; innerHashLength = 0;
chainValue[4] = blake2s_IV[4];
chainValue[5] = blake2s_IV[5];
if (salt != null) {
chainValue[4] ^= (bytes2int(salt, 0));
chainValue[5] ^= (bytes2int(salt, 4));
}
chainValue[6] = blake2s_IV[6];
chainValue[7] = blake2s_IV[7];
if (personalization != null) {
chainValue[6] ^= (bytes2int(personalization, 0));
chainValue[7] ^= (bytes2int(personalization, 4));
}
}
}
private void initializeInternalState() {
// initialize v:
System.arraycopy(chainValue, 0, internalState, 0, chainValue.length);
System.arraycopy(blake2s_IV, 0, internalState, chainValue.length, 4);
internalState[12] = t0 ^ blake2s_IV[4];
internalState[13] = t1 ^ blake2s_IV[5];
internalState[14] = f0 ^ blake2s_IV[6];
internalState[15] = blake2s_IV[7];// ^ f1 with f1 = 0
}
/**
* Update the message digest with a single byte.
*
* @param b the input byte to be entered.
*/
public void update(byte b) {
int remainingLength; // left bytes of buffer
// process the buffer if full else add to buffer:
remainingLength = BLOCK_LENGTH_BYTES - bufferPos;
if (remainingLength == 0) { // full buffer
t0 += BLOCK_LENGTH_BYTES;
if (t0 == 0) { // if message > 2^32
t1++;
}
compress(buffer, 0);
Arrays.fill(buffer, (byte)0);// clear buffer
buffer[0] = b;
bufferPos = 1;
} else {
buffer[bufferPos] = b;
bufferPos++;
}
}
/**
* Update the message digest with a block of bytes.
*
* @param message the byte array containing the data.
* @param offset the offset into the byte array where the data starts.
* @param len the length of the data.
*/
public void update(byte[] message, int offset, int len) {
if (message == null || len == 0)
return;
int remainingLength = 0; // left bytes of buffer
if (bufferPos != 0) { // commenced, incomplete buffer
// complete the buffer:
remainingLength = BLOCK_LENGTH_BYTES - bufferPos;
if (remainingLength < len) { // full buffer + at least 1 byte
System.arraycopy(message, offset, buffer, bufferPos,
remainingLength);
t0 += BLOCK_LENGTH_BYTES;
if (t0 == 0) { // if message > 2^32
t1++;
}
compress(buffer, 0);
bufferPos = 0;
Arrays.fill(buffer, (byte) 0);// clear buffer
} else {
System.arraycopy(message, offset, buffer, bufferPos, len);
bufferPos += len;
return;
}
}
// process blocks except last block (also if last block is full)
int messagePos;
int blockWiseLastPos = offset + len - BLOCK_LENGTH_BYTES;
for (messagePos = offset + remainingLength;
messagePos < blockWiseLastPos;
messagePos += BLOCK_LENGTH_BYTES) { // block wise 64 bytes
// without buffer:
t0 += BLOCK_LENGTH_BYTES;
if (t0 == 0) {
t1++;
}
compress(message, messagePos);
}
// fill the buffer with left bytes, this might be a full block
System.arraycopy(message, messagePos, buffer, 0, offset + len
- messagePos);
bufferPos += offset + len - messagePos;
}
/**
* Close the digest, producing the final digest value. The doFinal() call
* leaves the digest reset. Key, salt and personal string remain.
*
* @param out the array the digest is to be copied into.
* @param outOffset the offset into the out array the digest is to start at.
*/
public int doFinal(byte[] out, int outOffset) {
f0 = 0xFFFFFFFF;
t0 += bufferPos;
// bufferPos may be < 64, so (t0 == 0) does not work
// for 2^32 < message length > 2^32 - 63
if ((t0 < 0) && (bufferPos > -t0)) {
t1++;
}
compress(buffer, 0);
Arrays.fill(buffer, (byte) 0);// Holds eventually the key if input is null
Arrays.fill(internalState, 0);
for (int i = 0; i < chainValue.length && (i * 4 < digestLength); i++) {
byte[] bytes = int2bytes(chainValue[i]);
if (i * 4 < digestLength - 4) {
System.arraycopy(bytes, 0, out, outOffset + i * 4, 4);
} else {
System.arraycopy(bytes, 0, out, outOffset + i * 4,
digestLength - (i * 4));
}
}
Arrays.fill(chainValue, 0);
reset();
return digestLength;
}
/**
* Reset the digest back to its initial state. The key, the salt and the
* personal string will remain for further computations.
*/
public void reset() {
bufferPos = 0;
f0 = 0;
t0 = 0;
t1 = 0;
chainValue = null;
if (key != null) {
Arrays.fill(buffer, (byte) 0);
System.arraycopy(key, 0, buffer, 0, key.length);
bufferPos = BLOCK_LENGTH_BYTES; // zero padding
}
init();
}
private void compress(byte[] message, int messagePos) {
initializeInternalState();
int[] m = new int[16];
for (int j = 0; j < 16; j++) {
m[j] = bytes2int(message, messagePos + j * 4);
}
for (int round = 0; round < ROUNDS; round++) {
// G apply to columns of internalState:m[blake2s_sigma[round][2 *
// blockPos]] /+1
G(m[blake2s_sigma[round][0]], m[blake2s_sigma[round][1]], 0, 4, 8,
12);
G(m[blake2s_sigma[round][2]], m[blake2s_sigma[round][3]], 1, 5, 9,
13);
G(m[blake2s_sigma[round][4]], m[blake2s_sigma[round][5]], 2, 6, 10,
14);
G(m[blake2s_sigma[round][6]], m[blake2s_sigma[round][7]], 3, 7, 11,
15);
// G apply to diagonals of internalState:
G(m[blake2s_sigma[round][8]], m[blake2s_sigma[round][9]], 0, 5, 10,
15);
G(m[blake2s_sigma[round][10]], m[blake2s_sigma[round][11]], 1, 6,
11, 12);
G(m[blake2s_sigma[round][12]], m[blake2s_sigma[round][13]], 2, 7,
8, 13);
G(m[blake2s_sigma[round][14]], m[blake2s_sigma[round][15]], 3, 4,
9, 14);
}
// update chain values:
for (int offset = 0; offset < chainValue.length; offset++) {
chainValue[offset] = chainValue[offset] ^ internalState[offset]
^ internalState[offset + 8];
}
}
private void G(int m1, int m2, int posA, int posB, int posC, int posD) {
internalState[posA] = internalState[posA] + internalState[posB] + m1;
internalState[posD] = rotr32(internalState[posD] ^ internalState[posA],
16);
internalState[posC] = internalState[posC] + internalState[posD];
internalState[posB] = rotr32(internalState[posB] ^ internalState[posC],
12);
internalState[posA] = internalState[posA] + internalState[posB] + m2;
internalState[posD] = rotr32(internalState[posD] ^ internalState[posA],
8);
internalState[posC] = internalState[posC] + internalState[posD];
internalState[posB] = rotr32(internalState[posB] ^ internalState[posC],
7);
}
private int rotr32(int x, int rot) {
return x >>> rot | (x << (32 - rot));
}
// convert one int value in byte array
// little-endian byte order!
private byte[] int2bytes(int intValue) {
return new byte[] {
(byte) intValue, (byte) (intValue >> 8),
(byte) (intValue >> 16), (byte) (intValue >> 24)
};
}
// little-endian byte order!
private int bytes2int(byte[] byteArray, int offset) {
return (((int) byteArray[offset] & 0xFF)
| (((int) byteArray[offset + 1] & 0xFF) << 8)
| (((int) byteArray[offset + 2] & 0xFF) << 16)
| (((int) byteArray[offset + 3] & 0xFF) << 24));
}
/**
* Return the algorithm name.
*
* @return the algorithm name
*/
public String getAlgorithmName() {
return "BLAKE2s";
}
/**
* Return the size in bytes of the digest produced by this message digest.
*
* @return the size in bytes of the digest produced by this message digest.
*/
public int getDigestSize() {
return digestLength;
}
/**
* Return the size in bytes of the internal buffer the digest applies its
* compression function to.
*
* @return byte length of the digest's internal buffer.
*/
public int getByteLength() {
return BLOCK_LENGTH_BYTES;
}
/**
* Overwrite the key if it is no longer used (zeroization).
*/
public void clearKey() {
if (key != null) {
Arrays.fill(key, (byte) 0);
Arrays.fill(buffer, (byte) 0);
}
}
/**
* Overwrite the salt (pepper) if it is secret and no longer used
* (zeroization).
*/
public void clearSalt() {
if (salt != null) {
Arrays.fill(salt, (byte) 0);
}
}
}

View File

@@ -1,59 +1,107 @@
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.KeyPair;
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.crypto.SecretKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
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.StringUtils;
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
import org.spongycastle.crypto.CipherParameters;
import org.spongycastle.crypto.CryptoException;
import org.spongycastle.crypto.Digest;
import org.spongycastle.crypto.digests.Blake2bDigest;
import org.whispersystems.curve25519.Curve25519;
import org.whispersystems.curve25519.Curve25519KeyPair;
import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
import org.spongycastle.crypto.digests.SHA256Digest;
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
import org.spongycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.spongycastle.crypto.params.ECKeyGenerationParameters;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.crypto.params.KeyParameter;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.Security;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
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.util.ByteUtils.INT_16_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;
@NotNullByDefault
class CryptoComponentImpl implements CryptoComponent {
private static final Logger LOG =
Logger.getLogger(CryptoComponentImpl.class.getName());
private static final int AGREEMENT_KEY_PAIR_BITS = 256;
private static final int SIGNATURE_KEY_PAIR_BITS = 256;
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_FORMAT_SCRYPT = 0;
private static final int PBKDF_TARGET_MILLIS = 500;
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 PasswordBasedKdf passwordBasedKdf;
private final Curve25519 curve25519;
private final KeyPairGenerator signatureKeyPairGenerator;
private final ECKeyPairGenerator agreementKeyPairGenerator;
private final ECKeyPairGenerator signatureKeyPairGenerator;
private final KeyParser agreementKeyParser, signatureKeyParser;
private final MessageEncrypter messageEncrypter;
@Inject
CryptoComponentImpl(SecureRandomProvider secureRandomProvider,
PasswordBasedKdf passwordBasedKdf) {
CryptoComponentImpl(SecureRandomProvider secureRandomProvider) {
if (LOG.isLoggable(INFO)) {
SecureRandom defaultSecureRandom = new SecureRandom();
String name = defaultSecureRandom.getProvider().getName();
@@ -73,13 +121,16 @@ class CryptoComponentImpl implements CryptoComponent {
}
}
secureRandom = new SecureRandom();
this.passwordBasedKdf = passwordBasedKdf;
curve25519 = Curve25519.getInstance("java");
signatureKeyPairGenerator = new KeyPairGenerator();
signatureKeyPairGenerator.initialize(SIGNATURE_KEY_PAIR_BITS,
secureRandom);
agreementKeyParser = new Curve25519KeyParser();
signatureKeyParser = new EdKeyParser();
ECKeyGenerationParameters params = new ECKeyGenerationParameters(
PARAMETERS, secureRandom);
agreementKeyPairGenerator = new ECKeyPairGenerator();
agreementKeyPairGenerator.init(params);
signatureKeyPairGenerator = new ECKeyPairGenerator();
signatureKeyPairGenerator.init(params);
agreementKeyParser = new Sec1KeyParser(PARAMETERS,
AGREEMENT_KEY_PAIR_BITS);
signatureKeyParser = new Sec1KeyParser(PARAMETERS,
SIGNATURE_KEY_PAIR_BITS);
messageEncrypter = new MessageEncrypter(secureRandom);
}
@@ -123,17 +174,16 @@ class CryptoComponentImpl implements CryptoComponent {
// Package access for testing
byte[] performRawKeyAgreement(PrivateKey priv, PublicKey pub)
throws GeneralSecurityException {
if (!(priv instanceof Curve25519PrivateKey))
if (!(priv instanceof Sec1PrivateKey))
throw new IllegalArgumentException();
if (!(pub instanceof Curve25519PublicKey))
if (!(pub instanceof Sec1PublicKey))
throw new IllegalArgumentException();
ECPrivateKeyParameters ecPriv = ((Sec1PrivateKey) priv).getKey();
ECPublicKeyParameters ecPub = ((Sec1PublicKey) pub).getKey();
long now = System.currentTimeMillis();
byte[] secret = curve25519.calculateAgreement(pub.getEncoded(),
priv.getEncoded());
// If the shared secret is all zeroes, the public key is invalid
byte allZero = 0;
for (byte b : secret) allZero |= b;
if (allZero == 0) throw new GeneralSecurityException();
ECDHCBasicAgreement agreement = new ECDHCBasicAgreement();
agreement.init(ecPriv);
byte[] secret = agreement.calculateAgreement(ecPub).toByteArray();
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Deriving shared secret took " + duration + " ms");
@@ -142,10 +192,18 @@ class CryptoComponentImpl implements CryptoComponent {
@Override
public KeyPair generateAgreementKeyPair() {
Curve25519KeyPair keyPair = curve25519.generateKeyPair();
PublicKey pub = new Curve25519PublicKey(keyPair.getPublicKey());
PrivateKey priv = new Curve25519PrivateKey(keyPair.getPrivateKey());
return new KeyPair(pub, priv);
AsymmetricCipherKeyPair keyPair =
agreementKeyPairGenerator.generateKeyPair();
// Return a wrapper that uses the SEC 1 encoding
ECPublicKeyParameters ecPublicKey =
(ECPublicKeyParameters) keyPair.getPublic();
PublicKey publicKey = new Sec1PublicKey(ecPublicKey
);
ECPrivateKeyParameters ecPrivateKey =
(ECPrivateKeyParameters) keyPair.getPrivate();
PrivateKey privateKey = new Sec1PrivateKey(ecPrivateKey,
AGREEMENT_KEY_PAIR_BITS);
return new KeyPair(publicKey, privateKey);
}
@Override
@@ -155,12 +213,17 @@ class CryptoComponentImpl implements CryptoComponent {
@Override
public KeyPair generateSignatureKeyPair() {
java.security.KeyPair keyPair =
AsymmetricCipherKeyPair keyPair =
signatureKeyPairGenerator.generateKeyPair();
EdDSAPublicKey edPublicKey = (EdDSAPublicKey) keyPair.getPublic();
PublicKey publicKey = new EdPublicKey(edPublicKey.getAbyte());
EdDSAPrivateKey edPrivateKey = (EdDSAPrivateKey) keyPair.getPrivate();
PrivateKey privateKey = new EdPrivateKey(edPrivateKey.getSeed());
// Return a wrapper that uses the SEC 1 encoding
ECPublicKeyParameters ecPublicKey =
(ECPublicKeyParameters) keyPair.getPublic();
PublicKey publicKey = new Sec1PublicKey(ecPublicKey
);
ECPrivateKeyParameters ecPrivateKey =
(ECPrivateKeyParameters) keyPair.getPrivate();
PrivateKey privateKey = new Sec1PrivateKey(ecPrivateKey,
SIGNATURE_KEY_PAIR_BITS);
return new KeyPair(publicKey, privateKey);
}
@@ -175,47 +238,205 @@ class CryptoComponentImpl implements CryptoComponent {
}
@Override
public SecretKey deriveKey(String label, SecretKey k, byte[]... inputs) {
byte[] mac = mac(label, k, inputs);
if (mac.length != SecretKey.LENGTH) throw new IllegalStateException();
return new SecretKey(mac);
public SecretKey deriveHeaderKey(SecretKey master,
boolean alice) {
return new SecretKey(macKdf(master, alice ? A_INVITE : B_INVITE));
}
@Override
public SecretKey deriveSharedSecret(String label, PublicKey theirPublicKey,
KeyPair ourKeyPair, byte[]... inputs)
throws GeneralSecurityException {
public SecretKey deriveMacKey(SecretKey master, boolean alice) {
return new SecretKey(macKdf(master, alice ? A_MAC : B_MAC));
}
@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();
byte[][] hashInputs = new byte[inputs.length + 1][];
hashInputs[0] = performRawKeyAgreement(ourPriv, theirPublicKey);
System.arraycopy(inputs, 0, hashInputs, 1, inputs.length);
byte[] hash = hash(label, hashInputs);
if (hash.length != SecretKey.LENGTH) throw new IllegalStateException();
return new SecretKey(hash);
PublicKey theirPub = agreementKeyParser.parsePublicKey(theirPublicKey);
byte[] raw = performRawKeyAgreement(ourPriv, theirPub);
byte[] alicePub, bobPub;
if (alice) {
alicePub = ourKeyPair.getPublic().getEncoded();
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
public byte[] sign(String label, byte[] toSign, byte[] privateKey)
throws GeneralSecurityException {
PrivateKey key = signatureKeyParser.parsePrivateKey(privateKey);
Signature sig = new EdSignature();
sig.initSign(key);
updateSignature(sig, label, toSign);
return sig.sign();
Signature signature = new SignatureImpl(secureRandom);
KeyParser keyParser = getSignatureKeyParser();
PrivateKey key = keyParser.parsePrivateKey(privateKey);
signature.initSign(key);
updateSignature(signature, label, toSign);
return signature.sign();
}
@Override
public boolean verify(String label, byte[] signedData, byte[] publicKey,
byte[] signature) throws GeneralSecurityException {
PublicKey key = signatureKeyParser.parsePublicKey(publicKey);
Signature sig = new EdSignature();
Signature sig = new SignatureImpl(secureRandom);
KeyParser keyParser = getSignatureKeyParser();
PublicKey key = keyParser.parsePublicKey(publicKey);
sig.initVerify(key);
updateSignature(sig, label, signedData);
return sig.verify(signature);
}
private void updateSignature(Signature signature, String label,
byte[] toSign) throws GeneralSecurityException {
byte[] toSign) {
byte[] labelBytes = StringUtils.toUtf8(label);
byte[] length = new byte[INT_32_BYTES];
ByteUtils.writeUint32(labelBytes.length, length, 0);
@@ -229,7 +450,7 @@ class CryptoComponentImpl implements CryptoComponent {
@Override
public byte[] hash(String label, byte[]... inputs) {
byte[] labelBytes = StringUtils.toUtf8(label);
Digest digest = new Blake2bDigest(256);
Digest digest = new Blake2sDigest();
byte[] length = new byte[INT_32_BYTES];
ByteUtils.writeUint32(labelBytes.length, length, 0);
digest.update(length, 0, length.length);
@@ -245,13 +466,14 @@ class CryptoComponentImpl implements CryptoComponent {
}
@Override
public byte[] mac(String label, SecretKey macKey, byte[]... inputs) {
byte[] labelBytes = StringUtils.toUtf8(label);
Digest mac = new Blake2bDigest(macKey.getBytes(), 32, null, null);
public int getHashLength() {
return HASH_SIZE;
}
@Override
public byte[] mac(SecretKey macKey, byte[]... inputs) {
Digest mac = new Blake2sDigest(macKey.getBytes());
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) {
ByteUtils.writeUint32(input.length, length, 0);
mac.update(length, 0, length.length);
@@ -270,33 +492,23 @@ class CryptoComponentImpl implements CryptoComponent {
byte[] salt = new byte[PBKDF_SALT_BYTES];
secureRandom.nextBytes(salt);
// Calibrate the KDF
int cost = passwordBasedKdf.chooseCostParameter();
int iterations = chooseIterationCount(PBKDF_TARGET_MILLIS);
// Derive the key from the password
SecretKey key = passwordBasedKdf.deriveKey(password, salt, cost);
SecretKey key = new SecretKey(pbkdf2(password, salt, iterations));
// Generate a random IV
byte[] iv = new byte[STORAGE_IV_BYTES];
secureRandom.nextBytes(iv);
// The output contains the format version, salt, cost parameter, IV,
// ciphertext and MAC
int outputLen = 1 + salt.length + INT_32_BYTES + iv.length
+ input.length + macBytes;
// The output contains the salt, iterations, IV, ciphertext and MAC
int outputLen = salt.length + INT_32_BYTES + iv.length + input.length
+ macBytes;
byte[] output = new byte[outputLen];
int outputOff = 0;
// Format version
output[outputOff] = PBKDF_FORMAT_SCRYPT;
outputOff++;
// Salt
System.arraycopy(salt, 0, output, outputOff, salt.length);
outputOff += salt.length;
// Cost parameter
ByteUtils.writeUint32(cost, output, outputOff);
outputOff += INT_32_BYTES;
// IV
System.arraycopy(iv, 0, output, outputOff, iv.length);
outputOff += iv.length;
System.arraycopy(salt, 0, output, 0, salt.length);
ByteUtils.writeUint32(iterations, output, salt.length);
System.arraycopy(iv, 0, output, salt.length + INT_32_BYTES, iv.length);
// Initialise the cipher and encrypt the plaintext
try {
cipher.init(true, key, iv);
int outputOff = salt.length + INT_32_BYTES + iv.length;
cipher.process(input, 0, input.length, output, outputOff);
return output;
} catch (GeneralSecurityException e) {
@@ -305,36 +517,22 @@ class CryptoComponentImpl implements CryptoComponent {
}
@Override
@Nullable
public byte[] decryptWithPassword(byte[] input, String password) {
AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher();
int macBytes = cipher.getMacBytes();
// The input contains the format version, salt, cost parameter, IV,
// ciphertext and MAC
if (input.length < 1 + PBKDF_SALT_BYTES + INT_32_BYTES
+ STORAGE_IV_BYTES + macBytes)
// The input contains the salt, iterations, IV, ciphertext and MAC
if (input.length < PBKDF_SALT_BYTES + INT_32_BYTES + STORAGE_IV_BYTES
+ macBytes)
return null; // Invalid input
int inputOff = 0;
// Format version
byte formatVersion = input[inputOff];
inputOff++;
if (formatVersion != PBKDF_FORMAT_SCRYPT)
return null; // Unknown format
// Salt
byte[] salt = new byte[PBKDF_SALT_BYTES];
System.arraycopy(input, inputOff, salt, 0, salt.length);
inputOff += salt.length;
// Cost parameter
long cost = ByteUtils.readUint32(input, inputOff);
inputOff += INT_32_BYTES;
if (cost < 2 || cost > Integer.MAX_VALUE)
return null; // Invalid cost parameter
// IV
System.arraycopy(input, 0, salt, 0, salt.length);
long iterations = ByteUtils.readUint32(input, salt.length);
if (iterations < 0 || iterations > Integer.MAX_VALUE)
return null; // Invalid iteration count
byte[] iv = new byte[STORAGE_IV_BYTES];
System.arraycopy(input, inputOff, iv, 0, iv.length);
inputOff += iv.length;
System.arraycopy(input, salt.length + INT_32_BYTES, iv, 0, iv.length);
// Derive the key from the password
SecretKey key = passwordBasedKdf.deriveKey(password, salt, (int) cost);
SecretKey key = new SecretKey(pbkdf2(password, salt, (int) iterations));
// Initialise the cipher
try {
cipher.init(false, key, iv);
@@ -343,6 +541,7 @@ class CryptoComponentImpl implements CryptoComponent {
}
// Try to decrypt the ciphertext (may be invalid)
try {
int inputOff = salt.length + INT_32_BYTES + iv.length;
int inputLen = input.length - inputOff;
byte[] output = new byte[inputLen - macBytes];
cipher.process(input, inputOff, inputLen, output, 0);
@@ -365,4 +564,88 @@ class CryptoComponentImpl implements CryptoComponent {
public String asciiArmour(byte[] b, int 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
private byte[] pbkdf2(String password, byte[] salt, int iterations) {
byte[] utf8 = StringUtils.toUtf8(password);
Digest digest = new SHA256Digest();
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(digest);
gen.init(utf8, salt, iterations);
int keyLengthInBits = SecretKey.LENGTH * 8;
CipherParameters p = gen.generateDerivedParameters(keyLengthInBits);
return ((KeyParameter) p).getKey();
}
// Package access for testing
int chooseIterationCount(int targetMillis) {
List<Long> quickSamples = new ArrayList<>(PBKDF_SAMPLES);
List<Long> slowSamples = new ArrayList<>(PBKDF_SAMPLES);
long iterationNanos = 0, initNanos = 0;
while (iterationNanos <= 0 || initNanos <= 0) {
// Sample the running time with one iteration and two iterations
for (int i = 0; i < PBKDF_SAMPLES; i++) {
quickSamples.add(sampleRunningTime(1));
slowSamples.add(sampleRunningTime(2));
}
// Calculate the iteration time and the initialisation time
long quickMedian = median(quickSamples);
long slowMedian = median(slowSamples);
iterationNanos = slowMedian - quickMedian;
initNanos = quickMedian - iterationNanos;
if (LOG.isLoggable(INFO)) {
LOG.info("Init: " + initNanos + ", iteration: "
+ iterationNanos);
}
}
long targetNanos = targetMillis * 1000L * 1000L;
long iterations = (targetNanos - initNanos) / iterationNanos;
if (LOG.isLoggable(INFO)) LOG.info("Target iterations: " + iterations);
if (iterations < 1) return 1;
if (iterations > Integer.MAX_VALUE) return Integer.MAX_VALUE;
return (int) iterations;
}
private long sampleRunningTime(int iterations) {
byte[] password = {'p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
byte[] salt = new byte[PBKDF_SALT_BYTES];
int keyLengthInBits = SecretKey.LENGTH * 8;
long start = System.nanoTime();
Digest digest = new SHA256Digest();
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(digest);
gen.init(password, salt, iterations);
gen.generateDerivedParameters(keyLengthInBits);
return System.nanoTime() - start;
}
private long median(List<Long> list) {
int size = list.size();
if (size == 0) throw new IllegalArgumentException();
Collections.sort(list);
if (size % 2 == 1) return list.get(size / 2);
return list.get(size / 2 - 1) + list.get(size / 2) / 2;
}
}

View File

@@ -3,11 +3,9 @@ package org.briarproject.bramble.crypto;
import org.briarproject.bramble.TimeLoggingExecutor;
import org.briarproject.bramble.api.crypto.CryptoComponent;
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.StreamDecrypterFactory;
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.system.SecureRandomProvider;
@@ -67,9 +65,8 @@ public class CryptoModule {
@Provides
@Singleton
CryptoComponent provideCryptoComponent(
SecureRandomProvider secureRandomProvider,
ScryptKdf passwordBasedKdf) {
return new CryptoComponentImpl(secureRandomProvider, passwordBasedKdf);
SecureRandomProvider secureRandomProvider) {
return new CryptoComponentImpl(secureRandomProvider);
}
@Provides
@@ -77,12 +74,6 @@ public class CryptoModule {
return new PasswordStrengthEstimatorImpl();
}
@Provides
TransportCrypto provideTransportCrypto(
TransportCryptoImpl transportCrypto) {
return transportCrypto;
}
@Provides
StreamDecrypterFactory provideStreamDecrypterFactory(
Provider<AuthenticatedCipher> cipherProvider) {
@@ -90,17 +81,9 @@ public class CryptoModule {
}
@Provides
StreamEncrypterFactory provideStreamEncrypterFactory(
CryptoComponent crypto, TransportCrypto transportCrypto,
StreamEncrypterFactory provideStreamEncrypterFactory(CryptoComponent crypto,
Provider<AuthenticatedCipher> cipherProvider) {
return new StreamEncrypterFactoryImpl(crypto, transportCrypto,
cipherProvider);
}
@Provides
KeyAgreementCrypto provideKeyAgreementCrypto(
KeyAgreementCryptoImpl keyAgreementCrypto) {
return keyAgreementCrypto;
return new StreamEncrypterFactoryImpl(crypto, cipherProvider);
}
@Provides

View File

@@ -1,35 +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 Curve25519KeyParser implements KeyParser {
@Override
public PublicKey parsePublicKey(byte[] encodedKey)
throws GeneralSecurityException {
if (encodedKey.length != 32) throw new GeneralSecurityException();
return new Curve25519PublicKey(encodedKey);
}
@Override
public PrivateKey parsePrivateKey(byte[] encodedKey)
throws GeneralSecurityException {
if (encodedKey.length != 32) throw new GeneralSecurityException();
return new Curve25519PrivateKey(clamp(encodedKey));
}
static byte[] clamp(byte[] b) {
byte[] clamped = new byte[32];
System.arraycopy(b, 0, clamped, 0, 32);
clamped[0] &= 248;
clamped[31] &= 127;
clamped[31] |= 64;
return clamped;
}
}

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 Curve25519PrivateKey extends Bytes implements PrivateKey {
Curve25519PrivateKey(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 Curve25519PublicKey extends Bytes implements PublicKey {
Curve25519PublicKey(byte[] bytes) {
super(bytes);
}
@Override
public byte[] getEncoded() {
return getBytes();
}
}

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

@@ -0,0 +1,32 @@
package org.briarproject.bramble.crypto;
import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves;
import org.spongycastle.asn1.x9.X9ECParameters;
import org.spongycastle.crypto.params.ECDomainParameters;
import org.spongycastle.math.ec.ECCurve;
import org.spongycastle.math.ec.ECMultiplier;
import org.spongycastle.math.ec.ECPoint;
import org.spongycastle.math.ec.MontgomeryLadderMultiplier;
import java.math.BigInteger;
/**
* Parameters for curve brainpoolp256r1 - see RFC 5639.
*/
class EllipticCurveConstants {
static final ECDomainParameters PARAMETERS;
static {
// Start with the default implementation of the curve
X9ECParameters x9 = TeleTrusTNamedCurves.getByName("brainpoolp256r1");
// Use a constant-time multiplier
ECMultiplier monty = new MontgomeryLadderMultiplier();
ECCurve curve = x9.getCurve().configure().setMultiplier(monty).create();
BigInteger gX = x9.getG().getAffineXCoord().toBigInteger();
BigInteger gY = x9.getG().getAffineYCoord().toBigInteger();
ECPoint g = curve.createPoint(gX, gY);
// Convert to ECDomainParameters using the new multiplier
PARAMETERS = new ECDomainParameters(curve, g, x9.getN(), x9.getH());
}
}

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

@@ -1,10 +0,0 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.SecretKey;
interface PasswordBasedKdf {
int chooseCostParameter();
SecretKey deriveKey(String password, byte[] salt, int cost);
}

View File

@@ -1,62 +0,0 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.util.StringUtils;
import org.spongycastle.crypto.generators.SCrypt;
import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Level.INFO;
class ScryptKdf implements PasswordBasedKdf {
private static final Logger LOG =
Logger.getLogger(ScryptKdf.class.getName());
private static final int MIN_COST = 256; // Min parameter N
private static final int MAX_COST = 1024 * 1024; // Max parameter N
private static final int BLOCK_SIZE = 8; // Parameter r
private static final int PARALLELIZATION = 1; // Parameter p
private static final int TARGET_MS = 1000;
private final Clock clock;
@Inject
ScryptKdf(Clock clock) {
this.clock = clock;
}
@Override
public int chooseCostParameter() {
// Increase the cost from min to max while measuring performance
int cost = MIN_COST;
while (cost * 2 <= MAX_COST && measureDuration(cost) * 2 <= TARGET_MS)
cost *= 2;
if (LOG.isLoggable(INFO))
LOG.info("KDF cost parameter " + cost);
return cost;
}
private long measureDuration(int cost) {
byte[] password = new byte[16], salt = new byte[32];
long start = clock.currentTimeMillis();
SCrypt.generate(password, salt, cost, BLOCK_SIZE, PARALLELIZATION,
SecretKey.LENGTH);
return clock.currentTimeMillis() - start;
}
@Override
public SecretKey deriveKey(String password, byte[] salt, int cost) {
long start = System.currentTimeMillis();
byte[] passwordBytes = StringUtils.toUtf8(password);
SecretKey k = new SecretKey(SCrypt.generate(passwordBytes, salt, cost,
BLOCK_SIZE, PARALLELIZATION, SecretKey.LENGTH));
long duration = System.currentTimeMillis() - start;
if (LOG.isLoggable(INFO))
LOG.info("Deriving key from password took " + duration + " ms");
return k;
}
}

View File

@@ -22,25 +22,25 @@ interface Signature {
/**
* @see {@link java.security.Signature#update(byte)}
*/
void update(byte b) throws GeneralSecurityException;
void update(byte b);
/**
* @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)}
*/
void update(byte[] b, int off, int len) throws GeneralSecurityException;
void update(byte[] b, int off, int len);
/**
* @see {@link java.security.Signature#sign()}
*/
byte[] sign() throws GeneralSecurityException;
byte[] sign();
/**
* @see {@link java.security.Signature#verify(byte[])}
*/
boolean verify(byte[] signature) throws GeneralSecurityException;
boolean verify(byte[] signature);
}

View File

@@ -0,0 +1,90 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.spongycastle.crypto.Digest;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.crypto.params.ParametersWithRandom;
import org.spongycastle.crypto.signers.DSADigestSigner;
import org.spongycastle.crypto.signers.DSAKCalculator;
import org.spongycastle.crypto.signers.ECDSASigner;
import org.spongycastle.crypto.signers.HMacDSAKCalculator;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.util.logging.Logger;
import javax.annotation.concurrent.NotThreadSafe;
import static java.util.logging.Level.INFO;
@NotThreadSafe
@NotNullByDefault
class SignatureImpl implements Signature {
private static final Logger LOG =
Logger.getLogger(SignatureImpl.class.getName());
private final SecureRandom secureRandom;
private final DSADigestSigner signer;
SignatureImpl(SecureRandom secureRandom) {
this.secureRandom = secureRandom;
Digest digest = new Blake2sDigest();
DSAKCalculator calculator = new HMacDSAKCalculator(digest);
signer = new DSADigestSigner(new ECDSASigner(calculator), digest);
}
@Override
public void initSign(PrivateKey k) throws GeneralSecurityException {
if (!(k instanceof Sec1PrivateKey))
throw new IllegalArgumentException();
ECPrivateKeyParameters priv = ((Sec1PrivateKey) k).getKey();
signer.init(true, new ParametersWithRandom(priv, secureRandom));
}
@Override
public void initVerify(PublicKey k) throws GeneralSecurityException {
if (!(k instanceof Sec1PublicKey))
throw new IllegalArgumentException();
ECPublicKeyParameters pub = ((Sec1PublicKey) k).getKey();
signer.init(false, pub);
}
@Override
public void update(byte b) {
signer.update(b);
}
@Override
public void update(byte[] b) {
update(b, 0, b.length);
}
@Override
public void update(byte[] b, int off, int len) {
signer.update(b, off, len);
}
@Override
public byte[] sign() {
long now = System.currentTimeMillis();
byte[] signature = signer.generateSignature();
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Generating signature took " + duration + " ms");
return signature;
}
@Override
public boolean verify(byte[] signature) {
long now = System.currentTimeMillis();
boolean valid = signer.verifySignature(signature);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Verifying signature took " + duration + " ms");
return valid;
}
}

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.StreamEncrypter;
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.transport.StreamContext;
@@ -23,15 +22,12 @@ import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENG
class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
private final CryptoComponent crypto;
private final TransportCrypto transportCrypto;
private final Provider<AuthenticatedCipher> cipherProvider;
@Inject
StreamEncrypterFactoryImpl(CryptoComponent crypto,
TransportCrypto transportCrypto,
Provider<AuthenticatedCipher> cipherProvider) {
this.crypto = crypto;
this.transportCrypto = transportCrypto;
this.cipherProvider = cipherProvider;
}
@@ -41,8 +37,7 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
AuthenticatedCipher cipher = cipherProvider.get();
long streamNumber = ctx.getStreamNumber();
byte[] tag = new byte[TAG_LENGTH];
transportCrypto.encodeTag(tag, ctx.getTagKey(), PROTOCOL_VERSION,
streamNumber);
crypto.encodeTag(tag, ctx.getTagKey(), PROTOCOL_VERSION, streamNumber);
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
crypto.getSecureRandom().nextBytes(streamHeaderNonce);
SecretKey frameKey = crypto.generateSecretKey();

View File

@@ -1,136 +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 org.spongycastle.crypto.digests.Blake2bDigest;
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 Blake2bDigest(tagKey.getBytes(), 32, null, null);
// 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

@@ -97,12 +97,9 @@ interface Database<T> {
/**
* Stores a message.
*
* @param sender the contact from whom the message was received, or null
* if the message was created locally.
*/
void addMessage(T txn, Message m, State state, boolean shared,
@Nullable ContactId sender) throws DbException;
void addMessage(T txn, Message m, State state, boolean shared)
throws DbException;
/**
* Adds a dependency between two messages in the given group.
@@ -115,6 +112,16 @@ interface Database<T> {
*/
void addOfferedMessage(T txn, ContactId c, MessageId m) throws DbException;
/**
* Initialises the status of the given message with respect to the given
* contact.
*
* @param ack whether the message needs to be acknowledged.
* @param seen whether the contact has seen the message.
*/
void addStatus(T txn, ContactId c, MessageId m, boolean ack, boolean seen)
throws DbException;
/**
* Stores a transport.
*/
@@ -273,7 +280,7 @@ interface Database<T> {
* <p/>
* Read-only.
*/
Map<ContactId, Boolean> getGroupVisibility(T txn, GroupId g)
Collection<ContactId> getGroupVisibility(T txn, GroupId g)
throws DbException;
/**
@@ -424,27 +431,31 @@ interface Database<T> {
throws DbException;
/**
* Returns the IDs of any messages that need to be validated.
* Returns the IDs of any messages that need to be validated by the given
* client.
* <p/>
* Read-only.
*/
Collection<MessageId> getMessagesToValidate(T txn) throws DbException;
Collection<MessageId> getMessagesToValidate(T txn, ClientId c)
throws DbException;
/**
* Returns the IDs of any messages that are pending delivery due to
* dependencies on other messages.
* Returns the IDs of any messages that are still pending due to
* dependencies to other messages for the given client.
* <p/>
* Read-only.
*/
Collection<MessageId> getPendingMessages(T txn) throws DbException;
Collection<MessageId> getPendingMessages(T txn, ClientId c)
throws DbException;
/**
* Returns the IDs of any messages that have a shared dependent but have
* not yet been shared themselves.
* Returns the IDs of any messages from the given client
* that have a shared dependent, but are still not shared themselves.
* <p/>
* Read-only.
*/
Collection<MessageId> getMessagesToShare(T txn) throws DbException;
Collection<MessageId> getMessagesToShare(T txn, ClientId c)
throws DbException;
/**
* Returns the next time (in milliseconds since the Unix epoch) when a
@@ -573,6 +584,13 @@ interface Database<T> {
*/
void removeMessage(T txn, MessageId m) throws DbException;
/**
* Removes an offered message that was offered by the given contact, or
* returns false if there is no such message.
*/
boolean removeOfferedMessage(T txn, ContactId c, MessageId m)
throws DbException;
/**
* Removes the given offered messages that were offered by the given
* contact.
@@ -580,6 +598,12 @@ interface Database<T> {
void removeOfferedMessages(T txn, ContactId c,
Collection<MessageId> requested) throws DbException;
/**
* Removes the status of the given message with respect to the given
* contact.
*/
void removeStatus(T txn, ContactId c, MessageId m) throws DbException;
/**
* Removes a transport (and all associated state) from the database.
*/

View File

@@ -215,7 +215,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
if (!db.containsGroup(txn, m.getGroupId()))
throw new NoSuchGroupException();
if (!db.containsMessage(txn, m.getId())) {
db.addMessage(txn, m, DELIVERED, shared, null);
addMessage(txn, m, DELIVERED, shared, null);
transaction.attach(new MessageAddedEvent(m, null));
transaction.attach(new MessageStateChangedEvent(m.getId(), true,
DELIVERED));
@@ -224,6 +224,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.mergeMessageMetadata(txn, m.getId(), meta);
}
private void addMessage(T txn, Message m, State state, boolean shared,
@Nullable ContactId sender) throws DbException {
db.addMessage(txn, m, state, shared);
for (ContactId c : db.getGroupVisibility(txn, m.getGroupId())) {
boolean offered = db.removeOfferedMessage(txn, c, m.getId());
boolean seen = offered || (sender != null && c.equals(sender));
db.addStatus(txn, c, m.getId(), seen, seen);
}
}
@Override
public void addTransport(Transaction transaction, TransportId t,
int maxLatency) throws DbException {
@@ -455,24 +465,24 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
}
@Override
public Collection<MessageId> getMessagesToValidate(Transaction transaction)
throws DbException {
public Collection<MessageId> getMessagesToValidate(Transaction transaction,
ClientId c) throws DbException {
T txn = unbox(transaction);
return db.getMessagesToValidate(txn);
return db.getMessagesToValidate(txn, c);
}
@Override
public Collection<MessageId> getPendingMessages(Transaction transaction)
throws DbException {
public Collection<MessageId> getPendingMessages(Transaction transaction,
ClientId c) throws DbException {
T txn = unbox(transaction);
return db.getPendingMessages(txn);
return db.getPendingMessages(txn, c);
}
@Override
public Collection<MessageId> getMessagesToShare(Transaction transaction)
throws DbException {
public Collection<MessageId> getMessagesToShare(
Transaction transaction, ClientId c) throws DbException {
T txn = unbox(transaction);
return db.getMessagesToShare(txn);
return db.getMessagesToShare(txn, c);
}
@Nullable
@@ -573,7 +583,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
@Override
public long getNextSendTime(Transaction transaction, ContactId c)
throws DbException {
throws DbException {
T txn = unbox(transaction);
return db.getNextSendTime(txn, c);
}
@@ -672,7 +682,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.raiseSeenFlag(txn, c, m.getId());
db.raiseAckFlag(txn, c, m.getId());
} else {
db.addMessage(txn, m, UNKNOWN, false, c);
addMessage(txn, m, UNKNOWN, false, c);
transaction.attach(new MessageAddedEvent(m, c));
}
transaction.attach(new MessageToAckEvent(c));
@@ -740,8 +750,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
GroupId id = g.getId();
if (!db.containsGroup(txn, id))
throw new NoSuchGroupException();
Collection<ContactId> affected =
db.getGroupVisibility(txn, id).keySet();
Collection<ContactId> affected = db.getGroupVisibility(txn, id);
db.removeGroup(txn, id);
transaction.attach(new GroupRemovedEvent(g));
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
@@ -811,9 +820,19 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
throw new NoSuchGroupException();
Visibility old = db.getGroupVisibility(txn, c, g);
if (old == v) return;
if (old == INVISIBLE) db.addGroupVisibility(txn, c, g, v == SHARED);
else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g);
else db.setGroupVisibility(txn, c, g, v == SHARED);
if (old == INVISIBLE) {
db.addGroupVisibility(txn, c, g, v == SHARED);
for (MessageId m : db.getMessageIds(txn, g)) {
boolean seen = db.removeOfferedMessage(txn, c, m);
db.addStatus(txn, c, m, seen, seen);
}
} else if (v == INVISIBLE) {
db.removeGroupVisibility(txn, c, g);
for (MessageId m : db.getMessageIds(txn, g))
db.removeStatus(txn, c, m);
} else {
db.setGroupVisibility(txn, c, g, v == SHARED);
}
List<ContactId> affected = Collections.singletonList(c);
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
}

View File

@@ -24,23 +24,21 @@ import javax.inject.Inject;
class H2Database 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 = "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 String url;
@Inject
H2Database(DatabaseConfig config, Clock clock) {
super(HASH_TYPE, SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE,
clock);
super(HASH_TYPE, BINARY_TYPE, COUNTER_TYPE, SECRET_TYPE, clock);
this.config = config;
File dir = config.getDatabaseDirectory();
String path = new File(dir, "db").getAbsolutePath();
url = "jdbc:h2:split:" + path + ";CIPHER=AES;MULTI_THREADED=1"
+ ";WRITE_DELAY=0";
+ ";WRITE_DELAY=0;DB_CLOSE_ON_EXIT=false";
}
@Override
@@ -95,10 +93,6 @@ class H2Database extends JdbcDatabase {
// Separate the file password from the user password with a space
String hex = StringUtils.toHexString(key.getBytes());
props.put("password", hex + " password");
return DriverManager.getConnection(getUrl(), props);
}
String getUrl() {
return url;
return DriverManager.getConnection(url, props);
}
}

View File

@@ -1,101 +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.db.MigrationListener;
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.annotation.Nullable;
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(@Nullable MigrationListener listener) throws DbException {
boolean reopen = config.databaseExists();
if (!reopen) config.getDatabaseDirectory().mkdirs();
super.open("org.hsqldb.jdbc.JDBCDriver", reopen, listener);
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

@@ -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,65 +1,61 @@
package org.briarproject.bramble.identity;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.data.BdfWriter;
import org.briarproject.bramble.api.data.BdfWriterFactory;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.util.ByteUtils;
import org.briarproject.bramble.util.StringUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
import static org.briarproject.bramble.api.identity.AuthorId.LABEL;
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
@Immutable
@NotNullByDefault
class AuthorFactoryImpl implements AuthorFactory {
private final CryptoComponent crypto;
private final BdfWriterFactory bdfWriterFactory;
private final Clock clock;
@Inject
AuthorFactoryImpl(CryptoComponent crypto, Clock clock) {
AuthorFactoryImpl(CryptoComponent crypto, BdfWriterFactory bdfWriterFactory,
Clock clock) {
this.crypto = crypto;
this.bdfWriterFactory = bdfWriterFactory;
this.clock = clock;
}
@Override
public Author createAuthor(String name, byte[] publicKey) {
return createAuthor(FORMAT_VERSION, name, publicKey);
}
@Override
public Author createAuthor(int formatVersion, String name,
byte[] publicKey) {
AuthorId id = getId(formatVersion, name, publicKey);
return new Author(id, formatVersion, name, publicKey);
return new Author(getId(name, publicKey), name, publicKey);
}
@Override
public LocalAuthor createLocalAuthor(String name, byte[] publicKey,
byte[] privateKey) {
return createLocalAuthor(FORMAT_VERSION, name, publicKey, privateKey);
return new LocalAuthor(getId(name, publicKey), name, publicKey,
privateKey, clock.currentTimeMillis());
}
@Override
public LocalAuthor createLocalAuthor(int formatVersion, String name,
byte[] publicKey, byte[] privateKey) {
AuthorId id = getId(formatVersion, name, publicKey);
return new LocalAuthor(id, formatVersion, name, publicKey, privateKey,
clock.currentTimeMillis());
}
private AuthorId getId(int formatVersion, String name, byte[] publicKey) {
byte[] formatVersionBytes = new byte[INT_32_BYTES];
ByteUtils.writeUint32(formatVersion, formatVersionBytes, 0);
return new AuthorId(crypto.hash(LABEL, formatVersionBytes,
StringUtils.toUtf8(name), publicKey));
private AuthorId getId(String name, byte[] publicKey) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
BdfWriter w = bdfWriterFactory.createWriter(out);
try {
w.writeListStart();
w.writeString(name);
w.writeRaw(publicKey);
w.writeListEnd();
} catch (IOException e) {
// Shouldn't happen with ByteArrayOutputStream
throw new RuntimeException(e);
}
return new AuthorId(crypto.hash(AuthorId.LABEL, out.toByteArray()));
}
}

View File

@@ -0,0 +1,36 @@
package org.briarproject.bramble.identity;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.data.BdfReader;
import org.briarproject.bramble.api.data.ObjectReader;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.IOException;
import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
@Immutable
@NotNullByDefault
class AuthorReader implements ObjectReader<Author> {
private final AuthorFactory authorFactory;
AuthorReader(AuthorFactory authorFactory) {
this.authorFactory = authorFactory;
}
@Override
public Author readObject(BdfReader r) throws IOException {
r.readListStart();
String name = r.readString(MAX_AUTHOR_NAME_LENGTH);
if (name.length() == 0) throw new FormatException();
byte[] publicKey = r.readRaw(MAX_PUBLIC_KEY_LENGTH);
r.readListEnd();
return authorFactory.createAuthor(name, publicKey);
}
}

View File

@@ -1,7 +1,13 @@
package org.briarproject.bramble.identity;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.data.BdfWriterFactory;
import org.briarproject.bramble.api.data.ObjectReader;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.system.Clock;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -18,14 +24,19 @@ public class IdentityModule {
}
@Provides
AuthorFactory provideAuthorFactory(AuthorFactoryImpl authorFactory) {
return authorFactory;
AuthorFactory provideAuthorFactory(CryptoComponent crypto,
BdfWriterFactory bdfWriterFactory, Clock clock) {
return new AuthorFactoryImpl(crypto, bdfWriterFactory, clock);
}
@Provides
@Singleton
IdentityManager provideIdentityManager(
IdentityManagerImpl identityManager) {
return identityManager;
IdentityManager provideIdentityModule(DatabaseComponent db) {
return new IdentityManagerImpl(db);
}
@Provides
ObjectReader<Author> provideAuthorReader(AuthorFactory authorFactory) {
return new AuthorReader(authorFactory);
}
}

View File

@@ -1,6 +1,6 @@
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.data.BdfList;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
@@ -41,7 +41,7 @@ class KeyAgreementConnector {
Logger.getLogger(KeyAgreementConnector.class.getName());
private final Callbacks callbacks;
private final KeyAgreementCrypto keyAgreementCrypto;
private final CryptoComponent crypto;
private final PluginManager pluginManager;
private final ConnectionChooser connectionChooser;
@@ -53,10 +53,10 @@ class KeyAgreementConnector {
private volatile boolean alice = false, stopped = false;
KeyAgreementConnector(Callbacks callbacks,
KeyAgreementCrypto keyAgreementCrypto, PluginManager pluginManager,
CryptoComponent crypto, PluginManager pluginManager,
ConnectionChooser connectionChooser) {
this.callbacks = callbacks;
this.keyAgreementCrypto = keyAgreementCrypto;
this.crypto = crypto;
this.pluginManager = pluginManager;
this.connectionChooser = connectionChooser;
}
@@ -64,8 +64,8 @@ class KeyAgreementConnector {
Payload listen(KeyPair localKeyPair) {
LOG.info("Starting BQP listeners");
// Derive commitment
byte[] commitment = keyAgreementCrypto.deriveKeyCommitment(
localKeyPair.getPublic());
byte[] commitment = crypto.deriveKeyCommitment(
localKeyPair.getPublic().getEncoded());
// Start all listeners and collect their descriptors
List<TransportDescriptor> descriptors = new ArrayList<>();
for (DuplexPlugin plugin : pluginManager.getKeyAgreementPlugins()) {

View File

@@ -2,7 +2,7 @@ package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.data.BdfReaderFactory;
import org.briarproject.bramble.api.data.BdfWriterFactory;
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.PayloadParser;
@@ -13,9 +13,9 @@ import dagger.Provides;
public class KeyAgreementModule {
@Provides
KeyAgreementTask provideKeyAgreementTask(
KeyAgreementTaskImpl keyAgreementTask) {
return keyAgreementTask;
KeyAgreementTaskFactory provideKeyAgreementTaskFactory(
KeyAgreementTaskFactoryImpl keyAgreementTaskFactory) {
return keyAgreementTaskFactory;
}
@Provides

View File

@@ -1,10 +1,7 @@
package org.briarproject.bramble.keyagreement;
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.KeyParser;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.keyagreement.Payload;
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
@@ -14,10 +11,6 @@ import java.io.IOException;
import java.security.GeneralSecurityException;
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.
* <p/>
@@ -64,7 +57,6 @@ class KeyAgreementProtocol {
private final Callbacks callbacks;
private final CryptoComponent crypto;
private final KeyAgreementCrypto keyAgreementCrypto;
private final PayloadEncoder payloadEncoder;
private final KeyAgreementTransport transport;
private final Payload theirPayload, ourPayload;
@@ -72,13 +64,11 @@ class KeyAgreementProtocol {
private final boolean alice;
KeyAgreementProtocol(Callbacks callbacks, CryptoComponent crypto,
KeyAgreementCrypto keyAgreementCrypto,
PayloadEncoder payloadEncoder, KeyAgreementTransport transport,
Payload theirPayload, Payload ourPayload, KeyPair ourKeyPair,
boolean alice) {
this.callbacks = callbacks;
this.crypto = crypto;
this.keyAgreementCrypto = keyAgreementCrypto;
this.payloadEncoder = payloadEncoder;
this.transport = transport;
this.theirPayload = theirPayload;
@@ -96,7 +86,7 @@ class KeyAgreementProtocol {
*/
SecretKey perform() throws AbortException, IOException {
try {
PublicKey theirPublicKey;
byte[] theirPublicKey;
if (alice) {
sendKey();
// Alice waits here for Bob to scan her QR code, determine his
@@ -115,7 +105,7 @@ class KeyAgreementProtocol {
receiveConfirm(s, theirPublicKey);
sendConfirm(s, theirPublicKey);
}
return crypto.deriveKey(MASTER_SECRET_LABEL, s);
return crypto.deriveMasterSecret(s);
} catch (AbortException e) {
sendAbort(e.getCause() != null);
throw e;
@@ -126,41 +116,27 @@ class KeyAgreementProtocol {
transport.sendKey(ourKeyPair.getPublic().getEncoded());
}
private PublicKey receiveKey() throws AbortException {
byte[] publicKeyBytes = transport.receiveKey();
private byte[] receiveKey() throws AbortException {
byte[] publicKey = transport.receiveKey();
callbacks.initialRecordReceived();
KeyParser keyParser = crypto.getAgreementKeyParser();
try {
PublicKey publicKey = keyParser.parsePublicKey(publicKeyBytes);
byte[] expected = keyAgreementCrypto.deriveKeyCommitment(publicKey);
if (!Arrays.equals(expected, theirPayload.getCommitment()))
throw new AbortException();
return publicKey;
} catch (GeneralSecurityException e) {
byte[] expected = crypto.deriveKeyCommitment(publicKey);
if (!Arrays.equals(expected, theirPayload.getCommitment()))
throw new AbortException();
}
return publicKey;
}
private SecretKey deriveSharedSecret(PublicKey theirPublicKey)
private SecretKey deriveSharedSecret(byte[] theirPublicKey)
throws AbortException {
try {
byte[] ourPublicKeyBytes = ourKeyPair.getPublic().getEncoded();
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);
return crypto.deriveSharedSecret(theirPublicKey, ourKeyPair, alice);
} catch (GeneralSecurityException e) {
throw new AbortException(e);
}
}
private void sendConfirm(SecretKey s, PublicKey theirPublicKey)
private void sendConfirm(SecretKey s, byte[] theirPublicKey)
throws IOException {
byte[] confirm = keyAgreementCrypto.deriveConfirmationRecord(s,
byte[] confirm = crypto.deriveConfirmationRecord(s,
payloadEncoder.encode(theirPayload),
payloadEncoder.encode(ourPayload),
theirPublicKey, ourKeyPair,
@@ -168,10 +144,10 @@ class KeyAgreementProtocol {
transport.sendConfirm(confirm);
}
private void receiveConfirm(SecretKey s, PublicKey theirPublicKey)
private void receiveConfirm(SecretKey s, byte[] theirPublicKey)
throws AbortException {
byte[] confirm = transport.receiveConfirm();
byte[] expected = keyAgreementCrypto.deriveConfirmationRecord(s,
byte[] expected = crypto.deriveConfirmationRecord(s,
payloadEncoder.encode(theirPayload),
payloadEncoder.encode(ourPayload),
theirPublicKey, ourKeyPair,

View File

@@ -0,0 +1,41 @@
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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import javax.inject.Provider;
@Immutable
@NotNullByDefault
class KeyAgreementTaskFactoryImpl implements KeyAgreementTaskFactory {
private final CryptoComponent crypto;
private final EventBus eventBus;
private final PayloadEncoder payloadEncoder;
private final PluginManager pluginManager;
private final Provider<ConnectionChooser> connectionChooserProvider;
@Inject
KeyAgreementTaskFactoryImpl(CryptoComponent crypto, EventBus eventBus,
PayloadEncoder payloadEncoder, PluginManager pluginManager,
Provider<ConnectionChooser> connectionChooserProvider) {
this.crypto = crypto;
this.eventBus = eventBus;
this.payloadEncoder = payloadEncoder;
this.pluginManager = pluginManager;
this.connectionChooserProvider = connectionChooserProvider;
}
@Override
public KeyAgreementTask createTask() {
return new KeyAgreementTaskImpl(crypto, eventBus, payloadEncoder,
pluginManager, connectionChooserProvider.get());
}
}

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.keyagreement;
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.SecretKey;
import org.briarproject.bramble.api.event.EventBus;
@@ -22,8 +21,6 @@ import org.briarproject.bramble.api.plugin.PluginManager;
import java.io.IOException;
import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
@MethodsNotNullByDefault
@@ -35,7 +32,6 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
Logger.getLogger(KeyAgreementTaskImpl.class.getName());
private final CryptoComponent crypto;
private final KeyAgreementCrypto keyAgreementCrypto;
private final EventBus eventBus;
private final PayloadEncoder payloadEncoder;
private final KeyPair localKeyPair;
@@ -44,18 +40,15 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
private Payload localPayload;
private Payload remotePayload;
@Inject
KeyAgreementTaskImpl(CryptoComponent crypto,
KeyAgreementCrypto keyAgreementCrypto, EventBus eventBus,
KeyAgreementTaskImpl(CryptoComponent crypto, EventBus eventBus,
PayloadEncoder payloadEncoder, PluginManager pluginManager,
ConnectionChooser connectionChooser) {
this.crypto = crypto;
this.keyAgreementCrypto = keyAgreementCrypto;
this.eventBus = eventBus;
this.payloadEncoder = payloadEncoder;
localKeyPair = crypto.generateAgreementKeyPair();
connector = new KeyAgreementConnector(this, keyAgreementCrypto,
pluginManager, connectionChooser);
connector = new KeyAgreementConnector(this, crypto, pluginManager,
connectionChooser);
}
@Override
@@ -102,8 +95,8 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
// Run BQP protocol over the connection
LOG.info("Starting BQP protocol");
KeyAgreementProtocol protocol = new KeyAgreementProtocol(this, crypto,
keyAgreementCrypto, payloadEncoder, transport, remotePayload,
localPayload, localKeyPair, alice);
payloadEncoder, transport, remotePayload, localPayload,
localKeyPair, alice);
try {
SecretKey master = protocol.perform();
KeyAgreementResult result =

View File

@@ -29,10 +29,10 @@ class PayloadEncoderImpl implements PayloadEncoder {
@Override
public byte[] encode(Payload p) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(PROTOCOL_VERSION);
BdfWriter w = bdfWriterFactory.createWriter(out);
try {
w.writeListStart(); // Payload start
w.writeLong(PROTOCOL_VERSION);
w.writeRaw(p.getCommitment());
for (TransportDescriptor d : p.getTransportDescriptors())
w.writeList(d.getDescriptor());

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UnsupportedVersionException;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.BdfReader;
import org.briarproject.bramble.api.data.BdfReaderFactory;
@@ -40,22 +39,20 @@ class PayloadParserImpl implements PayloadParser {
@Override
public Payload parse(byte[] raw) throws IOException {
ByteArrayInputStream in = new ByteArrayInputStream(raw);
// First byte: the protocol version
int protocolVersion = in.read();
if (protocolVersion == -1) throw new FormatException();
if (protocolVersion != PROTOCOL_VERSION)
throw new UnsupportedVersionException();
// The rest of the payload is a BDF list with one or more elements
BdfReader r = bdfReaderFactory.createReader(in);
// The payload is a BDF list with two or more elements
BdfList payload = r.readList();
if (payload.isEmpty()) throw new FormatException();
if (payload.size() < 2) throw new FormatException();
if (!r.eof()) throw new FormatException();
// First element: the public key commitment
byte[] commitment = payload.getRaw(0);
// First element: the protocol version
long protocolVersion = payload.getLong(0);
if (protocolVersion != PROTOCOL_VERSION) throw new FormatException();
// Second element: the public key commitment
byte[] commitment = payload.getRaw(1);
if (commitment.length != COMMIT_LENGTH) throw new FormatException();
// Remaining elements: transport descriptors
List<TransportDescriptor> recognised = new ArrayList<>();
for (int i = 1; i < payload.size(); i++) {
for (int i = 2; i < payload.size(); i++) {
BdfList descriptor = payload.getList(i);
long transportId = descriptor.getLong(0);
if (transportId == TRANSPORT_ID_BLUETOOTH) {

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.plugin;
import org.briarproject.bramble.api.Multiset;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -37,14 +36,14 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
private final Lock lock = new ReentrantLock();
// The following are locking: lock
private final Map<TransportId, Multiset<ContactId>> connections;
private final Multiset<ContactId> contactCounts;
private final Map<TransportId, Map<ContactId, Integer>> connections;
private final Map<ContactId, Integer> contactCounts;
@Inject
ConnectionRegistryImpl(EventBus eventBus) {
this.eventBus = eventBus;
connections = new HashMap<>();
contactCounts = new Multiset<>();
contactCounts = new HashMap<>();
}
@Override
@@ -57,13 +56,21 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
boolean firstConnection = false;
lock.lock();
try {
Multiset<ContactId> m = connections.get(t);
Map<ContactId, Integer> m = connections.get(t);
if (m == null) {
m = new Multiset<>();
m = new HashMap<>();
connections.put(t, m);
}
m.add(c);
if (contactCounts.add(c) == 1) firstConnection = true;
Integer count = m.get(c);
if (count == null) m.put(c, 1);
else m.put(c, count + 1);
count = contactCounts.get(c);
if (count == null) {
firstConnection = true;
contactCounts.put(c, 1);
} else {
contactCounts.put(c, count + 1);
}
} finally {
lock.unlock();
}
@@ -84,10 +91,23 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
boolean lastConnection = false;
lock.lock();
try {
Multiset<ContactId> m = connections.get(t);
Map<ContactId, Integer> m = connections.get(t);
if (m == null) throw new IllegalArgumentException();
m.remove(c);
if (contactCounts.remove(c) == 0) lastConnection = true;
Integer count = m.remove(c);
if (count == null) throw new IllegalArgumentException();
if (count == 1) {
if (m.isEmpty()) connections.remove(t);
} else {
m.put(c, count - 1);
}
count = contactCounts.get(c);
if (count == null) throw new IllegalArgumentException();
if (count == 1) {
lastConnection = true;
contactCounts.remove(c);
} else {
contactCounts.put(c, count - 1);
}
} finally {
lock.unlock();
}
@@ -102,7 +122,7 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
public Collection<ContactId> getConnectedContacts(TransportId t) {
lock.lock();
try {
Multiset<ContactId> m = connections.get(t);
Map<ContactId, Integer> m = connections.get(t);
if (m == null) return Collections.emptyList();
List<ContactId> ids = new ArrayList<>(m.keySet());
if (LOG.isLoggable(INFO))
@@ -117,8 +137,8 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
public boolean isConnected(ContactId c, TransportId t) {
lock.lock();
try {
Multiset<ContactId> m = connections.get(t);
return m != null && m.contains(c);
Map<ContactId, Integer> m = connections.get(t);
return m != null && m.containsKey(c);
} finally {
lock.unlock();
}
@@ -128,7 +148,7 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
public boolean isConnected(ContactId c) {
lock.lock();
try {
return contactCounts.contains(c);
return contactCounts.containsKey(c);
} finally {
lock.unlock();
}

View File

@@ -58,8 +58,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
this.metadataParser = metadataParser;
this.contactGroupFactory = contactGroupFactory;
this.clock = clock;
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
CLIENT_VERSION);
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID);
}
@Override
@@ -131,7 +130,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
public Map<TransportId, TransportProperties> getLocalProperties()
throws DbException {
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 {
local = getLocalProperties(txn);
db.commitTransaction(txn);
@@ -166,7 +166,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
throws DbException {
try {
TransportProperties p = null;
Transaction txn = db.startTransaction(true);
// TODO: Transaction can be read-only when code is simplified
Transaction txn = db.startTransaction(false);
try {
// Find the latest local update
LatestUpdate latest = findLatest(txn, localGroup.getId(), t,
@@ -192,7 +193,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
public Map<ContactId, TransportProperties> getRemoteProperties(
TransportId t) throws DbException {
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 {
for (Contact c : db.getContacts(txn))
remote.put(c.getId(), getRemoteProperties(txn, c, t));
@@ -226,7 +228,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
public TransportProperties getRemoteProperties(ContactId c, TransportId t)
throws DbException {
TransportProperties p;
Transaction txn = db.startTransaction(true);
// TODO: Transaction can be read-only when code is simplified
Transaction txn = db.startTransaction(false);
try {
p = getRemoteProperties(txn, db.getContact(txn, c), t);
db.commitTransaction(txn);
@@ -289,8 +292,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
}
private Group getContactGroup(Contact c) {
return contactGroupFactory.createContactGroup(CLIENT_ID,
CLIENT_VERSION, c);
return contactGroupFactory.createContactGroup(CLIENT_ID, c);
}
private void storeMessage(Transaction txn, GroupId g, TransportId t,
@@ -317,6 +319,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
private Map<TransportId, LatestUpdate> findLatestLocal(Transaction txn)
throws DbException, FormatException {
// TODO: This can be simplified before 1.0
Map<TransportId, LatestUpdate> latestUpdates = new HashMap<>();
Map<MessageId, BdfDictionary> metadata = clientHelper
.getMessageMetadataAsDictionary(txn, localGroup.getId());
@@ -324,7 +327,17 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
BdfDictionary meta = e.getValue();
TransportId t = new TransportId(meta.getString("transportId"));
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;
}
@@ -332,16 +345,38 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
@Nullable
private LatestUpdate findLatest(Transaction txn, GroupId g, TransportId t,
boolean local) throws DbException, FormatException {
// TODO: This can be simplified before 1.0
LatestUpdate latest = null;
Map<MessageId, BdfDictionary> metadata =
clientHelper.getMessageMetadataAsDictionary(txn, g);
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
BdfDictionary meta = e.getValue();
if (meta.getString("transportId").equals(t.getString())
&& 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)

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.GroupFactory;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.util.ByteUtils;
import org.briarproject.bramble.util.StringUtils;
import javax.annotation.concurrent.Immutable;
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
@NotNullByDefault
class GroupFactoryImpl implements GroupFactory {
@@ -28,12 +23,9 @@ class GroupFactoryImpl implements GroupFactory {
}
@Override
public Group createGroup(ClientId c, int clientVersion, byte[] descriptor) {
byte[] clientVersionBytes = new byte[INT_32_BYTES];
ByteUtils.writeUint32(clientVersion, clientVersionBytes, 0);
byte[] hash = crypto.hash(LABEL, new byte[] {PROTOCOL_VERSION},
StringUtils.toUtf8(c.getString()), clientVersionBytes,
descriptor);
public Group createGroup(ClientId c, byte[] descriptor) {
byte[] hash = crypto.hash(GroupId.LABEL,
StringUtils.toUtf8(c.getString()), 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.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.MESSAGE_HEADER_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
@Immutable
@NotNullByDefault
@@ -34,9 +32,9 @@ class MessageFactoryImpl implements MessageFactory {
throw new IllegalArgumentException();
byte[] timeBytes = new byte[ByteUtils.INT_64_BYTES];
ByteUtils.writeUint64(timestamp, timeBytes, 0);
byte[] hash = crypto.hash(LABEL, new byte[] {PROTOCOL_VERSION},
g.getBytes(), timeBytes, body);
MessageId id = new MessageId(hash);
byte[] idHash =
crypto.hash(MessageId.LABEL, g.getBytes(), timeBytes, body);
MessageId id = new MessageId(idHash);
byte[] raw = new byte[MESSAGE_HEADER_LENGTH + body.length];
System.arraycopy(g.getBytes(), 0, raw, 0, UniqueId.LENGTH);
ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH);

View File

@@ -71,9 +71,11 @@ class ValidationManagerImpl implements ValidationManager, Service,
@Override
public void startService() {
if (used.getAndSet(true)) throw new IllegalStateException();
validateOutstandingMessagesAsync();
deliverOutstandingMessagesAsync();
shareOutstandingMessagesAsync();
for (ClientId c : validators.keySet()) {
validateOutstandingMessagesAsync(c);
deliverOutstandingMessagesAsync(c);
shareOutstandingMessagesAsync(c);
}
}
@Override
@@ -91,17 +93,17 @@ class ValidationManagerImpl implements ValidationManager, Service,
hooks.put(c, hook);
}
private void validateOutstandingMessagesAsync() {
dbExecutor.execute(this::validateOutstandingMessages);
private void validateOutstandingMessagesAsync(ClientId c) {
dbExecutor.execute(() -> validateOutstandingMessages(c));
}
@DatabaseExecutor
private void validateOutstandingMessages() {
private void validateOutstandingMessages(ClientId c) {
try {
Queue<MessageId> unvalidated = new LinkedList<>();
Transaction txn = db.startTransaction(true);
try {
unvalidated.addAll(db.getMessagesToValidate(txn));
unvalidated.addAll(db.getMessagesToValidate(txn, c));
db.commitTransaction(txn);
} finally {
db.endTransaction(txn);
@@ -146,17 +148,17 @@ class ValidationManagerImpl implements ValidationManager, Service,
}
}
private void deliverOutstandingMessagesAsync() {
dbExecutor.execute(this::deliverOutstandingMessages);
private void deliverOutstandingMessagesAsync(ClientId c) {
dbExecutor.execute(() -> deliverOutstandingMessages(c));
}
@DatabaseExecutor
private void deliverOutstandingMessages() {
private void deliverOutstandingMessages(ClientId c) {
try {
Queue<MessageId> pending = new LinkedList<>();
Transaction txn = db.startTransaction(true);
try {
pending.addAll(db.getPendingMessages(txn));
pending.addAll(db.getPendingMessages(txn, c));
db.commitTransaction(txn);
} finally {
db.endTransaction(txn);
@@ -351,17 +353,17 @@ class ValidationManagerImpl implements ValidationManager, Service,
return pending;
}
private void shareOutstandingMessagesAsync() {
dbExecutor.execute(this::shareOutstandingMessages);
private void shareOutstandingMessagesAsync(ClientId c) {
dbExecutor.execute(() -> shareOutstandingMessages(c));
}
@DatabaseExecutor
private void shareOutstandingMessages() {
private void shareOutstandingMessages(ClientId c) {
try {
Queue<MessageId> toShare = new LinkedList<>();
Transaction txn = db.startTransaction(true);
try {
toShare.addAll(db.getMessagesToShare(txn));
toShare.addAll(db.getMessagesToShare(txn, c));
db.commitTransaction(txn);
} finally {
db.endTransaction(txn);

View File

@@ -1,6 +1,6 @@
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.DatabaseExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -20,18 +20,17 @@ class TransportKeyManagerFactoryImpl implements
TransportKeyManagerFactory {
private final DatabaseComponent db;
private final TransportCrypto transportCrypto;
private final CryptoComponent crypto;
private final Executor dbExecutor;
private final ScheduledExecutorService scheduler;
private final Clock clock;
@Inject
TransportKeyManagerFactoryImpl(DatabaseComponent db,
TransportCrypto transportCrypto,
TransportKeyManagerFactoryImpl(DatabaseComponent db, CryptoComponent crypto,
@DatabaseExecutor Executor dbExecutor,
@Scheduler ScheduledExecutorService scheduler, Clock clock) {
this.db = db;
this.transportCrypto = transportCrypto;
this.crypto = crypto;
this.dbExecutor = dbExecutor;
this.scheduler = scheduler;
this.clock = clock;
@@ -40,8 +39,8 @@ class TransportKeyManagerFactoryImpl implements
@Override
public TransportKeyManager createTransportKeyManager(
TransportId transportId, long maxLatency) {
return new TransportKeyManagerImpl(db, transportCrypto, dbExecutor,
scheduler, clock, transportId, maxLatency);
return new TransportKeyManagerImpl(db, crypto, dbExecutor, scheduler,
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.contact.ContactId;
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.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
@@ -41,7 +41,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
Logger.getLogger(TransportKeyManagerImpl.class.getName());
private final DatabaseComponent db;
private final TransportCrypto transportCrypto;
private final CryptoComponent crypto;
private final Executor dbExecutor;
private final ScheduledExecutorService scheduler;
private final Clock clock;
@@ -54,12 +54,11 @@ class TransportKeyManagerImpl implements TransportKeyManager {
private final Map<ContactId, MutableOutgoingKeys> outContexts;
private final Map<ContactId, MutableTransportKeys> keys;
TransportKeyManagerImpl(DatabaseComponent db,
TransportCrypto transportCrypto, Executor dbExecutor,
@Scheduler ScheduledExecutorService scheduler, Clock clock,
TransportId transportId, long maxLatency) {
TransportKeyManagerImpl(DatabaseComponent db, CryptoComponent crypto,
Executor dbExecutor, @Scheduler ScheduledExecutorService scheduler,
Clock clock, TransportId transportId, long maxLatency) {
this.db = db;
this.transportCrypto = transportCrypto;
this.crypto = crypto;
this.dbExecutor = dbExecutor;
this.scheduler = scheduler;
this.clock = clock;
@@ -100,8 +99,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
ContactId c = e.getKey();
TransportKeys k = e.getValue();
TransportKeys k1 =
transportCrypto.rotateTransportKeys(k, rotationPeriod);
TransportKeys k1 = crypto.rotateTransportKeys(k, rotationPeriod);
if (k1.getRotationPeriod() > k.getRotationPeriod())
rotationResult.rotated.put(c, k1);
rotationResult.current.put(c, k1);
@@ -129,7 +127,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
for (long streamNumber : inKeys.getWindow().getUnseen()) {
TagContext tagCtx = new TagContext(c, inKeys, streamNumber);
byte[] tag = new byte[TAG_LENGTH];
transportCrypto.encodeTag(tag, inKeys.getTagKey(), PROTOCOL_VERSION,
crypto.encodeTag(tag, inKeys.getTagKey(), PROTOCOL_VERSION,
streamNumber);
inContexts.put(new Bytes(tag), tagCtx);
}
@@ -164,11 +162,11 @@ class TransportKeyManagerImpl implements TransportKeyManager {
// Work out what rotation period the timestamp belongs to
long rotationPeriod = timestamp / rotationPeriodLength;
// Derive the transport keys
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
master, rotationPeriod, alice);
TransportKeys k = crypto.deriveTransportKeys(transportId, master,
rotationPeriod, alice);
// Rotate the keys to the current rotation period if necessary
rotationPeriod = clock.currentTimeMillis() / rotationPeriodLength;
k = transportCrypto.rotateTransportKeys(k, rotationPeriod);
k = crypto.rotateTransportKeys(k, rotationPeriod);
// Initialise mutable state for the contact
addKeys(c, new MutableTransportKeys(k));
// 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
for (long streamNumber : change.getAdded()) {
byte[] addTag = new byte[TAG_LENGTH];
transportCrypto.encodeTag(addTag, inKeys.getTagKey(),
PROTOCOL_VERSION, streamNumber);
crypto.encodeTag(addTag, inKeys.getTagKey(), PROTOCOL_VERSION,
streamNumber);
inContexts.put(new Bytes(addTag), new TagContext(
tagCtx.contactId, inKeys, streamNumber));
}
@@ -245,7 +243,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
for (long streamNumber : change.getRemoved()) {
if (streamNumber == tagCtx.streamNumber) continue;
byte[] removeTag = new byte[TAG_LENGTH];
transportCrypto.encodeTag(removeTag, inKeys.getTagKey(),
crypto.encodeTag(removeTag, inKeys.getTagKey(),
PROTOCOL_VERSION, streamNumber);
inContexts.remove(new Bytes(removeTag));
}

View File

@@ -15,8 +15,6 @@ import org.briarproject.bramble.api.data.MetadataParser;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageFactory;
@@ -33,14 +31,9 @@ import java.security.GeneralSecurityException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
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.test.TestUtils.getAuthor;
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;
import static org.junit.Assert.fail;
@@ -61,8 +54,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
context.mock(MetadataEncoder.class);
private final CryptoComponent cryptoComponent =
context.mock(CryptoComponent.class);
private final AuthorFactory authorFactory =
context.mock(AuthorFactory.class);
private final ClientHelper clientHelper;
private final GroupId groupId = new GroupId(getRandomId());
private final BdfDictionary dictionary = new BdfDictionary();
@@ -74,15 +66,17 @@ public class ClientHelperImplTest extends BrambleTestCase {
private final Metadata metadata = new Metadata();
private final BdfList list = BdfList.of("Sign this!", getRandomBytes(42));
private final String label = StringUtils.getRandomString(5);
private final Author author = getAuthor();
private final ClientHelper clientHelper = new ClientHelperImpl(db,
messageFactory, bdfReaderFactory, bdfWriterFactory, metadataParser,
metadataEncoder, cryptoComponent, authorFactory);
public ClientHelperImplTest() {
clientHelper =
new ClientHelperImpl(db, messageFactory, bdfReaderFactory,
bdfWriterFactory, metadataParser, metadataEncoder,
cryptoComponent);
}
@Test
public void testAddLocalMessage() throws Exception {
boolean shared = new Random().nextBoolean();
boolean shared = true;
Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
@@ -186,7 +180,8 @@ public class ClientHelperImplTest extends BrambleTestCase {
oneOf(db).endTransaction(txn);
}});
assertEquals(map, clientHelper.getMessageMetadataAsDictionary(groupId));
assertEquals(map,
clientHelper.getMessageMetadataAsDictionary(groupId));
context.assertIsSatisfied();
}
@@ -323,7 +318,8 @@ public class ClientHelperImplTest extends BrambleTestCase {
}});
try {
clientHelper.verifySignature(label, rawMessage, publicKey, list);
clientHelper
.verifySignature(label, rawMessage, publicKey, list);
fail();
} catch (GeneralSecurityException e) {
// expected
@@ -331,166 +327,6 @@ public class ClientHelperImplTest extends BrambleTestCase {
}
}
@Test
public void testParsesAndEncodesAuthor() throws Exception {
context.checking(new Expectations() {{
oneOf(authorFactory).createAuthor(author.getFormatVersion(),
author.getName(), author.getPublicKey());
will(returnValue(author));
}});
BdfList authorList = clientHelper.toList(author);
assertEquals(author, clientHelper.parseAndValidateAuthor(authorList));
}
@Test
public void testAcceptsValidAuthor() throws Exception {
BdfList authorList = BdfList.of(
author.getFormatVersion(),
author.getName(),
author.getPublicKey()
);
context.checking(new Expectations() {{
oneOf(authorFactory).createAuthor(author.getFormatVersion(),
author.getName(), author.getPublicKey());
will(returnValue(author));
}});
assertEquals(author, clientHelper.parseAndValidateAuthor(authorList));
}
@Test(expected = FormatException.class)
public void testRejectsTooShortAuthor() throws Exception {
BdfList invalidAuthor = BdfList.of(
author.getFormatVersion(),
author.getName()
);
clientHelper.parseAndValidateAuthor(invalidAuthor);
}
@Test(expected = FormatException.class)
public void testRejectsTooLongAuthor() throws Exception {
BdfList invalidAuthor = BdfList.of(
author.getFormatVersion(),
author.getName(),
author.getPublicKey(),
"foo"
);
clientHelper.parseAndValidateAuthor(invalidAuthor);
}
@Test(expected = FormatException.class)
public void testRejectsAuthorWithNullFormatVersion() throws Exception {
BdfList invalidAuthor = BdfList.of(
null,
author.getName(),
author.getPublicKey()
);
clientHelper.parseAndValidateAuthor(invalidAuthor);
}
@Test(expected = FormatException.class)
public void testRejectsAuthorWithNonIntegerFormatVersion()
throws Exception {
BdfList invalidAuthor = BdfList.of(
"foo",
author.getName(),
author.getPublicKey()
);
clientHelper.parseAndValidateAuthor(invalidAuthor);
}
@Test(expected = FormatException.class)
public void testRejectsAuthorWithUnknownFormatVersion() throws Exception {
BdfList invalidAuthor = BdfList.of(
author.getFormatVersion() + 1,
author.getName(),
author.getPublicKey()
);
clientHelper.parseAndValidateAuthor(invalidAuthor);
}
@Test(expected = FormatException.class)
public void testRejectsAuthorWithTooShortName() throws Exception {
BdfList invalidAuthor = BdfList.of(
author.getFormatVersion(),
"",
author.getPublicKey()
);
clientHelper.parseAndValidateAuthor(invalidAuthor);
}
@Test(expected = FormatException.class)
public void testRejectsAuthorWithTooLongName() throws Exception {
BdfList invalidAuthor = BdfList.of(
author.getFormatVersion(),
getRandomString(MAX_AUTHOR_NAME_LENGTH + 1),
author.getPublicKey()
);
clientHelper.parseAndValidateAuthor(invalidAuthor);
}
@Test(expected = FormatException.class)
public void testRejectsAuthorWithNullName() throws Exception {
BdfList invalidAuthor = BdfList.of(
author.getFormatVersion(),
null,
author.getPublicKey()
);
clientHelper.parseAndValidateAuthor(invalidAuthor);
}
@Test(expected = FormatException.class)
public void testRejectsAuthorWithNonStringName() throws Exception {
BdfList invalidAuthor = BdfList.of(
author.getFormatVersion(),
getRandomBytes(5),
author.getPublicKey()
);
clientHelper.parseAndValidateAuthor(invalidAuthor);
}
@Test(expected = FormatException.class)
public void testRejectsAuthorWithTooShortPublicKey() throws Exception {
BdfList invalidAuthor = BdfList.of(
author.getFormatVersion(),
author.getName(),
new byte[0]
);
clientHelper.parseAndValidateAuthor(invalidAuthor);
}
@Test(expected = FormatException.class)
public void testRejectsAuthorWithTooLongPublicKey() throws Exception {
BdfList invalidAuthor = BdfList.of(
author.getFormatVersion(),
author.getName(),
getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1)
);
clientHelper.parseAndValidateAuthor(invalidAuthor);
}
@Test(expected = FormatException.class)
public void testRejectsAuthorWithNullPublicKey() throws Exception {
BdfList invalidAuthor = BdfList.of(
author.getFormatVersion(),
author.getName(),
null
);
clientHelper.parseAndValidateAuthor(invalidAuthor);
}
@Test(expected = FormatException.class)
public void testRejectsAuthorWithNonRawPublicKey() throws Exception {
BdfList invalidAuthor = BdfList.of(
author.getFormatVersion(),
author.getName(),
"foo"
);
clientHelper.parseAndValidateAuthor(invalidAuthor);
}
private byte[] expectToByteArray(BdfList list) throws Exception {
BdfWriter bdfWriter = context.mock(BdfWriter.class);
@@ -516,4 +352,5 @@ public class ClientHelperImplTest extends BrambleTestCase {
will(returnValue(eof));
}});
}
}

View File

@@ -18,9 +18,8 @@ import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Random;
import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.junit.Assert.assertEquals;
@@ -33,7 +32,9 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
private final KeyManager keyManager = context.mock(KeyManager.class);
private final ContactManager contactManager;
private final ContactId contactId = new ContactId(42);
private final Author remote = getAuthor();
private final Author remote =
new Author(new AuthorId(getRandomId()), "remote",
getRandomBytes(42));
private final AuthorId local = new AuthorId(getRandomId());
private final boolean verified = false, active = true;
private final Contact contact =
@@ -46,8 +47,8 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
@Test
public void testAddContact() throws Exception {
SecretKey master = getSecretKey();
long timestamp = System.currentTimeMillis();
boolean alice = new Random().nextBoolean();
long timestamp = 42;
boolean alice = true;
Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{

View File

@@ -3,76 +3,75 @@ package org.briarproject.bramble.crypto;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.util.StringUtils;
import org.junit.Test;
import org.spongycastle.crypto.digests.Blake2bDigest;
import java.util.Random;
import static org.junit.Assert.assertArrayEquals;
public class Blake2bDigestTest extends BrambleTestCase {
public class Blake2sDigestTest extends BrambleTestCase {
// Vectors from BLAKE2 web site: https://blake2.net/Blake2b-test.txt
private static final String[][] KEYED_TEST_VECTORS = {
// Vectors from BLAKE2 web site: https://blake2.net/blake2s-test.txt
private static final String[][] keyedTestVectors = {
// input/message, key, hash
{
"",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
"10ebb67700b1868efb4417987acf4690ae9d972fb7a590c2f02871799aaa4786b5e996e8f0f4eb981fc214b005f42d2ff4233499391653df7aefcbc13fc51568",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"48a8997da407876b3d79c0d92325ad3b89cbb754d86ab71aee047ad345fd2c49",
},
{
"00",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
"961f6dd1e4dd30f63901690c512e78e4b45e4742ed197c3c5e45c549fd25f2e4187b0bc9fe30492b16b0d0bc4ef9b0f34c7003fac09a5ef1532e69430234cebd",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"40d15fee7c328830166ac3f918650f807e7e01e177258cdc0a39b11f598066f1",
},
{
"0001",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
"da2cfbe2d8409a0f38026113884f84b50156371ae304c4430173d08a99d9fb1b983164a3770706d537f49e0c916d9f32b95cc37a95b99d857436f0232c88a965",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"6bb71300644cd3991b26ccd4d274acd1adeab8b1d7914546c1198bbe9fc9d803",
},
{
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
"f1aa2b044f8f0c638a3f362e677b5d891d6fd2ab0765f6ee1e4987de057ead357883d9b405b9d609eea1b869d97fb16d9b51017c553f3b93c0a1e0f1296fedcd",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"172ffc67153d12e0ca76a8b6cd5d4731885b39ce0cac93a8972a18006c8b8baf",
},
{
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
"c230f0802679cb33822ef8b3b21bf7a9a28942092901d7dac3760300831026cf354c9232df3e084d9903130c601f63c1f4a4a4b8106e468cd443bbe5a734f45f",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"4f8ce1e51d2fe7f24043a904d898ebfc91975418753413aa099b795ecb35cedb",
},
{
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
"142709d62e28fcccd0af97fad0f8465b971e82201dc51070faa0372aa43e92484be1c1e73ba10906d5d1853db6a4106e0a7bf9800d373d6dee2d46d62ef2a461",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"3fb735061abc519dfe979e54c1ee5bfad0a9d858b3315bad34bde999efd724dd",
},
};
@Test
public void testDigestWithKeyedTestVectors() {
for (String[] keyedTestVector : KEYED_TEST_VECTORS) {
Blake2sDigest digest = new Blake2sDigest(StringUtils.fromHexString(
keyedTestVectors[0][1]));
for (String[] keyedTestVector : keyedTestVectors) {
byte[] input = StringUtils.fromHexString(keyedTestVector[0]);
byte[] key = StringUtils.fromHexString(keyedTestVector[1]);
byte[] expected = StringUtils.fromHexString(keyedTestVector[2]);
digest.reset();
Blake2bDigest digest = new Blake2bDigest(key);
digest.update(input, 0, input.length);
byte[] hash = new byte[64];
byte[] hash = new byte[32];
digest.doFinal(hash, 0);
assertArrayEquals(expected, hash);
assertArrayEquals(StringUtils.fromHexString(keyedTestVector[2]),
hash);
}
}
@Test
public void testDigestWithKeyedTestVectorsAndRandomUpdate() {
Blake2sDigest digest = new Blake2sDigest(StringUtils.fromHexString(
keyedTestVectors[0][1]));
Random random = new Random();
for (int i = 0; i < 100; i++) {
for (String[] keyedTestVector : KEYED_TEST_VECTORS) {
for (String[] keyedTestVector : keyedTestVectors) {
byte[] input = StringUtils.fromHexString(keyedTestVector[0]);
if (input.length == 0) continue;
byte[] key = StringUtils.fromHexString(keyedTestVector[1]);
byte[] expected = StringUtils.fromHexString(keyedTestVector[2]);
Blake2bDigest digest = new Blake2bDigest(key);
if (input.length < 3) continue;
digest.reset();
int pos = random.nextInt(input.length);
if (pos > 0)
@@ -81,10 +80,11 @@ public class Blake2bDigestTest extends BrambleTestCase {
if (pos < (input.length - 1))
digest.update(input, pos + 1, input.length - (pos + 1));
byte[] hash = new byte[64];
byte[] hash = new byte[32];
digest.doFinal(hash, 0);
assertArrayEquals(expected, hash);
assertArrayEquals(StringUtils.fromHexString(keyedTestVector[2]),
hash);
}
}
}
@@ -98,12 +98,12 @@ public class Blake2bDigestTest extends BrambleTestCase {
byte[] input = new byte[key.length + 1];
for (byte i = 0; i < input.length; i++) input[i] = i;
// Hash the input
Blake2bDigest digest = new Blake2bDigest(key);
Blake2sDigest digest = new Blake2sDigest(key);
digest.update(input, 0, input.length);
byte[] hash = new byte[digest.getDigestSize()];
digest.doFinal(hash, 0);
// Create a second instance, hash the input without calling doFinal()
Blake2bDigest digest1 = new Blake2bDigest(key);
Blake2sDigest digest1 = new Blake2sDigest(key);
digest1.update(input, 0, input.length);
// Reset the second instance and hash the input again
digest1.reset();
@@ -116,10 +116,9 @@ public class Blake2bDigestTest extends BrambleTestCase {
// Self-test routine from https://tools.ietf.org/html/rfc7693#appendix-E
private static final String SELF_TEST_RESULT =
"C23A7800D98123BD10F506C61E29DA5603D763B8BBAD2E737F5E765A7BCCD475";
private static final int[] SELF_TEST_DIGEST_LEN = {20, 32, 48, 64};
private static final int[] SELF_TEST_INPUT_LEN =
{0, 3, 128, 129, 255, 1024};
"6A411F08CE25ADCDFB02ABA641451CEC53C598B24F4FC787FBDC88797F4C1DFE";
private static final int[] SELF_TEST_DIGEST_LEN = {16, 20, 28, 32};
private static final int[] SELF_TEST_INPUT_LEN = {0, 3, 64, 65, 255, 1024};
private static byte[] selfTestSequence(int len, int seed) {
int a = 0xDEAD4BAD * seed;
@@ -139,8 +138,8 @@ public class Blake2bDigestTest extends BrambleTestCase {
@Test
public void runSelfTest() {
Blake2bDigest testDigest = new Blake2bDigest(256);
byte[] md = new byte[64];
Blake2sDigest testDigest = new Blake2sDigest();
byte[] md = new byte[32];
for (int i = 0; i < 4; i++) {
int outlen = SELF_TEST_DIGEST_LEN[i];
@@ -149,7 +148,7 @@ public class Blake2bDigestTest extends BrambleTestCase {
// unkeyed hash
byte[] in = selfTestSequence(inlen, inlen);
Blake2bDigest unkeyedDigest = new Blake2bDigest(outlen * 8);
Blake2sDigest unkeyedDigest = new Blake2sDigest(outlen * 8);
unkeyedDigest.update(in, 0, inlen);
unkeyedDigest.doFinal(md, 0);
// hash the hash
@@ -157,7 +156,7 @@ public class Blake2bDigestTest extends BrambleTestCase {
// keyed hash
byte[] key = selfTestSequence(outlen, outlen);
Blake2bDigest keyedDigest = new Blake2bDigest(key, outlen, null,
Blake2sDigest keyedDigest = new Blake2sDigest(key, outlen, null,
null);
keyedDigest.update(in, 0, inlen);
keyedDigest.doFinal(md, 0);

View File

@@ -1,169 +0,0 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.junit.Test;
import java.security.GeneralSecurityException;
import static org.briarproject.bramble.util.StringUtils.fromHexString;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertTrue;
public class EdSignatureTest extends SignatureTest {
// Test vectors from RFC 8032: secret key, public key, message, signature
// https://tools.ietf.org/html/rfc8032#section-7.1
private static final String[][] TEST_VECTORS = {{
"9d61b19deffd5a60ba844af492ec2cc4" +
"4449c5697b326919703bac031cae7f60",
"d75a980182b10ab7d54bfed3c964073a" +
"0ee172f3daa62325af021a68f707511a",
"",
"e5564300c360ac729086e2cc806e828a" +
"84877f1eb8e5d974d873e06522490155" +
"5fb8821590a33bacc61e39701cf9b46b" +
"d25bf5f0595bbe24655141438e7a100b"
}, {
"4ccd089b28ff96da9db6c346ec114e0f" +
"5b8a319f35aba624da8cf6ed4fb8a6fb",
"3d4017c3e843895a92b70aa74d1b7ebc" +
"9c982ccf2ec4968cc0cd55f12af4660c",
"72",
"92a009a9f0d4cab8720e820b5f642540" +
"a2b27b5416503f8fb3762223ebdb69da" +
"085ac1e43e15996e458f3613d0f11d8c" +
"387b2eaeb4302aeeb00d291612bb0c00"
}, {
"c5aa8df43f9f837bedb7442f31dcb7b1" +
"66d38535076f094b85ce3a2e0b4458f7",
"fc51cd8e6218a1a38da47ed00230f058" +
"0816ed13ba3303ac5deb911548908025",
"af82",
"6291d657deec24024827e69c3abe01a3" +
"0ce548a284743a445e3680d7db5ac3ac" +
"18ff9b538d16f290ae67f760984dc659" +
"4a7c15e9716ed28dc027beceea1ec40a"
}, {
"f5e5767cf153319517630f226876b86c" +
"8160cc583bc013744c6bf255f5cc0ee5",
"278117fc144c72340f67d0f2316e8386" +
"ceffbf2b2428c9c51fef7c597f1d426e",
"08b8b2b733424243760fe426a4b54908" +
"632110a66c2f6591eabd3345e3e4eb98" +
"fa6e264bf09efe12ee50f8f54e9f77b1" +
"e355f6c50544e23fb1433ddf73be84d8" +
"79de7c0046dc4996d9e773f4bc9efe57" +
"38829adb26c81b37c93a1b270b20329d" +
"658675fc6ea534e0810a4432826bf58c" +
"941efb65d57a338bbd2e26640f89ffbc" +
"1a858efcb8550ee3a5e1998bd177e93a" +
"7363c344fe6b199ee5d02e82d522c4fe" +
"ba15452f80288a821a579116ec6dad2b" +
"3b310da903401aa62100ab5d1a36553e" +
"06203b33890cc9b832f79ef80560ccb9" +
"a39ce767967ed628c6ad573cb116dbef" +
"efd75499da96bd68a8a97b928a8bbc10" +
"3b6621fcde2beca1231d206be6cd9ec7" +
"aff6f6c94fcd7204ed3455c68c83f4a4" +
"1da4af2b74ef5c53f1d8ac70bdcb7ed1" +
"85ce81bd84359d44254d95629e9855a9" +
"4a7c1958d1f8ada5d0532ed8a5aa3fb2" +
"d17ba70eb6248e594e1a2297acbbb39d" +
"502f1a8c6eb6f1ce22b3de1a1f40cc24" +
"554119a831a9aad6079cad88425de6bd" +
"e1a9187ebb6092cf67bf2b13fd65f270" +
"88d78b7e883c8759d2c4f5c65adb7553" +
"878ad575f9fad878e80a0c9ba63bcbcc" +
"2732e69485bbc9c90bfbd62481d9089b" +
"eccf80cfe2df16a2cf65bd92dd597b07" +
"07e0917af48bbb75fed413d238f5555a" +
"7a569d80c3414a8d0859dc65a46128ba" +
"b27af87a71314f318c782b23ebfe808b" +
"82b0ce26401d2e22f04d83d1255dc51a" +
"ddd3b75a2b1ae0784504df543af8969b" +
"e3ea7082ff7fc9888c144da2af58429e" +
"c96031dbcad3dad9af0dcbaaaf268cb8" +
"fcffead94f3c7ca495e056a9b47acdb7" +
"51fb73e666c6c655ade8297297d07ad1" +
"ba5e43f1bca32301651339e22904cc8c" +
"42f58c30c04aafdb038dda0847dd988d" +
"cda6f3bfd15c4b4c4525004aa06eeff8" +
"ca61783aacec57fb3d1f92b0fe2fd1a8" +
"5f6724517b65e614ad6808d6f6ee34df" +
"f7310fdc82aebfd904b01e1dc54b2927" +
"094b2db68d6f903b68401adebf5a7e08" +
"d78ff4ef5d63653a65040cf9bfd4aca7" +
"984a74d37145986780fc0b16ac451649" +
"de6188a7dbdf191f64b5fc5e2ab47b57" +
"f7f7276cd419c17a3ca8e1b939ae49e4" +
"88acba6b965610b5480109c8b17b80e1" +
"b7b750dfc7598d5d5011fd2dcc5600a3" +
"2ef5b52a1ecc820e308aa342721aac09" +
"43bf6686b64b2579376504ccc493d97e" +
"6aed3fb0f9cd71a43dd497f01f17c0e2" +
"cb3797aa2a2f256656168e6c496afc5f" +
"b93246f6b1116398a346f1a641f3b041" +
"e989f7914f90cc2c7fff357876e506b5" +
"0d334ba77c225bc307ba537152f3f161" +
"0e4eafe595f6d9d90d11faa933a15ef1" +
"369546868a7f3a45a96768d40fd9d034" +
"12c091c6315cf4fde7cb68606937380d" +
"b2eaaa707b4c4185c32eddcdd306705e" +
"4dc1ffc872eeee475a64dfac86aba41c" +
"0618983f8741c5ef68d3a101e8a3b8ca" +
"c60c905c15fc910840b94c00a0b9d0",
"0aab4c900501b3e24d7cdf4663326a3a" +
"87df5e4843b2cbdb67cbf6e460fec350" +
"aa5371b1508f9f4528ecea23c436d94b" +
"5e8fcd4f681e30a6ac00a9704a188a03"
}, {
"833fe62409237b9d62ec77587520911e" +
"9a759cec1d19755b7da901b96dca3d42",
"ec172b93ad5e563bf4932c70e1245034" +
"c35467ef2efd4d64ebf819683467e2bf",
"ddaf35a193617abacc417349ae204131" +
"12e6fa4e89a97ea20a9eeee64b55d39a" +
"2192992a274fc1a836ba3c23a3feebbd" +
"454d4423643ce80e2a9ac94fa54ca49f",
"dc2a4459e7369633a52b1bf277839a00" +
"201009a3efbf3ecb69bea2186c26b589" +
"09351fc9ac90b3ecfdfbc7c66431e030" +
"3dca179c138ac17ad9bef1177331a704"
}};
@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);
}
@Test
public void testRfc8032TestVectors() throws Exception {
for (String[] vector : TEST_VECTORS) {
byte[] privateKeyBytes = fromHexString(vector[0]);
byte[] publicKeyBytes = fromHexString(vector[1]);
byte[] messageBytes = fromHexString(vector[2]);
byte[] signatureBytes = fromHexString(vector[3]);
EdSignature signature = new EdSignature();
signature.initSign(new EdPrivateKey(privateKeyBytes));
signature.update(messageBytes);
assertArrayEquals(signatureBytes, signature.sign());
signature.initVerify(new EdPublicKey(publicKeyBytes));
signature.update(messageBytes);
assertTrue(signature.verify(signatureBytes));
}
}
}

View File

@@ -13,11 +13,11 @@ import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.math.ec.ECCurve;
import org.spongycastle.math.ec.ECPoint;
import org.spongycastle.math.ec.MontgomeryLadderMultiplier;
import java.math.BigInteger;
import java.security.SecureRandom;
import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS;
import static org.junit.Assert.assertEquals;
public class EllipticCurveMultiplicationTest extends BrambleTestCase {
@@ -31,11 +31,15 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase {
ECPoint defaultG = defaultX9Parameters.getG();
BigInteger defaultN = defaultX9Parameters.getN();
BigInteger defaultH = defaultX9Parameters.getH();
// Check that the default parameters are equal to our parameters
assertEquals(PARAMETERS.getCurve(), defaultCurve);
assertEquals(PARAMETERS.getG(), defaultG);
assertEquals(PARAMETERS.getN(), defaultN);
assertEquals(PARAMETERS.getH(), defaultH);
// ECDomainParameters doesn't have an equals() method, but it's just a
// container for the parameters
ECDomainParameters defaultParameters = new ECDomainParameters(
defaultCurve, defaultG, defaultN, defaultH);
// Instantiate an implementation using the Montgomery ladder multiplier
ECDomainParameters montgomeryParameters =
constantTime(defaultParameters);
// Generate two key pairs with each set of parameters, using the same
// deterministic PRNG for both sets of parameters
byte[] seed = new byte[32];
@@ -43,7 +47,7 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase {
// Montgomery ladder multiplier
SecureRandom random = new PseudoSecureRandom(seed);
ECKeyGenerationParameters montgomeryGeneratorParams =
new ECKeyGenerationParameters(montgomeryParameters, random);
new ECKeyGenerationParameters(PARAMETERS, random);
ECKeyPairGenerator montgomeryGenerator = new ECKeyPairGenerator();
montgomeryGenerator.init(montgomeryGeneratorParams);
AsymmetricCipherKeyPair montgomeryKeyPair1 =
@@ -103,13 +107,4 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase {
assertEquals(sharedSecretMontgomeryMontgomery,
sharedSecretDefaultDefault);
}
private static ECDomainParameters constantTime(ECDomainParameters in) {
ECCurve curve = in.getCurve().configure().setMultiplier(
new MontgomeryLadderMultiplier()).create();
BigInteger x = in.getG().getAffineXCoord().toBigInteger();
BigInteger y = in.getG().getAffineYCoord().toBigInteger();
ECPoint g = curve.createPoint(x, y);
return new ECDomainParameters(curve, g, in.getN(), in.getH());
}
}

View File

@@ -1,20 +1,16 @@
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.teletrust.TeleTrusTNamedCurves;
import org.spongycastle.asn1.x9.X9ECParameters;
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
import org.spongycastle.crypto.BasicAgreement;
import org.spongycastle.crypto.Digest;
import org.spongycastle.crypto.agreement.ECDHBasicAgreement;
import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
import org.spongycastle.crypto.digests.Blake2bDigest;
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
import org.spongycastle.crypto.params.ECDomainParameters;
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.signers.DSADigestSigner;
import org.spongycastle.crypto.signers.DSAKCalculator;
@@ -23,22 +19,14 @@ import org.spongycastle.crypto.signers.HMacDSAKCalculator;
import org.spongycastle.math.ec.ECCurve;
import org.spongycastle.math.ec.ECPoint;
import org.spongycastle.math.ec.MontgomeryLadderMultiplier;
import org.whispersystems.curve25519.Curve25519;
import org.whispersystems.curve25519.Curve25519KeyPair;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.Signature;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static net.i2p.crypto.eddsa.EdDSAEngine.SIGNATURE_ALGORITHM;
// Not a JUnit test
public class EllipticCurvePerformanceTest {
@@ -49,9 +37,8 @@ public class EllipticCurvePerformanceTest {
"secp256k1", "secp256r1", "secp384r1", "secp521r1");
private static final List<String> BRAINPOOL_NAMES = Arrays.asList(
"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) {
ECDomainParameters params =
convertParams(SECNamedCurves.getByName(name));
@@ -64,31 +51,43 @@ public class EllipticCurvePerformanceTest {
runTest(name + " default", params);
runTest(name + " constant", constantTime(params));
}
runCurve25519Test();
runEd25519Test();
runTest("ours", EllipticCurveConstants.PARAMETERS);
}
private static void runTest(String name, ECDomainParameters params) {
// Generate two key pairs using the given parameters
ECKeyGenerationParameters generatorParams =
new ECKeyGenerationParameters(params, random);
ECKeyPairGenerator generator = new ECKeyPairGenerator();
generator.init(new ECKeyGenerationParameters(params, random));
generator.init(generatorParams);
AsymmetricCipherKeyPair keyPair1 = generator.generateKeyPair();
ECPublicKeyParameters public1 =
(ECPublicKeyParameters) keyPair1.getPublic();
ECPrivateKeyParameters private1 =
(ECPrivateKeyParameters) keyPair1.getPrivate();
AsymmetricCipherKeyPair keyPair2 = generator.generateKeyPair();
// Time some ECDH and ECDHC key agreements
long agreementMedian = runAgreementTest(keyPair1, keyPair2, false);
long agreementWithCofactorMedian =
runAgreementTest(keyPair1, keyPair2, true);
// Time some signatures
ECPublicKeyParameters public2 =
(ECPublicKeyParameters) keyPair2.getPublic();
// Time some ECDH key agreements
List<Long> samples = new ArrayList<>();
List<byte[]> signatures = new ArrayList<>();
for (int i = 0; i < SAMPLES; i++) {
Digest digest = new Blake2bDigest(256);
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<>();
samples.clear();
for (int i = 0; i < SAMPLES; i++) {
Digest digest = new Blake2sDigest();
DSAKCalculator calculator = new HMacDSAKCalculator(digest);
DSADigestSigner signer = new DSADigestSigner(new ECDSASigner(
calculator), digest);
long start = System.nanoTime();
signer.init(true,
new ParametersWithRandom(keyPair1.getPrivate(), random));
signer.init(true, new ParametersWithRandom(private1, random));
signer.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN);
signatures.add(signer.generateSignature());
samples.add(System.nanoTime() - start);
@@ -97,88 +96,22 @@ public class EllipticCurvePerformanceTest {
// Time some signature verifications
samples.clear();
for (int i = 0; i < SAMPLES; i++) {
Digest digest = new Blake2bDigest(256);
Digest digest = new Blake2sDigest();
DSAKCalculator calculator = new HMacDSAKCalculator(digest);
DSADigestSigner signer = new DSADigestSigner(new ECDSASigner(
calculator), digest);
long start = System.nanoTime();
signer.init(false, keyPair1.getPublic());
signer.init(false, public1);
signer.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN);
if (!signer.verifySignature(signatures.get(i)))
throw new AssertionError();
samples.add(System.nanoTime() - start);
}
long verificationMedian = median(samples);
System.out.println(String.format("%s: %,d %,d %,d %,d", name,
agreementMedian, agreementWithCofactorMedian,
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));
System.out.println(name + ": "
+ agreementMedian + " "
+ signatureMedian + " "
+ verificationMedian);
}
private static long median(List<Long> list) {

View File

@@ -22,7 +22,7 @@ public class HashTest extends BrambleTestCase {
private final byte[] inputBytes2 = new byte[0];
public HashTest() {
crypto = new CryptoComponentImpl(new TestSecureRandomProvider(), null);
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
}
@Test

View File

@@ -2,80 +2,41 @@ package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PublicKey;
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.TestSecureRandomProvider;
import org.junit.Test;
import org.whispersystems.curve25519.Curve25519;
import java.security.GeneralSecurityException;
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.briarproject.bramble.util.StringUtils.fromHexString;
import static org.junit.Assert.assertArrayEquals;
public class KeyAgreementTest extends BrambleTestCase {
// Test vector from RFC 7748: Alice's private and public keys, Bob's
// private and public keys, and the shared secret
// https://tools.ietf.org/html/rfc7748#section-6.1
private static final String ALICE_PRIVATE =
"77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a";
private static final String ALICE_PUBLIC =
"8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a";
private static final String BOB_PRIVATE =
"5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb";
private static final String BOB_PUBLIC =
"de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f";
private static final String SHARED_SECRET =
"4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742";
private final CryptoComponent crypto =
new CryptoComponentImpl(new TestSecureRandomProvider(), null);
private final byte[][] inputs;
public KeyAgreementTest() {
Random random = new Random();
inputs = new byte[random.nextInt(10) + 1][];
for (int i = 0; i < inputs.length; i++)
inputs[i] = getRandomBytes(random.nextInt(256));
@Test
public void testDeriveMasterSecret() 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 aMaster = crypto.deriveMasterSecret(aPub, bPair, true);
SecretKey bMaster = crypto.deriveMasterSecret(bPub, aPair, false);
assertArrayEquals(aMaster.getBytes(), bMaster.getBytes());
}
@Test
public void testDerivesSharedSecret() throws Exception {
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();
SecretKey aShared = crypto.deriveSharedSecret(SHARED_SECRET_LABEL,
bPair.getPublic(), aPair, inputs);
SecretKey bShared = crypto.deriveSharedSecret(SHARED_SECRET_LABEL,
aPair.getPublic(), bPair, inputs);
byte[] bPub = bPair.getPublic().getEncoded();
SecretKey aShared = crypto.deriveSharedSecret(bPub, aPair, true);
SecretKey bShared = crypto.deriveSharedSecret(aPub, bPair, false);
assertArrayEquals(aShared.getBytes(), bShared.getBytes());
}
@Test(expected = GeneralSecurityException.class)
public void testRejectsInvalidPublicKey() throws Exception {
KeyPair keyPair = crypto.generateAgreementKeyPair();
PublicKey invalid = new Curve25519PublicKey(new byte[32]);
crypto.deriveSharedSecret(SHARED_SECRET_LABEL, invalid, keyPair,
inputs);
}
@Test
public void testRfc7748TestVector() throws Exception {
// Private keys need to be clamped because curve25519-java does the
// clamping at key generation time, not multiplication time
byte[] aPriv = Curve25519KeyParser.clamp(fromHexString(ALICE_PRIVATE));
byte[] aPub = fromHexString(ALICE_PUBLIC);
byte[] bPriv = Curve25519KeyParser.clamp(fromHexString(BOB_PRIVATE));
byte[] bPub = fromHexString(BOB_PUBLIC);
byte[] sharedSecret = fromHexString(SHARED_SECRET);
Curve25519 curve25519 = Curve25519.getInstance("java");
assertArrayEquals(sharedSecret,
curve25519.calculateAgreement(aPub, bPriv));
assertArrayEquals(sharedSecret,
curve25519.calculateAgreement(bPub, aPriv));
}
}

View File

@@ -3,11 +3,11 @@ package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.Bytes;
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.TransportKeys;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.briarproject.bramble.test.TestUtils;
import org.junit.Test;
import java.util.ArrayList;
@@ -16,34 +16,35 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class KeyDerivationTest extends BrambleTestCase {
private final CryptoComponent crypto =
new CryptoComponentImpl(new TestSecureRandomProvider(), null);
private final TransportCrypto transportCrypto =
new TransportCryptoImpl(crypto);
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
public void testKeysAreDistinct() {
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
master, 123, true);
TransportKeys k = crypto.deriveTransportKeys(transportId, master,
123, true);
assertAllDifferent(k);
}
@Test
public void testCurrentKeysMatchCurrentKeysOfContact() {
// Start in rotation period 123
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
master, 123, true);
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
master, 123, false);
TransportKeys kA = crypto.deriveTransportKeys(transportId, master,
123, true);
TransportKeys kB = crypto.deriveTransportKeys(transportId, master,
123, false);
// Alice's incoming keys should equal Bob's outgoing keys
assertArrayEquals(kA.getCurrentIncomingKeys().getTagKey().getBytes(),
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
@@ -55,8 +56,8 @@ public class KeyDerivationTest extends BrambleTestCase {
assertArrayEquals(kA.getCurrentOutgoingKeys().getHeaderKey().getBytes(),
kB.getCurrentIncomingKeys().getHeaderKey().getBytes());
// Rotate into the future
kA = transportCrypto.rotateTransportKeys(kA, 456);
kB = transportCrypto.rotateTransportKeys(kB, 456);
kA = crypto.rotateTransportKeys(kA, 456);
kB = crypto.rotateTransportKeys(kB, 456);
// Alice's incoming keys should equal Bob's outgoing keys
assertArrayEquals(kA.getCurrentIncomingKeys().getTagKey().getBytes(),
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
@@ -72,23 +73,22 @@ public class KeyDerivationTest extends BrambleTestCase {
@Test
public void testPreviousKeysMatchPreviousKeysOfContact() {
// Start in rotation period 123
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
master, 123, true);
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
master, 123, false);
TransportKeys kA = crypto.deriveTransportKeys(transportId, master,
123, true);
TransportKeys kB = crypto.deriveTransportKeys(transportId, master,
123, false);
// Compare Alice's previous keys in period 456 with Bob's current keys
// in period 455
kA = transportCrypto.rotateTransportKeys(kA, 456);
kB = transportCrypto.rotateTransportKeys(kB, 455);
kA = crypto.rotateTransportKeys(kA, 456);
kB = crypto.rotateTransportKeys(kB, 455);
// Alice's previous incoming keys should equal Bob's outgoing keys
assertArrayEquals(kA.getPreviousIncomingKeys().getTagKey().getBytes(),
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
assertArrayEquals(
kA.getPreviousIncomingKeys().getHeaderKey().getBytes(),
assertArrayEquals(kA.getPreviousIncomingKeys().getHeaderKey().getBytes(),
kB.getCurrentOutgoingKeys().getHeaderKey().getBytes());
// Compare Alice's current keys in period 456 with Bob's previous keys
// in period 457
kB = transportCrypto.rotateTransportKeys(kB, 457);
kB = crypto.rotateTransportKeys(kB, 457);
// Alice's outgoing keys should equal Bob's previous incoming keys
assertArrayEquals(kA.getCurrentOutgoingKeys().getTagKey().getBytes(),
kB.getPreviousIncomingKeys().getTagKey().getBytes());
@@ -99,14 +99,14 @@ public class KeyDerivationTest extends BrambleTestCase {
@Test
public void testNextKeysMatchNextKeysOfContact() {
// Start in rotation period 123
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
master, 123, true);
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
master, 123, false);
TransportKeys kA = crypto.deriveTransportKeys(transportId, master,
123, true);
TransportKeys kB = crypto.deriveTransportKeys(transportId, master,
123, false);
// Compare Alice's current keys in period 456 with Bob's next keys in
// period 455
kA = transportCrypto.rotateTransportKeys(kA, 456);
kB = transportCrypto.rotateTransportKeys(kB, 455);
kA = crypto.rotateTransportKeys(kA, 456);
kB = crypto.rotateTransportKeys(kB, 455);
// Alice's outgoing keys should equal Bob's next incoming keys
assertArrayEquals(kA.getCurrentOutgoingKeys().getTagKey().getBytes(),
kB.getNextIncomingKeys().getTagKey().getBytes());
@@ -114,7 +114,7 @@ public class KeyDerivationTest extends BrambleTestCase {
kB.getNextIncomingKeys().getHeaderKey().getBytes());
// Compare Alice's next keys in period 456 with Bob's current keys
// in period 457
kB = transportCrypto.rotateTransportKeys(kB, 457);
kB = crypto.rotateTransportKeys(kB, 457);
// Alice's next incoming keys should equal Bob's outgoing keys
assertArrayEquals(kA.getNextIncomingKeys().getTagKey().getBytes(),
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
@@ -124,12 +124,12 @@ public class KeyDerivationTest extends BrambleTestCase {
@Test
public void testMasterKeyAffectsOutput() {
SecretKey master1 = getSecretKey();
SecretKey master1 = TestUtils.getSecretKey();
assertFalse(Arrays.equals(master.getBytes(), master1.getBytes()));
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
master, 123, true);
TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId,
master1, 123, true);
TransportKeys k = crypto.deriveTransportKeys(transportId, master,
123, true);
TransportKeys k1 = crypto.deriveTransportKeys(transportId, master1,
123, true);
assertAllDifferent(k, k1);
}
@@ -137,10 +137,10 @@ public class KeyDerivationTest extends BrambleTestCase {
public void testTransportIdAffectsOutput() {
TransportId transportId1 = new TransportId("id1");
assertFalse(transportId.getString().equals(transportId1.getString()));
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
master, 123, true);
TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId1,
master, 123, true);
TransportKeys k = crypto.deriveTransportKeys(transportId, master,
123, true);
TransportKeys k1 = crypto.deriveTransportKeys(transportId1, master,
123, true);
assertAllDifferent(k, k1);
}

View File

@@ -6,30 +6,29 @@ import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.briarproject.bramble.test.TestUtils;
import org.junit.Test;
import java.security.GeneralSecurityException;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_BYTES;
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_PUBLIC_KEY_BYTES;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertTrue;
public class KeyEncodingAndParsingTest extends BrambleTestCase {
private final CryptoComponentImpl crypto =
new CryptoComponentImpl(new TestSecureRandomProvider(), null);
new CryptoComponentImpl(new TestSecureRandomProvider());
@Test
public void testAgreementPublicKeyLength() throws Exception {
// Generate 10 agreement key pairs
for (int i = 0; i < 10; i++) {
KeyPair keyPair = crypto.generateAgreementKeyPair();
KeyPair keyPair = crypto.generateSignatureKeyPair();
// Check the length of the public key
byte[] publicKey = keyPair.getPublic().getEncoded();
assertTrue(publicKey.length <= MAX_AGREEMENT_PUBLIC_KEY_BYTES);
assertTrue(publicKey.length <= MAX_PUBLIC_KEY_LENGTH);
}
}
@@ -46,8 +45,7 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
aPub = parser.parsePublicKey(aPub.getEncoded());
aPub = parser.parsePublicKey(aPub.getEncoded());
// Derive the shared secret again - it should be the same
byte[] secret1 =
crypto.performRawKeyAgreement(bPair.getPrivate(), aPub);
byte[] secret1 = crypto.performRawKeyAgreement(bPair.getPrivate(), aPub);
assertArrayEquals(secret, secret1);
}
@@ -64,8 +62,7 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
bPriv = parser.parsePrivateKey(bPriv.getEncoded());
bPriv = parser.parsePrivateKey(bPriv.getEncoded());
// Derive the shared secret again - it should be the same
byte[] secret1 =
crypto.performRawKeyAgreement(bPriv, aPair.getPublic());
byte[] secret1 = crypto.performRawKeyAgreement(bPriv, aPair.getPublic());
assertArrayEquals(secret, secret1);
}
@@ -79,12 +76,12 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
// Parse some random byte arrays - expect GeneralSecurityException
for (int i = 0; i < 1000; i++) {
try {
parser.parsePublicKey(getRandomBytes(pubLength));
parser.parsePublicKey(TestUtils.getRandomBytes(pubLength));
} catch (GeneralSecurityException expected) {
// Expected
}
try {
parser.parsePrivateKey(getRandomBytes(privLength));
parser.parsePrivateKey(TestUtils.getRandomBytes(privLength));
} catch (GeneralSecurityException expected) {
// Expected
}
@@ -98,7 +95,7 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
KeyPair keyPair = crypto.generateSignatureKeyPair();
// Check the length of the public key
byte[] publicKey = keyPair.getPublic().getEncoded();
assertTrue(publicKey.length <= MAX_SIGNATURE_PUBLIC_KEY_BYTES);
assertTrue(publicKey.length <= MAX_PUBLIC_KEY_LENGTH);
}
}
@@ -109,53 +106,44 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
KeyPair keyPair = crypto.generateSignatureKeyPair();
byte[] key = keyPair.getPrivate().getEncoded();
// Sign some random data and check the length of the signature
byte[] toBeSigned = getRandomBytes(1234);
byte[] toBeSigned = TestUtils.getRandomBytes(1234);
byte[] signature = crypto.sign("label", toBeSigned, key);
assertTrue(signature.length <= MAX_SIGNATURE_BYTES);
assertTrue(signature.length <= MAX_SIGNATURE_LENGTH);
}
}
@Test
public void testSignaturePublicKeyEncodingAndParsing() throws Exception {
KeyParser parser = crypto.getSignatureKeyParser();
// Generate a key pair and sign some data
KeyPair keyPair = crypto.generateSignatureKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
byte[] message = getRandomBytes(123);
byte[] signature = crypto.sign("test", message,
privateKey.getEncoded());
// Verify the signature
assertTrue(crypto.verify("test", message, publicKey.getEncoded(),
signature));
// Generate two key pairs
KeyPair aPair = crypto.generateSignatureKeyPair();
KeyPair bPair = crypto.generateSignatureKeyPair();
// Derive the shared secret
PublicKey aPub = aPair.getPublic();
byte[] secret = crypto.performRawKeyAgreement(bPair.getPrivate(), aPub);
// Encode and parse the public key - no exceptions should be thrown
publicKey = parser.parsePublicKey(publicKey.getEncoded());
// Verify the signature again
assertTrue(crypto.verify("test", message, publicKey.getEncoded(),
signature));
aPub = parser.parsePublicKey(aPub.getEncoded());
aPub = parser.parsePublicKey(aPub.getEncoded());
// Derive the shared secret again - it should be the same
byte[] secret1 = crypto.performRawKeyAgreement(bPair.getPrivate(), aPub);
assertArrayEquals(secret, secret1);
}
@Test
public void testSignaturePrivateKeyEncodingAndParsing() throws Exception {
KeyParser parser = crypto.getSignatureKeyParser();
// Generate a key pair and sign some data
KeyPair keyPair = crypto.generateSignatureKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
byte[] message = getRandomBytes(123);
byte[] signature = crypto.sign("test", message,
privateKey.getEncoded());
// Verify the signature
assertTrue(crypto.verify("test", message, publicKey.getEncoded(),
signature));
// Generate two key pairs
KeyPair aPair = crypto.generateSignatureKeyPair();
KeyPair bPair = crypto.generateSignatureKeyPair();
// Derive the shared secret
PrivateKey bPriv = bPair.getPrivate();
byte[] secret = crypto.performRawKeyAgreement(bPriv, aPair.getPublic());
// Encode and parse the private key - no exceptions should be thrown
privateKey = parser.parsePrivateKey(privateKey.getEncoded());
// Sign the data again - the signatures should be the same
byte[] signature1 = crypto.sign("test", message,
privateKey.getEncoded());
assertTrue(crypto.verify("test", message, publicKey.getEncoded(),
signature1));
assertArrayEquals(signature, signature1);
bPriv = parser.parsePrivateKey(bPriv.getEncoded());
bPriv = parser.parsePrivateKey(bPriv.getEncoded());
// Derive the shared secret again - it should be the same
byte[] secret1 = crypto.performRawKeyAgreement(bPriv, aPair.getPublic());
assertArrayEquals(secret, secret1);
}
@Test
@@ -168,12 +156,12 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
// Parse some random byte arrays - expect GeneralSecurityException
for (int i = 0; i < 1000; i++) {
try {
parser.parsePublicKey(getRandomBytes(pubLength));
parser.parsePublicKey(TestUtils.getRandomBytes(pubLength));
} catch (GeneralSecurityException expected) {
// Expected
}
try {
parser.parsePrivateKey(getRandomBytes(privLength));
parser.parsePrivateKey(TestUtils.getRandomBytes(privLength));
} catch (GeneralSecurityException expected) {
// Expected
}

View File

@@ -4,49 +4,42 @@ import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.briarproject.bramble.test.TestUtils;
import org.junit.Test;
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.assertFalse;
public class MacTest extends BrambleTestCase {
private final CryptoComponent crypto =
new CryptoComponentImpl(new TestSecureRandomProvider(), null);
private final CryptoComponent crypto;
private final SecretKey key1 = getSecretKey(), key2 = getSecretKey();
private final String label1 = getRandomString(123);
private final String label2 = getRandomString(123);
private final byte[] input1 = getRandomBytes(123);
private final byte[] input2 = getRandomBytes(234);
private final byte[] input3 = new byte[0];
private final SecretKey k = TestUtils.getSecretKey();
private final byte[] inputBytes = TestUtils.getRandomBytes(123);
private final byte[] inputBytes1 = TestUtils.getRandomBytes(234);
private final byte[] inputBytes2 = new byte[0];
public MacTest() {
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
}
@Test
public void testIdenticalKeysAndInputsProduceIdenticalMacs() {
// Calculate the MAC twice - the results should be identical
byte[] mac = crypto.mac(label1, key1, input1, input2, input3);
byte[] mac1 = crypto.mac(label1, key1, input1, input2, input3);
byte[] mac = crypto.mac(k, inputBytes, inputBytes1, inputBytes2);
byte[] mac1 = crypto.mac(k, inputBytes, inputBytes1, inputBytes2);
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
public void testDifferentKeysProduceDifferentMacs() {
// Generate second random key
SecretKey k1 = TestUtils.getSecretKey();
// Calculate the MAC with each key - the results should be different
byte[] mac = crypto.mac(label1, key1, input1, input2, input3);
byte[] mac1 = crypto.mac(label1, key2, input1, input2, input3);
byte[] mac = crypto.mac(k, inputBytes, inputBytes1, inputBytes2);
byte[] mac1 = crypto.mac(k1, inputBytes, inputBytes1, inputBytes2);
assertFalse(Arrays.equals(mac, mac1));
}
@@ -54,8 +47,8 @@ public class MacTest extends BrambleTestCase {
public void testDifferentInputsProduceDifferentMacs() {
// Calculate the MAC with the inputs in different orders - the results
// should be different
byte[] mac = crypto.mac(label1, key1, input1, input2, input3);
byte[] mac1 = crypto.mac(label1, key1, input3, input2, input1);
byte[] mac = crypto.mac(k, inputBytes, inputBytes1, inputBytes2);
byte[] mac1 = crypto.mac(k, inputBytes2, inputBytes1, inputBytes);
assertFalse(Arrays.equals(mac, mac1));
}

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.system.SystemClock;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.briarproject.bramble.test.TestUtils;
@@ -9,13 +8,14 @@ import org.junit.Test;
import java.util.Random;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class PasswordBasedEncryptionTest extends BrambleTestCase {
public class PasswordBasedKdfTest extends BrambleTestCase {
private final CryptoComponentImpl crypto =
new CryptoComponentImpl(new TestSecureRandomProvider(),
new ScryptKdf(new SystemClock()));
new CryptoComponentImpl(new TestSecureRandomProvider());
@Test
public void testEncryptionAndDecryption() {
@@ -37,4 +37,17 @@ public class PasswordBasedEncryptionTest extends BrambleTestCase {
byte[] output = crypto.decryptWithPassword(ciphertext, password);
assertNull(output);
}
@Test
public void testCalibration() {
// If the target time is unachievable, one iteration should be used
int iterations = crypto.chooseIterationCount(0);
assertEquals(1, iterations);
// If the target time is long, more than one iteration should be used
iterations = crypto.chooseIterationCount(10 * 1000);
assertTrue(iterations > 1);
// If the target time is very long, max iterations should be used
iterations = crypto.chooseIterationCount(Integer.MAX_VALUE);
assertEquals(Integer.MAX_VALUE, iterations);
}
}

View File

@@ -2,7 +2,6 @@ package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.spongycastle.crypto.Digest;
import org.spongycastle.crypto.digests.Blake2bDigest;
import org.spongycastle.crypto.engines.Salsa20Engine;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.crypto.params.ParametersWithIV;
@@ -18,7 +17,7 @@ class PseudoRandom {
PseudoRandom(byte[] seed) {
// Hash the seed to produce a 32-byte key
byte[] key = new byte[32];
Digest digest = new Blake2bDigest(256);
Digest digest = new Blake2sDigest();
digest.update(seed, 0, seed.length);
digest.doFinal(key, 0);
// Initialise the stream cipher with an all-zero nonce

View File

@@ -1,97 +0,0 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.system.SystemClock;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
import static junit.framework.TestCase.assertTrue;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertEquals;
public class ScryptKdfTest extends BrambleTestCase {
@Test
public void testPasswordAffectsKey() throws Exception {
PasswordBasedKdf kdf = new ScryptKdf(new SystemClock());
byte[] salt = getRandomBytes(32);
Set<Bytes> keys = new HashSet<>();
for (int i = 0; i < 100; i++) {
String password = getRandomString(16);
SecretKey key = kdf.deriveKey(password, salt, 256);
assertTrue(keys.add(new Bytes(key.getBytes())));
}
}
@Test
public void testSaltAffectsKey() throws Exception {
PasswordBasedKdf kdf = new ScryptKdf(new SystemClock());
String password = getRandomString(16);
Set<Bytes> keys = new HashSet<>();
for (int i = 0; i < 100; i++) {
byte[] salt = getRandomBytes(32);
SecretKey key = kdf.deriveKey(password, salt, 256);
assertTrue(keys.add(new Bytes(key.getBytes())));
}
}
@Test
public void testCostParameterAffectsKey() throws Exception {
PasswordBasedKdf kdf = new ScryptKdf(new SystemClock());
String password = getRandomString(16);
byte[] salt = getRandomBytes(32);
Set<Bytes> keys = new HashSet<>();
for (int cost = 2; cost <= 256; cost *= 2) {
SecretKey key = kdf.deriveKey(password, salt, cost);
assertTrue(keys.add(new Bytes(key.getBytes())));
}
}
@Test
public void testCalibration() throws Exception {
Clock clock = new ArrayClock(
0, 50, // Duration for cost 256
0, 100, // Duration for cost 512
0, 200, // Duration for cost 1024
0, 400, // Duration for cost 2048
0, 800 // Duration for cost 4096
);
PasswordBasedKdf kdf = new ScryptKdf(clock);
assertEquals(4096, kdf.chooseCostParameter());
}
@Test
public void testCalibrationChoosesMinCost() throws Exception {
Clock clock = new ArrayClock(
0, 2000 // Duration for cost 256 is already too high
);
PasswordBasedKdf kdf = new ScryptKdf(clock);
assertEquals(256, kdf.chooseCostParameter());
}
private static class ArrayClock implements Clock {
private final long[] times;
private int index = 0;
private ArrayClock(long... times) {
this.times = times;
}
@Override
public long currentTimeMillis() {
return times[index++];
}
@Override
public void sleep(long milliseconds) throws InterruptedException {
Thread.sleep(milliseconds);
}
}
}

View File

@@ -8,32 +8,23 @@ import org.briarproject.bramble.test.TestUtils;
import org.briarproject.bramble.util.StringUtils;
import org.junit.Test;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse;
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 String label = StringUtils.getRandomString(42);
private final byte[] inputBytes = TestUtils.getRandomBytes(123);
protected abstract KeyPair generateKeyPair();
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(), null);
KeyPair k = generateKeyPair();
public SignatureTest() {
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
KeyPair k = crypto.generateSignatureKeyPair();
publicKey = k.getPublic().getEncoded();
privateKey = k.getPrivate().getEncoded();
}
@@ -42,19 +33,19 @@ public abstract class SignatureTest extends BrambleTestCase {
public void testIdenticalKeysAndInputsProduceIdenticalSignatures()
throws Exception {
// Calculate the Signature twice - the results should be identical
byte[] sig1 = sign(label, inputBytes, privateKey);
byte[] sig2 = sign(label, inputBytes, privateKey);
byte[] sig1 = crypto.sign(label, inputBytes, privateKey);
byte[] sig2 = crypto.sign(label, inputBytes, privateKey);
assertArrayEquals(sig1, sig2);
}
@Test
public void testDifferentKeysProduceDifferentSignatures() throws Exception {
// Generate second private key
KeyPair k2 = generateKeyPair();
KeyPair k2 = crypto.generateSignatureKeyPair();
byte[] privateKey2 = k2.getPrivate().getEncoded();
// Calculate the signature with each key
byte[] sig1 = sign(label, inputBytes, privateKey);
byte[] sig2 = sign(label, inputBytes, privateKey2);
byte[] sig1 = crypto.sign(label, inputBytes, privateKey);
byte[] sig2 = crypto.sign(label, inputBytes, privateKey2);
assertFalse(Arrays.equals(sig1, sig2));
}
@@ -65,8 +56,8 @@ public abstract class SignatureTest extends BrambleTestCase {
byte[] inputBytes2 = TestUtils.getRandomBytes(123);
// Calculate the signature with different inputs
// the results should be different
byte[] sig1 = sign(label, inputBytes, privateKey);
byte[] sig2 = sign(label, inputBytes2, privateKey);
byte[] sig1 = crypto.sign(label, inputBytes, privateKey);
byte[] sig2 = crypto.sign(label, inputBytes2, privateKey);
assertFalse(Arrays.equals(sig1, sig2));
}
@@ -77,25 +68,25 @@ public abstract class SignatureTest extends BrambleTestCase {
String label2 = StringUtils.getRandomString(42);
// Calculate the signature with different inputs
// the results should be different
byte[] sig1 = sign(label, inputBytes, privateKey);
byte[] sig2 = sign(label2, inputBytes, privateKey);
byte[] sig1 = crypto.sign(label, inputBytes, privateKey);
byte[] sig2 = crypto.sign(label2, inputBytes, privateKey);
assertFalse(Arrays.equals(sig1, sig2));
}
@Test
public void testSignatureVerification() throws Exception {
byte[] sig = sign(label, inputBytes, privateKey);
assertTrue(verify(label, inputBytes, publicKey, sig));
byte[] sig = crypto.sign(label, inputBytes, privateKey);
assertTrue(crypto.verify(label, inputBytes, publicKey, sig));
}
@Test
public void testDifferentKeyFailsVerification() throws Exception {
// Generate second private key
KeyPair k2 = generateKeyPair();
KeyPair k2 = crypto.generateSignatureKeyPair();
byte[] privateKey2 = k2.getPrivate().getEncoded();
// calculate the signature with different key, should fail to verify
byte[] sig = sign(label, inputBytes, privateKey2);
assertFalse(verify(label, inputBytes, publicKey, sig));
byte[] sig = crypto.sign(label, inputBytes, privateKey2);
assertFalse(crypto.verify(label, inputBytes, publicKey, sig));
}
@Test
@@ -103,8 +94,8 @@ public abstract class SignatureTest extends BrambleTestCase {
// Generate a second input
byte[] inputBytes2 = TestUtils.getRandomBytes(123);
// calculate the signature with different input, should fail to verify
byte[] sig = sign(label, inputBytes, privateKey);
assertFalse(verify(label, inputBytes2, publicKey, sig));
byte[] sig = crypto.sign(label, inputBytes, privateKey);
assertFalse(crypto.verify(label, inputBytes2, publicKey, sig));
}
@Test
@@ -112,8 +103,8 @@ public abstract class SignatureTest extends BrambleTestCase {
// Generate a second label
String label2 = StringUtils.getRandomString(42);
// calculate the signature with different label, should fail to verify
byte[] sig = sign(label, inputBytes, privateKey);
assertFalse(verify(label2, inputBytes, publicKey, sig));
byte[] sig = crypto.sign(label, inputBytes, privateKey);
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.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.TransportCrypto;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestSecureRandomProvider;
import org.briarproject.bramble.test.TestUtils;
import org.junit.Test;
import java.util.HashSet;
@@ -13,25 +14,25 @@ import java.util.Set;
import static junit.framework.TestCase.assertTrue;
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.test.TestUtils.getSecretKey;
public class TagEncodingTest extends BrambleMockTestCase {
public class TagEncodingTest extends BrambleTestCase {
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
private final TransportCrypto transportCrypto =
new TransportCryptoImpl(crypto);
private final SecretKey tagKey = getSecretKey();
private final CryptoComponent crypto;
private final SecretKey tagKey;
private final long streamNumber = 1234567890;
public TagEncodingTest() {
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
tagKey = TestUtils.getSecretKey();
}
@Test
public void testKeyAffectsTag() throws Exception {
Set<Bytes> set = new HashSet<>();
for (int i = 0; i < 100; i++) {
byte[] tag = new byte[TAG_LENGTH];
SecretKey tagKey = getSecretKey();
transportCrypto.encodeTag(tag, tagKey, PROTOCOL_VERSION,
streamNumber);
SecretKey tagKey = TestUtils.getSecretKey();
crypto.encodeTag(tag, tagKey, PROTOCOL_VERSION, streamNumber);
assertTrue(set.add(new Bytes(tag)));
}
}
@@ -41,8 +42,7 @@ public class TagEncodingTest extends BrambleMockTestCase {
Set<Bytes> set = new HashSet<>();
for (int i = 0; i < 100; i++) {
byte[] tag = new byte[TAG_LENGTH];
transportCrypto.encodeTag(tag, tagKey, PROTOCOL_VERSION + i,
streamNumber);
crypto.encodeTag(tag, tagKey, PROTOCOL_VERSION + i, streamNumber);
assertTrue(set.add(new Bytes(tag)));
}
}
@@ -52,8 +52,7 @@ public class TagEncodingTest extends BrambleMockTestCase {
Set<Bytes> set = new HashSet<>();
for (int i = 0; i < 100; i++) {
byte[] tag = new byte[TAG_LENGTH];
transportCrypto.encodeTag(tag, tagKey, PROTOCOL_VERSION,
streamNumber + i);
crypto.encodeTag(tag, tagKey, PROTOCOL_VERSION, streamNumber + i);
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;
import org.briarproject.bramble.api.crypto.SecretKey;
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.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
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
protected String getBinaryType() {
return "BINARY(32)";
private static final String CREATE_TABLE =
"CREATE TABLE foo (uniqueId BINARY(32), name VARCHAR NOT NULL)";
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
protected String getDriverName() {
return "org.h2.Driver";
@Test
public void testInsertUpdateAndDelete() throws Exception {
// 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
protected Connection openConnection(File db, boolean encrypt)
throws SQLException {
String url = "jdbc:h2:split:" + db.getAbsolutePath();
Properties props = new Properties();
props.setProperty("user", "user");
if (encrypt) {
url += ";CIPHER=AES";
String hex = StringUtils.toHexString(key.getBytes());
props.setProperty("password", hex + " password");
@Test
public void testBatchInsertUpdateAndDelete() throws Exception {
// 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);
}
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
protected void shutdownDatabase(File db, boolean encrypt)
throws SQLException {
// The DB is closed automatically when the connection is closed
@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
};
// 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

@@ -1,6 +0,0 @@
package org.briarproject.bramble.db;
interface BenchmarkTask<T> {
void run(T context) throws Exception;
}

View File

@@ -17,6 +17,7 @@ import org.briarproject.bramble.api.db.NoSuchTransportException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.EventBus;
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.identity.event.LocalAuthorAddedEvent;
import org.briarproject.bramble.api.identity.event.LocalAuthorRemovedEvent;
@@ -47,20 +48,18 @@ 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.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction;
import org.briarproject.bramble.test.TestUtils;
import org.briarproject.bramble.util.StringUtils;
import org.jmock.Expectations;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
@@ -69,9 +68,6 @@ import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERE
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
import static org.briarproject.bramble.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
import static org.briarproject.bramble.db.DatabaseConstants.MAX_OFFERED_MESSAGES;
import static org.briarproject.bramble.test.TestUtils.getAuthor;
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -89,7 +85,9 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
private final ClientId clientId;
private final GroupId groupId;
private final Group group;
private final AuthorId authorId;
private final Author author;
private final AuthorId localAuthorId;
private final LocalAuthor localAuthor;
private final MessageId messageId, messageId1;
private final int size;
@@ -102,15 +100,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
private final Contact contact;
public DatabaseComponentImplTest() {
clientId = new ClientId(getRandomString(123));
clientId = new ClientId(StringUtils.getRandomString(5));
groupId = new GroupId(TestUtils.getRandomId());
byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
group = new Group(groupId, clientId, descriptor);
author = getAuthor();
localAuthor = getLocalAuthor();
authorId = new AuthorId(TestUtils.getRandomId());
author = new Author(authorId, "Alice", new byte[MAX_PUBLIC_KEY_LENGTH]);
localAuthorId = new AuthorId(TestUtils.getRandomId());
long timestamp = System.currentTimeMillis();
localAuthor = new LocalAuthor(localAuthorId, "Bob",
new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp);
messageId = new MessageId(TestUtils.getRandomId());
messageId1 = new MessageId(TestUtils.getRandomId());
long timestamp = System.currentTimeMillis();
size = 1234;
raw = new byte[size];
message = new Message(messageId, groupId, timestamp, raw);
@@ -119,8 +120,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
transportId = new TransportId("id");
maxLatency = Integer.MAX_VALUE;
contactId = new ContactId(234);
contact = new Contact(contactId, author, localAuthor.getId(),
true, true);
contact = new Contact(contactId, author, localAuthorId, true, true);
}
private DatabaseComponent createDatabaseComponent(Database<Object> database,
@@ -142,27 +142,25 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(database).startTransaction();
will(returnValue(txn));
// registerLocalAuthor()
oneOf(database).containsLocalAuthor(txn, localAuthor.getId());
oneOf(database).containsLocalAuthor(txn, localAuthorId);
will(returnValue(false));
oneOf(database).addLocalAuthor(txn, localAuthor);
oneOf(eventBus).broadcast(with(any(LocalAuthorAddedEvent.class)));
// addContact()
oneOf(database).containsLocalAuthor(txn, localAuthor.getId());
oneOf(database).containsLocalAuthor(txn, localAuthorId);
will(returnValue(true));
oneOf(database).containsLocalAuthor(txn, author.getId());
oneOf(database).containsLocalAuthor(txn, authorId);
will(returnValue(false));
oneOf(database).containsContact(txn, author.getId(),
localAuthor.getId());
oneOf(database).containsContact(txn, authorId, localAuthorId);
will(returnValue(false));
oneOf(database).addContact(txn, author, localAuthor.getId(),
true, true);
oneOf(database).addContact(txn, author, localAuthorId, true, true);
will(returnValue(contactId));
oneOf(eventBus).broadcast(with(any(ContactAddedEvent.class)));
oneOf(eventBus).broadcast(with(any(
ContactStatusChangedEvent.class)));
// getContacts()
oneOf(database).getContacts(txn);
will(returnValue(singletonList(contact)));
will(returnValue(Collections.singletonList(contact)));
// addGroup()
oneOf(database).containsGroup(txn, groupId);
will(returnValue(false));
@@ -173,12 +171,12 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
will(returnValue(true));
// getGroups()
oneOf(database).getGroups(txn, clientId);
will(returnValue(singletonList(group)));
will(returnValue(Collections.singletonList(group)));
// removeGroup()
oneOf(database).containsGroup(txn, groupId);
will(returnValue(true));
oneOf(database).getGroupVisibility(txn, groupId);
will(returnValue(emptyMap()));
will(returnValue(Collections.emptyList()));
oneOf(database).removeGroup(txn, groupId);
oneOf(eventBus).broadcast(with(any(GroupRemovedEvent.class)));
oneOf(eventBus).broadcast(with(any(
@@ -189,9 +187,9 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(database).removeContact(txn, contactId);
oneOf(eventBus).broadcast(with(any(ContactRemovedEvent.class)));
// removeLocalAuthor()
oneOf(database).containsLocalAuthor(txn, localAuthor.getId());
oneOf(database).containsLocalAuthor(txn, localAuthorId);
will(returnValue(true));
oneOf(database).removeLocalAuthor(txn, localAuthor.getId());
oneOf(database).removeLocalAuthor(txn, localAuthorId);
oneOf(eventBus).broadcast(with(any(LocalAuthorRemovedEvent.class)));
// endTransaction()
oneOf(database).commitTransaction(txn);
@@ -205,17 +203,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
Transaction transaction = db.startTransaction(false);
try {
db.addLocalAuthor(transaction, localAuthor);
assertEquals(contactId, db.addContact(transaction, author,
localAuthor.getId(), true, true));
assertEquals(singletonList(contact),
assertEquals(contactId,
db.addContact(transaction, author, localAuthorId, true,
true));
assertEquals(Collections.singletonList(contact),
db.getContacts(transaction));
db.addGroup(transaction, group); // First time - listeners called
db.addGroup(transaction, group); // Second time - not called
assertEquals(singletonList(group),
assertEquals(Collections.singletonList(group),
db.getGroups(transaction, clientId));
db.removeGroup(transaction, group);
db.removeContact(transaction, contactId);
db.removeLocalAuthor(transaction, localAuthor.getId());
db.removeLocalAuthor(transaction, localAuthorId);
db.commitTransaction(transaction);
} finally {
db.endTransaction(transaction);
@@ -256,8 +255,13 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
will(returnValue(true));
oneOf(database).containsMessage(txn, messageId);
will(returnValue(false));
oneOf(database).addMessage(txn, message, DELIVERED, true, null);
oneOf(database).addMessage(txn, message, DELIVERED, true);
oneOf(database).mergeMessageMetadata(txn, messageId, metadata);
oneOf(database).getGroupVisibility(txn, groupId);
will(returnValue(Collections.singletonList(contactId)));
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
will(returnValue(false));
oneOf(database).addStatus(txn, contactId, messageId, false, false);
oneOf(database).commitTransaction(txn);
// The message was added, so the listeners should be called
oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class)));
@@ -393,7 +397,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
transaction = db.startTransaction(false);
try {
Ack a = new Ack(singletonList(messageId));
Ack a = new Ack(Collections.singletonList(messageId));
db.receiveAck(transaction, contactId, a);
fail();
} catch (NoSuchContactException expected) {
@@ -414,7 +418,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
transaction = db.startTransaction(false);
try {
Offer o = new Offer(singletonList(messageId));
Offer o = new Offer(Collections.singletonList(messageId));
db.receiveOffer(transaction, contactId, o);
fail();
} catch (NoSuchContactException expected) {
@@ -425,7 +429,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
transaction = db.startTransaction(false);
try {
Request r = new Request(singletonList(messageId));
Request r = new Request(Collections.singletonList(messageId));
db.receiveRequest(transaction, contactId, r);
fail();
} catch (NoSuchContactException expected) {
@@ -483,8 +487,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
// Check whether the pseudonym is in the DB (which it's not)
exactly(3).of(database).startTransaction();
will(returnValue(txn));
exactly(3).of(database).containsLocalAuthor(txn,
localAuthor.getId());
exactly(3).of(database).containsLocalAuthor(txn, localAuthorId);
will(returnValue(false));
exactly(3).of(database).abortTransaction(txn);
}});
@@ -493,7 +496,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
Transaction transaction = db.startTransaction(false);
try {
db.addContact(transaction, author, localAuthor.getId(), true, true);
db.addContact(transaction, author, localAuthorId, true, true);
fail();
} catch (NoSuchLocalAuthorException expected) {
// Expected
@@ -503,7 +506,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
transaction = db.startTransaction(false);
try {
db.getLocalAuthor(transaction, localAuthor.getId());
db.getLocalAuthor(transaction, localAuthorId);
fail();
} catch (NoSuchLocalAuthorException expected) {
// Expected
@@ -513,7 +516,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
transaction = db.startTransaction(false);
try {
db.removeLocalAuthor(transaction, localAuthor.getId());
db.removeLocalAuthor(transaction, localAuthorId);
fail();
} catch (NoSuchLocalAuthorException expected) {
// Expected
@@ -756,20 +759,18 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(database).startTransaction();
will(returnValue(txn));
// registerLocalAuthor()
oneOf(database).containsLocalAuthor(txn, localAuthor.getId());
oneOf(database).containsLocalAuthor(txn, localAuthorId);
will(returnValue(false));
oneOf(database).addLocalAuthor(txn, localAuthor);
oneOf(eventBus).broadcast(with(any(LocalAuthorAddedEvent.class)));
// addContact()
oneOf(database).containsLocalAuthor(txn, localAuthor.getId());
oneOf(database).containsLocalAuthor(txn, localAuthorId);
will(returnValue(true));
oneOf(database).containsLocalAuthor(txn, author.getId());
oneOf(database).containsLocalAuthor(txn, authorId);
will(returnValue(false));
oneOf(database).containsContact(txn, author.getId(),
localAuthor.getId());
oneOf(database).containsContact(txn, authorId, localAuthorId);
will(returnValue(false));
oneOf(database).addContact(txn, author, localAuthor.getId(),
true, true);
oneOf(database).addContact(txn, author, localAuthorId, true, true);
will(returnValue(contactId));
oneOf(eventBus).broadcast(with(any(ContactAddedEvent.class)));
oneOf(eventBus).broadcast(with(any(
@@ -791,8 +792,9 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
Transaction transaction = db.startTransaction(false);
try {
db.addLocalAuthor(transaction, localAuthor);
assertEquals(contactId, db.addContact(transaction, author,
localAuthor.getId(), true, true));
assertEquals(contactId,
db.addContact(transaction, author, localAuthorId, true,
true));
db.commitTransaction(transaction);
} finally {
db.endTransaction(transaction);
@@ -1020,7 +1022,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
Transaction transaction = db.startTransaction(false);
try {
Ack a = new Ack(singletonList(messageId));
Ack a = new Ack(Collections.singletonList(messageId));
db.receiveAck(transaction, contactId, a);
db.commitTransaction(transaction);
} finally {
@@ -1040,7 +1042,12 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
will(returnValue(VISIBLE));
oneOf(database).containsMessage(txn, messageId);
will(returnValue(false));
oneOf(database).addMessage(txn, message, UNKNOWN, false, contactId);
oneOf(database).addMessage(txn, message, UNKNOWN, false);
oneOf(database).getGroupVisibility(txn, groupId);
will(returnValue(Collections.singletonList(contactId)));
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
will(returnValue(false));
oneOf(database).addStatus(txn, contactId, messageId, true, true);
// Second time
oneOf(database).containsContact(txn, contactId);
will(returnValue(true));
@@ -1190,7 +1197,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
Transaction transaction = db.startTransaction(false);
try {
Request r = new Request(singletonList(messageId));
Request r = new Request(Collections.singletonList(messageId));
db.receiveRequest(transaction, contactId, r);
db.commitTransaction(transaction);
} finally {
@@ -1199,11 +1206,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
}
@Test
public void testChangingVisibilityFromInvisibleToVisibleCallsListeners()
throws Exception {
AtomicReference<GroupVisibilityUpdatedEvent> event =
new AtomicReference<>();
public void testChangingVisibilityCallsListeners() throws Exception {
context.checking(new Expectations() {{
oneOf(database).startTransaction();
will(returnValue(txn));
@@ -1212,13 +1215,16 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
oneOf(database).containsGroup(txn, groupId);
will(returnValue(true));
oneOf(database).getGroupVisibility(txn, contactId, groupId);
will(returnValue(INVISIBLE));
will(returnValue(INVISIBLE)); // Not yet visible
oneOf(database).addGroupVisibility(txn, contactId, groupId, false);
oneOf(database).getMessageIds(txn, groupId);
will(returnValue(Collections.singletonList(messageId)));
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
will(returnValue(false));
oneOf(database).addStatus(txn, contactId, messageId, false, false);
oneOf(database).commitTransaction(txn);
oneOf(eventBus).broadcast(with(any(
GroupVisibilityUpdatedEvent.class)));
will(new CaptureArgumentAction<>(event,
GroupVisibilityUpdatedEvent.class, 0));
}});
DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown);
@@ -1230,48 +1236,6 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
} finally {
db.endTransaction(transaction);
}
GroupVisibilityUpdatedEvent e = event.get();
assertNotNull(e);
assertEquals(singletonList(contactId), e.getAffectedContacts());
}
@Test
public void testChangingVisibilityFromVisibleToInvisibleCallsListeners()
throws Exception {
AtomicReference<GroupVisibilityUpdatedEvent> event =
new AtomicReference<>();
context.checking(new Expectations() {{
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).containsContact(txn, contactId);
will(returnValue(true));
oneOf(database).containsGroup(txn, groupId);
will(returnValue(true));
oneOf(database).getGroupVisibility(txn, contactId, groupId);
will(returnValue(VISIBLE));
oneOf(database).removeGroupVisibility(txn, contactId, groupId);
oneOf(database).commitTransaction(txn);
oneOf(eventBus).broadcast(with(any(
GroupVisibilityUpdatedEvent.class)));
will(new CaptureArgumentAction<>(event,
GroupVisibilityUpdatedEvent.class, 0));
}});
DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown);
Transaction transaction = db.startTransaction(false);
try {
db.setGroupVisibility(transaction, contactId, groupId, INVISIBLE);
db.commitTransaction(transaction);
} finally {
db.endTransaction(transaction);
}
GroupVisibilityUpdatedEvent e = event.get();
assertNotNull(e);
assertEquals(singletonList(contactId), e.getAffectedContacts());
}
@Test
@@ -1303,8 +1267,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
@Test
public void testTransportKeys() throws Exception {
TransportKeys transportKeys = createTransportKeys();
Map<ContactId, TransportKeys> keys =
singletonMap(contactId, transportKeys);
Map<ContactId, TransportKeys> keys = Collections.singletonMap(
contactId, transportKeys);
context.checking(new Expectations() {{
// startTransaction()
oneOf(database).startTransaction();
@@ -1442,10 +1406,10 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
context.checking(new Expectations() {{
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).containsLocalAuthor(txn, localAuthor.getId());
oneOf(database).containsLocalAuthor(txn, localAuthorId);
will(returnValue(true));
// Contact is a local identity
oneOf(database).containsLocalAuthor(txn, author.getId());
oneOf(database).containsLocalAuthor(txn, authorId);
will(returnValue(true));
oneOf(database).abortTransaction(txn);
}});
@@ -1455,7 +1419,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
Transaction transaction = db.startTransaction(false);
try {
db.addContact(transaction, author, localAuthor.getId(), true, true);
db.addContact(transaction, author, localAuthorId, true, true);
fail();
} catch (ContactExistsException expected) {
// Expected
@@ -1469,13 +1433,12 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
context.checking(new Expectations() {{
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).containsLocalAuthor(txn, localAuthor.getId());
oneOf(database).containsLocalAuthor(txn, localAuthorId);
will(returnValue(true));
oneOf(database).containsLocalAuthor(txn, author.getId());
oneOf(database).containsLocalAuthor(txn, authorId);
will(returnValue(false));
// Contact already exists for this local identity
oneOf(database).containsContact(txn, author.getId(),
localAuthor.getId());
oneOf(database).containsContact(txn, authorId, localAuthorId);
will(returnValue(true));
oneOf(database).abortTransaction(txn);
}});
@@ -1485,7 +1448,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
Transaction transaction = db.startTransaction(false);
try {
db.addContact(transaction, author, localAuthor.getId(), true, true);
db.addContact(transaction, author, localAuthorId, true, true);
fail();
} catch (ContactExistsException expected) {
// Expected
@@ -1513,8 +1476,13 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
will(returnValue(true));
oneOf(database).containsMessage(txn, messageId);
will(returnValue(false));
oneOf(database).addMessage(txn, message, DELIVERED, true, null);
oneOf(database).addMessage(txn, message, DELIVERED, true);
oneOf(database).getGroupVisibility(txn, groupId);
will(returnValue(Collections.singletonList(contactId)));
oneOf(database).mergeMessageMetadata(txn, messageId, metadata);
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
will(returnValue(false));
oneOf(database).addStatus(txn, contactId, messageId, false, false);
// addMessageDependencies()
oneOf(database).containsMessage(txn, messageId);
will(returnValue(true));

Some files were not shown because too many files have changed in this diff Show More