Compare commits

..

340 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
48918f4727 Bumped version numbers for beta release. 2017-11-30 13:35:43 +00:00
akwizgran
303b5bd395 Merge branch 'target-sdk-26' into 'master'
Target API version 26, upgrade support library

See merge request !626
2017-11-29 17:38:12 +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
97733a52c8 Updated translations. 2017-11-23 17:03:15 +00:00
akwizgran
89dcbec599 Upgrade Gradle plugin to 3.0.1. 2017-11-23 17:01:16 +00:00
akwizgran
6497809fe1 Merge branch '1103-dont-ask-again-doze' into 'master'
Show Doze Mode Warning with Don't Ask Again Option

Closes #1103

See merge request !625
2017-11-23 16:23:39 +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
akwizgran
9f3a63d8c4 Don't unregister receiver unless it was registered. 2017-11-22 11:37:58 +00:00
akwizgran
748fa77d94 Move doze receiver out of BriarService. 2017-11-22 11:07:28 +00:00
Torsten Grote
4ca86ee4eb Address review comments 2017-11-21 16:01:07 -02:00
Torsten Grote
ec2f372933 Remember that app entered doze mode and inform user when returning 2017-11-21 15:55:00 -02:00
Torsten Grote
4267800db2 Allow Account Creation without Doze White-listing 2017-11-21 15:55:00 -02:00
Torsten Grote
bb8cb9bcbb Show Doze Dialog only after startup and provide "don't ask again" option 2017-11-21 15:54:59 -02:00
akwizgran
d5b9e15ee1 Bump compileSdkVersion to match support library. 2017-11-21 17:33:40 +00:00
akwizgran
43ee3246f6 Remove redundant casts from findViewById. 2017-11-21 17:29:21 +00:00
akwizgran
b56724dee5 Set target SDK version to 26, upgrade support library. 2017-11-21 17:29:21 +00:00
akwizgran
92748ac872 Accept build tools license for CI. 2017-11-21 17:28:11 +00:00
akwizgran
b89686c287 Merge branch 'upgrade-gradle-witness' into 'master'
Upgrade Gradle Witness

See merge request !623
2017-11-21 17:11:06 +00:00
akwizgran
a34692630b Use testImplementation for Mockito. 2017-11-21 17:03:38 +00:00
akwizgran
735208562a Use java-library plugin for Java modules. 2017-11-21 16:35:08 +00:00
akwizgran
49826fdc56 Use new Gradle configurations for Android modules. 2017-11-21 16:35:08 +00:00
akwizgran
e8c54a609c Upgrade Gradle Witness. 2017-11-21 16:35:03 +00:00
akwizgran
ece2c51358 A few more Java 8 changes in merged code. 2017-11-21 16:21:15 +00:00
akwizgran
3ec8af4661 Merge branch 'use-java-8-language-features' into 'master'
Use java 8 language features

See merge request !621
2017-11-21 15:22:52 +00:00
Torsten Grote
77a08596fe Merge branch '764-bdf-list-dictionary-not-thread-safe' into 'master'
BdfList and BdfDictionary don't need to be thread-safe

Closes #764

See merge request !614
2017-11-21 13:00:23 +00:00
akwizgran
879f699b2b A few more lambdas. 2017-11-21 10:51:37 -02:00
akwizgran
d7383a3361 Effectively final. 2017-11-21 10:51:35 -02:00
akwizgran
a5b321a93b Multi-catch. 2017-11-21 10:49:10 -02:00
akwizgran
5fa6b0ca1c Lambdas. 2017-11-21 10:49:08 -02:00
akwizgran
27328afe3c Diamond operators. 2017-11-21 10:45:47 -02:00
Torsten Grote
2d26af1ae2 Merge branch 'java-8-language-features' into 'master'
Support Java 8 language features

See merge request !620
2017-11-21 12:09:27 +00:00
Torsten Grote
6db8f33e8f Merge branch 'log-network-usage' into 'master'
Log network usage at shutdown

See merge request !616
2017-11-21 11:45:42 +00:00
akwizgran
d6a7e6d52c Resolve merge conflicts.
# Conflicts:
#   briar-android/build.gradle
#   briar-android/src/test/java/org/briarproject/briar/android/login/SetupActivityTest.java
2017-11-21 10:27:31 +00:00
akwizgran
df99b3b666 Merge branch '1085-startup-wizard' into 'master'
Setup Wizard that asks for Doze Mode exception

Closes #1085 and #1018

See merge request !603
2017-11-21 09:40:10 +00:00
akwizgran
0f1c9f4fe2 Refactored tests for account setup and changing password. 2017-11-20 14:11:31 -02:00
Torsten Grote
5dcd5f79dc Test PasswordFragment account creation individually 2017-11-20 11:52:06 -02:00
Torsten Grote
8a81171739 Setup Wizard that asks for Doze Mode exception
Keep checking if we are whitelisted and request it if not
2017-11-20 11:52:05 -02:00
akwizgran
1c4f20f76f Merge branch 'simply-build-gradle' into 'master'
Simply bramble-androids's build.gradle

See merge request !622
2017-11-17 16:11:00 +00:00
goapunk
f84fa588f6 simply bramble-androids's build.gradle
Signed-off-by: goapunk <noobie@goapunks.net>
2017-11-17 16:43:07 +01:00
akwizgran
e30e34f342 Include java.lang.invoke classes in bootstrap classpath. 2017-11-16 15:26:05 +00:00
akwizgran
fc93ced067 Download the Android support repository for CI. 2017-11-16 12:54:57 +00:00
akwizgran
bb7df72d31 Compile against OpenJDK 6 standard library for CI. 2017-11-16 12:54:50 +00:00
akwizgran
f8425658e4 Support Java 8 language features in Java modules. 2017-11-16 11:46:35 +00:00
akwizgran
53c8cf09b6 Support Java 8 language features in Android modules. 2017-11-16 11:46:34 +00:00
akwizgran
9f29bf4949 Upgrade Gradle and Android Gradle plugin 2017-11-16 11:46:32 +00:00
akwizgran
98e2adf794 Fix Dagger setup, remove android-apt plugin. 2017-11-16 11:46:02 +00:00
Torsten Grote
2a43e0b0ed Merge branch '545-simple-db-indexes' into 'master'
Add some simple indexes to the DB

See merge request !618
2017-11-09 12:10:07 +00:00
akwizgran
773ae73820 Updated translations. 2017-11-09 12:05:21 +00:00
akwizgran
009db57bc5 Merge branch '482-delete-old-transport-property-updates' into 'master'
Delete old transport property updates

Closes #482

See merge request !617
2017-11-09 11:59:00 +00:00
akwizgran
5e98126e77 Completely remove old local updates from the database. 2017-11-09 10:58:51 +00:00
akwizgran
bd7ebfd83a Unit tests for TransportPropertyManagerImpl. 2017-11-08 16:44:26 +00:00
akwizgran
10f41ef157 Log network usage at shutdown. 2017-11-08 14:46:56 +00:00
akwizgran
1dd4960109 Transactions that delete old updates must be read-write. 2017-11-08 14:23:30 +00:00
akwizgran
75413b6c86 Delete old transport property updates.
Some of this code is only needed for backward compatibility - it can be removed when we break compatibility for 1.0.
2017-11-08 09:47:59 +00:00
akwizgran
b2180582a7 BdfList and BdfDictionary don't need to be thread-safe.
Same goes for Metadata.
2017-11-06 15:20:21 +00:00
akwizgran
8211ce7ae3 Add some simple indexes to the DB. 2017-11-03 15:06:34 +00:00
akwizgran
e6b1597fa7 Upgraded Gradle to 3.5. 2017-10-26 18:07:20 +01:00
akwizgran
8937d3cd9c Updated translations. 2017-10-24 17:01:11 +01:00
akwizgran
51f320d147 Merge branch '992-wake-lock-tag' into 'master'
Change wake lock tag

Closes #992 and #1087

See merge request !612
2017-10-24 13:36:26 +00:00
goapunk
e402a894bb Change wake lock tag
Signed-off-by: goapunk <noobie@goapunks.net>
2017-10-24 13:45:27 +02:00
Torsten Grote
9b577f1219 Merge branch 'remove-location-permission' into 'master'
Remove unused location permission

See merge request !611
2017-10-18 16:31:56 +00:00
akwizgran
220f678403 Removed unused location permission. 2017-10-18 14:05:11 +01:00
akwizgran
4173fc4daa Merge branch '1045-preference-divider' into 'master'
Don't use a custom widget to separate preference categories

Closes #1045

See merge request !609
2017-10-17 17:03:13 +00:00
Torsten Grote
c6756d2145 Merge branch 'gradle-plugin-2.3.3' into 'master'
Upgrade Gradle plugin and build tools

See merge request !610
2017-10-17 16:14:53 +00:00
akwizgran
6731f6eeb5 Added checksum for Gradle download. 2017-10-17 17:01:46 +01:00
akwizgran
6f7f8b40e3 Upgraded Gradle plugin and build tools. 2017-10-17 15:31:28 +01:00
akwizgran
1a83b2c99b Bumped version number for beta release. 2017-10-17 09:41:11 +01:00
akwizgran
f641fae1c7 Added new translations. 2017-10-16 17:10:53 +01:00
akwizgran
deb43d9872 Updated translations. 2017-10-16 17:08:07 +01:00
akwizgran
cee4e1305e Merge branch 'extend-expiry' into 'master'
Extend expiry and show a green snackbar about it once

See merge request !606
2017-10-12 17:03:26 +00:00
akwizgran
a1f989c43c Use black text for the expiry extension notice. 2017-10-12 17:51:57 +01:00
akwizgran
b67abadbac Use a setting to record whether update notice has been shown 2017-10-12 17:51:57 +01:00
Torsten Grote
8c29c85696 Extend expiry and show a green snackbar about it once 2017-10-12 17:51:57 +01:00
akwizgran
4fe4c298d7 Don't use a custom widget to separate preference categories. 2017-10-11 17:35:05 +01:00
akwizgran
13d35229d5 Merge branch '1091-reduce-polling-queries' into 'master'
Reduce number of DB queries used when polling for connections

Closes #1091

See merge request !604
2017-10-11 13:45:14 +00:00
Torsten Grote
f0137b41b6 Merge branch 'accept-sdk-license-agreement-for-ci' into 'master'
Accept build tools license agreement for CI runner

See merge request !607
2017-10-11 13:24:25 +00:00
akwizgran
b221d21903 Accept all SDK license agreements for CI runner. 2017-10-11 14:18:02 +01:00
Torsten Grote
8bac202626 Add Hindi, Finnish and Basque translations 2017-10-10 10:04:22 -03:00
Torsten Grote
973151c949 Merge branch 'report-bluetooth-and-wifi-support' into 'master'
Report Bluetooth LE and Wi-Fi Direct support in crash reports and feedback

See merge request !605
2017-10-10 12:16:29 +00:00
akwizgran
ed26ab78a5 Merge branch '158-permission-requests' into 'master'
Add permission requests for Android 6+

Closes #158

See merge request !601
2017-10-10 10:40:14 +00:00
akwizgran
8454b2d235 Code cleanup, shortened button text to help with layout. 2017-10-10 11:33:07 +01:00
akwizgran
91d0f89f60 Removed unused import. 2017-10-10 11:08:40 +01:00
akwizgran
e074672e86 Reduce DB queries for looking up transport properties. 2017-10-10 10:59:39 +01:00
akwizgran
6c1901fe5b Reduced DB queries when polling for LAN connections. 2017-10-09 15:20:03 +01:00
goapunk
49052be627 Add permission requests for Android 6+
* Add request for the camera

Signed-off-by: goapunk <noobie@goapunks.net>
2017-10-04 13:17:51 +02:00
Torsten Grote
5b5b540630 Merge branch '299-disable-bluetooth-at-shutdown' into 'master'
Disable Bluetooth at shutdown if we enabled it

See merge request !602
2017-10-03 15:38:22 +00:00
akwizgran
9993bac3a1 Disable Bluetooth at shutdown if we enabled it. 2017-10-03 15:59:07 +01:00
akwizgran
3c95988693 Merge branch '539-clear-notifications' into 'master'
Don't show dismissed notifications again when items are removed

Closes #539

See merge request !600
2017-10-02 14:46:54 +00:00
akwizgran
fc5c3b470e Merge branch 'patch-1' into 'master'
Contacts, on your side

See merge request !594
2017-10-02 13:14:00 +00:00
akwizgran
53f05a72ba Removed logging. 2017-09-29 15:31:25 +01:00
akwizgran
2c10ae7d06 Clear notifications when dismissed.
Also fixed an issue with notifications alerting again when items
were removed.
2017-09-29 15:23:27 +01:00
akwizgran
6b9010c557 Merge branch '703-create-test-data' into 'master'
Add an option to debug builds to create fake test data

