mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +01:00
Compare commits
201 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 | ||
|
|
37d4d79c64 | ||
|
|
05bc3f6a71 | ||
|
|
8b3960781a | ||
|
|
f3de4f53c5 | ||
|
|
166fc2948c |
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>
|
||||||
@@ -12,8 +12,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 14
|
minSdkVersion 14
|
||||||
targetSdkVersion 26
|
targetSdkVersion 26
|
||||||
versionCode 1620
|
versionCode 1700
|
||||||
versionName "0.16.20"
|
versionName "0.17.0"
|
||||||
consumerProguardFiles 'proguard-rules.txt'
|
consumerProguardFiles 'proguard-rules.txt'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,6 +43,7 @@ dependencyVerification {
|
|||||||
'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728',
|
'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728',
|
||||||
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
|
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
|
||||||
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||||
|
'net.i2p.crypto:eddsa:0.2.0:eddsa-0.2.0.jar:a7cb1b85c16e2f0730b9204106929a1d9aaae1df728adc7041a8b8b605692140',
|
||||||
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
|
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
|
||||||
'org.jacoco:org.jacoco.agent:0.7.4.201502262128:org.jacoco.agent-0.7.4.201502262128-runtime.jar:e357a0f1d573c2f702a273992b1b6cb661734f66311854efb3778a888515c5b5',
|
'org.jacoco:org.jacoco.agent:0.7.4.201502262128:org.jacoco.agent-0.7.4.201502262128-runtime.jar:e357a0f1d573c2f702a273992b1b6cb661734f66311854efb3778a888515c5b5',
|
||||||
'org.jacoco:org.jacoco.agent:0.7.4.201502262128:org.jacoco.agent-0.7.4.201502262128.jar:47b4bec6df11a1118da3953da8b9fa1e7079d6fec857faa1a3cf912e53a6fd4e',
|
'org.jacoco:org.jacoco.agent:0.7.4.201502262128:org.jacoco.agent-0.7.4.201502262128.jar:47b4bec6df11a1118da3953da8b9fa1e7079d6fec857faa1a3cf912e53a6fd4e',
|
||||||
|
|||||||
@@ -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.**
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ public class AndroidPluginModule {
|
|||||||
appContext, locationUtils, reporter, eventBus,
|
appContext, locationUtils, reporter, eventBus,
|
||||||
torSocketFactory, backoffFactory);
|
torSocketFactory, backoffFactory);
|
||||||
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
|
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
|
||||||
scheduler, backoffFactory, appContext);
|
backoffFactory, appContext);
|
||||||
Collection<DuplexPluginFactory> duplex =
|
Collection<DuplexPluginFactory> duplex =
|
||||||
Arrays.asList(bluetooth, tor, lan);
|
Arrays.asList(bluetooth, tor, lan);
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
|
|||||||
@@ -5,84 +5,43 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.net.Network;
|
|
||||||
import android.net.NetworkInfo;
|
import android.net.NetworkInfo;
|
||||||
import android.net.wifi.WifiInfo;
|
import android.os.Bundle;
|
||||||
import android.net.wifi.WifiManager;
|
|
||||||
|
|
||||||
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.duplex.DuplexPluginCallback;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.util.Collection;
|
|
||||||
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.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.net.SocketFactory;
|
|
||||||
|
|
||||||
import static android.content.Context.CONNECTIVITY_SERVICE;
|
import static android.content.Context.CONNECTIVITY_SERVICE;
|
||||||
import static android.content.Context.WIFI_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 android.net.wifi.WifiManager.EXTRA_WIFI_STATE;
|
||||||
import static android.os.Build.VERSION.SDK_INT;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.Collections.emptyList;
|
import static org.briarproject.bramble.util.AndroidUtils.logNetworkState;
|
||||||
import static java.util.Collections.singletonList;
|
|
||||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class AndroidLanTcpPlugin extends LanTcpPlugin {
|
class AndroidLanTcpPlugin extends LanTcpPlugin {
|
||||||
|
|
||||||
// See android.net.wifi.WifiManager
|
private static final String WIFI_AP_STATE_ACTION =
|
||||||
private static final String WIFI_AP_STATE_CHANGED_ACTION =
|
|
||||||
"android.net.wifi.WIFI_AP_STATE_CHANGED";
|
"android.net.wifi.WIFI_AP_STATE_CHANGED";
|
||||||
private static final int WIFI_AP_STATE_ENABLED = 13;
|
|
||||||
|
|
||||||
private static final byte[] WIFI_AP_ADDRESS_BYTES =
|
|
||||||
{(byte) 192, (byte) 168, 43, 1};
|
|
||||||
private static final InetAddress WIFI_AP_ADDRESS;
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(AndroidLanTcpPlugin.class.getName());
|
Logger.getLogger(AndroidLanTcpPlugin.class.getName());
|
||||||
|
|
||||||
static {
|
|
||||||
try {
|
|
||||||
WIFI_AP_ADDRESS = InetAddress.getByAddress(WIFI_AP_ADDRESS_BYTES);
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
// Should only be thrown if the address has an illegal length
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final ScheduledExecutorService scheduler;
|
|
||||||
private final Context appContext;
|
private final Context appContext;
|
||||||
private final ConnectivityManager connectivityManager;
|
|
||||||
@Nullable
|
|
||||||
private final WifiManager wifiManager;
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private volatile BroadcastReceiver networkStateReceiver = null;
|
private volatile BroadcastReceiver networkStateReceiver = null;
|
||||||
private volatile SocketFactory socketFactory;
|
|
||||||
|
|
||||||
AndroidLanTcpPlugin(Executor ioExecutor, ScheduledExecutorService scheduler,
|
AndroidLanTcpPlugin(Executor ioExecutor, Backoff backoff,
|
||||||
Backoff backoff, Context appContext, DuplexPluginCallback callback,
|
Context appContext, DuplexPluginCallback callback, int maxLatency,
|
||||||
int maxLatency, int maxIdleTime) {
|
int maxIdleTime) {
|
||||||
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime);
|
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime);
|
||||||
this.scheduler = scheduler;
|
|
||||||
this.appContext = appContext;
|
this.appContext = appContext;
|
||||||
ConnectivityManager connectivityManager = (ConnectivityManager)
|
|
||||||
appContext.getSystemService(CONNECTIVITY_SERVICE);
|
|
||||||
if (connectivityManager == null) throw new AssertionError();
|
|
||||||
this.connectivityManager = connectivityManager;
|
|
||||||
wifiManager = (WifiManager) appContext.getApplicationContext()
|
|
||||||
.getSystemService(WIFI_SERVICE);
|
|
||||||
socketFactory = SocketFactory.getDefault();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -93,8 +52,9 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
|
|||||||
networkStateReceiver = new NetworkStateReceiver();
|
networkStateReceiver = new NetworkStateReceiver();
|
||||||
IntentFilter filter = new IntentFilter();
|
IntentFilter filter = new IntentFilter();
|
||||||
filter.addAction(CONNECTIVITY_ACTION);
|
filter.addAction(CONNECTIVITY_ACTION);
|
||||||
filter.addAction(WIFI_AP_STATE_CHANGED_ACTION);
|
filter.addAction(WIFI_AP_STATE_ACTION);
|
||||||
appContext.registerReceiver(networkStateReceiver, filter);
|
appContext.registerReceiver(networkStateReceiver, filter);
|
||||||
|
if (LOG.isLoggable(INFO)) logNetworkState(appContext, LOG);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -105,92 +65,38 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
|
|||||||
tryToClose(socket);
|
tryToClose(socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Socket createSocket() throws IOException {
|
|
||||||
return socketFactory.createSocket();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Collection<InetAddress> getLocalIpAddresses() {
|
|
||||||
// If the device doesn't have wifi, don't open any sockets
|
|
||||||
if (wifiManager == null) return emptyList();
|
|
||||||
// If we're connected to a wifi network, use that network
|
|
||||||
WifiInfo info = wifiManager.getConnectionInfo();
|
|
||||||
if (info != null && info.getIpAddress() != 0)
|
|
||||||
return singletonList(intToInetAddress(info.getIpAddress()));
|
|
||||||
// If we're running an access point, return its address
|
|
||||||
if (super.getLocalIpAddresses().contains(WIFI_AP_ADDRESS))
|
|
||||||
return singletonList(WIFI_AP_ADDRESS);
|
|
||||||
// No suitable addresses
|
|
||||||
return emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private InetAddress intToInetAddress(int ip) {
|
|
||||||
byte[] ipBytes = new byte[4];
|
|
||||||
ipBytes[0] = (byte) (ip & 0xFF);
|
|
||||||
ipBytes[1] = (byte) ((ip >> 8) & 0xFF);
|
|
||||||
ipBytes[2] = (byte) ((ip >> 16) & 0xFF);
|
|
||||||
ipBytes[3] = (byte) ((ip >> 24) & 0xFF);
|
|
||||||
try {
|
|
||||||
return InetAddress.getByAddress(ipBytes);
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
// Should only be thrown if address has illegal length
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// On API 21 and later, a socket that is not created with the wifi
|
|
||||||
// network's socket factory may try to connect via another network
|
|
||||||
private SocketFactory getSocketFactory() {
|
|
||||||
if (SDK_INT < 21) return SocketFactory.getDefault();
|
|
||||||
for (Network net : connectivityManager.getAllNetworks()) {
|
|
||||||
NetworkInfo info = connectivityManager.getNetworkInfo(net);
|
|
||||||
if (info != null && info.getType() == TYPE_WIFI)
|
|
||||||
return net.getSocketFactory();
|
|
||||||
}
|
|
||||||
LOG.warning("Could not find suitable socket factory");
|
|
||||||
return SocketFactory.getDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (isApEnabledEvent(i)) {
|
if (LOG.isLoggable(INFO)) {
|
||||||
// The state change may be broadcast before the AP address is
|
if (CONNECTIVITY_ACTION.equals(i.getAction())) {
|
||||||
// visible, so delay handling the event
|
LOG.info("Connectivity change");
|
||||||
scheduler.schedule(this::handleConnectivityChange, 1, SECONDS);
|
Bundle extras = i.getExtras();
|
||||||
} else {
|
if (extras != null) {
|
||||||
handleConnectivityChange();
|
LOG.info("Extras:");
|
||||||
|
for (String key : extras.keySet())
|
||||||
|
LOG.info("\t" + key + ": " + extras.get(key));
|
||||||
|
}
|
||||||
|
} else if (WIFI_AP_STATE_ACTION.equals(i.getAction())) {
|
||||||
|
int state = i.getIntExtra(EXTRA_WIFI_STATE, 0);
|
||||||
|
if (state == 13) LOG.info("Wifi AP enabled");
|
||||||
|
else LOG.info("Wifi AP state " + state);
|
||||||
|
}
|
||||||
|
logNetworkState(appContext, LOG);
|
||||||
}
|
}
|
||||||
}
|
Object o = ctx.getSystemService(CONNECTIVITY_SERVICE);
|
||||||
|
ConnectivityManager cm = (ConnectivityManager) o;
|
||||||
private void handleConnectivityChange() {
|
NetworkInfo net = cm.getActiveNetworkInfo();
|
||||||
if (!running) return;
|
if (net != null && net.getType() == TYPE_WIFI
|
||||||
Collection<InetAddress> addrs = getLocalIpAddresses();
|
&& net.isConnected()) {
|
||||||
if (addrs.contains(WIFI_AP_ADDRESS)) {
|
LOG.info("Connected to Wi-Fi");
|
||||||
LOG.info("Providing wifi hotspot");
|
|
||||||
// There's no corresponding Network object and thus no way
|
|
||||||
// to get a suitable socket factory, so we won't be able to
|
|
||||||
// make outgoing connections on API 21+ if another network
|
|
||||||
// has internet access
|
|
||||||
socketFactory = SocketFactory.getDefault();
|
|
||||||
if (socket == null || socket.isClosed()) bind();
|
if (socket == null || socket.isClosed()) bind();
|
||||||
} else if (addrs.isEmpty()) {
|
} else {
|
||||||
LOG.info("Not connected to wifi");
|
LOG.info("Not connected to Wi-Fi");
|
||||||
socketFactory = SocketFactory.getDefault();
|
|
||||||
tryToClose(socket);
|
tryToClose(socket);
|
||||||
} else {
|
|
||||||
LOG.info("Connected to wifi");
|
|
||||||
socketFactory = getSocketFactory();
|
|
||||||
if (socket == null || socket.isClosed()) bind();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isApEnabledEvent(Intent i) {
|
|
||||||
return WIFI_AP_STATE_CHANGED_ACTION.equals(i.getAction()) &&
|
|
||||||
i.getIntExtra(EXTRA_WIFI_STATE, 0) == WIFI_AP_STATE_ENABLED;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
|||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
@@ -28,15 +27,12 @@ public class AndroidLanTcpPluginFactory 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 BackoffFactory backoffFactory;
|
private final BackoffFactory backoffFactory;
|
||||||
private final Context appContext;
|
private final Context appContext;
|
||||||
|
|
||||||
public AndroidLanTcpPluginFactory(Executor ioExecutor,
|
public AndroidLanTcpPluginFactory(Executor ioExecutor,
|
||||||
ScheduledExecutorService scheduler, BackoffFactory backoffFactory,
|
BackoffFactory backoffFactory, Context appContext) {
|
||||||
Context appContext) {
|
|
||||||
this.ioExecutor = ioExecutor;
|
this.ioExecutor = ioExecutor;
|
||||||
this.scheduler = scheduler;
|
|
||||||
this.backoffFactory = backoffFactory;
|
this.backoffFactory = backoffFactory;
|
||||||
this.appContext = appContext;
|
this.appContext = appContext;
|
||||||
}
|
}
|
||||||
@@ -55,7 +51,7 @@ public class AndroidLanTcpPluginFactory 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 AndroidLanTcpPlugin(ioExecutor, scheduler, backoff,
|
return new AndroidLanTcpPlugin(ioExecutor, backoff, appContext,
|
||||||
appContext, callback, MAX_LATENCY, MAX_IDLE_TIME);
|
callback, MAX_LATENCY, MAX_IDLE_TIME);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import android.os.PowerManager;
|
|||||||
import net.freehaven.tor.control.EventHandler;
|
import net.freehaven.tor.control.EventHandler;
|
||||||
import net.freehaven.tor.control.TorControlConnection;
|
import net.freehaven.tor.control.TorControlConnection;
|
||||||
|
|
||||||
import org.briarproject.bramble.PoliteExecutor;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.event.Event;
|
import org.briarproject.bramble.api.event.Event;
|
||||||
@@ -64,6 +63,8 @@ import java.util.concurrent.Future;
|
|||||||
import java.util.concurrent.ScheduledExecutorService;
|
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.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;
|
||||||
@@ -110,7 +111,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(TorPlugin.class.getName());
|
Logger.getLogger(TorPlugin.class.getName());
|
||||||
|
|
||||||
private final Executor ioExecutor, connectionStatusExecutor;
|
private final Executor ioExecutor;
|
||||||
private final ScheduledExecutorService scheduler;
|
private final ScheduledExecutorService scheduler;
|
||||||
private final Context appContext;
|
private final Context appContext;
|
||||||
private final LocationUtils locationUtils;
|
private final LocationUtils locationUtils;
|
||||||
@@ -124,6 +125,7 @@ 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 =
|
private final AtomicReference<Future<?>> connectivityCheck =
|
||||||
new AtomicReference<>();
|
new AtomicReference<>();
|
||||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||||
@@ -165,9 +167,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
// This tag will prevent Huawei's powermanager from killing us.
|
// This tag will prevent Huawei's powermanager from killing us.
|
||||||
wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "LocationManagerService");
|
wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "LocationManagerService");
|
||||||
wakeLock.setReferenceCounted(false);
|
wakeLock.setReferenceCounted(false);
|
||||||
// Don't execute more than one connection status check at a time
|
connectionStatusLock = new ReentrantLock();
|
||||||
connectionStatusExecutor = new PoliteExecutor("TorPlugin",
|
|
||||||
ioExecutor, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -697,46 +697,55 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateConnectionStatus() {
|
private void updateConnectionStatus() {
|
||||||
connectionStatusExecutor.execute(() -> {
|
ioExecutor.execute(() -> {
|
||||||
if (!running) return;
|
if (!running) return;
|
||||||
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 {
|
try {
|
||||||
if (!online) {
|
connectionStatusLock.lock();
|
||||||
LOG.info("Disabling network, device is offline");
|
updateConnectionStatusLocked();
|
||||||
enableNetwork(false);
|
} finally {
|
||||||
} else if (blocked) {
|
connectionStatusLock.unlock();
|
||||||
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() {
|
private void scheduleConnectionStatusUpdate() {
|
||||||
Future<?> newConnectivityCheck =
|
Future<?> newConnectivityCheck =
|
||||||
scheduler.schedule(this::updateConnectionStatus, 1, MINUTES);
|
scheduler.schedule(this::updateConnectionStatus, 1, MINUTES);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,6 +146,14 @@ public class StringUtils {
|
|||||||
return s.toString();
|
return s.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String ipToString(int ip) {
|
||||||
|
int ip1 = ip & 0xFF;
|
||||||
|
int ip2 = (ip >> 8) & 0xFF;
|
||||||
|
int ip3 = (ip >> 16) & 0xFF;
|
||||||
|
int ip4 = (ip >> 24) & 0xFF;
|
||||||
|
return ip1 + "." + ip2 + "." + ip3 + "." + ip4;
|
||||||
|
}
|
||||||
|
|
||||||
public static String getRandomString(int length) {
|
public static String getRandomString(int length) {
|
||||||
char[] c = new char[length];
|
char[] c = new char[length];
|
||||||
for (int i = 0; i < length; i++)
|
for (int i = 0; i < length; i++)
|
||||||
|
|||||||
@@ -13,9 +13,14 @@ import org.briarproject.bramble.api.sync.MessageId;
|
|||||||
import org.briarproject.bramble.util.IoUtils;
|
import org.briarproject.bramble.util.IoUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
||||||
@@ -63,7 +68,8 @@ public class TestUtils {
|
|||||||
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||||
byte[] privateKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
byte[] privateKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||||
long created = System.currentTimeMillis();
|
long created = System.currentTimeMillis();
|
||||||
return new LocalAuthor(id, name, publicKey, privateKey, created);
|
return new LocalAuthor(id, FORMAT_VERSION, name, publicKey, privateKey,
|
||||||
|
created);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Author getAuthor() {
|
public static Author getAuthor() {
|
||||||
@@ -74,7 +80,7 @@ public class TestUtils {
|
|||||||
AuthorId id = new AuthorId(getRandomId());
|
AuthorId id = new AuthorId(getRandomId());
|
||||||
String name = getRandomString(nameLength);
|
String name = getRandomString(nameLength);
|
||||||
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||||
return new Author(id, name, publicKey);
|
return new Author(id, FORMAT_VERSION, name, publicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Group getGroup(ClientId clientId) {
|
public static Group getGroup(ClientId clientId) {
|
||||||
@@ -99,4 +105,38 @@ public class TestUtils {
|
|||||||
long timestamp = System.currentTimeMillis();
|
long timestamp = System.currentTimeMillis();
|
||||||
return new Message(id, groupId, timestamp, raw);
|
return new Message(id, groupId, timestamp, raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static double getMedian(Collection<? extends Number> samples) {
|
||||||
|
int size = samples.size();
|
||||||
|
if (size == 0) throw new IllegalArgumentException();
|
||||||
|
List<Double> sorted = new ArrayList<>(size);
|
||||||
|
for (Number n : samples) sorted.add(n.doubleValue());
|
||||||
|
Collections.sort(sorted);
|
||||||
|
if (size % 2 == 1) return sorted.get(size / 2);
|
||||||
|
double low = sorted.get(size / 2 - 1), high = sorted.get(size / 2);
|
||||||
|
return (low + high) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double getMean(Collection<? extends Number> samples) {
|
||||||
|
if (samples.isEmpty()) throw new IllegalArgumentException();
|
||||||
|
double sum = 0;
|
||||||
|
for (Number n : samples) sum += n.doubleValue();
|
||||||
|
return sum / samples.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double getVariance(Collection<? extends Number> samples) {
|
||||||
|
if (samples.size() < 2) throw new IllegalArgumentException();
|
||||||
|
double mean = getMean(samples);
|
||||||
|
double sumSquareDiff = 0;
|
||||||
|
for (Number n : samples) {
|
||||||
|
double diff = n.doubleValue() - mean;
|
||||||
|
sumSquareDiff += diff * diff;
|
||||||
|
}
|
||||||
|
return sumSquareDiff / (samples.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double getStandardDeviation(
|
||||||
|
Collection<? extends Number> samples) {
|
||||||
|
return Math.sqrt(getVariance(samples));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,12 +9,15 @@ apply plugin: 'witness'
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation project(path: ':bramble-api', configuration: 'default')
|
implementation project(path: ':bramble-api', configuration: 'default')
|
||||||
implementation 'com.madgag.spongycastle:core:1.58.0.0'
|
implementation 'com.madgag.spongycastle:core:1.58.0.0'
|
||||||
implementation 'com.h2database:h2:1.4.192' // This is the last version that supports Java 1.6
|
implementation 'com.h2database:h2:1.4.192' // The last version that supports Java 1.6
|
||||||
implementation 'org.bitlet:weupnp:0.1.4'
|
implementation 'org.bitlet:weupnp:0.1.4'
|
||||||
|
implementation 'net.i2p.crypto:eddsa:0.2.0'
|
||||||
|
implementation 'org.whispersystems:curve25519-java:0.4.1'
|
||||||
|
|
||||||
apt 'com.google.dagger:dagger-compiler:2.0.2'
|
apt 'com.google.dagger:dagger-compiler:2.0.2'
|
||||||
|
|
||||||
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
|
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
|
||||||
|
testImplementation 'org.hsqldb:hsqldb:2.3.5' // The last version that supports Java 1.6
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
testImplementation "org.jmock:jmock:2.8.2"
|
testImplementation "org.jmock:jmock:2.8.2"
|
||||||
testImplementation "org.jmock:jmock-junit4:2.8.2"
|
testImplementation "org.jmock:jmock-junit4:2.8.2"
|
||||||
@@ -37,18 +40,21 @@ dependencyVerification {
|
|||||||
'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728',
|
'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728',
|
||||||
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||||
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
|
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
|
||||||
|
'net.i2p.crypto:eddsa:0.2.0:eddsa-0.2.0.jar:a7cb1b85c16e2f0730b9204106929a1d9aaae1df728adc7041a8b8b605692140',
|
||||||
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
|
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
|
||||||
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
|
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
|
||||||
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
|
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
|
||||||
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
|
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
|
||||||
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
|
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
|
||||||
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c',
|
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c',
|
||||||
|
'org.hsqldb:hsqldb:2.3.5:hsqldb-2.3.5.jar:6676a6977ac98997a80f827ddbd3fe8ca1e0853dad1492512135fd1a222ccfad',
|
||||||
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c',
|
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c',
|
||||||
'org.jmock:jmock-legacy:2.8.2:jmock-legacy-2.8.2.jar:f2b985a5c08a9edb7f37612330c058809da3f6a6d63ce792426ebf8ff0d6d31b',
|
'org.jmock:jmock-legacy:2.8.2:jmock-legacy-2.8.2.jar:f2b985a5c08a9edb7f37612330c058809da3f6a6d63ce792426ebf8ff0d6d31b',
|
||||||
'org.jmock:jmock-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760',
|
'org.jmock:jmock-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760',
|
||||||
'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16',
|
'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16',
|
||||||
'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80',
|
'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80',
|
||||||
'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220',
|
'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220',
|
||||||
|
'org.whispersystems:curve25519-java:0.4.1:curve25519-java-0.4.1.jar:7dd659d8822c06c3aea1a47f18fac9e5761e29cab8100030b877db445005f03e',
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
|
|||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.Metadata;
|
import org.briarproject.bramble.api.db.Metadata;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
|
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
@@ -32,7 +34,12 @@ import java.util.Map.Entry;
|
|||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
|
||||||
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||||
|
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||||
|
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
@@ -51,12 +58,14 @@ class ClientHelperImpl implements ClientHelper {
|
|||||||
private final MetadataParser metadataParser;
|
private final MetadataParser metadataParser;
|
||||||
private final MetadataEncoder metadataEncoder;
|
private final MetadataEncoder metadataEncoder;
|
||||||
private final CryptoComponent crypto;
|
private final CryptoComponent crypto;
|
||||||
|
private final AuthorFactory authorFactory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ClientHelperImpl(DatabaseComponent db, MessageFactory messageFactory,
|
ClientHelperImpl(DatabaseComponent db, MessageFactory messageFactory,
|
||||||
BdfReaderFactory bdfReaderFactory,
|
BdfReaderFactory bdfReaderFactory,
|
||||||
BdfWriterFactory bdfWriterFactory, MetadataParser metadataParser,
|
BdfWriterFactory bdfWriterFactory, MetadataParser metadataParser,
|
||||||
MetadataEncoder metadataEncoder, CryptoComponent crypto) {
|
MetadataEncoder metadataEncoder, CryptoComponent crypto,
|
||||||
|
AuthorFactory authorFactory) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.messageFactory = messageFactory;
|
this.messageFactory = messageFactory;
|
||||||
this.bdfReaderFactory = bdfReaderFactory;
|
this.bdfReaderFactory = bdfReaderFactory;
|
||||||
@@ -64,6 +73,7 @@ class ClientHelperImpl implements ClientHelper {
|
|||||||
this.metadataParser = metadataParser;
|
this.metadataParser = metadataParser;
|
||||||
this.metadataEncoder = metadataEncoder;
|
this.metadataEncoder = metadataEncoder;
|
||||||
this.crypto = crypto;
|
this.crypto = crypto;
|
||||||
|
this.authorFactory = authorFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -341,6 +351,11 @@ class ClientHelperImpl implements ClientHelper {
|
|||||||
raw.length - MESSAGE_HEADER_LENGTH);
|
raw.length - MESSAGE_HEADER_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BdfList toList(Author a) {
|
||||||
|
return BdfList.of(a.getFormatVersion(), a.getName(), a.getPublicKey());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] sign(String label, BdfList toSign, byte[] privateKey)
|
public byte[] sign(String label, BdfList toSign, byte[] privateKey)
|
||||||
throws FormatException, GeneralSecurityException {
|
throws FormatException, GeneralSecurityException {
|
||||||
@@ -355,4 +370,16 @@ class ClientHelperImpl implements ClientHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Author parseAndValidateAuthor(BdfList author)
|
||||||
|
throws FormatException {
|
||||||
|
checkSize(author, 3);
|
||||||
|
int formatVersion = author.getLong(0).intValue();
|
||||||
|
if (formatVersion != FORMAT_VERSION) throw new FormatException();
|
||||||
|
String name = author.getString(1);
|
||||||
|
checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH);
|
||||||
|
byte[] publicKey = author.getRaw(2);
|
||||||
|
checkLength(publicKey, 1, MAX_PUBLIC_KEY_LENGTH);
|
||||||
|
return authorFactory.createAuthor(formatVersion, name, publicKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
@@ -196,8 +201,8 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Add the contact
|
// Add the contact
|
||||||
ContactId contactId = addContact(remoteAuthor, masterSecret,
|
ContactId contactId = addContact(remoteAuthor, timestamp,
|
||||||
timestamp, alice, remoteProperties);
|
remoteProperties);
|
||||||
// Reuse the connection as a transport connection
|
// Reuse the connection as a transport connection
|
||||||
connectionManager.manageOutgoingConnection(contactId, transportId,
|
connectionManager.manageOutgoingConnection(contactId, transportId,
|
||||||
conn);
|
conn);
|
||||||
@@ -223,6 +228,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
|
|
||||||
// Write the name, public key and signature
|
// Write the name, public key and signature
|
||||||
w.writeListStart();
|
w.writeListStart();
|
||||||
|
w.writeLong(localAuthor.getFormatVersion());
|
||||||
w.writeString(localAuthor.getName());
|
w.writeString(localAuthor.getName());
|
||||||
w.writeRaw(localAuthor.getPublicKey());
|
w.writeRaw(localAuthor.getPublicKey());
|
||||||
w.writeRaw(sig);
|
w.writeRaw(sig);
|
||||||
@@ -232,11 +238,16 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
|
|
||||||
private Author receivePseudonym(BdfReader r, byte[] nonce)
|
private Author receivePseudonym(BdfReader r, byte[] nonce)
|
||||||
throws GeneralSecurityException, IOException {
|
throws GeneralSecurityException, IOException {
|
||||||
// Read the name, public key and signature
|
// Read the format version, name, public key and signature
|
||||||
r.readListStart();
|
r.readListStart();
|
||||||
|
int formatVersion = (int) r.readLong();
|
||||||
|
if (formatVersion != FORMAT_VERSION) throw new FormatException();
|
||||||
String name = r.readString(MAX_AUTHOR_NAME_LENGTH);
|
String name = r.readString(MAX_AUTHOR_NAME_LENGTH);
|
||||||
|
if (name.isEmpty()) throw new FormatException();
|
||||||
byte[] publicKey = r.readRaw(MAX_PUBLIC_KEY_LENGTH);
|
byte[] publicKey = r.readRaw(MAX_PUBLIC_KEY_LENGTH);
|
||||||
|
if (publicKey.length == 0) throw new FormatException();
|
||||||
byte[] sig = r.readRaw(MAX_SIGNATURE_LENGTH);
|
byte[] sig = r.readRaw(MAX_SIGNATURE_LENGTH);
|
||||||
|
if (sig.length == 0) throw new FormatException();
|
||||||
r.readListEnd();
|
r.readListEnd();
|
||||||
LOG.info("Received pseudonym");
|
LOG.info("Received pseudonym");
|
||||||
// Verify the signature
|
// Verify the signature
|
||||||
@@ -245,7 +256,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
LOG.info("Invalid signature");
|
LOG.info("Invalid signature");
|
||||||
throw new GeneralSecurityException();
|
throw new GeneralSecurityException();
|
||||||
}
|
}
|
||||||
return authorFactory.createAuthor(name, publicKey);
|
return authorFactory.createAuthor(formatVersion, name, publicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendTimestamp(BdfWriter w, long timestamp)
|
private void sendTimestamp(BdfWriter w, long timestamp)
|
||||||
@@ -294,15 +305,15 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
return remote;
|
return remote;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ContactId addContact(Author remoteAuthor, SecretKey master,
|
private ContactId addContact(Author remoteAuthor, long timestamp,
|
||||||
long timestamp, boolean alice,
|
|
||||||
Map<TransportId, TransportProperties> remoteProperties)
|
Map<TransportId, TransportProperties> remoteProperties)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
ContactId contactId;
|
ContactId contactId;
|
||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
contactId = contactManager.addContact(txn, remoteAuthor,
|
contactId = contactManager.addContact(txn, remoteAuthor,
|
||||||
localAuthor.getId(), master, timestamp, alice, true, true);
|
localAuthor.getId(), masterSecret, timestamp, alice,
|
||||||
|
true, true);
|
||||||
transportPropertyManager.addRemoteProperties(txn, contactId,
|
transportPropertyManager.addRemoteProperties(txn, contactId,
|
||||||
remoteProperties);
|
remoteProperties);
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
@@ -312,8 +323,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
return contactId;
|
return contactId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tryToClose(DuplexTransportConnection conn,
|
private void tryToClose(DuplexTransportConnection conn, boolean exception) {
|
||||||
boolean exception) {
|
|
||||||
try {
|
try {
|
||||||
LOG.info("Closing connection");
|
LOG.info("Closing connection");
|
||||||
conn.getReader().dispose(exception, true);
|
conn.getReader().dispose(exception, true);
|
||||||
|
|||||||
@@ -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<>(PBKDF_SAMPLES);
|
|
||||||
List<Long> slowSamples = new ArrayList<>(PBKDF_SAMPLES);
|
|
||||||
long iterationNanos = 0, initNanos = 0;
|
|
||||||
while (iterationNanos <= 0 || initNanos <= 0) {
|
|
||||||
// Sample the running time with one iteration and two iterations
|
|
||||||
for (int i = 0; i < PBKDF_SAMPLES; i++) {
|
|
||||||
quickSamples.add(sampleRunningTime(1));
|
|
||||||
slowSamples.add(sampleRunningTime(2));
|
|
||||||
}
|
|
||||||
// Calculate the iteration time and the initialisation time
|
|
||||||
long quickMedian = median(quickSamples);
|
|
||||||
long slowMedian = median(slowSamples);
|
|
||||||
iterationNanos = slowMedian - quickMedian;
|
|
||||||
initNanos = quickMedian - iterationNanos;
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info("Init: " + initNanos + ", iteration: "
|
|
||||||
+ iterationNanos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
long targetNanos = targetMillis * 1000L * 1000L;
|
|
||||||
long iterations = (targetNanos - initNanos) / iterationNanos;
|
|
||||||
if (LOG.isLoggable(INFO)) LOG.info("Target iterations: " + iterations);
|
|
||||||
if (iterations < 1) return 1;
|
|
||||||
if (iterations > Integer.MAX_VALUE) return Integer.MAX_VALUE;
|
|
||||||
return (int) iterations;
|
|
||||||
}
|
|
||||||
|
|
||||||
private long sampleRunningTime(int iterations) {
|
|
||||||
byte[] password = {'p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
|
|
||||||
byte[] salt = new byte[PBKDF_SALT_BYTES];
|
|
||||||
int keyLengthInBits = SecretKey.LENGTH * 8;
|
|
||||||
long start = System.nanoTime();
|
|
||||||
Digest digest = new SHA256Digest();
|
|
||||||
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(digest);
|
|
||||||
gen.init(password, salt, iterations);
|
|
||||||
gen.generateDerivedParameters(keyLengthInBits);
|
|
||||||
return System.nanoTime() - start;
|
|
||||||
}
|
|
||||||
|
|
||||||
private long median(List<Long> list) {
|
|
||||||
int size = list.size();
|
|
||||||
if (size == 0) throw new IllegalArgumentException();
|
|
||||||
Collections.sort(list);
|
|
||||||
if (size % 2 == 1) return list.get(size / 2);
|
|
||||||
return list.get(size / 2 - 1) + list.get(size / 2) / 2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ package org.briarproject.bramble.crypto;
|
|||||||
import org.briarproject.bramble.TimeLoggingExecutor;
|
import org.briarproject.bramble.TimeLoggingExecutor;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
||||||
|
import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
|
||||||
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
|
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
|
||||||
import org.briarproject.bramble.api.crypto.StreamDecrypterFactory;
|
import org.briarproject.bramble.api.crypto.StreamDecrypterFactory;
|
||||||
import org.briarproject.bramble.api.crypto.StreamEncrypterFactory;
|
import org.briarproject.bramble.api.crypto.StreamEncrypterFactory;
|
||||||
|
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
||||||
|
|
||||||
@@ -65,8 +67,9 @@ public class CryptoModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
CryptoComponent provideCryptoComponent(
|
CryptoComponent provideCryptoComponent(
|
||||||
SecureRandomProvider secureRandomProvider) {
|
SecureRandomProvider secureRandomProvider,
|
||||||
return new CryptoComponentImpl(secureRandomProvider);
|
ScryptKdf passwordBasedKdf) {
|
||||||
|
return new CryptoComponentImpl(secureRandomProvider, passwordBasedKdf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -74,6 +77,12 @@ public class CryptoModule {
|
|||||||
return new PasswordStrengthEstimatorImpl();
|
return new PasswordStrengthEstimatorImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
TransportCrypto provideTransportCrypto(
|
||||||
|
TransportCryptoImpl transportCrypto) {
|
||||||
|
return transportCrypto;
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
StreamDecrypterFactory provideStreamDecrypterFactory(
|
StreamDecrypterFactory provideStreamDecrypterFactory(
|
||||||
Provider<AuthenticatedCipher> cipherProvider) {
|
Provider<AuthenticatedCipher> cipherProvider) {
|
||||||
@@ -81,9 +90,17 @@ public class CryptoModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
StreamEncrypterFactory provideStreamEncrypterFactory(CryptoComponent crypto,
|
StreamEncrypterFactory provideStreamEncrypterFactory(
|
||||||
|
CryptoComponent crypto, TransportCrypto transportCrypto,
|
||||||
Provider<AuthenticatedCipher> cipherProvider) {
|
Provider<AuthenticatedCipher> cipherProvider) {
|
||||||
return new StreamEncrypterFactoryImpl(crypto, cipherProvider);
|
return new StreamEncrypterFactoryImpl(crypto, transportCrypto,
|
||||||
|
cipherProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
KeyAgreementCrypto provideKeyAgreementCrypto(
|
||||||
|
KeyAgreementCryptoImpl keyAgreementCrypto) {
|
||||||
|
return keyAgreementCrypto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,21 +24,23 @@ 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
|
||||||
@@ -93,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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,7 +34,6 @@ import java.sql.ResultSet;
|
|||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.sql.Statement;
|
import java.sql.Statement;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -73,31 +72,33 @@ import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
|
|||||||
abstract class JdbcDatabase implements Database<Connection> {
|
abstract class JdbcDatabase implements Database<Connection> {
|
||||||
|
|
||||||
// Package access for testing
|
// Package access for testing
|
||||||
static final int CODE_SCHEMA_VERSION = 32;
|
static final int CODE_SCHEMA_VERSION = 35;
|
||||||
|
|
||||||
private static final String CREATE_SETTINGS =
|
private static final String CREATE_SETTINGS =
|
||||||
"CREATE TABLE settings"
|
"CREATE TABLE settings"
|
||||||
+ " (namespace VARCHAR NOT NULL,"
|
+ " (namespace _STRING NOT NULL,"
|
||||||
+ " key VARCHAR NOT NULL,"
|
+ " settingKey _STRING NOT NULL,"
|
||||||
+ " value VARCHAR NOT NULL,"
|
+ " value _STRING NOT NULL,"
|
||||||
+ " PRIMARY KEY (namespace, key))";
|
+ " PRIMARY KEY (namespace, settingKey))";
|
||||||
|
|
||||||
private static final String CREATE_LOCAL_AUTHORS =
|
private static final String CREATE_LOCAL_AUTHORS =
|
||||||
"CREATE TABLE localAuthors"
|
"CREATE TABLE localAuthors"
|
||||||
+ " (authorId HASH NOT NULL,"
|
+ " (authorId _HASH NOT NULL,"
|
||||||
+ " name VARCHAR NOT NULL,"
|
+ " formatVersion INT NOT NULL,"
|
||||||
+ " publicKey BINARY NOT NULL,"
|
+ " name _STRING NOT NULL,"
|
||||||
+ " privateKey BINARY NOT NULL,"
|
+ " publicKey _BINARY NOT NULL,"
|
||||||
|
+ " privateKey _BINARY NOT NULL,"
|
||||||
+ " created BIGINT NOT NULL,"
|
+ " created BIGINT NOT NULL,"
|
||||||
+ " PRIMARY KEY (authorId))";
|
+ " PRIMARY KEY (authorId))";
|
||||||
|
|
||||||
private static final String CREATE_CONTACTS =
|
private static final String CREATE_CONTACTS =
|
||||||
"CREATE TABLE contacts"
|
"CREATE TABLE contacts"
|
||||||
+ " (contactId COUNTER,"
|
+ " (contactId _COUNTER,"
|
||||||
+ " authorId HASH NOT NULL,"
|
+ " authorId _HASH NOT NULL,"
|
||||||
+ " name VARCHAR NOT NULL,"
|
+ " formatVersion INT NOT NULL,"
|
||||||
+ " publicKey BINARY NOT NULL,"
|
+ " name _STRING NOT NULL,"
|
||||||
+ " localAuthorId HASH NOT NULL,"
|
+ " publicKey _BINARY NOT NULL,"
|
||||||
|
+ " localAuthorId _HASH NOT NULL,"
|
||||||
+ " verified BOOLEAN NOT NULL,"
|
+ " verified BOOLEAN NOT NULL,"
|
||||||
+ " active BOOLEAN NOT NULL,"
|
+ " active BOOLEAN NOT NULL,"
|
||||||
+ " PRIMARY KEY (contactId),"
|
+ " PRIMARY KEY (contactId),"
|
||||||
@@ -107,17 +108,17 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
|
|
||||||
private static final String CREATE_GROUPS =
|
private static final String CREATE_GROUPS =
|
||||||
"CREATE TABLE groups"
|
"CREATE TABLE groups"
|
||||||
+ " (groupId HASH NOT NULL,"
|
+ " (groupId _HASH NOT NULL,"
|
||||||
+ " clientId VARCHAR NOT NULL,"
|
+ " clientId _STRING NOT NULL,"
|
||||||
+ " descriptor BINARY NOT NULL,"
|
+ " descriptor _BINARY NOT NULL,"
|
||||||
+ " PRIMARY KEY (groupId))";
|
+ " PRIMARY KEY (groupId))";
|
||||||
|
|
||||||
private static final String CREATE_GROUP_METADATA =
|
private static final String CREATE_GROUP_METADATA =
|
||||||
"CREATE TABLE groupMetadata"
|
"CREATE TABLE groupMetadata"
|
||||||
+ " (groupId HASH NOT NULL,"
|
+ " (groupId _HASH NOT NULL,"
|
||||||
+ " key VARCHAR NOT NULL,"
|
+ " metaKey _STRING NOT NULL,"
|
||||||
+ " value BINARY NOT NULL,"
|
+ " value _BINARY NOT NULL,"
|
||||||
+ " PRIMARY KEY (groupId, key),"
|
+ " PRIMARY KEY (groupId, metaKey),"
|
||||||
+ " FOREIGN KEY (groupId)"
|
+ " FOREIGN KEY (groupId)"
|
||||||
+ " REFERENCES groups (groupId)"
|
+ " REFERENCES groups (groupId)"
|
||||||
+ " ON DELETE CASCADE)";
|
+ " ON DELETE CASCADE)";
|
||||||
@@ -125,7 +126,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
private static final String CREATE_GROUP_VISIBILITIES =
|
private static final String CREATE_GROUP_VISIBILITIES =
|
||||||
"CREATE TABLE groupVisibilities"
|
"CREATE TABLE groupVisibilities"
|
||||||
+ " (contactId INT NOT NULL,"
|
+ " (contactId INT NOT NULL,"
|
||||||
+ " groupId HASH NOT NULL,"
|
+ " groupId _HASH NOT NULL,"
|
||||||
+ " shared BOOLEAN NOT NULL,"
|
+ " shared BOOLEAN NOT NULL,"
|
||||||
+ " PRIMARY KEY (contactId, groupId),"
|
+ " PRIMARY KEY (contactId, groupId),"
|
||||||
+ " FOREIGN KEY (contactId)"
|
+ " FOREIGN KEY (contactId)"
|
||||||
@@ -137,8 +138,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
|
|
||||||
private static final String CREATE_MESSAGES =
|
private static final String CREATE_MESSAGES =
|
||||||
"CREATE TABLE messages"
|
"CREATE TABLE messages"
|
||||||
+ " (messageId HASH NOT NULL,"
|
+ " (messageId _HASH NOT NULL,"
|
||||||
+ " groupId HASH NOT NULL,"
|
+ " groupId _HASH NOT NULL,"
|
||||||
+ " timestamp BIGINT NOT NULL,"
|
+ " timestamp BIGINT NOT NULL,"
|
||||||
+ " state INT NOT NULL,"
|
+ " state INT NOT NULL,"
|
||||||
+ " shared BOOLEAN NOT NULL,"
|
+ " shared BOOLEAN NOT NULL,"
|
||||||
@@ -151,12 +152,12 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
|
|
||||||
private static final String CREATE_MESSAGE_METADATA =
|
private static final String CREATE_MESSAGE_METADATA =
|
||||||
"CREATE TABLE messageMetadata"
|
"CREATE TABLE messageMetadata"
|
||||||
+ " (messageId HASH NOT NULL,"
|
+ " (messageId _HASH NOT NULL,"
|
||||||
+ " groupId HASH NOT NULL," // Denormalised
|
+ " groupId _HASH NOT NULL," // Denormalised
|
||||||
+ " state INT NOT NULL," // Denormalised
|
+ " state INT NOT NULL," // Denormalised
|
||||||
+ " key VARCHAR NOT NULL,"
|
+ " metaKey _STRING NOT NULL,"
|
||||||
+ " value BINARY NOT NULL,"
|
+ " value _BINARY NOT NULL,"
|
||||||
+ " PRIMARY KEY (messageId, key),"
|
+ " PRIMARY KEY (messageId, metaKey),"
|
||||||
+ " FOREIGN KEY (messageId)"
|
+ " FOREIGN KEY (messageId)"
|
||||||
+ " REFERENCES messages (messageId)"
|
+ " REFERENCES messages (messageId)"
|
||||||
+ " ON DELETE CASCADE,"
|
+ " ON DELETE CASCADE,"
|
||||||
@@ -166,9 +167,9 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
|
|
||||||
private static final String CREATE_MESSAGE_DEPENDENCIES =
|
private static final String CREATE_MESSAGE_DEPENDENCIES =
|
||||||
"CREATE TABLE messageDependencies"
|
"CREATE TABLE messageDependencies"
|
||||||
+ " (groupId HASH NOT NULL,"
|
+ " (groupId _HASH NOT NULL,"
|
||||||
+ " messageId HASH NOT NULL,"
|
+ " messageId _HASH NOT NULL,"
|
||||||
+ " dependencyId HASH NOT NULL," // Not a foreign key
|
+ " dependencyId _HASH NOT NULL," // Not a foreign key
|
||||||
+ " FOREIGN KEY (groupId)"
|
+ " FOREIGN KEY (groupId)"
|
||||||
+ " REFERENCES groups (groupId)"
|
+ " REFERENCES groups (groupId)"
|
||||||
+ " ON DELETE CASCADE,"
|
+ " ON DELETE CASCADE,"
|
||||||
@@ -178,7 +179,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
|
|
||||||
private static final String CREATE_OFFERS =
|
private static final String CREATE_OFFERS =
|
||||||
"CREATE TABLE offers"
|
"CREATE TABLE offers"
|
||||||
+ " (messageId HASH NOT NULL," // Not a foreign key
|
+ " (messageId _HASH NOT NULL," // Not a foreign key
|
||||||
+ " contactId INT NOT NULL,"
|
+ " contactId INT NOT NULL,"
|
||||||
+ " PRIMARY KEY (messageId, contactId),"
|
+ " PRIMARY KEY (messageId, contactId),"
|
||||||
+ " FOREIGN KEY (contactId)"
|
+ " FOREIGN KEY (contactId)"
|
||||||
@@ -187,9 +188,9 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
|
|
||||||
private static final String CREATE_STATUSES =
|
private static final String CREATE_STATUSES =
|
||||||
"CREATE TABLE statuses"
|
"CREATE TABLE statuses"
|
||||||
+ " (messageId HASH NOT NULL,"
|
+ " (messageId _HASH NOT NULL,"
|
||||||
+ " contactId INT NOT NULL,"
|
+ " contactId INT NOT NULL,"
|
||||||
+ " groupId HASH NOT NULL," // Denormalised
|
+ " groupId _HASH NOT NULL," // Denormalised
|
||||||
+ " timestamp BIGINT NOT NULL," // Denormalised
|
+ " timestamp BIGINT NOT NULL," // Denormalised
|
||||||
+ " length INT NOT NULL," // Denormalised
|
+ " length INT NOT NULL," // Denormalised
|
||||||
+ " state INT NOT NULL," // Denormalised
|
+ " state INT NOT NULL," // Denormalised
|
||||||
@@ -214,20 +215,20 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
|
|
||||||
private static final String CREATE_TRANSPORTS =
|
private static final String CREATE_TRANSPORTS =
|
||||||
"CREATE TABLE transports"
|
"CREATE TABLE transports"
|
||||||
+ " (transportId VARCHAR NOT NULL,"
|
+ " (transportId _STRING NOT NULL,"
|
||||||
+ " maxLatency INT NOT NULL,"
|
+ " maxLatency INT NOT NULL,"
|
||||||
+ " PRIMARY KEY (transportId))";
|
+ " PRIMARY KEY (transportId))";
|
||||||
|
|
||||||
private static final String CREATE_INCOMING_KEYS =
|
private static final String CREATE_INCOMING_KEYS =
|
||||||
"CREATE TABLE incomingKeys"
|
"CREATE TABLE incomingKeys"
|
||||||
+ " (contactId INT NOT NULL,"
|
+ " (contactId INT NOT NULL,"
|
||||||
+ " transportId VARCHAR NOT NULL,"
|
+ " transportId _STRING NOT NULL,"
|
||||||
+ " period BIGINT NOT NULL,"
|
+ " rotationPeriod BIGINT NOT NULL,"
|
||||||
+ " tagKey SECRET NOT NULL,"
|
+ " tagKey _SECRET NOT NULL,"
|
||||||
+ " headerKey SECRET NOT NULL,"
|
+ " headerKey _SECRET NOT NULL,"
|
||||||
+ " base BIGINT NOT NULL,"
|
+ " base BIGINT NOT NULL,"
|
||||||
+ " bitmap BINARY NOT NULL,"
|
+ " bitmap _BINARY NOT NULL,"
|
||||||
+ " PRIMARY KEY (contactId, transportId, period),"
|
+ " PRIMARY KEY (contactId, transportId, rotationPeriod),"
|
||||||
+ " FOREIGN KEY (contactId)"
|
+ " FOREIGN KEY (contactId)"
|
||||||
+ " REFERENCES contacts (contactId)"
|
+ " REFERENCES contacts (contactId)"
|
||||||
+ " ON DELETE CASCADE,"
|
+ " ON DELETE CASCADE,"
|
||||||
@@ -238,10 +239,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
private static final String CREATE_OUTGOING_KEYS =
|
private static final String CREATE_OUTGOING_KEYS =
|
||||||
"CREATE TABLE outgoingKeys"
|
"CREATE TABLE outgoingKeys"
|
||||||
+ " (contactId INT NOT NULL,"
|
+ " (contactId INT NOT NULL,"
|
||||||
+ " transportId VARCHAR NOT NULL,"
|
+ " transportId _STRING NOT NULL,"
|
||||||
+ " period BIGINT NOT NULL,"
|
+ " rotationPeriod BIGINT NOT NULL,"
|
||||||
+ " tagKey SECRET NOT NULL,"
|
+ " tagKey _SECRET NOT NULL,"
|
||||||
+ " headerKey SECRET NOT NULL,"
|
+ " headerKey _SECRET NOT NULL,"
|
||||||
+ " stream BIGINT NOT NULL,"
|
+ " stream BIGINT NOT NULL,"
|
||||||
+ " PRIMARY KEY (contactId, transportId),"
|
+ " PRIMARY KEY (contactId, transportId),"
|
||||||
+ " FOREIGN KEY (contactId)"
|
+ " FOREIGN KEY (contactId)"
|
||||||
@@ -275,7 +276,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
Logger.getLogger(JdbcDatabase.class.getName());
|
Logger.getLogger(JdbcDatabase.class.getName());
|
||||||
|
|
||||||
// Different database libraries use different names for certain types
|
// Different database libraries use different names for certain types
|
||||||
private final String hashType, binaryType, counterType, secretType;
|
private final String hashType, secretType, binaryType;
|
||||||
|
private final String counterType, stringType;
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
|
|
||||||
// Locking: connectionsLock
|
// Locking: connectionsLock
|
||||||
@@ -290,12 +292,13 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
private final Lock connectionsLock = new ReentrantLock();
|
private final Lock connectionsLock = new ReentrantLock();
|
||||||
private final Condition connectionsChanged = connectionsLock.newCondition();
|
private final Condition connectionsChanged = connectionsLock.newCondition();
|
||||||
|
|
||||||
JdbcDatabase(String hashType, String binaryType, String counterType,
|
JdbcDatabase(String hashType, String secretType, String binaryType,
|
||||||
String secretType, Clock clock) {
|
String counterType, String stringType, Clock clock) {
|
||||||
this.hashType = hashType;
|
this.hashType = hashType;
|
||||||
|
this.secretType = secretType;
|
||||||
this.binaryType = binaryType;
|
this.binaryType = binaryType;
|
||||||
this.counterType = counterType;
|
this.counterType = counterType;
|
||||||
this.secretType = secretType;
|
this.stringType = stringType;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,7 +365,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
|
|
||||||
// Package access for testing
|
// Package access for testing
|
||||||
List<Migration<Connection>> getMigrations() {
|
List<Migration<Connection>> getMigrations() {
|
||||||
return Arrays.asList(new Migration30_31(), new Migration31_32());
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void storeSchemaVersion(Connection txn, int version)
|
private void storeSchemaVersion(Connection txn, int version)
|
||||||
@@ -430,16 +433,17 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String insertTypeNames(String s) {
|
private String insertTypeNames(String s) {
|
||||||
s = s.replaceAll("HASH", hashType);
|
s = s.replaceAll("_HASH", hashType);
|
||||||
s = s.replaceAll("BINARY", binaryType);
|
s = s.replaceAll("_SECRET", secretType);
|
||||||
s = s.replaceAll("COUNTER", counterType);
|
s = s.replaceAll("_BINARY", binaryType);
|
||||||
s = s.replaceAll("SECRET", secretType);
|
s = s.replaceAll("_COUNTER", counterType);
|
||||||
|
s = s.replaceAll("_STRING", stringType);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Connection startTransaction() throws DbException {
|
public Connection startTransaction() throws DbException {
|
||||||
Connection txn = null;
|
Connection txn;
|
||||||
connectionsLock.lock();
|
connectionsLock.lock();
|
||||||
try {
|
try {
|
||||||
if (closed) throw new DbClosedException();
|
if (closed) throw new DbClosedException();
|
||||||
@@ -547,16 +551,18 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
try {
|
try {
|
||||||
// Create a contact row
|
// Create a contact row
|
||||||
String sql = "INSERT INTO contacts"
|
String sql = "INSERT INTO contacts"
|
||||||
+ " (authorId, name, publicKey, localAuthorId,"
|
+ " (authorId, formatVersion, name, publicKey,"
|
||||||
|
+ " localAuthorId,"
|
||||||
+ " verified, active)"
|
+ " verified, active)"
|
||||||
+ " VALUES (?, ?, ?, ?, ?, ?)";
|
+ " VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setBytes(1, remote.getId().getBytes());
|
ps.setBytes(1, remote.getId().getBytes());
|
||||||
ps.setString(2, remote.getName());
|
ps.setInt(2, remote.getFormatVersion());
|
||||||
ps.setBytes(3, remote.getPublicKey());
|
ps.setString(3, remote.getName());
|
||||||
ps.setBytes(4, local.getBytes());
|
ps.setBytes(4, remote.getPublicKey());
|
||||||
ps.setBoolean(5, verified);
|
ps.setBytes(5, local.getBytes());
|
||||||
ps.setBoolean(6, active);
|
ps.setBoolean(6, verified);
|
||||||
|
ps.setBoolean(7, active);
|
||||||
int affected = ps.executeUpdate();
|
int affected = ps.executeUpdate();
|
||||||
if (affected != 1) throw new DbStateException();
|
if (affected != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
@@ -658,14 +664,16 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
try {
|
try {
|
||||||
String sql = "INSERT INTO localAuthors"
|
String sql = "INSERT INTO localAuthors"
|
||||||
+ " (authorId, name, publicKey, privateKey, created)"
|
+ " (authorId, formatVersion, name, publicKey,"
|
||||||
+ " VALUES (?, ?, ?, ?, ?)";
|
+ " privateKey, created)"
|
||||||
|
+ " VALUES (?, ?, ?, ?, ?, ?)";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setBytes(1, a.getId().getBytes());
|
ps.setBytes(1, a.getId().getBytes());
|
||||||
ps.setString(2, a.getName());
|
ps.setInt(2, a.getFormatVersion());
|
||||||
ps.setBytes(3, a.getPublicKey());
|
ps.setString(3, a.getName());
|
||||||
ps.setBytes(4, a.getPrivateKey());
|
ps.setBytes(4, a.getPublicKey());
|
||||||
ps.setLong(5, a.getTimeCreated());
|
ps.setBytes(5, a.getPrivateKey());
|
||||||
|
ps.setLong(6, a.getTimeCreated());
|
||||||
int affected = ps.executeUpdate();
|
int affected = ps.executeUpdate();
|
||||||
if (affected != 1) throw new DbStateException();
|
if (affected != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
@@ -822,7 +830,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
try {
|
try {
|
||||||
// Store the incoming keys
|
// Store the incoming keys
|
||||||
String sql = "INSERT INTO incomingKeys (contactId, transportId,"
|
String sql = "INSERT INTO incomingKeys (contactId, transportId,"
|
||||||
+ " period, tagKey, headerKey, base, bitmap)"
|
+ " rotationPeriod, tagKey, headerKey, base, bitmap)"
|
||||||
+ " VALUES (?, ?, ?, ?, ?, ?, ?)";
|
+ " VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, c.getInt());
|
ps.setInt(1, c.getInt());
|
||||||
@@ -857,8 +865,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (rows != 1) throw new DbStateException();
|
if (rows != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
// Store the outgoing keys
|
// Store the outgoing keys
|
||||||
sql = "INSERT INTO outgoingKeys (contactId, transportId, period,"
|
sql = "INSERT INTO outgoingKeys (contactId, transportId,"
|
||||||
+ " tagKey, headerKey, stream)"
|
+ " rotationPeriod, tagKey, headerKey, stream)"
|
||||||
+ " VALUES (?, ?, ?, ?, ?, ?)";
|
+ " VALUES (?, ?, ?, ?, ?, ?)";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, c.getInt());
|
ps.setInt(1, c.getInt());
|
||||||
@@ -1106,7 +1114,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT authorId, name, publicKey,"
|
String sql = "SELECT authorId, formatVersion, name, publicKey,"
|
||||||
+ " localAuthorId, verified, active"
|
+ " localAuthorId, verified, active"
|
||||||
+ " FROM contacts"
|
+ " FROM contacts"
|
||||||
+ " WHERE contactId = ?";
|
+ " WHERE contactId = ?";
|
||||||
@@ -1115,14 +1123,16 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
if (!rs.next()) throw new DbStateException();
|
if (!rs.next()) throw new DbStateException();
|
||||||
AuthorId authorId = new AuthorId(rs.getBytes(1));
|
AuthorId authorId = new AuthorId(rs.getBytes(1));
|
||||||
String name = rs.getString(2);
|
int formatVersion = rs.getInt(2);
|
||||||
byte[] publicKey = rs.getBytes(3);
|
String name = rs.getString(3);
|
||||||
AuthorId localAuthorId = new AuthorId(rs.getBytes(4));
|
byte[] publicKey = rs.getBytes(4);
|
||||||
boolean verified = rs.getBoolean(5);
|
AuthorId localAuthorId = new AuthorId(rs.getBytes(5));
|
||||||
boolean active = rs.getBoolean(6);
|
boolean verified = rs.getBoolean(6);
|
||||||
|
boolean active = rs.getBoolean(7);
|
||||||
rs.close();
|
rs.close();
|
||||||
ps.close();
|
ps.close();
|
||||||
Author author = new Author(authorId, name, publicKey);
|
Author author =
|
||||||
|
new Author(authorId, formatVersion, name, publicKey);
|
||||||
return new Contact(c, author, localAuthorId, verified, active);
|
return new Contact(c, author, localAuthorId, verified, active);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
tryToClose(rs);
|
tryToClose(rs);
|
||||||
@@ -1137,8 +1147,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT contactId, authorId, name, publicKey,"
|
String sql = "SELECT contactId, authorId, formatVersion, name,"
|
||||||
+ " localAuthorId, verified, active"
|
+ " publicKey, localAuthorId, verified, active"
|
||||||
+ " FROM contacts";
|
+ " FROM contacts";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
@@ -1146,12 +1156,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
ContactId contactId = new ContactId(rs.getInt(1));
|
ContactId contactId = new ContactId(rs.getInt(1));
|
||||||
AuthorId authorId = new AuthorId(rs.getBytes(2));
|
AuthorId authorId = new AuthorId(rs.getBytes(2));
|
||||||
String name = rs.getString(3);
|
int formatVersion = rs.getInt(3);
|
||||||
byte[] publicKey = rs.getBytes(4);
|
String name = rs.getString(4);
|
||||||
Author author = new Author(authorId, name, publicKey);
|
byte[] publicKey = rs.getBytes(5);
|
||||||
AuthorId localAuthorId = new AuthorId(rs.getBytes(5));
|
Author author =
|
||||||
boolean verified = rs.getBoolean(6);
|
new Author(authorId, formatVersion, name, publicKey);
|
||||||
boolean active = rs.getBoolean(7);
|
AuthorId localAuthorId = new AuthorId(rs.getBytes(6));
|
||||||
|
boolean verified = rs.getBoolean(7);
|
||||||
|
boolean active = rs.getBoolean(8);
|
||||||
contacts.add(new Contact(contactId, author, localAuthorId,
|
contacts.add(new Contact(contactId, author, localAuthorId,
|
||||||
verified, active));
|
verified, active));
|
||||||
}
|
}
|
||||||
@@ -1194,7 +1206,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT contactId, name, publicKey,"
|
String sql = "SELECT contactId, formatVersion, name, publicKey,"
|
||||||
+ " localAuthorId, verified, active"
|
+ " localAuthorId, verified, active"
|
||||||
+ " FROM contacts"
|
+ " FROM contacts"
|
||||||
+ " WHERE authorId = ?";
|
+ " WHERE authorId = ?";
|
||||||
@@ -1204,12 +1216,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
List<Contact> contacts = new ArrayList<>();
|
List<Contact> contacts = new ArrayList<>();
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
ContactId c = new ContactId(rs.getInt(1));
|
ContactId c = new ContactId(rs.getInt(1));
|
||||||
String name = rs.getString(2);
|
int formatVersion = rs.getInt(2);
|
||||||
byte[] publicKey = rs.getBytes(3);
|
String name = rs.getString(3);
|
||||||
AuthorId localAuthorId = new AuthorId(rs.getBytes(4));
|
byte[] publicKey = rs.getBytes(4);
|
||||||
boolean verified = rs.getBoolean(5);
|
AuthorId localAuthorId = new AuthorId(rs.getBytes(5));
|
||||||
boolean active = rs.getBoolean(6);
|
boolean verified = rs.getBoolean(6);
|
||||||
Author author = new Author(remote, name, publicKey);
|
boolean active = rs.getBoolean(7);
|
||||||
|
Author author =
|
||||||
|
new Author(remote, formatVersion, name, publicKey);
|
||||||
contacts.add(new Contact(c, author, localAuthorId, verified,
|
contacts.add(new Contact(c, author, localAuthorId, verified,
|
||||||
active));
|
active));
|
||||||
}
|
}
|
||||||
@@ -1329,19 +1343,21 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT name, publicKey, privateKey, created"
|
String sql = "SELECT formatVersion, name, publicKey,"
|
||||||
|
+ " privateKey, created"
|
||||||
+ " FROM localAuthors"
|
+ " FROM localAuthors"
|
||||||
+ " WHERE authorId = ?";
|
+ " WHERE authorId = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setBytes(1, a.getBytes());
|
ps.setBytes(1, a.getBytes());
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
if (!rs.next()) throw new DbStateException();
|
if (!rs.next()) throw new DbStateException();
|
||||||
String name = rs.getString(1);
|
int formatVersion = rs.getInt(1);
|
||||||
byte[] publicKey = rs.getBytes(2);
|
String name = rs.getString(2);
|
||||||
byte[] privateKey = rs.getBytes(3);
|
byte[] publicKey = rs.getBytes(3);
|
||||||
long created = rs.getLong(4);
|
byte[] privateKey = rs.getBytes(4);
|
||||||
LocalAuthor localAuthor = new LocalAuthor(a, name, publicKey,
|
long created = rs.getLong(5);
|
||||||
privateKey, created);
|
LocalAuthor localAuthor = new LocalAuthor(a, formatVersion, name,
|
||||||
|
publicKey, privateKey, created);
|
||||||
if (rs.next()) throw new DbStateException();
|
if (rs.next()) throw new DbStateException();
|
||||||
rs.close();
|
rs.close();
|
||||||
ps.close();
|
ps.close();
|
||||||
@@ -1359,19 +1375,21 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT authorId, name, publicKey, privateKey, created"
|
String sql = "SELECT authorId, formatVersion, name, publicKey,"
|
||||||
|
+ " privateKey, created"
|
||||||
+ " FROM localAuthors";
|
+ " FROM localAuthors";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
List<LocalAuthor> authors = new ArrayList<>();
|
List<LocalAuthor> authors = new ArrayList<>();
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
AuthorId authorId = new AuthorId(rs.getBytes(1));
|
AuthorId authorId = new AuthorId(rs.getBytes(1));
|
||||||
String name = rs.getString(2);
|
int formatVersion = rs.getInt(2);
|
||||||
byte[] publicKey = rs.getBytes(3);
|
String name = rs.getString(3);
|
||||||
byte[] privateKey = rs.getBytes(4);
|
byte[] publicKey = rs.getBytes(4);
|
||||||
long created = rs.getLong(5);
|
byte[] privateKey = rs.getBytes(5);
|
||||||
authors.add(new LocalAuthor(authorId, name, publicKey,
|
long created = rs.getLong(6);
|
||||||
privateKey, created));
|
authors.add(new LocalAuthor(authorId, formatVersion, name,
|
||||||
|
publicKey, privateKey, created));
|
||||||
}
|
}
|
||||||
rs.close();
|
rs.close();
|
||||||
ps.close();
|
ps.close();
|
||||||
@@ -1440,7 +1458,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
Set<MessageId> intersection = null;
|
Set<MessageId> intersection = null;
|
||||||
String sql = "SELECT messageId FROM messageMetadata"
|
String sql = "SELECT messageId FROM messageMetadata"
|
||||||
+ " WHERE groupId = ? AND state = ?"
|
+ " WHERE groupId = ? AND state = ?"
|
||||||
+ " AND key = ? AND value = ?";
|
+ " AND metaKey = ? AND value = ?";
|
||||||
for (Entry<String, byte[]> e : query.entrySet()) {
|
for (Entry<String, byte[]> e : query.entrySet()) {
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setBytes(1, g.getBytes());
|
ps.setBytes(1, g.getBytes());
|
||||||
@@ -1472,7 +1490,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT messageId, key, value"
|
String sql = "SELECT messageId, metaKey, value"
|
||||||
+ " FROM messageMetadata"
|
+ " FROM messageMetadata"
|
||||||
+ " WHERE groupId = ? AND state = ?";
|
+ " WHERE groupId = ? AND state = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
@@ -1517,7 +1535,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT key, value FROM groupMetadata"
|
String sql = "SELECT metaKey, value FROM groupMetadata"
|
||||||
+ " WHERE groupId = ?";
|
+ " WHERE groupId = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setBytes(1, g.getBytes());
|
ps.setBytes(1, g.getBytes());
|
||||||
@@ -1540,7 +1558,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT key, value FROM messageMetadata"
|
String sql = "SELECT metaKey, value FROM messageMetadata"
|
||||||
+ " WHERE state = ? AND messageId = ?";
|
+ " WHERE state = ? AND messageId = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, DELIVERED.getValue());
|
ps.setInt(1, DELIVERED.getValue());
|
||||||
@@ -1564,7 +1582,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT key, value FROM messageMetadata"
|
String sql = "SELECT metaKey, value FROM messageMetadata"
|
||||||
+ " WHERE (state = ? OR state = ?)"
|
+ " WHERE (state = ? OR state = ?)"
|
||||||
+ " AND messageId = ?";
|
+ " AND messageId = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
@@ -2008,7 +2026,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT key, value FROM settings WHERE namespace = ?";
|
String sql = "SELECT settingKey, value FROM settings"
|
||||||
|
+ " WHERE namespace = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setString(1, namespace);
|
ps.setString(1, namespace);
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
@@ -2031,10 +2050,11 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
// Retrieve the incoming keys
|
// Retrieve the incoming keys
|
||||||
String sql = "SELECT period, tagKey, headerKey, base, bitmap"
|
String sql = "SELECT rotationPeriod, tagKey, headerKey,"
|
||||||
|
+ " base, bitmap"
|
||||||
+ " FROM incomingKeys"
|
+ " FROM incomingKeys"
|
||||||
+ " WHERE transportId = ?"
|
+ " WHERE transportId = ?"
|
||||||
+ " ORDER BY contactId, period";
|
+ " ORDER BY contactId, rotationPeriod";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setString(1, t.getString());
|
ps.setString(1, t.getString());
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
@@ -2051,10 +2071,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
rs.close();
|
rs.close();
|
||||||
ps.close();
|
ps.close();
|
||||||
// Retrieve the outgoing keys in the same order
|
// Retrieve the outgoing keys in the same order
|
||||||
sql = "SELECT contactId, period, tagKey, headerKey, stream"
|
sql = "SELECT contactId, rotationPeriod, tagKey, headerKey, stream"
|
||||||
+ " FROM outgoingKeys"
|
+ " FROM outgoingKeys"
|
||||||
+ " WHERE transportId = ?"
|
+ " WHERE transportId = ?"
|
||||||
+ " ORDER BY contactId, period";
|
+ " ORDER BY contactId, rotationPeriod";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setString(1, t.getString());
|
ps.setString(1, t.getString());
|
||||||
rs = ps.executeQuery();
|
rs = ps.executeQuery();
|
||||||
@@ -2091,7 +2111,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
try {
|
try {
|
||||||
String sql = "UPDATE outgoingKeys SET stream = stream + 1"
|
String sql = "UPDATE outgoingKeys SET stream = stream + 1"
|
||||||
+ " WHERE contactId = ? AND transportId = ? AND period = ?";
|
+ " WHERE contactId = ? AND transportId = ?"
|
||||||
|
+ " AND rotationPeriod = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, c.getInt());
|
ps.setInt(1, c.getInt());
|
||||||
ps.setString(2, t.getString());
|
ps.setString(2, t.getString());
|
||||||
@@ -2168,7 +2189,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
g.getBytes(), meta, "groupMetadata", "groupId");
|
g.getBytes(), meta, "groupMetadata", "groupId");
|
||||||
if (added.isEmpty()) return;
|
if (added.isEmpty()) return;
|
||||||
// Insert any keys that don't already exist
|
// Insert any keys that don't already exist
|
||||||
String sql = "INSERT INTO groupMetadata (groupId, key, value)"
|
String sql = "INSERT INTO groupMetadata (groupId, metaKey, value)"
|
||||||
+ " VALUES (?, ?, ?)";
|
+ " VALUES (?, ?, ?)";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setBytes(1, g.getBytes());
|
ps.setBytes(1, g.getBytes());
|
||||||
@@ -2211,7 +2232,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
// Insert any keys that don't already exist
|
// Insert any keys that don't already exist
|
||||||
sql = "INSERT INTO messageMetadata"
|
sql = "INSERT INTO messageMetadata"
|
||||||
+ " (messageId, groupId, state, key, value)"
|
+ " (messageId, groupId, state, metaKey, value)"
|
||||||
+ " VALUES (?, ?, ?, ?, ?)";
|
+ " VALUES (?, ?, ?, ?, ?)";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setBytes(1, m.getBytes());
|
ps.setBytes(1, m.getBytes());
|
||||||
@@ -2252,7 +2273,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
// Delete any keys that are being removed
|
// Delete any keys that are being removed
|
||||||
if (!removed.isEmpty()) {
|
if (!removed.isEmpty()) {
|
||||||
String sql = "DELETE FROM " + tableName
|
String sql = "DELETE FROM " + tableName
|
||||||
+ " WHERE " + columnName + " = ? AND key = ?";
|
+ " WHERE " + columnName + " = ? AND metaKey = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setBytes(1, id);
|
ps.setBytes(1, id);
|
||||||
for (String key : removed) {
|
for (String key : removed) {
|
||||||
@@ -2271,7 +2292,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (notRemoved.isEmpty()) return Collections.emptyMap();
|
if (notRemoved.isEmpty()) return Collections.emptyMap();
|
||||||
// Update any keys that already exist
|
// Update any keys that already exist
|
||||||
String sql = "UPDATE " + tableName + " SET value = ?"
|
String sql = "UPDATE " + tableName + " SET value = ?"
|
||||||
+ " WHERE " + columnName + " = ? AND key = ?";
|
+ " WHERE " + columnName + " = ? AND metaKey = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setBytes(2, id);
|
ps.setBytes(2, id);
|
||||||
for (Entry<String, byte[]> e : notRemoved.entrySet()) {
|
for (Entry<String, byte[]> e : notRemoved.entrySet()) {
|
||||||
@@ -2308,7 +2329,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
try {
|
try {
|
||||||
// Update any settings that already exist
|
// Update any settings that already exist
|
||||||
String sql = "UPDATE settings SET value = ?"
|
String sql = "UPDATE settings SET value = ?"
|
||||||
+ " WHERE namespace = ? AND key = ?";
|
+ " WHERE namespace = ? AND settingKey = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
for (Entry<String, String> e : s.entrySet()) {
|
for (Entry<String, String> e : s.entrySet()) {
|
||||||
ps.setString(1, e.getValue());
|
ps.setString(1, e.getValue());
|
||||||
@@ -2323,7 +2344,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
if (rows > 1) throw new DbStateException();
|
if (rows > 1) throw new DbStateException();
|
||||||
}
|
}
|
||||||
// Insert any settings that don't already exist
|
// Insert any settings that don't already exist
|
||||||
sql = "INSERT INTO settings (namespace, key, value)"
|
sql = "INSERT INTO settings (namespace, settingKey, value)"
|
||||||
+ " VALUES (?, ?, ?)";
|
+ " VALUES (?, ?, ?)";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
int updateIndex = 0, inserted = 0;
|
int updateIndex = 0, inserted = 0;
|
||||||
@@ -2722,7 +2743,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
try {
|
try {
|
||||||
String sql = "UPDATE incomingKeys SET base = ?, bitmap = ?"
|
String sql = "UPDATE incomingKeys SET base = ?, bitmap = ?"
|
||||||
+ " WHERE contactId = ? AND transportId = ? AND period = ?";
|
+ " WHERE contactId = ? AND transportId = ?"
|
||||||
|
+ " AND rotationPeriod = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setLong(1, base);
|
ps.setLong(1, base);
|
||||||
ps.setBytes(2, bitmap);
|
ps.setBytes(2, bitmap);
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
package org.briarproject.bramble.db;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.sql.Statement;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
|
|
||||||
class Migration30_31 implements Migration<Connection> {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
Logger.getLogger(Migration30_31.class.getName());
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getStartVersion() {
|
|
||||||
return 30;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getEndVersion() {
|
|
||||||
return 31;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void migrate(Connection txn) throws DbException {
|
|
||||||
Statement s = null;
|
|
||||||
try {
|
|
||||||
s = txn.createStatement();
|
|
||||||
// Add groupId column
|
|
||||||
s.execute("ALTER TABLE messageMetadata"
|
|
||||||
+ " ADD COLUMN groupId BINARY(32) AFTER messageId");
|
|
||||||
// Populate groupId column
|
|
||||||
s.execute("UPDATE messageMetadata AS mm SET groupId ="
|
|
||||||
+ " (SELECT groupId FROM messages AS m"
|
|
||||||
+ " WHERE mm.messageId = m.messageId)");
|
|
||||||
// Add not null constraint now column has been populated
|
|
||||||
s.execute("ALTER TABLE messageMetadata"
|
|
||||||
+ " ALTER COLUMN groupId"
|
|
||||||
+ " SET NOT NULL");
|
|
||||||
// Add foreign key constraint
|
|
||||||
s.execute("ALTER TABLE messageMetadata"
|
|
||||||
+ " ADD CONSTRAINT groupIdForeignKey"
|
|
||||||
+ " FOREIGN KEY (groupId)"
|
|
||||||
+ " REFERENCES groups (groupId)"
|
|
||||||
+ " ON DELETE CASCADE");
|
|
||||||
// Add state column
|
|
||||||
s.execute("ALTER TABLE messageMetadata"
|
|
||||||
+ " ADD COLUMN state INT AFTER groupId");
|
|
||||||
// Populate state column
|
|
||||||
s.execute("UPDATE messageMetadata AS mm SET state ="
|
|
||||||
+ " (SELECT state FROM messages AS m"
|
|
||||||
+ " WHERE mm.messageId = m.messageId)");
|
|
||||||
// Add not null constraint now column has been populated
|
|
||||||
s.execute("ALTER TABLE messageMetadata"
|
|
||||||
+ " ALTER COLUMN state"
|
|
||||||
+ " SET NOT NULL");
|
|
||||||
} catch (SQLException e) {
|
|
||||||
tryToClose(s);
|
|
||||||
throw new DbException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tryToClose(@Nullable Statement s) {
|
|
||||||
try {
|
|
||||||
if (s != null) s.close();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
package org.briarproject.bramble.db;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.sql.Statement;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
|
|
||||||
class Migration31_32 implements Migration<Connection> {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
Logger.getLogger(Migration31_32.class.getName());
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getStartVersion() {
|
|
||||||
return 31;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getEndVersion() {
|
|
||||||
return 32;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void migrate(Connection txn) throws DbException {
|
|
||||||
Statement s = null;
|
|
||||||
try {
|
|
||||||
s = txn.createStatement();
|
|
||||||
// Add denormalised columns
|
|
||||||
s.execute("ALTER TABLE statuses ADD COLUMN"
|
|
||||||
+ " (groupId BINARY(32),"
|
|
||||||
+ " timestamp BIGINT,"
|
|
||||||
+ " length INT,"
|
|
||||||
+ " state INT,"
|
|
||||||
+ " groupShared BOOLEAN,"
|
|
||||||
+ " messageShared BOOLEAN,"
|
|
||||||
+ " deleted BOOLEAN)");
|
|
||||||
// Populate columns from messages table
|
|
||||||
s.execute("UPDATE statuses AS s SET (groupId, timestamp, length,"
|
|
||||||
+ " state, messageShared, deleted) ="
|
|
||||||
+ " (SELECT groupId, timestamp, length, state, shared,"
|
|
||||||
+ " raw IS NULL FROM messages AS m"
|
|
||||||
+ " WHERE s.messageId = m.messageId)");
|
|
||||||
// Populate column from groupVisibilities table
|
|
||||||
s.execute("UPDATE statuses AS s SET groupShared ="
|
|
||||||
+ " (SELECT shared FROM groupVisibilities AS gv"
|
|
||||||
+ " WHERE s.contactId = gv.contactId"
|
|
||||||
+ " AND s.groupId = gv.groupId)");
|
|
||||||
// Add not null constraints now columns have been populated
|
|
||||||
s.execute("ALTER TABLE statuses ALTER COLUMN groupId SET NOT NULL");
|
|
||||||
s.execute("ALTER TABLE statuses ALTER COLUMN timestamp"
|
|
||||||
+ " SET NOT NULL");
|
|
||||||
s.execute("ALTER TABLE statuses ALTER COLUMN length SET NOT NULL");
|
|
||||||
s.execute("ALTER TABLE statuses ALTER COLUMN state SET NOT NULL");
|
|
||||||
s.execute("ALTER TABLE statuses ALTER COLUMN groupShared"
|
|
||||||
+ " SET NOT NULL");
|
|
||||||
s.execute("ALTER TABLE statuses ALTER COLUMN messageShared"
|
|
||||||
+ " SET NOT NULL");
|
|
||||||
s.execute("ALTER TABLE statuses ALTER COLUMN deleted SET NOT NULL");
|
|
||||||
// Add foreign key constraint
|
|
||||||
s.execute("ALTER TABLE statuses"
|
|
||||||
+ " ADD CONSTRAINT statusesForeignKeyGroupId"
|
|
||||||
+ " FOREIGN KEY (groupId)"
|
|
||||||
+ " REFERENCES groups (groupId)"
|
|
||||||
+ " ON DELETE CASCADE");
|
|
||||||
} catch (SQLException e) {
|
|
||||||
tryToClose(s);
|
|
||||||
throw new DbException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tryToClose(@Nullable Statement s) {
|
|
||||||
try {
|
|
||||||
if (s != null) s.close();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +1,65 @@
|
|||||||
package org.briarproject.bramble.identity;
|
package org.briarproject.bramble.identity;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.data.BdfWriter;
|
|
||||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.briarproject.bramble.util.ByteUtils;
|
||||||
import java.io.ByteArrayOutputStream;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
|
||||||
|
import static org.briarproject.bramble.api.identity.AuthorId.LABEL;
|
||||||
|
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class AuthorFactoryImpl implements AuthorFactory {
|
class AuthorFactoryImpl implements AuthorFactory {
|
||||||
|
|
||||||
private final CryptoComponent crypto;
|
private final CryptoComponent crypto;
|
||||||
private final BdfWriterFactory bdfWriterFactory;
|
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
AuthorFactoryImpl(CryptoComponent crypto, BdfWriterFactory bdfWriterFactory,
|
AuthorFactoryImpl(CryptoComponent crypto, Clock clock) {
|
||||||
Clock clock) {
|
|
||||||
this.crypto = crypto;
|
this.crypto = crypto;
|
||||||
this.bdfWriterFactory = bdfWriterFactory;
|
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Author createAuthor(String name, byte[] publicKey) {
|
public Author createAuthor(String name, byte[] publicKey) {
|
||||||
return new Author(getId(name, publicKey), name, publicKey);
|
return createAuthor(FORMAT_VERSION, name, publicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Author createAuthor(int formatVersion, String name,
|
||||||
|
byte[] publicKey) {
|
||||||
|
AuthorId id = getId(formatVersion, name, publicKey);
|
||||||
|
return new Author(id, formatVersion, name, publicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LocalAuthor createLocalAuthor(String name, byte[] publicKey,
|
public LocalAuthor createLocalAuthor(String name, byte[] publicKey,
|
||||||
byte[] privateKey) {
|
byte[] privateKey) {
|
||||||
return new LocalAuthor(getId(name, publicKey), name, publicKey,
|
return createLocalAuthor(FORMAT_VERSION, name, publicKey, privateKey);
|
||||||
privateKey, clock.currentTimeMillis());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private AuthorId getId(String name, byte[] publicKey) {
|
@Override
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
public LocalAuthor createLocalAuthor(int formatVersion, String name,
|
||||||
BdfWriter w = bdfWriterFactory.createWriter(out);
|
byte[] publicKey, byte[] privateKey) {
|
||||||
try {
|
AuthorId id = getId(formatVersion, name, publicKey);
|
||||||
w.writeListStart();
|
return new LocalAuthor(id, formatVersion, name, publicKey, privateKey,
|
||||||
w.writeString(name);
|
clock.currentTimeMillis());
|
||||||
w.writeRaw(publicKey);
|
}
|
||||||
w.writeListEnd();
|
|
||||||
} catch (IOException e) {
|
private AuthorId getId(int formatVersion, String name, byte[] publicKey) {
|
||||||
// Shouldn't happen with ByteArrayOutputStream
|
byte[] formatVersionBytes = new byte[INT_32_BYTES];
|
||||||
throw new RuntimeException(e);
|
ByteUtils.writeUint32(formatVersion, formatVersionBytes, 0);
|
||||||
}
|
return new AuthorId(crypto.hash(LABEL, formatVersionBytes,
|
||||||
return new AuthorId(crypto.hash(AuthorId.LABEL, out.toByteArray()));
|
StringUtils.toUtf8(name), publicKey));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
package org.briarproject.bramble.identity;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
|
||||||
import org.briarproject.bramble.api.data.BdfReader;
|
|
||||||
import org.briarproject.bramble.api.data.ObjectReader;
|
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
|
||||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
@NotNullByDefault
|
|
||||||
class AuthorReader implements ObjectReader<Author> {
|
|
||||||
|
|
||||||
private final AuthorFactory authorFactory;
|
|
||||||
|
|
||||||
AuthorReader(AuthorFactory authorFactory) {
|
|
||||||
this.authorFactory = authorFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Author readObject(BdfReader r) throws IOException {
|
|
||||||
r.readListStart();
|
|
||||||
String name = r.readString(MAX_AUTHOR_NAME_LENGTH);
|
|
||||||
if (name.length() == 0) throw new FormatException();
|
|
||||||
byte[] publicKey = r.readRaw(MAX_PUBLIC_KEY_LENGTH);
|
|
||||||
r.readListEnd();
|
|
||||||
return authorFactory.createAuthor(name, publicKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,7 @@
|
|||||||
package org.briarproject.bramble.identity;
|
package org.briarproject.bramble.identity;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
|
||||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
|
||||||
import org.briarproject.bramble.api.data.ObjectReader;
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
|
||||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
@@ -24,19 +18,14 @@ public class IdentityModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
AuthorFactory provideAuthorFactory(CryptoComponent crypto,
|
AuthorFactory provideAuthorFactory(AuthorFactoryImpl authorFactory) {
|
||||||
BdfWriterFactory bdfWriterFactory, Clock clock) {
|
return authorFactory;
|
||||||
return new AuthorFactoryImpl(crypto, bdfWriterFactory, clock);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
IdentityManager provideIdentityModule(DatabaseComponent db) {
|
IdentityManager provideIdentityManager(
|
||||||
return new IdentityManagerImpl(db);
|
IdentityManagerImpl identityManager) {
|
||||||
}
|
return identityManager;
|
||||||
|
|
||||||
@Provides
|
|
||||||
ObjectReader<Author> provideAuthorReader(AuthorFactory authorFactory) {
|
|
||||||
return new AuthorReader(authorFactory);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package org.briarproject.bramble.keyagreement;
|
package org.briarproject.bramble.keyagreement;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||||
@@ -41,7 +41,7 @@ class KeyAgreementConnector {
|
|||||||
Logger.getLogger(KeyAgreementConnector.class.getName());
|
Logger.getLogger(KeyAgreementConnector.class.getName());
|
||||||
|
|
||||||
private final Callbacks callbacks;
|
private final Callbacks callbacks;
|
||||||
private final CryptoComponent crypto;
|
private final KeyAgreementCrypto keyAgreementCrypto;
|
||||||
private final PluginManager pluginManager;
|
private final PluginManager pluginManager;
|
||||||
private final ConnectionChooser connectionChooser;
|
private final ConnectionChooser connectionChooser;
|
||||||
|
|
||||||
@@ -53,10 +53,10 @@ class KeyAgreementConnector {
|
|||||||
private volatile boolean alice = false, stopped = false;
|
private volatile boolean alice = false, stopped = false;
|
||||||
|
|
||||||
KeyAgreementConnector(Callbacks callbacks,
|
KeyAgreementConnector(Callbacks callbacks,
|
||||||
CryptoComponent crypto, PluginManager pluginManager,
|
KeyAgreementCrypto keyAgreementCrypto, PluginManager pluginManager,
|
||||||
ConnectionChooser connectionChooser) {
|
ConnectionChooser connectionChooser) {
|
||||||
this.callbacks = callbacks;
|
this.callbacks = callbacks;
|
||||||
this.crypto = crypto;
|
this.keyAgreementCrypto = keyAgreementCrypto;
|
||||||
this.pluginManager = pluginManager;
|
this.pluginManager = pluginManager;
|
||||||
this.connectionChooser = connectionChooser;
|
this.connectionChooser = connectionChooser;
|
||||||
}
|
}
|
||||||
@@ -64,8 +64,8 @@ class KeyAgreementConnector {
|
|||||||
Payload listen(KeyPair localKeyPair) {
|
Payload listen(KeyPair localKeyPair) {
|
||||||
LOG.info("Starting BQP listeners");
|
LOG.info("Starting BQP listeners");
|
||||||
// Derive commitment
|
// Derive commitment
|
||||||
byte[] commitment = crypto.deriveKeyCommitment(
|
byte[] commitment = keyAgreementCrypto.deriveKeyCommitment(
|
||||||
localKeyPair.getPublic().getEncoded());
|
localKeyPair.getPublic());
|
||||||
// Start all listeners and collect their descriptors
|
// Start all listeners and collect their descriptors
|
||||||
List<TransportDescriptor> descriptors = new ArrayList<>();
|
List<TransportDescriptor> descriptors = new ArrayList<>();
|
||||||
for (DuplexPlugin plugin : pluginManager.getKeyAgreementPlugins()) {
|
for (DuplexPlugin plugin : pluginManager.getKeyAgreementPlugins()) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package org.briarproject.bramble.keyagreement;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
|
||||||
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
||||||
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
||||||
|
|
||||||
@@ -13,9 +13,9 @@ import dagger.Provides;
|
|||||||
public class KeyAgreementModule {
|
public class KeyAgreementModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
KeyAgreementTaskFactory provideKeyAgreementTaskFactory(
|
KeyAgreementTask provideKeyAgreementTask(
|
||||||
KeyAgreementTaskFactoryImpl keyAgreementTaskFactory) {
|
KeyAgreementTaskImpl keyAgreementTask) {
|
||||||
return keyAgreementTaskFactory;
|
return keyAgreementTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package org.briarproject.bramble.keyagreement;
|
package org.briarproject.bramble.keyagreement;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
|
import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
|
import org.briarproject.bramble.api.crypto.KeyParser;
|
||||||
|
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.api.keyagreement.Payload;
|
import org.briarproject.bramble.api.keyagreement.Payload;
|
||||||
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
||||||
@@ -11,6 +14,10 @@ import java.io.IOException;
|
|||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.MASTER_SECRET_LABEL;
|
||||||
|
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
|
||||||
|
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.SHARED_SECRET_LABEL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of the BQP protocol.
|
* Implementation of the BQP protocol.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -57,6 +64,7 @@ class KeyAgreementProtocol {
|
|||||||
|
|
||||||
private final Callbacks callbacks;
|
private final Callbacks callbacks;
|
||||||
private final CryptoComponent crypto;
|
private final CryptoComponent crypto;
|
||||||
|
private final KeyAgreementCrypto keyAgreementCrypto;
|
||||||
private final PayloadEncoder payloadEncoder;
|
private final PayloadEncoder payloadEncoder;
|
||||||
private final KeyAgreementTransport transport;
|
private final KeyAgreementTransport transport;
|
||||||
private final Payload theirPayload, ourPayload;
|
private final Payload theirPayload, ourPayload;
|
||||||
@@ -64,11 +72,13 @@ class KeyAgreementProtocol {
|
|||||||
private final boolean alice;
|
private final boolean alice;
|
||||||
|
|
||||||
KeyAgreementProtocol(Callbacks callbacks, CryptoComponent crypto,
|
KeyAgreementProtocol(Callbacks callbacks, CryptoComponent crypto,
|
||||||
|
KeyAgreementCrypto keyAgreementCrypto,
|
||||||
PayloadEncoder payloadEncoder, KeyAgreementTransport transport,
|
PayloadEncoder payloadEncoder, KeyAgreementTransport transport,
|
||||||
Payload theirPayload, Payload ourPayload, KeyPair ourKeyPair,
|
Payload theirPayload, Payload ourPayload, KeyPair ourKeyPair,
|
||||||
boolean alice) {
|
boolean alice) {
|
||||||
this.callbacks = callbacks;
|
this.callbacks = callbacks;
|
||||||
this.crypto = crypto;
|
this.crypto = crypto;
|
||||||
|
this.keyAgreementCrypto = keyAgreementCrypto;
|
||||||
this.payloadEncoder = payloadEncoder;
|
this.payloadEncoder = payloadEncoder;
|
||||||
this.transport = transport;
|
this.transport = transport;
|
||||||
this.theirPayload = theirPayload;
|
this.theirPayload = theirPayload;
|
||||||
@@ -86,7 +96,7 @@ class KeyAgreementProtocol {
|
|||||||
*/
|
*/
|
||||||
SecretKey perform() throws AbortException, IOException {
|
SecretKey perform() throws AbortException, IOException {
|
||||||
try {
|
try {
|
||||||
byte[] theirPublicKey;
|
PublicKey theirPublicKey;
|
||||||
if (alice) {
|
if (alice) {
|
||||||
sendKey();
|
sendKey();
|
||||||
// Alice waits here for Bob to scan her QR code, determine his
|
// Alice waits here for Bob to scan her QR code, determine his
|
||||||
@@ -105,7 +115,7 @@ class KeyAgreementProtocol {
|
|||||||
receiveConfirm(s, theirPublicKey);
|
receiveConfirm(s, theirPublicKey);
|
||||||
sendConfirm(s, theirPublicKey);
|
sendConfirm(s, theirPublicKey);
|
||||||
}
|
}
|
||||||
return crypto.deriveMasterSecret(s);
|
return crypto.deriveKey(MASTER_SECRET_LABEL, s);
|
||||||
} catch (AbortException e) {
|
} catch (AbortException e) {
|
||||||
sendAbort(e.getCause() != null);
|
sendAbort(e.getCause() != null);
|
||||||
throw e;
|
throw e;
|
||||||
@@ -116,27 +126,41 @@ class KeyAgreementProtocol {
|
|||||||
transport.sendKey(ourKeyPair.getPublic().getEncoded());
|
transport.sendKey(ourKeyPair.getPublic().getEncoded());
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] receiveKey() throws AbortException {
|
private PublicKey receiveKey() throws AbortException {
|
||||||
byte[] publicKey = transport.receiveKey();
|
byte[] publicKeyBytes = transport.receiveKey();
|
||||||
callbacks.initialRecordReceived();
|
callbacks.initialRecordReceived();
|
||||||
byte[] expected = crypto.deriveKeyCommitment(publicKey);
|
KeyParser keyParser = crypto.getAgreementKeyParser();
|
||||||
if (!Arrays.equals(expected, theirPayload.getCommitment()))
|
try {
|
||||||
|
PublicKey publicKey = keyParser.parsePublicKey(publicKeyBytes);
|
||||||
|
byte[] expected = keyAgreementCrypto.deriveKeyCommitment(publicKey);
|
||||||
|
if (!Arrays.equals(expected, theirPayload.getCommitment()))
|
||||||
|
throw new AbortException();
|
||||||
|
return publicKey;
|
||||||
|
} catch (GeneralSecurityException e) {
|
||||||
throw new AbortException();
|
throw new AbortException();
|
||||||
return publicKey;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private SecretKey deriveSharedSecret(byte[] theirPublicKey)
|
private SecretKey deriveSharedSecret(PublicKey theirPublicKey)
|
||||||
throws AbortException {
|
throws AbortException {
|
||||||
try {
|
try {
|
||||||
return crypto.deriveSharedSecret(theirPublicKey, ourKeyPair, alice);
|
byte[] ourPublicKeyBytes = ourKeyPair.getPublic().getEncoded();
|
||||||
|
byte[] theirPublicKeyBytes = theirPublicKey.getEncoded();
|
||||||
|
byte[][] inputs = {
|
||||||
|
new byte[] {PROTOCOL_VERSION},
|
||||||
|
alice ? ourPublicKeyBytes : theirPublicKeyBytes,
|
||||||
|
alice ? theirPublicKeyBytes : ourPublicKeyBytes
|
||||||
|
};
|
||||||
|
return crypto.deriveSharedSecret(SHARED_SECRET_LABEL,
|
||||||
|
theirPublicKey, ourKeyPair, inputs);
|
||||||
} catch (GeneralSecurityException e) {
|
} catch (GeneralSecurityException e) {
|
||||||
throw new AbortException(e);
|
throw new AbortException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendConfirm(SecretKey s, byte[] theirPublicKey)
|
private void sendConfirm(SecretKey s, PublicKey theirPublicKey)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
byte[] confirm = crypto.deriveConfirmationRecord(s,
|
byte[] confirm = keyAgreementCrypto.deriveConfirmationRecord(s,
|
||||||
payloadEncoder.encode(theirPayload),
|
payloadEncoder.encode(theirPayload),
|
||||||
payloadEncoder.encode(ourPayload),
|
payloadEncoder.encode(ourPayload),
|
||||||
theirPublicKey, ourKeyPair,
|
theirPublicKey, ourKeyPair,
|
||||||
@@ -144,10 +168,10 @@ class KeyAgreementProtocol {
|
|||||||
transport.sendConfirm(confirm);
|
transport.sendConfirm(confirm);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void receiveConfirm(SecretKey s, byte[] theirPublicKey)
|
private void receiveConfirm(SecretKey s, PublicKey theirPublicKey)
|
||||||
throws AbortException {
|
throws AbortException {
|
||||||
byte[] confirm = transport.receiveConfirm();
|
byte[] confirm = transport.receiveConfirm();
|
||||||
byte[] expected = crypto.deriveConfirmationRecord(s,
|
byte[] expected = keyAgreementCrypto.deriveConfirmationRecord(s,
|
||||||
payloadEncoder.encode(theirPayload),
|
payloadEncoder.encode(theirPayload),
|
||||||
payloadEncoder.encode(ourPayload),
|
payloadEncoder.encode(ourPayload),
|
||||||
theirPublicKey, ourKeyPair,
|
theirPublicKey, ourKeyPair,
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
package org.briarproject.bramble.keyagreement;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
|
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory;
|
|
||||||
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Provider;
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
@NotNullByDefault
|
|
||||||
class KeyAgreementTaskFactoryImpl implements KeyAgreementTaskFactory {
|
|
||||||
|
|
||||||
private final CryptoComponent crypto;
|
|
||||||
private final EventBus eventBus;
|
|
||||||
private final PayloadEncoder payloadEncoder;
|
|
||||||
private final PluginManager pluginManager;
|
|
||||||
private final Provider<ConnectionChooser> connectionChooserProvider;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
KeyAgreementTaskFactoryImpl(CryptoComponent crypto, EventBus eventBus,
|
|
||||||
PayloadEncoder payloadEncoder, PluginManager pluginManager,
|
|
||||||
Provider<ConnectionChooser> connectionChooserProvider) {
|
|
||||||
this.crypto = crypto;
|
|
||||||
this.eventBus = eventBus;
|
|
||||||
this.payloadEncoder = payloadEncoder;
|
|
||||||
this.pluginManager = pluginManager;
|
|
||||||
this.connectionChooserProvider = connectionChooserProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeyAgreementTask createTask() {
|
|
||||||
return new KeyAgreementTaskImpl(crypto, eventBus, payloadEncoder,
|
|
||||||
pluginManager, connectionChooserProvider.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.bramble.keyagreement;
|
package org.briarproject.bramble.keyagreement;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
|
import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
@@ -21,6 +22,8 @@ import org.briarproject.bramble.api.plugin.PluginManager;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@@ -32,6 +35,7 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
|
|||||||
Logger.getLogger(KeyAgreementTaskImpl.class.getName());
|
Logger.getLogger(KeyAgreementTaskImpl.class.getName());
|
||||||
|
|
||||||
private final CryptoComponent crypto;
|
private final CryptoComponent crypto;
|
||||||
|
private final KeyAgreementCrypto keyAgreementCrypto;
|
||||||
private final EventBus eventBus;
|
private final EventBus eventBus;
|
||||||
private final PayloadEncoder payloadEncoder;
|
private final PayloadEncoder payloadEncoder;
|
||||||
private final KeyPair localKeyPair;
|
private final KeyPair localKeyPair;
|
||||||
@@ -40,15 +44,18 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
|
|||||||
private Payload localPayload;
|
private Payload localPayload;
|
||||||
private Payload remotePayload;
|
private Payload remotePayload;
|
||||||
|
|
||||||
KeyAgreementTaskImpl(CryptoComponent crypto, EventBus eventBus,
|
@Inject
|
||||||
|
KeyAgreementTaskImpl(CryptoComponent crypto,
|
||||||
|
KeyAgreementCrypto keyAgreementCrypto, EventBus eventBus,
|
||||||
PayloadEncoder payloadEncoder, PluginManager pluginManager,
|
PayloadEncoder payloadEncoder, PluginManager pluginManager,
|
||||||
ConnectionChooser connectionChooser) {
|
ConnectionChooser connectionChooser) {
|
||||||
this.crypto = crypto;
|
this.crypto = crypto;
|
||||||
|
this.keyAgreementCrypto = keyAgreementCrypto;
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
this.payloadEncoder = payloadEncoder;
|
this.payloadEncoder = payloadEncoder;
|
||||||
localKeyPair = crypto.generateAgreementKeyPair();
|
localKeyPair = crypto.generateAgreementKeyPair();
|
||||||
connector = new KeyAgreementConnector(this, crypto, pluginManager,
|
connector = new KeyAgreementConnector(this, keyAgreementCrypto,
|
||||||
connectionChooser);
|
pluginManager, connectionChooser);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -95,8 +102,8 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
|
|||||||
// Run BQP protocol over the connection
|
// Run BQP protocol over the connection
|
||||||
LOG.info("Starting BQP protocol");
|
LOG.info("Starting BQP protocol");
|
||||||
KeyAgreementProtocol protocol = new KeyAgreementProtocol(this, crypto,
|
KeyAgreementProtocol protocol = new KeyAgreementProtocol(this, crypto,
|
||||||
payloadEncoder, transport, remotePayload, localPayload,
|
keyAgreementCrypto, payloadEncoder, transport, remotePayload,
|
||||||
localKeyPair, alice);
|
localPayload, localKeyPair, alice);
|
||||||
try {
|
try {
|
||||||
SecretKey master = protocol.perform();
|
SecretKey master = protocol.perform();
|
||||||
KeyAgreementResult result =
|
KeyAgreementResult result =
|
||||||
|
|||||||
@@ -29,10 +29,10 @@ class PayloadEncoderImpl implements PayloadEncoder {
|
|||||||
@Override
|
@Override
|
||||||
public byte[] encode(Payload p) {
|
public byte[] encode(Payload p) {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
out.write(PROTOCOL_VERSION);
|
||||||
BdfWriter w = bdfWriterFactory.createWriter(out);
|
BdfWriter w = bdfWriterFactory.createWriter(out);
|
||||||
try {
|
try {
|
||||||
w.writeListStart(); // Payload start
|
w.writeListStart(); // Payload start
|
||||||
w.writeLong(PROTOCOL_VERSION);
|
|
||||||
w.writeRaw(p.getCommitment());
|
w.writeRaw(p.getCommitment());
|
||||||
for (TransportDescriptor d : p.getTransportDescriptors())
|
for (TransportDescriptor d : p.getTransportDescriptors())
|
||||||
w.writeList(d.getDescriptor());
|
w.writeList(d.getDescriptor());
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.bramble.keyagreement;
|
package org.briarproject.bramble.keyagreement;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.UnsupportedVersionException;
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.data.BdfReader;
|
import org.briarproject.bramble.api.data.BdfReader;
|
||||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||||
@@ -39,20 +40,22 @@ class PayloadParserImpl implements PayloadParser {
|
|||||||
@Override
|
@Override
|
||||||
public Payload parse(byte[] raw) throws IOException {
|
public Payload parse(byte[] raw) throws IOException {
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(raw);
|
ByteArrayInputStream in = new ByteArrayInputStream(raw);
|
||||||
|
// First byte: the protocol version
|
||||||
|
int protocolVersion = in.read();
|
||||||
|
if (protocolVersion == -1) throw new FormatException();
|
||||||
|
if (protocolVersion != PROTOCOL_VERSION)
|
||||||
|
throw new UnsupportedVersionException();
|
||||||
|
// The rest of the payload is a BDF list with one or more elements
|
||||||
BdfReader r = bdfReaderFactory.createReader(in);
|
BdfReader r = bdfReaderFactory.createReader(in);
|
||||||
// The payload is a BDF list with two or more elements
|
|
||||||
BdfList payload = r.readList();
|
BdfList payload = r.readList();
|
||||||
if (payload.size() < 2) throw new FormatException();
|
if (payload.isEmpty()) throw new FormatException();
|
||||||
if (!r.eof()) throw new FormatException();
|
if (!r.eof()) throw new FormatException();
|
||||||
// First element: the protocol version
|
// First element: the public key commitment
|
||||||
long protocolVersion = payload.getLong(0);
|
byte[] commitment = payload.getRaw(0);
|
||||||
if (protocolVersion != PROTOCOL_VERSION) throw new FormatException();
|
|
||||||
// Second element: the public key commitment
|
|
||||||
byte[] commitment = payload.getRaw(1);
|
|
||||||
if (commitment.length != COMMIT_LENGTH) throw new FormatException();
|
if (commitment.length != COMMIT_LENGTH) throw new FormatException();
|
||||||
// Remaining elements: transport descriptors
|
// Remaining elements: transport descriptors
|
||||||
List<TransportDescriptor> recognised = new ArrayList<>();
|
List<TransportDescriptor> recognised = new ArrayList<>();
|
||||||
for (int i = 2; i < payload.size(); i++) {
|
for (int i = 1; i < payload.size(); i++) {
|
||||||
BdfList descriptor = payload.getList(i);
|
BdfList descriptor = payload.getList(i);
|
||||||
long transportId = descriptor.getLong(0);
|
long transportId = descriptor.getLong(0);
|
||||||
if (transportId == TRANSPORT_ID_BLUETOOTH) {
|
if (transportId == TRANSPORT_ID_BLUETOOTH) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.bramble.plugin;
|
package org.briarproject.bramble.plugin;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Multiset;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
@@ -36,14 +37,14 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
|
|||||||
private final Lock lock = new ReentrantLock();
|
private final Lock lock = new ReentrantLock();
|
||||||
|
|
||||||
// The following are locking: lock
|
// The following are locking: lock
|
||||||
private final Map<TransportId, Map<ContactId, Integer>> connections;
|
private final Map<TransportId, Multiset<ContactId>> connections;
|
||||||
private final Map<ContactId, Integer> contactCounts;
|
private final Multiset<ContactId> contactCounts;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ConnectionRegistryImpl(EventBus eventBus) {
|
ConnectionRegistryImpl(EventBus eventBus) {
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
connections = new HashMap<>();
|
connections = new HashMap<>();
|
||||||
contactCounts = new HashMap<>();
|
contactCounts = new Multiset<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -56,21 +57,13 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
|
|||||||
boolean firstConnection = false;
|
boolean firstConnection = false;
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
Map<ContactId, Integer> m = connections.get(t);
|
Multiset<ContactId> m = connections.get(t);
|
||||||
if (m == null) {
|
if (m == null) {
|
||||||
m = new HashMap<>();
|
m = new Multiset<>();
|
||||||
connections.put(t, m);
|
connections.put(t, m);
|
||||||
}
|
}
|
||||||
Integer count = m.get(c);
|
m.add(c);
|
||||||
if (count == null) m.put(c, 1);
|
if (contactCounts.add(c) == 1) firstConnection = true;
|
||||||
else m.put(c, count + 1);
|
|
||||||
count = contactCounts.get(c);
|
|
||||||
if (count == null) {
|
|
||||||
firstConnection = true;
|
|
||||||
contactCounts.put(c, 1);
|
|
||||||
} else {
|
|
||||||
contactCounts.put(c, count + 1);
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
@@ -91,23 +84,10 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
|
|||||||
boolean lastConnection = false;
|
boolean lastConnection = false;
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
Map<ContactId, Integer> m = connections.get(t);
|
Multiset<ContactId> m = connections.get(t);
|
||||||
if (m == null) throw new IllegalArgumentException();
|
if (m == null) throw new IllegalArgumentException();
|
||||||
Integer count = m.remove(c);
|
m.remove(c);
|
||||||
if (count == null) throw new IllegalArgumentException();
|
if (contactCounts.remove(c) == 0) lastConnection = true;
|
||||||
if (count == 1) {
|
|
||||||
if (m.isEmpty()) connections.remove(t);
|
|
||||||
} else {
|
|
||||||
m.put(c, count - 1);
|
|
||||||
}
|
|
||||||
count = contactCounts.get(c);
|
|
||||||
if (count == null) throw new IllegalArgumentException();
|
|
||||||
if (count == 1) {
|
|
||||||
lastConnection = true;
|
|
||||||
contactCounts.remove(c);
|
|
||||||
} else {
|
|
||||||
contactCounts.put(c, count - 1);
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
@@ -122,7 +102,7 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
|
|||||||
public Collection<ContactId> getConnectedContacts(TransportId t) {
|
public Collection<ContactId> getConnectedContacts(TransportId t) {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
Map<ContactId, Integer> m = connections.get(t);
|
Multiset<ContactId> m = connections.get(t);
|
||||||
if (m == null) return Collections.emptyList();
|
if (m == null) return Collections.emptyList();
|
||||||
List<ContactId> ids = new ArrayList<>(m.keySet());
|
List<ContactId> ids = new ArrayList<>(m.keySet());
|
||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO))
|
||||||
@@ -137,8 +117,8 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
|
|||||||
public boolean isConnected(ContactId c, TransportId t) {
|
public boolean isConnected(ContactId c, TransportId t) {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
Map<ContactId, Integer> m = connections.get(t);
|
Multiset<ContactId> m = connections.get(t);
|
||||||
return m != null && m.containsKey(c);
|
return m != null && m.contains(c);
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
@@ -148,7 +128,7 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
|
|||||||
public boolean isConnected(ContactId c) {
|
public boolean isConnected(ContactId c) {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
return contactCounts.containsKey(c);
|
return contactCounts.contains(c);
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -241,11 +241,10 @@ class LanTcpPlugin extends TcpPlugin {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
Socket s = new Socket();
|
||||||
try {
|
try {
|
||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO))
|
||||||
LOG.info("Connecting to " + scrubSocketAddress(remote));
|
LOG.info("Connecting to " + scrubSocketAddress(remote));
|
||||||
Socket s = createSocket();
|
|
||||||
s.bind(new InetSocketAddress(socket.getInetAddress(), 0));
|
|
||||||
s.connect(remote);
|
s.connect(remote);
|
||||||
s.setSoTimeout(socketTimeout);
|
s.setSoTimeout(socketTimeout);
|
||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO))
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.briarproject.bramble.plugin.tcp;
|
package org.briarproject.bramble.plugin.tcp;
|
||||||
|
|
||||||
import org.briarproject.bramble.PoliteExecutor;
|
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||||
@@ -48,7 +47,7 @@ abstract class TcpPlugin implements DuplexPlugin {
|
|||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(TcpPlugin.class.getName());
|
Logger.getLogger(TcpPlugin.class.getName());
|
||||||
|
|
||||||
protected final Executor ioExecutor, bindExecutor;
|
protected final Executor ioExecutor;
|
||||||
protected final Backoff backoff;
|
protected final Backoff backoff;
|
||||||
protected final DuplexPluginCallback callback;
|
protected final DuplexPluginCallback callback;
|
||||||
protected final int maxLatency, maxIdleTime, socketTimeout;
|
protected final int maxLatency, maxIdleTime, socketTimeout;
|
||||||
@@ -91,8 +90,6 @@ abstract class TcpPlugin implements DuplexPlugin {
|
|||||||
if (maxIdleTime > Integer.MAX_VALUE / 2)
|
if (maxIdleTime > Integer.MAX_VALUE / 2)
|
||||||
socketTimeout = Integer.MAX_VALUE;
|
socketTimeout = Integer.MAX_VALUE;
|
||||||
else socketTimeout = maxIdleTime * 2;
|
else socketTimeout = maxIdleTime * 2;
|
||||||
// Don't execute more than one bind operation at a time
|
|
||||||
bindExecutor = new PoliteExecutor("TcpPlugin", ioExecutor, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -113,9 +110,8 @@ abstract class TcpPlugin implements DuplexPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void bind() {
|
protected void bind() {
|
||||||
bindExecutor.execute(() -> {
|
ioExecutor.execute(() -> {
|
||||||
if (!running) return;
|
if (!running) return;
|
||||||
if (socket != null && !socket.isClosed()) return;
|
|
||||||
ServerSocket ss = null;
|
ServerSocket ss = null;
|
||||||
for (InetSocketAddress addr : getLocalSocketAddresses()) {
|
for (InetSocketAddress addr : getLocalSocketAddresses()) {
|
||||||
try {
|
try {
|
||||||
@@ -247,11 +243,10 @@ abstract class TcpPlugin implements DuplexPlugin {
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
Socket s = new Socket();
|
||||||
try {
|
try {
|
||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO))
|
||||||
LOG.info("Connecting to " + scrubSocketAddress(remote));
|
LOG.info("Connecting to " + scrubSocketAddress(remote));
|
||||||
Socket s = createSocket();
|
|
||||||
s.bind(new InetSocketAddress(socket.getInetAddress(), 0));
|
|
||||||
s.connect(remote);
|
s.connect(remote);
|
||||||
s.setSoTimeout(socketTimeout);
|
s.setSoTimeout(socketTimeout);
|
||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO))
|
||||||
@@ -266,10 +261,6 @@ abstract class TcpPlugin implements DuplexPlugin {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Socket createSocket() throws IOException {
|
|
||||||
return new Socket();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
InetSocketAddress parseSocketAddress(String ipPort) {
|
InetSocketAddress parseSocketAddress(String ipPort) {
|
||||||
if (StringUtils.isNullOrEmpty(ipPort)) return null;
|
if (StringUtils.isNullOrEmpty(ipPort)) return null;
|
||||||
|
|||||||
@@ -58,7 +58,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
this.metadataParser = metadataParser;
|
this.metadataParser = metadataParser;
|
||||||
this.contactGroupFactory = contactGroupFactory;
|
this.contactGroupFactory = contactGroupFactory;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID);
|
localGroup = contactGroupFactory.createLocalGroup(CLIENT_ID,
|
||||||
|
CLIENT_VERSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -130,8 +131,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
public Map<TransportId, TransportProperties> getLocalProperties()
|
public Map<TransportId, TransportProperties> getLocalProperties()
|
||||||
throws DbException {
|
throws DbException {
|
||||||
Map<TransportId, TransportProperties> local;
|
Map<TransportId, TransportProperties> local;
|
||||||
// TODO: Transaction can be read-only when code is simplified
|
Transaction txn = db.startTransaction(true);
|
||||||
Transaction txn = db.startTransaction(false);
|
|
||||||
try {
|
try {
|
||||||
local = getLocalProperties(txn);
|
local = getLocalProperties(txn);
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
@@ -166,8 +166,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
throws DbException {
|
throws DbException {
|
||||||
try {
|
try {
|
||||||
TransportProperties p = null;
|
TransportProperties p = null;
|
||||||
// TODO: Transaction can be read-only when code is simplified
|
Transaction txn = db.startTransaction(true);
|
||||||
Transaction txn = db.startTransaction(false);
|
|
||||||
try {
|
try {
|
||||||
// Find the latest local update
|
// Find the latest local update
|
||||||
LatestUpdate latest = findLatest(txn, localGroup.getId(), t,
|
LatestUpdate latest = findLatest(txn, localGroup.getId(), t,
|
||||||
@@ -193,8 +192,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
public Map<ContactId, TransportProperties> getRemoteProperties(
|
public Map<ContactId, TransportProperties> getRemoteProperties(
|
||||||
TransportId t) throws DbException {
|
TransportId t) throws DbException {
|
||||||
Map<ContactId, TransportProperties> remote = new HashMap<>();
|
Map<ContactId, TransportProperties> remote = new HashMap<>();
|
||||||
// TODO: Transaction can be read-only when code is simplified
|
Transaction txn = db.startTransaction(true);
|
||||||
Transaction txn = db.startTransaction(false);
|
|
||||||
try {
|
try {
|
||||||
for (Contact c : db.getContacts(txn))
|
for (Contact c : db.getContacts(txn))
|
||||||
remote.put(c.getId(), getRemoteProperties(txn, c, t));
|
remote.put(c.getId(), getRemoteProperties(txn, c, t));
|
||||||
@@ -228,8 +226,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
public TransportProperties getRemoteProperties(ContactId c, TransportId t)
|
public TransportProperties getRemoteProperties(ContactId c, TransportId t)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
TransportProperties p;
|
TransportProperties p;
|
||||||
// TODO: Transaction can be read-only when code is simplified
|
Transaction txn = db.startTransaction(true);
|
||||||
Transaction txn = db.startTransaction(false);
|
|
||||||
try {
|
try {
|
||||||
p = getRemoteProperties(txn, db.getContact(txn, c), t);
|
p = getRemoteProperties(txn, db.getContact(txn, c), t);
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
@@ -292,7 +289,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Group getContactGroup(Contact c) {
|
private Group getContactGroup(Contact c) {
|
||||||
return contactGroupFactory.createContactGroup(CLIENT_ID, c);
|
return contactGroupFactory.createContactGroup(CLIENT_ID,
|
||||||
|
CLIENT_VERSION, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void storeMessage(Transaction txn, GroupId g, TransportId t,
|
private void storeMessage(Transaction txn, GroupId g, TransportId t,
|
||||||
@@ -319,7 +317,6 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
|
|
||||||
private Map<TransportId, LatestUpdate> findLatestLocal(Transaction txn)
|
private Map<TransportId, LatestUpdate> findLatestLocal(Transaction txn)
|
||||||
throws DbException, FormatException {
|
throws DbException, FormatException {
|
||||||
// TODO: This can be simplified before 1.0
|
|
||||||
Map<TransportId, LatestUpdate> latestUpdates = new HashMap<>();
|
Map<TransportId, LatestUpdate> latestUpdates = new HashMap<>();
|
||||||
Map<MessageId, BdfDictionary> metadata = clientHelper
|
Map<MessageId, BdfDictionary> metadata = clientHelper
|
||||||
.getMessageMetadataAsDictionary(txn, localGroup.getId());
|
.getMessageMetadataAsDictionary(txn, localGroup.getId());
|
||||||
@@ -327,17 +324,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
BdfDictionary meta = e.getValue();
|
BdfDictionary meta = e.getValue();
|
||||||
TransportId t = new TransportId(meta.getString("transportId"));
|
TransportId t = new TransportId(meta.getString("transportId"));
|
||||||
long version = meta.getLong("version");
|
long version = meta.getLong("version");
|
||||||
LatestUpdate latest = latestUpdates.get(t);
|
latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
|
||||||
if (latest == null) {
|
|
||||||
latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
|
|
||||||
} else if (version > latest.version) {
|
|
||||||
// This update is newer - delete the previous one
|
|
||||||
db.removeMessage(txn, latest.messageId);
|
|
||||||
latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
|
|
||||||
} else {
|
|
||||||
// We've already found a newer update - delete this one
|
|
||||||
db.removeMessage(txn, e.getKey());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return latestUpdates;
|
return latestUpdates;
|
||||||
}
|
}
|
||||||
@@ -345,38 +332,16 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
@Nullable
|
@Nullable
|
||||||
private LatestUpdate findLatest(Transaction txn, GroupId g, TransportId t,
|
private LatestUpdate findLatest(Transaction txn, GroupId g, TransportId t,
|
||||||
boolean local) throws DbException, FormatException {
|
boolean local) throws DbException, FormatException {
|
||||||
// TODO: This can be simplified before 1.0
|
|
||||||
LatestUpdate latest = null;
|
|
||||||
Map<MessageId, BdfDictionary> metadata =
|
Map<MessageId, BdfDictionary> metadata =
|
||||||
clientHelper.getMessageMetadataAsDictionary(txn, g);
|
clientHelper.getMessageMetadataAsDictionary(txn, g);
|
||||||
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
|
for (Entry<MessageId, BdfDictionary> e : metadata.entrySet()) {
|
||||||
BdfDictionary meta = e.getValue();
|
BdfDictionary meta = e.getValue();
|
||||||
if (meta.getString("transportId").equals(t.getString())
|
if (meta.getString("transportId").equals(t.getString())
|
||||||
&& meta.getBoolean("local") == local) {
|
&& meta.getBoolean("local") == local) {
|
||||||
long version = meta.getLong("version");
|
return new LatestUpdate(e.getKey(), meta.getLong("version"));
|
||||||
if (latest == null) {
|
|
||||||
latest = new LatestUpdate(e.getKey(), version);
|
|
||||||
} else if (version > latest.version) {
|
|
||||||
// This update is newer - delete the previous one
|
|
||||||
if (local) {
|
|
||||||
db.removeMessage(txn, latest.messageId);
|
|
||||||
} else {
|
|
||||||
db.deleteMessage(txn, latest.messageId);
|
|
||||||
db.deleteMessageMetadata(txn, latest.messageId);
|
|
||||||
}
|
|
||||||
latest = new LatestUpdate(e.getKey(), version);
|
|
||||||
} else {
|
|
||||||
// We've already found a newer update - delete this one
|
|
||||||
if (local) {
|
|
||||||
db.removeMessage(txn, e.getKey());
|
|
||||||
} else {
|
|
||||||
db.deleteMessage(txn, e.getKey());
|
|
||||||
db.deleteMessageMetadata(txn, e.getKey());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return latest;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TransportProperties parseProperties(BdfList message)
|
private TransportProperties parseProperties(BdfList message)
|
||||||
|
|||||||
@@ -6,11 +6,16 @@ import org.briarproject.bramble.api.sync.ClientId;
|
|||||||
import org.briarproject.bramble.api.sync.Group;
|
import org.briarproject.bramble.api.sync.Group;
|
||||||
import org.briarproject.bramble.api.sync.GroupFactory;
|
import org.briarproject.bramble.api.sync.GroupFactory;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
|
import org.briarproject.bramble.util.ByteUtils;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.sync.GroupId.LABEL;
|
||||||
|
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
|
||||||
|
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class GroupFactoryImpl implements GroupFactory {
|
class GroupFactoryImpl implements GroupFactory {
|
||||||
@@ -23,9 +28,12 @@ class GroupFactoryImpl implements GroupFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Group createGroup(ClientId c, byte[] descriptor) {
|
public Group createGroup(ClientId c, int clientVersion, byte[] descriptor) {
|
||||||
byte[] hash = crypto.hash(GroupId.LABEL,
|
byte[] clientVersionBytes = new byte[INT_32_BYTES];
|
||||||
StringUtils.toUtf8(c.getString()), descriptor);
|
ByteUtils.writeUint32(clientVersion, clientVersionBytes, 0);
|
||||||
|
byte[] hash = crypto.hash(LABEL, new byte[] {PROTOCOL_VERSION},
|
||||||
|
StringUtils.toUtf8(c.getString()), clientVersionBytes,
|
||||||
|
descriptor);
|
||||||
return new Group(new GroupId(hash), c, descriptor);
|
return new Group(new GroupId(hash), c, descriptor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,10 @@ import org.briarproject.bramble.util.ByteUtils;
|
|||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.sync.MessageId.LABEL;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||||
|
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
@@ -32,9 +34,9 @@ class MessageFactoryImpl implements MessageFactory {
|
|||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
byte[] timeBytes = new byte[ByteUtils.INT_64_BYTES];
|
byte[] timeBytes = new byte[ByteUtils.INT_64_BYTES];
|
||||||
ByteUtils.writeUint64(timestamp, timeBytes, 0);
|
ByteUtils.writeUint64(timestamp, timeBytes, 0);
|
||||||
byte[] idHash =
|
byte[] hash = crypto.hash(LABEL, new byte[] {PROTOCOL_VERSION},
|
||||||
crypto.hash(MessageId.LABEL, g.getBytes(), timeBytes, body);
|
g.getBytes(), timeBytes, body);
|
||||||
MessageId id = new MessageId(idHash);
|
MessageId id = new MessageId(hash);
|
||||||
byte[] raw = new byte[MESSAGE_HEADER_LENGTH + body.length];
|
byte[] raw = new byte[MESSAGE_HEADER_LENGTH + body.length];
|
||||||
System.arraycopy(g.getBytes(), 0, raw, 0, UniqueId.LENGTH);
|
System.arraycopy(g.getBytes(), 0, raw, 0, UniqueId.LENGTH);
|
||||||
ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH);
|
ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH);
|
||||||
|
|||||||
@@ -4,9 +4,8 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
|||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.api.system.Scheduler;
|
import org.briarproject.bramble.api.system.Scheduler;
|
||||||
|
|
||||||
import java.util.concurrent.RejectedExecutionHandler;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
@@ -26,10 +25,7 @@ public class SystemModule {
|
|||||||
private final ScheduledExecutorService scheduler;
|
private final ScheduledExecutorService scheduler;
|
||||||
|
|
||||||
public SystemModule() {
|
public SystemModule() {
|
||||||
// Discard tasks that are submitted during shutdown
|
scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||||
RejectedExecutionHandler policy =
|
|
||||||
new ScheduledThreadPoolExecutor.DiscardPolicy();
|
|
||||||
scheduler = new ScheduledThreadPoolExecutor(1, policy);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package org.briarproject.bramble.transport;
|
package org.briarproject.bramble.transport;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
@@ -20,17 +20,18 @@ class TransportKeyManagerFactoryImpl implements
|
|||||||
TransportKeyManagerFactory {
|
TransportKeyManagerFactory {
|
||||||
|
|
||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
private final CryptoComponent crypto;
|
private final TransportCrypto transportCrypto;
|
||||||
private final Executor dbExecutor;
|
private final Executor dbExecutor;
|
||||||
private final ScheduledExecutorService scheduler;
|
private final ScheduledExecutorService scheduler;
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
TransportKeyManagerFactoryImpl(DatabaseComponent db, CryptoComponent crypto,
|
TransportKeyManagerFactoryImpl(DatabaseComponent db,
|
||||||
|
TransportCrypto transportCrypto,
|
||||||
@DatabaseExecutor Executor dbExecutor,
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
@Scheduler ScheduledExecutorService scheduler, Clock clock) {
|
@Scheduler ScheduledExecutorService scheduler, Clock clock) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.crypto = crypto;
|
this.transportCrypto = transportCrypto;
|
||||||
this.dbExecutor = dbExecutor;
|
this.dbExecutor = dbExecutor;
|
||||||
this.scheduler = scheduler;
|
this.scheduler = scheduler;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
@@ -39,8 +40,8 @@ class TransportKeyManagerFactoryImpl implements
|
|||||||
@Override
|
@Override
|
||||||
public TransportKeyManager createTransportKeyManager(
|
public TransportKeyManager createTransportKeyManager(
|
||||||
TransportId transportId, long maxLatency) {
|
TransportId transportId, long maxLatency) {
|
||||||
return new TransportKeyManagerImpl(db, crypto, dbExecutor, scheduler,
|
return new TransportKeyManagerImpl(db, transportCrypto, dbExecutor,
|
||||||
clock, transportId, maxLatency);
|
scheduler, clock, transportId, maxLatency);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ package org.briarproject.bramble.transport;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.Bytes;
|
import org.briarproject.bramble.api.Bytes;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
@@ -41,7 +41,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
|||||||
Logger.getLogger(TransportKeyManagerImpl.class.getName());
|
Logger.getLogger(TransportKeyManagerImpl.class.getName());
|
||||||
|
|
||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
private final CryptoComponent crypto;
|
private final TransportCrypto transportCrypto;
|
||||||
private final Executor dbExecutor;
|
private final Executor dbExecutor;
|
||||||
private final ScheduledExecutorService scheduler;
|
private final ScheduledExecutorService scheduler;
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
@@ -54,11 +54,12 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
|||||||
private final Map<ContactId, MutableOutgoingKeys> outContexts;
|
private final Map<ContactId, MutableOutgoingKeys> outContexts;
|
||||||
private final Map<ContactId, MutableTransportKeys> keys;
|
private final Map<ContactId, MutableTransportKeys> keys;
|
||||||
|
|
||||||
TransportKeyManagerImpl(DatabaseComponent db, CryptoComponent crypto,
|
TransportKeyManagerImpl(DatabaseComponent db,
|
||||||
Executor dbExecutor, @Scheduler ScheduledExecutorService scheduler,
|
TransportCrypto transportCrypto, Executor dbExecutor,
|
||||||
Clock clock, TransportId transportId, long maxLatency) {
|
@Scheduler ScheduledExecutorService scheduler, Clock clock,
|
||||||
|
TransportId transportId, long maxLatency) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.crypto = crypto;
|
this.transportCrypto = transportCrypto;
|
||||||
this.dbExecutor = dbExecutor;
|
this.dbExecutor = dbExecutor;
|
||||||
this.scheduler = scheduler;
|
this.scheduler = scheduler;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
@@ -99,7 +100,8 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
|||||||
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();
|
||||||
TransportKeys k1 = crypto.rotateTransportKeys(k, rotationPeriod);
|
TransportKeys k1 =
|
||||||
|
transportCrypto.rotateTransportKeys(k, rotationPeriod);
|
||||||
if (k1.getRotationPeriod() > k.getRotationPeriod())
|
if (k1.getRotationPeriod() > k.getRotationPeriod())
|
||||||
rotationResult.rotated.put(c, k1);
|
rotationResult.rotated.put(c, k1);
|
||||||
rotationResult.current.put(c, k1);
|
rotationResult.current.put(c, k1);
|
||||||
@@ -127,7 +129,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
|||||||
for (long streamNumber : inKeys.getWindow().getUnseen()) {
|
for (long streamNumber : inKeys.getWindow().getUnseen()) {
|
||||||
TagContext tagCtx = new TagContext(c, inKeys, streamNumber);
|
TagContext tagCtx = new TagContext(c, inKeys, streamNumber);
|
||||||
byte[] tag = new byte[TAG_LENGTH];
|
byte[] tag = new byte[TAG_LENGTH];
|
||||||
crypto.encodeTag(tag, inKeys.getTagKey(), PROTOCOL_VERSION,
|
transportCrypto.encodeTag(tag, inKeys.getTagKey(), PROTOCOL_VERSION,
|
||||||
streamNumber);
|
streamNumber);
|
||||||
inContexts.put(new Bytes(tag), tagCtx);
|
inContexts.put(new Bytes(tag), tagCtx);
|
||||||
}
|
}
|
||||||
@@ -162,11 +164,11 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
|||||||
// Work out what rotation period the timestamp belongs to
|
// Work out what rotation period the timestamp belongs to
|
||||||
long rotationPeriod = timestamp / rotationPeriodLength;
|
long rotationPeriod = timestamp / rotationPeriodLength;
|
||||||
// Derive the transport keys
|
// Derive the transport keys
|
||||||
TransportKeys k = crypto.deriveTransportKeys(transportId, master,
|
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
||||||
rotationPeriod, alice);
|
master, rotationPeriod, alice);
|
||||||
// Rotate the keys to the current rotation period if necessary
|
// Rotate the keys to the current rotation period if necessary
|
||||||
rotationPeriod = clock.currentTimeMillis() / rotationPeriodLength;
|
rotationPeriod = clock.currentTimeMillis() / rotationPeriodLength;
|
||||||
k = crypto.rotateTransportKeys(k, rotationPeriod);
|
k = transportCrypto.rotateTransportKeys(k, rotationPeriod);
|
||||||
// Initialise mutable state for the contact
|
// Initialise mutable state for the contact
|
||||||
addKeys(c, new MutableTransportKeys(k));
|
addKeys(c, new MutableTransportKeys(k));
|
||||||
// Write the keys back to the DB
|
// Write the keys back to the DB
|
||||||
@@ -234,8 +236,8 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
|||||||
// Add tags for any stream numbers added to the window
|
// Add tags for any stream numbers added to the window
|
||||||
for (long streamNumber : change.getAdded()) {
|
for (long streamNumber : change.getAdded()) {
|
||||||
byte[] addTag = new byte[TAG_LENGTH];
|
byte[] addTag = new byte[TAG_LENGTH];
|
||||||
crypto.encodeTag(addTag, inKeys.getTagKey(), PROTOCOL_VERSION,
|
transportCrypto.encodeTag(addTag, inKeys.getTagKey(),
|
||||||
streamNumber);
|
PROTOCOL_VERSION, streamNumber);
|
||||||
inContexts.put(new Bytes(addTag), new TagContext(
|
inContexts.put(new Bytes(addTag), new TagContext(
|
||||||
tagCtx.contactId, inKeys, streamNumber));
|
tagCtx.contactId, inKeys, streamNumber));
|
||||||
}
|
}
|
||||||
@@ -243,7 +245,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
|
|||||||
for (long streamNumber : change.getRemoved()) {
|
for (long streamNumber : change.getRemoved()) {
|
||||||
if (streamNumber == tagCtx.streamNumber) continue;
|
if (streamNumber == tagCtx.streamNumber) continue;
|
||||||
byte[] removeTag = new byte[TAG_LENGTH];
|
byte[] removeTag = new byte[TAG_LENGTH];
|
||||||
crypto.encodeTag(removeTag, inKeys.getTagKey(),
|
transportCrypto.encodeTag(removeTag, inKeys.getTagKey(),
|
||||||
PROTOCOL_VERSION, streamNumber);
|
PROTOCOL_VERSION, streamNumber);
|
||||||
inContexts.remove(new Bytes(removeTag));
|
inContexts.remove(new Bytes(removeTag));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import org.briarproject.bramble.api.data.MetadataParser;
|
|||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
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.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||||
@@ -31,9 +33,14 @@ import java.security.GeneralSecurityException;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
|
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
@@ -54,7 +61,8 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
context.mock(MetadataEncoder.class);
|
context.mock(MetadataEncoder.class);
|
||||||
private final CryptoComponent cryptoComponent =
|
private final CryptoComponent cryptoComponent =
|
||||||
context.mock(CryptoComponent.class);
|
context.mock(CryptoComponent.class);
|
||||||
private final ClientHelper clientHelper;
|
private final AuthorFactory authorFactory =
|
||||||
|
context.mock(AuthorFactory.class);
|
||||||
|
|
||||||
private final GroupId groupId = new GroupId(getRandomId());
|
private final GroupId groupId = new GroupId(getRandomId());
|
||||||
private final BdfDictionary dictionary = new BdfDictionary();
|
private final BdfDictionary dictionary = new BdfDictionary();
|
||||||
@@ -66,17 +74,15 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
private final Metadata metadata = new Metadata();
|
private final Metadata metadata = new Metadata();
|
||||||
private final BdfList list = BdfList.of("Sign this!", getRandomBytes(42));
|
private final BdfList list = BdfList.of("Sign this!", getRandomBytes(42));
|
||||||
private final String label = StringUtils.getRandomString(5);
|
private final String label = StringUtils.getRandomString(5);
|
||||||
|
private final Author author = getAuthor();
|
||||||
|
|
||||||
public ClientHelperImplTest() {
|
private final ClientHelper clientHelper = new ClientHelperImpl(db,
|
||||||
clientHelper =
|
messageFactory, bdfReaderFactory, bdfWriterFactory, metadataParser,
|
||||||
new ClientHelperImpl(db, messageFactory, bdfReaderFactory,
|
metadataEncoder, cryptoComponent, authorFactory);
|
||||||
bdfWriterFactory, metadataParser, metadataEncoder,
|
|
||||||
cryptoComponent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAddLocalMessage() throws Exception {
|
public void testAddLocalMessage() throws Exception {
|
||||||
boolean shared = true;
|
boolean shared = new Random().nextBoolean();
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
@@ -180,8 +186,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
oneOf(db).endTransaction(txn);
|
oneOf(db).endTransaction(txn);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
assertEquals(map,
|
assertEquals(map, clientHelper.getMessageMetadataAsDictionary(groupId));
|
||||||
clientHelper.getMessageMetadataAsDictionary(groupId));
|
|
||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,8 +323,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
clientHelper
|
clientHelper.verifySignature(label, rawMessage, publicKey, list);
|
||||||
.verifySignature(label, rawMessage, publicKey, list);
|
|
||||||
fail();
|
fail();
|
||||||
} catch (GeneralSecurityException e) {
|
} catch (GeneralSecurityException e) {
|
||||||
// expected
|
// expected
|
||||||
@@ -327,6 +331,166 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParsesAndEncodesAuthor() throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(authorFactory).createAuthor(author.getFormatVersion(),
|
||||||
|
author.getName(), author.getPublicKey());
|
||||||
|
will(returnValue(author));
|
||||||
|
}});
|
||||||
|
|
||||||
|
BdfList authorList = clientHelper.toList(author);
|
||||||
|
assertEquals(author, clientHelper.parseAndValidateAuthor(authorList));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAcceptsValidAuthor() throws Exception {
|
||||||
|
BdfList authorList = BdfList.of(
|
||||||
|
author.getFormatVersion(),
|
||||||
|
author.getName(),
|
||||||
|
author.getPublicKey()
|
||||||
|
);
|
||||||
|
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(authorFactory).createAuthor(author.getFormatVersion(),
|
||||||
|
author.getName(), author.getPublicKey());
|
||||||
|
will(returnValue(author));
|
||||||
|
}});
|
||||||
|
|
||||||
|
assertEquals(author, clientHelper.parseAndValidateAuthor(authorList));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testRejectsTooShortAuthor() throws Exception {
|
||||||
|
BdfList invalidAuthor = BdfList.of(
|
||||||
|
author.getFormatVersion(),
|
||||||
|
author.getName()
|
||||||
|
);
|
||||||
|
clientHelper.parseAndValidateAuthor(invalidAuthor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testRejectsTooLongAuthor() throws Exception {
|
||||||
|
BdfList invalidAuthor = BdfList.of(
|
||||||
|
author.getFormatVersion(),
|
||||||
|
author.getName(),
|
||||||
|
author.getPublicKey(),
|
||||||
|
"foo"
|
||||||
|
);
|
||||||
|
clientHelper.parseAndValidateAuthor(invalidAuthor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testRejectsAuthorWithNullFormatVersion() throws Exception {
|
||||||
|
BdfList invalidAuthor = BdfList.of(
|
||||||
|
null,
|
||||||
|
author.getName(),
|
||||||
|
author.getPublicKey()
|
||||||
|
);
|
||||||
|
clientHelper.parseAndValidateAuthor(invalidAuthor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testRejectsAuthorWithNonIntegerFormatVersion()
|
||||||
|
throws Exception {
|
||||||
|
BdfList invalidAuthor = BdfList.of(
|
||||||
|
"foo",
|
||||||
|
author.getName(),
|
||||||
|
author.getPublicKey()
|
||||||
|
);
|
||||||
|
clientHelper.parseAndValidateAuthor(invalidAuthor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testRejectsAuthorWithUnknownFormatVersion() throws Exception {
|
||||||
|
BdfList invalidAuthor = BdfList.of(
|
||||||
|
author.getFormatVersion() + 1,
|
||||||
|
author.getName(),
|
||||||
|
author.getPublicKey()
|
||||||
|
);
|
||||||
|
clientHelper.parseAndValidateAuthor(invalidAuthor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testRejectsAuthorWithTooShortName() throws Exception {
|
||||||
|
BdfList invalidAuthor = BdfList.of(
|
||||||
|
author.getFormatVersion(),
|
||||||
|
"",
|
||||||
|
author.getPublicKey()
|
||||||
|
);
|
||||||
|
clientHelper.parseAndValidateAuthor(invalidAuthor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testRejectsAuthorWithTooLongName() throws Exception {
|
||||||
|
BdfList invalidAuthor = BdfList.of(
|
||||||
|
author.getFormatVersion(),
|
||||||
|
getRandomString(MAX_AUTHOR_NAME_LENGTH + 1),
|
||||||
|
author.getPublicKey()
|
||||||
|
);
|
||||||
|
clientHelper.parseAndValidateAuthor(invalidAuthor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testRejectsAuthorWithNullName() throws Exception {
|
||||||
|
BdfList invalidAuthor = BdfList.of(
|
||||||
|
author.getFormatVersion(),
|
||||||
|
null,
|
||||||
|
author.getPublicKey()
|
||||||
|
);
|
||||||
|
clientHelper.parseAndValidateAuthor(invalidAuthor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testRejectsAuthorWithNonStringName() throws Exception {
|
||||||
|
BdfList invalidAuthor = BdfList.of(
|
||||||
|
author.getFormatVersion(),
|
||||||
|
getRandomBytes(5),
|
||||||
|
author.getPublicKey()
|
||||||
|
);
|
||||||
|
clientHelper.parseAndValidateAuthor(invalidAuthor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testRejectsAuthorWithTooShortPublicKey() throws Exception {
|
||||||
|
BdfList invalidAuthor = BdfList.of(
|
||||||
|
author.getFormatVersion(),
|
||||||
|
author.getName(),
|
||||||
|
new byte[0]
|
||||||
|
);
|
||||||
|
clientHelper.parseAndValidateAuthor(invalidAuthor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testRejectsAuthorWithTooLongPublicKey() throws Exception {
|
||||||
|
BdfList invalidAuthor = BdfList.of(
|
||||||
|
author.getFormatVersion(),
|
||||||
|
author.getName(),
|
||||||
|
getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1)
|
||||||
|
);
|
||||||
|
clientHelper.parseAndValidateAuthor(invalidAuthor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testRejectsAuthorWithNullPublicKey() throws Exception {
|
||||||
|
BdfList invalidAuthor = BdfList.of(
|
||||||
|
author.getFormatVersion(),
|
||||||
|
author.getName(),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
clientHelper.parseAndValidateAuthor(invalidAuthor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testRejectsAuthorWithNonRawPublicKey() throws Exception {
|
||||||
|
BdfList invalidAuthor = BdfList.of(
|
||||||
|
author.getFormatVersion(),
|
||||||
|
author.getName(),
|
||||||
|
"foo"
|
||||||
|
);
|
||||||
|
clientHelper.parseAndValidateAuthor(invalidAuthor);
|
||||||
|
}
|
||||||
|
|
||||||
private byte[] expectToByteArray(BdfList list) throws Exception {
|
private byte[] expectToByteArray(BdfList list) throws Exception {
|
||||||
BdfWriter bdfWriter = context.mock(BdfWriter.class);
|
BdfWriter bdfWriter = context.mock(BdfWriter.class);
|
||||||
|
|
||||||
@@ -352,5 +516,4 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
will(returnValue(eof));
|
will(returnValue(eof));
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ import org.junit.Test;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
@@ -32,9 +33,7 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
|||||||
private final KeyManager keyManager = context.mock(KeyManager.class);
|
private final KeyManager keyManager = context.mock(KeyManager.class);
|
||||||
private final ContactManager contactManager;
|
private final ContactManager contactManager;
|
||||||
private final ContactId contactId = new ContactId(42);
|
private final ContactId contactId = new ContactId(42);
|
||||||
private final Author remote =
|
private final Author remote = getAuthor();
|
||||||
new Author(new AuthorId(getRandomId()), "remote",
|
|
||||||
getRandomBytes(42));
|
|
||||||
private final AuthorId local = new AuthorId(getRandomId());
|
private final AuthorId local = new AuthorId(getRandomId());
|
||||||
private final boolean verified = false, active = true;
|
private final boolean verified = false, active = true;
|
||||||
private final Contact contact =
|
private final Contact contact =
|
||||||
@@ -47,8 +46,8 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testAddContact() throws Exception {
|
public void testAddContact() throws Exception {
|
||||||
SecretKey master = getSecretKey();
|
SecretKey master = getSecretKey();
|
||||||
long timestamp = 42;
|
long timestamp = System.currentTimeMillis();
|
||||||
boolean alice = true;
|
boolean alice = new Random().nextBoolean();
|
||||||
Transaction txn = new Transaction(null, false);
|
Transaction txn = new Transaction(null, false);
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
|
|||||||
@@ -3,75 +3,76 @@ package org.briarproject.bramble.crypto;
|
|||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.spongycastle.crypto.digests.Blake2bDigest;
|
||||||
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
|
||||||
public class Blake2sDigestTest extends BrambleTestCase {
|
public class Blake2bDigestTest extends BrambleTestCase {
|
||||||
|
|
||||||
// Vectors from BLAKE2 web site: https://blake2.net/blake2s-test.txt
|
// Vectors from BLAKE2 web site: https://blake2.net/Blake2b-test.txt
|
||||||
private static final String[][] keyedTestVectors = {
|
private static final String[][] KEYED_TEST_VECTORS = {
|
||||||
// input/message, key, hash
|
// input/message, key, hash
|
||||||
{
|
{
|
||||||
"",
|
"",
|
||||||
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
|
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
|
||||||
"48a8997da407876b3d79c0d92325ad3b89cbb754d86ab71aee047ad345fd2c49",
|
"10ebb67700b1868efb4417987acf4690ae9d972fb7a590c2f02871799aaa4786b5e996e8f0f4eb981fc214b005f42d2ff4233499391653df7aefcbc13fc51568",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"00",
|
"00",
|
||||||
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
|
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
|
||||||
"40d15fee7c328830166ac3f918650f807e7e01e177258cdc0a39b11f598066f1",
|
"961f6dd1e4dd30f63901690c512e78e4b45e4742ed197c3c5e45c549fd25f2e4187b0bc9fe30492b16b0d0bc4ef9b0f34c7003fac09a5ef1532e69430234cebd",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"0001",
|
"0001",
|
||||||
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
|
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
|
||||||
"6bb71300644cd3991b26ccd4d274acd1adeab8b1d7914546c1198bbe9fc9d803",
|
"da2cfbe2d8409a0f38026113884f84b50156371ae304c4430173d08a99d9fb1b983164a3770706d537f49e0c916d9f32b95cc37a95b99d857436f0232c88a965",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d",
|
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d",
|
||||||
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
|
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
|
||||||
"172ffc67153d12e0ca76a8b6cd5d4731885b39ce0cac93a8972a18006c8b8baf",
|
"f1aa2b044f8f0c638a3f362e677b5d891d6fd2ab0765f6ee1e4987de057ead357883d9b405b9d609eea1b869d97fb16d9b51017c553f3b93c0a1e0f1296fedcd",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3",
|
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3",
|
||||||
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
|
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
|
||||||
"4f8ce1e51d2fe7f24043a904d898ebfc91975418753413aa099b795ecb35cedb",
|
"c230f0802679cb33822ef8b3b21bf7a9a28942092901d7dac3760300831026cf354c9232df3e084d9903130c601f63c1f4a4a4b8106e468cd443bbe5a734f45f",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe",
|
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfe",
|
||||||
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
|
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
|
||||||
"3fb735061abc519dfe979e54c1ee5bfad0a9d858b3315bad34bde999efd724dd",
|
"142709d62e28fcccd0af97fad0f8465b971e82201dc51070faa0372aa43e92484be1c1e73ba10906d5d1853db6a4106e0a7bf9800d373d6dee2d46d62ef2a461",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDigestWithKeyedTestVectors() {
|
public void testDigestWithKeyedTestVectors() {
|
||||||
Blake2sDigest digest = new Blake2sDigest(StringUtils.fromHexString(
|
for (String[] keyedTestVector : KEYED_TEST_VECTORS) {
|
||||||
keyedTestVectors[0][1]));
|
|
||||||
for (String[] keyedTestVector : keyedTestVectors) {
|
|
||||||
byte[] input = StringUtils.fromHexString(keyedTestVector[0]);
|
byte[] input = StringUtils.fromHexString(keyedTestVector[0]);
|
||||||
digest.reset();
|
byte[] key = StringUtils.fromHexString(keyedTestVector[1]);
|
||||||
|
byte[] expected = StringUtils.fromHexString(keyedTestVector[2]);
|
||||||
|
|
||||||
|
Blake2bDigest digest = new Blake2bDigest(key);
|
||||||
digest.update(input, 0, input.length);
|
digest.update(input, 0, input.length);
|
||||||
byte[] hash = new byte[32];
|
byte[] hash = new byte[64];
|
||||||
digest.doFinal(hash, 0);
|
digest.doFinal(hash, 0);
|
||||||
|
|
||||||
assertArrayEquals(StringUtils.fromHexString(keyedTestVector[2]),
|
assertArrayEquals(expected, hash);
|
||||||
hash);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDigestWithKeyedTestVectorsAndRandomUpdate() {
|
public void testDigestWithKeyedTestVectorsAndRandomUpdate() {
|
||||||
Blake2sDigest digest = new Blake2sDigest(StringUtils.fromHexString(
|
|
||||||
keyedTestVectors[0][1]));
|
|
||||||
Random random = new Random();
|
Random random = new Random();
|
||||||
for (int i = 0; i < 100; i++) {
|
for (int i = 0; i < 100; i++) {
|
||||||
for (String[] keyedTestVector : keyedTestVectors) {
|
for (String[] keyedTestVector : KEYED_TEST_VECTORS) {
|
||||||
byte[] input = StringUtils.fromHexString(keyedTestVector[0]);
|
byte[] input = StringUtils.fromHexString(keyedTestVector[0]);
|
||||||
if (input.length < 3) continue;
|
if (input.length == 0) continue;
|
||||||
digest.reset();
|
byte[] key = StringUtils.fromHexString(keyedTestVector[1]);
|
||||||
|
byte[] expected = StringUtils.fromHexString(keyedTestVector[2]);
|
||||||
|
|
||||||
|
Blake2bDigest digest = new Blake2bDigest(key);
|
||||||
|
|
||||||
int pos = random.nextInt(input.length);
|
int pos = random.nextInt(input.length);
|
||||||
if (pos > 0)
|
if (pos > 0)
|
||||||
@@ -80,11 +81,10 @@ public class Blake2sDigestTest extends BrambleTestCase {
|
|||||||
if (pos < (input.length - 1))
|
if (pos < (input.length - 1))
|
||||||
digest.update(input, pos + 1, input.length - (pos + 1));
|
digest.update(input, pos + 1, input.length - (pos + 1));
|
||||||
|
|
||||||
byte[] hash = new byte[32];
|
byte[] hash = new byte[64];
|
||||||
digest.doFinal(hash, 0);
|
digest.doFinal(hash, 0);
|
||||||
|
|
||||||
assertArrayEquals(StringUtils.fromHexString(keyedTestVector[2]),
|
assertArrayEquals(expected, hash);
|
||||||
hash);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,12 +98,12 @@ public class Blake2sDigestTest extends BrambleTestCase {
|
|||||||
byte[] input = new byte[key.length + 1];
|
byte[] input = new byte[key.length + 1];
|
||||||
for (byte i = 0; i < input.length; i++) input[i] = i;
|
for (byte i = 0; i < input.length; i++) input[i] = i;
|
||||||
// Hash the input
|
// Hash the input
|
||||||
Blake2sDigest digest = new Blake2sDigest(key);
|
Blake2bDigest digest = new Blake2bDigest(key);
|
||||||
digest.update(input, 0, input.length);
|
digest.update(input, 0, input.length);
|
||||||
byte[] hash = new byte[digest.getDigestSize()];
|
byte[] hash = new byte[digest.getDigestSize()];
|
||||||
digest.doFinal(hash, 0);
|
digest.doFinal(hash, 0);
|
||||||
// Create a second instance, hash the input without calling doFinal()
|
// Create a second instance, hash the input without calling doFinal()
|
||||||
Blake2sDigest digest1 = new Blake2sDigest(key);
|
Blake2bDigest digest1 = new Blake2bDigest(key);
|
||||||
digest1.update(input, 0, input.length);
|
digest1.update(input, 0, input.length);
|
||||||
// Reset the second instance and hash the input again
|
// Reset the second instance and hash the input again
|
||||||
digest1.reset();
|
digest1.reset();
|
||||||
@@ -116,9 +116,10 @@ public class Blake2sDigestTest extends BrambleTestCase {
|
|||||||
|
|
||||||
// Self-test routine from https://tools.ietf.org/html/rfc7693#appendix-E
|
// Self-test routine from https://tools.ietf.org/html/rfc7693#appendix-E
|
||||||
private static final String SELF_TEST_RESULT =
|
private static final String SELF_TEST_RESULT =
|
||||||
"6A411F08CE25ADCDFB02ABA641451CEC53C598B24F4FC787FBDC88797F4C1DFE";
|
"C23A7800D98123BD10F506C61E29DA5603D763B8BBAD2E737F5E765A7BCCD475";
|
||||||
private static final int[] SELF_TEST_DIGEST_LEN = {16, 20, 28, 32};
|
private static final int[] SELF_TEST_DIGEST_LEN = {20, 32, 48, 64};
|
||||||
private static final int[] SELF_TEST_INPUT_LEN = {0, 3, 64, 65, 255, 1024};
|
private static final int[] SELF_TEST_INPUT_LEN =
|
||||||
|
{0, 3, 128, 129, 255, 1024};
|
||||||
|
|
||||||
private static byte[] selfTestSequence(int len, int seed) {
|
private static byte[] selfTestSequence(int len, int seed) {
|
||||||
int a = 0xDEAD4BAD * seed;
|
int a = 0xDEAD4BAD * seed;
|
||||||
@@ -138,8 +139,8 @@ public class Blake2sDigestTest extends BrambleTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void runSelfTest() {
|
public void runSelfTest() {
|
||||||
Blake2sDigest testDigest = new Blake2sDigest();
|
Blake2bDigest testDigest = new Blake2bDigest(256);
|
||||||
byte[] md = new byte[32];
|
byte[] md = new byte[64];
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
int outlen = SELF_TEST_DIGEST_LEN[i];
|
int outlen = SELF_TEST_DIGEST_LEN[i];
|
||||||
@@ -148,7 +149,7 @@ public class Blake2sDigestTest extends BrambleTestCase {
|
|||||||
|
|
||||||
// unkeyed hash
|
// unkeyed hash
|
||||||
byte[] in = selfTestSequence(inlen, inlen);
|
byte[] in = selfTestSequence(inlen, inlen);
|
||||||
Blake2sDigest unkeyedDigest = new Blake2sDigest(outlen * 8);
|
Blake2bDigest unkeyedDigest = new Blake2bDigest(outlen * 8);
|
||||||
unkeyedDigest.update(in, 0, inlen);
|
unkeyedDigest.update(in, 0, inlen);
|
||||||
unkeyedDigest.doFinal(md, 0);
|
unkeyedDigest.doFinal(md, 0);
|
||||||
// hash the hash
|
// hash the hash
|
||||||
@@ -156,7 +157,7 @@ public class Blake2sDigestTest extends BrambleTestCase {
|
|||||||
|
|
||||||
// keyed hash
|
// keyed hash
|
||||||
byte[] key = selfTestSequence(outlen, outlen);
|
byte[] key = selfTestSequence(outlen, outlen);
|
||||||
Blake2sDigest keyedDigest = new Blake2sDigest(key, outlen, null,
|
Blake2bDigest keyedDigest = new Blake2bDigest(key, outlen, null,
|
||||||
null);
|
null);
|
||||||
keyedDigest.update(in, 0, inlen);
|
keyedDigest.update(in, 0, inlen);
|
||||||
keyedDigest.doFinal(md, 0);
|
keyedDigest.doFinal(md, 0);
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
package org.briarproject.bramble.crypto;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.fromHexString;
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class EdSignatureTest extends SignatureTest {
|
||||||
|
|
||||||
|
// Test vectors from RFC 8032: secret key, public key, message, signature
|
||||||
|
// https://tools.ietf.org/html/rfc8032#section-7.1
|
||||||
|
private static final String[][] TEST_VECTORS = {{
|
||||||
|
"9d61b19deffd5a60ba844af492ec2cc4" +
|
||||||
|
"4449c5697b326919703bac031cae7f60",
|
||||||
|
"d75a980182b10ab7d54bfed3c964073a" +
|
||||||
|
"0ee172f3daa62325af021a68f707511a",
|
||||||
|
"",
|
||||||
|
"e5564300c360ac729086e2cc806e828a" +
|
||||||
|
"84877f1eb8e5d974d873e06522490155" +
|
||||||
|
"5fb8821590a33bacc61e39701cf9b46b" +
|
||||||
|
"d25bf5f0595bbe24655141438e7a100b"
|
||||||
|
}, {
|
||||||
|
"4ccd089b28ff96da9db6c346ec114e0f" +
|
||||||
|
"5b8a319f35aba624da8cf6ed4fb8a6fb",
|
||||||
|
"3d4017c3e843895a92b70aa74d1b7ebc" +
|
||||||
|
"9c982ccf2ec4968cc0cd55f12af4660c",
|
||||||
|
"72",
|
||||||
|
"92a009a9f0d4cab8720e820b5f642540" +
|
||||||
|
"a2b27b5416503f8fb3762223ebdb69da" +
|
||||||
|
"085ac1e43e15996e458f3613d0f11d8c" +
|
||||||
|
"387b2eaeb4302aeeb00d291612bb0c00"
|
||||||
|
}, {
|
||||||
|
"c5aa8df43f9f837bedb7442f31dcb7b1" +
|
||||||
|
"66d38535076f094b85ce3a2e0b4458f7",
|
||||||
|
"fc51cd8e6218a1a38da47ed00230f058" +
|
||||||
|
"0816ed13ba3303ac5deb911548908025",
|
||||||
|
"af82",
|
||||||
|
"6291d657deec24024827e69c3abe01a3" +
|
||||||
|
"0ce548a284743a445e3680d7db5ac3ac" +
|
||||||
|
"18ff9b538d16f290ae67f760984dc659" +
|
||||||
|
"4a7c15e9716ed28dc027beceea1ec40a"
|
||||||
|
}, {
|
||||||
|
"f5e5767cf153319517630f226876b86c" +
|
||||||
|
"8160cc583bc013744c6bf255f5cc0ee5",
|
||||||
|
"278117fc144c72340f67d0f2316e8386" +
|
||||||
|
"ceffbf2b2428c9c51fef7c597f1d426e",
|
||||||
|
"08b8b2b733424243760fe426a4b54908" +
|
||||||
|
"632110a66c2f6591eabd3345e3e4eb98" +
|
||||||
|
"fa6e264bf09efe12ee50f8f54e9f77b1" +
|
||||||
|
"e355f6c50544e23fb1433ddf73be84d8" +
|
||||||
|
"79de7c0046dc4996d9e773f4bc9efe57" +
|
||||||
|
"38829adb26c81b37c93a1b270b20329d" +
|
||||||
|
"658675fc6ea534e0810a4432826bf58c" +
|
||||||
|
"941efb65d57a338bbd2e26640f89ffbc" +
|
||||||
|
"1a858efcb8550ee3a5e1998bd177e93a" +
|
||||||
|
"7363c344fe6b199ee5d02e82d522c4fe" +
|
||||||
|
"ba15452f80288a821a579116ec6dad2b" +
|
||||||
|
"3b310da903401aa62100ab5d1a36553e" +
|
||||||
|
"06203b33890cc9b832f79ef80560ccb9" +
|
||||||
|
"a39ce767967ed628c6ad573cb116dbef" +
|
||||||
|
"efd75499da96bd68a8a97b928a8bbc10" +
|
||||||
|
"3b6621fcde2beca1231d206be6cd9ec7" +
|
||||||
|
"aff6f6c94fcd7204ed3455c68c83f4a4" +
|
||||||
|
"1da4af2b74ef5c53f1d8ac70bdcb7ed1" +
|
||||||
|
"85ce81bd84359d44254d95629e9855a9" +
|
||||||
|
"4a7c1958d1f8ada5d0532ed8a5aa3fb2" +
|
||||||
|
"d17ba70eb6248e594e1a2297acbbb39d" +
|
||||||
|
"502f1a8c6eb6f1ce22b3de1a1f40cc24" +
|
||||||
|
"554119a831a9aad6079cad88425de6bd" +
|
||||||
|
"e1a9187ebb6092cf67bf2b13fd65f270" +
|
||||||
|
"88d78b7e883c8759d2c4f5c65adb7553" +
|
||||||
|
"878ad575f9fad878e80a0c9ba63bcbcc" +
|
||||||
|
"2732e69485bbc9c90bfbd62481d9089b" +
|
||||||
|
"eccf80cfe2df16a2cf65bd92dd597b07" +
|
||||||
|
"07e0917af48bbb75fed413d238f5555a" +
|
||||||
|
"7a569d80c3414a8d0859dc65a46128ba" +
|
||||||
|
"b27af87a71314f318c782b23ebfe808b" +
|
||||||
|
"82b0ce26401d2e22f04d83d1255dc51a" +
|
||||||
|
"ddd3b75a2b1ae0784504df543af8969b" +
|
||||||
|
"e3ea7082ff7fc9888c144da2af58429e" +
|
||||||
|
"c96031dbcad3dad9af0dcbaaaf268cb8" +
|
||||||
|
"fcffead94f3c7ca495e056a9b47acdb7" +
|
||||||
|
"51fb73e666c6c655ade8297297d07ad1" +
|
||||||
|
"ba5e43f1bca32301651339e22904cc8c" +
|
||||||
|
"42f58c30c04aafdb038dda0847dd988d" +
|
||||||
|
"cda6f3bfd15c4b4c4525004aa06eeff8" +
|
||||||
|
"ca61783aacec57fb3d1f92b0fe2fd1a8" +
|
||||||
|
"5f6724517b65e614ad6808d6f6ee34df" +
|
||||||
|
"f7310fdc82aebfd904b01e1dc54b2927" +
|
||||||
|
"094b2db68d6f903b68401adebf5a7e08" +
|
||||||
|
"d78ff4ef5d63653a65040cf9bfd4aca7" +
|
||||||
|
"984a74d37145986780fc0b16ac451649" +
|
||||||
|
"de6188a7dbdf191f64b5fc5e2ab47b57" +
|
||||||
|
"f7f7276cd419c17a3ca8e1b939ae49e4" +
|
||||||
|
"88acba6b965610b5480109c8b17b80e1" +
|
||||||
|
"b7b750dfc7598d5d5011fd2dcc5600a3" +
|
||||||
|
"2ef5b52a1ecc820e308aa342721aac09" +
|
||||||
|
"43bf6686b64b2579376504ccc493d97e" +
|
||||||
|
"6aed3fb0f9cd71a43dd497f01f17c0e2" +
|
||||||
|
"cb3797aa2a2f256656168e6c496afc5f" +
|
||||||
|
"b93246f6b1116398a346f1a641f3b041" +
|
||||||
|
"e989f7914f90cc2c7fff357876e506b5" +
|
||||||
|
"0d334ba77c225bc307ba537152f3f161" +
|
||||||
|
"0e4eafe595f6d9d90d11faa933a15ef1" +
|
||||||
|
"369546868a7f3a45a96768d40fd9d034" +
|
||||||
|
"12c091c6315cf4fde7cb68606937380d" +
|
||||||
|
"b2eaaa707b4c4185c32eddcdd306705e" +
|
||||||
|
"4dc1ffc872eeee475a64dfac86aba41c" +
|
||||||
|
"0618983f8741c5ef68d3a101e8a3b8ca" +
|
||||||
|
"c60c905c15fc910840b94c00a0b9d0",
|
||||||
|
"0aab4c900501b3e24d7cdf4663326a3a" +
|
||||||
|
"87df5e4843b2cbdb67cbf6e460fec350" +
|
||||||
|
"aa5371b1508f9f4528ecea23c436d94b" +
|
||||||
|
"5e8fcd4f681e30a6ac00a9704a188a03"
|
||||||
|
}, {
|
||||||
|
"833fe62409237b9d62ec77587520911e" +
|
||||||
|
"9a759cec1d19755b7da901b96dca3d42",
|
||||||
|
"ec172b93ad5e563bf4932c70e1245034" +
|
||||||
|
"c35467ef2efd4d64ebf819683467e2bf",
|
||||||
|
"ddaf35a193617abacc417349ae204131" +
|
||||||
|
"12e6fa4e89a97ea20a9eeee64b55d39a" +
|
||||||
|
"2192992a274fc1a836ba3c23a3feebbd" +
|
||||||
|
"454d4423643ce80e2a9ac94fa54ca49f",
|
||||||
|
"dc2a4459e7369633a52b1bf277839a00" +
|
||||||
|
"201009a3efbf3ecb69bea2186c26b589" +
|
||||||
|
"09351fc9ac90b3ecfdfbc7c66431e030" +
|
||||||
|
"3dca179c138ac17ad9bef1177331a704"
|
||||||
|
}};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected KeyPair generateKeyPair() {
|
||||||
|
return crypto.generateSignatureKeyPair();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected byte[] sign(String label, byte[] toSign, byte[] privateKey)
|
||||||
|
throws GeneralSecurityException {
|
||||||
|
return crypto.sign(label, toSign, privateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean verify(String label, byte[] signedData, byte[] publicKey,
|
||||||
|
byte[] signature) throws GeneralSecurityException {
|
||||||
|
return crypto.verify(label, signedData, publicKey, signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRfc8032TestVectors() throws Exception {
|
||||||
|
for (String[] vector : TEST_VECTORS) {
|
||||||
|
byte[] privateKeyBytes = fromHexString(vector[0]);
|
||||||
|
byte[] publicKeyBytes = fromHexString(vector[1]);
|
||||||
|
byte[] messageBytes = fromHexString(vector[2]);
|
||||||
|
byte[] signatureBytes = fromHexString(vector[3]);
|
||||||
|
|
||||||
|
EdSignature signature = new EdSignature();
|
||||||
|
signature.initSign(new EdPrivateKey(privateKeyBytes));
|
||||||
|
signature.update(messageBytes);
|
||||||
|
assertArrayEquals(signatureBytes, signature.sign());
|
||||||
|
|
||||||
|
signature.initVerify(new EdPublicKey(publicKeyBytes));
|
||||||
|
signature.update(messageBytes);
|
||||||
|
assertTrue(signature.verify(signatureBytes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,11 +13,11 @@ import org.spongycastle.crypto.params.ECPrivateKeyParameters;
|
|||||||
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
||||||
import org.spongycastle.math.ec.ECCurve;
|
import org.spongycastle.math.ec.ECCurve;
|
||||||
import org.spongycastle.math.ec.ECPoint;
|
import org.spongycastle.math.ec.ECPoint;
|
||||||
|
import org.spongycastle.math.ec.MontgomeryLadderMultiplier;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
import static org.briarproject.bramble.crypto.EllipticCurveConstants.PARAMETERS;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
public class EllipticCurveMultiplicationTest extends BrambleTestCase {
|
public class EllipticCurveMultiplicationTest extends BrambleTestCase {
|
||||||
@@ -31,15 +31,11 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase {
|
|||||||
ECPoint defaultG = defaultX9Parameters.getG();
|
ECPoint defaultG = defaultX9Parameters.getG();
|
||||||
BigInteger defaultN = defaultX9Parameters.getN();
|
BigInteger defaultN = defaultX9Parameters.getN();
|
||||||
BigInteger defaultH = defaultX9Parameters.getH();
|
BigInteger defaultH = defaultX9Parameters.getH();
|
||||||
// Check that the default parameters are equal to our parameters
|
|
||||||
assertEquals(PARAMETERS.getCurve(), defaultCurve);
|
|
||||||
assertEquals(PARAMETERS.getG(), defaultG);
|
|
||||||
assertEquals(PARAMETERS.getN(), defaultN);
|
|
||||||
assertEquals(PARAMETERS.getH(), defaultH);
|
|
||||||
// ECDomainParameters doesn't have an equals() method, but it's just a
|
|
||||||
// container for the parameters
|
|
||||||
ECDomainParameters defaultParameters = new ECDomainParameters(
|
ECDomainParameters defaultParameters = new ECDomainParameters(
|
||||||
defaultCurve, defaultG, defaultN, defaultH);
|
defaultCurve, defaultG, defaultN, defaultH);
|
||||||
|
// Instantiate an implementation using the Montgomery ladder multiplier
|
||||||
|
ECDomainParameters montgomeryParameters =
|
||||||
|
constantTime(defaultParameters);
|
||||||
// Generate two key pairs with each set of parameters, using the same
|
// Generate two key pairs with each set of parameters, using the same
|
||||||
// deterministic PRNG for both sets of parameters
|
// deterministic PRNG for both sets of parameters
|
||||||
byte[] seed = new byte[32];
|
byte[] seed = new byte[32];
|
||||||
@@ -47,7 +43,7 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase {
|
|||||||
// Montgomery ladder multiplier
|
// Montgomery ladder multiplier
|
||||||
SecureRandom random = new PseudoSecureRandom(seed);
|
SecureRandom random = new PseudoSecureRandom(seed);
|
||||||
ECKeyGenerationParameters montgomeryGeneratorParams =
|
ECKeyGenerationParameters montgomeryGeneratorParams =
|
||||||
new ECKeyGenerationParameters(PARAMETERS, random);
|
new ECKeyGenerationParameters(montgomeryParameters, random);
|
||||||
ECKeyPairGenerator montgomeryGenerator = new ECKeyPairGenerator();
|
ECKeyPairGenerator montgomeryGenerator = new ECKeyPairGenerator();
|
||||||
montgomeryGenerator.init(montgomeryGeneratorParams);
|
montgomeryGenerator.init(montgomeryGeneratorParams);
|
||||||
AsymmetricCipherKeyPair montgomeryKeyPair1 =
|
AsymmetricCipherKeyPair montgomeryKeyPair1 =
|
||||||
@@ -107,4 +103,13 @@ public class EllipticCurveMultiplicationTest extends BrambleTestCase {
|
|||||||
assertEquals(sharedSecretMontgomeryMontgomery,
|
assertEquals(sharedSecretMontgomeryMontgomery,
|
||||||
sharedSecretDefaultDefault);
|
sharedSecretDefaultDefault);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ECDomainParameters constantTime(ECDomainParameters in) {
|
||||||
|
ECCurve curve = in.getCurve().configure().setMultiplier(
|
||||||
|
new MontgomeryLadderMultiplier()).create();
|
||||||
|
BigInteger x = in.getG().getAffineXCoord().toBigInteger();
|
||||||
|
BigInteger y = in.getG().getAffineYCoord().toBigInteger();
|
||||||
|
ECPoint g = curve.createPoint(x, y);
|
||||||
|
return new ECDomainParameters(curve, g, in.getN(), in.getH());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
package org.briarproject.bramble.crypto;
|
package org.briarproject.bramble.crypto;
|
||||||
|
|
||||||
|
import net.i2p.crypto.eddsa.EdDSASecurityProvider;
|
||||||
|
import net.i2p.crypto.eddsa.KeyPairGenerator;
|
||||||
|
|
||||||
import org.spongycastle.asn1.sec.SECNamedCurves;
|
import org.spongycastle.asn1.sec.SECNamedCurves;
|
||||||
import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves;
|
import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves;
|
||||||
import org.spongycastle.asn1.x9.X9ECParameters;
|
import org.spongycastle.asn1.x9.X9ECParameters;
|
||||||
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
|
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
|
||||||
|
import org.spongycastle.crypto.BasicAgreement;
|
||||||
import org.spongycastle.crypto.Digest;
|
import org.spongycastle.crypto.Digest;
|
||||||
|
import org.spongycastle.crypto.agreement.ECDHBasicAgreement;
|
||||||
import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
|
import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
|
||||||
|
import org.spongycastle.crypto.digests.Blake2bDigest;
|
||||||
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
|
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
|
||||||
import org.spongycastle.crypto.params.ECDomainParameters;
|
import org.spongycastle.crypto.params.ECDomainParameters;
|
||||||
import org.spongycastle.crypto.params.ECKeyGenerationParameters;
|
import org.spongycastle.crypto.params.ECKeyGenerationParameters;
|
||||||
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
|
|
||||||
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
|
||||||
import org.spongycastle.crypto.params.ParametersWithRandom;
|
import org.spongycastle.crypto.params.ParametersWithRandom;
|
||||||
import org.spongycastle.crypto.signers.DSADigestSigner;
|
import org.spongycastle.crypto.signers.DSADigestSigner;
|
||||||
import org.spongycastle.crypto.signers.DSAKCalculator;
|
import org.spongycastle.crypto.signers.DSAKCalculator;
|
||||||
@@ -19,14 +23,22 @@ import org.spongycastle.crypto.signers.HMacDSAKCalculator;
|
|||||||
import org.spongycastle.math.ec.ECCurve;
|
import org.spongycastle.math.ec.ECCurve;
|
||||||
import org.spongycastle.math.ec.ECPoint;
|
import org.spongycastle.math.ec.ECPoint;
|
||||||
import org.spongycastle.math.ec.MontgomeryLadderMultiplier;
|
import org.spongycastle.math.ec.MontgomeryLadderMultiplier;
|
||||||
|
import org.whispersystems.curve25519.Curve25519;
|
||||||
|
import org.whispersystems.curve25519.Curve25519KeyPair;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.Provider;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
import java.security.Signature;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static net.i2p.crypto.eddsa.EdDSAEngine.SIGNATURE_ALGORITHM;
|
||||||
|
|
||||||
// Not a JUnit test
|
// Not a JUnit test
|
||||||
public class EllipticCurvePerformanceTest {
|
public class EllipticCurvePerformanceTest {
|
||||||
|
|
||||||
@@ -37,8 +49,9 @@ public class EllipticCurvePerformanceTest {
|
|||||||
"secp256k1", "secp256r1", "secp384r1", "secp521r1");
|
"secp256k1", "secp256r1", "secp384r1", "secp521r1");
|
||||||
private static final List<String> BRAINPOOL_NAMES = Arrays.asList(
|
private static final List<String> BRAINPOOL_NAMES = Arrays.asList(
|
||||||
"brainpoolp256r1", "brainpoolp384r1", "brainpoolp512r1");
|
"brainpoolp256r1", "brainpoolp384r1", "brainpoolp512r1");
|
||||||
|
private static final Provider ED_PROVIDER = new EdDSASecurityProvider();
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) throws GeneralSecurityException {
|
||||||
for (String name : SEC_NAMES) {
|
for (String name : SEC_NAMES) {
|
||||||
ECDomainParameters params =
|
ECDomainParameters params =
|
||||||
convertParams(SECNamedCurves.getByName(name));
|
convertParams(SECNamedCurves.getByName(name));
|
||||||
@@ -51,43 +64,31 @@ public class EllipticCurvePerformanceTest {
|
|||||||
runTest(name + " default", params);
|
runTest(name + " default", params);
|
||||||
runTest(name + " constant", constantTime(params));
|
runTest(name + " constant", constantTime(params));
|
||||||
}
|
}
|
||||||
runTest("ours", EllipticCurveConstants.PARAMETERS);
|
runCurve25519Test();
|
||||||
|
runEd25519Test();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void runTest(String name, ECDomainParameters params) {
|
private static void runTest(String name, ECDomainParameters params) {
|
||||||
// Generate two key pairs using the given parameters
|
// Generate two key pairs using the given parameters
|
||||||
ECKeyGenerationParameters generatorParams =
|
|
||||||
new ECKeyGenerationParameters(params, random);
|
|
||||||
ECKeyPairGenerator generator = new ECKeyPairGenerator();
|
ECKeyPairGenerator generator = new ECKeyPairGenerator();
|
||||||
generator.init(generatorParams);
|
generator.init(new ECKeyGenerationParameters(params, random));
|
||||||
AsymmetricCipherKeyPair keyPair1 = generator.generateKeyPair();
|
AsymmetricCipherKeyPair keyPair1 = generator.generateKeyPair();
|
||||||
ECPublicKeyParameters public1 =
|
|
||||||
(ECPublicKeyParameters) keyPair1.getPublic();
|
|
||||||
ECPrivateKeyParameters private1 =
|
|
||||||
(ECPrivateKeyParameters) keyPair1.getPrivate();
|
|
||||||
AsymmetricCipherKeyPair keyPair2 = generator.generateKeyPair();
|
AsymmetricCipherKeyPair keyPair2 = generator.generateKeyPair();
|
||||||
ECPublicKeyParameters public2 =
|
// Time some ECDH and ECDHC key agreements
|
||||||
(ECPublicKeyParameters) keyPair2.getPublic();
|
long agreementMedian = runAgreementTest(keyPair1, keyPair2, false);
|
||||||
// Time some ECDH key agreements
|
long agreementWithCofactorMedian =
|
||||||
List<Long> samples = new ArrayList<>();
|
runAgreementTest(keyPair1, keyPair2, true);
|
||||||
for (int i = 0; i < SAMPLES; i++) {
|
|
||||||
ECDHCBasicAgreement agreement = new ECDHCBasicAgreement();
|
|
||||||
long start = System.nanoTime();
|
|
||||||
agreement.init(private1);
|
|
||||||
agreement.calculateAgreement(public2);
|
|
||||||
samples.add(System.nanoTime() - start);
|
|
||||||
}
|
|
||||||
long agreementMedian = median(samples);
|
|
||||||
// Time some signatures
|
// Time some signatures
|
||||||
|
List<Long> samples = new ArrayList<>();
|
||||||
List<byte[]> signatures = new ArrayList<>();
|
List<byte[]> signatures = new ArrayList<>();
|
||||||
samples.clear();
|
|
||||||
for (int i = 0; i < SAMPLES; i++) {
|
for (int i = 0; i < SAMPLES; i++) {
|
||||||
Digest digest = new Blake2sDigest();
|
Digest digest = new Blake2bDigest(256);
|
||||||
DSAKCalculator calculator = new HMacDSAKCalculator(digest);
|
DSAKCalculator calculator = new HMacDSAKCalculator(digest);
|
||||||
DSADigestSigner signer = new DSADigestSigner(new ECDSASigner(
|
DSADigestSigner signer = new DSADigestSigner(new ECDSASigner(
|
||||||
calculator), digest);
|
calculator), digest);
|
||||||
long start = System.nanoTime();
|
long start = System.nanoTime();
|
||||||
signer.init(true, new ParametersWithRandom(private1, random));
|
signer.init(true,
|
||||||
|
new ParametersWithRandom(keyPair1.getPrivate(), random));
|
||||||
signer.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN);
|
signer.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN);
|
||||||
signatures.add(signer.generateSignature());
|
signatures.add(signer.generateSignature());
|
||||||
samples.add(System.nanoTime() - start);
|
samples.add(System.nanoTime() - start);
|
||||||
@@ -96,22 +97,88 @@ public class EllipticCurvePerformanceTest {
|
|||||||
// Time some signature verifications
|
// Time some signature verifications
|
||||||
samples.clear();
|
samples.clear();
|
||||||
for (int i = 0; i < SAMPLES; i++) {
|
for (int i = 0; i < SAMPLES; i++) {
|
||||||
Digest digest = new Blake2sDigest();
|
Digest digest = new Blake2bDigest(256);
|
||||||
DSAKCalculator calculator = new HMacDSAKCalculator(digest);
|
DSAKCalculator calculator = new HMacDSAKCalculator(digest);
|
||||||
DSADigestSigner signer = new DSADigestSigner(new ECDSASigner(
|
DSADigestSigner signer = new DSADigestSigner(new ECDSASigner(
|
||||||
calculator), digest);
|
calculator), digest);
|
||||||
long start = System.nanoTime();
|
long start = System.nanoTime();
|
||||||
signer.init(false, public1);
|
signer.init(false, keyPair1.getPublic());
|
||||||
signer.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN);
|
signer.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN);
|
||||||
if (!signer.verifySignature(signatures.get(i)))
|
if (!signer.verifySignature(signatures.get(i)))
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
samples.add(System.nanoTime() - start);
|
samples.add(System.nanoTime() - start);
|
||||||
}
|
}
|
||||||
long verificationMedian = median(samples);
|
long verificationMedian = median(samples);
|
||||||
System.out.println(name + ": "
|
System.out.println(String.format("%s: %,d %,d %,d %,d", name,
|
||||||
+ agreementMedian + " "
|
agreementMedian, agreementWithCofactorMedian,
|
||||||
+ signatureMedian + " "
|
signatureMedian, verificationMedian));
|
||||||
+ verificationMedian);
|
}
|
||||||
|
|
||||||
|
private static long runAgreementTest(AsymmetricCipherKeyPair keyPair1,
|
||||||
|
AsymmetricCipherKeyPair keyPair2, boolean withCofactor) {
|
||||||
|
List<Long> samples = new ArrayList<>();
|
||||||
|
for (int i = 0; i < SAMPLES; i++) {
|
||||||
|
BasicAgreement agreement = createAgreement(withCofactor);
|
||||||
|
long start = System.nanoTime();
|
||||||
|
agreement.init(keyPair1.getPrivate());
|
||||||
|
agreement.calculateAgreement(keyPair2.getPublic());
|
||||||
|
samples.add(System.nanoTime() - start);
|
||||||
|
}
|
||||||
|
return median(samples);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BasicAgreement createAgreement(boolean withCofactor) {
|
||||||
|
if (withCofactor) return new ECDHCBasicAgreement();
|
||||||
|
else return new ECDHBasicAgreement();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void runCurve25519Test() {
|
||||||
|
Curve25519 curve25519 = Curve25519.getInstance("java");
|
||||||
|
Curve25519KeyPair keyPair1 = curve25519.generateKeyPair();
|
||||||
|
Curve25519KeyPair keyPair2 = curve25519.generateKeyPair();
|
||||||
|
// Time some key agreements
|
||||||
|
List<Long> samples = new ArrayList<>();
|
||||||
|
for (int i = 0; i < SAMPLES; i++) {
|
||||||
|
long start = System.nanoTime();
|
||||||
|
curve25519.calculateAgreement(keyPair1.getPublicKey(),
|
||||||
|
keyPair2.getPrivateKey());
|
||||||
|
samples.add(System.nanoTime() - start);
|
||||||
|
}
|
||||||
|
long agreementMedian = median(samples);
|
||||||
|
System.out.println(String.format("Curve25519: %,d - - -",
|
||||||
|
agreementMedian));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void runEd25519Test() throws GeneralSecurityException {
|
||||||
|
KeyPair keyPair = new KeyPairGenerator().generateKeyPair();
|
||||||
|
// Time some signatures
|
||||||
|
List<Long> samples = new ArrayList<>();
|
||||||
|
List<byte[]> signatures = new ArrayList<>();
|
||||||
|
for (int i = 0; i < SAMPLES; i++) {
|
||||||
|
Signature signature =
|
||||||
|
Signature.getInstance(SIGNATURE_ALGORITHM, ED_PROVIDER);
|
||||||
|
long start = System.nanoTime();
|
||||||
|
signature.initSign(keyPair.getPrivate(), random);
|
||||||
|
signature.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN);
|
||||||
|
signatures.add(signature.sign());
|
||||||
|
samples.add(System.nanoTime() - start);
|
||||||
|
}
|
||||||
|
long signatureMedian = median(samples);
|
||||||
|
// Time some signature verifications
|
||||||
|
samples.clear();
|
||||||
|
for (int i = 0; i < SAMPLES; i++) {
|
||||||
|
Signature signature =
|
||||||
|
Signature.getInstance(SIGNATURE_ALGORITHM, ED_PROVIDER);
|
||||||
|
long start = System.nanoTime();
|
||||||
|
signature.initVerify(keyPair.getPublic());
|
||||||
|
signature.update(new byte[BYTES_TO_SIGN], 0, BYTES_TO_SIGN);
|
||||||
|
if (!signature.verify(signatures.get(i)))
|
||||||
|
throw new AssertionError();
|
||||||
|
samples.add(System.nanoTime() - start);
|
||||||
|
}
|
||||||
|
long verificationMedian = median(samples);
|
||||||
|
System.out.println(String.format("Ed25519: - - %,d %,d",
|
||||||
|
signatureMedian, verificationMedian));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static long median(List<Long> list) {
|
private static long median(List<Long> list) {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public class HashTest extends BrambleTestCase {
|
|||||||
private final byte[] inputBytes2 = new byte[0];
|
private final byte[] inputBytes2 = new byte[0];
|
||||||
|
|
||||||
public HashTest() {
|
public HashTest() {
|
||||||
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
|
crypto = new CryptoComponentImpl(new TestSecureRandomProvider(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -2,41 +2,80 @@ package org.briarproject.bramble.crypto;
|
|||||||
|
|
||||||
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.PublicKey;
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.whispersystems.curve25519.Curve25519;
|
||||||
|
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.SHARED_SECRET_LABEL;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.fromHexString;
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
|
||||||
public class KeyAgreementTest extends BrambleTestCase {
|
public class KeyAgreementTest extends BrambleTestCase {
|
||||||
|
|
||||||
@Test
|
// Test vector from RFC 7748: Alice's private and public keys, Bob's
|
||||||
public void testDeriveMasterSecret() throws Exception {
|
// private and public keys, and the shared secret
|
||||||
SecureRandomProvider
|
// https://tools.ietf.org/html/rfc7748#section-6.1
|
||||||
secureRandomProvider = new TestSecureRandomProvider();
|
private static final String ALICE_PRIVATE =
|
||||||
CryptoComponent crypto = new CryptoComponentImpl(secureRandomProvider);
|
"77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a";
|
||||||
KeyPair aPair = crypto.generateAgreementKeyPair();
|
private static final String ALICE_PUBLIC =
|
||||||
byte[] aPub = aPair.getPublic().getEncoded();
|
"8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a";
|
||||||
KeyPair bPair = crypto.generateAgreementKeyPair();
|
private static final String BOB_PRIVATE =
|
||||||
byte[] bPub = bPair.getPublic().getEncoded();
|
"5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb";
|
||||||
SecretKey aMaster = crypto.deriveMasterSecret(aPub, bPair, true);
|
private static final String BOB_PUBLIC =
|
||||||
SecretKey bMaster = crypto.deriveMasterSecret(bPub, aPair, false);
|
"de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f";
|
||||||
assertArrayEquals(aMaster.getBytes(), bMaster.getBytes());
|
private static final String SHARED_SECRET =
|
||||||
|
"4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742";
|
||||||
|
|
||||||
|
private final CryptoComponent crypto =
|
||||||
|
new CryptoComponentImpl(new TestSecureRandomProvider(), null);
|
||||||
|
private final byte[][] inputs;
|
||||||
|
|
||||||
|
public KeyAgreementTest() {
|
||||||
|
Random random = new Random();
|
||||||
|
inputs = new byte[random.nextInt(10) + 1][];
|
||||||
|
for (int i = 0; i < inputs.length; i++)
|
||||||
|
inputs[i] = getRandomBytes(random.nextInt(256));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeriveSharedSecret() throws Exception {
|
public void testDerivesSharedSecret() throws Exception {
|
||||||
SecureRandomProvider
|
|
||||||
secureRandomProvider = new TestSecureRandomProvider();
|
|
||||||
CryptoComponent crypto = new CryptoComponentImpl(secureRandomProvider);
|
|
||||||
KeyPair aPair = crypto.generateAgreementKeyPair();
|
KeyPair aPair = crypto.generateAgreementKeyPair();
|
||||||
byte[] aPub = aPair.getPublic().getEncoded();
|
|
||||||
KeyPair bPair = crypto.generateAgreementKeyPair();
|
KeyPair bPair = crypto.generateAgreementKeyPair();
|
||||||
byte[] bPub = bPair.getPublic().getEncoded();
|
SecretKey aShared = crypto.deriveSharedSecret(SHARED_SECRET_LABEL,
|
||||||
SecretKey aShared = crypto.deriveSharedSecret(bPub, aPair, true);
|
bPair.getPublic(), aPair, inputs);
|
||||||
SecretKey bShared = crypto.deriveSharedSecret(aPub, bPair, false);
|
SecretKey bShared = crypto.deriveSharedSecret(SHARED_SECRET_LABEL,
|
||||||
|
aPair.getPublic(), bPair, inputs);
|
||||||
assertArrayEquals(aShared.getBytes(), bShared.getBytes());
|
assertArrayEquals(aShared.getBytes(), bShared.getBytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(expected = GeneralSecurityException.class)
|
||||||
|
public void testRejectsInvalidPublicKey() throws Exception {
|
||||||
|
KeyPair keyPair = crypto.generateAgreementKeyPair();
|
||||||
|
PublicKey invalid = new Curve25519PublicKey(new byte[32]);
|
||||||
|
crypto.deriveSharedSecret(SHARED_SECRET_LABEL, invalid, keyPair,
|
||||||
|
inputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRfc7748TestVector() throws Exception {
|
||||||
|
// Private keys need to be clamped because curve25519-java does the
|
||||||
|
// clamping at key generation time, not multiplication time
|
||||||
|
byte[] aPriv = Curve25519KeyParser.clamp(fromHexString(ALICE_PRIVATE));
|
||||||
|
byte[] aPub = fromHexString(ALICE_PUBLIC);
|
||||||
|
byte[] bPriv = Curve25519KeyParser.clamp(fromHexString(BOB_PRIVATE));
|
||||||
|
byte[] bPub = fromHexString(BOB_PUBLIC);
|
||||||
|
byte[] sharedSecret = fromHexString(SHARED_SECRET);
|
||||||
|
Curve25519 curve25519 = Curve25519.getInstance("java");
|
||||||
|
assertArrayEquals(sharedSecret,
|
||||||
|
curve25519.calculateAgreement(aPub, bPriv));
|
||||||
|
assertArrayEquals(sharedSecret,
|
||||||
|
curve25519.calculateAgreement(bPub, aPriv));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ package org.briarproject.bramble.crypto;
|
|||||||
import org.briarproject.bramble.api.Bytes;
|
import org.briarproject.bramble.api.Bytes;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
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.TransportCrypto;
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
||||||
import org.briarproject.bramble.test.TestUtils;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -16,35 +16,34 @@ import java.util.HashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public class KeyDerivationTest extends BrambleTestCase {
|
public class KeyDerivationTest extends BrambleTestCase {
|
||||||
|
|
||||||
|
private final CryptoComponent crypto =
|
||||||
|
new CryptoComponentImpl(new TestSecureRandomProvider(), null);
|
||||||
|
private final TransportCrypto transportCrypto =
|
||||||
|
new TransportCryptoImpl(crypto);
|
||||||
private final TransportId transportId = new TransportId("id");
|
private final TransportId transportId = new TransportId("id");
|
||||||
private final CryptoComponent crypto;
|
private final SecretKey master = getSecretKey();
|
||||||
private final SecretKey master;
|
|
||||||
|
|
||||||
public KeyDerivationTest() {
|
|
||||||
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
|
|
||||||
master = TestUtils.getSecretKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testKeysAreDistinct() {
|
public void testKeysAreDistinct() {
|
||||||
TransportKeys k = crypto.deriveTransportKeys(transportId, master,
|
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
||||||
123, true);
|
master, 123, true);
|
||||||
assertAllDifferent(k);
|
assertAllDifferent(k);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCurrentKeysMatchCurrentKeysOfContact() {
|
public void testCurrentKeysMatchCurrentKeysOfContact() {
|
||||||
// Start in rotation period 123
|
// Start in rotation period 123
|
||||||
TransportKeys kA = crypto.deriveTransportKeys(transportId, master,
|
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
|
||||||
123, true);
|
master, 123, true);
|
||||||
TransportKeys kB = crypto.deriveTransportKeys(transportId, master,
|
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
|
||||||
123, false);
|
master, 123, false);
|
||||||
// Alice's incoming keys should equal Bob's outgoing keys
|
// Alice's incoming keys should equal Bob's outgoing keys
|
||||||
assertArrayEquals(kA.getCurrentIncomingKeys().getTagKey().getBytes(),
|
assertArrayEquals(kA.getCurrentIncomingKeys().getTagKey().getBytes(),
|
||||||
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
|
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
|
||||||
@@ -56,8 +55,8 @@ public class KeyDerivationTest extends BrambleTestCase {
|
|||||||
assertArrayEquals(kA.getCurrentOutgoingKeys().getHeaderKey().getBytes(),
|
assertArrayEquals(kA.getCurrentOutgoingKeys().getHeaderKey().getBytes(),
|
||||||
kB.getCurrentIncomingKeys().getHeaderKey().getBytes());
|
kB.getCurrentIncomingKeys().getHeaderKey().getBytes());
|
||||||
// Rotate into the future
|
// Rotate into the future
|
||||||
kA = crypto.rotateTransportKeys(kA, 456);
|
kA = transportCrypto.rotateTransportKeys(kA, 456);
|
||||||
kB = crypto.rotateTransportKeys(kB, 456);
|
kB = transportCrypto.rotateTransportKeys(kB, 456);
|
||||||
// Alice's incoming keys should equal Bob's outgoing keys
|
// Alice's incoming keys should equal Bob's outgoing keys
|
||||||
assertArrayEquals(kA.getCurrentIncomingKeys().getTagKey().getBytes(),
|
assertArrayEquals(kA.getCurrentIncomingKeys().getTagKey().getBytes(),
|
||||||
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
|
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
|
||||||
@@ -73,22 +72,23 @@ public class KeyDerivationTest extends BrambleTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testPreviousKeysMatchPreviousKeysOfContact() {
|
public void testPreviousKeysMatchPreviousKeysOfContact() {
|
||||||
// Start in rotation period 123
|
// Start in rotation period 123
|
||||||
TransportKeys kA = crypto.deriveTransportKeys(transportId, master,
|
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
|
||||||
123, true);
|
master, 123, true);
|
||||||
TransportKeys kB = crypto.deriveTransportKeys(transportId, master,
|
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
|
||||||
123, false);
|
master, 123, false);
|
||||||
// Compare Alice's previous keys in period 456 with Bob's current keys
|
// Compare Alice's previous keys in period 456 with Bob's current keys
|
||||||
// in period 455
|
// in period 455
|
||||||
kA = crypto.rotateTransportKeys(kA, 456);
|
kA = transportCrypto.rotateTransportKeys(kA, 456);
|
||||||
kB = crypto.rotateTransportKeys(kB, 455);
|
kB = transportCrypto.rotateTransportKeys(kB, 455);
|
||||||
// Alice's previous incoming keys should equal Bob's outgoing keys
|
// Alice's previous incoming keys should equal Bob's outgoing keys
|
||||||
assertArrayEquals(kA.getPreviousIncomingKeys().getTagKey().getBytes(),
|
assertArrayEquals(kA.getPreviousIncomingKeys().getTagKey().getBytes(),
|
||||||
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
|
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
|
||||||
assertArrayEquals(kA.getPreviousIncomingKeys().getHeaderKey().getBytes(),
|
assertArrayEquals(
|
||||||
|
kA.getPreviousIncomingKeys().getHeaderKey().getBytes(),
|
||||||
kB.getCurrentOutgoingKeys().getHeaderKey().getBytes());
|
kB.getCurrentOutgoingKeys().getHeaderKey().getBytes());
|
||||||
// Compare Alice's current keys in period 456 with Bob's previous keys
|
// Compare Alice's current keys in period 456 with Bob's previous keys
|
||||||
// in period 457
|
// in period 457
|
||||||
kB = crypto.rotateTransportKeys(kB, 457);
|
kB = transportCrypto.rotateTransportKeys(kB, 457);
|
||||||
// Alice's outgoing keys should equal Bob's previous incoming keys
|
// Alice's outgoing keys should equal Bob's previous incoming keys
|
||||||
assertArrayEquals(kA.getCurrentOutgoingKeys().getTagKey().getBytes(),
|
assertArrayEquals(kA.getCurrentOutgoingKeys().getTagKey().getBytes(),
|
||||||
kB.getPreviousIncomingKeys().getTagKey().getBytes());
|
kB.getPreviousIncomingKeys().getTagKey().getBytes());
|
||||||
@@ -99,14 +99,14 @@ public class KeyDerivationTest extends BrambleTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testNextKeysMatchNextKeysOfContact() {
|
public void testNextKeysMatchNextKeysOfContact() {
|
||||||
// Start in rotation period 123
|
// Start in rotation period 123
|
||||||
TransportKeys kA = crypto.deriveTransportKeys(transportId, master,
|
TransportKeys kA = transportCrypto.deriveTransportKeys(transportId,
|
||||||
123, true);
|
master, 123, true);
|
||||||
TransportKeys kB = crypto.deriveTransportKeys(transportId, master,
|
TransportKeys kB = transportCrypto.deriveTransportKeys(transportId,
|
||||||
123, false);
|
master, 123, false);
|
||||||
// Compare Alice's current keys in period 456 with Bob's next keys in
|
// Compare Alice's current keys in period 456 with Bob's next keys in
|
||||||
// period 455
|
// period 455
|
||||||
kA = crypto.rotateTransportKeys(kA, 456);
|
kA = transportCrypto.rotateTransportKeys(kA, 456);
|
||||||
kB = crypto.rotateTransportKeys(kB, 455);
|
kB = transportCrypto.rotateTransportKeys(kB, 455);
|
||||||
// Alice's outgoing keys should equal Bob's next incoming keys
|
// Alice's outgoing keys should equal Bob's next incoming keys
|
||||||
assertArrayEquals(kA.getCurrentOutgoingKeys().getTagKey().getBytes(),
|
assertArrayEquals(kA.getCurrentOutgoingKeys().getTagKey().getBytes(),
|
||||||
kB.getNextIncomingKeys().getTagKey().getBytes());
|
kB.getNextIncomingKeys().getTagKey().getBytes());
|
||||||
@@ -114,7 +114,7 @@ public class KeyDerivationTest extends BrambleTestCase {
|
|||||||
kB.getNextIncomingKeys().getHeaderKey().getBytes());
|
kB.getNextIncomingKeys().getHeaderKey().getBytes());
|
||||||
// Compare Alice's next keys in period 456 with Bob's current keys
|
// Compare Alice's next keys in period 456 with Bob's current keys
|
||||||
// in period 457
|
// in period 457
|
||||||
kB = crypto.rotateTransportKeys(kB, 457);
|
kB = transportCrypto.rotateTransportKeys(kB, 457);
|
||||||
// Alice's next incoming keys should equal Bob's outgoing keys
|
// Alice's next incoming keys should equal Bob's outgoing keys
|
||||||
assertArrayEquals(kA.getNextIncomingKeys().getTagKey().getBytes(),
|
assertArrayEquals(kA.getNextIncomingKeys().getTagKey().getBytes(),
|
||||||
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
|
kB.getCurrentOutgoingKeys().getTagKey().getBytes());
|
||||||
@@ -124,12 +124,12 @@ public class KeyDerivationTest extends BrambleTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMasterKeyAffectsOutput() {
|
public void testMasterKeyAffectsOutput() {
|
||||||
SecretKey master1 = TestUtils.getSecretKey();
|
SecretKey master1 = getSecretKey();
|
||||||
assertFalse(Arrays.equals(master.getBytes(), master1.getBytes()));
|
assertFalse(Arrays.equals(master.getBytes(), master1.getBytes()));
|
||||||
TransportKeys k = crypto.deriveTransportKeys(transportId, master,
|
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
||||||
123, true);
|
master, 123, true);
|
||||||
TransportKeys k1 = crypto.deriveTransportKeys(transportId, master1,
|
TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId,
|
||||||
123, true);
|
master1, 123, true);
|
||||||
assertAllDifferent(k, k1);
|
assertAllDifferent(k, k1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,10 +137,10 @@ public class KeyDerivationTest extends BrambleTestCase {
|
|||||||
public void testTransportIdAffectsOutput() {
|
public void testTransportIdAffectsOutput() {
|
||||||
TransportId transportId1 = new TransportId("id1");
|
TransportId transportId1 = new TransportId("id1");
|
||||||
assertFalse(transportId.getString().equals(transportId1.getString()));
|
assertFalse(transportId.getString().equals(transportId1.getString()));
|
||||||
TransportKeys k = crypto.deriveTransportKeys(transportId, master,
|
TransportKeys k = transportCrypto.deriveTransportKeys(transportId,
|
||||||
123, true);
|
master, 123, true);
|
||||||
TransportKeys k1 = crypto.deriveTransportKeys(transportId1, master,
|
TransportKeys k1 = transportCrypto.deriveTransportKeys(transportId1,
|
||||||
123, true);
|
master, 123, true);
|
||||||
assertAllDifferent(k, k1);
|
assertAllDifferent(k, k1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,29 +6,30 @@ 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.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
||||||
import org.briarproject.bramble.test.TestUtils;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_BYTES;
|
||||||
|
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_PUBLIC_KEY_BYTES;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public class KeyEncodingAndParsingTest extends BrambleTestCase {
|
public class KeyEncodingAndParsingTest extends BrambleTestCase {
|
||||||
|
|
||||||
private final CryptoComponentImpl crypto =
|
private final CryptoComponentImpl crypto =
|
||||||
new CryptoComponentImpl(new TestSecureRandomProvider());
|
new CryptoComponentImpl(new TestSecureRandomProvider(), null);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAgreementPublicKeyLength() throws Exception {
|
public void testAgreementPublicKeyLength() throws Exception {
|
||||||
// Generate 10 agreement key pairs
|
// Generate 10 agreement key pairs
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
KeyPair keyPair = crypto.generateSignatureKeyPair();
|
KeyPair keyPair = crypto.generateAgreementKeyPair();
|
||||||
// Check the length of the public key
|
// Check the length of the public key
|
||||||
byte[] publicKey = keyPair.getPublic().getEncoded();
|
byte[] publicKey = keyPair.getPublic().getEncoded();
|
||||||
assertTrue(publicKey.length <= MAX_PUBLIC_KEY_LENGTH);
|
assertTrue(publicKey.length <= MAX_AGREEMENT_PUBLIC_KEY_BYTES);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +46,8 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
|
|||||||
aPub = parser.parsePublicKey(aPub.getEncoded());
|
aPub = parser.parsePublicKey(aPub.getEncoded());
|
||||||
aPub = parser.parsePublicKey(aPub.getEncoded());
|
aPub = parser.parsePublicKey(aPub.getEncoded());
|
||||||
// Derive the shared secret again - it should be the same
|
// Derive the shared secret again - it should be the same
|
||||||
byte[] secret1 = crypto.performRawKeyAgreement(bPair.getPrivate(), aPub);
|
byte[] secret1 =
|
||||||
|
crypto.performRawKeyAgreement(bPair.getPrivate(), aPub);
|
||||||
assertArrayEquals(secret, secret1);
|
assertArrayEquals(secret, secret1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +64,8 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
|
|||||||
bPriv = parser.parsePrivateKey(bPriv.getEncoded());
|
bPriv = parser.parsePrivateKey(bPriv.getEncoded());
|
||||||
bPriv = parser.parsePrivateKey(bPriv.getEncoded());
|
bPriv = parser.parsePrivateKey(bPriv.getEncoded());
|
||||||
// Derive the shared secret again - it should be the same
|
// Derive the shared secret again - it should be the same
|
||||||
byte[] secret1 = crypto.performRawKeyAgreement(bPriv, aPair.getPublic());
|
byte[] secret1 =
|
||||||
|
crypto.performRawKeyAgreement(bPriv, aPair.getPublic());
|
||||||
assertArrayEquals(secret, secret1);
|
assertArrayEquals(secret, secret1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,12 +79,12 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
|
|||||||
// Parse some random byte arrays - expect GeneralSecurityException
|
// Parse some random byte arrays - expect GeneralSecurityException
|
||||||
for (int i = 0; i < 1000; i++) {
|
for (int i = 0; i < 1000; i++) {
|
||||||
try {
|
try {
|
||||||
parser.parsePublicKey(TestUtils.getRandomBytes(pubLength));
|
parser.parsePublicKey(getRandomBytes(pubLength));
|
||||||
} catch (GeneralSecurityException expected) {
|
} catch (GeneralSecurityException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
parser.parsePrivateKey(TestUtils.getRandomBytes(privLength));
|
parser.parsePrivateKey(getRandomBytes(privLength));
|
||||||
} catch (GeneralSecurityException expected) {
|
} catch (GeneralSecurityException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
}
|
}
|
||||||
@@ -95,7 +98,7 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
|
|||||||
KeyPair keyPair = crypto.generateSignatureKeyPair();
|
KeyPair keyPair = crypto.generateSignatureKeyPair();
|
||||||
// Check the length of the public key
|
// Check the length of the public key
|
||||||
byte[] publicKey = keyPair.getPublic().getEncoded();
|
byte[] publicKey = keyPair.getPublic().getEncoded();
|
||||||
assertTrue(publicKey.length <= MAX_PUBLIC_KEY_LENGTH);
|
assertTrue(publicKey.length <= MAX_SIGNATURE_PUBLIC_KEY_BYTES);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,44 +109,53 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
|
|||||||
KeyPair keyPair = crypto.generateSignatureKeyPair();
|
KeyPair keyPair = crypto.generateSignatureKeyPair();
|
||||||
byte[] key = keyPair.getPrivate().getEncoded();
|
byte[] key = keyPair.getPrivate().getEncoded();
|
||||||
// Sign some random data and check the length of the signature
|
// Sign some random data and check the length of the signature
|
||||||
byte[] toBeSigned = TestUtils.getRandomBytes(1234);
|
byte[] toBeSigned = getRandomBytes(1234);
|
||||||
byte[] signature = crypto.sign("label", toBeSigned, key);
|
byte[] signature = crypto.sign("label", toBeSigned, key);
|
||||||
assertTrue(signature.length <= MAX_SIGNATURE_LENGTH);
|
assertTrue(signature.length <= MAX_SIGNATURE_BYTES);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSignaturePublicKeyEncodingAndParsing() throws Exception {
|
public void testSignaturePublicKeyEncodingAndParsing() throws Exception {
|
||||||
KeyParser parser = crypto.getSignatureKeyParser();
|
KeyParser parser = crypto.getSignatureKeyParser();
|
||||||
// Generate two key pairs
|
// Generate a key pair and sign some data
|
||||||
KeyPair aPair = crypto.generateSignatureKeyPair();
|
KeyPair keyPair = crypto.generateSignatureKeyPair();
|
||||||
KeyPair bPair = crypto.generateSignatureKeyPair();
|
PublicKey publicKey = keyPair.getPublic();
|
||||||
// Derive the shared secret
|
PrivateKey privateKey = keyPair.getPrivate();
|
||||||
PublicKey aPub = aPair.getPublic();
|
byte[] message = getRandomBytes(123);
|
||||||
byte[] secret = crypto.performRawKeyAgreement(bPair.getPrivate(), aPub);
|
byte[] signature = crypto.sign("test", message,
|
||||||
|
privateKey.getEncoded());
|
||||||
|
// Verify the signature
|
||||||
|
assertTrue(crypto.verify("test", message, publicKey.getEncoded(),
|
||||||
|
signature));
|
||||||
// Encode and parse the public key - no exceptions should be thrown
|
// Encode and parse the public key - no exceptions should be thrown
|
||||||
aPub = parser.parsePublicKey(aPub.getEncoded());
|
publicKey = parser.parsePublicKey(publicKey.getEncoded());
|
||||||
aPub = parser.parsePublicKey(aPub.getEncoded());
|
// Verify the signature again
|
||||||
// Derive the shared secret again - it should be the same
|
assertTrue(crypto.verify("test", message, publicKey.getEncoded(),
|
||||||
byte[] secret1 = crypto.performRawKeyAgreement(bPair.getPrivate(), aPub);
|
signature));
|
||||||
assertArrayEquals(secret, secret1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSignaturePrivateKeyEncodingAndParsing() throws Exception {
|
public void testSignaturePrivateKeyEncodingAndParsing() throws Exception {
|
||||||
KeyParser parser = crypto.getSignatureKeyParser();
|
KeyParser parser = crypto.getSignatureKeyParser();
|
||||||
// Generate two key pairs
|
// Generate a key pair and sign some data
|
||||||
KeyPair aPair = crypto.generateSignatureKeyPair();
|
KeyPair keyPair = crypto.generateSignatureKeyPair();
|
||||||
KeyPair bPair = crypto.generateSignatureKeyPair();
|
PublicKey publicKey = keyPair.getPublic();
|
||||||
// Derive the shared secret
|
PrivateKey privateKey = keyPair.getPrivate();
|
||||||
PrivateKey bPriv = bPair.getPrivate();
|
byte[] message = getRandomBytes(123);
|
||||||
byte[] secret = crypto.performRawKeyAgreement(bPriv, aPair.getPublic());
|
byte[] signature = crypto.sign("test", message,
|
||||||
|
privateKey.getEncoded());
|
||||||
|
// Verify the signature
|
||||||
|
assertTrue(crypto.verify("test", message, publicKey.getEncoded(),
|
||||||
|
signature));
|
||||||
// Encode and parse the private key - no exceptions should be thrown
|
// Encode and parse the private key - no exceptions should be thrown
|
||||||
bPriv = parser.parsePrivateKey(bPriv.getEncoded());
|
privateKey = parser.parsePrivateKey(privateKey.getEncoded());
|
||||||
bPriv = parser.parsePrivateKey(bPriv.getEncoded());
|
// Sign the data again - the signatures should be the same
|
||||||
// Derive the shared secret again - it should be the same
|
byte[] signature1 = crypto.sign("test", message,
|
||||||
byte[] secret1 = crypto.performRawKeyAgreement(bPriv, aPair.getPublic());
|
privateKey.getEncoded());
|
||||||
assertArrayEquals(secret, secret1);
|
assertTrue(crypto.verify("test", message, publicKey.getEncoded(),
|
||||||
|
signature1));
|
||||||
|
assertArrayEquals(signature, signature1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -156,12 +168,12 @@ public class KeyEncodingAndParsingTest extends BrambleTestCase {
|
|||||||
// Parse some random byte arrays - expect GeneralSecurityException
|
// Parse some random byte arrays - expect GeneralSecurityException
|
||||||
for (int i = 0; i < 1000; i++) {
|
for (int i = 0; i < 1000; i++) {
|
||||||
try {
|
try {
|
||||||
parser.parsePublicKey(TestUtils.getRandomBytes(pubLength));
|
parser.parsePublicKey(getRandomBytes(pubLength));
|
||||||
} catch (GeneralSecurityException expected) {
|
} catch (GeneralSecurityException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
parser.parsePrivateKey(TestUtils.getRandomBytes(privLength));
|
parser.parsePrivateKey(getRandomBytes(privLength));
|
||||||
} catch (GeneralSecurityException expected) {
|
} catch (GeneralSecurityException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,42 +4,49 @@ 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.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
||||||
import org.briarproject.bramble.test.TestUtils;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
|
||||||
public class MacTest extends BrambleTestCase {
|
public class MacTest extends BrambleTestCase {
|
||||||
|
|
||||||
private final CryptoComponent crypto;
|
private final CryptoComponent crypto =
|
||||||
|
new CryptoComponentImpl(new TestSecureRandomProvider(), null);
|
||||||
|
|
||||||
private final SecretKey k = TestUtils.getSecretKey();
|
private final SecretKey key1 = getSecretKey(), key2 = getSecretKey();
|
||||||
private final byte[] inputBytes = TestUtils.getRandomBytes(123);
|
private final String label1 = getRandomString(123);
|
||||||
private final byte[] inputBytes1 = TestUtils.getRandomBytes(234);
|
private final String label2 = getRandomString(123);
|
||||||
private final byte[] inputBytes2 = new byte[0];
|
private final byte[] input1 = getRandomBytes(123);
|
||||||
|
private final byte[] input2 = getRandomBytes(234);
|
||||||
public MacTest() {
|
private final byte[] input3 = new byte[0];
|
||||||
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIdenticalKeysAndInputsProduceIdenticalMacs() {
|
public void testIdenticalKeysAndInputsProduceIdenticalMacs() {
|
||||||
// Calculate the MAC twice - the results should be identical
|
// Calculate the MAC twice - the results should be identical
|
||||||
byte[] mac = crypto.mac(k, inputBytes, inputBytes1, inputBytes2);
|
byte[] mac = crypto.mac(label1, key1, input1, input2, input3);
|
||||||
byte[] mac1 = crypto.mac(k, inputBytes, inputBytes1, inputBytes2);
|
byte[] mac1 = crypto.mac(label1, key1, input1, input2, input3);
|
||||||
assertArrayEquals(mac, mac1);
|
assertArrayEquals(mac, mac1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDifferentLabelsProduceDifferentMacs() {
|
||||||
|
// Calculate the MAC with each label - the results should be different
|
||||||
|
byte[] mac = crypto.mac(label1, key1, input1, input2, input3);
|
||||||
|
byte[] mac1 = crypto.mac(label2, key1, input1, input2, input3);
|
||||||
|
assertFalse(Arrays.equals(mac, mac1));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDifferentKeysProduceDifferentMacs() {
|
public void testDifferentKeysProduceDifferentMacs() {
|
||||||
// Generate second random key
|
|
||||||
SecretKey k1 = TestUtils.getSecretKey();
|
|
||||||
// Calculate the MAC with each key - the results should be different
|
// Calculate the MAC with each key - the results should be different
|
||||||
byte[] mac = crypto.mac(k, inputBytes, inputBytes1, inputBytes2);
|
byte[] mac = crypto.mac(label1, key1, input1, input2, input3);
|
||||||
byte[] mac1 = crypto.mac(k1, inputBytes, inputBytes1, inputBytes2);
|
byte[] mac1 = crypto.mac(label1, key2, input1, input2, input3);
|
||||||
assertFalse(Arrays.equals(mac, mac1));
|
assertFalse(Arrays.equals(mac, mac1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,8 +54,8 @@ public class MacTest extends BrambleTestCase {
|
|||||||
public void testDifferentInputsProduceDifferentMacs() {
|
public void testDifferentInputsProduceDifferentMacs() {
|
||||||
// Calculate the MAC with the inputs in different orders - the results
|
// Calculate the MAC with the inputs in different orders - the results
|
||||||
// should be different
|
// should be different
|
||||||
byte[] mac = crypto.mac(k, inputBytes, inputBytes1, inputBytes2);
|
byte[] mac = crypto.mac(label1, key1, input1, input2, input3);
|
||||||
byte[] mac1 = crypto.mac(k, inputBytes2, inputBytes1, inputBytes);
|
byte[] mac1 = crypto.mac(label1, key1, input3, input2, input1);
|
||||||
assertFalse(Arrays.equals(mac, mac1));
|
assertFalse(Arrays.equals(mac, mac1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.briarproject.bramble.crypto;
|
package org.briarproject.bramble.crypto;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.system.SystemClock;
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
||||||
import org.briarproject.bramble.test.TestUtils;
|
import org.briarproject.bramble.test.TestUtils;
|
||||||
@@ -8,14 +9,13 @@ import org.junit.Test;
|
|||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
public class PasswordBasedKdfTest extends BrambleTestCase {
|
public class PasswordBasedEncryptionTest extends BrambleTestCase {
|
||||||
|
|
||||||
private final CryptoComponentImpl crypto =
|
private final CryptoComponentImpl crypto =
|
||||||
new CryptoComponentImpl(new TestSecureRandomProvider());
|
new CryptoComponentImpl(new TestSecureRandomProvider(),
|
||||||
|
new ScryptKdf(new SystemClock()));
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEncryptionAndDecryption() {
|
public void testEncryptionAndDecryption() {
|
||||||
@@ -37,17 +37,4 @@ public class PasswordBasedKdfTest extends BrambleTestCase {
|
|||||||
byte[] output = crypto.decryptWithPassword(ciphertext, password);
|
byte[] output = crypto.decryptWithPassword(ciphertext, password);
|
||||||
assertNull(output);
|
assertNull(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCalibration() {
|
|
||||||
// If the target time is unachievable, one iteration should be used
|
|
||||||
int iterations = crypto.chooseIterationCount(0);
|
|
||||||
assertEquals(1, iterations);
|
|
||||||
// If the target time is long, more than one iteration should be used
|
|
||||||
iterations = crypto.chooseIterationCount(10 * 1000);
|
|
||||||
assertTrue(iterations > 1);
|
|
||||||
// If the target time is very long, max iterations should be used
|
|
||||||
iterations = crypto.chooseIterationCount(Integer.MAX_VALUE);
|
|
||||||
assertEquals(Integer.MAX_VALUE, iterations);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ package org.briarproject.bramble.crypto;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.spongycastle.crypto.Digest;
|
import org.spongycastle.crypto.Digest;
|
||||||
|
import org.spongycastle.crypto.digests.Blake2bDigest;
|
||||||
import org.spongycastle.crypto.engines.Salsa20Engine;
|
import org.spongycastle.crypto.engines.Salsa20Engine;
|
||||||
import org.spongycastle.crypto.params.KeyParameter;
|
import org.spongycastle.crypto.params.KeyParameter;
|
||||||
import org.spongycastle.crypto.params.ParametersWithIV;
|
import org.spongycastle.crypto.params.ParametersWithIV;
|
||||||
@@ -17,7 +18,7 @@ class PseudoRandom {
|
|||||||
PseudoRandom(byte[] seed) {
|
PseudoRandom(byte[] seed) {
|
||||||
// Hash the seed to produce a 32-byte key
|
// Hash the seed to produce a 32-byte key
|
||||||
byte[] key = new byte[32];
|
byte[] key = new byte[32];
|
||||||
Digest digest = new Blake2sDigest();
|
Digest digest = new Blake2bDigest(256);
|
||||||
digest.update(seed, 0, seed.length);
|
digest.update(seed, 0, seed.length);
|
||||||
digest.doFinal(key, 0);
|
digest.doFinal(key, 0);
|
||||||
// Initialise the stream cipher with an all-zero nonce
|
// Initialise the stream cipher with an all-zero nonce
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package org.briarproject.bramble.crypto;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.Bytes;
|
||||||
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
import org.briarproject.bramble.system.SystemClock;
|
||||||
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static junit.framework.TestCase.assertTrue;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class ScryptKdfTest extends BrambleTestCase {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPasswordAffectsKey() throws Exception {
|
||||||
|
PasswordBasedKdf kdf = new ScryptKdf(new SystemClock());
|
||||||
|
byte[] salt = getRandomBytes(32);
|
||||||
|
Set<Bytes> keys = new HashSet<>();
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
String password = getRandomString(16);
|
||||||
|
SecretKey key = kdf.deriveKey(password, salt, 256);
|
||||||
|
assertTrue(keys.add(new Bytes(key.getBytes())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSaltAffectsKey() throws Exception {
|
||||||
|
PasswordBasedKdf kdf = new ScryptKdf(new SystemClock());
|
||||||
|
String password = getRandomString(16);
|
||||||
|
Set<Bytes> keys = new HashSet<>();
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
byte[] salt = getRandomBytes(32);
|
||||||
|
SecretKey key = kdf.deriveKey(password, salt, 256);
|
||||||
|
assertTrue(keys.add(new Bytes(key.getBytes())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCostParameterAffectsKey() throws Exception {
|
||||||
|
PasswordBasedKdf kdf = new ScryptKdf(new SystemClock());
|
||||||
|
String password = getRandomString(16);
|
||||||
|
byte[] salt = getRandomBytes(32);
|
||||||
|
Set<Bytes> keys = new HashSet<>();
|
||||||
|
for (int cost = 2; cost <= 256; cost *= 2) {
|
||||||
|
SecretKey key = kdf.deriveKey(password, salt, cost);
|
||||||
|
assertTrue(keys.add(new Bytes(key.getBytes())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCalibration() throws Exception {
|
||||||
|
Clock clock = new ArrayClock(
|
||||||
|
0, 50, // Duration for cost 256
|
||||||
|
0, 100, // Duration for cost 512
|
||||||
|
0, 200, // Duration for cost 1024
|
||||||
|
0, 400, // Duration for cost 2048
|
||||||
|
0, 800 // Duration for cost 4096
|
||||||
|
);
|
||||||
|
PasswordBasedKdf kdf = new ScryptKdf(clock);
|
||||||
|
assertEquals(4096, kdf.chooseCostParameter());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCalibrationChoosesMinCost() throws Exception {
|
||||||
|
Clock clock = new ArrayClock(
|
||||||
|
0, 2000 // Duration for cost 256 is already too high
|
||||||
|
);
|
||||||
|
PasswordBasedKdf kdf = new ScryptKdf(clock);
|
||||||
|
assertEquals(256, kdf.chooseCostParameter());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ArrayClock implements Clock {
|
||||||
|
|
||||||
|
private final long[] times;
|
||||||
|
private int index = 0;
|
||||||
|
|
||||||
|
private ArrayClock(long... times) {
|
||||||
|
this.times = times;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long currentTimeMillis() {
|
||||||
|
return times[index++];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sleep(long milliseconds) throws InterruptedException {
|
||||||
|
Thread.sleep(milliseconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,23 +8,32 @@ import org.briarproject.bramble.test.TestUtils;
|
|||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public class SignatureTest extends BrambleTestCase {
|
public abstract class SignatureTest extends BrambleTestCase {
|
||||||
|
|
||||||
private final CryptoComponent crypto;
|
protected final CryptoComponent crypto;
|
||||||
|
|
||||||
private final byte[] publicKey, privateKey;
|
private final byte[] publicKey, privateKey;
|
||||||
private final String label = StringUtils.getRandomString(42);
|
private final String label = StringUtils.getRandomString(42);
|
||||||
private final byte[] inputBytes = TestUtils.getRandomBytes(123);
|
private final byte[] inputBytes = TestUtils.getRandomBytes(123);
|
||||||
|
|
||||||
public SignatureTest() {
|
protected abstract KeyPair generateKeyPair();
|
||||||
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
|
|
||||||
KeyPair k = crypto.generateSignatureKeyPair();
|
protected abstract byte[] sign(String label, byte[] toSign,
|
||||||
|
byte[] privateKey) throws GeneralSecurityException;
|
||||||
|
|
||||||
|
protected abstract boolean verify(String label, byte[] signedData,
|
||||||
|
byte[] publicKey, byte[] signature) throws GeneralSecurityException;
|
||||||
|
|
||||||
|
SignatureTest() {
|
||||||
|
crypto = new CryptoComponentImpl(new TestSecureRandomProvider(), null);
|
||||||
|
KeyPair k = generateKeyPair();
|
||||||
publicKey = k.getPublic().getEncoded();
|
publicKey = k.getPublic().getEncoded();
|
||||||
privateKey = k.getPrivate().getEncoded();
|
privateKey = k.getPrivate().getEncoded();
|
||||||
}
|
}
|
||||||
@@ -33,19 +42,19 @@ public class SignatureTest extends BrambleTestCase {
|
|||||||
public void testIdenticalKeysAndInputsProduceIdenticalSignatures()
|
public void testIdenticalKeysAndInputsProduceIdenticalSignatures()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
// Calculate the Signature twice - the results should be identical
|
// Calculate the Signature twice - the results should be identical
|
||||||
byte[] sig1 = crypto.sign(label, inputBytes, privateKey);
|
byte[] sig1 = sign(label, inputBytes, privateKey);
|
||||||
byte[] sig2 = crypto.sign(label, inputBytes, privateKey);
|
byte[] sig2 = sign(label, inputBytes, privateKey);
|
||||||
assertArrayEquals(sig1, sig2);
|
assertArrayEquals(sig1, sig2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDifferentKeysProduceDifferentSignatures() throws Exception {
|
public void testDifferentKeysProduceDifferentSignatures() throws Exception {
|
||||||
// Generate second private key
|
// Generate second private key
|
||||||
KeyPair k2 = crypto.generateSignatureKeyPair();
|
KeyPair k2 = generateKeyPair();
|
||||||
byte[] privateKey2 = k2.getPrivate().getEncoded();
|
byte[] privateKey2 = k2.getPrivate().getEncoded();
|
||||||
// Calculate the signature with each key
|
// Calculate the signature with each key
|
||||||
byte[] sig1 = crypto.sign(label, inputBytes, privateKey);
|
byte[] sig1 = sign(label, inputBytes, privateKey);
|
||||||
byte[] sig2 = crypto.sign(label, inputBytes, privateKey2);
|
byte[] sig2 = sign(label, inputBytes, privateKey2);
|
||||||
assertFalse(Arrays.equals(sig1, sig2));
|
assertFalse(Arrays.equals(sig1, sig2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,8 +65,8 @@ public class SignatureTest extends BrambleTestCase {
|
|||||||
byte[] inputBytes2 = TestUtils.getRandomBytes(123);
|
byte[] inputBytes2 = TestUtils.getRandomBytes(123);
|
||||||
// Calculate the signature with different inputs
|
// Calculate the signature with different inputs
|
||||||
// the results should be different
|
// the results should be different
|
||||||
byte[] sig1 = crypto.sign(label, inputBytes, privateKey);
|
byte[] sig1 = sign(label, inputBytes, privateKey);
|
||||||
byte[] sig2 = crypto.sign(label, inputBytes2, privateKey);
|
byte[] sig2 = sign(label, inputBytes2, privateKey);
|
||||||
assertFalse(Arrays.equals(sig1, sig2));
|
assertFalse(Arrays.equals(sig1, sig2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,25 +77,25 @@ public class SignatureTest extends BrambleTestCase {
|
|||||||
String label2 = StringUtils.getRandomString(42);
|
String label2 = StringUtils.getRandomString(42);
|
||||||
// Calculate the signature with different inputs
|
// Calculate the signature with different inputs
|
||||||
// the results should be different
|
// the results should be different
|
||||||
byte[] sig1 = crypto.sign(label, inputBytes, privateKey);
|
byte[] sig1 = sign(label, inputBytes, privateKey);
|
||||||
byte[] sig2 = crypto.sign(label2, inputBytes, privateKey);
|
byte[] sig2 = sign(label2, inputBytes, privateKey);
|
||||||
assertFalse(Arrays.equals(sig1, sig2));
|
assertFalse(Arrays.equals(sig1, sig2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSignatureVerification() throws Exception {
|
public void testSignatureVerification() throws Exception {
|
||||||
byte[] sig = crypto.sign(label, inputBytes, privateKey);
|
byte[] sig = sign(label, inputBytes, privateKey);
|
||||||
assertTrue(crypto.verify(label, inputBytes, publicKey, sig));
|
assertTrue(verify(label, inputBytes, publicKey, sig));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDifferentKeyFailsVerification() throws Exception {
|
public void testDifferentKeyFailsVerification() throws Exception {
|
||||||
// Generate second private key
|
// Generate second private key
|
||||||
KeyPair k2 = crypto.generateSignatureKeyPair();
|
KeyPair k2 = generateKeyPair();
|
||||||
byte[] privateKey2 = k2.getPrivate().getEncoded();
|
byte[] privateKey2 = k2.getPrivate().getEncoded();
|
||||||
// calculate the signature with different key, should fail to verify
|
// calculate the signature with different key, should fail to verify
|
||||||
byte[] sig = crypto.sign(label, inputBytes, privateKey2);
|
byte[] sig = sign(label, inputBytes, privateKey2);
|
||||||
assertFalse(crypto.verify(label, inputBytes, publicKey, sig));
|
assertFalse(verify(label, inputBytes, publicKey, sig));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -94,8 +103,8 @@ public class SignatureTest extends BrambleTestCase {
|
|||||||
// Generate a second input
|
// Generate a second input
|
||||||
byte[] inputBytes2 = TestUtils.getRandomBytes(123);
|
byte[] inputBytes2 = TestUtils.getRandomBytes(123);
|
||||||
// calculate the signature with different input, should fail to verify
|
// calculate the signature with different input, should fail to verify
|
||||||
byte[] sig = crypto.sign(label, inputBytes, privateKey);
|
byte[] sig = sign(label, inputBytes, privateKey);
|
||||||
assertFalse(crypto.verify(label, inputBytes2, publicKey, sig));
|
assertFalse(verify(label, inputBytes2, publicKey, sig));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -103,8 +112,8 @@ public class SignatureTest extends BrambleTestCase {
|
|||||||
// Generate a second label
|
// Generate a second label
|
||||||
String label2 = StringUtils.getRandomString(42);
|
String label2 = StringUtils.getRandomString(42);
|
||||||
// calculate the signature with different label, should fail to verify
|
// calculate the signature with different label, should fail to verify
|
||||||
byte[] sig = crypto.sign(label, inputBytes, privateKey);
|
byte[] sig = sign(label, inputBytes, privateKey);
|
||||||
assertFalse(crypto.verify(label2, inputBytes, publicKey, sig));
|
assertFalse(verify(label2, inputBytes, publicKey, sig));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ package org.briarproject.bramble.crypto;
|
|||||||
import org.briarproject.bramble.api.Bytes;
|
import org.briarproject.bramble.api.Bytes;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
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.test.BrambleTestCase;
|
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
||||||
import org.briarproject.bramble.test.TestSecureRandomProvider;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.briarproject.bramble.test.TestUtils;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@@ -14,25 +13,25 @@ import java.util.Set;
|
|||||||
import static junit.framework.TestCase.assertTrue;
|
import static junit.framework.TestCase.assertTrue;
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
|
import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||||
|
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||||
|
|
||||||
public class TagEncodingTest extends BrambleTestCase {
|
public class TagEncodingTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
private final CryptoComponent crypto;
|
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||||
private final SecretKey tagKey;
|
|
||||||
|
private final TransportCrypto transportCrypto =
|
||||||
|
new TransportCryptoImpl(crypto);
|
||||||
|
private final SecretKey tagKey = getSecretKey();
|
||||||
private final long streamNumber = 1234567890;
|
private final long streamNumber = 1234567890;
|
||||||
|
|
||||||
public TagEncodingTest() {
|
|
||||||
crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
|
|
||||||
tagKey = TestUtils.getSecretKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testKeyAffectsTag() throws Exception {
|
public void testKeyAffectsTag() throws Exception {
|
||||||
Set<Bytes> set = new HashSet<>();
|
Set<Bytes> set = new HashSet<>();
|
||||||
for (int i = 0; i < 100; i++) {
|
for (int i = 0; i < 100; i++) {
|
||||||
byte[] tag = new byte[TAG_LENGTH];
|
byte[] tag = new byte[TAG_LENGTH];
|
||||||
SecretKey tagKey = TestUtils.getSecretKey();
|
SecretKey tagKey = getSecretKey();
|
||||||
crypto.encodeTag(tag, tagKey, PROTOCOL_VERSION, streamNumber);
|
transportCrypto.encodeTag(tag, tagKey, PROTOCOL_VERSION,
|
||||||
|
streamNumber);
|
||||||
assertTrue(set.add(new Bytes(tag)));
|
assertTrue(set.add(new Bytes(tag)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,7 +41,8 @@ public class TagEncodingTest extends BrambleTestCase {
|
|||||||
Set<Bytes> set = new HashSet<>();
|
Set<Bytes> set = new HashSet<>();
|
||||||
for (int i = 0; i < 100; i++) {
|
for (int i = 0; i < 100; i++) {
|
||||||
byte[] tag = new byte[TAG_LENGTH];
|
byte[] tag = new byte[TAG_LENGTH];
|
||||||
crypto.encodeTag(tag, tagKey, PROTOCOL_VERSION + i, streamNumber);
|
transportCrypto.encodeTag(tag, tagKey, PROTOCOL_VERSION + i,
|
||||||
|
streamNumber);
|
||||||
assertTrue(set.add(new Bytes(tag)));
|
assertTrue(set.add(new Bytes(tag)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,8 @@ public class TagEncodingTest extends BrambleTestCase {
|
|||||||
Set<Bytes> set = new HashSet<>();
|
Set<Bytes> set = new HashSet<>();
|
||||||
for (int i = 0; i < 100; i++) {
|
for (int i = 0; i < 100; i++) {
|
||||||
byte[] tag = new byte[TAG_LENGTH];
|
byte[] tag = new byte[TAG_LENGTH];
|
||||||
crypto.encodeTag(tag, tagKey, PROTOCOL_VERSION, streamNumber + i);
|
transportCrypto.encodeTag(tag, tagKey, PROTOCOL_VERSION,
|
||||||
|
streamNumber + i);
|
||||||
assertTrue(set.add(new Bytes(tag)));
|
assertTrue(set.add(new Bytes(tag)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,380 @@
|
|||||||
|
package org.briarproject.bramble.db;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
|
import org.briarproject.bramble.test.TestUtils;
|
||||||
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static java.sql.Types.BINARY;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
public abstract class BasicDatabaseTest extends BrambleTestCase {
|
||||||
|
|
||||||
|
private static final int BATCH_SIZE = 100;
|
||||||
|
|
||||||
|
private final File testDir = TestUtils.getTestDirectory();
|
||||||
|
private final File db = new File(testDir, "db");
|
||||||
|
|
||||||
|
protected abstract String getBinaryType();
|
||||||
|
|
||||||
|
protected abstract String getDriverName();
|
||||||
|
|
||||||
|
protected abstract Connection openConnection(File db, boolean encrypt)
|
||||||
|
throws SQLException;
|
||||||
|
|
||||||
|
protected abstract void shutdownDatabase(File db, boolean encrypt)
|
||||||
|
throws SQLException;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
testDir.mkdirs();
|
||||||
|
Class.forName(getDriverName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInsertUpdateAndDelete() throws Exception {
|
||||||
|
Connection connection = openConnection(db, false);
|
||||||
|
try {
|
||||||
|
// Create the table
|
||||||
|
createTable(connection);
|
||||||
|
// Generate an ID and two names
|
||||||
|
byte[] id = TestUtils.getRandomId();
|
||||||
|
String oldName = StringUtils.getRandomString(50);
|
||||||
|
String newName = StringUtils.getRandomString(50);
|
||||||
|
// Insert the ID and old name into the table
|
||||||
|
insertRow(connection, id, oldName);
|
||||||
|
// Check that the old name can be retrieved using the ID
|
||||||
|
assertTrue(rowExists(connection, id));
|
||||||
|
assertEquals(oldName, getName(connection, id));
|
||||||
|
// Update the name
|
||||||
|
updateRow(connection, id, newName);
|
||||||
|
// Check that the new name can be retrieved using the ID
|
||||||
|
assertTrue(rowExists(connection, id));
|
||||||
|
assertEquals(newName, getName(connection, id));
|
||||||
|
// Delete the row from the table
|
||||||
|
assertTrue(deleteRow(connection, id));
|
||||||
|
// Check that the row no longer exists
|
||||||
|
assertFalse(rowExists(connection, id));
|
||||||
|
// Deleting the row again should have no effect
|
||||||
|
assertFalse(deleteRow(connection, id));
|
||||||
|
} finally {
|
||||||
|
connection.close();
|
||||||
|
shutdownDatabase(db, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBatchInsertUpdateAndDelete() throws Exception {
|
||||||
|
Connection connection = openConnection(db, false);
|
||||||
|
try {
|
||||||
|
// Create the table
|
||||||
|
createTable(connection);
|
||||||
|
// Generate some IDs and two sets of names
|
||||||
|
byte[][] ids = new byte[BATCH_SIZE][];
|
||||||
|
String[] oldNames = new String[BATCH_SIZE];
|
||||||
|
String[] newNames = new String[BATCH_SIZE];
|
||||||
|
for (int i = 0; i < BATCH_SIZE; i++) {
|
||||||
|
ids[i] = TestUtils.getRandomId();
|
||||||
|
oldNames[i] = StringUtils.getRandomString(50);
|
||||||
|
newNames[i] = StringUtils.getRandomString(50);
|
||||||
|
}
|
||||||
|
// Insert the IDs and old names into the table as a batch
|
||||||
|
insertBatch(connection, ids, oldNames);
|
||||||
|
// Update the names as a batch
|
||||||
|
updateBatch(connection, ids, newNames);
|
||||||
|
// Check that the new names can be retrieved using the IDs
|
||||||
|
for (int i = 0; i < BATCH_SIZE; i++) {
|
||||||
|
assertTrue(rowExists(connection, ids[i]));
|
||||||
|
assertEquals(newNames[i], getName(connection, ids[i]));
|
||||||
|
}
|
||||||
|
// Delete the rows as a batch
|
||||||
|
boolean[] deleted = deleteBatch(connection, ids);
|
||||||
|
// Check that the rows no longer exist
|
||||||
|
for (int i = 0; i < BATCH_SIZE; i++) {
|
||||||
|
assertTrue(deleted[i]);
|
||||||
|
assertFalse(rowExists(connection, ids[i]));
|
||||||
|
}
|
||||||
|
// Deleting the rows again should have no effect
|
||||||
|
deleted = deleteBatch(connection, ids);
|
||||||
|
for (int i = 0; i < BATCH_SIZE; i++) assertFalse(deleted[i]);
|
||||||
|
} finally {
|
||||||
|
connection.close();
|
||||||
|
shutdownDatabase(db, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSortOrder() throws Exception {
|
||||||
|
byte[] first = new byte[] {
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0
|
||||||
|
};
|
||||||
|
byte[] second = new byte[] {
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 127
|
||||||
|
};
|
||||||
|
byte[] third = new byte[] {
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, (byte) 255
|
||||||
|
};
|
||||||
|
Connection connection = openConnection(db, false);
|
||||||
|
try {
|
||||||
|
// Create the table
|
||||||
|
createTable(connection);
|
||||||
|
// Insert the rows
|
||||||
|
insertRow(connection, first, "first");
|
||||||
|
insertRow(connection, second, "second");
|
||||||
|
insertRow(connection, third, "third");
|
||||||
|
insertRow(connection, null, "null");
|
||||||
|
// Check the ordering of the < operator: null is not comparable
|
||||||
|
assertNull(getPredecessor(connection, first));
|
||||||
|
assertEquals("first", getPredecessor(connection, second));
|
||||||
|
assertEquals("second", getPredecessor(connection, third));
|
||||||
|
assertNull(getPredecessor(connection, null));
|
||||||
|
// Check the ordering of ORDER BY: nulls come first
|
||||||
|
List<String> names = getNames(connection);
|
||||||
|
assertEquals(4, names.size());
|
||||||
|
assertEquals("null", names.get(0));
|
||||||
|
assertEquals("first", names.get(1));
|
||||||
|
assertEquals("second", names.get(2));
|
||||||
|
assertEquals("third", names.get(3));
|
||||||
|
} finally {
|
||||||
|
connection.close();
|
||||||
|
shutdownDatabase(db, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDataIsFoundWithoutEncryption() throws Exception {
|
||||||
|
testEncryption(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDataIsNotFoundWithEncryption() throws Exception {
|
||||||
|
testEncryption(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testEncryption(boolean encrypt) throws Exception {
|
||||||
|
byte[] sequence = new byte[] {'a', 'b', 'c', 'd', 'e', 'f', 'g'};
|
||||||
|
Connection connection = openConnection(db, encrypt);
|
||||||
|
try {
|
||||||
|
createTable(connection);
|
||||||
|
insertRow(connection, sequence, "abcdefg");
|
||||||
|
} finally {
|
||||||
|
connection.close();
|
||||||
|
shutdownDatabase(db, encrypt);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (findSequence(testDir, sequence) == encrypt) fail();
|
||||||
|
} finally {
|
||||||
|
shutdownDatabase(db, encrypt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createTable(Connection connection) throws SQLException {
|
||||||
|
Statement s = connection.createStatement();
|
||||||
|
String sql = "CREATE TABLE foo (uniqueId " + getBinaryType() + ","
|
||||||
|
+ " name VARCHAR(100) NOT NULL)";
|
||||||
|
s.executeUpdate(sql);
|
||||||
|
s.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertRow(Connection connection, byte[] id, String name)
|
||||||
|
throws SQLException {
|
||||||
|
String sql = "INSERT INTO foo (uniqueId, name) VALUES (?, ?)";
|
||||||
|
PreparedStatement ps = connection.prepareStatement(sql);
|
||||||
|
if (id == null) ps.setNull(1, BINARY);
|
||||||
|
else ps.setBytes(1, id);
|
||||||
|
ps.setString(2, name);
|
||||||
|
int affected = ps.executeUpdate();
|
||||||
|
assertEquals(1, affected);
|
||||||
|
ps.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean rowExists(Connection connection, byte[] id)
|
||||||
|
throws SQLException {
|
||||||
|
assertNotNull(id);
|
||||||
|
String sql = "SELECT * FROM foo WHERE uniqueID = ?";
|
||||||
|
PreparedStatement ps = connection.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, id);
|
||||||
|
ResultSet rs = ps.executeQuery();
|
||||||
|
boolean found = rs.next();
|
||||||
|
assertFalse(rs.next());
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getName(Connection connection, byte[] id)
|
||||||
|
throws SQLException {
|
||||||
|
assertNotNull(id);
|
||||||
|
String sql = "SELECT name FROM foo WHERE uniqueID = ?";
|
||||||
|
PreparedStatement ps = connection.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, id);
|
||||||
|
ResultSet rs = ps.executeQuery();
|
||||||
|
assertTrue(rs.next());
|
||||||
|
String name = rs.getString(1);
|
||||||
|
assertFalse(rs.next());
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateRow(Connection connection, byte[] id, String name)
|
||||||
|
throws SQLException {
|
||||||
|
String sql = "UPDATE foo SET name = ? WHERE uniqueId = ?";
|
||||||
|
PreparedStatement ps = connection.prepareStatement(sql);
|
||||||
|
if (id == null) ps.setNull(2, BINARY);
|
||||||
|
else ps.setBytes(2, id);
|
||||||
|
ps.setString(1, name);
|
||||||
|
assertEquals(1, ps.executeUpdate());
|
||||||
|
ps.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean deleteRow(Connection connection, byte[] id)
|
||||||
|
throws SQLException {
|
||||||
|
String sql = "DELETE FROM foo WHERE uniqueId = ?";
|
||||||
|
PreparedStatement ps = connection.prepareStatement(sql);
|
||||||
|
if (id == null) ps.setNull(1, BINARY);
|
||||||
|
else ps.setBytes(1, id);
|
||||||
|
int affected = ps.executeUpdate();
|
||||||
|
ps.close();
|
||||||
|
return affected == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertBatch(Connection connection, byte[][] ids,
|
||||||
|
String[] names) throws SQLException {
|
||||||
|
assertEquals(ids.length, names.length);
|
||||||
|
String sql = "INSERT INTO foo (uniqueId, name) VALUES (?, ?)";
|
||||||
|
PreparedStatement ps = connection.prepareStatement(sql);
|
||||||
|
for (int i = 0; i < ids.length; i++) {
|
||||||
|
if (ids[i] == null) ps.setNull(1, BINARY);
|
||||||
|
else ps.setBytes(1, ids[i]);
|
||||||
|
ps.setString(2, names[i]);
|
||||||
|
ps.addBatch();
|
||||||
|
}
|
||||||
|
int[] batchAffected = ps.executeBatch();
|
||||||
|
assertEquals(ids.length, batchAffected.length);
|
||||||
|
for (int affected : batchAffected) assertEquals(1, affected);
|
||||||
|
ps.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBatch(Connection connection, byte[][] ids,
|
||||||
|
String[] names) throws SQLException {
|
||||||
|
assertEquals(ids.length, names.length);
|
||||||
|
String sql = "UPDATE foo SET name = ? WHERE uniqueId = ?";
|
||||||
|
PreparedStatement ps = connection.prepareStatement(sql);
|
||||||
|
for (int i = 0; i < ids.length; i++) {
|
||||||
|
if (ids[i] == null) ps.setNull(2, BINARY);
|
||||||
|
else ps.setBytes(2, ids[i]);
|
||||||
|
ps.setString(1, names[i]);
|
||||||
|
ps.addBatch();
|
||||||
|
}
|
||||||
|
int[] batchAffected = ps.executeBatch();
|
||||||
|
assertEquals(ids.length, batchAffected.length);
|
||||||
|
for (int affected : batchAffected) assertEquals(1, affected);
|
||||||
|
ps.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean[] deleteBatch(Connection connection, byte[][] ids)
|
||||||
|
throws SQLException {
|
||||||
|
String sql = "DELETE FROM foo WHERE uniqueId = ?";
|
||||||
|
PreparedStatement ps = connection.prepareStatement(sql);
|
||||||
|
for (byte[] id : ids) {
|
||||||
|
if (id == null) ps.setNull(1, BINARY);
|
||||||
|
else ps.setBytes(1, id);
|
||||||
|
ps.addBatch();
|
||||||
|
}
|
||||||
|
int[] batchAffected = ps.executeBatch();
|
||||||
|
assertEquals(ids.length, batchAffected.length);
|
||||||
|
boolean[] ret = new boolean[ids.length];
|
||||||
|
for (int i = 0; i < batchAffected.length; i++)
|
||||||
|
ret[i] = batchAffected[i] == 1;
|
||||||
|
ps.close();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPredecessor(Connection connection, byte[] id)
|
||||||
|
throws SQLException {
|
||||||
|
String sql = "SELECT name FROM foo WHERE uniqueId < ?"
|
||||||
|
+ " ORDER BY uniqueId DESC";
|
||||||
|
PreparedStatement ps = connection.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, id);
|
||||||
|
ps.setMaxRows(1);
|
||||||
|
ResultSet rs = ps.executeQuery();
|
||||||
|
String name = rs.next() ? rs.getString(1) : null;
|
||||||
|
assertFalse(rs.next());
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getNames(Connection connection) throws SQLException {
|
||||||
|
String sql = "SELECT name FROM foo ORDER BY uniqueId NULLS FIRST";
|
||||||
|
List<String> names = new ArrayList<>();
|
||||||
|
PreparedStatement ps = connection.prepareStatement(sql);
|
||||||
|
ResultSet rs = ps.executeQuery();
|
||||||
|
while (rs.next()) names.add(rs.getString(1));
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean findSequence(File f, byte[] sequence) throws IOException {
|
||||||
|
if (f.isDirectory()) {
|
||||||
|
File[] children = f.listFiles();
|
||||||
|
if (children != null)
|
||||||
|
for (File child : children)
|
||||||
|
if (findSequence(child, sequence)) return true;
|
||||||
|
return false;
|
||||||
|
} else if (f.isFile()) {
|
||||||
|
FileInputStream in = new FileInputStream(f);
|
||||||
|
try {
|
||||||
|
int offset = 0;
|
||||||
|
while (true) {
|
||||||
|
int read = in.read();
|
||||||
|
if (read == -1) return false;
|
||||||
|
if (((byte) read) == sequence[offset]) {
|
||||||
|
offset++;
|
||||||
|
if (offset == sequence.length) return true;
|
||||||
|
} else {
|
||||||
|
offset = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
in.close();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() throws Exception {
|
||||||
|
TestUtils.deleteTestDirectory(testDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,345 +1,46 @@
|
|||||||
package org.briarproject.bramble.db;
|
package org.briarproject.bramble.db;
|
||||||
|
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.test.TestUtils;
|
import org.briarproject.bramble.test.TestUtils;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.DriverManager;
|
import java.sql.DriverManager;
|
||||||
import java.sql.PreparedStatement;
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.sql.Statement;
|
import java.util.Properties;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static java.sql.Types.BINARY;
|
public class BasicH2Test extends BasicDatabaseTest {
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.junit.Assert.assertNull;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
public class BasicH2Test extends BrambleTestCase {
|
private final SecretKey key = TestUtils.getSecretKey();
|
||||||
|
|
||||||
private static final String CREATE_TABLE =
|
@Override
|
||||||
"CREATE TABLE foo (uniqueId BINARY(32), name VARCHAR NOT NULL)";
|
protected String getBinaryType() {
|
||||||
private static final int BATCH_SIZE = 100;
|
return "BINARY(32)";
|
||||||
|
|
||||||
private final File testDir = TestUtils.getTestDirectory();
|
|
||||||
private final File db = new File(testDir, "db");
|
|
||||||
private final String url = "jdbc:h2:" + db.getAbsolutePath();
|
|
||||||
|
|
||||||
private Connection connection = null;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
testDir.mkdirs();
|
|
||||||
Class.forName("org.h2.Driver");
|
|
||||||
connection = DriverManager.getConnection(url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Override
|
||||||
public void testInsertUpdateAndDelete() throws Exception {
|
protected String getDriverName() {
|
||||||
// Create the table
|
return "org.h2.Driver";
|
||||||
createTable(connection);
|
|
||||||
// Generate an ID and two names
|
|
||||||
byte[] id = TestUtils.getRandomId();
|
|
||||||
String oldName = StringUtils.getRandomString(50);
|
|
||||||
String newName = StringUtils.getRandomString(50);
|
|
||||||
// Insert the ID and old name into the table
|
|
||||||
insertRow(id, oldName);
|
|
||||||
// Check that the old name can be retrieved using the ID
|
|
||||||
assertTrue(rowExists(id));
|
|
||||||
assertEquals(oldName, getName(id));
|
|
||||||
// Update the name
|
|
||||||
updateRow(id, newName);
|
|
||||||
// Check that the new name can be retrieved using the ID
|
|
||||||
assertTrue(rowExists(id));
|
|
||||||
assertEquals(newName, getName(id));
|
|
||||||
// Delete the row from the table
|
|
||||||
assertTrue(deleteRow(id));
|
|
||||||
// Check that the row no longer exists
|
|
||||||
assertFalse(rowExists(id));
|
|
||||||
// Deleting the row again should have no effect
|
|
||||||
assertFalse(deleteRow(id));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Override
|
||||||
public void testBatchInsertUpdateAndDelete() throws Exception {
|
protected Connection openConnection(File db, boolean encrypt)
|
||||||
// Create the table
|
throws SQLException {
|
||||||
createTable(connection);
|
String url = "jdbc:h2:split:" + db.getAbsolutePath();
|
||||||
// Generate some IDs and two sets of names
|
Properties props = new Properties();
|
||||||
byte[][] ids = new byte[BATCH_SIZE][];
|
props.setProperty("user", "user");
|
||||||
String[] oldNames = new String[BATCH_SIZE];
|
if (encrypt) {
|
||||||
String[] newNames = new String[BATCH_SIZE];
|
url += ";CIPHER=AES";
|
||||||
for (int i = 0; i < BATCH_SIZE; i++) {
|
String hex = StringUtils.toHexString(key.getBytes());
|
||||||
ids[i] = TestUtils.getRandomId();
|
props.setProperty("password", hex + " password");
|
||||||
oldNames[i] = StringUtils.getRandomString(50);
|
|
||||||
newNames[i] = StringUtils.getRandomString(50);
|
|
||||||
}
|
}
|
||||||
// Insert the IDs and old names into the table as a batch
|
return DriverManager.getConnection(url, props);
|
||||||
insertBatch(ids, oldNames);
|
|
||||||
// Update the names as a batch
|
|
||||||
updateBatch(ids, newNames);
|
|
||||||
// Check that the new names can be retrieved using the IDs
|
|
||||||
for (int i = 0; i < BATCH_SIZE; i++) {
|
|
||||||
assertTrue(rowExists(ids[i]));
|
|
||||||
assertEquals(newNames[i], getName(ids[i]));
|
|
||||||
}
|
|
||||||
// Delete the rows as a batch
|
|
||||||
boolean[] deleted = deleteBatch(ids);
|
|
||||||
// Check that the rows no longer exist
|
|
||||||
for (int i = 0; i < BATCH_SIZE; i++) {
|
|
||||||
assertTrue(deleted[i]);
|
|
||||||
assertFalse(rowExists(ids[i]));
|
|
||||||
}
|
|
||||||
// Deleting the rows again should have no effect
|
|
||||||
deleted = deleteBatch(ids);
|
|
||||||
for (int i = 0; i < BATCH_SIZE; i++) assertFalse(deleted[i]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Override
|
||||||
public void testSortOrder() throws Exception {
|
protected void shutdownDatabase(File db, boolean encrypt)
|
||||||
byte[] first = new byte[] {
|
throws SQLException {
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
// The DB is closed automatically when the connection is closed
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0
|
|
||||||
};
|
|
||||||
byte[] second = new byte[] {
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 127
|
|
||||||
};
|
|
||||||
byte[] third = new byte[] {
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, (byte) 255
|
|
||||||
};
|
|
||||||
// Create the table
|
|
||||||
createTable(connection);
|
|
||||||
// Insert the rows
|
|
||||||
insertRow(first, "first");
|
|
||||||
insertRow(second, "second");
|
|
||||||
insertRow(third, "third");
|
|
||||||
insertRow(null, "null");
|
|
||||||
// Check the ordering of the < operator: the null ID is not comparable
|
|
||||||
assertNull(getPredecessor(first));
|
|
||||||
assertEquals("first", getPredecessor(second));
|
|
||||||
assertEquals("second", getPredecessor(third));
|
|
||||||
assertNull(getPredecessor(null));
|
|
||||||
// Check the ordering of ORDER BY: nulls come first
|
|
||||||
List<String> names = getNames();
|
|
||||||
assertEquals(4, names.size());
|
|
||||||
assertEquals("null", names.get(0));
|
|
||||||
assertEquals("first", names.get(1));
|
|
||||||
assertEquals("second", names.get(2));
|
|
||||||
assertEquals("third", names.get(3));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createTable(Connection connection) throws SQLException {
|
|
||||||
try {
|
|
||||||
Statement s = connection.createStatement();
|
|
||||||
s.executeUpdate(CREATE_TABLE);
|
|
||||||
s.close();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
connection.close();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void insertRow(byte[] id, String name) throws SQLException {
|
|
||||||
String sql = "INSERT INTO foo (uniqueId, name) VALUES (?, ?)";
|
|
||||||
try {
|
|
||||||
PreparedStatement ps = connection.prepareStatement(sql);
|
|
||||||
if (id == null) ps.setNull(1, BINARY);
|
|
||||||
else ps.setBytes(1, id);
|
|
||||||
ps.setString(2, name);
|
|
||||||
int affected = ps.executeUpdate();
|
|
||||||
assertEquals(1, affected);
|
|
||||||
ps.close();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
connection.close();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean rowExists(byte[] id) throws SQLException {
|
|
||||||
assertNotNull(id);
|
|
||||||
String sql = "SELECT NULL FROM foo WHERE uniqueID = ?";
|
|
||||||
try {
|
|
||||||
PreparedStatement ps = connection.prepareStatement(sql);
|
|
||||||
ps.setBytes(1, id);
|
|
||||||
ResultSet rs = ps.executeQuery();
|
|
||||||
boolean found = rs.next();
|
|
||||||
assertFalse(rs.next());
|
|
||||||
rs.close();
|
|
||||||
ps.close();
|
|
||||||
return found;
|
|
||||||
} catch (SQLException e) {
|
|
||||||
connection.close();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getName(byte[] id) throws SQLException {
|
|
||||||
assertNotNull(id);
|
|
||||||
String sql = "SELECT name FROM foo WHERE uniqueID = ?";
|
|
||||||
try {
|
|
||||||
PreparedStatement ps = connection.prepareStatement(sql);
|
|
||||||
ps.setBytes(1, id);
|
|
||||||
ResultSet rs = ps.executeQuery();
|
|
||||||
assertTrue(rs.next());
|
|
||||||
String name = rs.getString(1);
|
|
||||||
assertFalse(rs.next());
|
|
||||||
rs.close();
|
|
||||||
ps.close();
|
|
||||||
return name;
|
|
||||||
} catch (SQLException e) {
|
|
||||||
connection.close();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateRow(byte[] id, String name) throws SQLException {
|
|
||||||
String sql = "UPDATE foo SET name = ? WHERE uniqueId = ?";
|
|
||||||
try {
|
|
||||||
PreparedStatement ps = connection.prepareStatement(sql);
|
|
||||||
if (id == null) ps.setNull(2, BINARY);
|
|
||||||
else ps.setBytes(2, id);
|
|
||||||
ps.setString(1, name);
|
|
||||||
assertEquals(1, ps.executeUpdate());
|
|
||||||
ps.close();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
connection.close();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean deleteRow(byte[] id) throws SQLException {
|
|
||||||
String sql = "DELETE FROM foo WHERE uniqueId = ?";
|
|
||||||
try {
|
|
||||||
PreparedStatement ps = connection.prepareStatement(sql);
|
|
||||||
if (id == null) ps.setNull(1, BINARY);
|
|
||||||
else ps.setBytes(1, id);
|
|
||||||
int affected = ps.executeUpdate();
|
|
||||||
ps.close();
|
|
||||||
return affected == 1;
|
|
||||||
} catch (SQLException e) {
|
|
||||||
connection.close();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void insertBatch(byte[][] ids, String[] names) throws SQLException {
|
|
||||||
assertEquals(ids.length, names.length);
|
|
||||||
String sql = "INSERT INTO foo (uniqueId, name) VALUES (?, ?)";
|
|
||||||
try {
|
|
||||||
PreparedStatement ps = connection.prepareStatement(sql);
|
|
||||||
for (int i = 0; i < ids.length; i++) {
|
|
||||||
if (ids[i] == null) ps.setNull(1, BINARY);
|
|
||||||
else ps.setBytes(1, ids[i]);
|
|
||||||
ps.setString(2, names[i]);
|
|
||||||
ps.addBatch();
|
|
||||||
}
|
|
||||||
int[] batchAffected = ps.executeBatch();
|
|
||||||
assertEquals(ids.length, batchAffected.length);
|
|
||||||
for (int affected : batchAffected) assertEquals(1, affected);
|
|
||||||
ps.close();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
connection.close();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateBatch(byte[][] ids, String[] names) throws SQLException {
|
|
||||||
assertEquals(ids.length, names.length);
|
|
||||||
String sql = "UPDATE foo SET name = ? WHERE uniqueId = ?";
|
|
||||||
try {
|
|
||||||
PreparedStatement ps = connection.prepareStatement(sql);
|
|
||||||
for (int i = 0; i < ids.length; i++) {
|
|
||||||
if (ids[i] == null) ps.setNull(2, BINARY);
|
|
||||||
else ps.setBytes(2, ids[i]);
|
|
||||||
ps.setString(1, names[i]);
|
|
||||||
ps.addBatch();
|
|
||||||
}
|
|
||||||
int[] batchAffected = ps.executeBatch();
|
|
||||||
assertEquals(ids.length, batchAffected.length);
|
|
||||||
for (int affected : batchAffected) assertEquals(1, affected);
|
|
||||||
ps.close();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
connection.close();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean[] deleteBatch(byte[][] ids) throws SQLException {
|
|
||||||
String sql = "DELETE FROM foo WHERE uniqueId = ?";
|
|
||||||
try {
|
|
||||||
PreparedStatement ps = connection.prepareStatement(sql);
|
|
||||||
for (byte[] id : ids) {
|
|
||||||
if (id == null) ps.setNull(1, BINARY);
|
|
||||||
else ps.setBytes(1, id);
|
|
||||||
ps.addBatch();
|
|
||||||
}
|
|
||||||
int[] batchAffected = ps.executeBatch();
|
|
||||||
assertEquals(ids.length, batchAffected.length);
|
|
||||||
boolean[] ret = new boolean[ids.length];
|
|
||||||
for (int i = 0; i < batchAffected.length; i++)
|
|
||||||
ret[i] = batchAffected[i] == 1;
|
|
||||||
ps.close();
|
|
||||||
return ret;
|
|
||||||
} catch (SQLException e) {
|
|
||||||
connection.close();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getPredecessor(byte[] id) throws SQLException {
|
|
||||||
String sql = "SELECT name FROM foo WHERE uniqueId < ?"
|
|
||||||
+ " ORDER BY uniqueId DESC LIMIT ?";
|
|
||||||
try {
|
|
||||||
PreparedStatement ps = connection.prepareStatement(sql);
|
|
||||||
ps.setBytes(1, id);
|
|
||||||
ps.setInt(2, 1);
|
|
||||||
ResultSet rs = ps.executeQuery();
|
|
||||||
String name = rs.next() ? rs.getString(1) : null;
|
|
||||||
assertFalse(rs.next());
|
|
||||||
rs.close();
|
|
||||||
ps.close();
|
|
||||||
return name;
|
|
||||||
} catch (SQLException e) {
|
|
||||||
connection.close();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> getNames() throws SQLException {
|
|
||||||
String sql = "SELECT name FROM foo ORDER BY uniqueId";
|
|
||||||
List<String> names = new ArrayList<>();
|
|
||||||
try {
|
|
||||||
PreparedStatement ps = connection.prepareStatement(sql);
|
|
||||||
ResultSet rs = ps.executeQuery();
|
|
||||||
while (rs.next()) names.add(rs.getString(1));
|
|
||||||
rs.close();
|
|
||||||
ps.close();
|
|
||||||
return names;
|
|
||||||
} catch (SQLException e) {
|
|
||||||
connection.close();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown() throws Exception {
|
|
||||||
if (connection != null) connection.close();
|
|
||||||
TestUtils.deleteTestDirectory(testDir);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package org.briarproject.bramble.db;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.bramble.test.TestUtils;
|
||||||
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
|
||||||
|
public class BasicHyperSqlTest extends BasicDatabaseTest {
|
||||||
|
|
||||||
|
private final SecretKey key = TestUtils.getSecretKey();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getBinaryType() {
|
||||||
|
return "BINARY(32)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getDriverName() {
|
||||||
|
return "org.hsqldb.jdbc.JDBCDriver";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Connection openConnection(File db, boolean encrypt)
|
||||||
|
throws SQLException {
|
||||||
|
String url = "jdbc:hsqldb:file:" + db.getAbsolutePath() +
|
||||||
|
";sql.enforce_size=false;allow_empty_batch=true";
|
||||||
|
if (encrypt) {
|
||||||
|
String hex = StringUtils.toHexString(key.getBytes());
|
||||||
|
url += ";encrypt_lobs=true;crypt_type=AES;crypt_key=" + hex;
|
||||||
|
}
|
||||||
|
return DriverManager.getConnection(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void shutdownDatabase(File db, boolean encrypt)
|
||||||
|
throws SQLException {
|
||||||
|
Connection c = openConnection(db, encrypt);
|
||||||
|
Statement s = c.createStatement();
|
||||||
|
s.executeQuery("SHUTDOWN");
|
||||||
|
s.close();
|
||||||
|
c.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user