Compare commits

..

176 Commits

Author SHA1 Message Date
akwizgran
cbb65ec807 Refactor key agreement connection choosing. 2018-02-27 17:51:25 +00:00
akwizgran
53b38d83e8 Ensure connections are closed when contact is removed. 2018-02-27 17:18:29 +00:00
akwizgran
692db742cf Ensure all key agreement connection tasks are stopped. 2018-02-26 14:05:59 +00:00
akwizgran
5b177c9901 Log Bluetooth bonding changes. 2018-02-26 14:03:45 +00:00
akwizgran
c00bf2b2cd Close old connections to stay within limit. 2018-02-23 12:06:54 +00:00
akwizgran
55150fe02a Limit the number of open Bluetooth connections. 2018-02-23 10:13:22 +00:00
Torsten Grote
3f1fb1ca1c Merge branch '346-qr-code-optimisations' into 'master'
Improve QR code scanning on phones with high res cameras and slow CPUs

Closes #1068

See merge request akwizgran/briar!699
2018-02-22 18:09:52 +00:00
akwizgran
bfdc79ac60 Use ConstraintLayout for intro fragment. 2018-02-22 17:10:19 +00:00
akwizgran
06897569d4 Add javadoc links. 2018-02-22 17:10:18 +00:00
akwizgran
66e3f6deba Crop camera preview before looking for QR code. 2018-02-22 17:10:16 +00:00
akwizgran
528a52d0f7 Add landscape layout for QR code fragment. 2018-02-22 17:09:33 +00:00
Torsten Grote
d395f0866a Merge branch '1173-qr-code-version' into 'master'
Use first byte of QR code payload for format version

Closes #1173

See merge request akwizgran/briar!702
2018-02-22 16:40:30 +00:00
akwizgran
363dfbc6aa Merge branch '1164-store-bluetooth-properties' into 'master'
Store Bluetooth address and UUID at first startup

Closes #1164

See merge request akwizgran/briar!694
2018-02-22 15:11:01 +00:00
Torsten Grote
c6f2941e74 Merge branch '542-include-requested-messages' into 'master'
Include requested messages when getting next send time

See merge request akwizgran/briar!704
2018-02-22 13:09:44 +00:00
akwizgran
a61cd01336 Address review comments. 2018-02-22 12:52:49 +00:00
akwizgran
10f63ad60b Include requested messages when getting next send time. 2018-02-22 12:46:33 +00:00
akwizgran
0b781cf272 Use first byte of QR code payload for format version. 2018-02-22 11:59:06 +00:00
akwizgran
6dc7277771 Merge branch '542-retransmission' into 'master'
Don't poll for retransmission

Closes #542

See merge request akwizgran/briar!695
2018-02-22 11:07:21 +00:00
akwizgran
d880b14e0c Merge branch '1134-old-qr-code-error' into 'master'
Show an error fragment when an unsupported QR code is scanned

Closes #1134

See merge request akwizgran/briar!675
2018-02-22 10:55:44 +00:00
Torsten Grote
f88c68eff4 Merge branch '545-denormalise-statuses' into 'master'
Add denormalised columns to statuses table

See merge request akwizgran/briar!691
2018-02-19 16:53:55 +00:00
akwizgran
5fce8cbe0a Don't poll for retransmission. 2018-02-19 16:27:04 +00:00
akwizgran
439654e71d Test that visibility change affects expected contacts. 2018-02-19 16:25:02 +00:00
akwizgran
7fe502e3cc Add denormalised columns to statuses table. 2018-02-19 16:07:08 +00:00
akwizgran
aa07d0cadd Merge branch '509-tap-viewfinder-to-auto-focus' into 'master'
Tap viewfinder to restart auto focus

Closes #509

See merge request akwizgran/briar!697
2018-02-19 15:57:49 +00:00
akwizgran
d9cca3d9eb Merge branch '1137-stop-polling-disabled-plugins' into 'master'
Don't poll disabled transport plugins

Closes #1137

See merge request akwizgran/briar!698
2018-02-19 14:45:56 +00:00
Torsten Grote
94c5f61cc3 Merge branch 'raw-qr-codes' into 'master'
Use raw byte mode for QR codes

See merge request akwizgran/briar!696
2018-02-19 14:44:38 +00:00
Torsten Grote
9cce0d8e15 Show an error fragment when an unsupported QR code is scanned 2018-02-19 09:41:16 -03:00
Torsten Grote
44488b5187 Merge branch 'multiset' into 'master'
Use a multiset for counting things

See merge request akwizgran/briar!688
2018-02-19 12:36:01 +00:00
akwizgran
4d6ac13338 Bump DB schema version as public key format has changed. 2018-02-14 14:29:06 +00:00
akwizgran
ef3afa7832 Don't poll disabled transport plugins. 2018-02-10 11:42:21 +00:00
akwizgran
687ea132f6 Tap viewfinder to restart auto focus. 2018-02-09 17:51:49 +00:00
akwizgran
452e544ed1 Use raw byte mode for QR codes. 2018-02-09 16:57:13 +00:00
akwizgran
6b60509122 Add curve25519-java to ProGuard rules. 2018-02-09 16:45:15 +00:00
akwizgran
e01e971822 Merge branch '236-curve25519' into 'master'
Use Curve25519 for key agreement

Closes #236

See merge request akwizgran/briar!693
2018-02-09 10:13:56 +00:00
akwizgran
186a7db8cb Merge branch '236-use-ed25519' into 'master'
Use Ed25519 for signatures

See merge request akwizgran/briar!686
2018-02-09 10:12:56 +00:00
akwizgran
565452f7d3 Don't set running = true until properties have been loaded. 2018-02-08 15:03:49 +00:00
akwizgran
de7e3dd225 Store Bluetooth address and UUID at first startup. 2018-02-08 14:56:04 +00:00
akwizgran
8bdf04a289 Clamp private keys, add test vectors. 2018-02-02 22:24:28 +00:00
akwizgran
56a5b8df87 Use Curve25519 for key agreement. 2018-02-02 17:52:18 +00:00
akwizgran
ad241a14e3 Use WhisperSystems Curve25519 library. 2018-02-02 17:07:43 +00:00
akwizgran
2a7bdcd270 Add Curve25519 and Ed25519 to performance tests.
Note: Curve25519 is tested using standard ECDH and ECDHC over the Curve25519 curve.
2018-02-02 17:06:42 +00:00
Torsten Grote
88c61ecfb5 Merge branch '594-db-migrations' into 'master'
Migrate schema when opening database

Closes #594

See merge request akwizgran/briar!680
2018-02-02 11:49:03 +00:00
Torsten Grote
bee9dbb9c4 Merge branch '545-remove-unnecessary-indexes' into 'master'
Remove unnecessary DB indexes

See merge request akwizgran/briar!687
2018-02-01 17:30:47 +00:00
akwizgran
36e0f97d82 Remove unnecessary DB indexes. 2018-02-01 17:21:17 +00:00
akwizgran
8bb08a2af9 Throw meaningful exceptions for schema errors. 2018-02-01 17:07:54 +00:00
akwizgran
6bf2cb69c5 Use Ed25519 for signatures. 2018-02-01 16:56:50 +00:00
akwizgran
7a1247e325 Add test vectors for Ed25519. 2018-02-01 16:17:51 +00:00
akwizgran
ce5879bdb5 Merge branch '1162-redundant-db-tasks' into 'master'
Avoid queueing redundant DB tasks during sync

Closes #1162

See merge request akwizgran/briar!681
2018-02-01 15:06:43 +00:00
akwizgran
55221a5066 Merge branch '1148-wrong-network-interface' into 'master'
Prefer LAN addresses with longer prefixes

Closes #1148

See merge request akwizgran/briar!659
2018-02-01 10:54:21 +00:00
akwizgran
9e7f1df8e9 Merge branch '1143-screen-overlay-dialog' into 'master'
Don't show screen overlay dialog if all overlay apps have been allowed

Closes #1143

See merge request akwizgran/briar!658
2018-02-01 10:46:41 +00:00
akwizgran
ec7e599143 Merge branch '1116-samsung-back-crash' into 'master'
Workaround for Samsung crash in Android 4.4

Closes #1116

See merge request akwizgran/briar!674
2018-02-01 10:41:09 +00:00
akwizgran
e0a67d1eb9 Remove unused argument. 2018-02-01 10:39:26 +00:00
akwizgran
a50ded2d50 Simplify dialog handling, work around Android bug. 2018-02-01 10:37:56 +00:00
akwizgran
dab9a3e73d Update screen overlay warning text. 2018-02-01 10:36:47 +00:00
akwizgran
dae8e6d759 Re-show dialog when activity resumes or is recreated. 2018-02-01 10:36:47 +00:00
akwizgran
60d38b034d Set layout weight so checkbox is visible. 2018-02-01 10:36:47 +00:00
akwizgran
863c908267 Cache the list of overlay apps. 2018-02-01 10:36:47 +00:00
akwizgran
753068288f Allow filtered taps if all overlay apps are whitelisted. 2018-02-01 10:36:46 +00:00
akwizgran
f9eda0b096 Fix test expectations. 2018-02-01 10:29:05 +00:00
akwizgran
f4401ee524 Add comment. 2018-02-01 10:22:10 +00:00
akwizgran
3dbc3cef56 Apply more than one migration if suitable. 2018-02-01 10:14:34 +00:00
akwizgran
bf4ecd21aa Add a generic multiset implementation. 2018-02-01 09:55:10 +00:00
akwizgran
ea3ada5573 Avoid queueing redundant DB tasks during sync. 2018-01-31 17:26:42 +00:00
akwizgran
9889f86f69 Add unit tests for migration logic. 2018-01-31 15:41:21 +00:00
akwizgran
4d62447a86 Migrate database schema if a migration is available. 2018-01-31 12:07:58 +00:00
akwizgran
7ec05ac0cd Merge branch '790-ask-before-turning-on-bluetooth' into 'master'
Ask before turning on Bluetooth to add a contact

Closes #790

See merge request akwizgran/briar!664
2018-01-29 15:37:41 +00:00
akwizgran
1b2a1d658d Merge branch '1007-samsung-transition-npe-fix' into 'master'
Another attempt at fixing an infamous Samsung activity transition NPE

Closes #1007

See merge request akwizgran/briar!677
2018-01-29 14:20:08 +00:00
Torsten Grote
a2bbc5e455 Another attempt at fixing an infamous Samsung activity transition NPE 2018-01-29 10:55:36 -02:00
Torsten Grote
006cb067ad Update translations
New translations: br, nl, he, sv, cs, ja
2018-01-29 10:33:17 -02:00
Torsten Grote
a2e422a23e Workaround for Samsung crash in Android 4.4
Closes #1116
2018-01-24 11:15:14 -02:00
akwizgran
02cec9bacb Merge branch 'tor-plugin-detect-connectivity-loss' into 'master'
Tor plugin should detect connectivity loss

See merge request akwizgran/briar!670
2018-01-23 17:15:09 +00:00
akwizgran
fcd9b20161 Merge branch 'scrypt' into 'master'
Use scrypt for password-based key derivation

See merge request akwizgran/briar!665
2018-01-22 15:18:36 +00:00
akwizgran
204711e5db Reduce minimum scrypt cost for low-end devices. 2018-01-22 14:39:58 +00:00
akwizgran
64c129d399 Add format version to password-encrypted database key. 2018-01-22 14:39:58 +00:00
akwizgran
6bdb099aa9 Use scrypt for password-based key derivation. 2018-01-22 14:39:49 +00:00
Torsten Grote
3e55be8a82 Merge branch 'change-password-activity' into 'master'
ChangePasswordActivity should extend BriarActivity

See merge request akwizgran/briar!671
2018-01-20 14:11:32 +00:00
akwizgran
8f37957a46 Use scheduler service to schedule connectivity checks. 2018-01-19 12:29:14 +00:00
akwizgran
a5386e0183 Listen for a wider range of connectivity-related events. 2018-01-19 12:28:22 +00:00
akwizgran
117e88bf1f Use Tor's OR connection events to detect lost connectivity. 2018-01-19 12:28:22 +00:00
akwizgran
f47900c4d3 ChangePasswordActivity should extend BriarActivity. 2018-01-19 11:50:27 +00:00
akwizgran
f641e16512 Merge branch 'blake2b' into 'master'
Use BLAKE2b for hashing

See merge request akwizgran/briar!667
2018-01-19 11:04:27 +00:00
akwizgran
df0613f290 Fix import of wrong Immutable annotation. 2018-01-19 09:54:19 +00:00
akwizgran
711475d45a Merge branch '1001-bluetooth-connects-to-contacts' into 'master'
Don't make Bluetooth connections when configured not to

Closes #1001

See merge request akwizgran/briar!663
2018-01-17 11:13:27 +00:00
akwizgran
3fd47fc1c7 Merge branch 'bluetooth-refactoring' into 'master'
Factor shared Bluetooth code into superclass

Closes #831

See merge request akwizgran/briar!662
2018-01-17 11:11:20 +00:00
Torsten Grote
a1a946edea Merge branch '617-author-versioning' into 'master'
Use a versioned format for encoding authors

See merge request akwizgran/briar!661
2018-01-16 18:36:32 +00:00
akwizgran
699b037a3e Remove redundant constant for max blog name length. 2018-01-16 17:39:49 +00:00
akwizgran
e474042af7 Use author encoding and parsing helpers everywhere. 2018-01-16 17:38:21 +00:00
akwizgran
68634e0f28 Add helper method for encoding authors. 2018-01-16 17:22:35 +00:00
akwizgran
1d81110fe5 Bump database schema version. 2018-01-16 15:32:52 +00:00
akwizgran
030b9ef053 Use a versioned format for encoding authors. 2018-01-16 15:30:59 +00:00
akwizgran
7d8d169b0a Merge branch '1092-denormalise-message-metadata' into 'master'
Add denormalised state column to messageMetadata table