Closes #703

See merge request !595
2017-09-28 10:37:03 +00:00
Torsten Grote
1bf0fdfa81 Add an option to debug builds to create fake test data 2017-09-27 13:55:29 -03:00
Torsten Grote
237759aac0 Add Simplified Chinese translation 2017-09-27 13:32:07 -03:00
akwizgran
2a141e0a97 Merge branch 'disableAaptCruncher' into 'master'
Disable PNG crunching for reproducibility

See merge request !596
2017-09-27 16:04:23 +00:00
akwizgran
d6900be68e Merge branch '1051-fix-pink' into 'master'
Fix pink navigation drawer items with current support library

Closes #1051

See merge request !598
2017-09-27 16:02:22 +00:00
Torsten Grote
a35d7c7204 Fix pink navigation drawer items with current support library 2017-09-27 12:09:06 -03:00
Torsten Grote
86287f9241 Merge branch 'spongy-castle-158' into 'master'
Upgrade Spongy Castle to 1.58

See merge request !597
2017-09-27 15:01:15 +00:00
akwizgran
0b2e3dd96f Upgrade Spongy Castle to 1.58. 2017-09-27 15:54:37 +01:00
Torsten Grote
90aa1d1ce7 Disable PNG crunching for reproducibility
This can help to prevent non-determinism introduced by the crunching
process.

More information:
e48f9f0773

With enabled and disabled crunching,
the size of the signed release APK was 17809681 bytes.

Related to #164
2017-09-27 11:35:25 -03:00
Michael Rogers
ef2286ab53 Bumped version number for beta release. 2017-09-20 14:51:10 +01:00
akwizgran
47b25f3221 Merge branch '1064-rss-date-npe' into 'master'
Fix NPE when some RSS items don't have dates and add test

Closes #1064

See merge request !591
2017-09-20 12:21:06 +00:00
Torsten Grote
c30bfa12ce Fix NPE when some RSS items don't have dates and add test 2017-09-20 09:11:06 -03:00
akwizgran
d0fc04251d Merge branch 'three-new-langs' into 'master'
Add Norwegian Bokmål, Occitan (post 1500) and Serbian

See merge request !593
2017-09-20 11:15:44 +00:00
akwizgran
dcbb41eb7a Merge branch '1069-forum-sharing-exception' into 'master'
Fix crash when sharing a forum while it was just shared with us

Closes #1069

See merge request !592
2017-09-20 11:14:20 +00:00
Allan Nordhøy
5c51259269 "Connection aborted!" no und 2017-09-19 19:39:57 +00:00
Allan Nordhøy
7eefa07052 Contact connections → contacts
by us → on your side
2017-09-19 18:56:22 +00:00
Torsten Grote
999bdf8866 Add Norwegian Bokmål, Occitan (post 1500) and Serbian 2017-09-19 14:47:39 -03:00
Torsten Grote
911c0c0fd9 Fix crash when sharing a forum while it was just shared with us 2017-09-19 14:30:57 -03:00
akwizgran
99d8cc64a6 Merge branch '1024-message-tree-npe' into 'master'
Don't add threaded messages to the UI before their parents

Closes #1024

See merge request !585
2017-09-19 15:37:58 +00:00
akwizgran
ba727d7568 Don't add threaded messages to the UI before their parents. 2017-09-19 16:31:27 +01:00
Torsten Grote
ed01048f9f Merge branch 'remove-old-bluetooth-code' into 'master'
Remove old Bluetooth code and location permission

See merge request !584
2017-09-19 14:16:13 +00:00
Torsten Grote
043ee3c58e Merge branch '1044-crash-when-setting-ringtone' into 'master'
Don't crash if the chosen ringtone can't be loaded

Closes #1044

See merge request !586
2017-09-19 13:11:44 +00:00
Torsten Grote
6e0af7deda Merge branch '1060-upgrade-tor' into 'master'
Upgrade Tor to 0.2.9.12

Closes #1060

See merge request !590
2017-09-19 12:14:55 +00:00
akwizgran
9591db2097 Upgrade Tor to 0.2.9.12.
Libevent 2.0.22-stable, OpenSSL 1.0.2l and GeoIP 2017-09-06.
2017-09-19 12:49:22 +01:00
akwizgran
329a4c64f6 Merge branch '1028-lost-reply-id' into 'master'
Keep the reply ID up to date in ThreadListActivity

Closes #1028

See merge request !587
2017-09-18 15:10:38 +00:00
Torsten Grote
79015bc5ae Merge branch '1042-catch-npe-when-getting-socket-streams' into 'master'
Catch NPE when getting socket input/output streams

Closes #1042

See merge request !589
2017-09-18 14:55:08 +00:00
akwizgran
27422ab9f9 Catch NPE when getting socket input/output streams.
Works around a bug in Android 7, fixed in 7.1.
2017-09-18 15:47:12 +01:00
Torsten Grote
abcb682498 Merge branch '1040-rss-feed-illegal-argument-exception' into 'master'
Catch IllegalArgumentException when parsing RSS feed

Closes #1040

See merge request !588
2017-09-18 14:38:22 +00:00
akwizgran
5044127c46 Catch IllegalArgumentException when parsing RSS feed. 2017-09-18 15:26:12 +01:00
akwizgran
0e4b8ca62e Keep the activity's reply ID up to date. 2017-09-18 15:13:16 +01:00
akwizgran
822017c69c Don't crash if the chosen ringtone can't be loaded. 2017-09-18 13:37:10 +01:00
akwizgran
eb6561b93d Updated translations for German, French and Russian. 2017-09-15 10:40:05 +01:00
akwizgran
eb9d0c00a8 Report Bluetooth LE and Wi-Fi Direct support. 2017-08-16 12:21:13 +01:00
Michael Rogers
d24b1884a2 Removed old Bluetooth code and the location permission it requires. 2017-08-11 12:42:47 +01:00
Michael Rogers
078534889e Bumped version number for beta release. 2017-08-04 15:16:51 +01:00
Torsten Grote
e92713006a Fix string in Spanish translation 2017-08-04 10:57:43 -03:00
akwizgran
18f43f3bc1 Merge branch '871-rss-feeds-lost' into 'master'
Fix bug where RSS feeds got lost when a fetching error occured

Closes #871

See merge request !583
2017-08-04 13:52:26 +00:00
akwizgran
a4118b40e1 Merge branch 'debug-build-alongside-beta' into 'master'
Make debug builds installable alongside official beta build

See merge request !582
2017-08-02 16:54:25 +00:00
Torsten Grote
de29fbc324 Fix bug where RSS feeds got lost when a fetching error occured 2017-08-01 15:32:51 -03:00
Torsten Grote
3197dcf9b5 Merge branch 'checked-camera-exceptions' into 'master'
Throw checked exceptions for camera errors

See merge request !580
2017-08-01 16:54:45 +00:00
akwizgran
35aad409fd Merge branch '994-notification-sound-delay' into 'master'
Always play a notification sound, if at least 2sec after last one

Closes #994

See merge request !581
2017-08-01 16:20:35 +00:00
Torsten Grote
08ce6a7331 Change app name for debug builds 2017-08-01 13:08:12 -03:00
Torsten Grote
33a0099065 Make debug builds installable alongside official beta build 2017-08-01 12:57:11 -03:00
Torsten Grote
34d20fafda Always play a notification sound, if at least 2sec after last one
This is the same behavior as Signal.
We might want to adjust the delay later on.

This is also introduces a new BriarNotificationBuilder as a first step
to clean up the Notification Manager code.
2017-08-01 12:47:11 -03:00
Michael Rogers
aafddcd0f0 Bumped version number for beta release (for real this time). 2017-08-01 16:43:47 +01:00
akwizgran
0d6983b4ef Throw checked exceptions for camera errors. 2017-08-01 15:56:20 +01:00
akwizgran
69bfb72171 Merge branch '1002-cam-get-params-npe' into 'master'
Catch RuntimeException when getting camera parameters

See merge request !579
2017-08-01 13:56:45 +00:00
Torsten Grote
1aa33ec9b2 Catch RuntimeException when getting camera parameters 2017-08-01 10:49:04 -03:00
akwizgran
6702df1e22 Merge branch '1008-qr-decoding-crash' into 'master'
Catch IllegalArgumentException when decoding QrCode

Closes #1008

See merge request !578
2017-08-01 13:36:09 +00:00
Torsten Grote
0ee6197d7f Catch IllegalArgumentException when decoding QrCode 2017-08-01 10:21:02 -03:00
602 changed files with 26027 additions and 17956 deletions

View File

@@ -6,9 +6,18 @@ cache:
- .gradle/caches - .gradle/caches
before_script: before_script:
- set -e
- export GRADLE_USER_HOME=$PWD/.gradle - export GRADLE_USER_HOME=$PWD/.gradle
# - export ANDROID_COMPILE_SDK=`sed -n 's,.*compileSdkVersion\s*\([0-9][0-9]*\).*,\1,p' app/build.gradle` # Accept the license for the Android build tools
# - echo y | android --silent update sdk --no-ui --filter android-${ANDROID_COMPILE_SDK} - echo y | /opt/android-sdk/tools/bin/sdkmanager "build-tools;26.0.2"
# Download OpenJDK 6 so we can compile against its standard library
- JDK_FILE=openjdk-6-jre-headless_6b38-1.13.10-1~deb7u1_amd64.deb
- if [ ! -d openjdk ]
- then
- wget -q http://ftp.uk.debian.org/debian/pool/main/o/openjdk-6/$JDK_FILE
- dpkg-deb -x $JDK_FILE openjdk
- fi
- export JAVA_6_HOME=$PWD/openjdk/usr/lib/jvm/java-6-openjdk-amd64
test: test:
script: script:

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

