mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Compare commits
302 Commits
beta-0.16.
...
network-lo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f25b16e680 | ||
|
|
467fdb6468 | ||
|
|
c0840dc332 | ||
|
|
235183a3af | ||
|
|
701e51dfc8 | ||
|
|
3361922834 | ||
|
|
fcabf697d6 | ||
|
|
4d502576c9 | ||
|
|
5fe68e6f82 | ||
|
|
f725c7ab9a | ||
|
|
03b4907311 | ||
|
|
809b9f8919 | ||
|
|
85cc23444c | ||
|
|
c073c5c8bd | ||
|
|
976c8a9578 | ||
|
|
d52ca14ebe | ||
|
|
a178dbae9e | ||
|
|
9a4f0b8e89 | ||
|
|
db7dbfce68 | ||
|
|
80770b0216 | ||
|
|
9f02bbbba1 | ||
|
|
190aeef34e | ||
|
|
6fbaae0e5e | ||
|
|
5cc0f08b8f | ||
|
|
976460e0b7 | ||
|
|
9cdd537600 | ||
|
|
c44a3d01b9 | ||
|
|
a8ed86575d | ||
|
|
46406d8d1a | ||
|
|
05210257a0 | ||
|
|
d5c89640c1 | ||
|
|
3f1fb1ca1c | ||
|
|
bfdc79ac60 | ||
|
|
06897569d4 | ||
|
|
66e3f6deba | ||
|
|
528a52d0f7 | ||
|
|
d395f0866a | ||
|
|
363dfbc6aa | ||
|
|
c6f2941e74 | ||
|
|
a61cd01336 | ||
|
|
10f63ad60b | ||
|
|
0b781cf272 | ||
|
|
6dc7277771 | ||
|
|
d880b14e0c | ||
|
|
f88c68eff4 | ||
|
|
5fce8cbe0a | ||
|
|
439654e71d | ||
|
|
7fe502e3cc | ||
|
|
aa07d0cadd | ||
|
|
d9cca3d9eb | ||
|
|
94c5f61cc3 | ||
|
|
9cce0d8e15 | ||
|
|
44488b5187 | ||
|
|
4d6ac13338 | ||
|
|
ef3afa7832 | ||
|
|
687ea132f6 | ||
|
|
452e544ed1 | ||
|
|
6b60509122 | ||
|
|
e01e971822 | ||
|
|
186a7db8cb | ||
|
|
565452f7d3 | ||
|
|
de7e3dd225 | ||
|
|
8bdf04a289 | ||
|
|
56a5b8df87 | ||
|
|
ad241a14e3 | ||
|
|
2a7bdcd270 | ||
|
|
88c61ecfb5 | ||
|
|
bee9dbb9c4 | ||
|
|
36e0f97d82 | ||
|
|
8bb08a2af9 | ||
|
|
6bf2cb69c5 | ||
|
|
7a1247e325 | ||
|
|
ce5879bdb5 | ||
|
|
55221a5066 | ||
|
|
9e7f1df8e9 | ||
|
|
ec7e599143 | ||
|
|
e0a67d1eb9 | ||
|
|
a50ded2d50 | ||
|
|
dab9a3e73d | ||
|
|
dae8e6d759 | ||
|
|
60d38b034d | ||
|
|
863c908267 | ||
|
|
753068288f | ||
|
|
f9eda0b096 | ||
|
|
f4401ee524 | ||
|
|
3dbc3cef56 | ||
|
|
bf4ecd21aa | ||
|
|
ea3ada5573 | ||
|
|
9889f86f69 | ||
|
|
4d62447a86 | ||
|
|
7ec05ac0cd | ||
|
|
1b2a1d658d | ||
|
|
a2bbc5e455 | ||
|
|
006cb067ad | ||
|
|
a2e422a23e | ||
|
|
02cec9bacb | ||
|
|
fcd9b20161 | ||
|
|
204711e5db | ||
|
|
64c129d399 | ||
|
|
6bdb099aa9 | ||
|
|
3e55be8a82 | ||
|
|
8f37957a46 | ||
|
|
a5386e0183 | ||
|
|
117e88bf1f | ||
|
|
f47900c4d3 | ||
|
|
f641e16512 | ||
|
|
df0613f290 | ||
|
|
711475d45a | ||
|
|
3fd47fc1c7 | ||
|
|
a1a946edea | ||
|
|
699b037a3e | ||
|
|
e474042af7 | ||
|
|
68634e0f28 | ||
|
|
1d81110fe5 | ||
|
|
030b9ef053 | ||
|
|
7d8d169b0a | ||
|
|
11e2d4ecfb | ||
|
|
80ad5d8c7b | ||
|
|
53a15c05aa | ||
|
|
45bc6a51b0 | ||
|
|
db21dcedb1 | ||
|
|
96c8274091 | ||
|
|
0c7c465ef7 | ||
|
|
fd6bf42ea4 | ||
|
|
0c5976b287 | ||
|
|
1a4aa7f065 | ||
|
|
f11a97631f | ||
|
|
bf953012af | ||
|
|
2f049fbead | ||
|
|
49a6f2af3a | ||
|
|
3d6c02c27c | ||
|
|
a1cfc0ec1d | ||
|
|
2f584501fe | ||
|
|
b524cec6af | ||
|
|
0a7b810fce | ||
|
|
f7a3b0f6ca | ||
|
|
b095dab77a | ||
|
|
b6b7ab622d | ||
|
|
55e674624a | ||
|
|
88a799df45 | ||
|
|
4bb726ac9a | ||
|
|
5a53665e96 | ||
|
|
cf51a1e299 | ||
|
|
04802cc8cd | ||
|
|
d0c1be0c32 | ||
|
|
0b9894a0f6 | ||
|
|
1a912a29f8 | ||
|
|
596c140310 | ||
|
|
302ced1476 | ||
|
|
3178c16bac | ||
|
|
341d18656d | ||
|
|
074755c0a8 | ||
|
|
5d528fce74 | ||
|
|
c80edc99b2 | ||
|
|
33378d9920 | ||
|
|
85a6e394b9 | ||
|
|
f2f98f28a3 | ||
|
|
d92e042971 | ||
|
|
6d6e47409f | ||
|
|
0084e51263 | ||
|
|
32e0b39771 | ||
|
|
7bb51f77ec | ||
|
|
c777a57a7d | ||
|
|
def5966767 | ||
|
|
14b18e9d42 | ||
|
|
fcff8d92f3 | ||
|
|
ea0e00f4ac | ||
|
|
f199105f6c | ||
|
|
b23c0b599b | ||
|
|
0327d4f38a | ||
|
|
4397a45519 | ||
|
|
365e159539 | ||
|
|
8171dd8bc9 | ||
|
|
c4beb60c22 | ||
|
|
4b88f0d9f1 | ||
|
|
116419f505 | ||
|
|
87b2624aa8 | ||
|
|
71fe6f3148 | ||
|
|
21df6cb809 | ||
|
|
1f0c385a5c | ||
|
|
986ea05fb2 | ||
|
|
90e395506f | ||
|
|
cf54360a93 | ||
|
|
a5d4ea4477 | ||
|
|
030b52261d | ||
|
|
a50e13c2e3 | ||
|
|
c8326103b4 | ||
|
|
0f2beee813 | ||
|
|
d2348a4e7d | ||
|
|
cc87e6fd1f | ||
|
|
1843aea2a7 | ||
|
|
9f7021acd3 | ||
|
|
ddea031cbf | ||
|
|
f0d8532f71 | ||
|
|
4883d157dc | ||
|
|
a1bec1e927 | ||
|
|
48918f4727 | ||
|
|
303b5bd395 | ||
|
|
37d4d79c64 | ||
|
|
05bc3f6a71 | ||
|
|
8b3960781a | ||
|
|
97733a52c8 | ||
|
|
89dcbec599 | ||
|
|
6497809fe1 | ||
|
|
f3de4f53c5 | ||
|
|
166fc2948c | ||
|
|
9f3a63d8c4 | ||
|
|
748fa77d94 | ||
|
|
4ca86ee4eb | ||
|
|
ec2f372933 | ||
|
|
4267800db2 | ||
|
|
bb8cb9bcbb | ||
|
|
d5b9e15ee1 | ||
|
|
43ee3246f6 | ||
|
|
b56724dee5 | ||
|
|
92748ac872 | ||
|
|
b89686c287 | ||
|
|
a34692630b | ||
|
|
735208562a | ||
|
|
49826fdc56 | ||
|
|
e8c54a609c | ||
|
|
ece2c51358 | ||
|
|
3ec8af4661 | ||
|
|
77a08596fe | ||
|
|
879f699b2b | ||
|
|
d7383a3361 | ||
|
|
a5b321a93b | ||
|
|
5fa6b0ca1c | ||
|
|
27328afe3c | ||
|
|
2d26af1ae2 | ||
|
|
6db8f33e8f | ||
|
|
d6a7e6d52c | ||
|
|
df99b3b666 | ||
|
|
0f1c9f4fe2 | ||
|
|
5dcd5f79dc | ||
|
|
8a81171739 | ||
|
|
1c4f20f76f | ||
|
|
f84fa588f6 | ||
|
|
e30e34f342 | ||
|
|
fc93ced067 | ||
|
|
bb7df72d31 | ||
|
|
f8425658e4 | ||
|
|
53c8cf09b6 | ||
|
|
9f29bf4949 | ||
|
|
98e2adf794 | ||
|
|
2a43e0b0ed | ||
|
|
773ae73820 | ||
|
|
009db57bc5 | ||
|
|
5e98126e77 | ||
|
|
bd7ebfd83a | ||
|
|
10f41ef157 | ||
|
|
1dd4960109 | ||
|
|
75413b6c86 | ||
|
|
b2180582a7 | ||
|
|
8211ce7ae3 | ||
|
|
e6b1597fa7 | ||
|
|
8937d3cd9c | ||
|
|
51f320d147 | ||
|
|
e402a894bb | ||
|
|
9b577f1219 | ||
|
|
220f678403 | ||
|
|
4173fc4daa | ||
|
|
c6756d2145 | ||
|
|
6731f6eeb5 | ||
|
|
6f7f8b40e3 | ||
|
|
1a83b2c99b | ||
|
|
f641fae1c7 | ||
|
|
deb43d9872 | ||
|
|
cee4e1305e | ||
|
|
a1f989c43c | ||
|
|
b67abadbac | ||
|
|
8c29c85696 | ||
|
|
4fe4c298d7 | ||
|
|
13d35229d5 | ||
|
|
f0137b41b6 | ||
|
|
b221d21903 | ||
|
|
8bac202626 | ||
|
|
973151c949 | ||
|
|
ed26ab78a5 | ||
|
|
8454b2d235 | ||
|
|
91d0f89f60 | ||
|
|
e074672e86 | ||
|
|
6c1901fe5b | ||
|
|
49052be627 | ||
|
|
5b5b540630 | ||
|
|
9993bac3a1 | ||
|
|
3c95988693 | ||
|
|
fc5c3b470e | ||
|
|
53f05a72ba | ||
|
|
2c10ae7d06 | ||
|
|
6b9010c557 | ||
|
|
1bf0fdfa81 | ||
|
|
237759aac0 | ||
|
|
2a141e0a97 | ||
|
|
d6900be68e | ||
|
|
a35d7c7204 | ||
|
|
86287f9241 | ||
|
|
0b2e3dd96f | ||
|
|
90aa1d1ce7 | ||
|
|
5c51259269 | ||
|
|
7eefa07052 | ||
|
|
eb9d0c00a8 |
@@ -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:
|
||||||
|
|||||||
23
.idea/runConfigurations/H2_Performance_Test.xml
generated
Normal file
23
.idea/runConfigurations/H2_Performance_Test.xml
generated
Normal 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>
|
||||||
23
.idea/runConfigurations/HyperSQL_Performance_Test.xml
generated
Normal file
23
.idea/runConfigurations/HyperSQL_Performance_Test.xml
generated
Normal 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>
|
||||||
@@ -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 1610
|
versionCode 1700
|
||||||
versionName "0.16.10"
|
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-09-06.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.12-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.12-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.12-x86.zip'
|
checksum chksum
|
||||||
dest "$torBinaryDir/tor_x86.zip"
|
}
|
||||||
onlyIfNewer true
|
|
||||||
}
|
|
||||||
|
|
||||||
task downloadTorBinaryX86Pie(type: Download) {
|
|
||||||
src 'https://briarproject.org/build/tor-0.2.9.12-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 'fe49d3adb86d3c512373101422a017dbb86c85a570524663f09dd8ce143a24f3'
|
|
||||||
}
|
|
||||||
|
|
||||||
task verifyTorBinaryArm(type: Verify, dependsOn: 'downloadTorBinaryArm') {
|
|
||||||
src "$torBinaryDir/tor_arm.zip"
|
|
||||||
algorithm 'SHA-256'
|
|
||||||
checksum '8ed0b347ffed1d6a4d2fd14495118eb92be83e9cc06e057e15220dc288b31688'
|
|
||||||
}
|
|
||||||
|
|
||||||
task verifyTorBinaryArmPie(type: Verify, dependsOn: 'downloadTorBinaryArmPie') {
|
|
||||||
src "$torBinaryDir/tor_arm_pie.zip"
|
|
||||||
algorithm 'SHA-256'
|
|
||||||
checksum '64403262511c29f462ca5e7c7621bfc3c944898364d1d5ad35a016bb8a034283'
|
|
||||||
}
|
|
||||||
|
|
||||||
task verifyTorBinaryX86(type: Verify, dependsOn: 'downloadTorBinaryX86') {
|
|
||||||
src "$torBinaryDir/tor_x86.zip"
|
|
||||||
algorithm 'SHA-256'
|
|
||||||
checksum '61e014607a2079bcf1646289c67bff6372b1aded6e1d8d83d7791efda9a4d5ab'
|
|
||||||
}
|
|
||||||
|
|
||||||
task verifyTorBinaryX86Pie(type: Verify, dependsOn: 'downloadTorBinaryX86Pie') {
|
|
||||||
src "$torBinaryDir/tor_x86_pie.zip"
|
|
||||||
algorithm 'SHA-256'
|
|
||||||
checksum '18fbc98356697dd0895836ab46d5c9877d1c539193464f7db1e82a65adaaf288'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
project.afterEvaluate {
|
project.afterEvaluate {
|
||||||
preBuild.dependsOn {
|
torBinaries.every { key, value ->
|
||||||
[
|
preBuild.dependsOn.add(verifyBinary(key, value))
|
||||||
'verifyTorGeoIp',
|
|
||||||
'verifyTorBinaryArm',
|
|
||||||
'verifyTorBinaryArmPie',
|
|
||||||
'verifyTorBinaryX86',
|
|
||||||
'verifyTorBinaryX86Pie'
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.**
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -1,473 +0,0 @@
|
|||||||
package org.briarproject.bramble.plugin.droidtooth;
|
|
||||||
|
|
||||||
import android.bluetooth.BluetoothAdapter;
|
|
||||||
import android.bluetooth.BluetoothDevice;
|
|
||||||
import android.bluetooth.BluetoothServerSocket;
|
|
||||||
import android.bluetooth.BluetoothSocket;
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.IntentFilter;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
|
||||||
import org.briarproject.bramble.api.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.security.SecureRandom;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.STATE_OFF;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
|
||||||
import static java.util.logging.Level.INFO;
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
|
|
||||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
|
|
||||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENABLE;
|
|
||||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
|
|
||||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
|
|
||||||
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
|
|
||||||
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
|
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
|
||||||
@ParametersNotNullByDefault
|
|
||||||
class DroidtoothPlugin implements DuplexPlugin {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
Logger.getLogger(DroidtoothPlugin.class.getName());
|
|
||||||
|
|
||||||
private final Executor ioExecutor;
|
|
||||||
private final AndroidExecutor androidExecutor;
|
|
||||||
private final Context appContext;
|
|
||||||
private final SecureRandom secureRandom;
|
|
||||||
private final Backoff backoff;
|
|
||||||
private final DuplexPluginCallback callback;
|
|
||||||
private final int maxLatency;
|
|
||||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
private volatile boolean running = false;
|
|
||||||
private volatile boolean wasEnabledByUs = false;
|
|
||||||
private volatile BluetoothStateReceiver receiver = null;
|
|
||||||
private volatile BluetoothServerSocket socket = null;
|
|
||||||
|
|
||||||
// Non-null if the plugin started successfully
|
|
||||||
private volatile BluetoothAdapter adapter = null;
|
|
||||||
|
|
||||||
DroidtoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor,
|
|
||||||
Context appContext, SecureRandom secureRandom, Backoff backoff,
|
|
||||||
DuplexPluginCallback callback, int maxLatency) {
|
|
||||||
this.ioExecutor = ioExecutor;
|
|
||||||
this.androidExecutor = androidExecutor;
|
|
||||||
this.appContext = appContext;
|
|
||||||
this.secureRandom = secureRandom;
|
|
||||||
this.backoff = backoff;
|
|
||||||
this.callback = callback;
|
|
||||||
this.maxLatency = maxLatency;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TransportId getId() {
|
|
||||||
return ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMaxLatency() {
|
|
||||||
return maxLatency;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMaxIdleTime() {
|
|
||||||
// Bluetooth detects dead connections so we don't need keepalives
|
|
||||||
return Integer.MAX_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() throws PluginException {
|
|
||||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
|
||||||
// BluetoothAdapter.getDefaultAdapter() must be called on a thread
|
|
||||||
// with a message queue, so submit it to the AndroidExecutor
|
|
||||||
try {
|
|
||||||
adapter = androidExecutor.runOnBackgroundThread(
|
|
||||||
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 supportsKeyAgreement() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
|
|
||||||
if (!isRunning()) return null;
|
|
||||||
// There's no point listening if we can't discover our own address
|
|
||||||
String address = AndroidUtils.getBluetoothAddress(appContext, adapter);
|
|
||||||
if (address.isEmpty()) return null;
|
|
||||||
// No truncation necessary because COMMIT_LENGTH = 16
|
|
||||||
UUID uuid = UUID.nameUUIDFromBytes(commitment);
|
|
||||||
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
|
|
||||||
// Bind a server socket for receiving key agreement connections
|
|
||||||
BluetoothServerSocket ss;
|
|
||||||
try {
|
|
||||||
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
|
|
||||||
"RFCOMM", uuid);
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
BdfList descriptor = new BdfList();
|
|
||||||
descriptor.add(TRANSPORT_ID_BLUETOOTH);
|
|
||||||
descriptor.add(StringUtils.macToBytes(address));
|
|
||||||
return new BluetoothKeyAgreementListener(descriptor, ss);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DuplexTransportConnection createKeyAgreementConnection(
|
|
||||||
byte[] commitment, BdfList descriptor, long timeout) {
|
|
||||||
if (!isRunning()) return null;
|
|
||||||
String address;
|
|
||||||
try {
|
|
||||||
address = parseAddress(descriptor);
|
|
||||||
} catch (FormatException e) {
|
|
||||||
LOG.info("Invalid address in key agreement descriptor");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// No truncation necessary because COMMIT_LENGTH = 16
|
|
||||||
UUID uuid = UUID.nameUUIDFromBytes(commitment);
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Connecting to key agreement UUID " + uuid);
|
|
||||||
BluetoothSocket s = connect(address, uuid.toString());
|
|
||||||
if (s == null) return null;
|
|
||||||
return new DroidtoothTransportConnection(this, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String parseAddress(BdfList descriptor) throws FormatException {
|
|
||||||
byte[] mac = descriptor.getRaw(1);
|
|
||||||
if (mac.length != 6) throw new FormatException();
|
|
||||||
return StringUtils.macToString(mac);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class BluetoothStateReceiver extends BroadcastReceiver {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context ctx, Intent intent) {
|
|
||||||
int state = intent.getIntExtra(EXTRA_STATE, 0);
|
|
||||||
if (state == STATE_ON) {
|
|
||||||
LOG.info("Bluetooth enabled");
|
|
||||||
bind();
|
|
||||||
} else if (state == STATE_OFF) {
|
|
||||||
LOG.info("Bluetooth disabled");
|
|
||||||
tryToClose(socket);
|
|
||||||
}
|
|
||||||
int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, 0);
|
|
||||||
if (scanMode == SCAN_MODE_NONE) {
|
|
||||||
LOG.info("Scan mode: None");
|
|
||||||
} else if (scanMode == SCAN_MODE_CONNECTABLE) {
|
|
||||||
LOG.info("Scan mode: Connectable");
|
|
||||||
} else if (scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
|
|
||||||
LOG.info("Scan mode: Discoverable");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class BluetoothKeyAgreementListener extends KeyAgreementListener {
|
|
||||||
|
|
||||||
private final BluetoothServerSocket ss;
|
|
||||||
|
|
||||||
private BluetoothKeyAgreementListener(BdfList descriptor,
|
|
||||||
BluetoothServerSocket ss) {
|
|
||||||
super(descriptor);
|
|
||||||
this.ss = ss;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Callable<KeyAgreementConnection> listen() {
|
|
||||||
return 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -55,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;
|
||||||
@@ -69,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;
|
||||||
@@ -84,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"
|
||||||
};
|
};
|
||||||
@@ -101,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;
|
||||||
@@ -113,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;
|
||||||
@@ -121,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;
|
||||||
@@ -148,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
|
||||||
@@ -202,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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,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();
|
||||||
@@ -368,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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -538,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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -559,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()) {
|
||||||
@@ -600,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -624,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
|
||||||
@@ -663,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();
|
||||||
}
|
}
|
||||||
@@ -681,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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
}
|
||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package org.briarproject.bramble.api.crypto;
|
package org.briarproject.bramble.api.crypto;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
|
||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
public interface CryptoComponent {
|
public interface CryptoComponent {
|
||||||
|
|
||||||
SecretKey generateSecretKey();
|
SecretKey generateSecretKey();
|
||||||
@@ -23,127 +25,46 @@ public interface CryptoComponent {
|
|||||||
KeyParser getMessageKeyParser();
|
KeyParser getMessageKeyParser();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Derives a stream header key from the given master secret.
|
* Derives another secret key from the given secret key.
|
||||||
* @param alice whether the key is for use by Alice or Bob.
|
|
||||||
*/
|
|
||||||
SecretKey deriveHeaderKey(SecretKey master, boolean alice);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derives a message authentication code key from the given master secret.
|
|
||||||
* @param alice whether the key is for use by Alice or Bob.
|
|
||||||
*/
|
|
||||||
SecretKey deriveMacKey(SecretKey master, boolean alice);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derives a nonce from the given master secret for one of the parties to
|
|
||||||
* sign.
|
|
||||||
* @param alice whether the nonce is for use by Alice or Bob.
|
|
||||||
*/
|
|
||||||
byte[] deriveSignatureNonce(SecretKey master, boolean alice);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derives a commitment to the provided public key.
|
|
||||||
* <p/>
|
|
||||||
* Part of BQP.
|
|
||||||
*
|
*
|
||||||
* @param publicKey the public key
|
* @param label a namespaced label indicating the purpose of the derived
|
||||||
* @return the commitment to the provided public key.
|
* key, to prevent it from being repurposed or colliding with a key derived
|
||||||
|
* for another purpose
|
||||||
*/
|
*/
|
||||||
byte[] deriveKeyCommitment(byte[] publicKey);
|
SecretKey deriveKey(String label, SecretKey k, byte[]... inputs);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Derives a common shared secret from two public keys and one of the
|
* Derives a common shared secret from two public keys and one of the
|
||||||
* corresponding private keys.
|
* corresponding private keys.
|
||||||
* <p/>
|
|
||||||
* Part of BQP.
|
|
||||||
*
|
*
|
||||||
* @param theirPublicKey the ephemeral public key of the remote party
|
* @param label a namespaced label indicating the purpose of this shared
|
||||||
* @param ourKeyPair our ephemeral keypair
|
* secret, to prevent it from being repurposed or colliding with a shared
|
||||||
* @param alice true if ourKeyPair belongs to Alice
|
* secret derived for another purpose
|
||||||
|
* @param theirPublicKey the public key of the remote party
|
||||||
|
* @param ourKeyPair the key pair of the local party
|
||||||
* @return the shared secret
|
* @return the shared secret
|
||||||
* @throws GeneralSecurityException
|
|
||||||
*/
|
*/
|
||||||
SecretKey deriveSharedSecret(byte[] theirPublicKey, KeyPair ourKeyPair,
|
SecretKey deriveSharedSecret(String label, PublicKey theirPublicKey,
|
||||||
boolean alice) throws GeneralSecurityException;
|
KeyPair ourKeyPair, byte[]... inputs)
|
||||||
|
throws GeneralSecurityException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Derives the content of a confirmation record.
|
* Signs the given byte[] with the given private key.
|
||||||
* <p/>
|
|
||||||
* Part of BQP.
|
|
||||||
*
|
*
|
||||||
* @param sharedSecret the common shared secret
|
* @param label a namespaced label indicating the purpose of this
|
||||||
* @param theirPayload the commit payload from the remote party
|
* signature, to prevent it from being repurposed or colliding with a
|
||||||
* @param ourPayload the commit payload we sent
|
* signature created for another purpose
|
||||||
* @param theirPublicKey the ephemeral public key of the remote party
|
|
||||||
* @param ourKeyPair our ephemeral keypair
|
|
||||||
* @param alice true if ourKeyPair belongs to Alice
|
|
||||||
* @param aliceRecord true if the confirmation record is for use by Alice
|
|
||||||
* @return the confirmation record
|
|
||||||
*/
|
|
||||||
byte[] deriveConfirmationRecord(SecretKey sharedSecret,
|
|
||||||
byte[] theirPayload, byte[] ourPayload,
|
|
||||||
byte[] theirPublicKey, KeyPair ourKeyPair,
|
|
||||||
boolean alice, boolean aliceRecord);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derives a master secret from the given shared secret.
|
|
||||||
* <p/>
|
|
||||||
* Part of BQP.
|
|
||||||
*
|
|
||||||
* @param sharedSecret the common shared secret
|
|
||||||
* @return the master secret
|
|
||||||
*/
|
|
||||||
SecretKey deriveMasterSecret(SecretKey sharedSecret);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derives a master secret from two public keys and one of the corresponding
|
|
||||||
* private keys.
|
|
||||||
* <p/>
|
|
||||||
* This is a helper method that calls
|
|
||||||
* deriveMasterSecret(deriveSharedSecret(theirPublicKey, ourKeyPair, alice))
|
|
||||||
*
|
|
||||||
* @param theirPublicKey the ephemeral public key of the remote party
|
|
||||||
* @param ourKeyPair our ephemeral keypair
|
|
||||||
* @param alice true if ourKeyPair belongs to Alice
|
|
||||||
* @return the shared secret
|
|
||||||
* @throws GeneralSecurityException
|
|
||||||
*/
|
|
||||||
SecretKey deriveMasterSecret(byte[] theirPublicKey, KeyPair ourKeyPair,
|
|
||||||
boolean alice) throws GeneralSecurityException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derives initial transport keys for the given transport in the given
|
|
||||||
* rotation period from the given master secret.
|
|
||||||
* @param alice whether the keys are for use by Alice or Bob.
|
|
||||||
*/
|
|
||||||
TransportKeys deriveTransportKeys(TransportId t, SecretKey master,
|
|
||||||
long rotationPeriod, boolean alice);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rotates the given transport keys to the given rotation period. If the
|
|
||||||
* keys are for a future rotation period they are not rotated.
|
|
||||||
*/
|
|
||||||
TransportKeys rotateTransportKeys(TransportKeys k, long rotationPeriod);
|
|
||||||
|
|
||||||
/** Encodes the pseudo-random tag that is used to recognise a stream. */
|
|
||||||
void encodeTag(byte[] tag, SecretKey tagKey, int protocolVersion,
|
|
||||||
long streamNumber);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signs the given byte[] with the given PrivateKey.
|
|
||||||
*
|
|
||||||
* @param label A label specific to this signature
|
|
||||||
* to ensure that the signature cannot be repurposed
|
|
||||||
*/
|
*/
|
||||||
byte[] sign(String label, byte[] toSign, byte[] privateKey)
|
byte[] sign(String label, byte[] toSign, byte[] privateKey)
|
||||||
throws GeneralSecurityException;
|
throws GeneralSecurityException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies that the given signature is valid for the signedData
|
* Verifies that the given signature is valid for the signed data
|
||||||
* and the given publicKey.
|
* and the given public key.
|
||||||
*
|
*
|
||||||
* @param label A label that was specific to this signature
|
* @param label a namespaced label indicating the purpose of this
|
||||||
* to ensure that the signature cannot be repurposed
|
* signature, to prevent it from being repurposed or colliding with a
|
||||||
|
* signature created for another purpose
|
||||||
* @return true if the signature was valid, false otherwise.
|
* @return true if the signature was valid, false otherwise.
|
||||||
*/
|
*/
|
||||||
boolean verify(String label, byte[] signedData, byte[] publicKey,
|
boolean verify(String label, byte[] signedData, byte[] publicKey,
|
||||||
@@ -153,23 +74,22 @@ public interface CryptoComponent {
|
|||||||
* Returns the hash of the given inputs. The inputs are unambiguously
|
* Returns the hash of the given inputs. The inputs are unambiguously
|
||||||
* combined by prefixing each input with its length.
|
* combined by prefixing each input with its length.
|
||||||
*
|
*
|
||||||
* @param label A label specific to this hash to ensure that hashes
|
* @param label a namespaced label indicating the purpose of this hash, to
|
||||||
* calculated for distinct purposes don't collide.
|
* prevent it from being repurposed or colliding with a hash created for
|
||||||
|
* another purpose
|
||||||
*/
|
*/
|
||||||
byte[] hash(String label, byte[]... inputs);
|
byte[] hash(String label, byte[]... inputs);
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the length of hashes produced by
|
|
||||||
* the {@link CryptoComponent#hash(String, byte[]...)} method.
|
|
||||||
*/
|
|
||||||
int getHashLength();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a message authentication code with the given key over the
|
* Returns a message authentication code with the given key over the
|
||||||
* given inputs. The inputs are unambiguously combined by prefixing each
|
* given inputs. The inputs are unambiguously combined by prefixing each
|
||||||
* input with its length.
|
* input with its length.
|
||||||
|
*
|
||||||
|
* @param label a namespaced label indicating the purpose of this MAC, to
|
||||||
|
* prevent it from being repurposed or colliding with a MAC created for
|
||||||
|
* another purpose
|
||||||
*/
|
*/
|
||||||
byte[] mac(SecretKey macKey, byte[]... inputs);
|
byte[] mac(String label, SecretKey macKey, byte[]... inputs);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypts and authenticates the given plaintext so it can be written to
|
* Encrypts and authenticates the given plaintext so it can be written to
|
||||||
@@ -185,6 +105,7 @@ public interface CryptoComponent {
|
|||||||
* given password. Returns null if the ciphertext cannot be decrypted and
|
* given password. Returns null if the ciphertext cannot be decrypted and
|
||||||
* authenticated (for example, if the password is wrong).
|
* authenticated (for example, if the password is wrong).
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
byte[] decryptWithPassword(byte[] ciphertext, String password);
|
byte[] decryptWithPassword(byte[] ciphertext, String password);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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 {
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
|
||||||
@@ -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();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 {
|
|
||||||
}
|
|
||||||
@@ -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";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ public interface DuplexPlugin extends Plugin {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to connect to the remote peer specified in the given descriptor.
|
* Attempts to connect to the remote peer specified in the given descriptor.
|
||||||
* Returns null if no connection can be established within the given time.
|
* Returns null if no connection can be established.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
DuplexTransportConnection createKeyAgreementConnection(
|
DuplexTransportConnection createKeyAgreementConnection(
|
||||||
byte[] remoteCommitment, BdfList descriptor, long timeout);
|
byte[] remoteCommitment, BdfList descriptor);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -36,4 +36,8 @@ public class ClientId implements Comparable<ClientId> {
|
|||||||
return id.hashCode();
|
return id.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||||
@@ -141,8 +142,10 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Derive the header keys for the transport streams
|
// Derive the header keys for the transport streams
|
||||||
SecretKey aliceHeaderKey = crypto.deriveHeaderKey(masterSecret, true);
|
SecretKey aliceHeaderKey = crypto.deriveKey(ALICE_KEY_LABEL,
|
||||||
SecretKey bobHeaderKey = crypto.deriveHeaderKey(masterSecret, false);
|
masterSecret, new byte[] {PROTOCOL_VERSION});
|
||||||
|
SecretKey bobHeaderKey = crypto.deriveKey(BOB_KEY_LABEL, masterSecret,
|
||||||
|
new byte[] {PROTOCOL_VERSION});
|
||||||
|
|
||||||
// Create the readers
|
// Create the readers
|
||||||
InputStream streamReader =
|
InputStream streamReader =
|
||||||
@@ -156,8 +159,10 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
BdfWriter w = bdfWriterFactory.createWriter(streamWriter);
|
BdfWriter w = bdfWriterFactory.createWriter(streamWriter);
|
||||||
|
|
||||||
// Derive the nonces to be signed
|
// Derive the nonces to be signed
|
||||||
byte[] aliceNonce = crypto.deriveSignatureNonce(masterSecret, true);
|
byte[] aliceNonce = crypto.mac(ALICE_NONCE_LABEL, masterSecret,
|
||||||
byte[] bobNonce = crypto.deriveSignatureNonce(masterSecret, false);
|
new byte[] {PROTOCOL_VERSION});
|
||||||
|
byte[] bobNonce = crypto.mac(BOB_NONCE_LABEL, masterSecret,
|
||||||
|
new byte[] {PROTOCOL_VERSION});
|
||||||
|
|
||||||
// Exchange pseudonyms, signed nonces, and timestamps
|
// Exchange pseudonyms, signed nonces, and timestamps
|
||||||
long localTimestamp = clock.currentTimeMillis();
|
long localTimestamp = clock.currentTimeMillis();
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,107 +1,59 @@
|
|||||||
package org.briarproject.bramble.crypto;
|
package org.briarproject.bramble.crypto;
|
||||||
|
|
||||||
|
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
|
||||||
|
import net.i2p.crypto.eddsa.EdDSAPublicKey;
|
||||||
|
import net.i2p.crypto.eddsa.KeyPairGenerator;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
import org.briarproject.bramble.api.crypto.KeyParser;
|
||||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
||||||
import org.briarproject.bramble.api.transport.IncomingKeys;
|
|
||||||
import org.briarproject.bramble.api.transport.OutgoingKeys;
|
|
||||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
|
||||||
import org.briarproject.bramble.util.ByteUtils;
|
import org.briarproject.bramble.util.ByteUtils;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
|
|
||||||
import org.spongycastle.crypto.CipherParameters;
|
|
||||||
import org.spongycastle.crypto.CryptoException;
|
import org.spongycastle.crypto.CryptoException;
|
||||||
import org.spongycastle.crypto.Digest;
|
import org.spongycastle.crypto.Digest;
|
||||||
import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
|
import org.spongycastle.crypto.digests.Blake2bDigest;
|
||||||
import org.spongycastle.crypto.digests.SHA256Digest;
|
import org.whispersystems.curve25519.Curve25519;
|
||||||
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
|
import org.whispersystems.curve25519.Curve25519KeyPair;
|
||||||
import org.spongycastle.crypto.generators.PKCS5S2ParametersGenerator;
|
|
||||||
import org.spongycastle.crypto.params.ECKeyGenerationParameters;
|
|
||||||
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
|
|
||||||
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
|
||||||
import org.spongycastle.crypto.params.KeyParameter;
|
|
||||||
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.Provider;
|
import java.security.Provider;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
|
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
|
||||||
import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS;
|
|
||||||
import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
|
|
||||||
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
|
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
|
||||||
import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
|
|
||||||
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
|
|
||||||
import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
class CryptoComponentImpl implements CryptoComponent {
|
class CryptoComponentImpl implements CryptoComponent {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(CryptoComponentImpl.class.getName());
|
Logger.getLogger(CryptoComponentImpl.class.getName());
|
||||||
|
|
||||||
private static final int AGREEMENT_KEY_PAIR_BITS = 256;
|
|
||||||
private static final int SIGNATURE_KEY_PAIR_BITS = 256;
|
private static final int SIGNATURE_KEY_PAIR_BITS = 256;
|
||||||
private static final int STORAGE_IV_BYTES = 24; // 196 bits
|
private static final int STORAGE_IV_BYTES = 24; // 196 bits
|
||||||
private static final int PBKDF_SALT_BYTES = 32; // 256 bits
|
private static final int PBKDF_SALT_BYTES = 32; // 256 bits
|
||||||
private static final int PBKDF_TARGET_MILLIS = 500;
|
private static final int PBKDF_FORMAT_SCRYPT = 0;
|
||||||
private static final int PBKDF_SAMPLES = 30;
|
|
||||||
private static final int HASH_SIZE = 256 / 8;
|
|
||||||
|
|
||||||
private static byte[] ascii(String s) {
|
|
||||||
return s.getBytes(Charset.forName("US-ASCII"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// KDF labels for contact exchange stream header key derivation
|
|
||||||
private static final byte[] A_INVITE = ascii("ALICE_INVITATION_KEY");
|
|
||||||
private static final byte[] B_INVITE = ascii("BOB_INVITATION_KEY");
|
|
||||||
// KDF labels for contact exchange signature nonce derivation
|
|
||||||
private static final byte[] A_SIG_NONCE = ascii("ALICE_SIGNATURE_NONCE");
|
|
||||||
private static final byte[] B_SIG_NONCE = ascii("BOB_SIGNATURE_NONCE");
|
|
||||||
// Hash label for BQP public key commitment derivation
|
|
||||||
private static final String COMMIT =
|
|
||||||
"org.briarproject.bramble.COMMIT";
|
|
||||||
// Hash label for shared secret derivation
|
|
||||||
private static final String SHARED_SECRET =
|
|
||||||
"org.briarproject.bramble.SHARED_SECRET";
|
|
||||||
// KDF label for BQP confirmation key derivation
|
|
||||||
private static final byte[] CONFIRMATION_KEY = ascii("CONFIRMATION_KEY");
|
|
||||||
// KDF label for master key derivation
|
|
||||||
private static final byte[] MASTER_KEY = ascii("MASTER_KEY");
|
|
||||||
// KDF labels for tag key derivation
|
|
||||||
private static final byte[] A_TAG = ascii("ALICE_TAG_KEY");
|
|
||||||
private static final byte[] B_TAG = ascii("BOB_TAG_KEY");
|
|
||||||
// KDF labels for header key derivation
|
|
||||||
private static final byte[] A_HEADER = ascii("ALICE_HEADER_KEY");
|
|
||||||
private static final byte[] B_HEADER = ascii("BOB_HEADER_KEY");
|
|
||||||
// KDF labels for MAC key derivation
|
|
||||||
private static final byte[] A_MAC = ascii("ALICE_MAC_KEY");
|
|
||||||
private static final byte[] B_MAC = ascii("BOB_MAC_KEY");
|
|
||||||
// KDF label for key rotation
|
|
||||||
private static final byte[] ROTATE = ascii("ROTATE");
|
|
||||||
|
|
||||||
private final SecureRandom secureRandom;
|
private final SecureRandom secureRandom;
|
||||||
private final ECKeyPairGenerator agreementKeyPairGenerator;
|
private final PasswordBasedKdf passwordBasedKdf;
|
||||||
private final ECKeyPairGenerator signatureKeyPairGenerator;
|
private final Curve25519 curve25519;
|
||||||
|
private final KeyPairGenerator signatureKeyPairGenerator;
|
||||||
private final KeyParser agreementKeyParser, signatureKeyParser;
|
private final KeyParser agreementKeyParser, signatureKeyParser;
|
||||||
private final MessageEncrypter messageEncrypter;
|
private final MessageEncrypter messageEncrypter;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
CryptoComponentImpl(SecureRandomProvider secureRandomProvider) {
|
CryptoComponentImpl(SecureRandomProvider secureRandomProvider,
|
||||||
|
PasswordBasedKdf passwordBasedKdf) {
|
||||||
if (LOG.isLoggable(INFO)) {
|
if (LOG.isLoggable(INFO)) {
|
||||||
SecureRandom defaultSecureRandom = new SecureRandom();
|
SecureRandom defaultSecureRandom = new SecureRandom();
|
||||||
String name = defaultSecureRandom.getProvider().getName();
|
String name = defaultSecureRandom.getProvider().getName();
|
||||||
@@ -121,16 +73,13 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
secureRandom = new SecureRandom();
|
secureRandom = new SecureRandom();
|
||||||
ECKeyGenerationParameters params = new ECKeyGenerationParameters(
|
this.passwordBasedKdf = passwordBasedKdf;
|
||||||
PARAMETERS, secureRandom);
|
curve25519 = Curve25519.getInstance("java");
|
||||||
agreementKeyPairGenerator = new ECKeyPairGenerator();
|
signatureKeyPairGenerator = new KeyPairGenerator();
|
||||||
agreementKeyPairGenerator.init(params);
|
signatureKeyPairGenerator.initialize(SIGNATURE_KEY_PAIR_BITS,
|
||||||
signatureKeyPairGenerator = new ECKeyPairGenerator();
|
secureRandom);
|
||||||
signatureKeyPairGenerator.init(params);
|
agreementKeyParser = new Curve25519KeyParser();
|
||||||
agreementKeyParser = new Sec1KeyParser(PARAMETERS,
|
signatureKeyParser = new EdKeyParser();
|
||||||
AGREEMENT_KEY_PAIR_BITS);
|
|
||||||
signatureKeyParser = new Sec1KeyParser(PARAMETERS,
|
|
||||||
SIGNATURE_KEY_PAIR_BITS);
|
|
||||||
messageEncrypter = new MessageEncrypter(secureRandom);
|
messageEncrypter = new MessageEncrypter(secureRandom);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,16 +123,17 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
// Package access for testing
|
// Package access for testing
|
||||||
byte[] performRawKeyAgreement(PrivateKey priv, PublicKey pub)
|
byte[] performRawKeyAgreement(PrivateKey priv, PublicKey pub)
|
||||||
throws GeneralSecurityException {
|
throws GeneralSecurityException {
|
||||||
if (!(priv instanceof Sec1PrivateKey))
|
if (!(priv instanceof Curve25519PrivateKey))
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
if (!(pub instanceof Sec1PublicKey))
|
if (!(pub instanceof Curve25519PublicKey))
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
ECPrivateKeyParameters ecPriv = ((Sec1PrivateKey) priv).getKey();
|
|
||||||
ECPublicKeyParameters ecPub = ((Sec1PublicKey) pub).getKey();
|
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
ECDHCBasicAgreement agreement = new ECDHCBasicAgreement();
|
byte[] secret = curve25519.calculateAgreement(pub.getEncoded(),
|
||||||
agreement.init(ecPriv);
|
priv.getEncoded());
|
||||||
byte[] secret = agreement.calculateAgreement(ecPub).toByteArray();
|
// If the shared secret is all zeroes, the public key is invalid
|
||||||
|
byte allZero = 0;
|
||||||
|
for (byte b : secret) allZero |= b;
|
||||||
|
if (allZero == 0) throw new GeneralSecurityException();
|
||||||
long duration = System.currentTimeMillis() - now;
|
long duration = System.currentTimeMillis() - now;
|
||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO))
|
||||||
LOG.info("Deriving shared secret took " + duration + " ms");
|
LOG.info("Deriving shared secret took " + duration + " ms");
|
||||||
@@ -192,18 +142,10 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyPair generateAgreementKeyPair() {
|
public KeyPair generateAgreementKeyPair() {
|
||||||
AsymmetricCipherKeyPair keyPair =
|
Curve25519KeyPair keyPair = curve25519.generateKeyPair();
|
||||||
agreementKeyPairGenerator.generateKeyPair();
|
PublicKey pub = new Curve25519PublicKey(keyPair.getPublicKey());
|
||||||
// Return a wrapper that uses the SEC 1 encoding
|
PrivateKey priv = new Curve25519PrivateKey(keyPair.getPrivateKey());
|
||||||
ECPublicKeyParameters ecPublicKey =
|
return new KeyPair(pub, priv);
|
||||||
(ECPublicKeyParameters) keyPair.getPublic();
|
|
||||||
PublicKey publicKey = new Sec1PublicKey(ecPublicKey
|
|
||||||
);
|
|
||||||
ECPrivateKeyParameters ecPrivateKey =
|
|
||||||
(ECPrivateKeyParameters) keyPair.getPrivate();
|
|
||||||
PrivateKey privateKey = new Sec1PrivateKey(ecPrivateKey,
|
|
||||||
AGREEMENT_KEY_PAIR_BITS);
|
|
||||||
return new KeyPair(publicKey, privateKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -213,17 +155,12 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyPair generateSignatureKeyPair() {
|
public KeyPair generateSignatureKeyPair() {
|
||||||
AsymmetricCipherKeyPair keyPair =
|
java.security.KeyPair keyPair =
|
||||||
signatureKeyPairGenerator.generateKeyPair();
|
signatureKeyPairGenerator.generateKeyPair();
|
||||||
// Return a wrapper that uses the SEC 1 encoding
|
EdDSAPublicKey edPublicKey = (EdDSAPublicKey) keyPair.getPublic();
|
||||||
ECPublicKeyParameters ecPublicKey =
|
PublicKey publicKey = new EdPublicKey(edPublicKey.getAbyte());
|
||||||
(ECPublicKeyParameters) keyPair.getPublic();
|
EdDSAPrivateKey edPrivateKey = (EdDSAPrivateKey) keyPair.getPrivate();
|
||||||
PublicKey publicKey = new Sec1PublicKey(ecPublicKey
|
PrivateKey privateKey = new EdPrivateKey(edPrivateKey.getSeed());
|
||||||
);
|
|
||||||
ECPrivateKeyParameters ecPrivateKey =
|
|
||||||
(ECPrivateKeyParameters) keyPair.getPrivate();
|
|
||||||
PrivateKey privateKey = new Sec1PrivateKey(ecPrivateKey,
|
|
||||||
SIGNATURE_KEY_PAIR_BITS);
|
|
||||||
return new KeyPair(publicKey, privateKey);
|
return new KeyPair(publicKey, privateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,205 +175,47 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SecretKey deriveHeaderKey(SecretKey master,
|
public SecretKey deriveKey(String label, SecretKey k, byte[]... inputs) {
|
||||||
boolean alice) {
|
byte[] mac = mac(label, k, inputs);
|
||||||
return new SecretKey(macKdf(master, alice ? A_INVITE : B_INVITE));
|
if (mac.length != SecretKey.LENGTH) throw new IllegalStateException();
|
||||||
|
return new SecretKey(mac);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SecretKey deriveMacKey(SecretKey master, boolean alice) {
|
public SecretKey deriveSharedSecret(String label, PublicKey theirPublicKey,
|
||||||
return new SecretKey(macKdf(master, alice ? A_MAC : B_MAC));
|
KeyPair ourKeyPair, byte[]... inputs)
|
||||||
}
|
throws GeneralSecurityException {
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] deriveSignatureNonce(SecretKey master,
|
|
||||||
boolean alice) {
|
|
||||||
return macKdf(master, alice ? A_SIG_NONCE : B_SIG_NONCE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] deriveKeyCommitment(byte[] publicKey) {
|
|
||||||
byte[] hash = hash(COMMIT, publicKey);
|
|
||||||
// The output is the first COMMIT_LENGTH bytes of the hash
|
|
||||||
byte[] commitment = new byte[COMMIT_LENGTH];
|
|
||||||
System.arraycopy(hash, 0, commitment, 0, COMMIT_LENGTH);
|
|
||||||
return commitment;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SecretKey deriveSharedSecret(byte[] theirPublicKey,
|
|
||||||
KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException {
|
|
||||||
PrivateKey ourPriv = ourKeyPair.getPrivate();
|
PrivateKey ourPriv = ourKeyPair.getPrivate();
|
||||||
PublicKey theirPub = agreementKeyParser.parsePublicKey(theirPublicKey);
|
byte[][] hashInputs = new byte[inputs.length + 1][];
|
||||||
byte[] raw = performRawKeyAgreement(ourPriv, theirPub);
|
hashInputs[0] = performRawKeyAgreement(ourPriv, theirPublicKey);
|
||||||
byte[] alicePub, bobPub;
|
System.arraycopy(inputs, 0, hashInputs, 1, inputs.length);
|
||||||
if (alice) {
|
byte[] hash = hash(label, hashInputs);
|
||||||
alicePub = ourKeyPair.getPublic().getEncoded();
|
if (hash.length != SecretKey.LENGTH) throw new IllegalStateException();
|
||||||
bobPub = theirPublicKey;
|
return new SecretKey(hash);
|
||||||
} else {
|
|
||||||
alicePub = theirPublicKey;
|
|
||||||
bobPub = ourKeyPair.getPublic().getEncoded();
|
|
||||||
}
|
|
||||||
return new SecretKey(hash(SHARED_SECRET, raw, alicePub, bobPub));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] deriveConfirmationRecord(SecretKey sharedSecret,
|
|
||||||
byte[] theirPayload, byte[] ourPayload, byte[] theirPublicKey,
|
|
||||||
KeyPair ourKeyPair, boolean alice, boolean aliceRecord) {
|
|
||||||
SecretKey ck = new SecretKey(macKdf(sharedSecret, CONFIRMATION_KEY));
|
|
||||||
byte[] alicePayload, alicePub, bobPayload, bobPub;
|
|
||||||
if (alice) {
|
|
||||||
alicePayload = ourPayload;
|
|
||||||
alicePub = ourKeyPair.getPublic().getEncoded();
|
|
||||||
bobPayload = theirPayload;
|
|
||||||
bobPub = theirPublicKey;
|
|
||||||
} else {
|
|
||||||
alicePayload = theirPayload;
|
|
||||||
alicePub = theirPublicKey;
|
|
||||||
bobPayload = ourPayload;
|
|
||||||
bobPub = ourKeyPair.getPublic().getEncoded();
|
|
||||||
}
|
|
||||||
if (aliceRecord)
|
|
||||||
return macKdf(ck, alicePayload, alicePub, bobPayload, bobPub);
|
|
||||||
else
|
|
||||||
return macKdf(ck, bobPayload, bobPub, alicePayload, alicePub);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SecretKey deriveMasterSecret(SecretKey sharedSecret) {
|
|
||||||
return new SecretKey(macKdf(sharedSecret, MASTER_KEY));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SecretKey deriveMasterSecret(byte[] theirPublicKey,
|
|
||||||
KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException {
|
|
||||||
return deriveMasterSecret(deriveSharedSecret(
|
|
||||||
theirPublicKey, ourKeyPair, alice));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TransportKeys deriveTransportKeys(TransportId t,
|
|
||||||
SecretKey master, long rotationPeriod, boolean alice) {
|
|
||||||
// Keys for the previous period are derived from the master secret
|
|
||||||
SecretKey inTagPrev = deriveTagKey(master, t, !alice);
|
|
||||||
SecretKey inHeaderPrev = deriveHeaderKey(master, t, !alice);
|
|
||||||
SecretKey outTagPrev = deriveTagKey(master, t, alice);
|
|
||||||
SecretKey outHeaderPrev = deriveHeaderKey(master, t, alice);
|
|
||||||
// Derive the keys for the current and next periods
|
|
||||||
SecretKey inTagCurr = rotateKey(inTagPrev, rotationPeriod);
|
|
||||||
SecretKey inHeaderCurr = rotateKey(inHeaderPrev, rotationPeriod);
|
|
||||||
SecretKey inTagNext = rotateKey(inTagCurr, rotationPeriod + 1);
|
|
||||||
SecretKey inHeaderNext = rotateKey(inHeaderCurr, rotationPeriod + 1);
|
|
||||||
SecretKey outTagCurr = rotateKey(outTagPrev, rotationPeriod);
|
|
||||||
SecretKey outHeaderCurr = rotateKey(outHeaderPrev, rotationPeriod);
|
|
||||||
// Initialise the reordering windows and stream counters
|
|
||||||
IncomingKeys inPrev = new IncomingKeys(inTagPrev, inHeaderPrev,
|
|
||||||
rotationPeriod - 1);
|
|
||||||
IncomingKeys inCurr = new IncomingKeys(inTagCurr, inHeaderCurr,
|
|
||||||
rotationPeriod);
|
|
||||||
IncomingKeys inNext = new IncomingKeys(inTagNext, inHeaderNext,
|
|
||||||
rotationPeriod + 1);
|
|
||||||
OutgoingKeys outCurr = new OutgoingKeys(outTagCurr, outHeaderCurr,
|
|
||||||
rotationPeriod);
|
|
||||||
// Collect and return the keys
|
|
||||||
return new TransportKeys(t, inPrev, inCurr, inNext, outCurr);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TransportKeys rotateTransportKeys(TransportKeys k,
|
|
||||||
long rotationPeriod) {
|
|
||||||
if (k.getRotationPeriod() >= rotationPeriod) return k;
|
|
||||||
IncomingKeys inPrev = k.getPreviousIncomingKeys();
|
|
||||||
IncomingKeys inCurr = k.getCurrentIncomingKeys();
|
|
||||||
IncomingKeys inNext = k.getNextIncomingKeys();
|
|
||||||
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
|
|
||||||
long startPeriod = outCurr.getRotationPeriod();
|
|
||||||
// Rotate the keys
|
|
||||||
for (long p = startPeriod + 1; p <= rotationPeriod; p++) {
|
|
||||||
inPrev = inCurr;
|
|
||||||
inCurr = inNext;
|
|
||||||
SecretKey inNextTag = rotateKey(inNext.getTagKey(), p + 1);
|
|
||||||
SecretKey inNextHeader = rotateKey(inNext.getHeaderKey(), p + 1);
|
|
||||||
inNext = new IncomingKeys(inNextTag, inNextHeader, p + 1);
|
|
||||||
SecretKey outCurrTag = rotateKey(outCurr.getTagKey(), p);
|
|
||||||
SecretKey outCurrHeader = rotateKey(outCurr.getHeaderKey(), p);
|
|
||||||
outCurr = new OutgoingKeys(outCurrTag, outCurrHeader, p);
|
|
||||||
}
|
|
||||||
// Collect and return the keys
|
|
||||||
return new TransportKeys(k.getTransportId(), inPrev, inCurr, inNext,
|
|
||||||
outCurr);
|
|
||||||
}
|
|
||||||
|
|
||||||
private SecretKey rotateKey(SecretKey k, long rotationPeriod) {
|
|
||||||
byte[] period = new byte[INT_64_BYTES];
|
|
||||||
ByteUtils.writeUint64(rotationPeriod, period, 0);
|
|
||||||
return new SecretKey(macKdf(k, ROTATE, period));
|
|
||||||
}
|
|
||||||
|
|
||||||
private SecretKey deriveTagKey(SecretKey master, TransportId t,
|
|
||||||
boolean alice) {
|
|
||||||
byte[] id = StringUtils.toUtf8(t.getString());
|
|
||||||
return new SecretKey(macKdf(master, alice ? A_TAG : B_TAG, id));
|
|
||||||
}
|
|
||||||
|
|
||||||
private SecretKey deriveHeaderKey(SecretKey master, TransportId t,
|
|
||||||
boolean alice) {
|
|
||||||
byte[] id = StringUtils.toUtf8(t.getString());
|
|
||||||
return new SecretKey(macKdf(master, alice ? A_HEADER : B_HEADER, id));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void encodeTag(byte[] tag, SecretKey tagKey, int protocolVersion,
|
|
||||||
long streamNumber) {
|
|
||||||
if (tag.length < TAG_LENGTH) throw new IllegalArgumentException();
|
|
||||||
if (protocolVersion < 0 || protocolVersion > MAX_16_BIT_UNSIGNED)
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
if (streamNumber < 0 || streamNumber > MAX_32_BIT_UNSIGNED)
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
// Initialise the PRF
|
|
||||||
Digest prf = new Blake2sDigest(tagKey.getBytes());
|
|
||||||
// The output of the PRF must be long enough to use as a tag
|
|
||||||
int macLength = prf.getDigestSize();
|
|
||||||
if (macLength < TAG_LENGTH) throw new IllegalStateException();
|
|
||||||
// The input is the protocol version as a 16-bit integer, followed by
|
|
||||||
// the stream number as a 64-bit integer
|
|
||||||
byte[] protocolVersionBytes = new byte[INT_16_BYTES];
|
|
||||||
ByteUtils.writeUint16(protocolVersion, protocolVersionBytes, 0);
|
|
||||||
prf.update(protocolVersionBytes, 0, protocolVersionBytes.length);
|
|
||||||
byte[] streamNumberBytes = new byte[INT_64_BYTES];
|
|
||||||
ByteUtils.writeUint64(streamNumber, streamNumberBytes, 0);
|
|
||||||
prf.update(streamNumberBytes, 0, streamNumberBytes.length);
|
|
||||||
byte[] mac = new byte[macLength];
|
|
||||||
prf.doFinal(mac, 0);
|
|
||||||
// The output is the first TAG_LENGTH bytes of the MAC
|
|
||||||
System.arraycopy(mac, 0, tag, 0, TAG_LENGTH);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] sign(String label, byte[] toSign, byte[] privateKey)
|
public byte[] sign(String label, byte[] toSign, byte[] privateKey)
|
||||||
throws GeneralSecurityException {
|
throws GeneralSecurityException {
|
||||||
Signature signature = new SignatureImpl(secureRandom);
|
PrivateKey key = signatureKeyParser.parsePrivateKey(privateKey);
|
||||||
KeyParser keyParser = getSignatureKeyParser();
|
Signature sig = new EdSignature();
|
||||||
PrivateKey key = keyParser.parsePrivateKey(privateKey);
|
sig.initSign(key);
|
||||||
signature.initSign(key);
|
updateSignature(sig, label, toSign);
|
||||||
updateSignature(signature, label, toSign);
|
return sig.sign();
|
||||||
return signature.sign();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean verify(String label, byte[] signedData, byte[] publicKey,
|
public boolean verify(String label, byte[] signedData, byte[] publicKey,
|
||||||
byte[] signature) throws GeneralSecurityException {
|
byte[] signature) throws GeneralSecurityException {
|
||||||
Signature sig = new SignatureImpl(secureRandom);
|
PublicKey key = signatureKeyParser.parsePublicKey(publicKey);
|
||||||
KeyParser keyParser = getSignatureKeyParser();
|
Signature sig = new EdSignature();
|
||||||
PublicKey key = keyParser.parsePublicKey(publicKey);
|
|
||||||
sig.initVerify(key);
|
sig.initVerify(key);
|
||||||
updateSignature(sig, label, signedData);
|
updateSignature(sig, label, signedData);
|
||||||
return sig.verify(signature);
|
return sig.verify(signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSignature(Signature signature, String label,
|
private void updateSignature(Signature signature, String label,
|
||||||
byte[] toSign) {
|
byte[] toSign) throws GeneralSecurityException {
|
||||||
byte[] labelBytes = StringUtils.toUtf8(label);
|
byte[] labelBytes = StringUtils.toUtf8(label);
|
||||||
byte[] length = new byte[INT_32_BYTES];
|
byte[] length = new byte[INT_32_BYTES];
|
||||||
ByteUtils.writeUint32(labelBytes.length, length, 0);
|
ByteUtils.writeUint32(labelBytes.length, length, 0);
|
||||||
@@ -450,7 +229,7 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
@Override
|
@Override
|
||||||
public byte[] hash(String label, byte[]... inputs) {
|
public byte[] hash(String label, byte[]... inputs) {
|
||||||
byte[] labelBytes = StringUtils.toUtf8(label);
|
byte[] labelBytes = StringUtils.toUtf8(label);
|
||||||
Digest digest = new Blake2sDigest();
|
Digest digest = new Blake2bDigest(256);
|
||||||
byte[] length = new byte[INT_32_BYTES];
|
byte[] length = new byte[INT_32_BYTES];
|
||||||
ByteUtils.writeUint32(labelBytes.length, length, 0);
|
ByteUtils.writeUint32(labelBytes.length, length, 0);
|
||||||
digest.update(length, 0, length.length);
|
digest.update(length, 0, length.length);
|
||||||
@@ -466,14 +245,13 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getHashLength() {
|
public byte[] mac(String label, SecretKey macKey, byte[]... inputs) {
|
||||||
return HASH_SIZE;
|
byte[] labelBytes = StringUtils.toUtf8(label);
|
||||||
}
|
Digest mac = new Blake2bDigest(macKey.getBytes(), 32, null, null);
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] mac(SecretKey macKey, byte[]... inputs) {
|
|
||||||
Digest mac = new Blake2sDigest(macKey.getBytes());
|
|
||||||
byte[] length = new byte[INT_32_BYTES];
|
byte[] length = new byte[INT_32_BYTES];
|
||||||
|
ByteUtils.writeUint32(labelBytes.length, length, 0);
|
||||||
|
mac.update(length, 0, length.length);
|
||||||
|
mac.update(labelBytes, 0, labelBytes.length);
|
||||||
for (byte[] input : inputs) {
|
for (byte[] input : inputs) {
|
||||||
ByteUtils.writeUint32(input.length, length, 0);
|
ByteUtils.writeUint32(input.length, length, 0);
|
||||||
mac.update(length, 0, length.length);
|
mac.update(length, 0, length.length);
|
||||||
@@ -492,23 +270,33 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
byte[] salt = new byte[PBKDF_SALT_BYTES];
|
byte[] salt = new byte[PBKDF_SALT_BYTES];
|
||||||
secureRandom.nextBytes(salt);
|
secureRandom.nextBytes(salt);
|
||||||
// Calibrate the KDF
|
// Calibrate the KDF
|
||||||
int iterations = chooseIterationCount(PBKDF_TARGET_MILLIS);
|
int cost = passwordBasedKdf.chooseCostParameter();
|
||||||
// Derive the key from the password
|
// Derive the key from the password
|
||||||
SecretKey key = new SecretKey(pbkdf2(password, salt, iterations));
|
SecretKey key = passwordBasedKdf.deriveKey(password, salt, cost);
|
||||||
// Generate a random IV
|
// Generate a random IV
|
||||||
byte[] iv = new byte[STORAGE_IV_BYTES];
|
byte[] iv = new byte[STORAGE_IV_BYTES];
|
||||||
secureRandom.nextBytes(iv);
|
secureRandom.nextBytes(iv);
|
||||||
// The output contains the salt, iterations, IV, ciphertext and MAC
|
// The output contains the format version, salt, cost parameter, IV,
|
||||||
int outputLen = salt.length + INT_32_BYTES + iv.length + input.length
|
// ciphertext and MAC
|
||||||
+ macBytes;
|
int outputLen = 1 + salt.length + INT_32_BYTES + iv.length
|
||||||
|
+ input.length + macBytes;
|
||||||
byte[] output = new byte[outputLen];
|
byte[] output = new byte[outputLen];
|
||||||
System.arraycopy(salt, 0, output, 0, salt.length);
|
int outputOff = 0;
|
||||||
ByteUtils.writeUint32(iterations, output, salt.length);
|
// Format version
|
||||||
System.arraycopy(iv, 0, output, salt.length + INT_32_BYTES, iv.length);
|
output[outputOff] = PBKDF_FORMAT_SCRYPT;
|
||||||
|
outputOff++;
|
||||||
|
// Salt
|
||||||
|
System.arraycopy(salt, 0, output, outputOff, salt.length);
|
||||||
|
outputOff += salt.length;
|
||||||
|
// Cost parameter
|
||||||
|
ByteUtils.writeUint32(cost, output, outputOff);
|
||||||
|
outputOff += INT_32_BYTES;
|
||||||
|
// IV
|
||||||
|
System.arraycopy(iv, 0, output, outputOff, iv.length);
|
||||||
|
outputOff += iv.length;
|
||||||
// Initialise the cipher and encrypt the plaintext
|
// Initialise the cipher and encrypt the plaintext
|
||||||
try {
|
try {
|
||||||
cipher.init(true, key, iv);
|
cipher.init(true, key, iv);
|
||||||
int outputOff = salt.length + INT_32_BYTES + iv.length;
|
|
||||||
cipher.process(input, 0, input.length, output, outputOff);
|
cipher.process(input, 0, input.length, output, outputOff);
|
||||||
return output;
|
return output;
|
||||||
} catch (GeneralSecurityException e) {
|
} catch (GeneralSecurityException e) {
|
||||||
@@ -517,22 +305,36 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Nullable
|
||||||
public byte[] decryptWithPassword(byte[] input, String password) {
|
public byte[] decryptWithPassword(byte[] input, String password) {
|
||||||
AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher();
|
AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher();
|
||||||
int macBytes = cipher.getMacBytes();
|
int macBytes = cipher.getMacBytes();
|
||||||
// The input contains the salt, iterations, IV, ciphertext and MAC
|
// The input contains the format version, salt, cost parameter, IV,
|
||||||
if (input.length < PBKDF_SALT_BYTES + INT_32_BYTES + STORAGE_IV_BYTES
|
// ciphertext and MAC
|
||||||
+ macBytes)
|
if (input.length < 1 + PBKDF_SALT_BYTES + INT_32_BYTES
|
||||||
|
+ STORAGE_IV_BYTES + macBytes)
|
||||||
return null; // Invalid input
|
return null; // Invalid input
|
||||||
|
int inputOff = 0;
|
||||||
|
// Format version
|
||||||
|
byte formatVersion = input[inputOff];
|
||||||
|
inputOff++;
|
||||||
|
if (formatVersion != PBKDF_FORMAT_SCRYPT)
|
||||||
|
return null; // Unknown format
|
||||||
|
// Salt
|
||||||
byte[] salt = new byte[PBKDF_SALT_BYTES];
|
byte[] salt = new byte[PBKDF_SALT_BYTES];
|
||||||
System.arraycopy(input, 0, salt, 0, salt.length);
|
System.arraycopy(input, inputOff, salt, 0, salt.length);
|
||||||
long iterations = ByteUtils.readUint32(input, salt.length);
|
inputOff += salt.length;
|
||||||
if (iterations < 0 || iterations > Integer.MAX_VALUE)
|
// Cost parameter
|
||||||
return null; // Invalid iteration count
|
long cost = ByteUtils.readUint32(input, inputOff);
|
||||||
|
inputOff += INT_32_BYTES;
|
||||||
|
if (cost < 2 || cost > Integer.MAX_VALUE)
|
||||||
|
return null; // Invalid cost parameter
|
||||||
|
// IV
|
||||||
byte[] iv = new byte[STORAGE_IV_BYTES];
|
byte[] iv = new byte[STORAGE_IV_BYTES];
|
||||||
System.arraycopy(input, salt.length + INT_32_BYTES, iv, 0, iv.length);
|
System.arraycopy(input, inputOff, iv, 0, iv.length);
|
||||||
|
inputOff += iv.length;
|
||||||
// Derive the key from the password
|
// Derive the key from the password
|
||||||
SecretKey key = new SecretKey(pbkdf2(password, salt, (int) iterations));
|
SecretKey key = passwordBasedKdf.deriveKey(password, salt, (int) cost);
|
||||||
// Initialise the cipher
|
// Initialise the cipher
|
||||||
try {
|
try {
|
||||||
cipher.init(false, key, iv);
|
cipher.init(false, key, iv);
|
||||||
@@ -541,7 +343,6 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
}
|
}
|
||||||
// Try to decrypt the ciphertext (may be invalid)
|
// Try to decrypt the ciphertext (may be invalid)
|
||||||
try {
|
try {
|
||||||
int inputOff = salt.length + INT_32_BYTES + iv.length;
|
|
||||||
int inputLen = input.length - inputOff;
|
int inputLen = input.length - inputOff;
|
||||||
byte[] output = new byte[inputLen - macBytes];
|
byte[] output = new byte[inputLen - macBytes];
|
||||||
cipher.process(input, inputOff, inputLen, output, 0);
|
cipher.process(input, inputOff, inputLen, output, 0);
|
||||||
@@ -564,88 +365,4 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
public String asciiArmour(byte[] b, int lineLength) {
|
public String asciiArmour(byte[] b, int lineLength) {
|
||||||
return AsciiArmour.wrap(b, lineLength);
|
return AsciiArmour.wrap(b, lineLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key derivation function based on a pseudo-random function - see
|
|
||||||
// NIST SP 800-108, section 5.1
|
|
||||||
private byte[] macKdf(SecretKey key, byte[]... inputs) {
|
|
||||||
// Initialise the PRF
|
|
||||||
Digest prf = new Blake2sDigest(key.getBytes());
|
|
||||||
// The output of the PRF must be long enough to use as a key
|
|
||||||
int macLength = prf.getDigestSize();
|
|
||||||
if (macLength < SecretKey.LENGTH) throw new IllegalStateException();
|
|
||||||
// Calculate the PRF over the concatenated length-prefixed inputs
|
|
||||||
byte[] length = new byte[INT_32_BYTES];
|
|
||||||
for (byte[] input : inputs) {
|
|
||||||
ByteUtils.writeUint32(input.length, length, 0);
|
|
||||||
prf.update(length, 0, length.length);
|
|
||||||
prf.update(input, 0, input.length);
|
|
||||||
}
|
|
||||||
byte[] mac = new byte[macLength];
|
|
||||||
prf.doFinal(mac, 0);
|
|
||||||
// The output is the first SecretKey.LENGTH bytes of the MAC
|
|
||||||
if (mac.length == SecretKey.LENGTH) return mac;
|
|
||||||
byte[] truncated = new byte[SecretKey.LENGTH];
|
|
||||||
System.arraycopy(mac, 0, truncated, 0, truncated.length);
|
|
||||||
return truncated;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Password-based key derivation function - see PKCS#5 v2.1, section 5.2
|
|
||||||
private byte[] pbkdf2(String password, byte[] salt, int iterations) {
|
|
||||||
byte[] utf8 = StringUtils.toUtf8(password);
|
|
||||||
Digest digest = new SHA256Digest();
|
|
||||||
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(digest);
|
|
||||||
gen.init(utf8, salt, iterations);
|
|
||||||
int keyLengthInBits = SecretKey.LENGTH * 8;
|
|
||||||
CipherParameters p = gen.generateDerivedParameters(keyLengthInBits);
|
|
||||||
return ((KeyParameter) p).getKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Package access for testing
|
|
||||||
int chooseIterationCount(int targetMillis) {
|
|
||||||
List<Long> quickSamples = new ArrayList<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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ class PasswordStrengthEstimatorImpl implements PasswordStrengthEstimator {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float estimateStrength(String password) {
|
public float estimateStrength(String password) {
|
||||||
HashSet<Character> unique = new HashSet<Character>();
|
HashSet<Character> unique = new HashSet<>();
|
||||||
int length = password.length();
|
int length = password.length();
|
||||||
for (int i = 0; i < length; i++) unique.add(password.charAt(i));
|
for (int i = 0; i < length; i++) unique.add(password.charAt(i));
|
||||||
return Math.min(1, (float) unique.size() / STRONG_UNIQUE_CHARS);
|
return Math.min(1, (float) unique.size() / STRONG_UNIQUE_CHARS);
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package org.briarproject.bramble.crypto;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
|
import org.spongycastle.crypto.generators.SCrypt;
|
||||||
|
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
|
||||||
|
class ScryptKdf implements PasswordBasedKdf {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
Logger.getLogger(ScryptKdf.class.getName());
|
||||||
|
|
||||||
|
private static final int MIN_COST = 256; // Min parameter N
|
||||||
|
private static final int MAX_COST = 1024 * 1024; // Max parameter N
|
||||||
|
private static final int BLOCK_SIZE = 8; // Parameter r
|
||||||
|
private static final int PARALLELIZATION = 1; // Parameter p
|
||||||
|
private static final int TARGET_MS = 1000;
|
||||||
|
|
||||||
|
private final Clock clock;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ScryptKdf(Clock clock) {
|
||||||
|
this.clock = clock;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int chooseCostParameter() {
|
||||||
|
// Increase the cost from min to max while measuring performance
|
||||||
|
int cost = MIN_COST;
|
||||||
|
while (cost * 2 <= MAX_COST && measureDuration(cost) * 2 <= TARGET_MS)
|
||||||
|
cost *= 2;
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("KDF cost parameter " + cost);
|
||||||
|
return cost;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long measureDuration(int cost) {
|
||||||
|
byte[] password = new byte[16], salt = new byte[32];
|
||||||
|
long start = clock.currentTimeMillis();
|
||||||
|
SCrypt.generate(password, salt, cost, BLOCK_SIZE, PARALLELIZATION,
|
||||||
|
SecretKey.LENGTH);
|
||||||
|
return clock.currentTimeMillis() - start;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecretKey deriveKey(String password, byte[] salt, int cost) {
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
byte[] passwordBytes = StringUtils.toUtf8(password);
|
||||||
|
SecretKey k = new SecretKey(SCrypt.generate(passwordBytes, salt, cost,
|
||||||
|
BLOCK_SIZE, PARALLELIZATION, SecretKey.LENGTH));
|
||||||
|
long duration = System.currentTimeMillis() - start;
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Deriving key from password took " + duration + " ms");
|
||||||
|
return k;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,25 +22,25 @@ interface Signature {
|
|||||||
/**
|
/**
|
||||||
* @see {@link java.security.Signature#update(byte)}
|
* @see {@link java.security.Signature#update(byte)}
|
||||||
*/
|
*/
|
||||||
void update(byte b);
|
void update(byte b) throws GeneralSecurityException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see {@link java.security.Signature#update(byte[])}
|
* @see {@link java.security.Signature#update(byte[])}
|
||||||
*/
|
*/
|
||||||
void update(byte[] b);
|
void update(byte[] b) throws GeneralSecurityException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see {@link java.security.Signature#update(byte[], int, int)}
|
* @see {@link java.security.Signature#update(byte[], int, int)}
|
||||||
*/
|
*/
|
||||||
void update(byte[] b, int off, int len);
|
void update(byte[] b, int off, int len) throws GeneralSecurityException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see {@link java.security.Signature#sign()}
|
* @see {@link java.security.Signature#sign()}
|
||||||
*/
|
*/
|
||||||
byte[] sign();
|
byte[] sign() throws GeneralSecurityException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see {@link java.security.Signature#verify(byte[])}
|
* @see {@link java.security.Signature#verify(byte[])}
|
||||||
*/
|
*/
|
||||||
boolean verify(byte[] signature);
|
boolean verify(byte[] signature) throws GeneralSecurityException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
package org.briarproject.bramble.crypto;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
|
||||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.spongycastle.crypto.Digest;
|
|
||||||
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
|
|
||||||
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
|
||||||
import org.spongycastle.crypto.params.ParametersWithRandom;
|
|
||||||
import org.spongycastle.crypto.signers.DSADigestSigner;
|
|
||||||
import org.spongycastle.crypto.signers.DSAKCalculator;
|
|
||||||
import org.spongycastle.crypto.signers.ECDSASigner;
|
|
||||||
import org.spongycastle.crypto.signers.HMacDSAKCalculator;
|
|
||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.NotThreadSafe;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
|
||||||
|
|
||||||
@NotThreadSafe
|
|
||||||
@NotNullByDefault
|
|
||||||
class SignatureImpl implements Signature {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
Logger.getLogger(SignatureImpl.class.getName());
|
|
||||||
|
|
||||||
private final SecureRandom secureRandom;
|
|
||||||
private final DSADigestSigner signer;
|
|
||||||
|
|
||||||
SignatureImpl(SecureRandom secureRandom) {
|
|
||||||
this.secureRandom = secureRandom;
|
|
||||||
Digest digest = new Blake2sDigest();
|
|
||||||
DSAKCalculator calculator = new HMacDSAKCalculator(digest);
|
|
||||||
signer = new DSADigestSigner(new ECDSASigner(calculator), digest);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initSign(PrivateKey k) throws GeneralSecurityException {
|
|
||||||
if (!(k instanceof Sec1PrivateKey))
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
ECPrivateKeyParameters priv = ((Sec1PrivateKey) k).getKey();
|
|
||||||
signer.init(true, new ParametersWithRandom(priv, secureRandom));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initVerify(PublicKey k) throws GeneralSecurityException {
|
|
||||||
if (!(k instanceof Sec1PublicKey))
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
ECPublicKeyParameters pub = ((Sec1PublicKey) k).getKey();
|
|
||||||
signer.init(false, pub);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update(byte b) {
|
|
||||||
signer.update(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update(byte[] b) {
|
|
||||||
update(b, 0, b.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update(byte[] b, int off, int len) {
|
|
||||||
signer.update(b, off, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] sign() {
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
byte[] signature = signer.generateSignature();
|
|
||||||
long duration = System.currentTimeMillis() - now;
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Generating signature took " + duration + " ms");
|
|
||||||
return signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean verify(byte[] signature) {
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
boolean valid = signer.verifySignature(signature);
|
|
||||||
long duration = System.currentTimeMillis() - now;
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Verifying signature took " + duration + " ms");
|
|
||||||
return valid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.crypto.CryptoComponent;
|
|||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.api.crypto.StreamEncrypter;
|
import org.briarproject.bramble.api.crypto.StreamEncrypter;
|
||||||
import org.briarproject.bramble.api.crypto.StreamEncrypterFactory;
|
import org.briarproject.bramble.api.crypto.StreamEncrypterFactory;
|
||||||
|
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.transport.StreamContext;
|
import org.briarproject.bramble.api.transport.StreamContext;
|
||||||
|
|
||||||
@@ -22,12 +23,15 @@ import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENG
|
|||||||
class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
|
class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
|
||||||
|
|
||||||
private final CryptoComponent crypto;
|
private final CryptoComponent crypto;
|
||||||
|
private final TransportCrypto transportCrypto;
|
||||||
private final Provider<AuthenticatedCipher> cipherProvider;
|
private final Provider<AuthenticatedCipher> cipherProvider;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
StreamEncrypterFactoryImpl(CryptoComponent crypto,
|
StreamEncrypterFactoryImpl(CryptoComponent crypto,
|
||||||
|
TransportCrypto transportCrypto,
|
||||||
Provider<AuthenticatedCipher> cipherProvider) {
|
Provider<AuthenticatedCipher> cipherProvider) {
|
||||||
this.crypto = crypto;
|
this.crypto = crypto;
|
||||||
|
this.transportCrypto = transportCrypto;
|
||||||
this.cipherProvider = cipherProvider;
|
this.cipherProvider = cipherProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +41,8 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
|
|||||||
AuthenticatedCipher cipher = cipherProvider.get();
|
AuthenticatedCipher cipher = cipherProvider.get();
|
||||||
long streamNumber = ctx.getStreamNumber();
|
long streamNumber = ctx.getStreamNumber();
|
||||||
byte[] tag = new byte[TAG_LENGTH];
|
byte[] tag = new byte[TAG_LENGTH];
|
||||||
crypto.encodeTag(tag, ctx.getTagKey(), PROTOCOL_VERSION, streamNumber);
|
transportCrypto.encodeTag(tag, ctx.getTagKey(), PROTOCOL_VERSION,
|
||||||
|
streamNumber);
|
||||||
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
|
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
|
||||||
crypto.getSecureRandom().nextBytes(streamHeaderNonce);
|
crypto.getSecureRandom().nextBytes(streamHeaderNonce);
|
||||||
SecretKey frameKey = crypto.generateSecretKey();
|
SecretKey frameKey = crypto.generateSecretKey();
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package org.briarproject.bramble.crypto;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
|
import org.briarproject.bramble.api.transport.IncomingKeys;
|
||||||
|
import org.briarproject.bramble.api.transport.OutgoingKeys;
|
||||||
|
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||||
|
import org.briarproject.bramble.util.ByteUtils;
|
||||||
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
|
import org.spongycastle.crypto.Digest;
|
||||||
|
import org.spongycastle.crypto.digests.Blake2bDigest;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.transport.TransportConstants.ALICE_HEADER_LABEL;
|
||||||
|
import static org.briarproject.bramble.api.transport.TransportConstants.ALICE_TAG_LABEL;
|
||||||
|
import static org.briarproject.bramble.api.transport.TransportConstants.BOB_HEADER_LABEL;
|
||||||
|
import static org.briarproject.bramble.api.transport.TransportConstants.BOB_TAG_LABEL;
|
||||||
|
import static org.briarproject.bramble.api.transport.TransportConstants.ROTATE_LABEL;
|
||||||
|
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||||
|
import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
|
||||||
|
import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
|
||||||
|
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
|
||||||
|
import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
||||||
|
|
||||||
|
class TransportCryptoImpl implements TransportCrypto {
|
||||||
|
|
||||||
|
private final CryptoComponent crypto;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
TransportCryptoImpl(CryptoComponent crypto) {
|
||||||
|
this.crypto = crypto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransportKeys deriveTransportKeys(TransportId t,
|
||||||
|
SecretKey master, long rotationPeriod, boolean alice) {
|
||||||
|
// Keys for the previous period are derived from the master secret
|
||||||
|
SecretKey inTagPrev = deriveTagKey(master, t, !alice);
|
||||||
|
SecretKey inHeaderPrev = deriveHeaderKey(master, t, !alice);
|
||||||
|
SecretKey outTagPrev = deriveTagKey(master, t, alice);
|
||||||
|
SecretKey outHeaderPrev = deriveHeaderKey(master, t, alice);
|
||||||
|
// Derive the keys for the current and next periods
|
||||||
|
SecretKey inTagCurr = rotateKey(inTagPrev, rotationPeriod);
|
||||||
|
SecretKey inHeaderCurr = rotateKey(inHeaderPrev, rotationPeriod);
|
||||||
|
SecretKey inTagNext = rotateKey(inTagCurr, rotationPeriod + 1);
|
||||||
|
SecretKey inHeaderNext = rotateKey(inHeaderCurr, rotationPeriod + 1);
|
||||||
|
SecretKey outTagCurr = rotateKey(outTagPrev, rotationPeriod);
|
||||||
|
SecretKey outHeaderCurr = rotateKey(outHeaderPrev, rotationPeriod);
|
||||||
|
// Initialise the reordering windows and stream counters
|
||||||
|
IncomingKeys inPrev = new IncomingKeys(inTagPrev, inHeaderPrev,
|
||||||
|
rotationPeriod - 1);
|
||||||
|
IncomingKeys inCurr = new IncomingKeys(inTagCurr, inHeaderCurr,
|
||||||
|
rotationPeriod);
|
||||||
|
IncomingKeys inNext = new IncomingKeys(inTagNext, inHeaderNext,
|
||||||
|
rotationPeriod + 1);
|
||||||
|
OutgoingKeys outCurr = new OutgoingKeys(outTagCurr, outHeaderCurr,
|
||||||
|
rotationPeriod);
|
||||||
|
// Collect and return the keys
|
||||||
|
return new TransportKeys(t, inPrev, inCurr, inNext, outCurr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransportKeys rotateTransportKeys(TransportKeys k,
|
||||||
|
long rotationPeriod) {
|
||||||
|
if (k.getRotationPeriod() >= rotationPeriod) return k;
|
||||||
|
IncomingKeys inPrev = k.getPreviousIncomingKeys();
|
||||||
|
IncomingKeys inCurr = k.getCurrentIncomingKeys();
|
||||||
|
IncomingKeys inNext = k.getNextIncomingKeys();
|
||||||
|
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
|
||||||
|
long startPeriod = outCurr.getRotationPeriod();
|
||||||
|
// Rotate the keys
|
||||||
|
for (long p = startPeriod + 1; p <= rotationPeriod; p++) {
|
||||||
|
inPrev = inCurr;
|
||||||
|
inCurr = inNext;
|
||||||
|
SecretKey inNextTag = rotateKey(inNext.getTagKey(), p + 1);
|
||||||
|
SecretKey inNextHeader = rotateKey(inNext.getHeaderKey(), p + 1);
|
||||||
|
inNext = new IncomingKeys(inNextTag, inNextHeader, p + 1);
|
||||||
|
SecretKey outCurrTag = rotateKey(outCurr.getTagKey(), p);
|
||||||
|
SecretKey outCurrHeader = rotateKey(outCurr.getHeaderKey(), p);
|
||||||
|
outCurr = new OutgoingKeys(outCurrTag, outCurrHeader, p);
|
||||||
|
}
|
||||||
|
// Collect and return the keys
|
||||||
|
return new TransportKeys(k.getTransportId(), inPrev, inCurr, inNext,
|
||||||
|
outCurr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SecretKey rotateKey(SecretKey k, long rotationPeriod) {
|
||||||
|
byte[] period = new byte[INT_64_BYTES];
|
||||||
|
ByteUtils.writeUint64(rotationPeriod, period, 0);
|
||||||
|
return crypto.deriveKey(ROTATE_LABEL, k, period);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SecretKey deriveTagKey(SecretKey master, TransportId t,
|
||||||
|
boolean alice) {
|
||||||
|
String label = alice ? ALICE_TAG_LABEL : BOB_TAG_LABEL;
|
||||||
|
byte[] id = StringUtils.toUtf8(t.getString());
|
||||||
|
return crypto.deriveKey(label, master, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SecretKey deriveHeaderKey(SecretKey master, TransportId t,
|
||||||
|
boolean alice) {
|
||||||
|
String label = alice ? ALICE_HEADER_LABEL : BOB_HEADER_LABEL;
|
||||||
|
byte[] id = StringUtils.toUtf8(t.getString());
|
||||||
|
return crypto.deriveKey(label, master, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void encodeTag(byte[] tag, SecretKey tagKey, int protocolVersion,
|
||||||
|
long streamNumber) {
|
||||||
|
if (tag.length < TAG_LENGTH) throw new IllegalArgumentException();
|
||||||
|
if (protocolVersion < 0 || protocolVersion > MAX_16_BIT_UNSIGNED)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
if (streamNumber < 0 || streamNumber > MAX_32_BIT_UNSIGNED)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
// Initialise the PRF
|
||||||
|
Digest prf = new Blake2bDigest(tagKey.getBytes(), 32, null, null);
|
||||||
|
// The output of the PRF must be long enough to use as a tag
|
||||||
|
int macLength = prf.getDigestSize();
|
||||||
|
if (macLength < TAG_LENGTH) throw new IllegalStateException();
|
||||||
|
// The input is the protocol version as a 16-bit integer, followed by
|
||||||
|
// the stream number as a 64-bit integer
|
||||||
|
byte[] protocolVersionBytes = new byte[INT_16_BYTES];
|
||||||
|
ByteUtils.writeUint16(protocolVersion, protocolVersionBytes, 0);
|
||||||
|
prf.update(protocolVersionBytes, 0, protocolVersionBytes.length);
|
||||||
|
byte[] streamNumberBytes = new byte[INT_64_BYTES];
|
||||||
|
ByteUtils.writeUint64(streamNumber, streamNumberBytes, 0);
|
||||||
|
prf.update(streamNumberBytes, 0, streamNumberBytes.length);
|
||||||
|
byte[] mac = new byte[macLength];
|
||||||
|
prf.doFinal(mac, 0);
|
||||||
|
// The output is the first TAG_LENGTH bytes of the MAC
|
||||||
|
System.arraycopy(mac, 0, tag, 0, TAG_LENGTH);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,8 +2,11 @@ package org.briarproject.bramble.db;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.db.DataTooNewException;
|
||||||
|
import org.briarproject.bramble.api.db.DataTooOldException;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.Metadata;
|
import org.briarproject.bramble.api.db.Metadata;
|
||||||
|
import org.briarproject.bramble.api.db.MigrationListener;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
@@ -37,8 +40,13 @@ interface Database<T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the database and returns true if the database already existed.
|
* Opens the database and returns true if the database already existed.
|
||||||
|
*
|
||||||
|
* @throws DataTooNewException if the data uses a newer schema than the
|
||||||
|
* current code
|
||||||
|
* @throws DataTooOldException if the data uses an older schema than the
|
||||||
|
* current code and cannot be migrated
|
||||||
*/
|
*/
|
||||||
boolean open() throws DbException;
|
boolean open(@Nullable MigrationListener listener) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prevents new transactions from starting, waits for all current
|
* Prevents new transactions from starting, waits for all current
|
||||||
@@ -89,9 +97,12 @@ interface Database<T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores a message.
|
* Stores a message.
|
||||||
|
*
|
||||||
|
* @param sender the contact from whom the message was received, or null
|
||||||
|
* if the message was created locally.
|
||||||
*/
|
*/
|
||||||
void addMessage(T txn, Message m, State state, boolean shared)
|
void addMessage(T txn, Message m, State state, boolean shared,
|
||||||
throws DbException;
|
@Nullable ContactId sender) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a dependency between two messages in the given group.
|
* Adds a dependency between two messages in the given group.
|
||||||
@@ -104,16 +115,6 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
void addOfferedMessage(T txn, ContactId c, MessageId m) throws DbException;
|
void addOfferedMessage(T txn, ContactId c, MessageId m) throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialises the status of the given message with respect to the given
|
|
||||||
* contact.
|
|
||||||
*
|
|
||||||
* @param ack whether the message needs to be acknowledged.
|
|
||||||
* @param seen whether the contact has seen the message.
|
|
||||||
*/
|
|
||||||
void addStatus(T txn, ContactId c, MessageId m, boolean ack, boolean seen)
|
|
||||||
throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores a transport.
|
* Stores a transport.
|
||||||
*/
|
*/
|
||||||
@@ -272,7 +273,7 @@ interface Database<T> {
|
|||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
Collection<ContactId> getGroupVisibility(T txn, GroupId g)
|
Map<ContactId, Boolean> getGroupVisibility(T txn, GroupId g)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -423,31 +424,37 @@ interface Database<T> {
|
|||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IDs of any messages that need to be validated by the given
|
* Returns the IDs of any messages that need to be validated.
|
||||||
* client.
|
|
||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
Collection<MessageId> getMessagesToValidate(T txn, ClientId c)
|
Collection<MessageId> getMessagesToValidate(T txn) throws DbException;
|
||||||
throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IDs of any messages that are still pending due to
|
* Returns the IDs of any messages that are pending delivery due to
|
||||||
* dependencies to other messages for the given client.
|
* dependencies on other messages.
|
||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
Collection<MessageId> getPendingMessages(T txn, ClientId c)
|
Collection<MessageId> getPendingMessages(T txn) throws DbException;
|
||||||
throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IDs of any messages from the given client
|
* Returns the IDs of any messages that have a shared dependent but have
|
||||||
* that have a shared dependent, but are still not shared themselves.
|
* not yet been shared themselves.
|
||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
Collection<MessageId> getMessagesToShare(T txn, ClientId c)
|
Collection<MessageId> getMessagesToShare(T txn) throws DbException;
|
||||||
throws DbException;
|
|
||||||
|
/**
|
||||||
|
* Returns the next time (in milliseconds since the Unix epoch) when a
|
||||||
|
* message is due to be sent to the given contact. The returned value may
|
||||||
|
* be zero if a message is due to be sent immediately, or Long.MAX_VALUE
|
||||||
|
* if no messages are scheduled to be sent.
|
||||||
|
* <p/>
|
||||||
|
* Read-only.
|
||||||
|
*/
|
||||||
|
long getNextSendTime(T txn, ContactId c) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the message with the given ID, in serialised form, or null if
|
* Returns the message with the given ID, in serialised form, or null if
|
||||||
@@ -566,13 +573,6 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
void removeMessage(T txn, MessageId m) throws DbException;
|
void removeMessage(T txn, MessageId m) throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes an offered message that was offered by the given contact, or
|
|
||||||
* returns false if there is no such message.
|
|
||||||
*/
|
|
||||||
boolean removeOfferedMessage(T txn, ContactId c, MessageId m)
|
|
||||||
throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the given offered messages that were offered by the given
|
* Removes the given offered messages that were offered by the given
|
||||||
* contact.
|
* contact.
|
||||||
@@ -580,12 +580,6 @@ interface Database<T> {
|
|||||||
void removeOfferedMessages(T txn, ContactId c,
|
void removeOfferedMessages(T txn, ContactId c,
|
||||||
Collection<MessageId> requested) throws DbException;
|
Collection<MessageId> requested) throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the status of the given message with respect to the given
|
|
||||||
* contact.
|
|
||||||
*/
|
|
||||||
void removeStatus(T txn, ContactId c, MessageId m) throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a transport (and all associated state) from the database.
|
* Removes a transport (and all associated state) from the database.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import org.briarproject.bramble.api.db.ContactExistsException;
|
|||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.Metadata;
|
import org.briarproject.bramble.api.db.Metadata;
|
||||||
|
import org.briarproject.bramble.api.db.MigrationListener;
|
||||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||||
import org.briarproject.bramble.api.db.NoSuchGroupException;
|
import org.briarproject.bramble.api.db.NoSuchGroupException;
|
||||||
import org.briarproject.bramble.api.db.NoSuchLocalAuthorException;
|
import org.briarproject.bramble.api.db.NoSuchLocalAuthorException;
|
||||||
@@ -90,8 +91,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
private final ReentrantReadWriteLock lock =
|
private final ReentrantReadWriteLock lock =
|
||||||
new ReentrantReadWriteLock(true);
|
new ReentrantReadWriteLock(true);
|
||||||
|
|
||||||
private volatile int shutdownHandle = -1;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
DatabaseComponentImpl(Database<T> db, Class<T> txnClass, EventBus eventBus,
|
DatabaseComponentImpl(Database<T> db, Class<T> txnClass, EventBus eventBus,
|
||||||
ShutdownManager shutdown) {
|
ShutdownManager shutdown) {
|
||||||
@@ -102,27 +101,22 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean open() throws DbException {
|
public boolean open(@Nullable MigrationListener listener)
|
||||||
Runnable shutdownHook = new Runnable() {
|
throws DbException {
|
||||||
@Override
|
boolean reopened = db.open(listener);
|
||||||
public void run() {
|
shutdown.addShutdownHook(() -> {
|
||||||
try {
|
try {
|
||||||
close();
|
close();
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
if (LOG.isLoggable(WARNING))
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
LOG.log(WARNING, e.toString(), e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
boolean reopened = db.open();
|
|
||||||
shutdownHandle = shutdown.addShutdownHook(shutdownHook);
|
|
||||||
return reopened;
|
return reopened;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws DbException {
|
public void close() throws DbException {
|
||||||
if (closed.getAndSet(true)) return;
|
if (closed.getAndSet(true)) return;
|
||||||
shutdown.removeShutdownHook(shutdownHandle);
|
|
||||||
db.close();
|
db.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,11 +135,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return new Transaction(db.startTransaction(), readOnly);
|
return new Transaction(db.startTransaction(), readOnly);
|
||||||
} catch (DbException e) {
|
} catch (DbException | RuntimeException e) {
|
||||||
if (readOnly) lock.readLock().unlock();
|
|
||||||
else lock.writeLock().unlock();
|
|
||||||
throw e;
|
|
||||||
} catch (RuntimeException e) {
|
|
||||||
if (readOnly) lock.readLock().unlock();
|
if (readOnly) lock.readLock().unlock();
|
||||||
else lock.writeLock().unlock();
|
else lock.writeLock().unlock();
|
||||||
throw e;
|
throw e;
|
||||||
@@ -225,7 +215,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
if (!db.containsGroup(txn, m.getGroupId()))
|
if (!db.containsGroup(txn, m.getGroupId()))
|
||||||
throw new NoSuchGroupException();
|
throw new NoSuchGroupException();
|
||||||
if (!db.containsMessage(txn, m.getId())) {
|
if (!db.containsMessage(txn, m.getId())) {
|
||||||
addMessage(txn, m, DELIVERED, shared, null);
|
db.addMessage(txn, m, DELIVERED, shared, null);
|
||||||
transaction.attach(new MessageAddedEvent(m, null));
|
transaction.attach(new MessageAddedEvent(m, null));
|
||||||
transaction.attach(new MessageStateChangedEvent(m.getId(), true,
|
transaction.attach(new MessageStateChangedEvent(m.getId(), true,
|
||||||
DELIVERED));
|
DELIVERED));
|
||||||
@@ -234,16 +224,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
db.mergeMessageMetadata(txn, m.getId(), meta);
|
db.mergeMessageMetadata(txn, m.getId(), meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addMessage(T txn, Message m, State state, boolean shared,
|
|
||||||
@Nullable ContactId sender) throws DbException {
|
|
||||||
db.addMessage(txn, m, state, shared);
|
|
||||||
for (ContactId c : db.getGroupVisibility(txn, m.getGroupId())) {
|
|
||||||
boolean offered = db.removeOfferedMessage(txn, c, m.getId());
|
|
||||||
boolean seen = offered || (sender != null && c.equals(sender));
|
|
||||||
db.addStatus(txn, c, m.getId(), seen, seen);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addTransport(Transaction transaction, TransportId t,
|
public void addTransport(Transaction transaction, TransportId t,
|
||||||
int maxLatency) throws DbException {
|
int maxLatency) throws DbException {
|
||||||
@@ -331,7 +311,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
if (!db.containsContact(txn, c))
|
if (!db.containsContact(txn, c))
|
||||||
throw new NoSuchContactException();
|
throw new NoSuchContactException();
|
||||||
Collection<MessageId> ids = db.getMessagesToSend(txn, c, maxLength);
|
Collection<MessageId> ids = db.getMessagesToSend(txn, c, maxLength);
|
||||||
List<byte[]> messages = new ArrayList<byte[]>(ids.size());
|
List<byte[]> messages = new ArrayList<>(ids.size());
|
||||||
for (MessageId m : ids) {
|
for (MessageId m : ids) {
|
||||||
messages.add(db.getRawMessage(txn, m));
|
messages.add(db.getRawMessage(txn, m));
|
||||||
db.updateExpiryTime(txn, c, m, maxLatency);
|
db.updateExpiryTime(txn, c, m, maxLatency);
|
||||||
@@ -381,7 +361,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
throw new NoSuchContactException();
|
throw new NoSuchContactException();
|
||||||
Collection<MessageId> ids = db.getRequestedMessagesToSend(txn, c,
|
Collection<MessageId> ids = db.getRequestedMessagesToSend(txn, c,
|
||||||
maxLength);
|
maxLength);
|
||||||
List<byte[]> messages = new ArrayList<byte[]>(ids.size());
|
List<byte[]> messages = new ArrayList<>(ids.size());
|
||||||
for (MessageId m : ids) {
|
for (MessageId m : ids) {
|
||||||
messages.add(db.getRawMessage(txn, m));
|
messages.add(db.getRawMessage(txn, m));
|
||||||
db.updateExpiryTime(txn, c, m, maxLatency);
|
db.updateExpiryTime(txn, c, m, maxLatency);
|
||||||
@@ -475,24 +455,24 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<MessageId> getMessagesToValidate(Transaction transaction,
|
public Collection<MessageId> getMessagesToValidate(Transaction transaction)
|
||||||
ClientId c) throws DbException {
|
throws DbException {
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
return db.getMessagesToValidate(txn, c);
|
return db.getMessagesToValidate(txn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<MessageId> getPendingMessages(Transaction transaction,
|
public Collection<MessageId> getPendingMessages(Transaction transaction)
|
||||||
ClientId c) throws DbException {
|
throws DbException {
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
return db.getPendingMessages(txn, c);
|
return db.getPendingMessages(txn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<MessageId> getMessagesToShare(
|
public Collection<MessageId> getMessagesToShare(Transaction transaction)
|
||||||
Transaction transaction, ClientId c) throws DbException {
|
throws DbException {
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
return db.getMessagesToShare(txn, c);
|
return db.getMessagesToShare(txn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -591,6 +571,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return db.getMessageDependents(txn, m);
|
return db.getMessageDependents(txn, m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getNextSendTime(Transaction transaction, ContactId c)
|
||||||
|
throws DbException {
|
||||||
|
T txn = unbox(transaction);
|
||||||
|
return db.getNextSendTime(txn, c);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Settings getSettings(Transaction transaction, String namespace)
|
public Settings getSettings(Transaction transaction, String namespace)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -661,7 +648,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
if (!db.containsContact(txn, c))
|
if (!db.containsContact(txn, c))
|
||||||
throw new NoSuchContactException();
|
throw new NoSuchContactException();
|
||||||
Collection<MessageId> acked = new ArrayList<MessageId>();
|
Collection<MessageId> acked = new ArrayList<>();
|
||||||
for (MessageId m : a.getMessageIds()) {
|
for (MessageId m : a.getMessageIds()) {
|
||||||
if (db.containsVisibleMessage(txn, c, m)) {
|
if (db.containsVisibleMessage(txn, c, m)) {
|
||||||
db.raiseSeenFlag(txn, c, m);
|
db.raiseSeenFlag(txn, c, m);
|
||||||
@@ -685,7 +672,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
db.raiseSeenFlag(txn, c, m.getId());
|
db.raiseSeenFlag(txn, c, m.getId());
|
||||||
db.raiseAckFlag(txn, c, m.getId());
|
db.raiseAckFlag(txn, c, m.getId());
|
||||||
} else {
|
} else {
|
||||||
addMessage(txn, m, UNKNOWN, false, c);
|
db.addMessage(txn, m, UNKNOWN, false, c);
|
||||||
transaction.attach(new MessageAddedEvent(m, c));
|
transaction.attach(new MessageAddedEvent(m, c));
|
||||||
}
|
}
|
||||||
transaction.attach(new MessageToAckEvent(c));
|
transaction.attach(new MessageToAckEvent(c));
|
||||||
@@ -753,7 +740,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
GroupId id = g.getId();
|
GroupId id = g.getId();
|
||||||
if (!db.containsGroup(txn, id))
|
if (!db.containsGroup(txn, id))
|
||||||
throw new NoSuchGroupException();
|
throw new NoSuchGroupException();
|
||||||
Collection<ContactId> affected = db.getGroupVisibility(txn, id);
|
Collection<ContactId> affected =
|
||||||
|
db.getGroupVisibility(txn, id).keySet();
|
||||||
db.removeGroup(txn, id);
|
db.removeGroup(txn, id);
|
||||||
transaction.attach(new GroupRemovedEvent(g));
|
transaction.attach(new GroupRemovedEvent(g));
|
||||||
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
||||||
@@ -770,6 +758,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
transaction.attach(new LocalAuthorRemovedEvent(a));
|
transaction.attach(new LocalAuthorRemovedEvent(a));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeMessage(Transaction transaction, MessageId m)
|
||||||
|
throws DbException {
|
||||||
|
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||||
|
T txn = unbox(transaction);
|
||||||
|
if (!db.containsMessage(txn, m))
|
||||||
|
throw new NoSuchMessageException();
|
||||||
|
db.removeMessage(txn, m);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeTransport(Transaction transaction, TransportId t)
|
public void removeTransport(Transaction transaction, TransportId t)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -813,19 +811,9 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
throw new NoSuchGroupException();
|
throw new NoSuchGroupException();
|
||||||
Visibility old = db.getGroupVisibility(txn, c, g);
|
Visibility old = db.getGroupVisibility(txn, c, g);
|
||||||
if (old == v) return;
|
if (old == v) return;
|
||||||
if (old == INVISIBLE) {
|
if (old == INVISIBLE) db.addGroupVisibility(txn, c, g, v == SHARED);
|
||||||
db.addGroupVisibility(txn, c, g, v == SHARED);
|
else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g);
|
||||||
for (MessageId m : db.getMessageIds(txn, g)) {
|
else db.setGroupVisibility(txn, c, g, v == SHARED);
|
||||||
boolean seen = db.removeOfferedMessage(txn, c, m);
|
|
||||||
db.addStatus(txn, c, m, seen, seen);
|
|
||||||
}
|
|
||||||
} else if (v == INVISIBLE) {
|
|
||||||
db.removeGroupVisibility(txn, c, g);
|
|
||||||
for (MessageId m : db.getMessageIds(txn, g))
|
|
||||||
db.removeStatus(txn, c, m);
|
|
||||||
} else {
|
|
||||||
db.setGroupVisibility(txn, c, g, v == SHARED);
|
|
||||||
}
|
|
||||||
List<ContactId> affected = Collections.singletonList(c);
|
List<ContactId> affected = Collections.singletonList(c);
|
||||||
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
||||||
}
|
}
|
||||||
@@ -886,8 +874,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
Map<ContactId, TransportKeys> keys) throws DbException {
|
Map<ContactId, TransportKeys> keys) throws DbException {
|
||||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
Map<ContactId, TransportKeys> filtered =
|
Map<ContactId, TransportKeys> filtered = new HashMap<>();
|
||||||
new HashMap<ContactId, TransportKeys>();
|
|
||||||
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
|
for (Entry<ContactId, TransportKeys> e : keys.entrySet()) {
|
||||||
ContactId c = e.getKey();
|
ContactId c = e.getKey();
|
||||||
TransportKeys k = e.getValue();
|
TransportKeys k = e.getValue();
|
||||||
|
|||||||
@@ -23,10 +23,4 @@ interface DatabaseConstants {
|
|||||||
*/
|
*/
|
||||||
String SCHEMA_VERSION_KEY = "schemaVersion";
|
String SCHEMA_VERSION_KEY = "schemaVersion";
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@link Settings} key under which the minimum supported database
|
|
||||||
* schema version is stored.
|
|
||||||
*/
|
|
||||||
String MIN_SCHEMA_VERSION_KEY = "minSchemaVersion";
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public class DatabaseExecutorModule {
|
|||||||
|
|
||||||
public DatabaseExecutorModule() {
|
public DatabaseExecutorModule() {
|
||||||
// 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();
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public class DatabaseModule {
|
|||||||
@Singleton
|
@Singleton
|
||||||
DatabaseComponent provideDatabaseComponent(Database<Connection> db,
|
DatabaseComponent provideDatabaseComponent(Database<Connection> db,
|
||||||
EventBus eventBus, ShutdownManager shutdown) {
|
EventBus eventBus, ShutdownManager shutdown) {
|
||||||
return new DatabaseComponentImpl<Connection>(db, Connection.class,
|
return new DatabaseComponentImpl<>(db, Connection.class, eventBus,
|
||||||
eventBus, shutdown);
|
shutdown);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.briarproject.bramble.db;
|
|||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.api.db.DatabaseConfig;
|
import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.db.MigrationListener;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
@@ -13,6 +14,7 @@ import java.sql.DriverManager;
|
|||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,28 +24,31 @@ import javax.inject.Inject;
|
|||||||
class H2Database extends JdbcDatabase {
|
class H2Database extends JdbcDatabase {
|
||||||
|
|
||||||
private static final String HASH_TYPE = "BINARY(32)";
|
private static final String HASH_TYPE = "BINARY(32)";
|
||||||
|
private static final String SECRET_TYPE = "BINARY(32)";
|
||||||
private static final String BINARY_TYPE = "BINARY";
|
private static final String BINARY_TYPE = "BINARY";
|
||||||
private static final String COUNTER_TYPE = "INT NOT NULL AUTO_INCREMENT";
|
private static final String COUNTER_TYPE = "INT NOT NULL AUTO_INCREMENT";
|
||||||
private static final String SECRET_TYPE = "BINARY(32)";
|
private static final String STRING_TYPE = "VARCHAR";
|
||||||
|
|
||||||
private final DatabaseConfig config;
|
private final DatabaseConfig config;
|
||||||
private final String url;
|
private final String url;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
H2Database(DatabaseConfig config, Clock clock) {
|
H2Database(DatabaseConfig config, Clock clock) {
|
||||||
super(HASH_TYPE, BINARY_TYPE, COUNTER_TYPE, SECRET_TYPE, clock);
|
super(HASH_TYPE, SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE,
|
||||||
|
clock);
|
||||||
this.config = config;
|
this.config = config;
|
||||||
File dir = config.getDatabaseDirectory();
|
File dir = config.getDatabaseDirectory();
|
||||||
String path = new File(dir, "db").getAbsolutePath();
|
String path = new File(dir, "db").getAbsolutePath();
|
||||||
url = "jdbc:h2:split:" + path + ";CIPHER=AES;MULTI_THREADED=1"
|
url = "jdbc:h2:split:" + path + ";CIPHER=AES;MULTI_THREADED=1"
|
||||||
+ ";WRITE_DELAY=0;DB_CLOSE_ON_EXIT=false";
|
+ ";WRITE_DELAY=0";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean open() throws DbException {
|
public boolean open(@Nullable MigrationListener listener)
|
||||||
|
throws DbException {
|
||||||
boolean reopen = config.databaseExists();
|
boolean reopen = config.databaseExists();
|
||||||
if (!reopen) config.getDatabaseDirectory().mkdirs();
|
if (!reopen) config.getDatabaseDirectory().mkdirs();
|
||||||
super.open("org.h2.Driver", reopen);
|
super.open("org.h2.Driver", reopen, listener);
|
||||||
return reopen;
|
return reopen;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,6 +95,10 @@ class H2Database extends JdbcDatabase {
|
|||||||
// Separate the file password from the user password with a space
|
// Separate the file password from the user password with a space
|
||||||
String hex = StringUtils.toHexString(key.getBytes());
|
String hex = StringUtils.toHexString(key.getBytes());
|
||||||
props.put("password", hex + " password");
|
props.put("password", hex + " password");
|
||||||
return DriverManager.getConnection(url, props);
|
return DriverManager.getConnection(getUrl(), props);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getUrl() {
|
||||||
|
return url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package org.briarproject.bramble.db;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.db.MigrationListener;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains all the HSQLDB-specific code for the database.
|
||||||
|
*/
|
||||||
|
@NotNullByDefault
|
||||||
|
class HyperSqlDatabase extends JdbcDatabase {
|
||||||
|
|
||||||
|
private static final String HASH_TYPE = "BINARY(32)";
|
||||||
|
private static final String SECRET_TYPE = "BINARY(32)";
|
||||||
|
private static final String BINARY_TYPE = "BINARY";
|
||||||
|
private static final String COUNTER_TYPE =
|
||||||
|
"INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY(START WITH 1)";
|
||||||
|
private static final String STRING_TYPE = "VARCHAR";
|
||||||
|
|
||||||
|
private final DatabaseConfig config;
|
||||||
|
private final String url;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
HyperSqlDatabase(DatabaseConfig config, Clock clock) {
|
||||||
|
super(HASH_TYPE, SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE,
|
||||||
|
clock);
|
||||||
|
this.config = config;
|
||||||
|
File dir = config.getDatabaseDirectory();
|
||||||
|
String path = new File(dir, "db").getAbsolutePath();
|
||||||
|
url = "jdbc:hsqldb:file:" + path
|
||||||
|
+ ";sql.enforce_size=false;allow_empty_batch=true"
|
||||||
|
+ ";encrypt_lobs=true;crypt_type=AES";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean open(@Nullable MigrationListener listener) throws DbException {
|
||||||
|
boolean reopen = config.databaseExists();
|
||||||
|
if (!reopen) config.getDatabaseDirectory().mkdirs();
|
||||||
|
super.open("org.hsqldb.jdbc.JDBCDriver", reopen, listener);
|
||||||
|
return reopen;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws DbException {
|
||||||
|
try {
|
||||||
|
super.closeAllConnections();
|
||||||
|
Connection c = createConnection();
|
||||||
|
Statement s = c.createStatement();
|
||||||
|
s.executeQuery("SHUTDOWN");
|
||||||
|
s.close();
|
||||||
|
c.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getFreeSpace() throws DbException {
|
||||||
|
File dir = config.getDatabaseDirectory();
|
||||||
|
long maxSize = config.getMaxSize();
|
||||||
|
long free = dir.getFreeSpace();
|
||||||
|
long used = getDiskSpace(dir);
|
||||||
|
long quota = maxSize - used;
|
||||||
|
return Math.min(free, quota);
|
||||||
|
}
|
||||||
|
|
||||||
|
private long getDiskSpace(File f) {
|
||||||
|
if (f.isDirectory()) {
|
||||||
|
long total = 0;
|
||||||
|
File[] children = f.listFiles();
|
||||||
|
if (children != null)
|
||||||
|
for (File child : children) total += getDiskSpace(child);
|
||||||
|
return total;
|
||||||
|
} else if (f.isFile()) {
|
||||||
|
return f.length();
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Connection createConnection() throws SQLException {
|
||||||
|
SecretKey key = config.getEncryptionKey();
|
||||||
|
if (key == null) throw new IllegalStateException();
|
||||||
|
String hex = StringUtils.toHexString(key.getBytes());
|
||||||
|
return DriverManager.getConnection(url + ";crypt_key=" + hex);
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user