Closes #1092

See merge request akwizgran/briar!654
2018-01-16 13:01:49 +00:00
akwizgran
11e2d4ecfb Fix indentation. 2018-01-16 12:54:16 +00:00
Torsten Grote
80ad5d8c7b Merge branch '1145-avoid-unnecessary-db-queries' into 'master'
Avoid unnecessary DB queries when starting clients

Closes #1145

See merge request akwizgran/briar!660
2018-01-16 12:03:49 +00:00
akwizgran
53a15c05aa Merge branch 'prefer-project-modules' into 'master'
Prefer project modules over prebuilt dependencies

See merge request akwizgran/briar!666
2018-01-12 17:33:53 +00:00
akwizgran
45bc6a51b0 Use BLAKE2b for hashing. 2018-01-12 17:33:28 +00:00
akwizgran
db21dcedb1 Prefer project modules over prebuilt dependencies. 2018-01-12 16:28:40 +00:00
akwizgran
96c8274091 Ask before turning on Bluetooth to add a contact. 2018-01-10 17:47:43 +00:00
akwizgran
0c7c465ef7 Remove unnecessary executor calls. 2018-01-10 16:55:17 +00:00
akwizgran
fd6bf42ea4 Don't make Bluetooth connections when configured not to. 2018-01-10 16:51:06 +00:00
akwizgran
0c5976b287 Factor shared Bluetooth code into superclass. 2018-01-10 13:03:07 +00:00
akwizgran
1a4aa7f065 Add tests for link-local addresses. 2018-01-10 11:00:13 +00:00
akwizgran
f11a97631f Avoid unnecessary DB queries when starting clients. 2018-01-07 11:24:41 +00:00
akwizgran
bf953012af Prefer LAN addresses with longer prefixes. 2018-01-05 14:25:10 +00:00
akwizgran
2f049fbead Merge branch '1132-upgrade-tor-0.2.9.14' into 'master'
Upgrade Tor to 0.2.9.14, GeoIP to 2017-11-06

Closes #1132

See merge request akwizgran/briar!653
2017-12-22 13:49:35 +00:00
akwizgran
49a6f2af3a Merge branch '1129-send-on-ctrl-enter' into 'master'
send message on ctrl + enter

Closes #1129

See merge request akwizgran/briar!649
2017-12-22 11:19:31 +00:00
sbkaf
3d6c02c27c send message on ctrl + enter 2017-12-22 11:06:15 +00:00
akwizgran
a1cfc0ec1d Merge branch '545-db-benchmarks' into 'master'
Database performance tests

See merge request akwizgran/briar!652
2017-12-18 18:15:05 +00:00
akwizgran
2f584501fe Add denormalised state column to messageMetadata table. 2017-12-18 18:01:03 +00:00
akwizgran
b524cec6af Upgrade Tor to 0.2.9.14, GeoIP to 2017-11-06. 2017-12-18 15:35:25 +00:00
akwizgran
0a7b810fce Disable logging for DB performance tests only. 2017-12-15 15:43:33 +00:00
akwizgran
f7a3b0f6ca Use diamond operator. 2017-12-15 15:26:25 +00:00
akwizgran
b095dab77a Remove unused test methods. 2017-12-15 15:24:20 +00:00
Torsten Grote
b6b7ab622d Merge branch 'fix-plugin-manager-test' into 'master'
Fix test expectations

See merge request !651
2017-12-14 16:01:50 +00:00
akwizgran
55e674624a Fix test expectations. 2017-12-14 15:47:26 +00:00
akwizgran
88a799df45 Rename some classes that don't involve JDBC. 2017-12-12 16:18:25 +00:00
akwizgran
4bb726ac9a Include test name in trace file name. 2017-12-12 12:17:34 +00:00
akwizgran
5a53665e96 Add trace tests. 2017-12-11 18:08:14 +00:00
akwizgran
cf51a1e299 Add sanity check for performance comparisons. 2017-12-11 17:29:14 +00:00
akwizgran
04802cc8cd Get class name using getClass(). 2017-12-11 16:39:49 +00:00
akwizgran
d0c1be0c32 Add tests to compare benchmarks. 2017-12-11 16:10:07 +00:00
akwizgran
0b9894a0f6 More performance tests. 2017-12-11 16:10:07 +00:00
akwizgran
1a912a29f8 Use the Mann-Whitney U test to determine steady state. 2017-12-11 16:10:07 +00:00
akwizgran
596c140310 Use a single output file for all tests. 2017-12-11 16:10:07 +00:00
akwizgran
302ced1476 Measure the first run to see the extent of warm-up. 2017-12-11 16:10:06 +00:00
akwizgran
3178c16bac Reuse test database to keep runtime reasonable. 2017-12-11 16:10:06 +00:00
akwizgran
341d18656d Add run configurations for DB benchmarks. 2017-12-11 16:10:00 +00:00
akwizgran
074755c0a8 Add database benchmarks. 2017-12-11 16:09:55 +00:00
akwizgran
5d528fce74 Merge branch '1112-screen-filter-crash' into 'master'
Don't show screen filter dialog after onSaveInstanceState()

Closes #1112

See merge request !642
2017-12-07 13:06:37 +00:00
Torsten Grote
c80edc99b2 Merge branch '617-protocol-versioning' into 'master'
Protocol versioning

See merge request !646
2017-12-07 12:17:50 +00:00
akwizgran
33378d9920 Merge branch '1088-huawei-whitelisting' into 'master'
Add button for Huawei's power manager to setup wizard

Closes #1088

See merge request !633
2017-12-05 17:22:44 +00:00
akwizgran
85a6e394b9 Merge branch '1127-notification-channels' into 'master'
Use channels for all notifications

Closes #1127

See merge request !643
2017-12-05 16:48:16 +00:00
akwizgran
f2f98f28a3 Include client version in group ID derivation. 2017-12-05 16:07:17 +00:00
akwizgran
d92e042971 Include protocol version in message ID derivation. 2017-12-05 16:07:17 +00:00
akwizgran
6d6e47409f Include protocol version in group ID derivation. 2017-12-05 16:07:17 +00:00
akwizgran
0084e51263 Include protocol version in key derivation. 2017-12-05 16:07:17 +00:00
akwizgran
32e0b39771 Include protocol version in shared secret derivation. 2017-12-05 16:07:17 +00:00
akwizgran
7bb51f77ec Merge branch '545-hyper-sql' into 'master'
Add HyperSQL as an alternative DB library for testing

See merge request !619
2017-12-05 16:05:42 +00:00
akwizgran
c777a57a7d Merge branch '617-crypto-labels' into 'master'
Use namespaced labels for all crypto operations

See merge request !632
2017-12-05 16:04:35 +00:00
akwizgran
def5966767 Sort order of channel IDs affects UI of Settings app. 2017-12-05 15:41:32 +00:00
akwizgran
14b18e9d42 Merge branch '1120-crash-removing-shutdown-hook' into 'master'
Don't remove shutdown hook when closing DB

Closes #1120

See merge request !644
2017-12-05 14:43:36 +00:00
akwizgran
fcff8d92f3 Don't remove shutdown hook when closing DB. 2017-12-05 12:27:41 +00:00
akwizgran
ea0e00f4ac Use channels for all notifications. 2017-12-05 12:09:22 +00:00
Torsten Grote
f199105f6c Add button for Huawei's power manager to setup wizard 2017-12-04 17:26:19 -02:00
akwizgran
b23c0b599b Don't show screen filter dialog after onSaveInstanceState(). 2017-12-04 15:25:12 +00:00
akwizgran
0327d4f38a Merge branch '1007-samsung-transition-npe' into 'master'
Don't set scene transition for Samsung devices running Android 7.0

Closes #1007

See merge request !640
2017-12-04 14:20:28 +00:00
akwizgran
4397a45519 Add links to protocol specs (which are out of date). 2017-12-04 14:16:49 +00:00
Torsten Grote
365e159539 Don't set scene transition for Samsung devices running Android 7.0 2017-12-04 10:51:32 -02:00
akwizgran
8171dd8bc9 Merge branch 'more-lambdas' into 'master'
Replace a few runnables with lambdas

See merge request !638
2017-12-01 17:42:58 +00:00
akwizgran
c4beb60c22 Add dependency hash for HyperSQL. 2017-12-01 17:41:45 +00:00
Torsten Grote
4b88f0d9f1 Merge branch 'package-name-briar-android' into 'master'
Change package name, bump expiry date

See merge request !637
2017-12-01 16:36:47 +00:00
akwizgran
116419f505 Don't show expiry warning for release builds. 2017-12-01 16:18:47 +00:00
akwizgran
87b2624aa8 Set IS_BETA_BUILD to false. 2017-12-01 16:16:37 +00:00
akwizgran
71fe6f3148 Bump expiry date to 31 December 2018. 2017-12-01 16:11:06 +00:00
akwizgran
21df6cb809 Change package name, version number for release branch. 2017-12-01 15:59:04 +00:00
akwizgran
1f0c385a5c Merge branch '1124-notification-channel-crash' into 'master'
Use NotificationChannel for foreground service to avoid crash on Android 8.1

Closes #1124

See merge request !634
2017-12-01 15:53:05 +00:00
Torsten Grote
986ea05fb2 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:44:51 -02:00
akwizgran
90e395506f Remove unnecessary DB_CLOSE_ON_EXIT parameter. 2017-12-01 14:13:37 +00:00
akwizgran
cf54360a93 Rename columns whose names are SQL keywords. 2017-12-01 14:13:33 +00:00
akwizgran
a5d4ea4477 Add HSQLDB as an alternative DB library. 2017-12-01 14:13:26 +00:00
akwizgran
030b52261d Replace a few runnables with lambdas. 2017-12-01 14:01:32 +00:00
akwizgran
a50e13c2e3 Merge branch 'transport-property-manager-cleanup' into 'master'
Simplify management of old transport property updates

See merge request !629
2017-11-30 17:46:15 +00:00
akwizgran
c8326103b4 Merge branch 'git-rev-parse-workaround' 2017-11-30 17:39:33 +00:00
akwizgran
0f2beee813 Use namespaced labels for transport key derivation. 2017-11-30 17:36:04 +00:00
akwizgran
d2348a4e7d Remove method that just wraps a MAC call. 2017-11-30 17:08:59 +00:00
akwizgran
cc87e6fd1f Factor out key agreement crypto from CryptoComponent. 2017-11-30 17:08:59 +00:00
akwizgran
1843aea2a7 Factor out transport crypto from CryptoComponent. 2017-11-30 17:08:59 +00:00
akwizgran
9f7021acd3 Include namespaced labels in crypto operations. 2017-11-30 17:08:56 +00:00
Torsten Grote
ddea031cbf Merge branch '1110-signature-labels' into 'master'
Don't use ClientId.toString() for signature labels

Closes #1110

See merge request !631
2017-11-30 17:03:07 +00:00
akwizgran
f0d8532f71 Specify 7 characters for Git revision. 2017-11-30 16:55:41 +00:00
akwizgran
4883d157dc Simplify management of old transport property updates. 2017-11-30 16:43:33 +00:00
akwizgran
a1bec1e927 Merge branch 'ed25519' into 'master'
Add support for Ed25519 signatures

See merge request !627
2017-11-30 16:22:04 +00:00
akwizgran
37d4d79c64 Don't rethrow SignatureException as RuntimeException. 2017-11-29 17:29:32 +00:00
akwizgran
05bc3f6a71 Don't use ClientId.toString() for signature labels. 2017-11-29 16:57:00 +00:00
akwizgran
8b3960781a Fix a typo. 2017-11-23 17:34:40 +00:00
akwizgran
f3de4f53c5 Add ProGuard rule to keep EdDSA classes. 2017-11-23 16:18:30 +00:00
akwizgran
166fc2948c Add support for Ed25519 signatures. 2017-11-23 16:17:41 +00:00
305 changed files with 9154 additions and 7869 deletions

View File

@@ -0,0 +1,23 @@
<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

@@ -0,0 +1,23 @@
<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 1619
versionName "0.16.19"
versionCode 1700
versionName "0.17.0"
consumerProguardFiles 'proguard-rules.txt'
}
@@ -43,6 +43,7 @@ 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,6 +8,10 @@
-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