@@ -6,99 +6,91 @@ apply plugin: 'witness'
apply plugin: 'de.undercouch.download' apply plugin: 'de.undercouch.download'
android { android {
compileSdkVersion 23 compileSdkVersion 27
buildToolsVersion "23.0.3" buildToolsVersion '26.0.2'
defaultConfig { defaultConfig {
minSdkVersion 14 minSdkVersion 14
targetSdkVersion 22 targetSdkVersion 26
versionCode 14 versionCode 1700
versionName "0.14" versionName "0.17.0"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_7 targetCompatibility JavaVersion.VERSION_1_8
} }
} }
dependencies { dependencies {
compile project(':bramble-core') implementation project(path: ':bramble-core', configuration: 'default')
compile fileTree(dir: 'libs', include: '*.jar') implementation fileTree(dir: 'libs', include: '*.jar')
provided 'javax.annotation:jsr250-api:1.0'
annotationProcessor 'com.google.dagger:dagger-compiler:2.0.2'
compileOnly 'javax.annotation:jsr250-api:1.0'
} }
def torBinaryDir = 'src/main/res/raw' dependencyVerification {
verify = [
task downloadTorGeoIp(type: Download) { 'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
src 'https://briarproject.org/build/geoip-2017-05-02.zip' 'com.google.dagger:dagger-compiler:2.0.2:dagger-compiler-2.0.2.jar:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3',
dest "$torBinaryDir/geoip.zip" 'com.google.dagger:dagger-producers:2.0-beta:dagger-producers-2.0-beta.jar:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b',
onlyIfNewer true 'com.google.dagger:dagger:2.0.2:dagger-2.0.2.jar:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9',
'com.google.guava:guava:18.0:guava-18.0.jar:d664fbfc03d2e5ce9cab2a44fb01f1d0bf9dfebeccc1a473b1f9ea31f79f6f99',
'com.h2database:h2:1.4.192:h2-1.4.192.jar:225b22e9857235c46c93861410b60b8c81c10dc8985f4faf188985ba5445126c',
'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728',
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'net.i2p.crypto:eddsa:0.2.0:eddsa-0.2.0.jar:a7cb1b85c16e2f0730b9204106929a1d9aaae1df728adc7041a8b8b605692140',
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
'org.jacoco:org.jacoco.agent:0.7.4.201502262128:org.jacoco.agent-0.7.4.201502262128-runtime.jar:e357a0f1d573c2f702a273992b1b6cb661734f66311854efb3778a888515c5b5',
'org.jacoco:org.jacoco.agent:0.7.4.201502262128:org.jacoco.agent-0.7.4.201502262128.jar:47b4bec6df11a1118da3953da8b9fa1e7079d6fec857faa1a3cf912e53a6fd4e',
'org.jacoco:org.jacoco.ant:0.7.4.201502262128:org.jacoco.ant-0.7.4.201502262128.jar:013ce2a68ba57a3c59215ae0dec4df3498c078062a38c3b94c841fc14450f283',
'org.jacoco:org.jacoco.core:0.7.4.201502262128:org.jacoco.core-0.7.4.201502262128.jar:ec4c74554312fac5116350164786f91b35c9e082fa4ea598bfa42b5db05d7abb',
'org.jacoco:org.jacoco.report:0.7.4.201502262128:org.jacoco.report-0.7.4.201502262128.jar:7a3554c605e088e7e323b1084656243f0444fa353e2f2dee1f1a4204eb64ff09',
'org.ow2.asm:asm-debug-all:5.0.1:asm-debug-all-5.0.1.jar:4734de5b515a454b0096db6971fb068e5f70e6f10bbee2b3bd2fdfe5d978ed57',
]
} }
task downloadTorBinaryArm(type: Download) { ext.torBinaryDir = 'src/main/res/raw'
src 'https://briarproject.org/build/tor-0.2.9.11-arm.zip' ext.torVersion = '0.2.9.14'
dest "$torBinaryDir/tor_arm.zip" ext.geoipVersion = '2017-11-06'
onlyIfNewer true ext.torDownloadUrl = 'https://briarproject.org/build/'
def torBinaries = [
"tor_arm" : '1710ea6c47b7f4c1a88bdf4858c7893837635db10e8866854eed8d61629f50e8',
"tor_arm_pie": '974e6949507db8fa2ea45231817c2c3677ed4ccf5488a2252317d744b0be1917',
"tor_x86" : '3a5e45b3f051fcda9353b098b7086e762ffe7ba9242f7d7c8bf6523faaa8b1e9',
"tor_x86_pie": 'd1d96d8ce1a4b68accf04850185780d10cd5563d3552f7e1f040f8ca32cb4e51',
"geoip" : '8239b98374493529a29096e45fc5877d4d6fdad0146ad8380b291f90d61484ea'
]
def downloadBinary(name) {
return tasks.create("downloadBinary${name}", Download) {
src "${torDownloadUrl}${name}.zip"
.replace('tor_', "tor-${torVersion}-")
.replace('geoip', "geoip-${geoipVersion}")
.replaceAll('_', '-')
dest "${torBinaryDir}/${name}.zip"
onlyIfNewer true
}
} }
task downloadTorBinaryArmPie(type: Download) { def verifyBinary(name, chksum) {
src 'https://briarproject.org/build/tor-0.2.9.11-arm-pie.zip' return tasks.create([
dest "$torBinaryDir/tor_arm_pie.zip" name : "verifyBinary${name}",
onlyIfNewer true type : Verify,
} dependsOn: downloadBinary(name)]) {
src "${torBinaryDir}/${name}.zip"
task downloadTorBinaryX86(type: Download) { algorithm 'SHA-256'
src 'https://briarproject.org/build/tor-0.2.9.11-x86.zip' checksum chksum
dest "$torBinaryDir/tor_x86.zip" }
onlyIfNewer true
}
task downloadTorBinaryX86Pie(type: Download) {
src 'https://briarproject.org/build/tor-0.2.9.11-x86-pie.zip'
dest "$torBinaryDir/tor_x86_pie.zip"
onlyIfNewer true
}
task verifyTorGeoIp(type: Verify, dependsOn: 'downloadTorGeoIp') {
src "$torBinaryDir/geoip.zip"
algorithm 'SHA-256'
checksum '51f4d1272fb867e1f3b36b67a584e2a33c40b40f62305457d799fd399cd77c9b'
}
task verifyTorBinaryArm(type: Verify, dependsOn: 'downloadTorBinaryArm') {
src "$torBinaryDir/tor_arm.zip"
algorithm 'SHA-256'
checksum '1da6008663a8ad98b349e62acbbf42c379f65ec504fa467cb119c187cd5a4c6b'
}
task verifyTorBinaryArmPie(type: Verify, dependsOn: 'downloadTorBinaryArmPie') {
src "$torBinaryDir/tor_arm_pie.zip"
algorithm 'SHA-256'
checksum 'eb061f880829e05f104690ac744848133f2dacef04759d425a2cff0df32c271e'
}
task verifyTorBinaryX86(type: Verify, dependsOn: 'downloadTorBinaryX86') {
src "$torBinaryDir/tor_x86.zip"
algorithm 'SHA-256'
checksum 'f5308aff8303daca082f82227d02b51ddedba4ab1d1420739ada0427ae5dbb41'
}
task verifyTorBinaryX86Pie(type: Verify, dependsOn: 'downloadTorBinaryX86Pie') {
src "$torBinaryDir/tor_x86_pie.zip"
algorithm 'SHA-256'
checksum '889a6c81ac73d05d35ed610ca5a913cee44d333e4ae1749c2a107f2f7dd8197b'
} }
project.afterEvaluate { project.afterEvaluate {
preBuild.dependsOn { torBinaries.every { key, value ->
[ preBuild.dependsOn.add(verifyBinary(key, value))
'verifyTorGeoIp',
'verifyTorBinaryArm',
'verifyTorBinaryArmPie',
'verifyTorBinaryX86',
'verifyTorBinaryX86Pie'
]
} }
} }

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

@@ -11,8 +11,6 @@
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_LOGS"/> <uses-permission android:name="android.permission.READ_LOGS"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.WAKE_LOCK"/>
<!-- Since API 23, this is needed to add contacts via Bluetooth -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<application <application
android:allowBackup="false" android:allowBackup="false"

View File

