Compare commits

..

201 Commits

Author SHA1 Message Date
akwizgran
f25b16e680 Listen for wifi AP state changes.
We won't necessarily receive a connectivity change event when the wifi AP is enabled.
2018-03-26 17:19:17 +01:00
akwizgran
467fdb6468 Log active network info. 2018-03-26 11:49:03 +01:00
akwizgran
c0840dc332 Log a lot of information about the network state. 2018-03-22 17:41:07 +00:00
Torsten Grote
235183a3af Merge branch '1177-blank-viewfinder' into 'master'
Show viewfinder again after connection fails

Closes #1177

See merge request akwizgran/briar!735
2018-03-20 13:13:14 +00:00
akwizgran
701e51dfc8 When resetting, restart camera if we've stopped it. 2018-03-20 11:58:58 +00:00
akwizgran
3361922834 Don't create a stack of QR code fragments. 2018-03-20 11:58:57 +00:00
akwizgran
fcabf697d6 Remove performance logging. 2018-03-20 11:58:57 +00:00
Torsten Grote
4d502576c9 Merge branch '545-remove-clientid-from-validator-db-methods' into 'master'
Remove client ID from validator's DB methods

See merge request akwizgran/briar!732
2018-03-14 12:39:59 +00:00
akwizgran
5fe68e6f82 Remove client ID from validator's DB methods. 2018-03-09 16:11:58 +00:00
akwizgran
f725c7ab9a Merge branch '1169-settings-npe' into 'master'
Disable settings until they have been loaded

Closes #1169

See merge request akwizgran/briar!726
2018-03-08 15:40:13 +00:00
Torsten Grote
03b4907311 Disable settings until they have been loaded
In practise, this is not noticeable in the UI.
Only when the database is congested, it should become visible and
prevent a crash when the sound setting is clicked.
2018-03-07 15:20:09 -03:00
Torsten Grote
809b9f8919 Update translations 2018-03-07 09:49:46 -03:00
akwizgran
85cc23444c Merge branch 'transport-indicators-no-buttons' into 'master'
Prevent transport indicators from looking like buttons

Closes #185

See merge request akwizgran/briar!714
2018-03-07 10:41:13 +00:00
akwizgran
c073c5c8bd Merge branch '283-key-exchange-connections' into 'master'
Refactor key agreement connection choosing

Closes #283

See merge request akwizgran/briar!711
2018-03-07 10:37:45 +00:00
akwizgran
976c8a9578 Merge branch '1174-link-click-crash' into 'master'
Get unwrapped context when clicking links to prevent crash on Android 4

Closes #1174

See merge request akwizgran/briar!709
2018-03-07 10:36:33 +00:00
akwizgran
d52ca14ebe Merge branch '1168-startup-status-screen' into 'master'
Show status message while opening and migrating DB

Closes #1168

See merge request akwizgran/briar!708
2018-03-07 10:30:11 +00:00
Torsten Grote
a178dbae9e Prevent transport indicators from looking like buttons 2018-03-06 16:58:23 -03:00
akwizgran
9a4f0b8e89 Add more lifecycle states, merge lifecycle events. 2018-03-06 15:21:26 -03:00
Torsten Grote
db7dbfce68 Start NavDrawerActivity only after database was opened and services started 2018-03-06 15:14:37 -03:00
Torsten Grote
80770b0216 Show a status screen when opening the database or applying migrations 2018-03-06 15:14:36 -03:00
Torsten Grote
9f02bbbba1 Do not show splash screen when signed in 2018-03-06 15:14:35 -03:00
Torsten Grote
190aeef34e Passing in reference to FragmentManager when clicking links to prevent crash on Android 4 2018-03-06 15:01:37 -03:00
akwizgran
6fbaae0e5e Merge branch 'fix-intro-fragment' into 'master'
Fix uncentered intro fragment

See merge request akwizgran/briar!712
2018-03-05 10:51:56 +00:00
akwizgran
5cc0f08b8f Merge branch '1154-fix-notification-light' into 'master'
Fix notification light

Closes #1154

See merge request akwizgran/briar!710
2018-03-05 10:49:29 +00:00
goapunk
976460e0b7 fix uncentered intro fragment
Signed-off-by: goapunk <noobie@goapunks.net>
2018-03-03 16:21:34 +01:00
akwizgran
9cdd537600 Refactor key agreement connection choosing. 2018-03-02 13:11:56 +00:00
Torsten Grote
c44a3d01b9 Fix notification light 2018-02-28 12:53:22 -03:00
akwizgran
a8ed86575d Merge branch '1136-startup-failure-ux' into 'master'
Improve UX for startup failures

Closes #1136

See merge request akwizgran/briar!706
2018-02-26 17:18:44 +00:00
Torsten Grote
46406d8d1a Improve UX for startup failures
Show a proper error message when database is too new or too old.
2018-02-26 13:39:07 -03:00
Torsten Grote
05210257a0 Merge branch '1176-startup-failure-crash' into 'master'
Inject StartupFailureActivity to prevent NPE

Closes #1176

See merge request akwizgran/briar!705
2018-02-23 12:24:29 +00:00
akwizgran
d5c89640c1 Inject StartupFailureActivity to prevent NPE. 2018-02-23 09:22:31 +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
327 changed files with 10882 additions and 8206 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 { defaultConfig {
minSdkVersion 14 minSdkVersion 14
targetSdkVersion 26 targetSdkVersion 26
versionCode 1618 versionCode 1700
versionName "0.16.18" versionName "0.17.0"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
} }
@@ -43,6 +43,7 @@ dependencyVerification {
'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728', 'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728',
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f', 'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'net.i2p.crypto:eddsa:0.2.0:eddsa-0.2.0.jar:a7cb1b85c16e2f0730b9204106929a1d9aaae1df728adc7041a8b8b605692140',
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb', 'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
'org.jacoco:org.jacoco.agent:0.7.4.201502262128:org.jacoco.agent-0.7.4.201502262128-runtime.jar:e357a0f1d573c2f702a273992b1b6cb661734f66311854efb3778a888515c5b5', 'org.jacoco:org.jacoco.agent:0.7.4.201502262128:org.jacoco.agent-0.7.4.201502262128-runtime.jar:e357a0f1d573c2f702a273992b1b6cb661734f66311854efb3778a888515c5b5',
'org.jacoco:org.jacoco.agent:0.7.4.201502262128:org.jacoco.agent-0.7.4.201502262128.jar:47b4bec6df11a1118da3953da8b9fa1e7079d6fec857faa1a3cf912e53a6fd4e', 'org.jacoco:org.jacoco.agent:0.7.4.201502262128:org.jacoco.agent-0.7.4.201502262128.jar:47b4bec6df11a1118da3953da8b9fa1e7079d6fec857faa1a3cf912e53a6fd4e',

View File

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

View File

@@ -14,7 +14,7 @@ import org.briarproject.bramble.api.reporting.DevReporter;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.api.system.LocationUtils; import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.Scheduler; import org.briarproject.bramble.api.system.Scheduler;
import org.briarproject.bramble.plugin.droidtooth.DroidtoothPluginFactory; import org.briarproject.bramble.plugin.bluetooth.AndroidBluetoothPluginFactory;
import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory; import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
import org.briarproject.bramble.plugin.tor.TorPluginFactory; import org.briarproject.bramble.plugin.tor.TorPluginFactory;
@@ -41,8 +41,9 @@ public class AndroidPluginModule {
Application app, LocationUtils locationUtils, DevReporter reporter, Application app, LocationUtils locationUtils, DevReporter reporter,
EventBus eventBus) { EventBus eventBus) {
Context appContext = app.getApplicationContext(); Context appContext = app.getApplicationContext();
DuplexPluginFactory bluetooth = new DroidtoothPluginFactory(ioExecutor, DuplexPluginFactory bluetooth =
androidExecutor, appContext, random, eventBus, backoffFactory); new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor,
appContext, random, eventBus, backoffFactory);
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, scheduler, DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, scheduler,
appContext, locationUtils, reporter, eventBus, appContext, locationUtils, reporter, eventBus,
torSocketFactory, backoffFactory); torSocketFactory, backoffFactory);

View File

@@ -0,0 +1,206 @@
package org.briarproject.bramble.plugin.bluetooth;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.util.AndroidUtils;
import java.io.Closeable;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE;
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
import static android.bluetooth.BluetoothAdapter.STATE_OFF;
import static android.bluetooth.BluetoothAdapter.STATE_ON;
import static java.util.logging.Level.WARNING;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
private static final Logger LOG =
Logger.getLogger(AndroidBluetoothPlugin.class.getName());
private final AndroidExecutor androidExecutor;
private final Context appContext;
private volatile boolean wasEnabledByUs = false;
private volatile BluetoothStateReceiver receiver = null;
// Non-null if the plugin started successfully
private volatile BluetoothAdapter adapter = null;
AndroidBluetoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor,
Context appContext, SecureRandom secureRandom, Backoff backoff,
DuplexPluginCallback callback, int maxLatency) {
super(ioExecutor, secureRandom, backoff, callback, maxLatency);
this.androidExecutor = androidExecutor;
this.appContext = appContext;
}
@Override
public void start() throws PluginException {
super.start();
// Listen for changes to the Bluetooth state
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_STATE_CHANGED);
filter.addAction(ACTION_SCAN_MODE_CHANGED);
receiver = new BluetoothStateReceiver();
appContext.registerReceiver(receiver, filter);
}
@Override
public void stop() {
super.stop();
if (receiver != null) appContext.unregisterReceiver(receiver);
}
@Override
void initialiseAdapter() throws IOException {
// BluetoothAdapter.getDefaultAdapter() must be called on a thread
// with a message queue, so submit it to the AndroidExecutor
try {
adapter = androidExecutor.runOnBackgroundThread(
BluetoothAdapter::getDefaultAdapter).get();
} catch (InterruptedException | ExecutionException e) {
throw new IOException(e);
}
if (adapter == null)
throw new IOException("Bluetooth is not supported");
}
@Override
boolean isAdapterEnabled() {
return adapter != null && adapter.isEnabled();
}
@Override
void enableAdapter() {
if (adapter != null && !adapter.isEnabled()) {
if (adapter.enable()) {
LOG.info("Enabling Bluetooth");
wasEnabledByUs = true;
} else {
LOG.info("Could not enable Bluetooth");
}
}
}
@Override
void disableAdapterIfEnabledByUs() {
if (isAdapterEnabled() && wasEnabledByUs) {
if (adapter.disable()) LOG.info("Disabling Bluetooth");
else LOG.info("Could not disable Bluetooth");
wasEnabledByUs = false;
}
}
@Override
void setEnabledByUs() {
wasEnabledByUs = true;
}
@Override
@Nullable
String getBluetoothAddress() {
String address = AndroidUtils.getBluetoothAddress(appContext, adapter);
return address.isEmpty() ? null : address;
}
@Override
BluetoothServerSocket openServerSocket(String uuid) throws IOException {
return adapter.listenUsingInsecureRfcommWithServiceRecord(
"RFCOMM", UUID.fromString(uuid));
}
@Override
void tryToClose(@Nullable BluetoothServerSocket ss) {
try {
if (ss != null) ss.close();
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
@Override
DuplexTransportConnection acceptConnection(BluetoothServerSocket ss)
throws IOException {
return wrapSocket(ss.accept());
}
private DuplexTransportConnection wrapSocket(BluetoothSocket s) {
return new AndroidBluetoothTransportConnection(this, s);
}
@Override
boolean isValidAddress(String address) {
return BluetoothAdapter.checkBluetoothAddress(address);
}
@Override
DuplexTransportConnection connectTo(String address, String uuid)
throws IOException {
BluetoothDevice d = adapter.getRemoteDevice(address);
UUID u = UUID.fromString(uuid);
BluetoothSocket s = null;
try {
s = d.createInsecureRfcommSocketToServiceRecord(u);
s.connect();
return wrapSocket(s);
} catch (IOException e) {
tryToClose(s);
throw e;
}
}
private void tryToClose(@Nullable Closeable c) {
try {
if (c != null) c.close();
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
private class BluetoothStateReceiver extends BroadcastReceiver {
@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");
}
}
}
}

View File

@@ -1,4 +1,4 @@
package org.briarproject.bramble.plugin.droidtooth; package org.briarproject.bramble.plugin.bluetooth;
import android.content.Context; import android.content.Context;
@@ -21,7 +21,7 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class DroidtoothPluginFactory implements DuplexPluginFactory { public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
@@ -35,7 +35,7 @@ public class DroidtoothPluginFactory implements DuplexPluginFactory {
private final EventBus eventBus; private final EventBus eventBus;
private final BackoffFactory backoffFactory; private final BackoffFactory backoffFactory;
public DroidtoothPluginFactory(Executor ioExecutor, public AndroidBluetoothPluginFactory(Executor ioExecutor,
AndroidExecutor androidExecutor, Context appContext, AndroidExecutor androidExecutor, Context appContext,
SecureRandom secureRandom, EventBus eventBus, SecureRandom secureRandom, EventBus eventBus,
BackoffFactory backoffFactory) { BackoffFactory backoffFactory) {
@@ -61,7 +61,7 @@ public class DroidtoothPluginFactory implements DuplexPluginFactory {
public DuplexPlugin createPlugin(DuplexPluginCallback callback) { public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE); MAX_POLLING_INTERVAL, BACKOFF_BASE);
DroidtoothPlugin plugin = new DroidtoothPlugin(ioExecutor, AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(ioExecutor,
androidExecutor, appContext, secureRandom, backoff, callback, androidExecutor, appContext, secureRandom, backoff, callback,
MAX_LATENCY); MAX_LATENCY);
eventBus.addListener(plugin); eventBus.addListener(plugin);

View File

@@ -1,4 +1,4 @@
package org.briarproject.bramble.plugin.droidtooth; package org.briarproject.bramble.plugin.bluetooth;
import android.bluetooth.BluetoothSocket; import android.bluetooth.BluetoothSocket;
@@ -11,11 +11,12 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
@NotNullByDefault @NotNullByDefault
class DroidtoothTransportConnection extends AbstractDuplexTransportConnection { class AndroidBluetoothTransportConnection
extends AbstractDuplexTransportConnection {
private final BluetoothSocket socket; private final BluetoothSocket socket;
DroidtoothTransportConnection(Plugin plugin, BluetoothSocket socket) { AndroidBluetoothTransportConnection(Plugin plugin, BluetoothSocket socket) {
super(plugin); super(plugin);
this.socket = socket; this.socket = socket;
} }

View File

@@ -1,490 +0,0 @@
package org.briarproject.bramble.plugin.droidtooth;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.DisableBluetoothEvent;
import org.briarproject.bramble.api.plugin.event.EnableBluetoothEvent;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.util.AndroidUtils;
import org.briarproject.bramble.util.StringUtils;
import java.io.Closeable;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE;
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
import static android.bluetooth.BluetoothAdapter.STATE_OFF;
import static android.bluetooth.BluetoothAdapter.STATE_ON;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENABLE;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class DroidtoothPlugin implements DuplexPlugin, EventListener {
private static final Logger LOG =
Logger.getLogger(DroidtoothPlugin.class.getName());
private final Executor ioExecutor;
private final AndroidExecutor androidExecutor;
private final Context appContext;
private final SecureRandom secureRandom;
private final Backoff backoff;
private final DuplexPluginCallback callback;
private final int maxLatency;
private final AtomicBoolean used = new AtomicBoolean(false);
private volatile boolean running = false;
private volatile boolean wasEnabledByUs = false;
private volatile BluetoothStateReceiver receiver = null;
private volatile BluetoothServerSocket socket = null;
// Non-null if the plugin started successfully
private volatile BluetoothAdapter adapter = null;
DroidtoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor,
Context appContext, SecureRandom secureRandom, Backoff backoff,
DuplexPluginCallback callback, int maxLatency) {
this.ioExecutor = ioExecutor;
this.androidExecutor = androidExecutor;
this.appContext = appContext;
this.secureRandom = secureRandom;
this.backoff = backoff;
this.callback = callback;
this.maxLatency = maxLatency;
}
@Override
public TransportId getId() {
return ID;
}
@Override
public int getMaxLatency() {
return maxLatency;
}
@Override
public int getMaxIdleTime() {
// Bluetooth detects dead connections so we don't need keepalives
return Integer.MAX_VALUE;
}
@Override
public void start() throws PluginException {
if (used.getAndSet(true)) throw new IllegalStateException();
// BluetoothAdapter.getDefaultAdapter() must be called on a thread
// with a message queue, so submit it to the AndroidExecutor
try {
adapter = androidExecutor.runOnBackgroundThread(
BluetoothAdapter::getDefaultAdapter).get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
LOG.warning("Interrupted while getting BluetoothAdapter");
throw new PluginException(e);
} catch (ExecutionException e) {
throw new PluginException(e);
}
if (adapter == null) {
LOG.info("Bluetooth is not supported");
throw new PluginException();
}
running = true;
// Listen for changes to the Bluetooth state
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_STATE_CHANGED);
filter.addAction(ACTION_SCAN_MODE_CHANGED);
receiver = new BluetoothStateReceiver();
appContext.registerReceiver(receiver, filter);
// If Bluetooth is enabled, bind a socket
if (adapter.isEnabled()) {
bind();
} else {
// Enable Bluetooth if settings allow
if (callback.getSettings().getBoolean(PREF_BT_ENABLE, false)) {
enableAdapter();
} else {
LOG.info("Not enabling Bluetooth");
}
}
}
private void bind() {
ioExecutor.execute(() -> {
if (!isRunning()) return;
String address = AndroidUtils.getBluetoothAddress(appContext,
adapter);
if (LOG.isLoggable(INFO))
LOG.info("Local address " + scrubMacAddress(address));
if (!StringUtils.isNullOrEmpty(address)) {
// Advertise the Bluetooth address to contacts
TransportProperties p = new TransportProperties();
p.put(PROP_ADDRESS, address);
callback.mergeLocalProperties(p);
}
// Bind a server socket to accept connections from contacts
BluetoothServerSocket ss;
try {
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
"RFCOMM", getUuid());
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return;
}
if (!isRunning()) {
tryToClose(ss);
return;
}
LOG.info("Socket bound");
socket = ss;
backoff.reset();
callback.transportEnabled();
acceptContactConnections();
});
}
private UUID getUuid() {
String uuid = callback.getLocalProperties().get(PROP_UUID);
if (uuid == null) {
byte[] random = new byte[UUID_BYTES];
secureRandom.nextBytes(random);
uuid = UUID.nameUUIDFromBytes(random).toString();
TransportProperties p = new TransportProperties();
p.put(PROP_UUID, uuid);
callback.mergeLocalProperties(p);
}
return UUID.fromString(uuid);
}
private void tryToClose(@Nullable BluetoothServerSocket ss) {
try {
if (ss != null) ss.close();
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally {
callback.transportDisabled();
}
}
private void acceptContactConnections() {
while (isRunning()) {
BluetoothSocket s;
try {
s = socket.accept();
} catch (IOException e) {
// This is expected when the socket is closed
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
return;
}
if (LOG.isLoggable(INFO)) {
String address = s.getRemoteDevice().getAddress();
LOG.info("Connection from " + scrubMacAddress(address));
}
backoff.reset();
callback.incomingConnectionCreated(wrapSocket(s));
}
}
private DuplexTransportConnection wrapSocket(BluetoothSocket s) {
return new DroidtoothTransportConnection(this, s);
}
private void enableAdapter() {
if (adapter != null && !adapter.isEnabled()) {
if (adapter.enable()) {
LOG.info("Enabling Bluetooth");
wasEnabledByUs = true;
} else {
LOG.info("Could not enable Bluetooth");
}
}
}
@Override
public void stop() {
running = false;
if (receiver != null) appContext.unregisterReceiver(receiver);
tryToClose(socket);
disableAdapter();
}
private void disableAdapter() {
if (adapter != null && adapter.isEnabled() && wasEnabledByUs) {
if (adapter.disable()) LOG.info("Disabling Bluetooth");
else LOG.info("Could not disable Bluetooth");
}
}
@Override
public boolean isRunning() {
return running && adapter != null && adapter.isEnabled();
}
@Override
public boolean shouldPoll() {
return true;
}
@Override
public int getPollingInterval() {
return backoff.getPollingInterval();
}
@Override
public void poll(Collection<ContactId> connected) {
if (!isRunning()) return;
backoff.increment();
// Try to connect to known devices in parallel
Map<ContactId, TransportProperties> remote =
callback.getRemoteProperties();
for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
ContactId c = e.getKey();
if (connected.contains(c)) continue;
String address = e.getValue().get(PROP_ADDRESS);
if (StringUtils.isNullOrEmpty(address)) continue;
String uuid = e.getValue().get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) continue;
ioExecutor.execute(() -> {
if (!running) return;
BluetoothSocket s = connect(address, uuid);
if (s != null) {
backoff.reset();
callback.outgoingConnectionCreated(c, wrapSocket(s));
}
});
}
}
@Nullable
private BluetoothSocket connect(String address, String uuid) {
// Validate the address
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
if (LOG.isLoggable(WARNING))
// not scrubbing here to be able to figure out the problem
LOG.warning("Invalid address " + address);
return null;
}
// Validate the UUID
UUID u;
try {
u = UUID.fromString(uuid);
} catch (IllegalArgumentException e) {
if (LOG.isLoggable(WARNING)) LOG.warning("Invalid UUID " + uuid);
return null;
}
// Try to connect
BluetoothDevice d = adapter.getRemoteDevice(address);
BluetoothSocket s = null;
try {
s = d.createInsecureRfcommSocketToServiceRecord(u);
if (LOG.isLoggable(INFO))
LOG.info("Connecting to " + scrubMacAddress(address));
s.connect();
if (LOG.isLoggable(INFO))
LOG.info("Connected to " + scrubMacAddress(address));
return s;
} catch (IOException e) {
if (LOG.isLoggable(INFO)) {
LOG.info("Failed to connect to " + scrubMacAddress(address)
+ ": " + e);
}
tryToClose(s);
return null;
}
}
private void tryToClose(@Nullable Closeable c) {
try {
if (c != null) c.close();
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
@Override
public DuplexTransportConnection createConnection(ContactId c) {
if (!isRunning()) 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;
BluetoothSocket s = connect(address, uuid);
if (s == null) return null;
return new DroidtoothTransportConnection(this, s);
}
@Override
public boolean supportsKeyAgreement() {
return true;
}
@Override
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
if (!isRunning()) return null;
// There's no point listening if we can't discover our own address
String address = AndroidUtils.getBluetoothAddress(appContext, adapter);
if (address.isEmpty()) return null;
// No truncation necessary because COMMIT_LENGTH = 16
UUID uuid = UUID.nameUUIDFromBytes(commitment);
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
// Bind a server socket for receiving key agreement connections
BluetoothServerSocket ss;
try {
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
"RFCOMM", uuid);
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return null;
}
BdfList descriptor = new BdfList();
descriptor.add(TRANSPORT_ID_BLUETOOTH);
descriptor.add(StringUtils.macToBytes(address));
return new BluetoothKeyAgreementListener(descriptor, ss);
}
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor, long timeout) {
if (!isRunning()) return null;
String address;
try {
address = parseAddress(descriptor);
} catch (FormatException e) {
LOG.info("Invalid address in key agreement descriptor");
return null;
}
// No truncation necessary because COMMIT_LENGTH = 16
UUID uuid = UUID.nameUUIDFromBytes(commitment);
if (LOG.isLoggable(INFO))
LOG.info("Connecting to key agreement UUID " + uuid);
BluetoothSocket s = connect(address, uuid.toString());
if (s == null) return null;
return new DroidtoothTransportConnection(this, s);
}
private String parseAddress(BdfList descriptor) throws FormatException {
byte[] mac = descriptor.getRaw(1);
if (mac.length != 6) throw new FormatException();
return StringUtils.macToString(mac);
}
@Override
public void eventOccurred(Event e) {
if (e instanceof EnableBluetoothEvent) {
enableAdapterAsync();
} else if (e instanceof DisableBluetoothEvent) {
disableAdapterAsync();
}
}
private void enableAdapterAsync() {
ioExecutor.execute(this::enableAdapter);
}
private void disableAdapterAsync() {
ioExecutor.execute(this::disableAdapter);
}
private class BluetoothStateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context ctx, Intent intent) {
int state = intent.getIntExtra(EXTRA_STATE, 0);
if (state == STATE_ON) {
LOG.info("Bluetooth enabled");
bind();
} else if (state == STATE_OFF) {
LOG.info("Bluetooth disabled");
tryToClose(socket);
}
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");
}
}
}
private class BluetoothKeyAgreementListener extends KeyAgreementListener {
private final BluetoothServerSocket ss;
private BluetoothKeyAgreementListener(BdfList descriptor,
BluetoothServerSocket ss) {
super(descriptor);
this.ss = ss;
}
@Override
public Callable<KeyAgreementConnection> listen() {
return () -> {
BluetoothSocket s = ss.accept();
if (LOG.isLoggable(INFO))
LOG.info(ID.getString() + ": Incoming connection");
return new KeyAgreementConnection(
new DroidtoothTransportConnection(
DroidtoothPlugin.this, s), ID);
};
}
@Override
public void close() {
try {
ss.close();
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}
}

View File

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

View File

@@ -614,7 +614,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override @Override
public DuplexTransportConnection createKeyAgreementConnection( public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor, long timeout) { byte[] commitment, BdfList descriptor) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@@ -716,8 +716,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
boolean online = net != null && net.isConnected(); boolean online = net != null && net.isConnected();
boolean wifi = online && net.getType() == TYPE_WIFI; boolean wifi = online && net.getType() == TYPE_WIFI;
String country = locationUtils.getCurrentCountry(); String country = locationUtils.getCurrentCountry();
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked( boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(country);
country);
Settings s = callback.getSettings(); Settings s = callback.getSettings();
int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_ALWAYS); int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_ALWAYS);