@@ -21,6 +21,8 @@ import org.briarproject.bramble.util.AndroidUtils;
import java.io.Closeable;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
@@ -37,7 +39,16 @@ import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERA
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
import static android.bluetooth.BluetoothAdapter.STATE_OFF;
import static android.bluetooth.BluetoothAdapter.STATE_ON;
import static android.bluetooth.BluetoothDevice.ACTION_BOND_STATE_CHANGED;
import static android.bluetooth.BluetoothDevice.BOND_BONDED;
import static android.bluetooth.BluetoothDevice.BOND_BONDING;
import static android.bluetooth.BluetoothDevice.BOND_NONE;
import static android.bluetooth.BluetoothDevice.EXTRA_BOND_STATE;
import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE;
import static android.bluetooth.BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -55,10 +66,12 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
// Non-null if the plugin started successfully
private volatile BluetoothAdapter adapter = null;
AndroidBluetoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor,
AndroidBluetoothPlugin(BluetoothConnectionManager connectionManager,
Executor ioExecutor, AndroidExecutor androidExecutor,
Context appContext, SecureRandom secureRandom, Backoff backoff,
DuplexPluginCallback callback, int maxLatency) {
super(ioExecutor, secureRandom, backoff, callback, maxLatency);
super(connectionManager, ioExecutor, secureRandom, backoff, callback,
maxLatency);
this.androidExecutor = androidExecutor;
this.appContext = appContext;
}
@@ -70,6 +83,7 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_STATE_CHANGED);
filter.addAction(ACTION_SCAN_MODE_CHANGED);
filter.addAction(ACTION_BOND_STATE_CHANGED);
receiver = new BluetoothStateReceiver();
appContext.registerReceiver(receiver, filter);
}
@@ -154,7 +168,8 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
}
private DuplexTransportConnection wrapSocket(BluetoothSocket s) {
return new AndroidBluetoothTransportConnection(this, s);
return new AndroidBluetoothTransportConnection(this,
connectionManager, s);
}
@Override
@@ -165,6 +180,17 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
@Override
DuplexTransportConnection connectTo(String address, String uuid)
throws IOException {
if (LOG.isLoggable(INFO)) {
boolean found = false;
List<String> addresses = new ArrayList<>();
for (BluetoothDevice d : adapter.getBondedDevices()) {
addresses.add(scrubMacAddress(d.getAddress()));
if (d.getAddress().equals(address)) found = true;
}
LOG.info("Bonded devices: " + addresses);
if (found) LOG.info("Connecting to bonded device");
else LOG.info("Connecting to unbonded device");
}
BluetoothDevice d = adapter.getRemoteDevice(address);
UUID u = UUID.fromString(uuid);
BluetoothSocket s = null;
@@ -190,16 +216,42 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
@Override
public void onReceive(Context ctx, Intent intent) {
int state = intent.getIntExtra(EXTRA_STATE, 0);
if (state == STATE_ON) onAdapterEnabled();
else if (state == STATE_OFF) onAdapterDisabled();
int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, 0);
if (scanMode == SCAN_MODE_NONE) {
LOG.info("Scan mode: None");
} else if (scanMode == SCAN_MODE_CONNECTABLE) {
LOG.info("Scan mode: Connectable");
} else if (scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
LOG.info("Scan mode: Discoverable");
String action = intent.getAction();
if (ACTION_STATE_CHANGED.equals(action)) {
int state = intent.getIntExtra(EXTRA_STATE, 0);
if (state == STATE_ON) onAdapterEnabled();
else if (state == STATE_OFF) onAdapterDisabled();
} else if (ACTION_SCAN_MODE_CHANGED.equals(action)) {
int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, 0);
if (scanMode == SCAN_MODE_NONE) {
LOG.info("Scan mode: None");
} else if (scanMode == SCAN_MODE_CONNECTABLE) {
LOG.info("Scan mode: Connectable");
} else if (scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
LOG.info("Scan mode: Discoverable");
}
} else if (ACTION_BOND_STATE_CHANGED.equals(action)) {
BluetoothDevice d = intent.getParcelableExtra(EXTRA_DEVICE);
if (LOG.isLoggable(INFO)) {
LOG.info("Bond state changed for "
+ scrubMacAddress(d.getAddress()));
}
int oldState = intent.getIntExtra(EXTRA_PREVIOUS_BOND_STATE, 0);
if (oldState == BOND_NONE) {
LOG.info("Old state: none");
} else if (oldState == BOND_BONDING) {
LOG.info("Old state: bonding");
} else if (oldState == BOND_BONDED) {
LOG.info("Old state: bonded");
}
int state = intent.getIntExtra(EXTRA_BOND_STATE, 0);
if (state == BOND_NONE) {
LOG.info("New state: none");
} else if (state == BOND_BONDING) {
LOG.info("New state: bonding");
} else if (state == BOND_BONDED) {
LOG.info("New state: bonded");
}
}
}
}

View File

@@ -59,11 +59,13 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
@Override
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
BluetoothConnectionManager connectionManager =
new BluetoothConnectionManagerImpl();
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(ioExecutor,
androidExecutor, appContext, secureRandom, backoff, callback,
MAX_LATENCY);
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(
connectionManager, ioExecutor, androidExecutor, appContext,
secureRandom, backoff, callback, MAX_LATENCY);
eventBus.addListener(plugin);
return plugin;
}

View File

@@ -14,10 +14,14 @@ import java.io.OutputStream;
class AndroidBluetoothTransportConnection
extends AbstractDuplexTransportConnection {
private final BluetoothConnectionManager connectionManager;
private final BluetoothSocket socket;
AndroidBluetoothTransportConnection(Plugin plugin, BluetoothSocket socket) {
AndroidBluetoothTransportConnection(Plugin plugin,
BluetoothConnectionManager connectionManager,
BluetoothSocket socket) {
super(plugin);
this.connectionManager = connectionManager;
this.socket = socket;
}
@@ -33,6 +37,10 @@ class AndroidBluetoothTransportConnection
@Override
protected void closeConnection(boolean exception) throws IOException {
socket.close();
try {
socket.close();
} finally {
connectionManager.connectionClosed(this);
}
}
}

View File

@@ -0,0 +1,101 @@
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

@@ -0,0 +1,9 @@
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,6 +5,7 @@ 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;
@@ -93,10 +94,13 @@ 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,18 +12,19 @@ public interface ContactGroupFactory {
/**
* Creates a group that is not shared with any contacts.
*/
Group createLocalGroup(ClientId clientId);
Group createLocalGroup(ClientId clientId, int clientVersion);
/**
* Creates a group for the given client to share with the given contact.
*/
Group createContactGroup(ClientId clientId, Contact contact);
Group createContactGroup(ClientId clientId, int clientVersion,
Contact contact);
/**
* Creates a group for the given client to share between the given authors
* identified by their AuthorIds.
*/
Group createContactGroup(ClientId clientId, AuthorId authorId1,
AuthorId authorId2);
Group createContactGroup(ClientId clientId, int clientVersion,
AuthorId authorId1, AuthorId authorId2);
}

View File

@@ -12,6 +12,32 @@ 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,11 +1,13 @@
package org.briarproject.bramble.api.crypto;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.transport.TransportKeys;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import javax.annotation.Nullable;
@NotNullByDefault
public interface CryptoComponent {
SecretKey generateSecretKey();
@@ -23,127 +25,46 @@ public interface CryptoComponent {
KeyParser getMessageKeyParser();
/**
* Derives a stream header key from the given master secret.
* @param alice whether the key is for use by Alice or Bob.
*/
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.
* Derives another secret key from the given secret key.
*
* @param publicKey the public key
* @return the commitment to the provided public 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
*/
byte[] deriveKeyCommitment(byte[] publicKey);
SecretKey deriveKey(String label, SecretKey k, byte[]... inputs);
/**
* Derives a common shared secret from two public keys and one of the
* corresponding private keys.
* <p/>
* Part of BQP.
*
* @param theirPublicKey the ephemeral public key of the remote party
* @param ourKeyPair our ephemeral keypair
* @param alice true if ourKeyPair belongs to Alice
* @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
* @return the shared secret
* @throws GeneralSecurityException
*/
SecretKey deriveSharedSecret(byte[] theirPublicKey, KeyPair ourKeyPair,
boolean alice) throws GeneralSecurityException;
SecretKey deriveSharedSecret(String label, PublicKey theirPublicKey,
KeyPair ourKeyPair, byte[]... inputs)
throws GeneralSecurityException;
/**
* Derives the content of a confirmation record.
* <p/>
* Part of BQP.
* Signs the given byte[] with the given private key.
*
* @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
* @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
*/
byte[] sign(String label, byte[] toSign, byte[] privateKey)
throws GeneralSecurityException;
/**
* Verifies that the given signature is valid for the signedData
* and the given publicKey.
* Verifies that the given signature is valid for the signed data
* and the given public key.
*
* @param label A label that was specific to this signature
* to ensure that the signature cannot be repurposed
* @param label a namespaced label indicating the purpose of this
* signature, to prevent it from being repurposed or colliding with a
* signature created for another purpose
* @return true if the signature was valid, false otherwise.
*/
boolean verify(String label, byte[] signedData, byte[] publicKey,
@@ -153,23 +74,22 @@ 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 label specific to this hash to ensure that hashes
* calculated for distinct purposes don't collide.
* @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
*/
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(SecretKey macKey, byte[]... inputs);
byte[] mac(String label, SecretKey macKey, byte[]... inputs);
/**
* Encrypts and authenticates the given plaintext so it can be written to
@@ -185,6 +105,7 @@ 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

@@ -0,0 +1,19 @@
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

@@ -0,0 +1,50 @@
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

@@ -0,0 +1,32 @@
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

@@ -1,11 +0,0 @@
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

@@ -43,7 +43,7 @@ public interface DatabaseComponent {
* @throws DataTooOldException if the data uses an older schema than the
* current code and cannot be migrated
*/
boolean open(@Nullable MigrationListener listener) throws DbException;
boolean open() throws DbException;
/**
* Waits for any open transactions to finish and closes the database.

View File

@@ -1,11 +0,0 @@
package org.briarproject.bramble.api.db;
public interface MigrationListener {
/**
* This is called when a migration is started while opening the database.
* It will be called once for each migration being applied.
*/
void onMigrationRun();
}

View File

@@ -1,11 +1,13 @@
package org.briarproject.bramble.api.identity;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.UnsupportedEncodingException;
import org.briarproject.bramble.util.StringUtils;
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.
*/
@@ -17,20 +19,25 @@ 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, 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)
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)
throw new IllegalArgumentException();
this.id = id;
this.formatVersion = formatVersion;
this.name = name;
this.publicKey = publicKey;
}
@@ -42,6 +49,13 @@ 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,5 +1,8 @@
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 {
/**
@@ -8,26 +11,14 @@ public interface AuthorConstants {
int MAX_AUTHOR_NAME_LENGTH = 50;
/**
* 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.
* The maximum length of a public key in bytes. This applies to the
* signature algorithm used by the current {@link Author format version}.
*/
int MAX_PUBLIC_KEY_LENGTH = 65;
int MAX_PUBLIC_KEY_LENGTH = MAX_SIGNATURE_PUBLIC_KEY_BYTES;
/**
* 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.
* The maximum length of a signature in bytes. This applies to the
* signature algorithm used by the current {@link Author format version}.
*/
int MAX_SIGNATURE_LENGTH = 72;
int MAX_SIGNATURE_LENGTH = MAX_SIGNATURE_BYTES;
}

View File