@@ -13,7 +13,8 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.api.reporting.DevReporter; import org.briarproject.bramble.api.reporting.DevReporter;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.api.system.LocationUtils; import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.plugin.droidtooth.DroidtoothPluginFactory; import org.briarproject.bramble.api.system.Scheduler;
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;
@@ -22,6 +23,7 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import javax.net.SocketFactory; import javax.net.SocketFactory;
@@ -33,19 +35,21 @@ public class AndroidPluginModule {
@Provides @Provides
PluginConfig providePluginConfig(@IoExecutor Executor ioExecutor, PluginConfig providePluginConfig(@IoExecutor Executor ioExecutor,
@Scheduler ScheduledExecutorService scheduler,
AndroidExecutor androidExecutor, SecureRandom random, AndroidExecutor androidExecutor, SecureRandom random,
SocketFactory torSocketFactory, BackoffFactory backoffFactory, SocketFactory torSocketFactory, BackoffFactory backoffFactory,
Application app, LocationUtils locationUtils, DevReporter reporter, Application app, LocationUtils locationUtils, DevReporter reporter,
EventBus eventBus) { EventBus eventBus) {
Context appContext = app.getApplicationContext(); Context appContext = app.getApplicationContext();
DuplexPluginFactory bluetooth = new DroidtoothPluginFactory(ioExecutor, DuplexPluginFactory bluetooth =
androidExecutor, appContext, random, backoffFactory); new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor,
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, appContext, appContext, random, eventBus, backoffFactory);
locationUtils, reporter, eventBus, torSocketFactory, DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, scheduler,
backoffFactory); appContext, locationUtils, reporter, eventBus,
torSocketFactory, backoffFactory);
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor, DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
backoffFactory, appContext); backoffFactory, appContext);
final Collection<DuplexPluginFactory> duplex = Collection<DuplexPluginFactory> duplex =
Arrays.asList(bluetooth, tor, lan); Arrays.asList(bluetooth, tor, lan);
@NotNullByDefault @NotNullByDefault
PluginConfig pluginConfig = new PluginConfig() { PluginConfig pluginConfig = new PluginConfig() {

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,7 +1,8 @@
package org.briarproject.bramble.plugin.droidtooth; package org.briarproject.bramble.plugin.bluetooth;
import android.content.Context; import android.content.Context;
import org.briarproject.bramble.api.event.EventBus;
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;
import org.briarproject.bramble.api.plugin.BackoffFactory; import org.briarproject.bramble.api.plugin.BackoffFactory;
@@ -20,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
@@ -31,15 +32,18 @@ public class DroidtoothPluginFactory implements DuplexPluginFactory {
private final AndroidExecutor androidExecutor; private final AndroidExecutor androidExecutor;
private final Context appContext; private final Context appContext;
private final SecureRandom secureRandom; private final SecureRandom secureRandom;
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, BackoffFactory backoffFactory) { SecureRandom secureRandom, EventBus eventBus,
BackoffFactory backoffFactory) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.androidExecutor = androidExecutor; this.androidExecutor = androidExecutor;
this.appContext = appContext; this.appContext = appContext;
this.secureRandom = secureRandom; this.secureRandom = secureRandom;
this.eventBus = eventBus;
this.backoffFactory = backoffFactory; this.backoffFactory = backoffFactory;
} }
@@ -57,7 +61,10 @@ 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);
return new DroidtoothPlugin(ioExecutor, androidExecutor, appContext, AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(ioExecutor,
secureRandom, backoff, callback, MAX_LATENCY); androidExecutor, appContext, secureRandom, backoff, callback,
MAX_LATENCY);
eventBus.addListener(plugin);
return 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,682 +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.crypto.PseudoRandom;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.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.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.io.InputStream;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.Future;
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 android.bluetooth.BluetoothDevice.EXTRA_DEVICE;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.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 {
private static final Logger LOG =
Logger.getLogger(DroidtoothPlugin.class.getName());
private static final String FOUND =
"android.bluetooth.device.action.FOUND";
private static final String DISCOVERY_FINISHED =
"android.bluetooth.adapter.action.DISCOVERY_FINISHED";
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(
new Callable<BluetoothAdapter>() {
@Override
public BluetoothAdapter call() throws Exception {
return 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)) {
wasEnabledByUs = true;
if (adapter.enable()) LOG.info("Enabling Bluetooth");
else LOG.info("Could not enable Bluetooth");
} else {
LOG.info("Not enabling Bluetooth");
}
}
}
private void bind() {
ioExecutor.execute(new Runnable() {
@Override
public void run() {
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);
}
@Override
public void stop() {
running = false;
if (receiver != null) appContext.unregisterReceiver(receiver);
tryToClose(socket);
// Disable Bluetooth if we enabled it and it's still enabled
if (wasEnabledByUs && adapter.isEnabled()) {
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()) {
final ContactId c = e.getKey();
if (connected.contains(c)) continue;
final String address = e.getValue().get(PROP_ADDRESS);
if (StringUtils.isNullOrEmpty(address)) continue;
final String uuid = e.getValue().get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) continue;
ioExecutor.execute(new Runnable() {
@Override
public void run() {
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().get(c);
if (p == null) return null;
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 supportsInvitations() {
return true;
}
@Override
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
long timeout, boolean alice) {
if (!isRunning()) return null;
// Use the invitation codes to generate the UUID
byte[] b = r.nextBytes(UUID_BYTES);
UUID uuid = UUID.nameUUIDFromBytes(b);
if (LOG.isLoggable(INFO)) LOG.info("Invitation UUID " + uuid);
// Bind a server socket for receiving invitation connections
BluetoothServerSocket ss;
try {
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
"RFCOMM", uuid);
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return null;
}
// Create the background tasks
CompletionService<BluetoothSocket> complete =
new ExecutorCompletionService<>(ioExecutor);
List<Future<BluetoothSocket>> futures = new ArrayList<>();
if (alice) {
// Return the first connected socket
futures.add(complete.submit(new ListeningTask(ss)));
futures.add(complete.submit(new DiscoveryTask(uuid.toString())));
} else {
// Return the first socket with readable data
futures.add(complete.submit(new ReadableTask(
new ListeningTask(ss))));
futures.add(complete.submit(new ReadableTask(
new DiscoveryTask(uuid.toString()))));
}
BluetoothSocket chosen = null;
try {
Future<BluetoothSocket> f = complete.poll(timeout, MILLISECONDS);
if (f == null) return null; // No task completed within the timeout
chosen = f.get();
return new DroidtoothTransportConnection(this, chosen);
} catch (InterruptedException e) {
LOG.info("Interrupted while exchanging invitations");
Thread.currentThread().interrupt();
return null;
} catch (ExecutionException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return null;
} finally {
// Closing the socket will terminate the listener task
tryToClose(ss);
closeSockets(futures, chosen);
}
}
private void closeSockets(final List<Future<BluetoothSocket>> futures,
@Nullable final BluetoothSocket chosen) {
ioExecutor.execute(new Runnable() {
@Override
public void run() {
for (Future<BluetoothSocket> f : futures) {
try {
if (f.cancel(true)) {
LOG.info("Cancelled task");
} else {
BluetoothSocket s = f.get();
if (s != null && s != chosen) {
LOG.info("Closing unwanted socket");
s.close();
}
}
} catch (InterruptedException e) {
LOG.info("Interrupted while closing sockets");
return;
} catch (ExecutionException | IOException e) {
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
}
}
}
});
}
@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 invitation 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);
}
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 DiscoveryTask implements Callable<BluetoothSocket> {
private final String uuid;
private DiscoveryTask(String uuid) {
this.uuid = uuid;
}
@Override
public BluetoothSocket call() throws Exception {
// Repeat discovery until we connect or get interrupted
while (true) {
// Discover nearby devices
LOG.info("Discovering nearby devices");
List<String> addresses = discoverDevices();
if (addresses.isEmpty()) {
LOG.info("No devices discovered");
continue;
}
// Connect to any device with the right UUID
for (String address : addresses) {
BluetoothSocket s = connect(address, uuid);
if (s != null) {
LOG.info("Outgoing connection");
return s;
}
}
}
}
private List<String> discoverDevices() throws InterruptedException {
IntentFilter filter = new IntentFilter();
filter.addAction(FOUND);
filter.addAction(DISCOVERY_FINISHED);
DiscoveryReceiver disco = new DiscoveryReceiver();
appContext.registerReceiver(disco, filter);
LOG.info("Starting discovery");
adapter.startDiscovery();
return disco.waitForAddresses();
}
}
private static class DiscoveryReceiver extends BroadcastReceiver {
private final CountDownLatch finished = new CountDownLatch(1);
private final List<String> addresses = new CopyOnWriteArrayList<>();
@Override
public void onReceive(Context ctx, Intent intent) {
String action = intent.getAction();
if (action.equals(DISCOVERY_FINISHED)) {
LOG.info("Discovery finished");
ctx.unregisterReceiver(this);
finished.countDown();
} else if (action.equals(FOUND)) {
BluetoothDevice d = intent.getParcelableExtra(EXTRA_DEVICE);
if (LOG.isLoggable(INFO)) {
LOG.info("Discovered device: " +
scrubMacAddress(d.getAddress()));
}
addresses.add(d.getAddress());
}
}
private List<String> waitForAddresses() throws InterruptedException {
finished.await();
List<String> shuffled = new ArrayList<>(addresses);
Collections.shuffle(shuffled);
return shuffled;
}
}
private static class ListeningTask implements Callable<BluetoothSocket> {
private final BluetoothServerSocket serverSocket;
private ListeningTask(BluetoothServerSocket serverSocket) {
this.serverSocket = serverSocket;
}
@Override
public BluetoothSocket call() throws IOException {
BluetoothSocket s = serverSocket.accept();
LOG.info("Incoming connection");
return s;
}
}
private static class ReadableTask implements Callable<BluetoothSocket> {
private final Callable<BluetoothSocket> connectionTask;
private ReadableTask(Callable<BluetoothSocket> connectionTask) {
this.connectionTask = connectionTask;
}
@Override
public BluetoothSocket call() throws Exception {
BluetoothSocket s = connectionTask.call();
InputStream in = s.getInputStream();
while (in.available() == 0) {
LOG.info("Waiting for data");
Thread.sleep(1000);
}
LOG.info("Data available");
return s;
}
}
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 new Callable<KeyAgreementConnection>() {
@Override
public KeyAgreementConnection call() throws IOException {
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

@@ -17,7 +17,6 @@ import net.freehaven.tor.control.EventHandler;
import net.freehaven.tor.control.TorControlConnection; import net.freehaven.tor.control.TorControlConnection;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.PseudoRandom;
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.Event;
import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.event.EventListener;
@@ -56,10 +55,16 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner; import java.util.Scanner;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
@@ -70,10 +75,15 @@ import javax.net.SocketFactory;
import static android.content.Context.CONNECTIVITY_SERVICE; import static android.content.Context.CONNECTIVITY_SERVICE;
import static android.content.Context.MODE_PRIVATE; import static android.content.Context.MODE_PRIVATE;
import static android.content.Context.POWER_SERVICE; import static android.content.Context.POWER_SERVICE;
import static android.content.Intent.ACTION_SCREEN_OFF;
import static android.content.Intent.ACTION_SCREEN_ON;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
import static android.os.PowerManager.PARTIAL_WAKE_LOCK; import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS; import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
@@ -85,13 +95,13 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_NEVER; import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_NEVER;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_WIFI; import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_WIFI;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_PORT; import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_PORT;
import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION;
import static org.briarproject.bramble.util.PrivacyUtils.scrubOnion; import static org.briarproject.bramble.util.PrivacyUtils.scrubOnion;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
class TorPlugin implements DuplexPlugin, EventHandler, EventListener { class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private static final String PROP_ONION = "onion";
private static final String[] EVENTS = { private static final String[] EVENTS = {
"CIRC", "ORCONN", "HS_DESC", "NOTICE", "WARN", "ERR" "CIRC", "ORCONN", "HS_DESC", "NOTICE", "WARN", "ERR"
}; };
@@ -102,6 +112,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
Logger.getLogger(TorPlugin.class.getName()); Logger.getLogger(TorPlugin.class.getName());
private final Executor ioExecutor; private final Executor ioExecutor;
private final ScheduledExecutorService scheduler;
private final Context appContext; private final Context appContext;
private final LocationUtils locationUtils; private final LocationUtils locationUtils;
private final DevReporter reporter; private final DevReporter reporter;
@@ -114,6 +125,9 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private final File torDirectory, torFile, geoIpFile, configFile; private final File torDirectory, torFile, geoIpFile, configFile;
private final File doneFile, cookieFile; private final File doneFile, cookieFile;
private final PowerManager.WakeLock wakeLock; private final PowerManager.WakeLock wakeLock;
private final Lock connectionStatusLock;
private final AtomicReference<Future<?>> connectivityCheck =
new AtomicReference<>();
private final AtomicBoolean used = new AtomicBoolean(false); private final AtomicBoolean used = new AtomicBoolean(false);
private volatile boolean running = false; private volatile boolean running = false;
@@ -122,12 +136,13 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private volatile TorControlConnection controlConnection = null; private volatile TorControlConnection controlConnection = null;
private volatile BroadcastReceiver networkStateReceiver = null; private volatile BroadcastReceiver networkStateReceiver = null;
TorPlugin(Executor ioExecutor, Context appContext, TorPlugin(Executor ioExecutor, ScheduledExecutorService scheduler,
LocationUtils locationUtils, DevReporter reporter, Context appContext, LocationUtils locationUtils,
SocketFactory torSocketFactory, Backoff backoff, DevReporter reporter, SocketFactory torSocketFactory,
DuplexPluginCallback callback, String architecture, int maxLatency, Backoff backoff, DuplexPluginCallback callback,
int maxIdleTime) { String architecture, int maxLatency, int maxIdleTime) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.scheduler = scheduler;
this.appContext = appContext; this.appContext = appContext;
this.locationUtils = locationUtils; this.locationUtils = locationUtils;
this.reporter = reporter; this.reporter = reporter;
@@ -149,8 +164,10 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
cookieFile = new File(torDirectory, ".tor/control_auth_cookie"); cookieFile = new File(torDirectory, ".tor/control_auth_cookie");
Object o = appContext.getSystemService(POWER_SERVICE); Object o = appContext.getSystemService(POWER_SERVICE);
PowerManager pm = (PowerManager) o; PowerManager pm = (PowerManager) o;
wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "TorPlugin"); // This tag will prevent Huawei's powermanager from killing us.
wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "LocationManagerService");
wakeLock.setReferenceCounted(false); wakeLock.setReferenceCounted(false);
connectionStatusLock = new ReentrantLock();
} }
@Override @Override
@@ -203,11 +220,11 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
Scanner stdout = new Scanner(torProcess.getInputStream()); Scanner stdout = new Scanner(torProcess.getInputStream());
Scanner stderr = new Scanner(torProcess.getErrorStream()); Scanner stderr = new Scanner(torProcess.getErrorStream());
while (stdout.hasNextLine() || stderr.hasNextLine()){ while (stdout.hasNextLine() || stderr.hasNextLine()) {
if(stdout.hasNextLine()) { if (stdout.hasNextLine()) {
LOG.info(stdout.nextLine()); LOG.info(stdout.nextLine());
} }
if(stderr.hasNextLine()){ if (stderr.hasNextLine()) {
LOG.info(stderr.nextLine()); LOG.info(stderr.nextLine());
} }
} }
@@ -256,7 +273,11 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
// Register to receive network status events // Register to receive network status events
networkStateReceiver = new NetworkStateReceiver(); networkStateReceiver = new NetworkStateReceiver();
IntentFilter filter = new IntentFilter(CONNECTIVITY_ACTION); IntentFilter filter = new IntentFilter();
filter.addAction(CONNECTIVITY_ACTION);
filter.addAction(ACTION_SCREEN_ON);
filter.addAction(ACTION_SCREEN_OFF);
if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
appContext.registerReceiver(networkStateReceiver, filter); appContext.registerReceiver(networkStateReceiver, filter);
// Bind a server socket to receive incoming hidden service connections // Bind a server socket to receive incoming hidden service connections
bind(); bind();
@@ -369,57 +390,45 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
private void sendDevReports() { private void sendDevReports() {
ioExecutor.execute(new Runnable() { ioExecutor.execute(() -> {
@Override // TODO: Trigger this with a TransportEnabledEvent
public void run() { File reportDir = AndroidUtils.getReportDir(appContext);
// TODO: Trigger this with a TransportEnabledEvent reporter.sendReports(reportDir);
File reportDir = AndroidUtils.getReportDir(appContext);
reporter.sendReports(reportDir);
}
}); });
} }
private void bind() { private void bind() {
ioExecutor.execute(new Runnable() { ioExecutor.execute(() -> {
@Override // If there's already a port number stored in config, reuse it
public void run() { String portString = callback.getSettings().get(PREF_TOR_PORT);
// If there's already a port number stored in config, reuse it int port;
String portString = callback.getSettings().get(PREF_TOR_PORT); if (StringUtils.isNullOrEmpty(portString)) port = 0;
int port; else port = Integer.parseInt(portString);
if (StringUtils.isNullOrEmpty(portString)) port = 0; // Bind a server socket to receive connections from Tor
else port = Integer.parseInt(portString); ServerSocket ss = null;
// Bind a server socket to receive connections from Tor try {
ServerSocket ss = null; ss = new ServerSocket();
try { ss.bind(new InetSocketAddress("127.0.0.1", port));
ss = new ServerSocket(); } catch (IOException e) {
ss.bind(new InetSocketAddress("127.0.0.1", port)); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} catch (IOException e) { tryToClose(ss);
if (LOG.isLoggable(WARNING)) return;
LOG.log(WARNING, e.toString(), e);
tryToClose(ss);
return;
}
if (!running) {
tryToClose(ss);
return;
}
socket = ss;
// Store the port number
final String localPort = String.valueOf(ss.getLocalPort());
Settings s = new Settings();
s.put(PREF_TOR_PORT, localPort);
callback.mergeSettings(s);
// Create a hidden service if necessary
ioExecutor.execute(new Runnable() {
@Override
public void run() {
publishHiddenService(localPort);
}
});
backoff.reset();
// Accept incoming hidden service connections from Tor
acceptContactConnections(ss);
} }
if (!running) {
tryToClose(ss);
return;
}
socket = ss;
// Store the port number
String localPort = String.valueOf(ss.getLocalPort());
Settings s = new Settings();
s.put(PREF_TOR_PORT, localPort);
callback.mergeSettings(s);
// Create a hidden service if necessary
ioExecutor.execute(() -> publishHiddenService(localPort));
backoff.reset();
// Accept incoming hidden service connections from Tor
acceptContactConnections(ss);
}); });
} }
@@ -539,20 +548,21 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
public void poll(Collection<ContactId> connected) { public void poll(Collection<ContactId> connected) {
if (!isRunning()) return; if (!isRunning()) return;
backoff.increment(); backoff.increment();
// TODO: Pass properties to connectAndCallBack() Map<ContactId, TransportProperties> remote =
for (ContactId c : callback.getRemoteProperties().keySet()) callback.getRemoteProperties();
if (!connected.contains(c)) connectAndCallBack(c); for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
ContactId c = e.getKey();
if (!connected.contains(c)) connectAndCallBack(c, e.getValue());
}
} }
private void connectAndCallBack(final ContactId c) { private void connectAndCallBack(ContactId c, TransportProperties p) {
ioExecutor.execute(new Runnable() { ioExecutor.execute(() -> {
@Override if (!isRunning()) return;
public void run() { DuplexTransportConnection d = createConnection(p);
DuplexTransportConnection d = createConnection(c); if (d != null) {
if (d != null) { backoff.reset();
backoff.reset(); callback.outgoingConnectionCreated(c, d);
callback.outgoingConnectionCreated(c, d);
}
} }
}); });
} }
@@ -560,8 +570,11 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override @Override
public DuplexTransportConnection createConnection(ContactId c) { public DuplexTransportConnection createConnection(ContactId c) {
if (!isRunning()) return null; if (!isRunning()) return null;
TransportProperties p = callback.getRemoteProperties().get(c); return createConnection(callback.getRemoteProperties(c));
if (p == null) return null; }
@Nullable
private DuplexTransportConnection createConnection(TransportProperties p) {
String onion = p.get(PROP_ONION); String onion = p.get(PROP_ONION);
if (StringUtils.isNullOrEmpty(onion)) return null; if (StringUtils.isNullOrEmpty(onion)) return null;
if (!ONION.matcher(onion).matches()) { if (!ONION.matcher(onion).matches()) {
@@ -589,17 +602,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
} }
@Override
public boolean supportsInvitations() {
return false;
}
@Override
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
long timeout, boolean alice) {
throw new UnsupportedOperationException();
}
@Override @Override
public boolean supportsKeyAgreement() { public boolean supportsKeyAgreement() {
return false; return false;
@@ -612,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();
} }
@@ -636,6 +638,8 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override @Override
public void orConnStatus(String status, String orName) { public void orConnStatus(String status, String orName) {
if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status); if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
if (status.equals("CLOSED") || status.equals("FAILED"))
updateConnectionStatus(); // Check whether we've lost connectivity
} }
@Override @Override
@@ -675,7 +679,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
@Override @Override
public void onEvent(int event, String path) { public void onEvent(int event, @Nullable String path) {
stopWatching(); stopWatching();
latch.countDown(); latch.countDown();
} }
@@ -693,60 +697,74 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
private void updateConnectionStatus() { private void updateConnectionStatus() {
ioExecutor.execute(new Runnable() { ioExecutor.execute(() -> {
@Override if (!running) return;
public void run() { try {
if (!running) return; connectionStatusLock.lock();
updateConnectionStatusLocked();
Object o = appContext.getSystemService(CONNECTIVITY_SERVICE); } finally {
ConnectivityManager cm = (ConnectivityManager) o; connectionStatusLock.unlock();
NetworkInfo net = cm.getActiveNetworkInfo();
boolean online = net != null && net.isConnected();
boolean wifi = online && net.getType() == TYPE_WIFI;
String country = locationUtils.getCurrentCountry();
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(
country);
Settings s = callback.getSettings();
int network = s.getInt(PREF_TOR_NETWORK,
PREF_TOR_NETWORK_ALWAYS);
if (LOG.isLoggable(INFO)) {
LOG.info("Online: " + online + ", wifi: " + wifi);
if ("".equals(country)) LOG.info("Country code unknown");
else LOG.info("Country code: " + country);
}
try {
if (!online) {
LOG.info("Disabling network, device is offline");
enableNetwork(false);
} else if (blocked) {
LOG.info("Disabling network, country is blocked");
enableNetwork(false);
} else if (network == PREF_TOR_NETWORK_NEVER
|| (network == PREF_TOR_NETWORK_WIFI && !wifi)) {
LOG.info("Disabling network due to data setting");
enableNetwork(false);
} else {
LOG.info("Enabling network");
enableNetwork(true);
}
} catch (IOException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
} }
}); });
} }
// Locking: connectionStatusLock
private void updateConnectionStatusLocked() {
Object o = appContext.getSystemService(CONNECTIVITY_SERVICE);
ConnectivityManager cm = (ConnectivityManager) o;
NetworkInfo net = cm.getActiveNetworkInfo();
boolean online = net != null && net.isConnected();
boolean wifi = online && net.getType() == TYPE_WIFI;
String country = locationUtils.getCurrentCountry();
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(country);
Settings s = callback.getSettings();
int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_ALWAYS);
if (LOG.isLoggable(INFO)) {
LOG.info("Online: " + online + ", wifi: " + wifi);
if ("".equals(country)) LOG.info("Country code unknown");
else LOG.info("Country code: " + country);
}
try {
if (!online) {
LOG.info("Disabling network, device is offline");
enableNetwork(false);
} else if (blocked) {
LOG.info("Disabling network, country is blocked");
enableNetwork(false);
} else if (network == PREF_TOR_NETWORK_NEVER
|| (network == PREF_TOR_NETWORK_WIFI && !wifi)) {
LOG.info("Disabling network due to data setting");
enableNetwork(false);
} else {
LOG.info("Enabling network");
enableNetwork(true);
}
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
private void scheduleConnectionStatusUpdate() {
Future<?> newConnectivityCheck =
scheduler.schedule(this::updateConnectionStatus, 1, MINUTES);
Future<?> oldConnectivityCheck =
connectivityCheck.getAndSet(newConnectivityCheck);
if (oldConnectivityCheck != null) oldConnectivityCheck.cancel(false);
}
private class NetworkStateReceiver extends BroadcastReceiver { private class NetworkStateReceiver extends BroadcastReceiver {
@Override @Override
public void onReceive(Context ctx, Intent i) { public void onReceive(Context ctx, Intent i) {
if (!running) return; if (!running) return;
if (CONNECTIVITY_ACTION.equals(i.getAction())) { String action = i.getAction();
LOG.info("Detected connectivity change"); if (LOG.isLoggable(INFO)) LOG.info("Received broadcast " + action);
updateConnectionStatus(); updateConnectionStatus();
if (ACTION_SCREEN_ON.equals(action)
|| ACTION_SCREEN_OFF.equals(action)) {
scheduleConnectionStatusUpdate();
} }
} }
} }

View File

@@ -17,6 +17,7 @@ import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.util.AndroidUtils; import org.briarproject.bramble.util.AndroidUtils;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
@@ -36,6 +37,7 @@ public class TorPluginFactory implements DuplexPluginFactory {
private static final double BACKOFF_BASE = 1.2; private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor; private final Executor ioExecutor;
private final ScheduledExecutorService scheduler;
private final Context appContext; private final Context appContext;
private final LocationUtils locationUtils; private final LocationUtils locationUtils;
private final DevReporter reporter; private final DevReporter reporter;
@@ -43,11 +45,13 @@ public class TorPluginFactory implements DuplexPluginFactory {
private final SocketFactory torSocketFactory; private final SocketFactory torSocketFactory;
private final BackoffFactory backoffFactory; private final BackoffFactory backoffFactory;
public TorPluginFactory(Executor ioExecutor, Context appContext, public TorPluginFactory(Executor ioExecutor,
ScheduledExecutorService scheduler, Context appContext,
LocationUtils locationUtils, DevReporter reporter, LocationUtils locationUtils, DevReporter reporter,
EventBus eventBus, SocketFactory torSocketFactory, EventBus eventBus, SocketFactory torSocketFactory,
BackoffFactory backoffFactory) { BackoffFactory backoffFactory) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.scheduler = scheduler;
this.appContext = appContext; this.appContext = appContext;
this.locationUtils = locationUtils; this.locationUtils = locationUtils;
this.reporter = reporter; this.reporter = reporter;
@@ -89,9 +93,9 @@ public class TorPluginFactory implements DuplexPluginFactory {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE); MAX_POLLING_INTERVAL, BACKOFF_BASE);
TorPlugin plugin = new TorPlugin(ioExecutor, appContext, locationUtils, TorPlugin plugin = new TorPlugin(ioExecutor, scheduler, appContext,
reporter, torSocketFactory, backoff, callback, architecture, locationUtils, reporter, torSocketFactory, backoff, callback,
MAX_LATENCY, MAX_IDLE_TIME); architecture, MAX_LATENCY, MAX_IDLE_TIME);
eventBus.addListener(plugin); eventBus.addListener(plugin);
return plugin; return plugin;
} }

View File

@@ -3,6 +3,7 @@ package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin; import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection;
import org.briarproject.bramble.util.IoUtils;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@@ -21,12 +22,12 @@ class TorTransportConnection extends AbstractDuplexTransportConnection {
@Override @Override
protected InputStream getInputStream() throws IOException { protected InputStream getInputStream() throws IOException {
return socket.getInputStream(); return IoUtils.getInputStream(socket);
} }
@Override @Override
protected OutputStream getOutputStream() throws IOException { protected OutputStream getOutputStream() throws IOException {
return socket.getOutputStream(); return IoUtils.getOutputStream(socket);
} }
@Override @Override

View File

@@ -27,14 +27,11 @@ class AndroidExecutorImpl implements AndroidExecutor {
@Inject @Inject
AndroidExecutorImpl(Application app) { AndroidExecutorImpl(Application app) {
uiHandler = new Handler(app.getApplicationContext().getMainLooper()); uiHandler = new Handler(app.getApplicationContext().getMainLooper());
loop = new Runnable() { loop = () -> {
@Override Looper.prepare();
public void run() { backgroundHandler = new Handler();
Looper.prepare(); startLatch.countDown();
backgroundHandler = new Handler(); Looper.loop();
startLatch.countDown();
Looper.loop();
}
}; };
} }

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

@@ -1,30 +1,39 @@
apply plugin: 'java' apply plugin: 'java-library'
sourceCompatibility = 1.6 sourceCompatibility = 1.8
targetCompatibility = 1.6 targetCompatibility = 1.8
apply plugin: 'witness' apply plugin: 'witness'
dependencies { dependencies {
compile "com.google.dagger:dagger:2.0.2" implementation "com.google.dagger:dagger:2.0.2"
compile 'com.google.dagger:dagger-compiler:2.0.2' implementation 'com.google.code.findbugs:jsr305:3.0.2'
compile 'com.google.code.findbugs:jsr305:3.0.2'
testCompile 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
testCompile "org.jmock:jmock:2.8.2" testImplementation "org.jmock:jmock:2.8.2"
testCompile "org.jmock:jmock-junit4:2.8.2" testImplementation "org.jmock:jmock-junit4:2.8.2"
testCompile "org.jmock:jmock-legacy:2.8.2" testImplementation "org.jmock:jmock-legacy:2.8.2"
testCompile "org.hamcrest:hamcrest-library:1.3" testImplementation "org.hamcrest:hamcrest-library:1.3"
testCompile "org.hamcrest:hamcrest-core:1.3" testImplementation "org.hamcrest:hamcrest-core:1.3"
} }
dependencyVerification { dependencyVerification {
verify = [ verify = [
'com.google.dagger:dagger:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9', 'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861',
'com.google.dagger:dagger-compiler:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3', 'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'com.google.code.findbugs:jsr305:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7', 'com.google.dagger:dagger:2.0.2:dagger-2.0.2.jar:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9',
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'com.google.dagger:dagger-producers:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b', 'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
'com.google.guava:guava:d664fbfc03d2e5ce9cab2a44fb01f1d0bf9dfebeccc1a473b1f9ea31f79f6f99', 'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c',
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c',
'org.jmock:jmock-legacy:2.8.2:jmock-legacy-2.8.2.jar:f2b985a5c08a9edb7f37612330c058809da3f6a6d63ce792426ebf8ff0d6d31b',
'org.jmock:jmock-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760',
'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16',
'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80',
'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220',
] ]
} }
@@ -39,3 +48,8 @@ task jarTest(type: Jar, dependsOn: testClasses) {
artifacts { artifacts {
testOutput jarTest testOutput jarTest
} }
// If a Java 6 JRE is available, check we're not using any Java 7 or 8 APIs
tasks.withType(JavaCompile) {
useJava6StandardLibrary(it)
}

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

@@ -23,7 +23,7 @@ public class BdfMessageContext {
} }
public BdfMessageContext(BdfDictionary dictionary) { public BdfMessageContext(BdfDictionary dictionary) {
this(dictionary, Collections.<MessageId>emptyList()); this(dictionary, Collections.emptyList());
} }
public BdfDictionary getDictionary() { public BdfDictionary getDictionary() {

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,17 +1,17 @@
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();
PseudoRandom getPseudoRandom(int seed1, int seed2);
SecureRandom getSecureRandom(); SecureRandom getSecureRandom();
KeyPair generateAgreementKeyPair(); KeyPair generateAgreementKeyPair();
@@ -24,137 +24,47 @@ public interface CryptoComponent {
KeyParser getMessageKeyParser(); KeyParser getMessageKeyParser();
/** Generates a random invitation code. */
int generateBTInvitationCode();
/** /**
* Derives a confirmation code from the given master secret. * Derives another secret key from the given secret key.
* @param alice whether the code is for use by Alice or Bob.
*/
int deriveBTConfirmationCode(SecretKey master, boolean alice);
/**
* Derives a stream header key from the given master secret.
* @param alice whether the key is for use by Alice or Bob.
*/
SecretKey deriveHeaderKey(SecretKey master, boolean alice);
/**
* Derives a message authentication code key from the given master secret.
* @param alice whether the key is for use by Alice or Bob.
*/
SecretKey deriveMacKey(SecretKey master, boolean alice);
/**
* Derives a nonce from the given master secret for one of the parties to
* sign.
* @param alice whether the nonce is for use by Alice or Bob.
*/
byte[] deriveSignatureNonce(SecretKey master, boolean alice);
/**
* Derives a commitment to the provided public key.
* <p/>
* Part of BQP.
* *
* @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,
@@ -164,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
@@ -196,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

@@ -1,12 +0,0 @@
package org.briarproject.bramble.api.crypto;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
/**
* A deterministic pseudo-random number generator.
*/
@NotNullByDefault
public interface PseudoRandom {
byte[] nextBytes(int bytes);
}

View File

@@ -14,8 +14,9 @@ public interface StreamDecrypterFactory {
StreamDecrypter createStreamDecrypter(InputStream in, StreamContext ctx); StreamDecrypter createStreamDecrypter(InputStream in, StreamContext ctx);
/** /**
* Creates a {@link StreamDecrypter} for decrypting an invitation stream. * Creates a {@link StreamDecrypter} for decrypting a contact exchange
* stream.
*/ */
StreamDecrypter createInvitationStreamDecrypter(InputStream in, StreamDecrypter createContactExchangeStreamDecrypter(InputStream in,
SecretKey headerKey); SecretKey headerKey);
} }

View File

@@ -14,8 +14,9 @@ public interface StreamEncrypterFactory {
StreamEncrypter createStreamEncrypter(OutputStream out, StreamContext ctx); StreamEncrypter createStreamEncrypter(OutputStream out, StreamContext ctx);
/** /**
* Creates a {@link StreamEncrypter} for encrypting an invitation stream. * Creates a {@link StreamEncrypter} for encrypting a contact exchange
* stream.
*/ */
StreamEncrypter createInvitationStreamEncrypter(OutputStream out, StreamEncrypter createContactExchangeStreamDecrypter(OutputStream out,
SecretKey headerKey); SecretKey headerKey);
} }

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

@@ -4,11 +4,14 @@ import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentSkipListMap; import java.util.Map.Entry;
import java.util.TreeMap;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
public class BdfDictionary extends ConcurrentSkipListMap<String, Object> { @NotThreadSafe
public class BdfDictionary extends TreeMap<String, Object> {
public static final Object NULL_VALUE = new Object(); public static final Object NULL_VALUE = new Object();

View File

@@ -3,15 +3,17 @@ package org.briarproject.bramble.api.data;
import org.briarproject.bramble.api.Bytes; import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Vector;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE; import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
public class BdfList extends Vector<Object> { @NotThreadSafe
public class BdfList extends ArrayList<Object> {
/** /**
* Factory method for constructing lists inline. * Factory method for constructing lists inline.

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

@@ -0,0 +1,7 @@
package org.briarproject.bramble.api.db;
/**
* Thrown when the database uses a newer schema than the current code.
*/
public class DataTooNewException extends DbException {
}

View File

@@ -0,0 +1,8 @@
package org.briarproject.bramble.api.db;
/**
* Thrown when the database uses an older schema than the current code and
* cannot be migrated.
*/
public class DataTooOldException extends DbException {
}

View File

@@ -37,8 +37,13 @@ public interface DatabaseComponent {
/** /**
* Opens the database and returns true if the database already existed. * Opens the database and returns true if the database already existed.
*
* @throws DataTooNewException if the data uses a newer schema than the
* current code
* @throws DataTooOldException if the data uses an older schema than the
* current code and cannot be migrated
*/ */
boolean open() throws DbException; boolean open(@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.
@@ -122,8 +127,9 @@ public interface DatabaseComponent {
throws DbException; throws DbException;
/** /**
* Deletes the message with the given ID. The message ID and any other * Deletes the message with the given ID. Unlike
* associated data are not deleted. * {@link #removeMessage(Transaction, MessageId)}, the message ID and any
* other associated data are not deleted.
*/ */
void deleteMessage(Transaction txn, MessageId m) throws DbException; void deleteMessage(Transaction txn, MessageId m) throws DbException;
@@ -253,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
@@ -372,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/>
@@ -452,6 +467,11 @@ public interface DatabaseComponent {
*/ */
void removeLocalAuthor(Transaction txn, AuthorId a) throws DbException; void removeLocalAuthor(Transaction txn, AuthorId a) throws DbException;
/**
* Removes a message (and all associated state) from the database.
*/
void removeMessage(Transaction txn, 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

@@ -1,11 +1,11 @@
package org.briarproject.bramble.api.db; package org.briarproject.bramble.api.db;
import java.util.Hashtable; import java.util.TreeMap;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.NotThreadSafe;
@ThreadSafe @NotThreadSafe
public class Metadata extends Hashtable<String, byte[]> { public class Metadata extends TreeMap<String, byte[]> {
/** /**
* Special value to indicate that a key is being removed. * Special value to indicate that a key is being removed.

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

@@ -45,7 +45,7 @@ public class Transaction {
* committed. * committed.
*/ */
public void attach(Event e) { public void attach(Event e) {
if (events == null) events = new ArrayList<Event>(); if (events == null) events = new ArrayList<>();
events.add(e); events.add(e);
} }

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

@@ -1,20 +0,0 @@
package org.briarproject.bramble.api.invitation;
public interface InvitationConstants {
/**
* The connection timeout in milliseconds.
*/
long CONNECTION_TIMEOUT = 60 * 1000;
/**
* The confirmation timeout in milliseconds.
*/
long CONFIRMATION_TIMEOUT = 60 * 1000;
/**
* The number of bits in an invitation or confirmation code. Codes must fit
* into six decimal digits.
*/
int CODE_BITS = 19;
}

View File

@@ -1,47 +0,0 @@
package org.briarproject.bramble.api.invitation;
/**
* An interface for receiving updates about the state of an
* {@link InvitationTask}.
*/
public interface InvitationListener {
/** Called if a connection to the remote peer is established. */
void connectionSucceeded();
/**
* Called if a connection to the remote peer cannot be established. This
* indicates that the protocol has ended unsuccessfully.
*/
void connectionFailed();
/** Called if key agreement with the remote peer succeeds. */
void keyAgreementSucceeded(int localCode, int remoteCode);
/**
* Called if key agreement with the remote peer fails or the connection is
* lost. This indicates that the protocol has ended unsuccessfully.
*/
void keyAgreementFailed();
/** Called if the remote peer's confirmation check succeeds. */
void remoteConfirmationSucceeded();
/**
* Called if remote peer's confirmation check fails or the connection is
* lost. This indicates that the protocol has ended unsuccessfully.
*/
void remoteConfirmationFailed();
/**
* Called if the exchange of pseudonyms succeeds. This indicates that the
* protocol has ended successfully.
*/
void pseudonymExchangeSucceeded(String remoteName);
/**
* Called if the exchange of pseudonyms fails or the connection is lost.
* This indicates that the protocol has ended unsuccessfully.
*/
void pseudonymExchangeFailed();
}

View File

@@ -1,85 +0,0 @@
package org.briarproject.bramble.api.invitation;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
/**
* A snapshot of the state of an {@link InvitationTask}.
*/
@Immutable
@NotNullByDefault
public class InvitationState {
private final int localInvitationCode, remoteInvitationCode;
private final int localConfirmationCode, remoteConfirmationCode;
private final boolean connected, connectionFailed;
private final boolean localCompared, remoteCompared;
private final boolean localMatched, remoteMatched;
@Nullable
private final String contactName;
public InvitationState(int localInvitationCode, int remoteInvitationCode,
int localConfirmationCode, int remoteConfirmationCode,
boolean connected, boolean connectionFailed, boolean localCompared,
boolean remoteCompared, boolean localMatched,
boolean remoteMatched, @Nullable String contactName) {
this.localInvitationCode = localInvitationCode;
this.remoteInvitationCode = remoteInvitationCode;
this.localConfirmationCode = localConfirmationCode;
this.remoteConfirmationCode = remoteConfirmationCode;
this.connected = connected;
this.connectionFailed = connectionFailed;
this.localCompared = localCompared;
this.remoteCompared = remoteCompared;
this.localMatched = localMatched;
this.remoteMatched = remoteMatched;
this.contactName = contactName;
}
public int getLocalInvitationCode() {
return localInvitationCode;
}
public int getRemoteInvitationCode() {
return remoteInvitationCode;
}
public int getLocalConfirmationCode() {
return localConfirmationCode;
}
public int getRemoteConfirmationCode() {
return remoteConfirmationCode;
}
public boolean getConnected() {
return connected;
}
public boolean getConnectionFailed() {
return connectionFailed;
}
public boolean getLocalCompared() {
return localCompared;
}
public boolean getRemoteCompared() {
return remoteCompared;
}
public boolean getLocalMatched() {
return localMatched;
}
public boolean getRemoteMatched() {
return remoteMatched;
}
@Nullable
public String getContactName() {
return contactName;
}
}

View File

@@ -1,38 +0,0 @@
package org.briarproject.bramble.api.invitation;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
/**
* A task for exchanging invitations with a remote peer.
*/
@NotNullByDefault
public interface InvitationTask {
/**
* Adds a listener to be informed of state changes and returns the
* task's current state.
*/
InvitationState addListener(InvitationListener l);
/**
* Removes the given listener.
*/
void removeListener(InvitationListener l);
/**
* Asynchronously starts the connection process.
*/
void connect();
/**
* Asynchronously informs the remote peer that the local peer's
* confirmation codes matched.
*/
void localConfirmationSucceeded();
/**
* Asynchronously informs the remote peer that the local peer's
* confirmation codes did not match.
*/
void localConfirmationFailed();
}

View File

@@ -1,15 +0,0 @@
package org.briarproject.bramble.api.invitation;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
/**
* Creates tasks for exchanging invitations with remote peers.
*/
@NotNullByDefault
public interface InvitationTaskFactory {
/**
* Creates a task using the given local and remote invitation codes.
*/
InvitationTask createTask(int localCode, int remoteCode);
}

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

@@ -4,5 +4,10 @@ public interface LanTcpConstants {
TransportId ID = new TransportId("org.briarproject.bramble.lan"); TransportId ID = new TransportId("org.briarproject.bramble.lan");
// a transport property (shared with contacts)
String PROP_IP_PORTS = "ipPorts";
// a local setting
String PREF_LAN_IP_PORTS = "ipPorts"; String PREF_LAN_IP_PORTS = "ipPorts";
} }

View File

@@ -29,6 +29,11 @@ public interface PluginCallback {
*/ */
Map<ContactId, TransportProperties> getRemoteProperties(); Map<ContactId, TransportProperties> getRemoteProperties();
/**
* Returns the plugin's remote transport properties for the given contact.
*/
TransportProperties getRemoteProperties(ContactId c);
/** /**
* Merges the given settings with the namespaced settings * Merges the given settings with the namespaced settings
*/ */

View File

@@ -32,11 +32,6 @@ public interface PluginManager {
*/ */
Collection<DuplexPlugin> getDuplexPlugins(); Collection<DuplexPlugin> getDuplexPlugins();
/**
* Returns any duplex plugins that support invitations.
*/
Collection<DuplexPlugin> getInvitationPlugins();
/** /**
* Returns any duplex plugins that support key agreement. * Returns any duplex plugins that support key agreement.
*/ */

View File

@@ -4,6 +4,8 @@ public interface TorConstants {
TransportId ID = new TransportId("org.briarproject.bramble.tor"); TransportId ID = new TransportId("org.briarproject.bramble.tor");
String PROP_ONION = "onion";
int SOCKS_PORT = 59050; int SOCKS_PORT = 59050;
int CONTROL_PORT = 59051; int CONTROL_PORT = 59051;
@@ -16,4 +18,5 @@ public interface TorConstants {
int PREF_TOR_NETWORK_NEVER = 0; int PREF_TOR_NETWORK_NEVER = 0;
int PREF_TOR_NETWORK_WIFI = 1; int PREF_TOR_NETWORK_WIFI = 1;
int PREF_TOR_NETWORK_ALWAYS = 2; int PREF_TOR_NETWORK_ALWAYS = 2;
} }

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.api.plugin.duplex; package org.briarproject.bramble.api.plugin.duplex;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.PseudoRandom;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener; import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -23,20 +22,6 @@ public interface DuplexPlugin extends Plugin {
@Nullable @Nullable
DuplexTransportConnection createConnection(ContactId c); DuplexTransportConnection createConnection(ContactId c);
/**
* Returns true if the plugin supports exchanging invitations.
*/
boolean supportsInvitations();
/**
* Attempts to create and return an invitation connection to the remote
* peer. Returns null if no connection can be established within the given
* time.
*/
@Nullable
DuplexTransportConnection createInvitationConnection(PseudoRandom r,
long timeout, boolean alice);
/** /**
* Returns true if the plugin supports short-range key agreement. * Returns true if the plugin supports short-range key agreement.
*/ */
@@ -51,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

@@ -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 asks the Bluetooth plugin to disable the Bluetooth adapter if
* we previously enabled it.
*/
@Immutable
@NotNullByDefault
public class DisableBluetoothEvent extends Event {
}

View File

@@ -0,0 +1,14 @@
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 asks the Bluetooth plugin to enable the Bluetooth adapter.
*/
@Immutable
@NotNullByDefault
public class EnableBluetoothEvent extends Event {
}

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.
@@ -33,7 +38,7 @@ public interface TransportPropertyManager {
/** /**
* Returns the local transport properties for all transports. * Returns the local transport properties for all transports.
* <br/> * <br/>
* Read-Only * TODO: Transaction can be read-only when code is simplified
*/ */
Map<TransportId, TransportProperties> getLocalProperties(Transaction txn) Map<TransportId, TransportProperties> getLocalProperties(Transaction txn)
throws DbException; throws DbException;
@@ -49,6 +54,13 @@ public interface TransportPropertyManager {
Map<ContactId, TransportProperties> getRemoteProperties(TransportId t) Map<ContactId, TransportProperties> getRemoteProperties(TransportId t)
throws DbException; throws DbException;
/**
* Returns the remote transport properties for the given contact and
* transport.
*/
TransportProperties getRemoteProperties(ContactId c, TransportId t)
throws DbException;
/** /**
* Merges the given properties with the existing local properties for the * Merges the given properties with the existing local properties for the
* given transport. * given transport.

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

@@ -22,7 +22,7 @@ public class MessageContext {
} }
public MessageContext(Metadata metadata) { public MessageContext(Metadata metadata) {
this(metadata, Collections.<MessageId>emptyList()); this(metadata, Collections.emptyList());
} }
public Metadata getMetadata() { public Metadata getMetadata() {

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

@@ -15,9 +15,9 @@ public interface StreamReaderFactory {
InputStream createStreamReader(InputStream in, StreamContext ctx); InputStream createStreamReader(InputStream in, StreamContext ctx);
/** /**
* Creates an {@link InputStream InputStream} for reading from an * Creates an {@link InputStream InputStream} for reading from a contact
* invitation stream. * exchangestream.
*/ */
InputStream createInvitationStreamReader(InputStream in, InputStream createContactExchangeStreamReader(InputStream in,
SecretKey headerKey); SecretKey headerKey);
} }

View File

@@ -15,9 +15,9 @@ public interface StreamWriterFactory {
OutputStream createStreamWriter(OutputStream out, StreamContext ctx); OutputStream createStreamWriter(OutputStream out, StreamContext ctx);
/** /**
* Creates an {@link OutputStream OutputStream} for writing to an * Creates an {@link OutputStream OutputStream} for writing to a contact
* invitation stream. * exchange stream.
*/ */
OutputStream createInvitationStreamWriter(OutputStream out, OutputStream createContactExchangeStreamWriter(OutputStream out,
SecretKey headerKey); SecretKey headerKey);
} }

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

@@ -8,6 +8,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.Socket;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -59,4 +60,24 @@ public class IoUtils {
offset += read; offset += read;
} }
} }
// Workaround for a bug in Android 7, see
// https://android-review.googlesource.com/#/c/271775/
public static InputStream getInputStream(Socket s) throws IOException {
try {
return s.getInputStream();
} catch (NullPointerException e) {
throw new IOException(e);
}
}
// Workaround for a bug in Android 7, see
// https://android-review.googlesource.com/#/c/271775/
public static OutputStream getOutputStream(Socket s) throws IOException {
try {
return s.getOutputStream();
} catch (NullPointerException e) {
throw new IOException(e);
}
}
} }

View File

@@ -8,6 +8,7 @@ import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetDecoder;
import java.util.Collection; import java.util.Collection;
import java.util.Random;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -27,6 +28,7 @@ public class StringUtils {
'0', '1', '2', '3', '4', '5', '6', '7', '0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F' '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
}; };
private static final Random random = new Random();
public static boolean isNullOrEmpty(@Nullable String s) { public static boolean isNullOrEmpty(@Nullable String s) {
return s == null || s.length() == 0; return s == null || s.length() == 0;
@@ -124,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(":", ""));
@@ -139,4 +145,20 @@ 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) {
char[] c = new char[length];
for (int i = 0; i < length; i++)
c[i] = (char) ('a' + random.nextInt(26));
return new String(c);
}
} }

View File

@@ -3,8 +3,7 @@ package org.briarproject.bramble.test;
import org.jmock.Mockery; import org.jmock.Mockery;
import org.junit.After; import org.junit.After;
public abstract class BrambleMockTestCase extends public abstract class BrambleMockTestCase extends BrambleTestCase {
BrambleTestCase {
protected final Mockery context = new Mockery(); protected final Mockery context = new Mockery();

View File

@@ -8,12 +8,9 @@ public abstract class BrambleTestCase {
public BrambleTestCase() { public BrambleTestCase() {
// Ensure exceptions thrown on worker threads cause tests to fail // Ensure exceptions thrown on worker threads cause tests to fail
UncaughtExceptionHandler fail = new UncaughtExceptionHandler() { UncaughtExceptionHandler fail = (thread, throwable) -> {
@Override throwable.printStackTrace();
public void uncaughtException(Thread thread, Throwable throwable) { fail();
throwable.printStackTrace();
fail();
}
}; };
Thread.setDefaultUncaughtExceptionHandler(fail); Thread.setDefaultUncaughtExceptionHandler(fail);
} }

View File

@@ -2,12 +2,32 @@ package org.briarproject.bramble.test;
import org.briarproject.bramble.api.UniqueId; import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.Group;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.util.IoUtils; import org.briarproject.bramble.util.IoUtils;
import java.io.File; import java.io.File;
import java.util.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_PUBLIC_KEY_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
public class TestUtils { public class TestUtils {
private static final AtomicInteger nextTestDir = private static final AtomicInteger nextTestDir =
@@ -34,15 +54,89 @@ public class TestUtils {
return getRandomBytes(UniqueId.LENGTH); return getRandomBytes(UniqueId.LENGTH);
} }
public static String getRandomString(int length) {
char[] c = new char[length];
for (int i = 0; i < length; i++)
c[i] = (char) ('a' + random.nextInt(26));
return new String(c);
}
public static SecretKey getSecretKey() { public static SecretKey getSecretKey() {
return new SecretKey(getRandomBytes(SecretKey.LENGTH)); return new SecretKey(getRandomBytes(SecretKey.LENGTH));
} }
public static LocalAuthor getLocalAuthor() {
return getLocalAuthor(1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH));
}
public static LocalAuthor getLocalAuthor(int nameLength) {
AuthorId id = new AuthorId(getRandomId());
String name = getRandomString(nameLength);
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
byte[] privateKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
long created = System.currentTimeMillis();
return new LocalAuthor(id, FORMAT_VERSION, name, publicKey, privateKey,
created);
}
public static Author getAuthor() {
return getAuthor(1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH));
}
public static Author getAuthor(int nameLength) {
AuthorId id = new AuthorId(getRandomId());
String name = getRandomString(nameLength);
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
return new Author(id, FORMAT_VERSION, name, publicKey);
}
public static Group getGroup(ClientId clientId) {
int descriptorLength = 1 + random.nextInt(MAX_GROUP_DESCRIPTOR_LENGTH);
return getGroup(clientId, descriptorLength);
}
public static Group getGroup(ClientId clientId, int descriptorLength) {
GroupId groupId = new GroupId(getRandomId());
byte[] descriptor = getRandomBytes(descriptorLength);
return new Group(groupId, clientId, descriptor);
}
public static Message getMessage(GroupId groupId) {
int bodyLength = 1 + random.nextInt(MAX_MESSAGE_BODY_LENGTH);
return getMessage(groupId, MESSAGE_HEADER_LENGTH + bodyLength);
}
public static Message getMessage(GroupId groupId, int rawLength) {
MessageId id = new MessageId(getRandomId());
byte[] raw = getRandomBytes(rawLength);
long timestamp = System.currentTimeMillis();
return new Message(id, groupId, timestamp, raw);
}
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

@@ -1,28 +1,60 @@
plugins { apply plugin: 'java-library'
id 'java' sourceCompatibility = 1.8
id 'net.ltgt.apt' version '0.9' targetCompatibility = 1.8
id 'idea'
}
sourceCompatibility = 1.6
targetCompatibility = 1.6
apply plugin: 'net.ltgt.apt'
apply plugin: 'idea'
apply plugin: 'witness' apply plugin: 'witness'
dependencies { dependencies {
compile project(':bramble-api') implementation project(path: ':bramble-api', configuration: 'default')
compile 'com.madgag.spongycastle:core:1.56.0.0' implementation 'com.madgag.spongycastle:core:1.58.0.0'
compile '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
compile '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'
testCompile project(path: ':bramble-api', configuration: 'testOutput') apt 'com.google.dagger:dagger-compiler:2.0.2'
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
testImplementation 'org.hsqldb:hsqldb:2.3.5' // The last version that supports Java 1.6
testImplementation 'junit:junit:4.12'
testImplementation "org.jmock:jmock:2.8.2"
testImplementation "org.jmock:jmock-junit4:2.8.2"
testImplementation "org.jmock:jmock-legacy:2.8.2"
testImplementation "org.hamcrest:hamcrest-library:1.3"
testImplementation "org.hamcrest:hamcrest-core:1.3"
testApt 'com.google.dagger:dagger-compiler:2.0.2'
} }
dependencyVerification { dependencyVerification {
verify = [ verify = [
'com.madgag.spongycastle:core:5e791b0eaa9e0c4594231b44f616a52adddb7dccedeb0ad9ad74887e19499a23', 'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861',
'com.h2database:h2:225b22e9857235c46c93861410b60b8c81c10dc8985f4faf188985ba5445126c', 'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'org.bitlet:weupnp:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb', 'com.google.dagger:dagger-compiler:2.0.2:dagger-compiler-2.0.2.jar:b74bc9de063dd4c6400b232231f2ef5056145b8fbecbf5382012007dd1c071b3',
'com.google.dagger:dagger-producers:2.0-beta:dagger-producers-2.0-beta.jar:99ec15e8a0507ba569e7655bc1165ee5e5ca5aa914b3c8f7e2c2458f724edd6b',
'com.google.dagger:dagger:2.0.2:dagger-2.0.2.jar:84c0282ed8be73a29e0475d639da030b55dee72369e58dd35ae7d4fe6243dcf9',
'com.google.guava:guava:18.0:guava-18.0.jar:d664fbfc03d2e5ce9cab2a44fb01f1d0bf9dfebeccc1a473b1f9ea31f79f6f99',
'com.h2database:h2:1.4.192:h2-1.4.192.jar:225b22e9857235c46c93861410b60b8c81c10dc8985f4faf188985ba5445126c',
'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
'net.i2p.crypto:eddsa:0.2.0:eddsa-0.2.0.jar:a7cb1b85c16e2f0730b9204106929a1d9aaae1df728adc7041a8b8b605692140',
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c',
'org.hsqldb:hsqldb:2.3.5:hsqldb-2.3.5.jar:6676a6977ac98997a80f827ddbd3fe8ca1e0853dad1492512135fd1a222ccfad',
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c',
'org.jmock:jmock-legacy:2.8.2:jmock-legacy-2.8.2.jar:f2b985a5c08a9edb7f37612330c058809da3f6a6d63ce792426ebf8ff0d6d31b',
'org.jmock:jmock-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760',
'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16',
'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80',
'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220',
'org.whispersystems:curve25519-java:0.4.1:curve25519-java-0.4.1.jar:7dd659d8822c06c3aea1a47f18fac9e5761e29cab8100030b877db445005f03e',
] ]
} }
@@ -37,3 +69,8 @@ task jarTest(type: Jar, dependsOn: testClasses) {
artifacts { artifacts {
testOutput jarTest testOutput jarTest
} }
// If a Java 6 JRE is available, check we're not using any Java 7 or 8 APIs
tasks.withType(JavaCompile) {
useJava6StandardLibrary(it)
}

View File

@@ -8,7 +8,6 @@ import org.briarproject.bramble.db.DatabaseExecutorModule;
import org.briarproject.bramble.db.DatabaseModule; import org.briarproject.bramble.db.DatabaseModule;
import org.briarproject.bramble.event.EventModule; import org.briarproject.bramble.event.EventModule;
import org.briarproject.bramble.identity.IdentityModule; import org.briarproject.bramble.identity.IdentityModule;
import org.briarproject.bramble.invitation.InvitationModule;
import org.briarproject.bramble.keyagreement.KeyAgreementModule; import org.briarproject.bramble.keyagreement.KeyAgreementModule;
import org.briarproject.bramble.lifecycle.LifecycleModule; import org.briarproject.bramble.lifecycle.LifecycleModule;
import org.briarproject.bramble.plugin.PluginModule; import org.briarproject.bramble.plugin.PluginModule;
@@ -32,7 +31,6 @@ import dagger.Module;
DatabaseExecutorModule.class, DatabaseExecutorModule.class,
EventModule.class, EventModule.class,
IdentityModule.class, IdentityModule.class,
InvitationModule.class,
KeyAgreementModule.class, KeyAgreementModule.class,
LifecycleModule.class, LifecycleModule.class,
PluginModule.class, PluginModule.class,

View File

@@ -24,7 +24,7 @@ public class PoliteExecutor implements Executor {
private final Object lock = new Object(); private final Object lock = new Object();
@GuardedBy("lock") @GuardedBy("lock")
private final Queue<Runnable> queue = new LinkedList<Runnable>(); private final Queue<Runnable> queue = new LinkedList<>();
private final Executor delegate; private final Executor delegate;
private final int maxConcurrentTasks; private final int maxConcurrentTasks;
private final Logger log; private final Logger log;
@@ -48,20 +48,17 @@ public class PoliteExecutor implements Executor {
} }
@Override @Override
public void execute(final Runnable r) { public void execute(Runnable r) {
final long submitted = System.currentTimeMillis(); long submitted = System.currentTimeMillis();
Runnable wrapped = new Runnable() { Runnable wrapped = () -> {
@Override if (log.isLoggable(LOG_LEVEL)) {
public void run() { long queued = System.currentTimeMillis() - submitted;
if (log.isLoggable(LOG_LEVEL)) { log.log(LOG_LEVEL, "Queue time " + queued + " ms");
long queued = System.currentTimeMillis() - submitted; }
log.log(LOG_LEVEL, "Queue time " + queued + " ms"); try {
} r.run();
try { } finally {
r.run(); scheduleNext();
} finally {
scheduleNext();
}
} }
}; };
synchronized (lock) { synchronized (lock) {

View File

@@ -28,19 +28,16 @@ public class TimeLoggingExecutor extends ThreadPoolExecutor {
} }
@Override @Override
public void execute(final Runnable r) { public void execute(Runnable r) {
if (log.isLoggable(LOG_LEVEL)) { if (log.isLoggable(LOG_LEVEL)) {
final long submitted = System.currentTimeMillis(); long submitted = System.currentTimeMillis();
super.execute(new Runnable() { super.execute(() -> {
@Override long started = System.currentTimeMillis();
public void run() { long queued = started - submitted;
long started = System.currentTimeMillis(); log.log(LOG_LEVEL, "Queue time " + queued + " ms");
long queued = started - submitted; r.run();
log.log(LOG_LEVEL, "Queue time " + queued + " ms"); long executing = System.currentTimeMillis() - started;
r.run(); log.log(LOG_LEVEL, "Execution time " + executing + " ms");
long executing = System.currentTimeMillis() - started;
log.log(LOG_LEVEL, "Execution time " + executing + " ms");
}
}); });
} else { } else {
super.execute(r); super.execute(r);

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
@@ -201,8 +211,7 @@ class ClientHelperImpl implements ClientHelper {
public Map<MessageId, BdfDictionary> getMessageMetadataAsDictionary( public Map<MessageId, BdfDictionary> getMessageMetadataAsDictionary(
Transaction txn, GroupId g) throws DbException, FormatException { Transaction txn, GroupId g) throws DbException, FormatException {
Map<MessageId, Metadata> raw = db.getMessageMetadata(txn, g); Map<MessageId, Metadata> raw = db.getMessageMetadata(txn, g);
Map<MessageId, BdfDictionary> parsed = Map<MessageId, BdfDictionary> parsed = new HashMap<>(raw.size());
new HashMap<MessageId, BdfDictionary>(raw.size());
for (Entry<MessageId, Metadata> e : raw.entrySet()) for (Entry<MessageId, Metadata> e : raw.entrySet())
parsed.put(e.getKey(), metadataParser.parse(e.getValue())); parsed.put(e.getKey(), metadataParser.parse(e.getValue()));
return parsed; return parsed;
@@ -229,8 +238,7 @@ class ClientHelperImpl implements ClientHelper {
FormatException { FormatException {
Metadata metadata = metadataEncoder.encode(query); Metadata metadata = metadataEncoder.encode(query);
Map<MessageId, Metadata> raw = db.getMessageMetadata(txn, g, metadata); Map<MessageId, Metadata> raw = db.getMessageMetadata(txn, g, metadata);
Map<MessageId, BdfDictionary> parsed = Map<MessageId, BdfDictionary> parsed = new HashMap<>(raw.size());
new HashMap<MessageId, BdfDictionary>(raw.size());
for (Entry<MessageId, Metadata> e : raw.entrySet()) for (Entry<MessageId, Metadata> e : raw.entrySet())
parsed.put(e.getKey(), metadataParser.parse(e.getValue())); parsed.put(e.getKey(), metadataParser.parse(e.getValue()));
return parsed; return parsed;
@@ -343,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 {
@@ -357,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;
@@ -80,7 +81,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
private volatile boolean alice; private volatile boolean alice;
@Inject @Inject
public ContactExchangeTaskImpl(DatabaseComponent db, ContactExchangeTaskImpl(DatabaseComponent db,
AuthorFactory authorFactory, BdfReaderFactory bdfReaderFactory, AuthorFactory authorFactory, BdfReaderFactory bdfReaderFactory,
BdfWriterFactory bdfWriterFactory, Clock clock, BdfWriterFactory bdfWriterFactory, Clock clock,
ConnectionManager connectionManager, ContactManager contactManager, ConnectionManager connectionManager, ContactManager contactManager,
@@ -141,23 +142,27 @@ 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 =
streamReaderFactory.createInvitationStreamReader(in, streamReaderFactory.createContactExchangeStreamReader(in,
alice ? bobHeaderKey : aliceHeaderKey); alice ? bobHeaderKey : aliceHeaderKey);
BdfReader r = bdfReaderFactory.createReader(streamReader); BdfReader r = bdfReaderFactory.createReader(streamReader);
// Create the writers // Create the writers
OutputStream streamWriter = OutputStream streamWriter =
streamWriterFactory.createInvitationStreamWriter(out, streamWriterFactory.createContactExchangeStreamWriter(out,
alice ? aliceHeaderKey : bobHeaderKey); alice ? aliceHeaderKey : bobHeaderKey);
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();
@@ -184,12 +189,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
// Close the outgoing stream and expect EOF on the incoming stream // Close the outgoing stream and expect EOF on the incoming stream
w.close(); w.close();
if (!r.eof()) LOG.warning("Unexpected data at end of connection"); if (!r.eof()) LOG.warning("Unexpected data at end of connection");
} catch (GeneralSecurityException e) { } catch (GeneralSecurityException | IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
listener.contactExchangeFailed();
tryToClose(conn, true);
return;
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
listener.contactExchangeFailed(); listener.contactExchangeFailed();
tryToClose(conn, true); tryToClose(conn, true);
@@ -201,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);
@@ -228,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);
@@ -237,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
@@ -250,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)
@@ -276,8 +282,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
private Map<TransportId, TransportProperties> receiveTransportProperties( private Map<TransportId, TransportProperties> receiveTransportProperties(
BdfReader r) throws IOException { BdfReader r) throws IOException {
Map<TransportId, TransportProperties> remote = Map<TransportId, TransportProperties> remote = new HashMap<>();
new HashMap<TransportId, TransportProperties>();
r.readListStart(); r.readListStart();
while (!r.hasListEnd()) { while (!r.hasListEnd()) {
r.readListStart(); r.readListStart();
@@ -300,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);
@@ -318,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

@@ -34,8 +34,8 @@ class ContactManagerImpl implements ContactManager {
ContactManagerImpl(DatabaseComponent db, KeyManager keyManager) { ContactManagerImpl(DatabaseComponent db, KeyManager keyManager) {
this.db = db; this.db = db;
this.keyManager = keyManager; this.keyManager = keyManager;
addHooks = new CopyOnWriteArrayList<AddContactHook>(); addHooks = new CopyOnWriteArrayList<>();
removeHooks = new CopyOnWriteArrayList<RemoveContactHook>(); removeHooks = new CopyOnWriteArrayList<>();
} }
@Override @Override
@@ -125,7 +125,7 @@ class ContactManagerImpl implements ContactManager {
} finally { } finally {
db.endTransaction(txn); db.endTransaction(txn);
} }
List<Contact> active = new ArrayList<Contact>(contacts.size()); List<Contact> active = new ArrayList<>(contacts.size());
for (Contact c : contacts) if (c.isActive()) active.add(c); for (Contact c : contacts) if (c.isActive()) active.add(c);
return active; return active;
} }

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,112 +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.PseudoRandom;
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.invitation.InvitationConstants.CODE_BITS;
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 bluetooth confirmation code derivation
private static final byte[] BT_A_CONFIRM = ascii("ALICE_CONFIRMATION_CODE");
private static final byte[] BT_B_CONFIRM = ascii("BOB_CONFIRMATION_CODE");
// 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();
@@ -126,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);
} }
@@ -171,14 +115,6 @@ class CryptoComponentImpl implements CryptoComponent {
return new SecretKey(b); return new SecretKey(b);
} }
@Override
public PseudoRandom getPseudoRandom(int seed1, int seed2) {
byte[] seed = new byte[INT_32_BYTES * 2];
ByteUtils.writeUint32(seed1, seed, 0);
ByteUtils.writeUint32(seed2, seed, INT_32_BYTES);
return new PseudoRandomImpl(seed);
}
@Override @Override
public SecureRandom getSecureRandom() { public SecureRandom getSecureRandom() {
return secureRandom; return secureRandom;
@@ -187,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");
@@ -205,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
@@ -226,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);
} }
@@ -251,219 +175,47 @@ class CryptoComponentImpl implements CryptoComponent {
} }
@Override @Override
public int generateBTInvitationCode() { public SecretKey deriveKey(String label, SecretKey k, byte[]... inputs) {
int codeBytes = (CODE_BITS + 7) / 8; byte[] mac = mac(label, k, inputs);
byte[] random = new byte[codeBytes]; if (mac.length != SecretKey.LENGTH) throw new IllegalStateException();
secureRandom.nextBytes(random); return new SecretKey(mac);
return ByteUtils.readUint(random, CODE_BITS);
} }
@Override @Override
public int deriveBTConfirmationCode(SecretKey master, boolean alice) { public SecretKey deriveSharedSecret(String label, PublicKey theirPublicKey,
byte[] b = macKdf(master, alice ? BT_A_CONFIRM : BT_B_CONFIRM); KeyPair ourKeyPair, byte[]... inputs)
return ByteUtils.readUint(b, CODE_BITS); throws GeneralSecurityException {
}
@Override
public SecretKey deriveHeaderKey(SecretKey master,
boolean alice) {
return new SecretKey(macKdf(master, alice ? A_INVITE : B_INVITE));
}
@Override
public SecretKey deriveMacKey(SecretKey master, boolean alice) {
return new SecretKey(macKdf(master, alice ? A_MAC : B_MAC));
}
@Override
public byte[] deriveSignatureNonce(SecretKey master,
boolean alice) {
return macKdf(master, alice ? A_SIG_NONCE : B_SIG_NONCE);
}
@Override
public byte[] deriveKeyCommitment(byte[] publicKey) {
byte[] hash = hash(COMMIT, publicKey);
// The output is the first COMMIT_LENGTH bytes of the hash
byte[] commitment = new byte[COMMIT_LENGTH];
System.arraycopy(hash, 0, commitment, 0, COMMIT_LENGTH);
return commitment;
}
@Override
public SecretKey deriveSharedSecret(byte[] theirPublicKey,
KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException {
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);
@@ -477,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);
@@ -493,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);
@@ -519,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) {
@@ -544,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);
@@ -568,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);
@@ -591,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<Long>(PBKDF_SAMPLES);
List<Long> slowSamples = new ArrayList<Long>(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;
@@ -48,7 +50,7 @@ public class CryptoModule {
public CryptoModule() { public CryptoModule() {
// Use an unbounded queue // Use an unbounded queue
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(); BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
// Discard tasks that are submitted during shutdown // Discard tasks that are submitted during shutdown
RejectedExecutionHandler policy = RejectedExecutionHandler policy =
new ThreadPoolExecutor.DiscardPolicy(); new ThreadPoolExecutor.DiscardPolicy();
@@ -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);
}

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