View File

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

View File

@@ -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.data.BdfList;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
@@ -93,10 +94,13 @@ public interface ClientHelper {
BdfList toList(Message m) throws FormatException; BdfList toList(Message m) throws FormatException;
BdfList toList(Author a);
byte[] sign(String label, BdfList toSign, byte[] privateKey) byte[] sign(String label, BdfList toSign, byte[] privateKey)
throws FormatException, GeneralSecurityException; throws FormatException, GeneralSecurityException;
void verifySignature(String label, byte[] sig, byte[] publicKey, void verifySignature(String label, byte[] sig, byte[] publicKey,
BdfList signed) throws FormatException, GeneralSecurityException; 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. * 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. * 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 * Creates a group for the given client to share between the given authors
* identified by their AuthorIds. * identified by their AuthorIds.
*/ */
Group createContactGroup(ClientId clientId, AuthorId authorId1, Group createContactGroup(ClientId clientId, int clientVersion,
AuthorId authorId2); AuthorId authorId1, AuthorId authorId2);
} }

View File

@@ -12,6 +12,32 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
@NotNullByDefault @NotNullByDefault
public interface ContactExchangeTask { public interface ContactExchangeTask {
/**
* The current version of the contact exchange protocol
*/
int PROTOCOL_VERSION = 0;
/**
* Label for deriving Alice's header key from the master secret.
*/
String ALICE_KEY_LABEL =
"org.briarproject.bramble.contact/ALICE_HEADER_KEY";
/**
* Label for deriving Bob's header key from the master secret.
*/
String BOB_KEY_LABEL = "org.briarproject.bramble.contact/BOB_HEADER_KEY";
/**
* Label for deriving Alice's key binding nonce from the master secret.
*/
String ALICE_NONCE_LABEL = "org.briarproject.bramble.contact/ALICE_NONCE";
/**
* Label for deriving Bob's key binding nonce from the master secret.
*/
String BOB_NONCE_LABEL = "org.briarproject.bramble.contact/BOB_NONCE";
/** /**
* Exchanges contact information with a remote peer. * Exchanges contact information with a remote peer.
*/ */

View File

@@ -1,11 +1,13 @@
package org.briarproject.bramble.api.crypto; package org.briarproject.bramble.api.crypto;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.transport.TransportKeys;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.SecureRandom; import java.security.SecureRandom;
import javax.annotation.Nullable;
@NotNullByDefault
public interface CryptoComponent { public interface CryptoComponent {
SecretKey generateSecretKey(); SecretKey generateSecretKey();
@@ -23,127 +25,46 @@ public interface CryptoComponent {
KeyParser getMessageKeyParser(); KeyParser getMessageKeyParser();
/** /**
* Derives a stream header key from the given master secret. * Derives another secret key from the given secret key.
* @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.
* *
* @param publicKey the public key * @param label a namespaced label indicating the purpose of the derived
* @return the commitment to the provided public key. * 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 * Derives a common shared secret from two public keys and one of the
* corresponding private keys. * corresponding private keys.
* <p/>
* Part of BQP.
* *
* @param theirPublicKey the ephemeral public key of the remote party * @param label a namespaced label indicating the purpose of this shared
* @param ourKeyPair our ephemeral keypair * secret, to prevent it from being repurposed or colliding with a shared
* @param alice true if ourKeyPair belongs to Alice * 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 * @return the shared secret
* @throws GeneralSecurityException
*/ */
SecretKey deriveSharedSecret(byte[] theirPublicKey, KeyPair ourKeyPair, SecretKey deriveSharedSecret(String label, PublicKey theirPublicKey,
boolean alice) throws GeneralSecurityException; KeyPair ourKeyPair, byte[]... inputs)
throws GeneralSecurityException;
/** /**
* Derives the content of a confirmation record. * Signs the given byte[] with the given private key.
* <p/>
* Part of BQP.
* *
* @param sharedSecret the common shared secret * @param label a namespaced label indicating the purpose of this
* @param theirPayload the commit payload from the remote party * signature, to prevent it from being repurposed or colliding with a
* @param ourPayload the commit payload we sent * signature created for another purpose
* @param theirPublicKey the ephemeral public key of the remote party
* @param ourKeyPair our ephemeral keypair
* @param alice true if ourKeyPair belongs to Alice
* @param aliceRecord true if the confirmation record is for use by Alice
* @return the confirmation record
*/
byte[] deriveConfirmationRecord(SecretKey sharedSecret,
byte[] theirPayload, byte[] ourPayload,
byte[] theirPublicKey, KeyPair ourKeyPair,
boolean alice, boolean aliceRecord);
/**
* Derives a master secret from the given shared secret.
* <p/>
* Part of BQP.
*
* @param sharedSecret the common shared secret
* @return the master secret
*/
SecretKey deriveMasterSecret(SecretKey sharedSecret);
/**
* Derives a master secret from two public keys and one of the corresponding
* private keys.
* <p/>
* This is a helper method that calls
* deriveMasterSecret(deriveSharedSecret(theirPublicKey, ourKeyPair, alice))
*
* @param theirPublicKey the ephemeral public key of the remote party
* @param ourKeyPair our ephemeral keypair
* @param alice true if ourKeyPair belongs to Alice
* @return the shared secret
* @throws GeneralSecurityException
*/
SecretKey deriveMasterSecret(byte[] theirPublicKey, KeyPair ourKeyPair,
boolean alice) throws GeneralSecurityException;
/**
* Derives initial transport keys for the given transport in the given
* rotation period from the given master secret.
* @param alice whether the keys are for use by Alice or Bob.
*/
TransportKeys deriveTransportKeys(TransportId t, SecretKey master,
long rotationPeriod, boolean alice);
/**
* Rotates the given transport keys to the given rotation period. If the
* keys are for a future rotation period they are not rotated.
*/
TransportKeys rotateTransportKeys(TransportKeys k, long rotationPeriod);
/** Encodes the pseudo-random tag that is used to recognise a stream. */
void encodeTag(byte[] tag, SecretKey tagKey, int protocolVersion,
long streamNumber);
/**
* Signs the given byte[] with the given PrivateKey.
*
* @param label A label specific to this signature
* to ensure that the signature cannot be repurposed
*/ */
byte[] sign(String label, byte[] toSign, byte[] privateKey) byte[] sign(String label, byte[] toSign, byte[] privateKey)
throws GeneralSecurityException; throws GeneralSecurityException;
/** /**
* Verifies that the given signature is valid for the signedData * Verifies that the given signature is valid for the signed data
* and the given publicKey. * and the given public key.
* *
* @param label A label that was specific to this signature * @param label a namespaced label indicating the purpose of this
* to ensure that the signature cannot be repurposed * signature, to prevent it from being repurposed or colliding with a
* signature created for another purpose
* @return true if the signature was valid, false otherwise. * @return true if the signature was valid, false otherwise.
*/ */
boolean verify(String label, byte[] signedData, byte[] publicKey, boolean verify(String label, byte[] signedData, byte[] publicKey,
@@ -153,23 +74,22 @@ public interface CryptoComponent {
* Returns the hash of the given inputs. The inputs are unambiguously * Returns the hash of the given inputs. The inputs are unambiguously
* combined by prefixing each input with its length. * combined by prefixing each input with its length.
* *
* @param label A label specific to this hash to ensure that hashes * @param label a namespaced label indicating the purpose of this hash, to
* calculated for distinct purposes don't collide. * prevent it from being repurposed or colliding with a hash created for
* another purpose
*/ */
byte[] hash(String label, byte[]... inputs); byte[] hash(String label, byte[]... inputs);
/**
* Returns the length of hashes produced by
* the {@link CryptoComponent#hash(String, byte[]...)} method.
*/
int getHashLength();
/** /**
* Returns a message authentication code with the given key over the * Returns a message authentication code with the given key over the
* given inputs. The inputs are unambiguously combined by prefixing each * given inputs. The inputs are unambiguously combined by prefixing each
* input with its length. * input with its length.
*
* @param label a namespaced label indicating the purpose of this MAC, to
* prevent it from being repurposed or colliding with a MAC created for
* another purpose
*/ */
byte[] mac(SecretKey macKey, byte[]... inputs); byte[] mac(String label, SecretKey macKey, byte[]... inputs);
/** /**
* Encrypts and authenticates the given plaintext so it can be written to * Encrypts and authenticates the given plaintext so it can be written to
@@ -185,6 +105,7 @@ public interface CryptoComponent {
* given password. Returns null if the ciphertext cannot be decrypted and * given password. Returns null if the ciphertext cannot be decrypted and
* authenticated (for example, if the password is wrong). * authenticated (for example, if the password is wrong).
*/ */
@Nullable
byte[] decryptWithPassword(byte[] ciphertext, String password); 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 * @throws DataTooOldException if the data uses an older schema than the
* current code and cannot be migrated * current code and cannot be migrated
*/ */
boolean open() throws DbException; boolean open(@Nullable MigrationListener listener) throws DbException;
/** /**
* Waits for any open transactions to finish and closes the database. * Waits for any open transactions to finish and closes the database.
@@ -259,31 +259,30 @@ public interface DatabaseComponent {
Collection<LocalAuthor> getLocalAuthors(Transaction txn) throws DbException; Collection<LocalAuthor> getLocalAuthors(Transaction txn) throws DbException;
/** /**
* Returns the IDs of any messages that need to be validated by the given * Returns the IDs of any messages that need to be validated.
* client.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getMessagesToValidate(Transaction txn, ClientId c) Collection<MessageId> getMessagesToValidate(Transaction txn)
throws DbException; throws DbException;
/** /**
* Returns the IDs of any messages that are valid but pending delivery due * Returns the IDs of any messages that are pending delivery due to
* to dependencies on other messages for the given client. * dependencies on other messages.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getPendingMessages(Transaction txn, ClientId c) Collection<MessageId> getPendingMessages(Transaction txn)
throws DbException; throws DbException;
/** /**
* Returns the IDs of any messages from the given client * Returns the IDs of any messages that have shared dependents but have
* that have a shared dependent, but are still not shared themselves. * not yet been shared themselves.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getMessagesToShare(Transaction txn, Collection<MessageId> getMessagesToShare(Transaction txn)
ClientId c) throws DbException; throws DbException;
/** /**
* Returns the message with the given ID, in serialised form, or null if * Returns the message with the given ID, in serialised form, or null if
@@ -378,6 +377,16 @@ public interface DatabaseComponent {
MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m) MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
throws DbException; throws DbException;
/*
* Returns the next time (in milliseconds since the Unix epoch) when a
* message is due to be sent to the given contact. The returned value may
* be zero if a message is due to be sent immediately, or Long.MAX_VALUE if
* no messages are scheduled to be sent.
* <p/>
* Read-only.
*/
long getNextSendTime(Transaction txn, ContactId c) throws DbException;
/** /**
* Returns all settings in the given namespace. * Returns all settings in the given namespace.
* <p/> * <p/>

View File

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

View File

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

View File

@@ -5,8 +5,27 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault @NotNullByDefault
public interface AuthorFactory { public interface AuthorFactory {
/**
* Creates an author with the current format version and the given name and
* public key.
*/
Author createAuthor(String name, byte[] publicKey); 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, LocalAuthor createLocalAuthor(String name, byte[] publicKey,
byte[] privateKey); 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. * Label for hashing authors to calculate their identities.
*/ */
public static final String LABEL = "org.briarproject.bramble.AUTHOR_ID"; public static final String LABEL = "org.briarproject.bramble/AUTHOR_ID";
public AuthorId(byte[] id) { public AuthorId(byte[] id) {
super(id); super(id);

View File

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

View File

@@ -3,9 +3,9 @@ package org.briarproject.bramble.api.keyagreement;
public interface KeyAgreementConstants { 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. * The length of the record header in bytes.
@@ -22,7 +22,10 @@ public interface KeyAgreementConstants {
*/ */
int COMMIT_LENGTH = 16; int COMMIT_LENGTH = 16;
long CONNECTION_TIMEOUT = 20 * 1000; // Milliseconds /**
* The connection timeout in milliseconds.
*/
long CONNECTION_TIMEOUT = 20 * 1000;
/** /**
* The transport identifier for Bluetooth. * The transport identifier for Bluetooth.
@@ -33,4 +36,16 @@ public interface KeyAgreementConstants {
* The transport identifier for LAN. * The transport identifier for LAN.
*/ */
int TRANSPORT_ID_LAN = 1; int TRANSPORT_ID_LAN = 1;
/**
* Label for deriving the shared secret.
*/
String SHARED_SECRET_LABEL =
"org.briarproject.bramble.keyagreement/SHARED_SECRET";
/**
* Label for deriving the master secret.
*/
String MASTER_SECRET_LABEL =
"org.briarproject.bramble.keyagreement/MASTER_SECRET";
} }

View File

@@ -2,7 +2,7 @@ package org.briarproject.bramble.api.keyagreement;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import java.util.concurrent.Callable; import java.io.IOException;
/** /**
* An class for managing a particular key agreement listener. * An class for managing a particular key agreement listener.
@@ -24,11 +24,11 @@ public abstract class KeyAgreementListener {
} }
/** /**
* Starts listening for incoming connections, and returns a Callable that * Blocks until an incoming connection is received and returns it.
* will return a KeyAgreementConnection when an incoming connection is *
* received. * @throws IOException if an error occurs or {@link #close()} is called.
*/ */
public abstract Callable<KeyAgreementConnection> listen(); public abstract KeyAgreementConnection accept() throws IOException;
/** /**
* Closes the underlying server socket. * Closes the underlying server socket.

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,7 +21,25 @@ public interface LifecycleManager {
* The result of calling {@link #startServices(String)}. * The result of calling {@link #startServices(String)}.
*/ */
enum StartResult { enum StartResult {
ALREADY_RUNNING, DB_ERROR, SERVICE_ERROR, SUCCESS 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();
}
} }
/** /**
@@ -71,4 +89,10 @@ public interface LifecycleManager {
* the {@link DatabaseComponent} to be closed before returning. * the {@link DatabaseComponent} to be closed before returning.
*/ */
void waitForShutdown() throws InterruptedException; void waitForShutdown() throws InterruptedException;
/**
* Returns the current state of the lifecycle.
*/
LifecycleState getLifecycleState();
} }

View File

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

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

@@ -36,9 +36,9 @@ public interface DuplexPlugin extends Plugin {
/** /**
* Attempts to connect to the remote peer specified in the given descriptor. * Attempts to connect to the remote peer specified in the given descriptor.
* Returns null if no connection can be established within the given time. * Returns null if no connection can be established.
*/ */
@Nullable @Nullable
DuplexTransportConnection createKeyAgreementConnection( DuplexTransportConnection createKeyAgreementConnection(
byte[] remoteCommitment, BdfList descriptor, long timeout); byte[] remoteCommitment, BdfList descriptor);
} }

View File

@@ -0,0 +1,15 @@
package org.briarproject.bramble.api.plugin.event;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* An event that informs the Bluetooth plugin that we have enabled the
* Bluetooth adapter.
*/
@Immutable
@NotNullByDefault
public class BluetoothEnabledEvent extends Event {
}

View File

@@ -6,7 +6,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
/** /**
* An event asks the Bluetooth plugin to enable the Bluetooth adapter. * An event that asks the Bluetooth plugin to enable the Bluetooth adapter.
*/ */
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault

View File

@@ -17,6 +17,11 @@ public interface TransportPropertyManager {
*/ */
ClientId CLIENT_ID = new ClientId("org.briarproject.briar.properties"); ClientId CLIENT_ID = new ClientId("org.briarproject.briar.properties");
/**
* The current version of the transport property client.
*/
int CLIENT_VERSION = 0;
/** /**
* Stores the given properties received while adding a contact - they will * Stores the given properties received while adding a contact - they will
* be superseded by any properties synced from the contact. * be superseded by any properties synced from the contact.

View File

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

View File

@@ -6,7 +6,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
public interface GroupFactory { public interface GroupFactory {
/** /**
* Creates a group with the given client ID 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. * Label for hashing groups to calculate their identifiers.
*/ */
public static final String LABEL = "org.briarproject.bramble.GROUP_ID"; public static final String LABEL = "org.briarproject.bramble/GROUP_ID";
public GroupId(byte[] id) { public GroupId(byte[] id) {
super(id); super(id);

View File

@@ -16,7 +16,7 @@ public class MessageId extends UniqueId {
/** /**
* Label for hashing messages to calculate their identifiers. * Label for hashing messages to calculate their identifiers.
*/ */
public static final String LABEL = "org.briarproject.bramble.MESSAGE_ID"; public static final String LABEL = "org.briarproject.bramble/MESSAGE_ID";
public MessageId(byte[] id) { public MessageId(byte[] id) {
super(id); super(id);

View File

@@ -7,7 +7,7 @@ public interface TransportConstants {
/** /**
* The current version of the transport protocol. * The current version of the transport protocol.
*/ */
int PROTOCOL_VERSION = 3; int PROTOCOL_VERSION = 4;
/** /**
* The length of the pseudo-random tag in bytes. * The length of the pseudo-random tag in bytes.
@@ -80,4 +80,32 @@ public interface TransportConstants {
* The size of the reordering window. * The size of the reordering window.
*/ */
int REORDERING_WINDOW_SIZE = 32; int REORDERING_WINDOW_SIZE = 32;
/**
* Label for deriving Alice's initial tag key from the master secret.
*/
String ALICE_TAG_LABEL = "org.briarproject.bramble.transport/ALICE_TAG_KEY";
/**
* Label for deriving Bob's initial tag key from the master secret.
*/
String BOB_TAG_LABEL = "org.briarproject.bramble.transport/BOB_TAG_KEY";
/**
* Label for deriving Alice's initial header key from the master secret.
*/
String ALICE_HEADER_LABEL =
"org.briarproject.bramble.transport/ALICE_HEADER_KEY";
/**
* Label for deriving Bob's initial header key from the master secret.
*/
String BOB_HEADER_LABEL =
"org.briarproject.bramble.transport/BOB_HEADER_KEY";
/**
* Label for deriving the next period's key in key rotation.
*/
String ROTATE_LABEL = "org.briarproject.bramble.transport/ROTATE";
} }

View File

@@ -126,6 +126,10 @@ public class StringUtils {
return toUtf8(s).length > maxLength; return toUtf8(s).length > maxLength;
} }
public static boolean isValidMac(String mac) {
return MAC.matcher(mac).matches();
}
public static byte[] macToBytes(String mac) { public static byte[] macToBytes(String mac) {
if (!MAC.matcher(mac).matches()) throw new IllegalArgumentException(); if (!MAC.matcher(mac).matches()) throw new IllegalArgumentException();
return fromHexString(mac.replaceAll(":", "")); return fromHexString(mac.replaceAll(":", ""));
@@ -142,6 +146,14 @@ public class StringUtils {
return s.toString(); return s.toString();
} }
public static String ipToString(int ip) {
int ip1 = ip & 0xFF;
int ip2 = (ip >> 8) & 0xFF;
int ip3 = (ip >> 16) & 0xFF;
int ip4 = (ip >> 24) & 0xFF;
return ip1 + "." + ip2 + "." + ip3 + "." + ip4;
}
public static String getRandomString(int length) { public static String getRandomString(int length) {
char[] c = new char[length]; char[] c = new char[length];
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)

View File

@@ -13,9 +13,14 @@ import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.util.IoUtils; import org.briarproject.bramble.util.IoUtils;
import java.io.File; import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Random; import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_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[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
byte[] privateKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH); byte[] privateKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
long created = System.currentTimeMillis(); 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() { public static Author getAuthor() {
@@ -74,7 +80,7 @@ public class TestUtils {
AuthorId id = new AuthorId(getRandomId()); AuthorId id = new AuthorId(getRandomId());
String name = getRandomString(nameLength); String name = getRandomString(nameLength);
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH); 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) { public static Group getGroup(ClientId clientId) {
@@ -99,4 +105,38 @@ public class TestUtils {
long timestamp = System.currentTimeMillis(); long timestamp = System.currentTimeMillis();
return new Message(id, groupId, timestamp, raw); 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 { dependencies {
implementation project(path: ':bramble-api', configuration: 'default') implementation project(path: ':bramble-api', configuration: 'default')
implementation 'com.madgag.spongycastle:core:1.58.0.0' implementation 'com.madgag.spongycastle:core:1.58.0.0'
implementation 'com.h2database:h2:1.4.192' // 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 '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' apt 'com.google.dagger:dagger-compiler:2.0.2'
testImplementation project(path: ':bramble-api', configuration: 'testOutput') testImplementation project(path: ':bramble-api', configuration: 'testOutput')
testImplementation 'org.hsqldb:hsqldb:2.3.5' // The last version that supports Java 1.6
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
testImplementation "org.jmock:jmock:2.8.2" testImplementation "org.jmock:jmock:2.8.2"
testImplementation "org.jmock:jmock-junit4:2.8.2" testImplementation "org.jmock:jmock-junit4:2.8.2"
@@ -37,18 +40,21 @@ dependencyVerification {
'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728', 'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a', 'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
'net.i2p.crypto:eddsa:0.2.0:eddsa-0.2.0.jar:a7cb1b85c16e2f0730b9204106929a1d9aaae1df728adc7041a8b8b605692140',
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d', 'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8', 'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8', 'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb', 'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9', 'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c', 'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c',
'org.hsqldb:hsqldb:2.3.5:hsqldb-2.3.5.jar:6676a6977ac98997a80f827ddbd3fe8ca1e0853dad1492512135fd1a222ccfad',
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c', 'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c',
'org.jmock:jmock-legacy:2.8.2:jmock-legacy-2.8.2.jar:f2b985a5c08a9edb7f37612330c058809da3f6a6d63ce792426ebf8ff0d6d31b', 'org.jmock:jmock-legacy:2.8.2:jmock-legacy-2.8.2.jar:f2b985a5c08a9edb7f37612330c058809da3f6a6d63ce792426ebf8ff0d6d31b',
'org.jmock:jmock-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760', 'org.jmock:jmock-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760',
'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16', 'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16',
'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80', 'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80',
'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220', 'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220',
'org.whispersystems:curve25519-java:0.4.1:curve25519-java-0.4.1.jar:7dd659d8822c06c3aea1a47f18fac9e5761e29cab8100030b877db445005f03e',
] ]
} }

View File

@@ -15,6 +15,8 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata; import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorFactory;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
@@ -32,7 +34,12 @@ import java.util.Map.Entry;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; 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.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
@@ -51,12 +58,14 @@ class ClientHelperImpl implements ClientHelper {
private final MetadataParser metadataParser; private final MetadataParser metadataParser;
private final MetadataEncoder metadataEncoder; private final MetadataEncoder metadataEncoder;
private final CryptoComponent crypto; private final CryptoComponent crypto;
private final AuthorFactory authorFactory;
@Inject @Inject
ClientHelperImpl(DatabaseComponent db, MessageFactory messageFactory, ClientHelperImpl(DatabaseComponent db, MessageFactory messageFactory,
BdfReaderFactory bdfReaderFactory, BdfReaderFactory bdfReaderFactory,
BdfWriterFactory bdfWriterFactory, MetadataParser metadataParser, BdfWriterFactory bdfWriterFactory, MetadataParser metadataParser,
MetadataEncoder metadataEncoder, CryptoComponent crypto) { MetadataEncoder metadataEncoder, CryptoComponent crypto,
AuthorFactory authorFactory) {
this.db = db; this.db = db;
this.messageFactory = messageFactory; this.messageFactory = messageFactory;
this.bdfReaderFactory = bdfReaderFactory; this.bdfReaderFactory = bdfReaderFactory;
@@ -64,6 +73,7 @@ class ClientHelperImpl implements ClientHelper {
this.metadataParser = metadataParser; this.metadataParser = metadataParser;
this.metadataEncoder = metadataEncoder; this.metadataEncoder = metadataEncoder;
this.crypto = crypto; this.crypto = crypto;
this.authorFactory = authorFactory;
} }
@Override @Override
@@ -341,6 +351,11 @@ class ClientHelperImpl implements ClientHelper {
raw.length - MESSAGE_HEADER_LENGTH); raw.length - MESSAGE_HEADER_LENGTH);
} }
@Override
public BdfList toList(Author a) {
return BdfList.of(a.getFormatVersion(), a.getName(), a.getPublicKey());
}
@Override @Override
public byte[] sign(String label, BdfList toSign, byte[] privateKey) public byte[] sign(String label, BdfList toSign, byte[] privateKey)
throws FormatException, GeneralSecurityException { 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.ClientHelper;
import org.briarproject.bramble.api.client.ContactGroupFactory; 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.Module;
import dagger.Provides; import dagger.Provides;
@@ -18,19 +10,14 @@ import dagger.Provides;
public class ClientModule { public class ClientModule {
@Provides @Provides
ClientHelper provideClientHelper(DatabaseComponent db, ClientHelper provideClientHelper(ClientHelperImpl clientHelper) {
MessageFactory messageFactory, BdfReaderFactory bdfReaderFactory, return clientHelper;
BdfWriterFactory bdfWriterFactory, MetadataParser metadataParser,
MetadataEncoder metadataEncoder, CryptoComponent cryptoComponent) {
return new ClientHelperImpl(db, messageFactory, bdfReaderFactory,
bdfWriterFactory, metadataParser, metadataEncoder,
cryptoComponent);
} }
@Provides @Provides
ContactGroupFactory provideContactGroupFactory(GroupFactory groupFactory, ContactGroupFactory provideContactGroupFactory(
ClientHelper clientHelper) { ContactGroupFactoryImpl contactGroupFactory) {
return new ContactGroupFactoryImpl(groupFactory, clientHelper); return contactGroupFactory;
} }
} }

View File

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

View File

@@ -1,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; package org.briarproject.bramble.crypto;
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
import net.i2p.crypto.eddsa.EdDSAPublicKey;
import net.i2p.crypto.eddsa.KeyPairGenerator;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.KeyParser; import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.PrivateKey; import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.SecureRandomProvider; import org.briarproject.bramble.api.system.SecureRandomProvider;
import org.briarproject.bramble.api.transport.IncomingKeys;
import org.briarproject.bramble.api.transport.OutgoingKeys;
import org.briarproject.bramble.api.transport.TransportKeys;
import org.briarproject.bramble.util.ByteUtils; import org.briarproject.bramble.util.ByteUtils;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
import org.spongycastle.crypto.CipherParameters;
import org.spongycastle.crypto.CryptoException; import org.spongycastle.crypto.CryptoException;
import org.spongycastle.crypto.Digest; import org.spongycastle.crypto.Digest;
import org.spongycastle.crypto.agreement.ECDHCBasicAgreement; import org.spongycastle.crypto.digests.Blake2bDigest;
import org.spongycastle.crypto.digests.SHA256Digest; import org.whispersystems.curve25519.Curve25519;
import org.spongycastle.crypto.generators.ECKeyPairGenerator; import org.whispersystems.curve25519.Curve25519KeyPair;
import org.spongycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.spongycastle.crypto.params.ECKeyGenerationParameters;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.crypto.params.KeyParameter;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.Provider; import java.security.Provider;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.Security; import java.security.Security;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS;
import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES; import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
@NotNullByDefault
class CryptoComponentImpl implements CryptoComponent { class CryptoComponentImpl implements CryptoComponent {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(CryptoComponentImpl.class.getName()); 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 SIGNATURE_KEY_PAIR_BITS = 256;
private static final int STORAGE_IV_BYTES = 24; // 196 bits private static final int STORAGE_IV_BYTES = 24; // 196 bits
private static final int PBKDF_SALT_BYTES = 32; // 256 bits private static final int PBKDF_SALT_BYTES = 32; // 256 bits
private static final int PBKDF_TARGET_MILLIS = 500; private static final int PBKDF_FORMAT_SCRYPT = 0;
private static final int PBKDF_SAMPLES = 30;
private static final int HASH_SIZE = 256 / 8;
private static byte[] ascii(String s) {
return s.getBytes(Charset.forName("US-ASCII"));
}
// KDF labels for contact exchange stream header key derivation
private static final byte[] A_INVITE = ascii("ALICE_INVITATION_KEY");
private static final byte[] B_INVITE = ascii("BOB_INVITATION_KEY");
// KDF labels for contact exchange signature nonce derivation
private static final byte[] A_SIG_NONCE = ascii("ALICE_SIGNATURE_NONCE");
private static final byte[] B_SIG_NONCE = ascii("BOB_SIGNATURE_NONCE");
// Hash label for BQP public key commitment derivation
private static final String COMMIT =
"org.briarproject.bramble.COMMIT";
// Hash label for shared secret derivation
private static final String SHARED_SECRET =
"org.briarproject.bramble.SHARED_SECRET";
// KDF label for BQP confirmation key derivation
private static final byte[] CONFIRMATION_KEY = ascii("CONFIRMATION_KEY");
// KDF label for master key derivation
private static final byte[] MASTER_KEY = ascii("MASTER_KEY");
// KDF labels for tag key derivation
private static final byte[] A_TAG = ascii("ALICE_TAG_KEY");
private static final byte[] B_TAG = ascii("BOB_TAG_KEY");
// KDF labels for header key derivation
private static final byte[] A_HEADER = ascii("ALICE_HEADER_KEY");
private static final byte[] B_HEADER = ascii("BOB_HEADER_KEY");
// KDF labels for MAC key derivation
private static final byte[] A_MAC = ascii("ALICE_MAC_KEY");
private static final byte[] B_MAC = ascii("BOB_MAC_KEY");
// KDF label for key rotation
private static final byte[] ROTATE = ascii("ROTATE");
private final SecureRandom secureRandom; private final SecureRandom secureRandom;
private final ECKeyPairGenerator agreementKeyPairGenerator; private final PasswordBasedKdf passwordBasedKdf;
private final ECKeyPairGenerator signatureKeyPairGenerator; private final Curve25519 curve25519;
private final KeyPairGenerator signatureKeyPairGenerator;
private final KeyParser agreementKeyParser, signatureKeyParser; private final KeyParser agreementKeyParser, signatureKeyParser;
private final MessageEncrypter messageEncrypter; private final MessageEncrypter messageEncrypter;
@Inject @Inject
CryptoComponentImpl(SecureRandomProvider secureRandomProvider) { CryptoComponentImpl(SecureRandomProvider secureRandomProvider,
PasswordBasedKdf passwordBasedKdf) {
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
SecureRandom defaultSecureRandom = new SecureRandom(); SecureRandom defaultSecureRandom = new SecureRandom();
String name = defaultSecureRandom.getProvider().getName(); String name = defaultSecureRandom.getProvider().getName();
@@ -121,16 +73,13 @@ class CryptoComponentImpl implements CryptoComponent {
} }
} }
secureRandom = new SecureRandom(); secureRandom = new SecureRandom();
ECKeyGenerationParameters params = new ECKeyGenerationParameters( this.passwordBasedKdf = passwordBasedKdf;
PARAMETERS, secureRandom); curve25519 = Curve25519.getInstance("java");
agreementKeyPairGenerator = new ECKeyPairGenerator(); signatureKeyPairGenerator = new KeyPairGenerator();
agreementKeyPairGenerator.init(params); signatureKeyPairGenerator.initialize(SIGNATURE_KEY_PAIR_BITS,
signatureKeyPairGenerator = new ECKeyPairGenerator(); secureRandom);
signatureKeyPairGenerator.init(params); agreementKeyParser = new Curve25519KeyParser();
agreementKeyParser = new Sec1KeyParser(PARAMETERS, signatureKeyParser = new EdKeyParser();
AGREEMENT_KEY_PAIR_BITS);
signatureKeyParser = new Sec1KeyParser(PARAMETERS,
SIGNATURE_KEY_PAIR_BITS);
messageEncrypter = new MessageEncrypter(secureRandom); messageEncrypter = new MessageEncrypter(secureRandom);
} }
@@ -174,16 +123,17 @@ class CryptoComponentImpl implements CryptoComponent {
// Package access for testing // Package access for testing
byte[] performRawKeyAgreement(PrivateKey priv, PublicKey pub) byte[] performRawKeyAgreement(PrivateKey priv, PublicKey pub)
throws GeneralSecurityException { throws GeneralSecurityException {
if (!(priv instanceof Sec1PrivateKey)) if (!(priv instanceof Curve25519PrivateKey))
throw new IllegalArgumentException(); throw new IllegalArgumentException();
if (!(pub instanceof Sec1PublicKey)) if (!(pub instanceof Curve25519PublicKey))
throw new IllegalArgumentException(); throw new IllegalArgumentException();
ECPrivateKeyParameters ecPriv = ((Sec1PrivateKey) priv).getKey();
ECPublicKeyParameters ecPub = ((Sec1PublicKey) pub).getKey();
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
ECDHCBasicAgreement agreement = new ECDHCBasicAgreement(); byte[] secret = curve25519.calculateAgreement(pub.getEncoded(),
agreement.init(ecPriv); priv.getEncoded());
byte[] secret = agreement.calculateAgreement(ecPub).toByteArray(); // 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; long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Deriving shared secret took " + duration + " ms"); LOG.info("Deriving shared secret took " + duration + " ms");
@@ -192,18 +142,10 @@ class CryptoComponentImpl implements CryptoComponent {
@Override @Override
public KeyPair generateAgreementKeyPair() { public KeyPair generateAgreementKeyPair() {
AsymmetricCipherKeyPair keyPair = Curve25519KeyPair keyPair = curve25519.generateKeyPair();
agreementKeyPairGenerator.generateKeyPair(); PublicKey pub = new Curve25519PublicKey(keyPair.getPublicKey());
// Return a wrapper that uses the SEC 1 encoding PrivateKey priv = new Curve25519PrivateKey(keyPair.getPrivateKey());
ECPublicKeyParameters ecPublicKey = return new KeyPair(pub, priv);
(ECPublicKeyParameters) keyPair.getPublic();
PublicKey publicKey = new Sec1PublicKey(ecPublicKey
);
ECPrivateKeyParameters ecPrivateKey =
(ECPrivateKeyParameters) keyPair.getPrivate();
PrivateKey privateKey = new Sec1PrivateKey(ecPrivateKey,
AGREEMENT_KEY_PAIR_BITS);
return new KeyPair(publicKey, privateKey);
} }
@Override @Override
@@ -213,17 +155,12 @@ class CryptoComponentImpl implements CryptoComponent {
@Override @Override
public KeyPair generateSignatureKeyPair() { public KeyPair generateSignatureKeyPair() {
AsymmetricCipherKeyPair keyPair = java.security.KeyPair keyPair =
signatureKeyPairGenerator.generateKeyPair(); signatureKeyPairGenerator.generateKeyPair();
// Return a wrapper that uses the SEC 1 encoding EdDSAPublicKey edPublicKey = (EdDSAPublicKey) keyPair.getPublic();
ECPublicKeyParameters ecPublicKey = PublicKey publicKey = new EdPublicKey(edPublicKey.getAbyte());
(ECPublicKeyParameters) keyPair.getPublic(); EdDSAPrivateKey edPrivateKey = (EdDSAPrivateKey) keyPair.getPrivate();
PublicKey publicKey = new Sec1PublicKey(ecPublicKey PrivateKey privateKey = new EdPrivateKey(edPrivateKey.getSeed());
);
ECPrivateKeyParameters ecPrivateKey =
(ECPrivateKeyParameters) keyPair.getPrivate();
PrivateKey privateKey = new Sec1PrivateKey(ecPrivateKey,
SIGNATURE_KEY_PAIR_BITS);
return new KeyPair(publicKey, privateKey); return new KeyPair(publicKey, privateKey);
} }
@@ -238,205 +175,47 @@ class CryptoComponentImpl implements CryptoComponent {
} }
@Override @Override
public SecretKey deriveHeaderKey(SecretKey master, public SecretKey deriveKey(String label, SecretKey k, byte[]... inputs) {
boolean alice) { byte[] mac = mac(label, k, inputs);
return new SecretKey(macKdf(master, alice ? A_INVITE : B_INVITE)); if (mac.length != SecretKey.LENGTH) throw new IllegalStateException();
return new SecretKey(mac);
} }
@Override @Override
public SecretKey deriveMacKey(SecretKey master, boolean alice) { public SecretKey deriveSharedSecret(String label, PublicKey theirPublicKey,
return new SecretKey(macKdf(master, alice ? A_MAC : B_MAC)); KeyPair ourKeyPair, byte[]... inputs)
} throws GeneralSecurityException {
@Override
public byte[] deriveSignatureNonce(SecretKey master,
boolean alice) {
return macKdf(master, alice ? A_SIG_NONCE : B_SIG_NONCE);
}
@Override
public byte[] deriveKeyCommitment(byte[] publicKey) {
byte[] hash = hash(COMMIT, publicKey);
// The output is the first COMMIT_LENGTH bytes of the hash
byte[] commitment = new byte[COMMIT_LENGTH];
System.arraycopy(hash, 0, commitment, 0, COMMIT_LENGTH);
return commitment;
}
@Override
public SecretKey deriveSharedSecret(byte[] theirPublicKey,
KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException {
PrivateKey ourPriv = ourKeyPair.getPrivate(); PrivateKey ourPriv = ourKeyPair.getPrivate();
PublicKey theirPub = agreementKeyParser.parsePublicKey(theirPublicKey); byte[][] hashInputs = new byte[inputs.length + 1][];
byte[] raw = performRawKeyAgreement(ourPriv, theirPub); hashInputs[0] = performRawKeyAgreement(ourPriv, theirPublicKey);
byte[] alicePub, bobPub; System.arraycopy(inputs, 0, hashInputs, 1, inputs.length);
if (alice) { byte[] hash = hash(label, hashInputs);
alicePub = ourKeyPair.getPublic().getEncoded(); if (hash.length != SecretKey.LENGTH) throw new IllegalStateException();
bobPub = theirPublicKey; return new SecretKey(hash);
} else {
alicePub = theirPublicKey;
bobPub = ourKeyPair.getPublic().getEncoded();
}
return new SecretKey(hash(SHARED_SECRET, raw, alicePub, bobPub));
}
@Override
public byte[] deriveConfirmationRecord(SecretKey sharedSecret,
byte[] theirPayload, byte[] ourPayload, byte[] theirPublicKey,
KeyPair ourKeyPair, boolean alice, boolean aliceRecord) {
SecretKey ck = new SecretKey(macKdf(sharedSecret, CONFIRMATION_KEY));
byte[] alicePayload, alicePub, bobPayload, bobPub;
if (alice) {
alicePayload = ourPayload;
alicePub = ourKeyPair.getPublic().getEncoded();
bobPayload = theirPayload;
bobPub = theirPublicKey;
} else {
alicePayload = theirPayload;
alicePub = theirPublicKey;
bobPayload = ourPayload;
bobPub = ourKeyPair.getPublic().getEncoded();
}
if (aliceRecord)
return macKdf(ck, alicePayload, alicePub, bobPayload, bobPub);
else
return macKdf(ck, bobPayload, bobPub, alicePayload, alicePub);
}
@Override
public SecretKey deriveMasterSecret(SecretKey sharedSecret) {
return new SecretKey(macKdf(sharedSecret, MASTER_KEY));
}
@Override
public SecretKey deriveMasterSecret(byte[] theirPublicKey,
KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException {
return deriveMasterSecret(deriveSharedSecret(
theirPublicKey, ourKeyPair, alice));
}
@Override
public TransportKeys deriveTransportKeys(TransportId t,
SecretKey master, long rotationPeriod, boolean alice) {
// Keys for the previous period are derived from the master secret
SecretKey inTagPrev = deriveTagKey(master, t, !alice);
SecretKey inHeaderPrev = deriveHeaderKey(master, t, !alice);
SecretKey outTagPrev = deriveTagKey(master, t, alice);
SecretKey outHeaderPrev = deriveHeaderKey(master, t, alice);
// Derive the keys for the current and next periods
SecretKey inTagCurr = rotateKey(inTagPrev, rotationPeriod);
SecretKey inHeaderCurr = rotateKey(inHeaderPrev, rotationPeriod);
SecretKey inTagNext = rotateKey(inTagCurr, rotationPeriod + 1);
SecretKey inHeaderNext = rotateKey(inHeaderCurr, rotationPeriod + 1);
SecretKey outTagCurr = rotateKey(outTagPrev, rotationPeriod);
SecretKey outHeaderCurr = rotateKey(outHeaderPrev, rotationPeriod);
// Initialise the reordering windows and stream counters
IncomingKeys inPrev = new IncomingKeys(inTagPrev, inHeaderPrev,
rotationPeriod - 1);
IncomingKeys inCurr = new IncomingKeys(inTagCurr, inHeaderCurr,
rotationPeriod);
IncomingKeys inNext = new IncomingKeys(inTagNext, inHeaderNext,
rotationPeriod + 1);
OutgoingKeys outCurr = new OutgoingKeys(outTagCurr, outHeaderCurr,
rotationPeriod);
// Collect and return the keys
return new TransportKeys(t, inPrev, inCurr, inNext, outCurr);
}
@Override
public TransportKeys rotateTransportKeys(TransportKeys k,
long rotationPeriod) {
if (k.getRotationPeriod() >= rotationPeriod) return k;
IncomingKeys inPrev = k.getPreviousIncomingKeys();
IncomingKeys inCurr = k.getCurrentIncomingKeys();
IncomingKeys inNext = k.getNextIncomingKeys();
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
long startPeriod = outCurr.getRotationPeriod();
// Rotate the keys
for (long p = startPeriod + 1; p <= rotationPeriod; p++) {
inPrev = inCurr;
inCurr = inNext;
SecretKey inNextTag = rotateKey(inNext.getTagKey(), p + 1);
SecretKey inNextHeader = rotateKey(inNext.getHeaderKey(), p + 1);
inNext = new IncomingKeys(inNextTag, inNextHeader, p + 1);
SecretKey outCurrTag = rotateKey(outCurr.getTagKey(), p);
SecretKey outCurrHeader = rotateKey(outCurr.getHeaderKey(), p);
outCurr = new OutgoingKeys(outCurrTag, outCurrHeader, p);
}
// Collect and return the keys
return new TransportKeys(k.getTransportId(), inPrev, inCurr, inNext,
outCurr);
}
private SecretKey rotateKey(SecretKey k, long rotationPeriod) {
byte[] period = new byte[INT_64_BYTES];
ByteUtils.writeUint64(rotationPeriod, period, 0);
return new SecretKey(macKdf(k, ROTATE, period));
}
private SecretKey deriveTagKey(SecretKey master, TransportId t,
boolean alice) {
byte[] id = StringUtils.toUtf8(t.getString());
return new SecretKey(macKdf(master, alice ? A_TAG : B_TAG, id));
}
private SecretKey deriveHeaderKey(SecretKey master, TransportId t,
boolean alice) {
byte[] id = StringUtils.toUtf8(t.getString());
return new SecretKey(macKdf(master, alice ? A_HEADER : B_HEADER, id));
}
@Override
public void encodeTag(byte[] tag, SecretKey tagKey, int protocolVersion,
long streamNumber) {
if (tag.length < TAG_LENGTH) throw new IllegalArgumentException();
if (protocolVersion < 0 || protocolVersion > MAX_16_BIT_UNSIGNED)
throw new IllegalArgumentException();
if (streamNumber < 0 || streamNumber > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
// Initialise the PRF
Digest prf = new Blake2sDigest(tagKey.getBytes());
// The output of the PRF must be long enough to use as a tag
int macLength = prf.getDigestSize();
if (macLength < TAG_LENGTH) throw new IllegalStateException();
// The input is the protocol version as a 16-bit integer, followed by
// the stream number as a 64-bit integer
byte[] protocolVersionBytes = new byte[INT_16_BYTES];
ByteUtils.writeUint16(protocolVersion, protocolVersionBytes, 0);
prf.update(protocolVersionBytes, 0, protocolVersionBytes.length);
byte[] streamNumberBytes = new byte[INT_64_BYTES];
ByteUtils.writeUint64(streamNumber, streamNumberBytes, 0);
prf.update(streamNumberBytes, 0, streamNumberBytes.length);
byte[] mac = new byte[macLength];
prf.doFinal(mac, 0);
// The output is the first TAG_LENGTH bytes of the MAC
System.arraycopy(mac, 0, tag, 0, TAG_LENGTH);
} }
@Override @Override
public byte[] sign(String label, byte[] toSign, byte[] privateKey) public byte[] sign(String label, byte[] toSign, byte[] privateKey)
throws GeneralSecurityException { throws GeneralSecurityException {
Signature signature = new SignatureImpl(secureRandom); PrivateKey key = signatureKeyParser.parsePrivateKey(privateKey);
KeyParser keyParser = getSignatureKeyParser(); Signature sig = new EdSignature();
PrivateKey key = keyParser.parsePrivateKey(privateKey); sig.initSign(key);
signature.initSign(key); updateSignature(sig, label, toSign);
updateSignature(signature, label, toSign); return sig.sign();
return signature.sign();
} }
@Override @Override
public boolean verify(String label, byte[] signedData, byte[] publicKey, public boolean verify(String label, byte[] signedData, byte[] publicKey,
byte[] signature) throws GeneralSecurityException { byte[] signature) throws GeneralSecurityException {
Signature sig = new SignatureImpl(secureRandom); PublicKey key = signatureKeyParser.parsePublicKey(publicKey);
KeyParser keyParser = getSignatureKeyParser(); Signature sig = new EdSignature();
PublicKey key = keyParser.parsePublicKey(publicKey);
sig.initVerify(key); sig.initVerify(key);
updateSignature(sig, label, signedData); updateSignature(sig, label, signedData);
return sig.verify(signature); return sig.verify(signature);
} }
private void updateSignature(Signature signature, String label, private void updateSignature(Signature signature, String label,
byte[] toSign) { byte[] toSign) throws GeneralSecurityException {
byte[] labelBytes = StringUtils.toUtf8(label); byte[] labelBytes = StringUtils.toUtf8(label);
byte[] length = new byte[INT_32_BYTES]; byte[] length = new byte[INT_32_BYTES];
ByteUtils.writeUint32(labelBytes.length, length, 0); ByteUtils.writeUint32(labelBytes.length, length, 0);
@@ -450,7 +229,7 @@ class CryptoComponentImpl implements CryptoComponent {
@Override @Override
public byte[] hash(String label, byte[]... inputs) { public byte[] hash(String label, byte[]... inputs) {
byte[] labelBytes = StringUtils.toUtf8(label); byte[] labelBytes = StringUtils.toUtf8(label);
Digest digest = new Blake2sDigest(); Digest digest = new Blake2bDigest(256);
byte[] length = new byte[INT_32_BYTES]; byte[] length = new byte[INT_32_BYTES];
ByteUtils.writeUint32(labelBytes.length, length, 0); ByteUtils.writeUint32(labelBytes.length, length, 0);
digest.update(length, 0, length.length); digest.update(length, 0, length.length);
@@ -466,14 +245,13 @@ class CryptoComponentImpl implements CryptoComponent {
} }
@Override @Override
public int getHashLength() { public byte[] mac(String label, SecretKey macKey, byte[]... inputs) {
return HASH_SIZE; byte[] labelBytes = StringUtils.toUtf8(label);
} Digest mac = new Blake2bDigest(macKey.getBytes(), 32, null, null);
@Override
public byte[] mac(SecretKey macKey, byte[]... inputs) {
Digest mac = new Blake2sDigest(macKey.getBytes());
byte[] length = new byte[INT_32_BYTES]; byte[] length = new byte[INT_32_BYTES];
ByteUtils.writeUint32(labelBytes.length, length, 0);
mac.update(length, 0, length.length);
mac.update(labelBytes, 0, labelBytes.length);
for (byte[] input : inputs) { for (byte[] input : inputs) {
ByteUtils.writeUint32(input.length, length, 0); ByteUtils.writeUint32(input.length, length, 0);
mac.update(length, 0, length.length); mac.update(length, 0, length.length);
@@ -492,23 +270,33 @@ class CryptoComponentImpl implements CryptoComponent {
byte[] salt = new byte[PBKDF_SALT_BYTES]; byte[] salt = new byte[PBKDF_SALT_BYTES];
secureRandom.nextBytes(salt); secureRandom.nextBytes(salt);
// Calibrate the KDF // Calibrate the KDF
int iterations = chooseIterationCount(PBKDF_TARGET_MILLIS); int cost = passwordBasedKdf.chooseCostParameter();
// Derive the key from the password // 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 // Generate a random IV
byte[] iv = new byte[STORAGE_IV_BYTES]; byte[] iv = new byte[STORAGE_IV_BYTES];
secureRandom.nextBytes(iv); secureRandom.nextBytes(iv);
// The output contains the salt, iterations, IV, ciphertext and MAC // The output contains the format version, salt, cost parameter, IV,
int outputLen = salt.length + INT_32_BYTES + iv.length + input.length // ciphertext and MAC
+ macBytes; int outputLen = 1 + salt.length + INT_32_BYTES + iv.length
+ input.length + macBytes;
byte[] output = new byte[outputLen]; byte[] output = new byte[outputLen];
System.arraycopy(salt, 0, output, 0, salt.length); int outputOff = 0;
ByteUtils.writeUint32(iterations, output, salt.length); // Format version
System.arraycopy(iv, 0, output, salt.length + INT_32_BYTES, iv.length); 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 // Initialise the cipher and encrypt the plaintext
try { try {
cipher.init(true, key, iv); cipher.init(true, key, iv);
int outputOff = salt.length + INT_32_BYTES + iv.length;
cipher.process(input, 0, input.length, output, outputOff); cipher.process(input, 0, input.length, output, outputOff);
return output; return output;
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException e) {
@@ -517,22 +305,36 @@ class CryptoComponentImpl implements CryptoComponent {
} }
@Override @Override
@Nullable
public byte[] decryptWithPassword(byte[] input, String password) { public byte[] decryptWithPassword(byte[] input, String password) {
AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher(); AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher();
int macBytes = cipher.getMacBytes(); int macBytes = cipher.getMacBytes();
// The input contains the salt, iterations, IV, ciphertext and MAC // The input contains the format version, salt, cost parameter, IV,
if (input.length < PBKDF_SALT_BYTES + INT_32_BYTES + STORAGE_IV_BYTES // ciphertext and MAC
+ macBytes) if (input.length < 1 + PBKDF_SALT_BYTES + INT_32_BYTES
+ STORAGE_IV_BYTES + macBytes)
return null; // Invalid input 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]; byte[] salt = new byte[PBKDF_SALT_BYTES];
System.arraycopy(input, 0, salt, 0, salt.length); System.arraycopy(input, inputOff, salt, 0, salt.length);
long iterations = ByteUtils.readUint32(input, salt.length); inputOff += salt.length;
if (iterations < 0 || iterations > Integer.MAX_VALUE) // Cost parameter
return null; // Invalid iteration count 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]; 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 // 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 // Initialise the cipher
try { try {
cipher.init(false, key, iv); cipher.init(false, key, iv);
@@ -541,7 +343,6 @@ class CryptoComponentImpl implements CryptoComponent {
} }
// Try to decrypt the ciphertext (may be invalid) // Try to decrypt the ciphertext (may be invalid)
try { try {
int inputOff = salt.length + INT_32_BYTES + iv.length;
int inputLen = input.length - inputOff; int inputLen = input.length - inputOff;
byte[] output = new byte[inputLen - macBytes]; byte[] output = new byte[inputLen - macBytes];
cipher.process(input, inputOff, inputLen, output, 0); cipher.process(input, inputOff, inputLen, output, 0);
@@ -564,88 +365,4 @@ class CryptoComponentImpl implements CryptoComponent {
public String asciiArmour(byte[] b, int lineLength) { public String asciiArmour(byte[] b, int lineLength) {
return AsciiArmour.wrap(b, lineLength); return AsciiArmour.wrap(b, lineLength);
} }
// Key derivation function based on a pseudo-random function - see
// NIST SP 800-108, section 5.1
private byte[] macKdf(SecretKey key, byte[]... inputs) {
// Initialise the PRF
Digest prf = new Blake2sDigest(key.getBytes());
// The output of the PRF must be long enough to use as a key
int macLength = prf.getDigestSize();
if (macLength < SecretKey.LENGTH) throw new IllegalStateException();
// Calculate the PRF over the concatenated length-prefixed inputs
byte[] length = new byte[INT_32_BYTES];
for (byte[] input : inputs) {
ByteUtils.writeUint32(input.length, length, 0);
prf.update(length, 0, length.length);
prf.update(input, 0, input.length);
}
byte[] mac = new byte[macLength];
prf.doFinal(mac, 0);
// The output is the first SecretKey.LENGTH bytes of the MAC
if (mac.length == SecretKey.LENGTH) return mac;
byte[] truncated = new byte[SecretKey.LENGTH];
System.arraycopy(mac, 0, truncated, 0, truncated.length);
return truncated;
}
// Password-based key derivation function - see PKCS#5 v2.1, section 5.2
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.TimeLoggingExecutor;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.CryptoExecutor; import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator; import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.crypto.StreamDecrypterFactory; import org.briarproject.bramble.api.crypto.StreamDecrypterFactory;
import org.briarproject.bramble.api.crypto.StreamEncrypterFactory; import org.briarproject.bramble.api.crypto.StreamEncrypterFactory;
import org.briarproject.bramble.api.crypto.TransportCrypto;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.system.SecureRandomProvider; import org.briarproject.bramble.api.system.SecureRandomProvider;
@@ -65,8 +67,9 @@ public class CryptoModule {
@Provides @Provides
@Singleton @Singleton
CryptoComponent provideCryptoComponent( CryptoComponent provideCryptoComponent(
SecureRandomProvider secureRandomProvider) { SecureRandomProvider secureRandomProvider,
return new CryptoComponentImpl(secureRandomProvider); ScryptKdf passwordBasedKdf) {
return new CryptoComponentImpl(secureRandomProvider, passwordBasedKdf);
} }
@Provides @Provides
@@ -74,6 +77,12 @@ public class CryptoModule {
return new PasswordStrengthEstimatorImpl(); return new PasswordStrengthEstimatorImpl();
} }
@Provides
TransportCrypto provideTransportCrypto(
TransportCryptoImpl transportCrypto) {
return transportCrypto;
}
@Provides @Provides
StreamDecrypterFactory provideStreamDecrypterFactory( StreamDecrypterFactory provideStreamDecrypterFactory(
Provider<AuthenticatedCipher> cipherProvider) { Provider<AuthenticatedCipher> cipherProvider) {
@@ -81,9 +90,17 @@ public class CryptoModule {
} }
@Provides @Provides
StreamEncrypterFactory provideStreamEncrypterFactory(CryptoComponent crypto, StreamEncrypterFactory provideStreamEncrypterFactory(
CryptoComponent crypto, TransportCrypto transportCrypto,
Provider<AuthenticatedCipher> cipherProvider) { Provider<AuthenticatedCipher> cipherProvider) {
return new StreamEncrypterFactoryImpl(crypto, cipherProvider); return new StreamEncrypterFactoryImpl(crypto, transportCrypto,
cipherProvider);
}
@Provides
KeyAgreementCrypto provideKeyAgreementCrypto(
KeyAgreementCryptoImpl keyAgreementCrypto) {
return keyAgreementCrypto;
} }
@Provides @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)} * @see {@link java.security.Signature#update(byte)}
*/ */
void update(byte b); void update(byte b) throws GeneralSecurityException;
/** /**
* @see {@link java.security.Signature#update(byte[])} * @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)} * @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()} * @see {@link java.security.Signature#sign()}
*/ */
byte[] sign(); byte[] sign() throws GeneralSecurityException;
/** /**
* @see {@link java.security.Signature#verify(byte[])} * @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.SecretKey;
import org.briarproject.bramble.api.crypto.StreamEncrypter; import org.briarproject.bramble.api.crypto.StreamEncrypter;
import org.briarproject.bramble.api.crypto.StreamEncrypterFactory; import org.briarproject.bramble.api.crypto.StreamEncrypterFactory;
import org.briarproject.bramble.api.crypto.TransportCrypto;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.transport.StreamContext; import org.briarproject.bramble.api.transport.StreamContext;
@@ -22,12 +23,15 @@ import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENG
class StreamEncrypterFactoryImpl implements StreamEncrypterFactory { class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
private final CryptoComponent crypto; private final CryptoComponent crypto;
private final TransportCrypto transportCrypto;
private final Provider<AuthenticatedCipher> cipherProvider; private final Provider<AuthenticatedCipher> cipherProvider;
@Inject @Inject
StreamEncrypterFactoryImpl(CryptoComponent crypto, StreamEncrypterFactoryImpl(CryptoComponent crypto,
TransportCrypto transportCrypto,
Provider<AuthenticatedCipher> cipherProvider) { Provider<AuthenticatedCipher> cipherProvider) {
this.crypto = crypto; this.crypto = crypto;
this.transportCrypto = transportCrypto;
this.cipherProvider = cipherProvider; this.cipherProvider = cipherProvider;
} }
@@ -37,7 +41,8 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
AuthenticatedCipher cipher = cipherProvider.get(); AuthenticatedCipher cipher = cipherProvider.get();
long streamNumber = ctx.getStreamNumber(); long streamNumber = ctx.getStreamNumber();
byte[] tag = new byte[TAG_LENGTH]; byte[] tag = new byte[TAG_LENGTH];
crypto.encodeTag(tag, ctx.getTagKey(), PROTOCOL_VERSION, streamNumber); transportCrypto.encodeTag(tag, ctx.getTagKey(), PROTOCOL_VERSION,
streamNumber);
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH]; byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
crypto.getSecureRandom().nextBytes(streamHeaderNonce); crypto.getSecureRandom().nextBytes(streamHeaderNonce);
SecretKey frameKey = crypto.generateSecretKey(); SecretKey frameKey = crypto.generateSecretKey();

View File

@@ -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,6 +6,7 @@ import org.briarproject.bramble.api.db.DataTooNewException;
import org.briarproject.bramble.api.db.DataTooOldException; import org.briarproject.bramble.api.db.DataTooOldException;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata; 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.Author;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
@@ -45,7 +46,7 @@ interface Database<T> {
* @throws DataTooOldException if the data uses an older schema than the * @throws DataTooOldException if the data uses an older schema than the
* current code and cannot be migrated * current code and cannot be migrated
*/ */
boolean open() throws DbException; boolean open(@Nullable MigrationListener listener) throws DbException;
/** /**
* Prevents new transactions from starting, waits for all current * Prevents new transactions from starting, waits for all current
@@ -96,9 +97,12 @@ interface Database<T> {
/** /**
* Stores a message. * 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) void addMessage(T txn, Message m, State state, boolean shared,
throws DbException; @Nullable ContactId sender) throws DbException;
/** /**
* Adds a dependency between two messages in the given group. * Adds a dependency between two messages in the given group.
@@ -111,16 +115,6 @@ interface Database<T> {
*/ */
void addOfferedMessage(T txn, ContactId c, MessageId m) throws DbException; 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. * Stores a transport.
*/ */
@@ -279,7 +273,7 @@ interface Database<T> {
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<ContactId> getGroupVisibility(T txn, GroupId g) Map<ContactId, Boolean> getGroupVisibility(T txn, GroupId g)
throws DbException; throws DbException;
/** /**
@@ -430,31 +424,37 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Returns the IDs of any messages that need to be validated by the given * Returns the IDs of any messages that need to be validated.
* client.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getMessagesToValidate(T txn, ClientId c) Collection<MessageId> getMessagesToValidate(T txn) throws DbException;
throws DbException;
/** /**
* Returns the IDs of any messages that are still pending due to * Returns the IDs of any messages that are pending delivery due to
* dependencies to other messages for the given client. * dependencies on other messages.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getPendingMessages(T txn, ClientId c) Collection<MessageId> getPendingMessages(T txn) throws DbException;
throws DbException;
/** /**
* Returns the IDs of any messages from the given client * Returns the IDs of any messages that have a shared dependent but have
* that have a shared dependent, but are still not shared themselves. * not yet been shared themselves.
* <p/> * <p/>
* Read-only. * Read-only.
*/ */
Collection<MessageId> getMessagesToShare(T txn, ClientId c) Collection<MessageId> getMessagesToShare(T txn) throws DbException;
throws DbException;
/**
* Returns the next time (in milliseconds since the Unix epoch) when a
* message is due to be sent to the given contact. The returned value may
* be zero if a message is due to be sent immediately, or Long.MAX_VALUE
* if no messages are scheduled to be sent.
* <p/>
* Read-only.
*/
long getNextSendTime(T txn, ContactId c) throws DbException;
/** /**
* Returns the message with the given ID, in serialised form, or null if * Returns the message with the given ID, in serialised form, or null if
@@ -573,13 +573,6 @@ interface Database<T> {
*/ */
void removeMessage(T txn, MessageId m) throws DbException; 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 * Removes the given offered messages that were offered by the given
* contact. * contact.
@@ -587,12 +580,6 @@ interface Database<T> {
void removeOfferedMessages(T txn, ContactId c, void removeOfferedMessages(T txn, ContactId c,
Collection<MessageId> requested) throws DbException; 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. * Removes a transport (and all associated state) from the database.
*/ */

View File

@@ -10,6 +10,7 @@ import org.briarproject.bramble.api.db.ContactExistsException;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata; 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.NoSuchContactException;
import org.briarproject.bramble.api.db.NoSuchGroupException; import org.briarproject.bramble.api.db.NoSuchGroupException;
import org.briarproject.bramble.api.db.NoSuchLocalAuthorException; import org.briarproject.bramble.api.db.NoSuchLocalAuthorException;
@@ -100,8 +101,9 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public boolean open() throws DbException { public boolean open(@Nullable MigrationListener listener)
boolean reopened = db.open(); throws DbException {
boolean reopened = db.open(listener);
shutdown.addShutdownHook(() -> { shutdown.addShutdownHook(() -> {
try { try {
close(); close();
@@ -213,7 +215,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
if (!db.containsGroup(txn, m.getGroupId())) if (!db.containsGroup(txn, m.getGroupId()))
throw new NoSuchGroupException(); throw new NoSuchGroupException();
if (!db.containsMessage(txn, m.getId())) { 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 MessageAddedEvent(m, null));
transaction.attach(new MessageStateChangedEvent(m.getId(), true, transaction.attach(new MessageStateChangedEvent(m.getId(), true,
DELIVERED)); DELIVERED));
@@ -222,16 +224,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.mergeMessageMetadata(txn, m.getId(), meta); 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 @Override
public void addTransport(Transaction transaction, TransportId t, public void addTransport(Transaction transaction, TransportId t,
int maxLatency) throws DbException { int maxLatency) throws DbException {
@@ -463,24 +455,24 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
} }
@Override @Override
public Collection<MessageId> getMessagesToValidate(Transaction transaction, public Collection<MessageId> getMessagesToValidate(Transaction transaction)
ClientId c) throws DbException { throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
return db.getMessagesToValidate(txn, c); return db.getMessagesToValidate(txn);
} }
@Override @Override
public Collection<MessageId> getPendingMessages(Transaction transaction, public Collection<MessageId> getPendingMessages(Transaction transaction)
ClientId c) throws DbException { throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
return db.getPendingMessages(txn, c); return db.getPendingMessages(txn);
} }
@Override @Override
public Collection<MessageId> getMessagesToShare( public Collection<MessageId> getMessagesToShare(Transaction transaction)
Transaction transaction, ClientId c) throws DbException { throws DbException {
T txn = unbox(transaction); T txn = unbox(transaction);
return db.getMessagesToShare(txn, c); return db.getMessagesToShare(txn);
} }
@Nullable @Nullable
@@ -579,6 +571,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.getMessageDependents(txn, m); return db.getMessageDependents(txn, m);
} }
@Override
public long getNextSendTime(Transaction transaction, ContactId c)
throws DbException {
T txn = unbox(transaction);
return db.getNextSendTime(txn, c);
}
@Override @Override
public Settings getSettings(Transaction transaction, String namespace) public Settings getSettings(Transaction transaction, String namespace)
throws DbException { throws DbException {
@@ -673,7 +672,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.raiseSeenFlag(txn, c, m.getId()); db.raiseSeenFlag(txn, c, m.getId());
db.raiseAckFlag(txn, c, m.getId()); db.raiseAckFlag(txn, c, m.getId());
} else { } else {
addMessage(txn, m, UNKNOWN, false, c); db.addMessage(txn, m, UNKNOWN, false, c);
transaction.attach(new MessageAddedEvent(m, c)); transaction.attach(new MessageAddedEvent(m, c));
} }
transaction.attach(new MessageToAckEvent(c)); transaction.attach(new MessageToAckEvent(c));
@@ -741,7 +740,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
GroupId id = g.getId(); GroupId id = g.getId();
if (!db.containsGroup(txn, id)) if (!db.containsGroup(txn, id))
throw new NoSuchGroupException(); throw new NoSuchGroupException();
Collection<ContactId> affected = db.getGroupVisibility(txn, id); Collection<ContactId> affected =
db.getGroupVisibility(txn, id).keySet();
db.removeGroup(txn, id); db.removeGroup(txn, id);
transaction.attach(new GroupRemovedEvent(g)); transaction.attach(new GroupRemovedEvent(g));
transaction.attach(new GroupVisibilityUpdatedEvent(affected)); transaction.attach(new GroupVisibilityUpdatedEvent(affected));
@@ -811,19 +811,9 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
throw new NoSuchGroupException(); throw new NoSuchGroupException();
Visibility old = db.getGroupVisibility(txn, c, g); Visibility old = db.getGroupVisibility(txn, c, g);
if (old == v) return; if (old == v) return;
if (old == INVISIBLE) { if (old == INVISIBLE) db.addGroupVisibility(txn, c, g, v == SHARED);
db.addGroupVisibility(txn, c, g, v == SHARED); else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g);
for (MessageId m : db.getMessageIds(txn, g)) { else db.setGroupVisibility(txn, c, g, v == SHARED);
boolean seen = db.removeOfferedMessage(txn, c, m);
db.addStatus(txn, c, m, seen, seen);
}
} else if (v == INVISIBLE) {
db.removeGroupVisibility(txn, c, g);
for (MessageId m : db.getMessageIds(txn, g))
db.removeStatus(txn, c, m);
} else {
db.setGroupVisibility(txn, c, g, v == SHARED);
}
List<ContactId> affected = Collections.singletonList(c); List<ContactId> affected = Collections.singletonList(c);
transaction.attach(new GroupVisibilityUpdatedEvent(affected)); transaction.attach(new GroupVisibilityUpdatedEvent(affected));
} }

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.db;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.db.DbException; 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
@@ -13,6 +14,7 @@ import java.sql.DriverManager;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Properties; import java.util.Properties;
import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
/** /**
@@ -22,28 +24,31 @@ import javax.inject.Inject;
class H2Database extends JdbcDatabase { class H2Database extends JdbcDatabase {
private static final String HASH_TYPE = "BINARY(32)"; private static final String HASH_TYPE = "BINARY(32)";
private static final String SECRET_TYPE = "BINARY(32)";
private static final String BINARY_TYPE = "BINARY"; private static final String BINARY_TYPE = "BINARY";
private static final String COUNTER_TYPE = "INT NOT NULL AUTO_INCREMENT"; private static final String COUNTER_TYPE = "INT NOT NULL AUTO_INCREMENT";
private static final String SECRET_TYPE = "BINARY(32)"; private static final String STRING_TYPE = "VARCHAR";
private final DatabaseConfig config; private final DatabaseConfig config;
private final String url; private final String url;
@Inject @Inject
H2Database(DatabaseConfig config, Clock clock) { H2Database(DatabaseConfig config, Clock clock) {
super(HASH_TYPE, BINARY_TYPE, COUNTER_TYPE, SECRET_TYPE, clock); super(HASH_TYPE, SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE,
clock);
this.config = config; this.config = config;
File dir = config.getDatabaseDirectory(); File dir = config.getDatabaseDirectory();
String path = new File(dir, "db").getAbsolutePath(); String path = new File(dir, "db").getAbsolutePath();
url = "jdbc:h2:split:" + path + ";CIPHER=AES;MULTI_THREADED=1" url = "jdbc:h2:split:" + path + ";CIPHER=AES;MULTI_THREADED=1"
+ ";WRITE_DELAY=0;DB_CLOSE_ON_EXIT=false"; + ";WRITE_DELAY=0";
} }
@Override @Override
public boolean open() throws DbException { public boolean open(@Nullable MigrationListener listener)
throws DbException {
boolean reopen = config.databaseExists(); boolean reopen = config.databaseExists();
if (!reopen) config.getDatabaseDirectory().mkdirs(); if (!reopen) config.getDatabaseDirectory().mkdirs();
super.open("org.h2.Driver", reopen); super.open("org.h2.Driver", reopen, listener);
return reopen; return reopen;
} }
@@ -90,6 +95,10 @@ class H2Database extends JdbcDatabase {
// Separate the file password from the user password with a space // Separate the file password from the user password with a space
String hex = StringUtils.toHexString(key.getBytes()); String hex = StringUtils.toHexString(key.getBytes());
props.put("password", hex + " password"); props.put("password", hex + " password");
return DriverManager.getConnection(url, props); return DriverManager.getConnection(getUrl(), props);
}
String getUrl() {
return url;
} }
} }

View File

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

View File

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

View File

@@ -0,0 +1,36 @@
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.
*/
void submit(Callable<KeyAgreementConnection> task);
/**
* 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.
*
* @param timeout the timeout in milliseconds
* @throws InterruptedException if the thread is interrupted while waiting
* for a connection to become available
*/
@Nullable
KeyAgreementConnection poll(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.
*/
void stop();
}

View File

@@ -0,0 +1,112 @@
package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
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.List;
import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.util.logging.Level.INFO;
@NotNullByDefault
@ThreadSafe
class ConnectionChooserImpl implements ConnectionChooser {
private static final Logger LOG =
Logger.getLogger(ConnectionChooserImpl.class.getName());
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<>();
@Inject
ConnectionChooserImpl(Clock clock, @IoExecutor Executor ioExecutor) {
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());
}
});
}
@Nullable
@Override
public KeyAgreementConnection poll(long timeout)
throws InterruptedException {
long now = clock.currentTimeMillis();
long end = now + timeout;
synchronized (lock) {
while (!stopped && results.isEmpty() && now < end) {
lock.wait(end - now);
now = clock.currentTimeMillis();
}
return results.poll();
}
}
@Override
public void stop() {
List<KeyAgreementConnection> unused;
synchronized (lock) {
unused = new ArrayList<>(results);
results.clear();
stopped = true;
lock.notifyAll();
}
if (LOG.isLoggable(INFO))
LOG.info("Closing " + unused.size() + " unused connections");
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);
conn.getWriter().dispose(false);
} catch (IOException e) {
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
}
}
}

View File

@@ -1,6 +1,6 @@
package org.briarproject.bramble.keyagreement; package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection; import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
@@ -13,23 +13,19 @@ import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.system.Clock;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.Future;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.CONNECTION_TIMEOUT; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.CONNECTION_TIMEOUT;
@@ -45,33 +41,31 @@ class KeyAgreementConnector {
Logger.getLogger(KeyAgreementConnector.class.getName()); Logger.getLogger(KeyAgreementConnector.class.getName());
private final Callbacks callbacks; private final Callbacks callbacks;
private final Clock clock; private final KeyAgreementCrypto keyAgreementCrypto;
private final CryptoComponent crypto;
private final PluginManager pluginManager; private final PluginManager pluginManager;
private final CompletionService<KeyAgreementConnection> connect; private final ConnectionChooser connectionChooser;
private final List<KeyAgreementListener> listeners = new ArrayList<>(); private final List<KeyAgreementListener> listeners =
private final List<Future<KeyAgreementConnection>> pending = new CopyOnWriteArrayList<>();
new ArrayList<>(); private final CountDownLatch aliceLatch = new CountDownLatch(1);
private final AtomicBoolean waitingSent = new AtomicBoolean(false);
private volatile boolean connecting = false; private volatile boolean alice = false, stopped = false;
private volatile boolean alice = false;
KeyAgreementConnector(Callbacks callbacks, Clock clock, KeyAgreementConnector(Callbacks callbacks,
CryptoComponent crypto, PluginManager pluginManager, KeyAgreementCrypto keyAgreementCrypto, PluginManager pluginManager,
Executor ioExecutor) { ConnectionChooser connectionChooser) {
this.callbacks = callbacks; this.callbacks = callbacks;
this.clock = clock; this.keyAgreementCrypto = keyAgreementCrypto;
this.crypto = crypto;
this.pluginManager = pluginManager; this.pluginManager = pluginManager;
connect = new ExecutorCompletionService<>(ioExecutor); this.connectionChooser = connectionChooser;
} }
public Payload listen(KeyPair localKeyPair) { Payload listen(KeyPair localKeyPair) {
LOG.info("Starting BQP listeners"); LOG.info("Starting BQP listeners");
// Derive commitment // Derive commitment
byte[] commitment = crypto.deriveKeyCommitment( byte[] commitment = keyAgreementCrypto.deriveKeyCommitment(
localKeyPair.getPublic().getEncoded()); localKeyPair.getPublic());
// Start all listeners and collect their descriptors // Start all listeners and collect their descriptors
List<TransportDescriptor> descriptors = new ArrayList<>(); List<TransportDescriptor> descriptors = new ArrayList<>();
for (DuplexPlugin plugin : pluginManager.getKeyAgreementPlugins()) { for (DuplexPlugin plugin : pluginManager.getKeyAgreementPlugins()) {
@@ -80,8 +74,9 @@ class KeyAgreementConnector {
if (l != null) { if (l != null) {
TransportId id = plugin.getId(); TransportId id = plugin.getId();
descriptors.add(new TransportDescriptor(id, l.getDescriptor())); descriptors.add(new TransportDescriptor(id, l.getDescriptor()));
pending.add(connect.submit(new ReadableTask(l.listen()))); if (LOG.isLoggable(INFO)) LOG.info("Listening via " + id);
listeners.add(l); listeners.add(l);
connectionChooser.submit(new ReadableTask(l::accept));
} }
} }
return new Payload(commitment, descriptors); return new Payload(commitment, descriptors);
@@ -89,125 +84,92 @@ class KeyAgreementConnector {
void stopListening() { void stopListening() {
LOG.info("Stopping BQP listeners"); LOG.info("Stopping BQP listeners");
for (KeyAgreementListener l : listeners) { stopped = true;
l.close(); aliceLatch.countDown();
} for (KeyAgreementListener l : listeners) l.close();
listeners.clear(); connectionChooser.stop();
} }
@Nullable @Nullable
public KeyAgreementTransport connect(Payload remotePayload, public KeyAgreementTransport connect(Payload remotePayload, boolean alice) {
boolean alice) { // Let the ReadableTasks know if we are Alice
// Let the listeners know if we are Alice
this.connecting = true;
this.alice = alice; this.alice = alice;
long end = clock.currentTimeMillis() + CONNECTION_TIMEOUT; aliceLatch.countDown();
// Start connecting over supported transports // Start connecting over supported transports
LOG.info("Starting outgoing BQP connections"); if (LOG.isLoggable(INFO)) {
LOG.info("Starting outgoing BQP connections as "
+ (alice ? "Alice" : "Bob"));
}
for (TransportDescriptor d : remotePayload.getTransportDescriptors()) { for (TransportDescriptor d : remotePayload.getTransportDescriptors()) {
Plugin p = pluginManager.getPlugin(d.getId()); Plugin p = pluginManager.getPlugin(d.getId());
if (p instanceof DuplexPlugin) { if (p instanceof DuplexPlugin) {
if (LOG.isLoggable(INFO))
LOG.info("Connecting via " + d.getId());
DuplexPlugin plugin = (DuplexPlugin) p; DuplexPlugin plugin = (DuplexPlugin) p;
pending.add(connect.submit(new ReadableTask( byte[] commitment = remotePayload.getCommitment();
new ConnectorTask(plugin, remotePayload.getCommitment(), BdfList descriptor = d.getDescriptor();
d.getDescriptor(), end)))); connectionChooser.submit(new ReadableTask(
new ConnectorTask(plugin, commitment, descriptor)));
} }
} }
// Get chosen connection // Get chosen connection
KeyAgreementConnection chosen = null;
try { try {
long now = clock.currentTimeMillis(); KeyAgreementConnection chosen =
Future<KeyAgreementConnection> f = connectionChooser.poll(CONNECTION_TIMEOUT);
connect.poll(end - now, MILLISECONDS); if (chosen == null) return null;
if (f == null)
return null; // No task completed within the timeout.
chosen = f.get();
return new KeyAgreementTransport(chosen); return new KeyAgreementTransport(chosen);
} catch (InterruptedException e) { } catch (InterruptedException e) {
LOG.info("Interrupted while waiting for connection"); LOG.info("Interrupted while waiting for connection");
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
return null; return null;
} catch (ExecutionException | IOException e) { } catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return null; return null;
} finally { } finally {
stopListening(); stopListening();
// Close all other connections
closePending(chosen);
} }
} }
private void closePending(@Nullable KeyAgreementConnection chosen) { private void waitingForAlice() {
for (Future<KeyAgreementConnection> f : pending) { if (!waitingSent.getAndSet(true)) callbacks.connectionWaiting();
try {
if (f.cancel(true)) {
LOG.info("Cancelled task");
} else if (!f.isCancelled()) {
KeyAgreementConnection c = f.get();
if (c != null && c != chosen)
tryToClose(c.getConnection(), false);
}
} catch (InterruptedException e) {
LOG.info("Interrupted while closing sockets");
Thread.currentThread().interrupt();
return;
} catch (ExecutionException e) {
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
}
}
}
private void tryToClose(DuplexTransportConnection conn, boolean exception) {
try {
if (LOG.isLoggable(INFO))
LOG.info("Closing connection, exception: " + exception);
conn.getReader().dispose(exception, true);
conn.getWriter().dispose(exception);
} catch (IOException e) {
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
}
} }
private class ConnectorTask implements Callable<KeyAgreementConnection> { private class ConnectorTask implements Callable<KeyAgreementConnection> {
private final byte[] commitment; private final byte[] commitment;
private final BdfList descriptor; private final BdfList descriptor;
private final long end;
private final DuplexPlugin plugin; private final DuplexPlugin plugin;
private ConnectorTask(DuplexPlugin plugin, byte[] commitment, private ConnectorTask(DuplexPlugin plugin, byte[] commitment,
BdfList descriptor, long end) { BdfList descriptor) {
this.plugin = plugin; this.plugin = plugin;
this.commitment = commitment; this.commitment = commitment;
this.descriptor = descriptor; this.descriptor = descriptor;
this.end = end;
} }
@Nullable
@Override @Override
public KeyAgreementConnection call() throws Exception { public KeyAgreementConnection call() throws Exception {
// Repeat attempts until we connect, get interrupted, or time out // Repeat attempts until we connect, get stopped, or get interrupted
while (true) { while (!stopped) {
long now = clock.currentTimeMillis();
if (now > end) throw new IOException();
DuplexTransportConnection conn = DuplexTransportConnection conn =
plugin.createKeyAgreementConnection(commitment, plugin.createKeyAgreementConnection(commitment,
descriptor, end - now); descriptor);
if (conn != null) { if (conn != null) {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info(plugin.getId().getString() + LOG.info(plugin.getId() + ": Outgoing connection");
": Outgoing connection");
return new KeyAgreementConnection(conn, plugin.getId()); return new KeyAgreementConnection(conn, plugin.getId());
} }
// Wait 2s before retry (to circumvent transient failures) // Wait 2s before retry (to circumvent transient failures)
Thread.sleep(2000); Thread.sleep(2000);
} }
return null;
} }
} }
private class ReadableTask private class ReadableTask implements Callable<KeyAgreementConnection> {
implements Callable<KeyAgreementConnection> {
private final Callable<KeyAgreementConnection> connectionTask; private final Callable<KeyAgreementConnection> connectionTask;
@@ -215,24 +177,23 @@ class KeyAgreementConnector {
this.connectionTask = connectionTask; this.connectionTask = connectionTask;
} }
@Nullable
@Override @Override
public KeyAgreementConnection call() throws Exception { public KeyAgreementConnection call() throws Exception {
KeyAgreementConnection c = connectionTask.call(); 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(); InputStream in = c.getConnection().getReader().getInputStream();
boolean waitingSent = false; while (!stopped && in.available() == 0) {
while (!alice && in.available() == 0) { if (LOG.isLoggable(INFO))
if (!waitingSent && connecting && !alice) { LOG.info(c.getTransportId() + ": Waiting for data");
// Bob waits here until Alice obtains his payload. waitingForAlice();
callbacks.connectionWaiting(); Thread.sleep(500);
waitingSent = true;
}
if (LOG.isLoggable(INFO)) {
LOG.info(c.getTransportId().getString() +
": Waiting for connection");
}
Thread.sleep(1000);
} }
if (!alice && LOG.isLoggable(INFO)) if (!stopped && LOG.isLoggable(INFO))
LOG.info(c.getTransportId().getString() + ": Data available"); LOG.info(c.getTransportId().getString() + ": Data available");
return c; return c;
} }

View File

@@ -1,19 +1,10 @@
package org.briarproject.bramble.keyagreement; package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.data.BdfReaderFactory; import org.briarproject.bramble.api.data.BdfReaderFactory;
import org.briarproject.bramble.api.data.BdfWriterFactory; import org.briarproject.bramble.api.data.BdfWriterFactory;
import org.briarproject.bramble.api.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.keyagreement.PayloadEncoder;
import org.briarproject.bramble.api.keyagreement.PayloadParser; import org.briarproject.bramble.api.keyagreement.PayloadParser;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.system.Clock;
import java.util.concurrent.Executor;
import javax.inject.Singleton;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
@@ -22,13 +13,9 @@ import dagger.Provides;
public class KeyAgreementModule { public class KeyAgreementModule {
@Provides @Provides
@Singleton KeyAgreementTask provideKeyAgreementTask(
KeyAgreementTaskFactory provideKeyAgreementTaskFactory(Clock clock, KeyAgreementTaskImpl keyAgreementTask) {
CryptoComponent crypto, EventBus eventBus, return keyAgreementTask;
@IoExecutor Executor ioExecutor, PayloadEncoder payloadEncoder,
PluginManager pluginManager) {
return new KeyAgreementTaskFactoryImpl(clock, crypto, eventBus,
ioExecutor, payloadEncoder, pluginManager);
} }
@Provides @Provides
@@ -40,4 +27,10 @@ public class KeyAgreementModule {
PayloadParser providePayloadParser(BdfReaderFactory bdfReaderFactory) { PayloadParser providePayloadParser(BdfReaderFactory bdfReaderFactory) {
return new PayloadParserImpl(bdfReaderFactory); return new PayloadParserImpl(bdfReaderFactory);
} }
@Provides
ConnectionChooser provideConnectionChooser(
ConnectionChooserImpl connectionChooser) {
return connectionChooser;
}
} }

View File

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

View File

@@ -1,46 +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.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.system.Clock;
import java.util.concurrent.Executor;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
@Immutable
@NotNullByDefault
class KeyAgreementTaskFactoryImpl implements KeyAgreementTaskFactory {
private final Clock clock;
private final CryptoComponent crypto;
private final EventBus eventBus;
private final Executor ioExecutor;
private final PayloadEncoder payloadEncoder;
private final PluginManager pluginManager;
@Inject
KeyAgreementTaskFactoryImpl(Clock clock, CryptoComponent crypto,
EventBus eventBus, @IoExecutor Executor ioExecutor,
PayloadEncoder payloadEncoder, PluginManager pluginManager) {
this.clock = clock;
this.crypto = crypto;
this.eventBus = eventBus;
this.ioExecutor = ioExecutor;
this.payloadEncoder = payloadEncoder;
this.pluginManager = pluginManager;
}
@Override
public KeyAgreementTask createTask() {
return new KeyAgreementTaskImpl(clock, crypto, eventBus, payloadEncoder,
pluginManager, ioExecutor);
}
}

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.keyagreement; package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
@@ -17,24 +18,24 @@ import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager; import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.system.Clock;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
class KeyAgreementTaskImpl extends Thread implements class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
KeyAgreementTask, KeyAgreementConnector.Callbacks, KeyAgreementProtocol.Callbacks, KeyAgreementConnector.Callbacks {
KeyAgreementProtocol.Callbacks {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(KeyAgreementTaskImpl.class.getName()); Logger.getLogger(KeyAgreementTaskImpl.class.getName());
private final CryptoComponent crypto; private final CryptoComponent crypto;
private final KeyAgreementCrypto keyAgreementCrypto;
private final EventBus eventBus; private final EventBus eventBus;
private final PayloadEncoder payloadEncoder; private final PayloadEncoder payloadEncoder;
private final KeyPair localKeyPair; private final KeyPair localKeyPair;
@@ -43,15 +44,18 @@ class KeyAgreementTaskImpl extends Thread implements
private Payload localPayload; private Payload localPayload;
private Payload remotePayload; private Payload remotePayload;
KeyAgreementTaskImpl(Clock clock, CryptoComponent crypto, @Inject
EventBus eventBus, PayloadEncoder payloadEncoder, KeyAgreementTaskImpl(CryptoComponent crypto,
PluginManager pluginManager, Executor ioExecutor) { KeyAgreementCrypto keyAgreementCrypto, EventBus eventBus,
PayloadEncoder payloadEncoder, PluginManager pluginManager,
ConnectionChooser connectionChooser) {
this.crypto = crypto; this.crypto = crypto;
this.keyAgreementCrypto = keyAgreementCrypto;
this.eventBus = eventBus; this.eventBus = eventBus;
this.payloadEncoder = payloadEncoder; this.payloadEncoder = payloadEncoder;
localKeyPair = crypto.generateAgreementKeyPair(); localKeyPair = crypto.generateAgreementKeyPair();
connector = new KeyAgreementConnector(this, clock, crypto, connector = new KeyAgreementConnector(this, keyAgreementCrypto,
pluginManager, ioExecutor); pluginManager, connectionChooser);
} }
@Override @Override
@@ -65,10 +69,8 @@ class KeyAgreementTaskImpl extends Thread implements
@Override @Override
public synchronized void stopListening() { public synchronized void stopListening() {
if (localPayload != null) { if (localPayload != null) {
if (remotePayload == null) if (remotePayload == null) connector.stopListening();
connector.stopListening(); else interrupt();
else
interrupt();
} }
} }
@@ -100,8 +102,8 @@ class KeyAgreementTaskImpl extends Thread implements
// Run BQP protocol over the connection // Run BQP protocol over the connection
LOG.info("Starting BQP protocol"); LOG.info("Starting BQP protocol");
KeyAgreementProtocol protocol = new KeyAgreementProtocol(this, crypto, KeyAgreementProtocol protocol = new KeyAgreementProtocol(this, crypto,
payloadEncoder, transport, remotePayload, localPayload, keyAgreementCrypto, payloadEncoder, transport, remotePayload,
localKeyPair, alice); localPayload, localKeyPair, alice);
try { try {
SecretKey master = protocol.perform(); SecretKey master = protocol.perform();
KeyAgreementResult result = KeyAgreementResult result =

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,6 +3,8 @@ package org.briarproject.bramble.plugin.bluetooth;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection; import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener; import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
@@ -13,8 +15,11 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback; import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.BluetoothEnabledEvent;
import org.briarproject.bramble.api.plugin.event.DisableBluetoothEvent;
import org.briarproject.bramble.api.plugin.event.EnableBluetoothEvent;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.util.OsUtils; import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import java.io.IOException; import java.io.IOException;
@@ -23,30 +28,25 @@ import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.bluetooth.BluetoothStateException;
import javax.bluetooth.LocalDevice;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import javax.microedition.io.StreamConnectionNotifier;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static javax.bluetooth.DiscoveryAgent.GIAC;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID; import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENABLE;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS; import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID; import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES; import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
class BluetoothPlugin implements DuplexPlugin { abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(BluetoothPlugin.class.getName()); Logger.getLogger(BluetoothPlugin.class.getName());
@@ -58,9 +58,38 @@ class BluetoothPlugin implements DuplexPlugin {
private final int maxLatency; private final int maxLatency;
private final AtomicBoolean used = new AtomicBoolean(false); private final AtomicBoolean used = new AtomicBoolean(false);
private volatile boolean running = false; private volatile boolean running = false, contactConnections = false;
private volatile StreamConnectionNotifier socket = null; private volatile String contactConnectionsUuid = null;
private volatile LocalDevice localDevice = null; private volatile SS socket = null;
abstract void initialiseAdapter() throws IOException;
abstract boolean isAdapterEnabled();
abstract void enableAdapter();
abstract void disableAdapterIfEnabledByUs();
abstract void setEnabledByUs();
/**
* Returns the local Bluetooth address, or null if no valid address can
* be found.
*/
@Nullable
abstract String getBluetoothAddress();
abstract SS openServerSocket(String uuid) throws IOException;
abstract void tryToClose(@Nullable SS ss);
abstract DuplexTransportConnection acceptConnection(SS ss)
throws IOException;
abstract boolean isValidAddress(String address);
abstract DuplexTransportConnection connectTo(String address, String uuid)
throws IOException;
BluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom, BluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom,
Backoff backoff, DuplexPluginCallback callback, int maxLatency) { Backoff backoff, DuplexPluginCallback callback, int maxLatency) {
@@ -71,6 +100,19 @@ class BluetoothPlugin implements DuplexPlugin {
this.maxLatency = maxLatency; this.maxLatency = maxLatency;
} }
void onAdapterEnabled() {
LOG.info("Bluetooth enabled");
// We may not have been able to get the local address before
ioExecutor.execute(this::updateProperties);
if (shouldAllowContactConnections()) bind();
}
void onAdapterDisabled() {
LOG.info("Bluetooth disabled");
tryToClose(socket);
callback.transportDisabled();
}
@Override @Override
public TransportId getId() { public TransportId getId() {
return ID; return ID;
@@ -90,107 +132,103 @@ class BluetoothPlugin implements DuplexPlugin {
@Override @Override
public void start() throws PluginException { public void start() throws PluginException {
if (used.getAndSet(true)) throw new IllegalStateException(); if (used.getAndSet(true)) throw new IllegalStateException();
// Initialise the Bluetooth stack
try { try {
localDevice = LocalDevice.getLocalDevice(); initialiseAdapter();
} catch (UnsatisfiedLinkError e) { } catch (IOException e) {
// On Linux the user may need to install libbluetooth-dev
if (OsUtils.isLinux())
callback.showMessage("BLUETOOTH_INSTALL_LIBS");
throw new PluginException(e);
} catch (BluetoothStateException e) {
throw new PluginException(e); throw new PluginException(e);
} }
if (LOG.isLoggable(INFO)) updateProperties();
LOG.info("Local address " + localDevice.getBluetoothAddress());
running = true; running = true;
bind(); loadSettings();
if (shouldAllowContactConnections()) {
if (isAdapterEnabled()) bind();
else enableAdapter();
}
}
private void loadSettings() {
contactConnections =
callback.getSettings().getBoolean(PREF_BT_ENABLE, false);
}
private boolean shouldAllowContactConnections() {
return contactConnections;
} }
private void bind() { private void bind() {
ioExecutor.execute(() -> { ioExecutor.execute(() -> {
if (!running) return; if (!isRunning() || !shouldAllowContactConnections()) return;
// Advertise the Bluetooth address to contacts
TransportProperties p = new TransportProperties();
p.put(PROP_ADDRESS, localDevice.getBluetoothAddress());
callback.mergeLocalProperties(p);
// Bind a server socket to accept connections from contacts // Bind a server socket to accept connections from contacts
String url = makeUrl("localhost", getUuid()); SS ss;
StreamConnectionNotifier ss;
try { try {
ss = (StreamConnectionNotifier) Connector.open(url); ss = openServerSocket(contactConnectionsUuid);
} catch (IOException e) { } catch (IOException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
LOG.log(WARNING, e.toString(), e);
return; return;
} }
if (!running) { if (!isRunning() || !shouldAllowContactConnections()) {
tryToClose(ss); tryToClose(ss);
return; return;
} }
socket = ss; socket = ss;
backoff.reset(); backoff.reset();
callback.transportEnabled(); callback.transportEnabled();
acceptContactConnections(ss); acceptContactConnections();
}); });
} }
private String makeUrl(String address, String uuid) { private void updateProperties() {
return "btspp://" + address + ":" + uuid + ";name=RFCOMM"; TransportProperties p = callback.getLocalProperties();
} String address = p.get(PROP_ADDRESS);
String uuid = p.get(PROP_UUID);
private String getUuid() { boolean changed = false;
String uuid = callback.getLocalProperties().get(PROP_UUID); if (address == null) {
address = getBluetoothAddress();
if (LOG.isLoggable(INFO))
LOG.info("Local address " + scrubMacAddress(address));
if (!StringUtils.isNullOrEmpty(address)) {
p.put(PROP_ADDRESS, address);
changed = true;
}
}
if (uuid == null) { if (uuid == null) {
byte[] random = new byte[UUID_BYTES]; byte[] random = new byte[UUID_BYTES];
secureRandom.nextBytes(random); secureRandom.nextBytes(random);
uuid = UUID.nameUUIDFromBytes(random).toString(); uuid = UUID.nameUUIDFromBytes(random).toString();
TransportProperties p = new TransportProperties();
p.put(PROP_UUID, uuid); p.put(PROP_UUID, uuid);
callback.mergeLocalProperties(p); changed = true;
} }
return uuid; contactConnectionsUuid = uuid;
if (changed) callback.mergeLocalProperties(p);
} }
private void tryToClose(@Nullable StreamConnectionNotifier ss) { private void acceptContactConnections() {
try {
if (ss != null) ss.close();
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally {
callback.transportDisabled();
}
}
private void acceptContactConnections(StreamConnectionNotifier ss) {
while (true) { while (true) {
StreamConnection s; DuplexTransportConnection conn;
try { try {
s = ss.acceptAndOpen(); conn = acceptConnection(socket);
} catch (IOException e) { } catch (IOException e) {
// This is expected when the socket is closed // This is expected when the socket is closed
if (LOG.isLoggable(INFO)) LOG.info(e.toString()); if (LOG.isLoggable(INFO)) LOG.info(e.toString());
return; return;
} }
backoff.reset(); backoff.reset();
callback.incomingConnectionCreated(wrapSocket(s)); callback.incomingConnectionCreated(conn);
if (!running) return; if (!running) return;
} }
} }
private DuplexTransportConnection wrapSocket(StreamConnection s) {
return new BluetoothTransportConnection(this, s);
}
@Override @Override
public void stop() { public void stop() {
running = false; running = false;
tryToClose(socket); tryToClose(socket);
callback.transportDisabled();
disableAdapterIfEnabledByUs();
} }
@Override @Override
public boolean isRunning() { public boolean isRunning() {
return running; return running && isAdapterEnabled();
} }
@Override @Override
@@ -205,7 +243,7 @@ class BluetoothPlugin implements DuplexPlugin {
@Override @Override
public void poll(Collection<ContactId> connected) { public void poll(Collection<ContactId> connected) {
if (!running) return; if (!isRunning() || !shouldAllowContactConnections()) return;
backoff.increment(); backoff.increment();
// Try to connect to known devices in parallel // Try to connect to known devices in parallel
Map<ContactId, TransportProperties> remote = Map<ContactId, TransportProperties> remote =
@@ -218,41 +256,56 @@ class BluetoothPlugin implements DuplexPlugin {
String uuid = e.getValue().get(PROP_UUID); String uuid = e.getValue().get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) continue; if (StringUtils.isNullOrEmpty(uuid)) continue;
ioExecutor.execute(() -> { ioExecutor.execute(() -> {
if (!running) return; if (!isRunning() || !shouldAllowContactConnections()) return;
StreamConnection s = connect(makeUrl(address, uuid)); DuplexTransportConnection conn = connect(address, uuid);
if (s != null) { if (conn != null) {
backoff.reset(); backoff.reset();
callback.outgoingConnectionCreated(c, wrapSocket(s)); callback.outgoingConnectionCreated(c, conn);
} }
}); });
} }
} }
@Nullable @Nullable
private StreamConnection connect(String url) { private DuplexTransportConnection connect(String address, String uuid) {
if (LOG.isLoggable(INFO)) LOG.info("Connecting to " + url); // Validate the address
if (!isValidAddress(address)) {
if (LOG.isLoggable(WARNING))
// Not scrubbing here to be able to figure out the problem
LOG.warning("Invalid address " + address);
return null;
}
// Validate the UUID
try { try {
StreamConnection s = (StreamConnection) Connector.open(url); //noinspection ResultOfMethodCallIgnored
if (LOG.isLoggable(INFO)) LOG.info("Connected to " + url); UUID.fromString(uuid);
return s; } catch (IllegalArgumentException e) {
if (LOG.isLoggable(WARNING)) LOG.warning("Invalid UUID " + uuid);
return null;
}
if (LOG.isLoggable(INFO))
LOG.info("Connecting to " + scrubMacAddress(address));
try {
DuplexTransportConnection conn = connectTo(address, uuid);
if (LOG.isLoggable(INFO))
LOG.info("Connected to " + scrubMacAddress(address));
return conn;
} catch (IOException e) { } catch (IOException e) {
if (LOG.isLoggable(INFO)) LOG.info("Could not connect to " + url); if (LOG.isLoggable(INFO))
LOG.info("Could not connect to " + scrubMacAddress(address));
return null; return null;
} }
} }
@Override @Override
public DuplexTransportConnection createConnection(ContactId c) { public DuplexTransportConnection createConnection(ContactId c) {
if (!running) return null; if (!isRunning() || !shouldAllowContactConnections()) return null;
TransportProperties p = callback.getRemoteProperties(c); TransportProperties p = callback.getRemoteProperties(c);
String address = p.get(PROP_ADDRESS); String address = p.get(PROP_ADDRESS);
if (StringUtils.isNullOrEmpty(address)) return null; if (StringUtils.isNullOrEmpty(address)) return null;
String uuid = p.get(PROP_UUID); String uuid = p.get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) return null; if (StringUtils.isNullOrEmpty(uuid)) return null;
String url = makeUrl(address, uuid); return connect(address, uuid);
StreamConnection s = connect(url);
if (s == null) return null;
return new BluetoothTransportConnection(this, s);
} }
@Override @Override
@@ -262,35 +315,34 @@ class BluetoothPlugin implements DuplexPlugin {
@Override @Override
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) { public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
if (!running) return null; if (!isRunning()) return null;
// There's no point listening if we can't discover our own address
String address = getBluetoothAddress();
if (address == null) return null;
// No truncation necessary because COMMIT_LENGTH = 16 // No truncation necessary because COMMIT_LENGTH = 16
String uuid = UUID.nameUUIDFromBytes(commitment).toString(); String uuid = UUID.nameUUIDFromBytes(commitment).toString();
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid); if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
String url = makeUrl("localhost", uuid); // Bind a server socket for receiving key agreement connections
// Make the device discoverable if possible SS ss;
makeDeviceDiscoverable();
// Bind a server socket for receiving key agreementconnections
StreamConnectionNotifier ss;
try { try {
ss = (StreamConnectionNotifier) Connector.open(url); ss = openServerSocket(uuid);
} catch (IOException e) { } catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return null; return null;
} }
if (!running) { if (!isRunning()) {
tryToClose(ss); tryToClose(ss);
return null; return null;
} }
BdfList descriptor = new BdfList(); BdfList descriptor = new BdfList();
descriptor.add(TRANSPORT_ID_BLUETOOTH); descriptor.add(TRANSPORT_ID_BLUETOOTH);
String address = localDevice.getBluetoothAddress();
descriptor.add(StringUtils.macToBytes(address)); descriptor.add(StringUtils.macToBytes(address));
return new BluetoothKeyAgreementListener(descriptor, ss); return new BluetoothKeyAgreementListener(descriptor, ss);
} }
@Override @Override
public DuplexTransportConnection createKeyAgreementConnection( public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor, long timeout) { byte[] commitment, BdfList descriptor) {
if (!isRunning()) return null; if (!isRunning()) return null;
String address; String address;
try { try {
@@ -303,10 +355,7 @@ class BluetoothPlugin implements DuplexPlugin {
String uuid = UUID.nameUUIDFromBytes(commitment).toString(); String uuid = UUID.nameUUIDFromBytes(commitment).toString();
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Connecting to key agreement UUID " + uuid); LOG.info("Connecting to key agreement UUID " + uuid);
String url = makeUrl(address, uuid); return connect(address, uuid);
StreamConnection s = connect(url);
if (s == null) return null;
return new BluetoothTransportConnection(this, s);
} }
private String parseAddress(BdfList descriptor) throws FormatException { private String parseAddress(BdfList descriptor) throws FormatException {
@@ -315,44 +364,56 @@ class BluetoothPlugin implements DuplexPlugin {
return StringUtils.macToString(mac); return StringUtils.macToString(mac);
} }
private void makeDeviceDiscoverable() { @Override
// Try to make the device discoverable (requires root on Linux) public void eventOccurred(Event e) {
try { if (e instanceof EnableBluetoothEvent) {
localDevice.setDiscoverable(GIAC); ioExecutor.execute(this::enableAdapter);
} catch (BluetoothStateException e) { } else if (e instanceof DisableBluetoothEvent) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); ioExecutor.execute(this::disableAdapterIfEnabledByUs);
} else if (e instanceof BluetoothEnabledEvent) {
setEnabledByUs();
} else if (e instanceof SettingsUpdatedEvent) {
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
if (s.getNamespace().equals(ID.getString()))
ioExecutor.execute(this::onSettingsUpdated);
}
}
private void onSettingsUpdated() {
boolean wasAllowed = shouldAllowContactConnections();
loadSettings();
boolean isAllowed = shouldAllowContactConnections();
if (wasAllowed && !isAllowed) {
LOG.info("Contact connections disabled");
tryToClose(socket);
callback.transportDisabled();
disableAdapterIfEnabledByUs();
} else if (!wasAllowed && isAllowed) {
LOG.info("Contact connections enabled");
if (isAdapterEnabled()) bind();
else enableAdapter();
} }
} }
private class BluetoothKeyAgreementListener extends KeyAgreementListener { private class BluetoothKeyAgreementListener extends KeyAgreementListener {
private final StreamConnectionNotifier ss; private final SS ss;
private BluetoothKeyAgreementListener(BdfList descriptor, private BluetoothKeyAgreementListener(BdfList descriptor, SS ss) {
StreamConnectionNotifier ss) {
super(descriptor); super(descriptor);
this.ss = ss; this.ss = ss;
} }
@Override @Override
public Callable<KeyAgreementConnection> listen() { public KeyAgreementConnection accept() throws IOException {
return () -> { DuplexTransportConnection conn = acceptConnection(ss);
StreamConnection s = ss.acceptAndOpen(); if (LOG.isLoggable(INFO)) LOG.info(ID + ": Incoming connection");
if (LOG.isLoggable(INFO)) return new KeyAgreementConnection(conn, ID);
LOG.info(ID.getString() + ": Incoming connection");
return new KeyAgreementConnection(
new BluetoothTransportConnection(
BluetoothPlugin.this, s), ID);
};
} }
@Override @Override
public void close() { public void close() {
try { tryToClose(ss);
ss.close();
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
} }
} }
} }

View File

@@ -25,7 +25,6 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -224,7 +223,7 @@ class LanTcpPlugin extends TcpPlugin {
@Override @Override
public DuplexTransportConnection createKeyAgreementConnection( public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor, long timeout) { byte[] commitment, BdfList descriptor) {
if (!isRunning()) return null; if (!isRunning()) return null;
InetSocketAddress remote; InetSocketAddress remote;
try { try {
@@ -283,14 +282,11 @@ class LanTcpPlugin extends TcpPlugin {
} }
@Override @Override
public Callable<KeyAgreementConnection> listen() { public KeyAgreementConnection accept() throws IOException {
return () -> { Socket s = ss.accept();
Socket s = ss.accept(); if (LOG.isLoggable(INFO)) LOG.info(ID + ": Incoming connection");
if (LOG.isLoggable(INFO)) return new KeyAgreementConnection(new TcpTransportConnection(
LOG.info(ID.getString() + ": Incoming connection"); LanTcpPlugin.this, s), ID);
return new KeyAgreementConnection(
new TcpTransportConnection(LanTcpPlugin.this, s), ID);
};
} }
@Override @Override

View File

@@ -297,7 +297,7 @@ abstract class TcpPlugin implements DuplexPlugin {
@Override @Override
public DuplexTransportConnection createKeyAgreementConnection( public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, BdfList descriptor, long timeout) { byte[] commitment, BdfList descriptor) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }

View File

@@ -58,7 +58,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
this.metadataParser = metadataParser; this.metadataParser = metadataParser;
this.contactGroupFactory = contactGroupFactory; this.contactGroupFactory = contactGroupFactory;
this.clock = clock; this.clock = clock;
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID); localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
CLIENT_VERSION);
} }
@Override @Override
@@ -130,8 +131,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
public Map<TransportId, TransportProperties> getLocalProperties() public Map<TransportId, TransportProperties> getLocalProperties()
throws DbException { throws DbException {
Map<TransportId, TransportProperties> local; Map<TransportId, TransportProperties> local;
// TODO: Transaction can be read-only when code is simplified Transaction txn = db.startTransaction(true);
Transaction txn = db.startTransaction(false);
try { try {
local = getLocalProperties(txn); local = getLocalProperties(txn);
db.commitTransaction(txn); db.commitTransaction(txn);
@@ -166,8 +166,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
throws DbException { throws DbException {
try { try {
TransportProperties p = null; TransportProperties p = null;
// TODO: Transaction can be read-only when code is simplified Transaction txn = db.startTransaction(true);
Transaction txn = db.startTransaction(false);
try { try {
// Find the latest local update // Find the latest local update
LatestUpdate latest = findLatest(txn, localGroup.getId(), t, LatestUpdate latest = findLatest(txn, localGroup.getId(), t,
@@ -193,8 +192,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
public Map<ContactId, TransportProperties> getRemoteProperties( public Map<ContactId, TransportProperties> getRemoteProperties(
TransportId t) throws DbException { TransportId t) throws DbException {
Map<ContactId, TransportProperties> remote = new HashMap<>(); Map<ContactId, TransportProperties> remote = new HashMap<>();
// TODO: Transaction can be read-only when code is simplified Transaction txn = db.startTransaction(true);
Transaction txn = db.startTransaction(false);
try { try {
for (Contact c : db.getContacts(txn)) for (Contact c : db.getContacts(txn))
remote.put(c.getId(), getRemoteProperties(txn, c, t)); remote.put(c.getId(), getRemoteProperties(txn, c, t));
@@ -228,8 +226,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
public TransportProperties getRemoteProperties(ContactId c, TransportId t) public TransportProperties getRemoteProperties(ContactId c, TransportId t)
throws DbException { throws DbException {
TransportProperties p; TransportProperties p;
// TODO: Transaction can be read-only when code is simplified Transaction txn = db.startTransaction(true);
Transaction txn = db.startTransaction(false);
try { try {
p = getRemoteProperties(txn, db.getContact(txn, c), t); p = getRemoteProperties(txn, db.getContact(txn, c), t);
db.commitTransaction(txn); db.commitTransaction(txn);
@@ -292,7 +289,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
} }
private Group getContactGroup(Contact c) { 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, private void storeMessage(Transaction txn, GroupId g, TransportId t,
@@ -319,7 +317,6 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
private Map<TransportId, LatestUpdate> findLatestLocal(Transaction txn) private Map<TransportId, LatestUpdate> findLatestLocal(Transaction txn)
throws DbException, FormatException { throws DbException, FormatException {
// TODO: This can be simplified before 1.0
Map<TransportId, LatestUpdate> latestUpdates = new HashMap<>(); Map<TransportId, LatestUpdate> latestUpdates = new HashMap<>();
Map<MessageId, BdfDictionary> metadata = clientHelper Map<MessageId, BdfDictionary> metadata = clientHelper
.getMessageMetadataAsDictionary(txn, localGroup.getId()); .getMessageMetadataAsDictionary(txn, localGroup.getId());
@@ -327,17 +324,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
BdfDictionary meta = e.getValue(); BdfDictionary meta = e.getValue();
TransportId t = new TransportId(meta.getString("transportId")); TransportId t = new TransportId(meta.getString("transportId"));
long version = meta.getLong("version"); long version = meta.getLong("version");
LatestUpdate latest = latestUpdates.get(t); latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
if (latest == null) {
latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
} else if (version > latest.version) {
// This update is newer - delete the previous one
db.removeMessage(txn, latest.messageId);
latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
} else {
// We've already found a newer update - delete this one
db.removeMessage(txn, e.getKey());
}
} }
return latestUpdates; return latestUpdates;
} }
@@ -345,38 +332,16 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
@Nullable @Nullable
private LatestUpdate findLatest(Transaction txn, GroupId g, TransportId t, private LatestUpdate findLatest(Transaction txn, GroupId g, TransportId t,
boolean local) throws DbException, FormatException { boolean local) throws DbException, FormatException {
// TODO: This can be simplified before 1.0
LatestUpdate latest = null;
Map<MessageId, BdfDictionary> metadata = Map<MessageId, BdfDictionary> metadata =
clientHelper.getMessageMetadataAsDictionary(txn, g); clientHelper.getMessageMetadataAsDictionary(txn, g);
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) { for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
BdfDictionary meta = e.getValue(); BdfDictionary meta = e.getValue();
if (meta.getString("transportId").equals(t.getString()) if (meta.getString("transportId").equals(t.getString())
&& meta.getBoolean("local") == local) { && meta.getBoolean("local") == local) {
long version = meta.getLong("version"); return new LatestUpdate(e.getKey(), 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 latest; return null;
} }
private TransportProperties parseProperties(BdfList message) 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.EventBus;
import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent; import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Ack; import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.Offer; import org.briarproject.bramble.api.sync.Offer;
@@ -30,6 +30,7 @@ import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
@@ -37,6 +38,7 @@ import javax.annotation.concurrent.ThreadSafe;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.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_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
@@ -50,13 +52,14 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD
@NotNullByDefault @NotNullByDefault
class DuplexOutgoingSession implements SyncSession, EventListener { class DuplexOutgoingSession implements SyncSession, EventListener {
// Check for retransmittable records once every 60 seconds
private static final int RETX_QUERY_INTERVAL = 60 * 1000;
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(DuplexOutgoingSession.class.getName()); Logger.getLogger(DuplexOutgoingSession.class.getName());
private static final ThrowingRunnable<IOException> CLOSE = () -> { private static final ThrowingRunnable<IOException> CLOSE = () -> {
}; };
private static final ThrowingRunnable<IOException>
NEXT_SEND_TIME_DECREASED = () -> {
};
private final DatabaseComponent db; private final DatabaseComponent db;
private final Executor dbExecutor; private final Executor dbExecutor;
@@ -72,6 +75,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
private final AtomicBoolean generateOfferQueued = new AtomicBoolean(false); private final AtomicBoolean generateOfferQueued = new AtomicBoolean(false);
private final AtomicBoolean generateRequestQueued = private final AtomicBoolean generateRequestQueued =
new AtomicBoolean(false); new AtomicBoolean(false);
private final AtomicLong nextSendTime = new AtomicLong(Long.MAX_VALUE);
private volatile boolean interrupted = false; private volatile boolean interrupted = false;
@@ -101,15 +105,15 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
generateRequest(); generateRequest();
long now = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
long nextKeepalive = now + maxIdleTime; long nextKeepalive = now + maxIdleTime;
long nextRetxQuery = now + RETX_QUERY_INTERVAL;
boolean dataToFlush = true; boolean dataToFlush = true;
// Write records until interrupted // Write records until interrupted
try { try {
while (!interrupted) { while (!interrupted) {
// Work out how long we should wait for a record // Work out how long we should wait for a record
now = clock.currentTimeMillis(); now = clock.currentTimeMillis();
long wait = Math.min(nextKeepalive, nextRetxQuery) - now; long keepaliveWait = Math.max(0, nextKeepalive - now);
if (wait < 0) wait = 0; long sendWait = Math.max(0, nextSendTime.get() - now);
long wait = Math.min(keepaliveWait, sendWait);
// Flush any unflushed data if we're going to wait // Flush any unflushed data if we're going to wait
if (wait > 0 && dataToFlush && writerTasks.isEmpty()) { if (wait > 0 && dataToFlush && writerTasks.isEmpty()) {
recordWriter.flush(); recordWriter.flush();
@@ -121,20 +125,25 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
MILLISECONDS); MILLISECONDS);
if (task == null) { if (task == null) {
now = clock.currentTimeMillis(); now = clock.currentTimeMillis();
if (now >= nextRetxQuery) { if (now >= nextSendTime.get()) {
// Check for retransmittable records // Check for retransmittable messages
LOG.info("Checking for retransmittable messages");
setNextSendTime(Long.MAX_VALUE);
generateBatch(); generateBatch();
generateOffer(); generateOffer();
nextRetxQuery = now + RETX_QUERY_INTERVAL;
} }
if (now >= nextKeepalive) { if (now >= nextKeepalive) {
// Flush the stream to keep it alive // Flush the stream to keep it alive
LOG.info("Sending keepalive");
recordWriter.flush(); recordWriter.flush();
dataToFlush = false; dataToFlush = false;
nextKeepalive = now + maxIdleTime; nextKeepalive = now + maxIdleTime;
} }
} else if (task == CLOSE) { } else if (task == CLOSE) {
LOG.info("Closed");
break; break;
} else if (task == NEXT_SEND_TIME_DECREASED) {
LOG.info("Next send time decreased");
} else { } else {
task.run(); task.run();
dataToFlush = true; dataToFlush = true;
@@ -170,6 +179,11 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
dbExecutor.execute(new GenerateRequest()); dbExecutor.execute(new GenerateRequest());
} }
private void setNextSendTime(long time) {
long old = nextSendTime.getAndSet(time);
if (time < old) writerTasks.add(NEXT_SEND_TIME_DECREASED);
}
@Override @Override
public void interrupt() { public void interrupt() {
interrupted = true; interrupted = true;
@@ -196,8 +210,9 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
} else if (e instanceof MessageToRequestEvent) { } else if (e instanceof MessageToRequestEvent) {
if (((MessageToRequestEvent) e).getContactId().equals(contactId)) if (((MessageToRequestEvent) e).getContactId().equals(contactId))
generateRequest(); generateRequest();
} else if (e instanceof ShutdownEvent) { } else if (e instanceof LifecycleEvent) {
interrupt(); LifecycleEvent l = (LifecycleEvent) e;
if (l.getLifecycleState() == STOPPING) interrupt();
} }
} }
@@ -259,6 +274,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
try { try {
b = db.generateRequestedBatch(txn, contactId, b = db.generateRequestedBatch(txn, contactId,
MAX_RECORD_PAYLOAD_LENGTH, maxLatency); MAX_RECORD_PAYLOAD_LENGTH, maxLatency);
setNextSendTime(db.getNextSendTime(txn, contactId));
db.commitTransaction(txn); db.commitTransaction(txn);
} finally { } finally {
db.endTransaction(txn); db.endTransaction(txn);
@@ -305,6 +321,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
try { try {
o = db.generateOffer(txn, contactId, MAX_MESSAGE_IDS, o = db.generateOffer(txn, contactId, MAX_MESSAGE_IDS,
maxLatency); maxLatency);
setNextSendTime(db.getNextSendTime(txn, contactId));
db.commitTransaction(txn); db.commitTransaction(txn);
} finally { } finally {
db.endTransaction(txn); db.endTransaction(txn);

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.Group;
import org.briarproject.bramble.api.sync.GroupFactory; import org.briarproject.bramble.api.sync.GroupFactory;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.util.ByteUtils;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.bramble.api.sync.GroupId.LABEL;
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class GroupFactoryImpl implements GroupFactory { class GroupFactoryImpl implements GroupFactory {
@@ -23,9 +28,12 @@ class GroupFactoryImpl implements GroupFactory {
} }
@Override @Override
public Group createGroup(ClientId c, byte[] descriptor) { public Group createGroup(ClientId c, int clientVersion, byte[] descriptor) {
byte[] hash = crypto.hash(GroupId.LABEL, byte[] clientVersionBytes = new byte[INT_32_BYTES];
StringUtils.toUtf8(c.getString()), descriptor); 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); 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.EventBus;
import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent; import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Ack; import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
@@ -27,6 +27,7 @@ import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
/** /**
* An incoming {@link SyncSession}. * An incoming {@link SyncSession}.
@@ -96,8 +97,9 @@ class IncomingSession implements SyncSession, EventListener {
if (e instanceof ContactRemovedEvent) { if (e instanceof ContactRemovedEvent) {
ContactRemovedEvent c = (ContactRemovedEvent) e; ContactRemovedEvent c = (ContactRemovedEvent) e;
if (c.getContactId().equals(contactId)) interrupt(); if (c.getContactId().equals(contactId)) interrupt();
} else if (e instanceof ShutdownEvent) { } else if (e instanceof LifecycleEvent) {
interrupt(); LifecycleEvent l = (LifecycleEvent) e;
if (l.getLifecycleState() == STOPPING) interrupt();
} }
} }

View File

@@ -12,8 +12,10 @@ import org.briarproject.bramble.util.ByteUtils;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.bramble.api.sync.MessageId.LABEL;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH; import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
@@ -32,9 +34,9 @@ class MessageFactoryImpl implements MessageFactory {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
byte[] timeBytes = new byte[ByteUtils.INT_64_BYTES]; byte[] timeBytes = new byte[ByteUtils.INT_64_BYTES];
ByteUtils.writeUint64(timestamp, timeBytes, 0); ByteUtils.writeUint64(timestamp, timeBytes, 0);
byte[] idHash = byte[] hash = crypto.hash(LABEL, new byte[] {PROTOCOL_VERSION},
crypto.hash(MessageId.LABEL, g.getBytes(), timeBytes, body); g.getBytes(), timeBytes, body);
MessageId id = new MessageId(idHash); MessageId id = new MessageId(hash);
byte[] raw = new byte[MESSAGE_HEADER_LENGTH + body.length]; byte[] raw = new byte[MESSAGE_HEADER_LENGTH + body.length];
System.arraycopy(g.getBytes(), 0, raw, 0, UniqueId.LENGTH); System.arraycopy(g.getBytes(), 0, raw, 0, UniqueId.LENGTH);
ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH); ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH);

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
package org.briarproject.bramble.transport; 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.DatabaseComponent;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -20,17 +20,18 @@ class TransportKeyManagerFactoryImpl implements
TransportKeyManagerFactory { TransportKeyManagerFactory {
private final DatabaseComponent db; private final DatabaseComponent db;
private final CryptoComponent crypto; private final TransportCrypto transportCrypto;
private final Executor dbExecutor; private final Executor dbExecutor;
private final ScheduledExecutorService scheduler; private final ScheduledExecutorService scheduler;
private final Clock clock; private final Clock clock;
@Inject @Inject
TransportKeyManagerFactoryImpl(DatabaseComponent db, CryptoComponent crypto, TransportKeyManagerFactoryImpl(DatabaseComponent db,
TransportCrypto transportCrypto,
@DatabaseExecutor Executor dbExecutor, @DatabaseExecutor Executor dbExecutor,
@Scheduler ScheduledExecutorService scheduler, Clock clock) { @Scheduler ScheduledExecutorService scheduler, Clock clock) {
this.db = db; this.db = db;
this.crypto = crypto; this.transportCrypto = transportCrypto;
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
this.scheduler = scheduler; this.scheduler = scheduler;
this.clock = clock; this.clock = clock;
@@ -39,8 +40,8 @@ class TransportKeyManagerFactoryImpl implements
@Override @Override
public TransportKeyManager createTransportKeyManager( public TransportKeyManager createTransportKeyManager(
TransportId transportId, long maxLatency) { TransportId transportId, long maxLatency) {
return new TransportKeyManagerImpl(db, crypto, dbExecutor, scheduler, return new TransportKeyManagerImpl(db, transportCrypto, dbExecutor,
clock, transportId, maxLatency); scheduler, clock, transportId, maxLatency);
} }
} }

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