@@ -5,8 +5,27 @@ 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, String name, byte[] publicKey,
byte[] privateKey, long created) {
super(id, name, publicKey);
public LocalAuthor(AuthorId id, int formatVersion, String name,
byte[] publicKey, byte[] privateKey, long created) {
super(id, formatVersion, 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.
* The current version of the BQP protocol. Version number 89 is reserved.
*/
byte PROTOCOL_VERSION = 2;
byte PROTOCOL_VERSION = 4;
/**
* The length of the record header in bytes.
@@ -22,7 +22,10 @@ public interface KeyAgreementConstants {
*/
int COMMIT_LENGTH = 16;
long CONNECTION_TIMEOUT = 20 * 1000; // Milliseconds
/**
* The connection timeout in milliseconds.
*/
long CONNECTION_TIMEOUT = 20 * 1000;
/**
* The transport identifier for Bluetooth.
@@ -33,4 +36,16 @@ 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

@@ -1,15 +0,0 @@
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

@@ -21,25 +21,7 @@ public interface LifecycleManager {
* The result of calling {@link #startServices(String)}.
*/
enum StartResult {
ALREADY_RUNNING,
DB_ERROR,
DATA_TOO_OLD_ERROR,
DATA_TOO_NEW_ERROR,
SERVICE_ERROR,
SUCCESS
}
/**
* The state the lifecycle can be in.
* Returned by {@link #getLifecycleState()}
*/
enum LifecycleState {
STARTING, MIGRATING_DATABASE, STARTING_SERVICES, RUNNING, STOPPING;
public boolean isAfter(LifecycleState state) {
return ordinal() > state.ordinal();
}
ALREADY_RUNNING, DB_ERROR, SERVICE_ERROR, SUCCESS
}
/**
@@ -89,10 +71,4 @@ public interface LifecycleManager {
* the {@link DatabaseComponent} to be closed before returning.
*/
void waitForShutdown() throws InterruptedException;
/**
* Returns the current state of the lifecycle.
*/
LifecycleState getLifecycleState();
}

View File

@@ -1,20 +0,0 @@
package org.briarproject.bramble.api.lifecycle.event;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState;
/**
* An event that is broadcast when the app enters a new lifecycle state.
*/
public class LifecycleEvent extends Event {
private final LifecycleState state;
public LifecycleEvent(LifecycleState state) {
this.state = state;
}
public LifecycleState getLifecycleState() {
return state;
}
}

View File

@@ -0,0 +1,9 @@
package org.briarproject.bramble.api.lifecycle.event;
import org.briarproject.bramble.api.event.Event;
/**
* An event that is broadcast when the app is shutting down.
*/
public class ShutdownEvent extends Event {
}

View File

@@ -17,6 +17,11 @@ 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,4 +36,8 @@ 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 and descriptor.
* Creates a group with the given client ID, client version and descriptor.
*/
Group createGroup(ClientId c, byte[] descriptor);
Group createGroup(ClientId c, int clientVersion, 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 = 3;
int PROTOCOL_VERSION = 4;
/**
* The length of the pseudo-random tag in bytes.
@@ -80,4 +80,32 @@ 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

@@ -13,9 +13,14 @@ 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;
@@ -63,7 +68,8 @@ 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, name, publicKey, privateKey, created);
return new LocalAuthor(id, FORMAT_VERSION, name, publicKey, privateKey,
created);
}
public static Author getAuthor() {
@@ -74,7 +80,7 @@ public class TestUtils {
AuthorId id = new AuthorId(getRandomId());
String name = getRandomString(nameLength);
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
return new Author(id, name, publicKey);
return new Author(id, FORMAT_VERSION, name, publicKey);
}
public static Group getGroup(ClientId clientId) {
@@ -99,4 +105,38 @@ 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,12 +9,15 @@ 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' // This is the last version that supports Java 1.6
implementation 'com.h2database:h2:1.4.192' // 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"
@@ -37,18 +40,21 @@ 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,6 +15,8 @@ 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;
@@ -32,7 +34,12 @@ 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
@@ -51,12 +58,14 @@ 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) {
MetadataEncoder metadataEncoder, CryptoComponent crypto,
AuthorFactory authorFactory) {
this.db = db;
this.messageFactory = messageFactory;
this.bdfReaderFactory = bdfReaderFactory;
@@ -64,6 +73,7 @@ class ClientHelperImpl implements ClientHelper {
this.metadataParser = metadataParser;
this.metadataEncoder = metadataEncoder;
this.crypto = crypto;
this.authorFactory = authorFactory;
}
@Override
@@ -341,6 +351,11 @@ 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 {
@@ -355,4 +370,16 @@ 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,14 +2,6 @@ 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;
@@ -18,19 +10,14 @@ import dagger.Provides;
public class ClientModule {
@Provides
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);
ClientHelper provideClientHelper(ClientHelperImpl clientHelper) {
return clientHelper;
}
@Provides
ContactGroupFactory provideContactGroupFactory(GroupFactory groupFactory,
ClientHelper clientHelper) {
return new ContactGroupFactoryImpl(groupFactory, clientHelper);
ContactGroupFactory provideContactGroupFactory(
ContactGroupFactoryImpl contactGroupFactory) {
return contactGroupFactory;
}
}

View File

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

View File

@@ -43,6 +43,7 @@ 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;
@@ -141,8 +142,10 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
}
// Derive the header keys for the transport streams
SecretKey aliceHeaderKey = crypto.deriveHeaderKey(masterSecret, true);
SecretKey bobHeaderKey = crypto.deriveHeaderKey(masterSecret, false);
SecretKey aliceHeaderKey = crypto.deriveKey(ALICE_KEY_LABEL,
masterSecret, new byte[] {PROTOCOL_VERSION});
SecretKey bobHeaderKey = crypto.deriveKey(BOB_KEY_LABEL, masterSecret,
new byte[] {PROTOCOL_VERSION});
// Create the readers
InputStream streamReader =
@@ -156,8 +159,10 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
BdfWriter w = bdfWriterFactory.createWriter(streamWriter);
// Derive the nonces to be signed
byte[] aliceNonce = crypto.deriveSignatureNonce(masterSecret, true);
byte[] bobNonce = crypto.deriveSignatureNonce(masterSecret, false);
byte[] aliceNonce = crypto.mac(ALICE_NONCE_LABEL, masterSecret,
new byte[] {PROTOCOL_VERSION});
byte[] bobNonce = crypto.mac(BOB_NONCE_LABEL, masterSecret,
new byte[] {PROTOCOL_VERSION});
// Exchange pseudonyms, signed nonces, and timestamps
long localTimestamp = clock.currentTimeMillis();
@@ -196,8 +201,8 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
try {
// Add the contact
ContactId contactId = addContact(remoteAuthor, masterSecret,
timestamp, alice, remoteProperties);
ContactId contactId = addContact(remoteAuthor, timestamp,
remoteProperties);
// Reuse the connection as a transport connection
connectionManager.manageOutgoingConnection(contactId, transportId,
conn);
@@ -223,6 +228,7 @@ 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);
@@ -232,11 +238,16 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
private Author receivePseudonym(BdfReader r, byte[] nonce)
throws GeneralSecurityException, IOException {
// Read the name, public key and signature
// Read the format version, 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
@@ -245,7 +256,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
LOG.info("Invalid signature");
throw new GeneralSecurityException();
}
return authorFactory.createAuthor(name, publicKey);
return authorFactory.createAuthor(formatVersion, name, publicKey);
}
private void sendTimestamp(BdfWriter w, long timestamp)
@@ -294,15 +305,15 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
return remote;
}
private ContactId addContact(Author remoteAuthor, SecretKey master,
long timestamp, boolean alice,
private ContactId addContact(Author remoteAuthor, long timestamp,
Map<TransportId, TransportProperties> remoteProperties)
throws DbException {
ContactId contactId;
Transaction txn = db.startTransaction(false);
try {
contactId = contactManager.addContact(txn, remoteAuthor,
localAuthor.getId(), master, timestamp, alice, true, true);
localAuthor.getId(), masterSecret, timestamp, alice,
true, true);
transportPropertyManager.addRemoteProperties(txn, contactId,
remoteProperties);
db.commitTransaction(txn);
@@ -312,8 +323,7 @@ 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

@@ -1,547 +0,0 @@
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,107 +1,59 @@
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.plugin.TransportId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
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.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 org.spongycastle.crypto.digests.Blake2bDigest;
import org.whispersystems.curve25519.Curve25519;
import org.whispersystems.curve25519.Curve25519KeyPair;
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_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 static final int PBKDF_FORMAT_SCRYPT = 0;
private final SecureRandom secureRandom;
private final ECKeyPairGenerator agreementKeyPairGenerator;
private final ECKeyPairGenerator signatureKeyPairGenerator;
private final PasswordBasedKdf passwordBasedKdf;
private final Curve25519 curve25519;
private final KeyPairGenerator signatureKeyPairGenerator;
private final KeyParser agreementKeyParser, signatureKeyParser;
private final MessageEncrypter messageEncrypter;
@Inject
CryptoComponentImpl(SecureRandomProvider secureRandomProvider) {
CryptoComponentImpl(SecureRandomProvider secureRandomProvider,
PasswordBasedKdf passwordBasedKdf) {
if (LOG.isLoggable(INFO)) {
SecureRandom defaultSecureRandom = new SecureRandom();
String name = defaultSecureRandom.getProvider().getName();
@@ -121,16 +73,13 @@ class CryptoComponentImpl implements CryptoComponent {
}
}
secureRandom = new SecureRandom();
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);
this.passwordBasedKdf = passwordBasedKdf;
curve25519 = Curve25519.getInstance("java");
signatureKeyPairGenerator = new KeyPairGenerator();
signatureKeyPairGenerator.initialize(SIGNATURE_KEY_PAIR_BITS,
secureRandom);
agreementKeyParser = new Curve25519KeyParser();
signatureKeyParser = new EdKeyParser();
messageEncrypter = new MessageEncrypter(secureRandom);
}
@@ -174,16 +123,17 @@ class CryptoComponentImpl implements CryptoComponent {
// Package access for testing
byte[] performRawKeyAgreement(PrivateKey priv, PublicKey pub)
throws GeneralSecurityException {
if (!(priv instanceof Sec1PrivateKey))
if (!(priv instanceof Curve25519PrivateKey))
throw new IllegalArgumentException();
if (!(pub instanceof Sec1PublicKey))
if (!(pub instanceof Curve25519PublicKey))
throw new IllegalArgumentException();
ECPrivateKeyParameters ecPriv = ((Sec1PrivateKey) priv).getKey();
ECPublicKeyParameters ecPub = ((Sec1PublicKey) pub).getKey();
long now = System.currentTimeMillis();
ECDHCBasicAgreement agreement = new ECDHCBasicAgreement();
agreement.init(ecPriv);
byte[] secret = agreement.calculateAgreement(ecPub).toByteArray();
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();
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Deriving shared secret took " + duration + " ms");
@@ -192,18 +142,10 @@ class CryptoComponentImpl implements CryptoComponent {
@Override
public KeyPair generateAgreementKeyPair() {
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);
Curve25519KeyPair keyPair = curve25519.generateKeyPair();
PublicKey pub = new Curve25519PublicKey(keyPair.getPublicKey());
PrivateKey priv = new Curve25519PrivateKey(keyPair.getPrivateKey());
return new KeyPair(pub, priv);
}
@Override
@@ -213,17 +155,12 @@ class CryptoComponentImpl implements CryptoComponent {
@Override
public KeyPair generateSignatureKeyPair() {
AsymmetricCipherKeyPair keyPair =
java.security.KeyPair keyPair =
signatureKeyPairGenerator.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,
SIGNATURE_KEY_PAIR_BITS);
EdDSAPublicKey edPublicKey = (EdDSAPublicKey) keyPair.getPublic();
PublicKey publicKey = new EdPublicKey(edPublicKey.getAbyte());
EdDSAPrivateKey edPrivateKey = (EdDSAPrivateKey) keyPair.getPrivate();
PrivateKey privateKey = new EdPrivateKey(edPrivateKey.getSeed());
return new KeyPair(publicKey, privateKey);
}
@@ -238,205 +175,47 @@ class CryptoComponentImpl implements CryptoComponent {
}
@Override
public SecretKey deriveHeaderKey(SecretKey master,
boolean alice) {
return new SecretKey(macKdf(master, alice ? A_INVITE : B_INVITE));
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);
}
@Override
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 {
public SecretKey deriveSharedSecret(String label, PublicKey theirPublicKey,
KeyPair ourKeyPair, byte[]... inputs)
throws GeneralSecurityException {
PrivateKey ourPriv = ourKeyPair.getPrivate();
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);
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);
}
@Override
public byte[] sign(String label, byte[] toSign, byte[] privateKey)
throws GeneralSecurityException {
Signature signature = new SignatureImpl(secureRandom);
KeyParser keyParser = getSignatureKeyParser();
PrivateKey key = keyParser.parsePrivateKey(privateKey);
signature.initSign(key);
updateSignature(signature, label, toSign);
return signature.sign();
PrivateKey key = signatureKeyParser.parsePrivateKey(privateKey);
Signature sig = new EdSignature();
sig.initSign(key);
updateSignature(sig, label, toSign);
return sig.sign();
}
@Override
public boolean verify(String label, byte[] signedData, byte[] publicKey,
byte[] signature) throws GeneralSecurityException {
Signature sig = new SignatureImpl(secureRandom);
KeyParser keyParser = getSignatureKeyParser();
PublicKey key = keyParser.parsePublicKey(publicKey);
PublicKey key = signatureKeyParser.parsePublicKey(publicKey);
Signature sig = new EdSignature();
sig.initVerify(key);
updateSignature(sig, label, signedData);
return sig.verify(signature);
}
private void updateSignature(Signature signature, String label,
byte[] toSign) {
byte[] toSign) throws GeneralSecurityException {
byte[] labelBytes = StringUtils.toUtf8(label);
byte[] length = new byte[INT_32_BYTES];
ByteUtils.writeUint32(labelBytes.length, length, 0);
@@ -450,7 +229,7 @@ class CryptoComponentImpl implements CryptoComponent {
@Override
public byte[] hash(String label, byte[]... inputs) {
byte[] labelBytes = StringUtils.toUtf8(label);
Digest digest = new Blake2sDigest();
Digest digest = new Blake2bDigest(256);
byte[] length = new byte[INT_32_BYTES];
ByteUtils.writeUint32(labelBytes.length, length, 0);
digest.update(length, 0, length.length);
@@ -466,14 +245,13 @@ class CryptoComponentImpl implements CryptoComponent {
}
@Override
public int getHashLength() {
return HASH_SIZE;
}
@Override
public byte[] mac(SecretKey macKey, byte[]... inputs) {
Digest mac = new Blake2sDigest(macKey.getBytes());
public byte[] mac(String label, SecretKey macKey, byte[]... inputs) {
byte[] labelBytes = StringUtils.toUtf8(label);
Digest mac = new Blake2bDigest(macKey.getBytes(), 32, null, null);
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);
@@ -492,23 +270,33 @@ class CryptoComponentImpl implements CryptoComponent {
byte[] salt = new byte[PBKDF_SALT_BYTES];
secureRandom.nextBytes(salt);
// Calibrate the KDF
int iterations = chooseIterationCount(PBKDF_TARGET_MILLIS);
int cost = passwordBasedKdf.chooseCostParameter();
// Derive the key from the password
SecretKey key = new SecretKey(pbkdf2(password, salt, iterations));
SecretKey key = passwordBasedKdf.deriveKey(password, salt, cost);
// Generate a random IV
byte[] iv = new byte[STORAGE_IV_BYTES];
secureRandom.nextBytes(iv);
// The output contains the salt, iterations, IV, ciphertext and MAC
int outputLen = salt.length + INT_32_BYTES + iv.length + input.length
+ macBytes;
// 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;
byte[] output = new byte[outputLen];
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);
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;
// 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) {
@@ -517,22 +305,36 @@ 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 salt, iterations, IV, ciphertext and MAC
if (input.length < PBKDF_SALT_BYTES + INT_32_BYTES + STORAGE_IV_BYTES
+ macBytes)
// 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)
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, 0, salt, 0, salt.length);
long iterations = ByteUtils.readUint32(input, salt.length);
if (iterations < 0 || iterations > Integer.MAX_VALUE)
return null; // Invalid iteration count
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
byte[] iv = new byte[STORAGE_IV_BYTES];
System.arraycopy(input, salt.length + INT_32_BYTES, iv, 0, iv.length);
System.arraycopy(input, inputOff, iv, 0, iv.length);
inputOff += iv.length;
// Derive the key from the password
SecretKey key = new SecretKey(pbkdf2(password, salt, (int) iterations));
SecretKey key = passwordBasedKdf.deriveKey(password, salt, (int) cost);
// Initialise the cipher
try {
cipher.init(false, key, iv);
@@ -541,7 +343,6 @@ 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);
@@ -564,88 +365,4 @@ 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,9 +3,11 @@ 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;
@@ -65,8 +67,9 @@ public class CryptoModule {
@Provides
@Singleton
CryptoComponent provideCryptoComponent(
SecureRandomProvider secureRandomProvider) {
return new CryptoComponentImpl(secureRandomProvider);
SecureRandomProvider secureRandomProvider,
ScryptKdf passwordBasedKdf) {
return new CryptoComponentImpl(secureRandomProvider, passwordBasedKdf);
}
@Provides
@@ -74,6 +77,12 @@ public class CryptoModule {
return new PasswordStrengthEstimatorImpl();
}
@Provides
TransportCrypto provideTransportCrypto(
TransportCryptoImpl transportCrypto) {
return transportCrypto;
}
@Provides
StreamDecrypterFactory provideStreamDecrypterFactory(
Provider<AuthenticatedCipher> cipherProvider) {
@@ -81,9 +90,17 @@ public class CryptoModule {
}
@Provides
StreamEncrypterFactory provideStreamEncrypterFactory(CryptoComponent crypto,
StreamEncrypterFactory provideStreamEncrypterFactory(
CryptoComponent crypto, TransportCrypto transportCrypto,
Provider<AuthenticatedCipher> cipherProvider) {
return new StreamEncrypterFactoryImpl(crypto, cipherProvider);
return new StreamEncrypterFactoryImpl(crypto, transportCrypto,
cipherProvider);
}
@Provides
KeyAgreementCrypto provideKeyAgreementCrypto(
KeyAgreementCryptoImpl keyAgreementCrypto) {
return keyAgreementCrypto;
}
@Provides

View File

@@ -0,0 +1,35 @@
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

@@ -0,0 +1,18 @@
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

@@ -0,0 +1,18 @@
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

@@ -0,0 +1,26 @@
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

@@ -0,0 +1,18 @@
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

@@ -0,0 +1,18 @@
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

@@ -0,0 +1,83 @@
package org.briarproject.bramble.crypto;
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
import net.i2p.crypto.eddsa.EdDSAPublicKey;
import net.i2p.crypto.eddsa.EdDSASecurityProvider;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import static net.i2p.crypto.eddsa.EdDSAEngine.SIGNATURE_ALGORITHM;
@NotNullByDefault
class EdSignature implements Signature {
private static final Provider PROVIDER = new EdDSASecurityProvider();
private static final EdDSANamedCurveSpec CURVE_SPEC =
EdDSANamedCurveTable.getByName("Ed25519");
private final java.security.Signature signature;
EdSignature() {
try {
signature = java.security.Signature
.getInstance(SIGNATURE_ALGORITHM, PROVIDER);
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
@Override
public void initSign(PrivateKey k) throws GeneralSecurityException {
if (!(k instanceof EdPrivateKey))
throw new IllegalArgumentException();
EdDSAPrivateKey privateKey = new EdDSAPrivateKey(
new EdDSAPrivateKeySpec(k.getEncoded(), CURVE_SPEC));
signature.initSign(privateKey);
}
@Override
public void initVerify(PublicKey k) throws GeneralSecurityException {
if (!(k instanceof EdPublicKey))
throw new IllegalArgumentException();
EdDSAPublicKey publicKey = new EdDSAPublicKey(
new EdDSAPublicKeySpec(k.getEncoded(), CURVE_SPEC));
signature.initVerify(publicKey);
}
@Override
public void update(byte b) throws GeneralSecurityException {
signature.update(b);
}
@Override
public void update(byte[] b) throws GeneralSecurityException {
signature.update(b);
}
@Override
public void update(byte[] b, int off, int len)
throws GeneralSecurityException {
signature.update(b, off, len);
}
@Override
public byte[] sign() throws GeneralSecurityException {
return signature.sign();
}
@Override
public boolean verify(byte[] sig) throws GeneralSecurityException {
return signature.verify(sig);
}
}

View File

@@ -1,32 +0,0 @@
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

@@ -0,0 +1,56 @@
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

@@ -0,0 +1,10 @@
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

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

View File

@@ -1,90 +0,0 @@
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,6 +4,7 @@ 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;
@@ -22,12 +23,15 @@ 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;
}
@@ -37,7 +41,8 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
AuthenticatedCipher cipher = cipherProvider.get();
long streamNumber = ctx.getStreamNumber();
byte[] tag = new byte[TAG_LENGTH];
crypto.encodeTag(tag, ctx.getTagKey(), PROTOCOL_VERSION, streamNumber);
transportCrypto.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

@@ -0,0 +1,136 @@
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

@@ -6,7 +6,6 @@ import org.briarproject.bramble.api.db.DataTooNewException;
import org.briarproject.bramble.api.db.DataTooOldException;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.MigrationListener;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.LocalAuthor;
@@ -46,7 +45,7 @@ interface Database<T> {
* @throws DataTooOldException if the data uses an older schema than the
* current code and cannot be migrated
*/
boolean open(@Nullable MigrationListener listener) throws DbException;
boolean open() throws DbException;
/**
* Prevents new transactions from starting, waits for all current
@@ -97,9 +96,12 @@ 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)
throws DbException;
void addMessage(T txn, Message m, State state, boolean shared,
@Nullable ContactId sender) throws DbException;
/**
* Adds a dependency between two messages in the given group.
@@ -112,16 +114,6 @@ 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.
*/
@@ -280,7 +272,7 @@ interface Database<T> {
* <p/>
* Read-only.
*/
Collection<ContactId> getGroupVisibility(T txn, GroupId g)
Map<ContactId, Boolean> getGroupVisibility(T txn, GroupId g)
throws DbException;
/**
@@ -584,13 +576,6 @@ 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.
@@ -598,12 +583,6 @@ 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

@@ -10,7 +10,6 @@ import org.briarproject.bramble.api.db.ContactExistsException;
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.MigrationListener;
import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.NoSuchGroupException;
import org.briarproject.bramble.api.db.NoSuchLocalAuthorException;
@@ -101,9 +100,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
}
@Override
public boolean open(@Nullable MigrationListener listener)
throws DbException {
boolean reopened = db.open(listener);
public boolean open() throws DbException {
boolean reopened = db.open();
shutdown.addShutdownHook(() -> {
try {
close();
@@ -215,7 +213,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
if (!db.containsGroup(txn, m.getGroupId()))
throw new NoSuchGroupException();
if (!db.containsMessage(txn, m.getId())) {
addMessage(txn, m, DELIVERED, shared, null);
db.addMessage(txn, m, DELIVERED, shared, null);
transaction.attach(new MessageAddedEvent(m, null));
transaction.attach(new MessageStateChangedEvent(m.getId(), true,
DELIVERED));
@@ -224,16 +222,6 @@ 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 {
@@ -682,7 +670,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.raiseSeenFlag(txn, c, m.getId());
db.raiseAckFlag(txn, c, m.getId());
} else {
addMessage(txn, m, UNKNOWN, false, c);
db.addMessage(txn, m, UNKNOWN, false, c);
transaction.attach(new MessageAddedEvent(m, c));
}
transaction.attach(new MessageToAckEvent(c));
@@ -750,7 +738,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
GroupId id = g.getId();
if (!db.containsGroup(txn, id))
throw new NoSuchGroupException();
Collection<ContactId> affected = db.getGroupVisibility(txn, id);
Collection<ContactId> affected =
db.getGroupVisibility(txn, id).keySet();
db.removeGroup(txn, id);
transaction.attach(new GroupRemovedEvent(g));
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
@@ -820,19 +809,9 @@ 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);
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);
}
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);
List<ContactId> affected = Collections.singletonList(c);
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
}

View File

@@ -3,7 +3,6 @@ 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;
@@ -14,7 +13,6 @@ import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import javax.annotation.Nullable;
import javax.inject.Inject;
/**
@@ -24,29 +22,30 @@ 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 SECRET_TYPE = "BINARY(32)";
private static final String STRING_TYPE = "VARCHAR";
private final DatabaseConfig config;
private final String url;
@Inject
H2Database(DatabaseConfig config, Clock clock) {
super(HASH_TYPE, BINARY_TYPE, COUNTER_TYPE, SECRET_TYPE, 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:h2:split:" + path + ";CIPHER=AES;MULTI_THREADED=1"
+ ";WRITE_DELAY=0;DB_CLOSE_ON_EXIT=false";
+ ";WRITE_DELAY=0";
}
@Override
public boolean open(@Nullable MigrationListener listener)
throws DbException {
public boolean open() throws DbException {
boolean reopen = config.databaseExists();
if (!reopen) config.getDatabaseDirectory().mkdirs();
super.open("org.h2.Driver", reopen, listener);
super.open("org.h2.Driver", reopen);
return reopen;
}
@@ -93,6 +92,10 @@ 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(url, props);
return DriverManager.getConnection(getUrl(), props);
}
String getUrl() {
return url;
}
}

View File

@@ -0,0 +1,99 @@
package org.briarproject.bramble.db;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.util.StringUtils;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import javax.inject.Inject;
/**
* Contains all the HSQLDB-specific code for the database.
*/
@NotNullByDefault
class HyperSqlDatabase extends JdbcDatabase {
private static final String HASH_TYPE = "BINARY(32)";
private static final String SECRET_TYPE = "BINARY(32)";
private static final String BINARY_TYPE = "BINARY";
private static final String COUNTER_TYPE =
"INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY(START WITH 1)";
private static final String STRING_TYPE = "VARCHAR";
private final DatabaseConfig config;
private final String url;
@Inject
HyperSqlDatabase(DatabaseConfig config, Clock clock) {
super(HASH_TYPE, SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE,
clock);
this.config = config;
File dir = config.getDatabaseDirectory();
String path = new File(dir, "db").getAbsolutePath();
url = "jdbc:hsqldb:file:" + path
+ ";sql.enforce_size=false;allow_empty_batch=true"
+ ";encrypt_lobs=true;crypt_type=AES";
}
@Override
public boolean open() throws DbException {
boolean reopen = config.databaseExists();
if (!reopen) config.getDatabaseDirectory().mkdirs();
super.open("org.hsqldb.jdbc.JDBCDriver", reopen);
return reopen;
}
@Override
public void close() throws DbException {
try {
super.closeAllConnections();
Connection c = createConnection();
Statement s = c.createStatement();
s.executeQuery("SHUTDOWN");
s.close();
c.close();
} catch (SQLException e) {
throw new DbException(e);
}
}
@Override
public long getFreeSpace() throws DbException {
File dir = config.getDatabaseDirectory();
long maxSize = config.getMaxSize();
long free = dir.getFreeSpace();
long used = getDiskSpace(dir);
long quota = maxSize - used;
return Math.min(free, quota);
}
private long getDiskSpace(File f) {
if (f.isDirectory()) {
long total = 0;
File[] children = f.listFiles();
if (children != null)
for (File child : children) total += getDiskSpace(child);
return total;
} else if (f.isFile()) {
return f.length();
} else {
return 0;
}
}
@Override
protected Connection createConnection() throws SQLException {
SecretKey key = config.getEncryptionKey();
if (key == null) throw new IllegalStateException();
String hex = StringUtils.toHexString(key.getBytes());
return DriverManager.getConnection(url + ";crypt_key=" + hex);
}
}

View File

@@ -1,75 +0,0 @@
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,61 +1,65 @@
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 java.io.ByteArrayOutputStream;
import java.io.IOException;
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.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, BdfWriterFactory bdfWriterFactory,
Clock clock) {
AuthorFactoryImpl(CryptoComponent crypto, Clock clock) {
this.crypto = crypto;
this.bdfWriterFactory = bdfWriterFactory;
this.clock = clock;
}
@Override
public Author createAuthor(String name, byte[] publicKey) {
return new Author(getId(name, publicKey), name, 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);
}
@Override
public LocalAuthor createLocalAuthor(String name, byte[] publicKey,
byte[] privateKey) {
return new LocalAuthor(getId(name, publicKey), name, publicKey,
privateKey, clock.currentTimeMillis());
return createLocalAuthor(FORMAT_VERSION, name, publicKey, privateKey);
}
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()));
@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));
}
}

View File

@@ -1,36 +0,0 @@
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,13 +1,7 @@
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;
@@ -24,19 +18,14 @@ public class IdentityModule {
}
@Provides
AuthorFactory provideAuthorFactory(CryptoComponent crypto,
BdfWriterFactory bdfWriterFactory, Clock clock) {
return new AuthorFactoryImpl(crypto, bdfWriterFactory, clock);
AuthorFactory provideAuthorFactory(AuthorFactoryImpl authorFactory) {
return authorFactory;
}
@Provides
@Singleton
IdentityManager provideIdentityModule(DatabaseComponent db) {
return new IdentityManagerImpl(db);
}
@Provides
ObjectReader<Author> provideAuthorReader(AuthorFactory authorFactory) {
return new AuthorReader(authorFactory);
IdentityManager provideIdentityManager(
IdentityManagerImpl identityManager) {
return identityManager;
}
}

View File

@@ -2,35 +2,38 @@ package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
import java.util.concurrent.Callable;
import javax.annotation.Nullable;
interface ConnectionChooser {
/**
* Submits a connection task to the chooser.
* Adds a connection to the set of connections that may be chosen. If
* {@link #stop()} has already been called, the connection will be closed
* immediately.
*/
void submit(Callable<KeyAgreementConnection> task);
void addConnection(KeyAgreementConnection c);
/**
* Returns a connection returned by any of the tasks submitted to the
* chooser, waiting up to the given amount of time for a connection if
* necessary. Returns null if the time elapses without a connection
* becoming available.
* Chooses one of the connections passed to
* {@link #addConnection(KeyAgreementConnection)} and returns it,
* waiting up to the given amount of time for a suitable connection to
* become available. Returns null if the time elapses without a suitable
* connection becoming available.
*
* @param alice true if the local party is Alice
* @param timeout the timeout in milliseconds
* @throws InterruptedException if the thread is interrupted while waiting
* for a connection to become available
* for a suitable connection to become available
*/
@Nullable
KeyAgreementConnection poll(long timeout) throws InterruptedException;
KeyAgreementConnection chooseConnection(boolean alice, long timeout)
throws InterruptedException;
/**
* Stops the chooser. Any connections already returned to the chooser are
* closed unless they have been removed from the chooser by calling
* {@link #poll(long)}. Any connections subsequently returned to the
* chooser will also be closed.
* Stops the chooser from accepting new connections. Closes any connections
* already passed to {@link #addConnection(KeyAgreementConnection)}
* and not chosen. Any connections subsequently passed to
* {@link #addConnection(KeyAgreementConnection)} will be closed.
*/
void stop();
}

View File

@@ -1,18 +1,16 @@
package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.system.Clock;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Iterator;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
@@ -20,6 +18,7 @@ import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
@NotNullByDefault
@ThreadSafe
@@ -28,54 +27,114 @@ class ConnectionChooserImpl implements ConnectionChooser {
private static final Logger LOG =
Logger.getLogger(ConnectionChooserImpl.class.getName());
private final EventBus eventBus;
private final Clock clock;
private final Executor ioExecutor;
private final Object lock = new Object();
// The following are locking: lock
private boolean stopped = false;
private final Queue<KeyAgreementConnection> results = new LinkedList<>();
private final List<KeyAgreementConnection> connections =
new ArrayList<>(); // Locking: lock
private boolean stopped = false; // Locking: lock
@Inject
ConnectionChooserImpl(Clock clock, @IoExecutor Executor ioExecutor) {
ConnectionChooserImpl(EventBus eventBus, Clock clock) {
this.eventBus = eventBus;
this.clock = clock;
this.ioExecutor = ioExecutor;
}
@Override
public void submit(Callable<KeyAgreementConnection> task) {
ioExecutor.execute(() -> {
try {
KeyAgreementConnection c = task.call();
if (c != null) addResult(c);
} catch (Exception e) {
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
public void addConnection(KeyAgreementConnection conn) {
boolean close = false;
synchronized (lock) {
if (stopped) {
// Already stopped, close the connection
close = true;
} else {
connections.add(conn);
lock.notifyAll();
}
});
}
if (close) tryToClose(conn.getConnection());
}
@Nullable
@Override
public KeyAgreementConnection poll(long timeout)
public KeyAgreementConnection chooseConnection(boolean alice, long timeout)
throws InterruptedException {
if (alice) return chooseConnectionAlice(timeout);
else return chooseConnectionBob(timeout);
}
@Nullable
private KeyAgreementConnection chooseConnectionAlice(long timeout)
throws InterruptedException {
LOG.info("Choosing connection for Alice");
long now = clock.currentTimeMillis();
long end = now + timeout;
KeyAgreementConnection chosen;
synchronized (lock) {
while (!stopped && results.isEmpty() && now < end) {
// Wait until we're stopped, a connection is added, or we time out
while (!stopped && connections.isEmpty() && now < end) {
lock.wait(end - now);
now = clock.currentTimeMillis();
}
return results.poll();
if (connections.isEmpty()) {
LOG.info("No suitable connection for Alice");
return null;
}
// Choose the first connection
chosen = connections.remove(0);
}
if (LOG.isLoggable(INFO))
LOG.info("Choosing " + chosen.getTransportId());
return chosen;
}
@Nullable
private KeyAgreementConnection chooseConnectionBob(long timeout)
throws InterruptedException {
LOG.info("Choosing connection for Bob");
// Bob waits here for Alice to scan his QR code, determine her role,
// choose a connection and send her key
eventBus.broadcast(new KeyAgreementWaitingEvent());
long now = clock.currentTimeMillis();
long end = now + timeout;
synchronized (lock) {
while (!stopped && now < end) {
// Check whether any connection has data available
Iterator<KeyAgreementConnection> it = connections.iterator();
while (it.hasNext()) {
KeyAgreementConnection c = it.next();
try {
int available = c.getConnection().getReader()
.getInputStream().available();
if (available > 0) {
if (LOG.isLoggable(INFO))
LOG.info("Choosing " + c.getTransportId());
it.remove();
return c;
}
} catch (IOException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
tryToClose(c.getConnection());
it.remove();
}
}
// Wait for 1 second before checking again
lock.wait(Math.min(1000, end - now));
now = clock.currentTimeMillis();
}
}
LOG.info("No suitable connection for Bob");
return null;
}
@Override
public void stop() {
List<KeyAgreementConnection> unused;
synchronized (lock) {
unused = new ArrayList<>(results);
results.clear();
stopped = true;
unused = new ArrayList<>(connections);
connections.clear();
lock.notifyAll();
}
if (LOG.isLoggable(INFO))
@@ -83,24 +142,6 @@ class ConnectionChooserImpl implements ConnectionChooser {
for (KeyAgreementConnection c : unused) tryToClose(c.getConnection());
}
private void addResult(KeyAgreementConnection c) {
if (LOG.isLoggable(INFO))
LOG.info("Got connection for " + c.getTransportId());
boolean close = false;
synchronized (lock) {
if (stopped) {
close = true;
} else {
results.add(c);
lock.notifyAll();
}
}
if (close) {
LOG.info("Already stopped");
tryToClose(c.getConnection());
}
}
private void tryToClose(DuplexTransportConnection conn) {
try {
conn.getReader().dispose(false, true);

View File

@@ -1,27 +1,26 @@
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.data.BdfList;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.keyagreement.Payload;
import org.briarproject.bramble.api.keyagreement.TransportDescriptor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.system.Clock;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
@@ -33,39 +32,35 @@ import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.CO
@NotNullByDefault
class KeyAgreementConnector {
interface Callbacks {
void connectionWaiting();
}
private static final Logger LOG =
Logger.getLogger(KeyAgreementConnector.class.getName());
private final Callbacks callbacks;
private final CryptoComponent crypto;
private final Clock clock;
private final KeyAgreementCrypto keyAgreementCrypto;
private final PluginManager pluginManager;
private final Executor ioExecutor;
private final ConnectionChooser connectionChooser;
private final List<KeyAgreementListener> listeners =
new CopyOnWriteArrayList<>();
private final CountDownLatch aliceLatch = new CountDownLatch(1);
private final AtomicBoolean waitingSent = new AtomicBoolean(false);
private volatile boolean alice = false, stopped = false;
private volatile boolean stopped = false;
KeyAgreementConnector(Callbacks callbacks,
CryptoComponent crypto, PluginManager pluginManager,
KeyAgreementConnector(Clock clock, KeyAgreementCrypto keyAgreementCrypto,
PluginManager pluginManager, Executor ioExecutor,
ConnectionChooser connectionChooser) {
this.callbacks = callbacks;
this.crypto = crypto;
this.clock = clock;
this.keyAgreementCrypto = keyAgreementCrypto;
this.pluginManager = pluginManager;
this.ioExecutor = ioExecutor;
this.connectionChooser = connectionChooser;
}
Payload listen(KeyPair localKeyPair) {
LOG.info("Starting BQP listeners");
// Derive commitment
byte[] commitment = crypto.deriveKeyCommitment(
localKeyPair.getPublic().getEncoded());
byte[] commitment = keyAgreementCrypto.deriveKeyCommitment(
localKeyPair.getPublic());
// Start all listeners and collect their descriptors
List<TransportDescriptor> descriptors = new ArrayList<>();
for (DuplexPlugin plugin : pluginManager.getKeyAgreementPlugins()) {
@@ -76,7 +71,14 @@ class KeyAgreementConnector {
descriptors.add(new TransportDescriptor(id, l.getDescriptor()));
if (LOG.isLoggable(INFO)) LOG.info("Listening via " + id);
listeners.add(l);
connectionChooser.submit(new ReadableTask(l::accept));
ioExecutor.execute(() -> {
try {
connectionChooser.addConnection(l.accept());
} catch (IOException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
});
}
}
return new Payload(commitment, descriptors);
@@ -85,17 +87,13 @@ class KeyAgreementConnector {
void stopListening() {
LOG.info("Stopping BQP listeners");
stopped = true;
aliceLatch.countDown();
for (KeyAgreementListener l : listeners) l.close();
listeners.clear();
connectionChooser.stop();
}
@Nullable
public KeyAgreementTransport connect(Payload remotePayload, boolean alice) {
// Let the ReadableTasks know if we are Alice
this.alice = alice;
aliceLatch.countDown();
// Start connecting over supported transports
if (LOG.isLoggable(INFO)) {
LOG.info("Starting outgoing BQP connections as "
@@ -109,16 +107,23 @@ class KeyAgreementConnector {
DuplexPlugin plugin = (DuplexPlugin) p;
byte[] commitment = remotePayload.getCommitment();
BdfList descriptor = d.getDescriptor();
connectionChooser.submit(new ReadableTask(
new ConnectorTask(plugin, commitment, descriptor)));
ioExecutor.execute(() -> {
try {
KeyAgreementConnection c =
connect(plugin, commitment, descriptor);
if (c != null) connectionChooser.addConnection(c);
} catch (InterruptedException e) {
LOG.info("Interrupted while waiting to connect");
}
});
}
}
// Get chosen connection
try {
KeyAgreementConnection chosen =
connectionChooser.poll(CONNECTION_TIMEOUT);
if (chosen == null) return null;
KeyAgreementConnection chosen = connectionChooser.chooseConnection(
alice, CONNECTION_TIMEOUT);
if (chosen == null) return null; // No suitable connection
return new KeyAgreementTransport(chosen);
} catch (InterruptedException e) {
LOG.info("Interrupted while waiting for connection");
@@ -132,70 +137,20 @@ class KeyAgreementConnector {
}
}
private void waitingForAlice() {
if (!waitingSent.getAndSet(true)) callbacks.connectionWaiting();
}
private class ConnectorTask implements Callable<KeyAgreementConnection> {
private final byte[] commitment;
private final BdfList descriptor;
private final DuplexPlugin plugin;
private ConnectorTask(DuplexPlugin plugin, byte[] commitment,
BdfList descriptor) {
this.plugin = plugin;
this.commitment = commitment;
this.descriptor = descriptor;
}
@Nullable
@Override
public KeyAgreementConnection call() throws Exception {
// Repeat attempts until we connect, get stopped, or get interrupted
while (!stopped) {
DuplexTransportConnection conn =
plugin.createKeyAgreementConnection(commitment,
descriptor);
if (conn != null) {
if (LOG.isLoggable(INFO))
LOG.info(plugin.getId() + ": Outgoing connection");
return new KeyAgreementConnection(conn, plugin.getId());
}
// Wait 2s before retry (to circumvent transient failures)
Thread.sleep(2000);
}
return null;
}
}
private class ReadableTask implements Callable<KeyAgreementConnection> {
private final Callable<KeyAgreementConnection> connectionTask;
private ReadableTask(Callable<KeyAgreementConnection> connectionTask) {
this.connectionTask = connectionTask;
}
@Nullable
@Override
public KeyAgreementConnection call() throws Exception {
KeyAgreementConnection c = connectionTask.call();
if (c == null) return null;
aliceLatch.await();
if (alice || stopped) return c;
// Bob waits here for Alice to scan his QR code, determine her
// role, and send her key
InputStream in = c.getConnection().getReader().getInputStream();
while (!stopped && in.available() == 0) {
if (LOG.isLoggable(INFO))
LOG.info(c.getTransportId() + ": Waiting for data");
waitingForAlice();
Thread.sleep(500);
}
if (!stopped && LOG.isLoggable(INFO))
LOG.info(c.getTransportId().getString() + ": Data available");
return c;
@Nullable
@IoExecutor
private KeyAgreementConnection connect(DuplexPlugin plugin,
byte[] commitment, BdfList descriptor) throws InterruptedException {
// Repeat attempts until we time out, get stopped, or get interrupted
long end = clock.currentTimeMillis() + CONNECTION_TIMEOUT;
while (!stopped && clock.currentTimeMillis() < end) {
DuplexTransportConnection conn =
plugin.createKeyAgreementConnection(commitment, descriptor);
if (conn != null)
return new KeyAgreementConnection(conn, plugin.getId());
// Wait 2s before retry (to circumvent transient failures)
Thread.sleep(2000);
}
return null;
}
}

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.KeyAgreementTaskFactory;
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
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
KeyAgreementTaskFactory provideKeyAgreementTaskFactory(
KeyAgreementTaskFactoryImpl keyAgreementTaskFactory) {
return keyAgreementTaskFactory;
KeyAgreementTask provideKeyAgreementTask(
KeyAgreementTaskImpl keyAgreementTask) {
return keyAgreementTask;
}
@Provides

View File

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

View File

@@ -1,41 +0,0 @@
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,6 +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.SecretKey;
import org.briarproject.bramble.api.event.EventBus;
@@ -14,24 +15,30 @@ import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFinishedEvent
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStartedEvent;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.system.Clock;
import java.io.IOException;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
KeyAgreementProtocol.Callbacks, KeyAgreementConnector.Callbacks {
class KeyAgreementTaskImpl extends Thread implements
KeyAgreementTask, KeyAgreementProtocol.Callbacks {
private static final Logger LOG =
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;
@@ -40,15 +47,19 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
private Payload localPayload;
private Payload remotePayload;
KeyAgreementTaskImpl(CryptoComponent crypto, EventBus eventBus,
@Inject
KeyAgreementTaskImpl(Clock clock, CryptoComponent crypto,
KeyAgreementCrypto keyAgreementCrypto, EventBus eventBus,
PayloadEncoder payloadEncoder, PluginManager pluginManager,
@IoExecutor Executor ioExecutor,
ConnectionChooser connectionChooser) {
this.crypto = crypto;
this.keyAgreementCrypto = keyAgreementCrypto;
this.eventBus = eventBus;
this.payloadEncoder = payloadEncoder;
localKeyPair = crypto.generateAgreementKeyPair();
connector = new KeyAgreementConnector(this, crypto, pluginManager,
connectionChooser);
connector = new KeyAgreementConnector(clock, keyAgreementCrypto,
pluginManager, ioExecutor, connectionChooser);
}
@Override
@@ -95,8 +106,8 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
// Run BQP protocol over the connection
LOG.info("Starting BQP protocol");
KeyAgreementProtocol protocol = new KeyAgreementProtocol(this, crypto,
payloadEncoder, transport, remotePayload, localPayload,
localKeyPair, alice);
keyAgreementCrypto, 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,6 +1,7 @@
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;
@@ -39,20 +40,22 @@ 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.size() < 2) throw new FormatException();
if (payload.isEmpty()) throw new FormatException();
if (!r.eof()) throw new FormatException();
// 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);
// First element: the public key commitment
byte[] commitment = payload.getRaw(0);
if (commitment.length != COMMIT_LENGTH) throw new FormatException();
// Remaining elements: transport descriptors
List<TransportDescriptor> recognised = new ArrayList<>();
for (int i = 2; i < payload.size(); i++) {
for (int i = 1; i < payload.size(); i++) {
BdfList descriptor = payload.getList(i);
long transportId = descriptor.getLong(0);
if (transportId == TRANSPORT_ID_BLUETOOTH) {

View File

@@ -2,11 +2,8 @@ package org.briarproject.bramble.lifecycle;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.db.DataTooNewException;
import org.briarproject.bramble.api.db.DataTooOldException;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.MigrationListener;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.identity.AuthorFactory;
@@ -15,7 +12,7 @@ import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.lifecycle.ServiceException;
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Client;
@@ -32,21 +29,14 @@ import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_NEW_ERROR;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_OLD_ERROR;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DB_ERROR;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SERVICE_ERROR;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
@ThreadSafe
@NotNullByDefault
class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
class LifecycleManagerImpl implements LifecycleManager {
private static final Logger LOG =
Logger.getLogger(LifecycleManagerImpl.class.getName());
@@ -64,8 +54,6 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
private final CountDownLatch startupLatch = new CountDownLatch(1);
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
private volatile LifecycleState state = STARTING;
@Inject
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
CryptoComponent crypto, AuthorFactory authorFactory,
@@ -131,7 +119,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
LOG.info("Starting services");
long start = System.currentTimeMillis();
boolean reopened = db.open(this);
boolean reopened = db.open();
long duration = System.currentTimeMillis() - start;
if (LOG.isLoggable(INFO)) {
if (reopened)
@@ -143,10 +131,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
registerLocalAuthor(createLocalAuthor(nickname));
}
state = STARTING_SERVICES;
dbLatch.countDown();
eventBus.broadcast(new LifecycleEvent(STARTING_SERVICES));
Transaction txn = db.startTransaction(false);
try {
for (Client c : clients) {
@@ -172,17 +157,8 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
+ " took " + duration + " ms");
}
}
state = RUNNING;
startupLatch.countDown();
eventBus.broadcast(new LifecycleEvent(RUNNING));
return SUCCESS;
} catch (DataTooOldException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return DATA_TOO_OLD_ERROR;
} catch (DataTooNewException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return DATA_TOO_NEW_ERROR;
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return DB_ERROR;
@@ -194,12 +170,6 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
}
}
@Override
public void onMigrationRun() {
state = MIGRATING_DATABASE;
eventBus.broadcast(new LifecycleEvent(MIGRATING_DATABASE));
}
@Override
public void stopServices() {
try {
@@ -210,8 +180,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
}
try {
LOG.info("Stopping services");
state = STOPPING;
eventBus.broadcast(new LifecycleEvent(STOPPING));
eventBus.broadcast(new ShutdownEvent());
for (Service s : services) {
long start = System.currentTimeMillis();
s.stopService();
@@ -256,8 +225,4 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
shutdownLatch.await();
}
@Override
public LifecycleState getLifecycleState() {
return state;
}
}

View File

@@ -247,12 +247,12 @@ class ConnectionManagerImpl implements ConnectionManager {
ctx = keyManager.getStreamContext(transportId, tag);
} catch (IOException | DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
disposeReader(true, false);
dispose(true, false);
return;
}
if (ctx == null) {
LOG.info("Unrecognised tag");
disposeReader(false, false);
dispose(false, false);
return;
}
contactId = ctx.getContactId();
@@ -263,10 +263,10 @@ class ConnectionManagerImpl implements ConnectionManager {
// Create and run the incoming session
incomingSession = createIncomingSession(ctx, reader);
incomingSession.run();
disposeReader(false, true);
dispose(false, true);
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
disposeReader(true, true);
dispose(true, true);
} finally {
connectionRegistry.unregisterConnection(contactId, transportId,
true);
@@ -280,39 +280,28 @@ class ConnectionManagerImpl implements ConnectionManager {
ctx = keyManager.getStreamContext(contactId, transportId);
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
disposeWriter(true);
dispose(true, true);
return;
}
if (ctx == null) {
LOG.warning("Could not allocate stream context");
disposeWriter(true);
dispose(true, true);
return;
}
try {
// Create and run the outgoing session
outgoingSession = createDuplexOutgoingSession(ctx, writer);
outgoingSession.run();
disposeWriter(false);
dispose(false, true);
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
disposeWriter(true);
dispose(true, true);
}
}
private void disposeReader(boolean exception, boolean recognised) {
if (exception && outgoingSession != null)
outgoingSession.interrupt();
private void dispose(boolean exception, boolean recognised) {
try {
reader.dispose(exception, recognised);
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
private void disposeWriter(boolean exception) {
if (exception && incomingSession != null)
incomingSession.interrupt();
try {
writer.dispose(exception);
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
@@ -346,12 +335,12 @@ class ConnectionManagerImpl implements ConnectionManager {
ctx = keyManager.getStreamContext(contactId, transportId);
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
disposeWriter(true);
dispose(true);
return;
}
if (ctx == null) {
LOG.warning("Could not allocate stream context");
disposeWriter(true);
dispose(true);
return;
}
// Start the incoming session on another thread
@@ -360,10 +349,10 @@ class ConnectionManagerImpl implements ConnectionManager {
// Create and run the outgoing session
outgoingSession = createDuplexOutgoingSession(ctx, writer);
outgoingSession.run();
disposeWriter(false);
dispose(false);
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
disposeWriter(true);
dispose(true);
}
}
@@ -375,19 +364,19 @@ class ConnectionManagerImpl implements ConnectionManager {
ctx = keyManager.getStreamContext(transportId, tag);
} catch (IOException | DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
disposeReader(true, false);
dispose(true);
return;
}
// Unrecognised tags are suspicious in this case
if (ctx == null) {
LOG.warning("Unrecognised tag for returning stream");
disposeReader(true, false);
dispose(true);
return;
}
// Check that the stream comes from the expected contact
if (!ctx.getContactId().equals(contactId)) {
LOG.warning("Wrong contact ID for returning stream");
disposeReader(true, true);
dispose(true);
return;
}
connectionRegistry.registerConnection(contactId, transportId,
@@ -396,30 +385,20 @@ class ConnectionManagerImpl implements ConnectionManager {
// Create and run the incoming session
incomingSession = createIncomingSession(ctx, reader);
incomingSession.run();
disposeReader(false, true);
dispose(false);
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
disposeReader(true, true);
dispose(true);
} finally {
connectionRegistry.unregisterConnection(contactId, transportId,
false);
}
}
private void disposeReader(boolean exception, boolean recognised) {
if (exception && outgoingSession != null)
outgoingSession.interrupt();
try {
reader.dispose(exception, recognised);
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
private void disposeWriter(boolean exception) {
if (exception && incomingSession != null)
incomingSession.interrupt();
private void dispose(boolean exception) {
try {
// 'Recognised' is always true because we opened the connection
reader.dispose(exception, true);
writer.dispose(exception);
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);

View File

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

View File

@@ -0,0 +1,36 @@
package org.briarproject.bramble.plugin.bluetooth;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
@NotNullByDefault
interface BluetoothConnectionManager {
/**
* Returns true if a contact connection can be opened without exceeding
* the connection limit. This method does not need to be called for key
* exchange connections.
*/
boolean canOpenConnection();
/**
* Passes a newly opened connection to the manager. The manager may close
* the new connection or another connection to stay within the connection
* limit.
* <p/>
* Returns false if the manager has closed the new connection (this will
* never be the case for key exchange connections).
*/
boolean connectionOpened(DuplexTransportConnection conn,
boolean isForKeyExchange);
/**
* Informs the manager that the given connection has been closed.
*/
void connectionClosed(DuplexTransportConnection conn);
/**
* Informs the manager that all connections have been closed.
*/
void allConnectionsClosed();
}

View File

@@ -0,0 +1,88 @@
package org.briarproject.bramble.plugin.bluetooth;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import java.io.IOException;
import java.util.LinkedList;
import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.INFO;
@NotNullByDefault
@ThreadSafe
class BluetoothConnectionManagerImpl implements BluetoothConnectionManager {
private static final int MAX_OPEN_CONNECTIONS = 5;
private static final Logger LOG =
Logger.getLogger(BluetoothConnectionManagerImpl.class.getName());
private final Object lock = new Object();
private final LinkedList<DuplexTransportConnection> connections =
new LinkedList<>(); // Locking: lock
@Override
public boolean canOpenConnection() {
synchronized (lock) {
int open = connections.size();
if (LOG.isLoggable(INFO)) LOG.info(open + " open connections");
return open < MAX_OPEN_CONNECTIONS;
}
}
@Override
public boolean connectionOpened(DuplexTransportConnection conn,
boolean isForKeyExchange) {
DuplexTransportConnection close = null;
synchronized (lock) {
int open = connections.size();
boolean accept = isForKeyExchange || open < MAX_OPEN_CONNECTIONS;
if (accept) {
if (LOG.isLoggable(INFO))
LOG.info("Accepting connection, " + (open + 1) + " open");
connections.add(conn);
if (open == MAX_OPEN_CONNECTIONS) {
LOG.info("Closing old connection to stay within limit");
close = connections.poll();
}
} else {
if (LOG.isLoggable(INFO))
LOG.info("Refusing connection, " + open + " open");
close = conn;
}
}
if (close != null) tryToClose(close);
return close != conn;
}
private void tryToClose(DuplexTransportConnection conn) {
try {
conn.getReader().dispose(false, true);
conn.getWriter().dispose(false);
} catch (IOException e) {
if (LOG.isLoggable(INFO)) LOG.log(INFO, e.toString(), e);
}
}
@Override
public void connectionClosed(DuplexTransportConnection conn) {
synchronized (lock) {
connections.remove(conn);
if (LOG.isLoggable(INFO)) {
int open = connections.size();
LOG.info("Connection closed, " + open + " open");
}
}
}
@Override
public void allConnectionsClosed() {
synchronized (lock) {
connections.clear();
LOG.info("All connections closed");
}
}
}

View File

@@ -51,6 +51,8 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
private static final Logger LOG =
Logger.getLogger(BluetoothPlugin.class.getName());
protected final BluetoothConnectionManager connectionManager;
private final Executor ioExecutor;
private final SecureRandom secureRandom;
private final Backoff backoff;
@@ -91,8 +93,10 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
abstract DuplexTransportConnection connectTo(String address, String uuid)
throws IOException;
BluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom,
BluetoothPlugin(BluetoothConnectionManager connectionManager,
Executor ioExecutor, SecureRandom secureRandom,
Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
this.connectionManager = connectionManager;
this.ioExecutor = ioExecutor;
this.secureRandom = secureRandom;
this.backoff = backoff;
@@ -110,6 +114,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
void onAdapterDisabled() {
LOG.info("Bluetooth disabled");
tryToClose(socket);
connectionManager.allConnectionsClosed();
callback.transportDisabled();
}
@@ -213,7 +218,8 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
return;
}
backoff.reset();
callback.incomingConnectionCreated(conn);
if (connectionManager.connectionOpened(conn, false))
callback.incomingConnectionCreated(conn);
if (!running) return;
}
}
@@ -257,10 +263,15 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
if (StringUtils.isNullOrEmpty(uuid)) continue;
ioExecutor.execute(() -> {
if (!isRunning() || !shouldAllowContactConnections()) return;
if (!connectionManager.canOpenConnection()) {
LOG.info("Not connecting, too many open connections");
return;
}
DuplexTransportConnection conn = connect(address, uuid);
if (conn != null) {
backoff.reset();
callback.outgoingConnectionCreated(c, conn);
if (connectionManager.connectionOpened(conn, false))
callback.outgoingConnectionCreated(c, conn);
}
});
}
@@ -300,12 +311,19 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
@Override
public DuplexTransportConnection createConnection(ContactId c) {
if (!isRunning() || !shouldAllowContactConnections()) return null;
if (!connectionManager.canOpenConnection()) {
LOG.info("Not connecting, too many open connections");
return null;
}
TransportProperties p = callback.getRemoteProperties(c);
String address = p.get(PROP_ADDRESS);
if (StringUtils.isNullOrEmpty(address)) return null;
String uuid = p.get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) return null;
return connect(address, uuid);
DuplexTransportConnection conn = connect(address, uuid);
if (conn == null) return null;
// TODO: Why don't we reset the backoff here?
return connectionManager.connectionOpened(conn, false) ? conn : null;
}
@Override
@@ -355,7 +373,10 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
if (LOG.isLoggable(INFO))
LOG.info("Connecting to key agreement UUID " + uuid);
return connect(address, uuid);
DuplexTransportConnection conn = connect(address, uuid);
// The connection limit doesn't apply to key agreement
if (conn != null) connectionManager.connectionOpened(conn, true);
return conn;
}
private String parseAddress(BdfList descriptor) throws FormatException {
@@ -408,6 +429,8 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
public KeyAgreementConnection accept() throws IOException {
DuplexTransportConnection conn = acceptConnection(ss);
if (LOG.isLoggable(INFO)) LOG.info(ID + ": Incoming connection");
// The connection limit doesn't apply to key agreement
connectionManager.connectionOpened(conn, true);
return new KeyAgreementConnection(conn, ID);
}

View File

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

View File

@@ -10,7 +10,7 @@ import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.Offer;
@@ -38,7 +38,6 @@ import javax.annotation.concurrent.ThreadSafe;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
@@ -210,9 +209,8 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
} else if (e instanceof MessageToRequestEvent) {
if (((MessageToRequestEvent) e).getContactId().equals(contactId))
generateRequest();
} else if (e instanceof LifecycleEvent) {
LifecycleEvent l = (LifecycleEvent) e;
if (l.getLifecycleState() == STOPPING) interrupt();
} else if (e instanceof ShutdownEvent) {
interrupt();
}
}

View File

@@ -6,11 +6,16 @@ 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 {
@@ -23,9 +28,12 @@ class GroupFactoryImpl implements GroupFactory {
}
@Override
public Group createGroup(ClientId c, byte[] descriptor) {
byte[] hash = crypto.hash(GroupId.LABEL,
StringUtils.toUtf8(c.getString()), descriptor);
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);
return new Group(new GroupId(hash), c, descriptor);
}
}

View File

@@ -11,7 +11,7 @@ import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.Message;
@@ -27,7 +27,6 @@ import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
/**
* An incoming {@link SyncSession}.
@@ -97,9 +96,8 @@ class IncomingSession implements SyncSession, EventListener {
if (e instanceof ContactRemovedEvent) {
ContactRemovedEvent c = (ContactRemovedEvent) e;
if (c.getContactId().equals(contactId)) interrupt();
} else if (e instanceof LifecycleEvent) {
LifecycleEvent l = (LifecycleEvent) e;
if (l.getLifecycleState() == STOPPING) interrupt();
} else if (e instanceof ShutdownEvent) {
interrupt();
}
}

View File

@@ -12,8 +12,10 @@ 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
@@ -32,9 +34,9 @@ class MessageFactoryImpl implements MessageFactory {
throw new IllegalArgumentException();
byte[] timeBytes = new byte[ByteUtils.INT_64_BYTES];
ByteUtils.writeUint64(timestamp, timeBytes, 0);
byte[] idHash =
crypto.hash(MessageId.LABEL, g.getBytes(), timeBytes, body);
MessageId id = new MessageId(idHash);
byte[] hash = crypto.hash(LABEL, new byte[] {PROTOCOL_VERSION},
g.getBytes(), timeBytes, body);
MessageId id = new MessageId(hash);
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

@@ -10,7 +10,7 @@ import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.RecordWriter;
@@ -28,7 +28,6 @@ import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
@@ -110,9 +109,8 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
if (e instanceof ContactRemovedEvent) {
ContactRemovedEvent c = (ContactRemovedEvent) e;
if (c.getContactId().equals(contactId)) interrupt();
} else if (e instanceof LifecycleEvent) {
LifecycleEvent l = (LifecycleEvent) e;
if (l.getLifecycleState() == STOPPING) interrupt();
} else if (e instanceof ShutdownEvent) {
interrupt();
}
}

View File

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

View File

@@ -15,6 +15,8 @@ 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;
@@ -31,9 +33,14 @@ 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;
@@ -54,7 +61,8 @@ public class ClientHelperImplTest extends BrambleTestCase {
context.mock(MetadataEncoder.class);
private final CryptoComponent cryptoComponent =
context.mock(CryptoComponent.class);
private final ClientHelper clientHelper;
private final AuthorFactory authorFactory =
context.mock(AuthorFactory.class);
private final GroupId groupId = new GroupId(getRandomId());
private final BdfDictionary dictionary = new BdfDictionary();
@@ -66,17 +74,15 @@ 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();
public ClientHelperImplTest() {
clientHelper =
new ClientHelperImpl(db, messageFactory, bdfReaderFactory,
bdfWriterFactory, metadataParser, metadataEncoder,
cryptoComponent);
}
private final ClientHelper clientHelper = new ClientHelperImpl(db,
messageFactory, bdfReaderFactory, bdfWriterFactory, metadataParser,
metadataEncoder, cryptoComponent, authorFactory);
@Test
public void testAddLocalMessage() throws Exception {
boolean shared = true;
boolean shared = new Random().nextBoolean();
Transaction txn = new Transaction(null, false);
context.checking(new Expectations() {{
@@ -180,8 +186,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
oneOf(db).endTransaction(txn);
}});
assertEquals(map,
clientHelper.getMessageMetadataAsDictionary(groupId));
assertEquals(map, clientHelper.getMessageMetadataAsDictionary(groupId));
context.assertIsSatisfied();
}
@@ -318,8 +323,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
}});
try {
clientHelper
.verifySignature(label, rawMessage, publicKey, list);
clientHelper.verifySignature(label, rawMessage, publicKey, list);
fail();
} catch (GeneralSecurityException e) {
// expected
@@ -327,6 +331,166 @@ 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);
@@ -352,5 +516,4 @@ public class ClientHelperImplTest extends BrambleTestCase {
will(returnValue(eof));
}});
}
}

View File

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

View File

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

View File

@@ -0,0 +1,169 @@
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,15 +31,11 @@ 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];
@@ -47,7 +43,7 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase {
// Montgomery ladder multiplier
SecureRandom random = new PseudoSecureRandom(seed);
ECKeyGenerationParameters montgomeryGeneratorParams =
new ECKeyGenerationParameters(PARAMETERS, random);
new ECKeyGenerationParameters(montgomeryParameters, random);
ECKeyPairGenerator montgomeryGenerator = new ECKeyPairGenerator();
montgomeryGenerator.init(montgomeryGeneratorParams);
AsymmetricCipherKeyPair montgomeryKeyPair1 =
@@ -107,4 +103,13 @@ 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,16 +1,20 @@
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;
@@ -19,14 +23,22 @@ 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 {
@@ -37,8 +49,9 @@ 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) {
public static void main(String[] args) throws GeneralSecurityException {
for (String name : SEC_NAMES) {
ECDomainParameters params =
convertParams(SECNamedCurves.getByName(name));
@@ -51,43 +64,31 @@ public class EllipticCurvePerformanceTest {
runTest(name + " default", params);
runTest(name + " constant", constantTime(params));
}
runTest("ours", EllipticCurveConstants.PARAMETERS);
runCurve25519Test();
runEd25519Test();
}
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(generatorParams);
generator.init(new ECKeyGenerationParameters(params, random));
AsymmetricCipherKeyPair keyPair1 = generator.generateKeyPair();
ECPublicKeyParameters public1 =
(ECPublicKeyParameters) keyPair1.getPublic();
ECPrivateKeyParameters private1 =
(ECPrivateKeyParameters) keyPair1.getPrivate();
AsymmetricCipherKeyPair keyPair2 = generator.generateKeyPair();
ECPublicKeyParameters public2 =
(ECPublicKeyParameters) keyPair2.getPublic();
// Time some ECDH key agreements
List<Long> samples = new ArrayList<>();
for (int i = 0; i < SAMPLES; i++) {
ECDHCBasicAgreement agreement = new ECDHCBasicAgreement();
long start = System.nanoTime();
agreement.init(private1);
agreement.calculateAgreement(public2);
samples.add(System.nanoTime() - start);
}
long agreementMedian = median(samples);
// Time some ECDH and ECDHC key agreements
long agreementMedian = runAgreementTest(keyPair1, keyPair2, false);
long agreementWithCofactorMedian =
runAgreementTest(keyPair1, keyPair2, true);
// Time some signatures
List<Long> samples = new ArrayList<>();
List<byte[]> signatures = new ArrayList<>();
samples.clear();
for (int i = 0; i < SAMPLES; i++) {
Digest digest = new Blake2sDigest();
Digest digest = new Blake2bDigest(256);
DSAKCalculator calculator = new HMacDSAKCalculator(digest);
DSADigestSigner signer = new DSADigestSigner(new ECDSASigner(
calculator), digest);
long start = System.nanoTime();
signer.init(true, new ParametersWithRandom(private1, random));
signer.init(true,
new ParametersWithRandom(keyPair1.getPrivate(), random));
signer.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN);
signatures.add(signer.generateSignature());
samples.add(System.nanoTime() - start);
@@ -96,22 +97,88 @@ public class EllipticCurvePerformanceTest {
// Time some signature verifications
samples.clear();
for (int i = 0; i < SAMPLES; i++) {
Digest digest = new Blake2sDigest();
Digest digest = new Blake2bDigest(256);
DSAKCalculator calculator = new HMacDSAKCalculator(digest);
DSADigestSigner signer = new DSADigestSigner(new ECDSASigner(
calculator), digest);
long start = System.nanoTime();
signer.init(false, public1);
signer.init(false, keyPair1.getPublic());
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(name + ": "
+ agreementMedian + " "
+ signatureMedian + " "
+ verificationMedian);
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));
}
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());
crypto = new CryptoComponentImpl(new TestSecureRandomProvider(), null);
}
@Test

View File

@@ -2,41 +2,80 @@ 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
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 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 testDeriveSharedSecret() throws Exception {
SecureRandomProvider
secureRandomProvider = new TestSecureRandomProvider();
CryptoComponent crypto = new CryptoComponentImpl(secureRandomProvider);
public void testDerivesSharedSecret() throws Exception {
KeyPair aPair = crypto.generateAgreementKeyPair();
byte[] aPub = aPair.getPublic().getEncoded();
KeyPair bPair = crypto.generateAgreementKeyPair();
byte[] bPub = bPair.getPublic().getEncoded();
SecretKey aShared = crypto.deriveSharedSecret(bPub, aPair, true);
SecretKey bShared = crypto.deriveSharedSecret(aPub, bPair, false);
SecretKey aShared = crypto.deriveSharedSecret(SHARED_SECRET_LABEL,
bPair.getPublic(), aPair, inputs);
SecretKey bShared = crypto.deriveSharedSecret(SHARED_SECRET_LABEL,
aPair.getPublic(), bPair, inputs);
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,35 +16,34 @@ 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 CryptoComponent crypto;
private final SecretKey master;
public KeyDerivationTest() {
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
master = TestUtils.getSecretKey();
}
private final SecretKey master = getSecretKey();
@Test
public void testKeysAreDistinct() {
TransportKeys k = crypto.deriveTransportKeys(transportId, master,
123, true);
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
master, 123, true);
assertAllDifferent(k);
}
@Test
public void testCurrentKeysMatchCurrentKeysOfContact() {
// Start in rotation period 123
TransportKeys kA = crypto.deriveTransportKeys(transportId, master,
123, true);
TransportKeys kB = crypto.deriveTransportKeys(transportId, master,
123, false);
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
master, 123, true);
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
master, 123, false);
// Alice's incoming keys should equal Bob's outgoing keys
assertArrayEquals(kA.getCurrentIncomingKeys().getTagKey().getBytes(),
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
@@ -56,8 +55,8 @@ public class KeyDerivationTest extends BrambleTestCase {
assertArrayEquals(kA.getCurrentOutgoingKeys().getHeaderKey().getBytes(),
kB.getCurrentIncomingKeys().getHeaderKey().getBytes());
// Rotate into the future
kA = crypto.rotateTransportKeys(kA, 456);
kB = crypto.rotateTransportKeys(kB, 456);
kA = transportCrypto.rotateTransportKeys(kA, 456);
kB = transportCrypto.rotateTransportKeys(kB, 456);
// Alice's incoming keys should equal Bob's outgoing keys
assertArrayEquals(kA.getCurrentIncomingKeys().getTagKey().getBytes(),
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
@@ -73,22 +72,23 @@ public class KeyDerivationTest extends BrambleTestCase {
@Test
public void testPreviousKeysMatchPreviousKeysOfContact() {
// Start in rotation period 123
TransportKeys kA = crypto.deriveTransportKeys(transportId, master,
123, true);
TransportKeys kB = crypto.deriveTransportKeys(transportId, master,
123, false);
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
master, 123, true);
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
master, 123, false);
// Compare Alice's previous keys in period 456 with Bob's current keys
// in period 455
kA = crypto.rotateTransportKeys(kA, 456);
kB = crypto.rotateTransportKeys(kB, 455);
kA = transportCrypto.rotateTransportKeys(kA, 456);
kB = transportCrypto.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 = crypto.rotateTransportKeys(kB, 457);
kB = transportCrypto.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 = crypto.deriveTransportKeys(transportId, master,
123, true);
TransportKeys kB = crypto.deriveTransportKeys(transportId, master,
123, false);
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
master, 123, true);
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
master, 123, false);
// Compare Alice's current keys in period 456 with Bob's next keys in
// period 455
kA = crypto.rotateTransportKeys(kA, 456);
kB = crypto.rotateTransportKeys(kB, 455);
kA = transportCrypto.rotateTransportKeys(kA, 456);
kB = transportCrypto.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 = crypto.rotateTransportKeys(kB, 457);
kB = transportCrypto.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 = TestUtils.getSecretKey();
SecretKey master1 = getSecretKey();
assertFalse(Arrays.equals(master.getBytes(), master1.getBytes()));
TransportKeys k = crypto.deriveTransportKeys(transportId, master,
123, true);
TransportKeys k1 = crypto.deriveTransportKeys(transportId, master1,
123, true);
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
master, 123, true);
TransportKeys k1 = transportCrypto.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 = crypto.deriveTransportKeys(transportId, master,
123, true);
TransportKeys k1 = crypto.deriveTransportKeys(transportId1, master,
123, true);
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
master, 123, true);
TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId1,
master, 123, true);
assertAllDifferent(k, k1);
}

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