mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +01:00
Compare commits
4 Commits
network-lo
...
beta-0.16.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9902c023ca | ||
|
|
e8baee6734 | ||
|
|
74e3fee7aa | ||
|
|
05aac696b7 |
23
.idea/runConfigurations/H2_Performance_Test.xml
generated
23
.idea/runConfigurations/H2_Performance_Test.xml
generated
@@ -1,23 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<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 1700
|
versionCode 1613
|
||||||
versionName "0.17.0"
|
versionName "0.16.13"
|
||||||
consumerProguardFiles 'proguard-rules.txt'
|
consumerProguardFiles 'proguard-rules.txt'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +43,6 @@ 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',
|
||||||
@@ -55,16 +54,16 @@ dependencyVerification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ext.torBinaryDir = 'src/main/res/raw'
|
ext.torBinaryDir = 'src/main/res/raw'
|
||||||
ext.torVersion = '0.2.9.14'
|
ext.torVersion = '0.2.9.12'
|
||||||
ext.geoipVersion = '2017-11-06'
|
ext.geoipVersion = '2017-09-06'
|
||||||
ext.torDownloadUrl = 'https://briarproject.org/build/'
|
ext.torDownloadUrl = 'https://briarproject.org/build/'
|
||||||
|
|
||||||
def torBinaries = [
|
def torBinaries = [
|
||||||
"tor_arm" : '1710ea6c47b7f4c1a88bdf4858c7893837635db10e8866854eed8d61629f50e8',
|
"tor_arm" : '8ed0b347ffed1d6a4d2fd14495118eb92be83e9cc06e057e15220dc288b31688',
|
||||||
"tor_arm_pie": '974e6949507db8fa2ea45231817c2c3677ed4ccf5488a2252317d744b0be1917',
|
"tor_arm_pie": '64403262511c29f462ca5e7c7621bfc3c944898364d1d5ad35a016bb8a034283',
|
||||||
"tor_x86" : '3a5e45b3f051fcda9353b098b7086e762ffe7ba9242f7d7c8bf6523faaa8b1e9',
|
"tor_x86" : '61e014607a2079bcf1646289c67bff6372b1aded6e1d8d83d7791efda9a4d5ab',
|
||||||
"tor_x86_pie": 'd1d96d8ce1a4b68accf04850185780d10cd5563d3552f7e1f040f8ca32cb4e51',
|
"tor_x86_pie": '18fbc98356697dd0895836ab46d5c9877d1c539193464f7db1e82a65adaaf288',
|
||||||
"geoip" : '8239b98374493529a29096e45fc5877d4d6fdad0146ad8380b291f90d61484ea'
|
"geoip" : 'fe49d3adb86d3c512373101422a017dbb86c85a570524663f09dd8ce143a24f3'
|
||||||
]
|
]
|
||||||
|
|
||||||
def downloadBinary(name) {
|
def downloadBinary(name) {
|
||||||
|
|||||||
@@ -8,10 +8,6 @@
|
|||||||
-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.**
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
|||||||
import org.briarproject.bramble.api.reporting.DevReporter;
|
import org.briarproject.bramble.api.reporting.DevReporter;
|
||||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
import org.briarproject.bramble.api.system.LocationUtils;
|
import org.briarproject.bramble.api.system.LocationUtils;
|
||||||
import org.briarproject.bramble.api.system.Scheduler;
|
import org.briarproject.bramble.plugin.droidtooth.DroidtoothPluginFactory;
|
||||||
import org.briarproject.bramble.plugin.bluetooth.AndroidBluetoothPluginFactory;
|
|
||||||
import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
|
import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory;
|
||||||
import org.briarproject.bramble.plugin.tor.TorPluginFactory;
|
import org.briarproject.bramble.plugin.tor.TorPluginFactory;
|
||||||
|
|
||||||
@@ -23,7 +22,6 @@ import java.util.Arrays;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
|
|
||||||
import javax.net.SocketFactory;
|
import javax.net.SocketFactory;
|
||||||
|
|
||||||
@@ -35,18 +33,16 @@ public class AndroidPluginModule {
|
|||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
PluginConfig providePluginConfig(@IoExecutor Executor ioExecutor,
|
PluginConfig providePluginConfig(@IoExecutor Executor ioExecutor,
|
||||||
@Scheduler ScheduledExecutorService scheduler,
|
|
||||||
AndroidExecutor androidExecutor, SecureRandom random,
|
AndroidExecutor androidExecutor, SecureRandom random,
|
||||||
SocketFactory torSocketFactory, BackoffFactory backoffFactory,
|
SocketFactory torSocketFactory, BackoffFactory backoffFactory,
|
||||||
Application app, LocationUtils locationUtils, DevReporter reporter,
|
Application app, LocationUtils locationUtils, DevReporter reporter,
|
||||||
EventBus eventBus) {
|
EventBus eventBus) {
|
||||||
Context appContext = app.getApplicationContext();
|
Context appContext = app.getApplicationContext();
|
||||||
DuplexPluginFactory bluetooth =
|
DuplexPluginFactory bluetooth = new DroidtoothPluginFactory(ioExecutor,
|
||||||
new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor,
|
androidExecutor, appContext, random, eventBus, backoffFactory);
|
||||||
appContext, random, eventBus, backoffFactory);
|
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, appContext,
|
||||||
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, scheduler,
|
locationUtils, reporter, eventBus, torSocketFactory,
|
||||||
appContext, locationUtils, reporter, eventBus,
|
backoffFactory);
|
||||||
torSocketFactory, backoffFactory);
|
|
||||||
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
|
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
|
||||||
backoffFactory, appContext);
|
backoffFactory, appContext);
|
||||||
Collection<DuplexPluginFactory> duplex =
|
Collection<DuplexPluginFactory> duplex =
|
||||||
|
|||||||
@@ -1,206 +0,0 @@
|
|||||||
package org.briarproject.bramble.plugin.bluetooth;
|
|
||||||
|
|
||||||
import android.bluetooth.BluetoothAdapter;
|
|
||||||
import android.bluetooth.BluetoothDevice;
|
|
||||||
import android.bluetooth.BluetoothServerSocket;
|
|
||||||
import android.bluetooth.BluetoothSocket;
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.IntentFilter;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.plugin.Backoff;
|
|
||||||
import org.briarproject.bramble.api.plugin.PluginException;
|
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
|
||||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
|
||||||
import org.briarproject.bramble.util.AndroidUtils;
|
|
||||||
|
|
||||||
import java.io.Closeable;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.STATE_OFF;
|
|
||||||
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
|
||||||
@ParametersNotNullByDefault
|
|
||||||
class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
Logger.getLogger(AndroidBluetoothPlugin.class.getName());
|
|
||||||
|
|
||||||
private final AndroidExecutor androidExecutor;
|
|
||||||
private final Context appContext;
|
|
||||||
|
|
||||||
private volatile boolean wasEnabledByUs = false;
|
|
||||||
private volatile BluetoothStateReceiver receiver = null;
|
|
||||||
|
|
||||||
// Non-null if the plugin started successfully
|
|
||||||
private volatile BluetoothAdapter adapter = null;
|
|
||||||
|
|
||||||
AndroidBluetoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor,
|
|
||||||
Context appContext, SecureRandom secureRandom, Backoff backoff,
|
|
||||||
DuplexPluginCallback callback, int maxLatency) {
|
|
||||||
super(ioExecutor, secureRandom, backoff, callback, maxLatency);
|
|
||||||
this.androidExecutor = androidExecutor;
|
|
||||||
this.appContext = appContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() throws PluginException {
|
|
||||||
super.start();
|
|
||||||
// Listen for changes to the Bluetooth state
|
|
||||||
IntentFilter filter = new IntentFilter();
|
|
||||||
filter.addAction(ACTION_STATE_CHANGED);
|
|
||||||
filter.addAction(ACTION_SCAN_MODE_CHANGED);
|
|
||||||
receiver = new BluetoothStateReceiver();
|
|
||||||
appContext.registerReceiver(receiver, filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stop() {
|
|
||||||
super.stop();
|
|
||||||
if (receiver != null) appContext.unregisterReceiver(receiver);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void initialiseAdapter() throws IOException {
|
|
||||||
// BluetoothAdapter.getDefaultAdapter() must be called on a thread
|
|
||||||
// with a message queue, so submit it to the AndroidExecutor
|
|
||||||
try {
|
|
||||||
adapter = androidExecutor.runOnBackgroundThread(
|
|
||||||
BluetoothAdapter::getDefaultAdapter).get();
|
|
||||||
} catch (InterruptedException | ExecutionException e) {
|
|
||||||
throw new IOException(e);
|
|
||||||
}
|
|
||||||
if (adapter == null)
|
|
||||||
throw new IOException("Bluetooth is not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
boolean isAdapterEnabled() {
|
|
||||||
return adapter != null && adapter.isEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void enableAdapter() {
|
|
||||||
if (adapter != null && !adapter.isEnabled()) {
|
|
||||||
if (adapter.enable()) {
|
|
||||||
LOG.info("Enabling Bluetooth");
|
|
||||||
wasEnabledByUs = true;
|
|
||||||
} else {
|
|
||||||
LOG.info("Could not enable Bluetooth");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void disableAdapterIfEnabledByUs() {
|
|
||||||
if (isAdapterEnabled() && wasEnabledByUs) {
|
|
||||||
if (adapter.disable()) LOG.info("Disabling Bluetooth");
|
|
||||||
else LOG.info("Could not disable Bluetooth");
|
|
||||||
wasEnabledByUs = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void setEnabledByUs() {
|
|
||||||
wasEnabledByUs = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Nullable
|
|
||||||
String getBluetoothAddress() {
|
|
||||||
String address = AndroidUtils.getBluetoothAddress(appContext, adapter);
|
|
||||||
return address.isEmpty() ? null : address;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
BluetoothServerSocket openServerSocket(String uuid) throws IOException {
|
|
||||||
return adapter.listenUsingInsecureRfcommWithServiceRecord(
|
|
||||||
"RFCOMM", UUID.fromString(uuid));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void tryToClose(@Nullable BluetoothServerSocket ss) {
|
|
||||||
try {
|
|
||||||
if (ss != null) ss.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
DuplexTransportConnection acceptConnection(BluetoothServerSocket ss)
|
|
||||||
throws IOException {
|
|
||||||
return wrapSocket(ss.accept());
|
|
||||||
}
|
|
||||||
|
|
||||||
private DuplexTransportConnection wrapSocket(BluetoothSocket s) {
|
|
||||||
return new AndroidBluetoothTransportConnection(this, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
boolean isValidAddress(String address) {
|
|
||||||
return BluetoothAdapter.checkBluetoothAddress(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
DuplexTransportConnection connectTo(String address, String uuid)
|
|
||||||
throws IOException {
|
|
||||||
BluetoothDevice d = adapter.getRemoteDevice(address);
|
|
||||||
UUID u = UUID.fromString(uuid);
|
|
||||||
BluetoothSocket s = null;
|
|
||||||
try {
|
|
||||||
s = d.createInsecureRfcommSocketToServiceRecord(u);
|
|
||||||
s.connect();
|
|
||||||
return wrapSocket(s);
|
|
||||||
} catch (IOException e) {
|
|
||||||
tryToClose(s);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tryToClose(@Nullable Closeable c) {
|
|
||||||
try {
|
|
||||||
if (c != null) c.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class BluetoothStateReceiver extends BroadcastReceiver {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context ctx, Intent intent) {
|
|
||||||
int state = intent.getIntExtra(EXTRA_STATE, 0);
|
|
||||||
if (state == STATE_ON) onAdapterEnabled();
|
|
||||||
else if (state == STATE_OFF) onAdapterDisabled();
|
|
||||||
int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, 0);
|
|
||||||
if (scanMode == SCAN_MODE_NONE) {
|
|
||||||
LOG.info("Scan mode: None");
|
|
||||||
} else if (scanMode == SCAN_MODE_CONNECTABLE) {
|
|
||||||
LOG.info("Scan mode: Connectable");
|
|
||||||
} else if (scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
|
|
||||||
LOG.info("Scan mode: Discoverable");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,490 @@
|
|||||||
|
package org.briarproject.bramble.plugin.droidtooth;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
import android.bluetooth.BluetoothDevice;
|
||||||
|
import android.bluetooth.BluetoothServerSocket;
|
||||||
|
import android.bluetooth.BluetoothSocket;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
|
import org.briarproject.bramble.api.event.Event;
|
||||||
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||||
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.Backoff;
|
||||||
|
import org.briarproject.bramble.api.plugin.PluginException;
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||||
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback;
|
||||||
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
|
import org.briarproject.bramble.api.plugin.event.DisableBluetoothEvent;
|
||||||
|
import org.briarproject.bramble.api.plugin.event.EnableBluetoothEvent;
|
||||||
|
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||||
|
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||||
|
import org.briarproject.bramble.util.AndroidUtils;
|
||||||
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.EXTRA_SCAN_MODE;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.STATE_OFF;
|
||||||
|
import static android.bluetooth.BluetoothAdapter.STATE_ON;
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
|
||||||
|
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
|
||||||
|
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENABLE;
|
||||||
|
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS;
|
||||||
|
import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
|
||||||
|
import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES;
|
||||||
|
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
|
||||||
|
|
||||||
|
@MethodsNotNullByDefault
|
||||||
|
@ParametersNotNullByDefault
|
||||||
|
class DroidtoothPlugin implements DuplexPlugin, EventListener {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
Logger.getLogger(DroidtoothPlugin.class.getName());
|
||||||
|
|
||||||
|
private final Executor ioExecutor;
|
||||||
|
private final AndroidExecutor androidExecutor;
|
||||||
|
private final Context appContext;
|
||||||
|
private final SecureRandom secureRandom;
|
||||||
|
private final Backoff backoff;
|
||||||
|
private final DuplexPluginCallback callback;
|
||||||
|
private final int maxLatency;
|
||||||
|
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
private volatile boolean running = false;
|
||||||
|
private volatile boolean wasEnabledByUs = false;
|
||||||
|
private volatile BluetoothStateReceiver receiver = null;
|
||||||
|
private volatile BluetoothServerSocket socket = null;
|
||||||
|
|
||||||
|
// Non-null if the plugin started successfully
|
||||||
|
private volatile BluetoothAdapter adapter = null;
|
||||||
|
|
||||||
|
DroidtoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor,
|
||||||
|
Context appContext, SecureRandom secureRandom, Backoff backoff,
|
||||||
|
DuplexPluginCallback callback, int maxLatency) {
|
||||||
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.androidExecutor = androidExecutor;
|
||||||
|
this.appContext = appContext;
|
||||||
|
this.secureRandom = secureRandom;
|
||||||
|
this.backoff = backoff;
|
||||||
|
this.callback = callback;
|
||||||
|
this.maxLatency = maxLatency;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransportId getId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxLatency() {
|
||||||
|
return maxLatency;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxIdleTime() {
|
||||||
|
// Bluetooth detects dead connections so we don't need keepalives
|
||||||
|
return Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() throws PluginException {
|
||||||
|
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||||
|
// BluetoothAdapter.getDefaultAdapter() must be called on a thread
|
||||||
|
// with a message queue, so submit it to the AndroidExecutor
|
||||||
|
try {
|
||||||
|
adapter = androidExecutor.runOnBackgroundThread(
|
||||||
|
BluetoothAdapter::getDefaultAdapter).get();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
LOG.warning("Interrupted while getting BluetoothAdapter");
|
||||||
|
throw new PluginException(e);
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
throw new PluginException(e);
|
||||||
|
}
|
||||||
|
if (adapter == null) {
|
||||||
|
LOG.info("Bluetooth is not supported");
|
||||||
|
throw new PluginException();
|
||||||
|
}
|
||||||
|
running = true;
|
||||||
|
// Listen for changes to the Bluetooth state
|
||||||
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addAction(ACTION_STATE_CHANGED);
|
||||||
|
filter.addAction(ACTION_SCAN_MODE_CHANGED);
|
||||||
|
receiver = new BluetoothStateReceiver();
|
||||||
|
appContext.registerReceiver(receiver, filter);
|
||||||
|
// If Bluetooth is enabled, bind a socket
|
||||||
|
if (adapter.isEnabled()) {
|
||||||
|
bind();
|
||||||
|
} else {
|
||||||
|
// Enable Bluetooth if settings allow
|
||||||
|
if (callback.getSettings().getBoolean(PREF_BT_ENABLE, false)) {
|
||||||
|
enableAdapter();
|
||||||
|
} else {
|
||||||
|
LOG.info("Not enabling Bluetooth");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bind() {
|
||||||
|
ioExecutor.execute(() -> {
|
||||||
|
if (!isRunning()) return;
|
||||||
|
String address = AndroidUtils.getBluetoothAddress(appContext,
|
||||||
|
adapter);
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Local address " + scrubMacAddress(address));
|
||||||
|
if (!StringUtils.isNullOrEmpty(address)) {
|
||||||
|
// Advertise the Bluetooth address to contacts
|
||||||
|
TransportProperties p = new TransportProperties();
|
||||||
|
p.put(PROP_ADDRESS, address);
|
||||||
|
callback.mergeLocalProperties(p);
|
||||||
|
}
|
||||||
|
// Bind a server socket to accept connections from contacts
|
||||||
|
BluetoothServerSocket ss;
|
||||||
|
try {
|
||||||
|
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
|
||||||
|
"RFCOMM", getUuid());
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isRunning()) {
|
||||||
|
tryToClose(ss);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOG.info("Socket bound");
|
||||||
|
socket = ss;
|
||||||
|
backoff.reset();
|
||||||
|
callback.transportEnabled();
|
||||||
|
acceptContactConnections();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private UUID getUuid() {
|
||||||
|
String uuid = callback.getLocalProperties().get(PROP_UUID);
|
||||||
|
if (uuid == null) {
|
||||||
|
byte[] random = new byte[UUID_BYTES];
|
||||||
|
secureRandom.nextBytes(random);
|
||||||
|
uuid = UUID.nameUUIDFromBytes(random).toString();
|
||||||
|
TransportProperties p = new TransportProperties();
|
||||||
|
p.put(PROP_UUID, uuid);
|
||||||
|
callback.mergeLocalProperties(p);
|
||||||
|
}
|
||||||
|
return UUID.fromString(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryToClose(@Nullable BluetoothServerSocket ss) {
|
||||||
|
try {
|
||||||
|
if (ss != null) ss.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
|
} finally {
|
||||||
|
callback.transportDisabled();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void acceptContactConnections() {
|
||||||
|
while (isRunning()) {
|
||||||
|
BluetoothSocket s;
|
||||||
|
try {
|
||||||
|
s = socket.accept();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// This is expected when the socket is closed
|
||||||
|
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
String address = s.getRemoteDevice().getAddress();
|
||||||
|
LOG.info("Connection from " + scrubMacAddress(address));
|
||||||
|
}
|
||||||
|
backoff.reset();
|
||||||
|
callback.incomingConnectionCreated(wrapSocket(s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DuplexTransportConnection wrapSocket(BluetoothSocket s) {
|
||||||
|
return new DroidtoothTransportConnection(this, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enableAdapter() {
|
||||||
|
if (adapter != null && !adapter.isEnabled()) {
|
||||||
|
if (adapter.enable()) {
|
||||||
|
LOG.info("Enabling Bluetooth");
|
||||||
|
wasEnabledByUs = true;
|
||||||
|
} else {
|
||||||
|
LOG.info("Could not enable Bluetooth");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
running = false;
|
||||||
|
if (receiver != null) appContext.unregisterReceiver(receiver);
|
||||||
|
tryToClose(socket);
|
||||||
|
disableAdapter();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void disableAdapter() {
|
||||||
|
if (adapter != null && adapter.isEnabled() && wasEnabledByUs) {
|
||||||
|
if (adapter.disable()) LOG.info("Disabling Bluetooth");
|
||||||
|
else LOG.info("Could not disable Bluetooth");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRunning() {
|
||||||
|
return running && adapter != null && adapter.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldPoll() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPollingInterval() {
|
||||||
|
return backoff.getPollingInterval();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void poll(Collection<ContactId> connected) {
|
||||||
|
if (!isRunning()) return;
|
||||||
|
backoff.increment();
|
||||||
|
// Try to connect to known devices in parallel
|
||||||
|
Map<ContactId, TransportProperties> remote =
|
||||||
|
callback.getRemoteProperties();
|
||||||
|
for (Entry<ContactId, TransportProperties> e : remote.entrySet()) {
|
||||||
|
ContactId c = e.getKey();
|
||||||
|
if (connected.contains(c)) continue;
|
||||||
|
String address = e.getValue().get(PROP_ADDRESS);
|
||||||
|
if (StringUtils.isNullOrEmpty(address)) continue;
|
||||||
|
String uuid = e.getValue().get(PROP_UUID);
|
||||||
|
if (StringUtils.isNullOrEmpty(uuid)) continue;
|
||||||
|
ioExecutor.execute(() -> {
|
||||||
|
if (!running) return;
|
||||||
|
BluetoothSocket s = connect(address, uuid);
|
||||||
|
if (s != null) {
|
||||||
|
backoff.reset();
|
||||||
|
callback.outgoingConnectionCreated(c, wrapSocket(s));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private BluetoothSocket connect(String address, String uuid) {
|
||||||
|
// Validate the address
|
||||||
|
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
|
||||||
|
if (LOG.isLoggable(WARNING))
|
||||||
|
// not scrubbing here to be able to figure out the problem
|
||||||
|
LOG.warning("Invalid address " + address);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Validate the UUID
|
||||||
|
UUID u;
|
||||||
|
try {
|
||||||
|
u = UUID.fromString(uuid);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.warning("Invalid UUID " + uuid);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Try to connect
|
||||||
|
BluetoothDevice d = adapter.getRemoteDevice(address);
|
||||||
|
BluetoothSocket s = null;
|
||||||
|
try {
|
||||||
|
s = d.createInsecureRfcommSocketToServiceRecord(u);
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Connecting to " + scrubMacAddress(address));
|
||||||
|
s.connect();
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Connected to " + scrubMacAddress(address));
|
||||||
|
return s;
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info("Failed to connect to " + scrubMacAddress(address)
|
||||||
|
+ ": " + e);
|
||||||
|
}
|
||||||
|
tryToClose(s);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryToClose(@Nullable Closeable c) {
|
||||||
|
try {
|
||||||
|
if (c != null) c.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DuplexTransportConnection createConnection(ContactId c) {
|
||||||
|
if (!isRunning()) return null;
|
||||||
|
TransportProperties p = callback.getRemoteProperties(c);
|
||||||
|
String address = p.get(PROP_ADDRESS);
|
||||||
|
if (StringUtils.isNullOrEmpty(address)) return null;
|
||||||
|
String uuid = p.get(PROP_UUID);
|
||||||
|
if (StringUtils.isNullOrEmpty(uuid)) return null;
|
||||||
|
BluetoothSocket s = connect(address, uuid);
|
||||||
|
if (s == null) return null;
|
||||||
|
return new DroidtoothTransportConnection(this, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsKeyAgreement() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
|
||||||
|
if (!isRunning()) return null;
|
||||||
|
// There's no point listening if we can't discover our own address
|
||||||
|
String address = AndroidUtils.getBluetoothAddress(appContext, adapter);
|
||||||
|
if (address.isEmpty()) return null;
|
||||||
|
// No truncation necessary because COMMIT_LENGTH = 16
|
||||||
|
UUID uuid = UUID.nameUUIDFromBytes(commitment);
|
||||||
|
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
|
||||||
|
// Bind a server socket for receiving key agreement connections
|
||||||
|
BluetoothServerSocket ss;
|
||||||
|
try {
|
||||||
|
ss = adapter.listenUsingInsecureRfcommWithServiceRecord(
|
||||||
|
"RFCOMM", uuid);
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
BdfList descriptor = new BdfList();
|
||||||
|
descriptor.add(TRANSPORT_ID_BLUETOOTH);
|
||||||
|
descriptor.add(StringUtils.macToBytes(address));
|
||||||
|
return new BluetoothKeyAgreementListener(descriptor, ss);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DuplexTransportConnection createKeyAgreementConnection(
|
||||||
|
byte[] commitment, BdfList descriptor, long timeout) {
|
||||||
|
if (!isRunning()) return null;
|
||||||
|
String address;
|
||||||
|
try {
|
||||||
|
address = parseAddress(descriptor);
|
||||||
|
} catch (FormatException e) {
|
||||||
|
LOG.info("Invalid address in key agreement descriptor");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// No truncation necessary because COMMIT_LENGTH = 16
|
||||||
|
UUID uuid = UUID.nameUUIDFromBytes(commitment);
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Connecting to key agreement UUID " + uuid);
|
||||||
|
BluetoothSocket s = connect(address, uuid.toString());
|
||||||
|
if (s == null) return null;
|
||||||
|
return new DroidtoothTransportConnection(this, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String parseAddress(BdfList descriptor) throws FormatException {
|
||||||
|
byte[] mac = descriptor.getRaw(1);
|
||||||
|
if (mac.length != 6) throw new FormatException();
|
||||||
|
return StringUtils.macToString(mac);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eventOccurred(Event e) {
|
||||||
|
if (e instanceof EnableBluetoothEvent) {
|
||||||
|
enableAdapterAsync();
|
||||||
|
} else if (e instanceof DisableBluetoothEvent) {
|
||||||
|
disableAdapterAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enableAdapterAsync() {
|
||||||
|
ioExecutor.execute(this::enableAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void disableAdapterAsync() {
|
||||||
|
ioExecutor.execute(this::disableAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BluetoothStateReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context ctx, Intent intent) {
|
||||||
|
int state = intent.getIntExtra(EXTRA_STATE, 0);
|
||||||
|
if (state == STATE_ON) {
|
||||||
|
LOG.info("Bluetooth enabled");
|
||||||
|
bind();
|
||||||
|
} else if (state == STATE_OFF) {
|
||||||
|
LOG.info("Bluetooth disabled");
|
||||||
|
tryToClose(socket);
|
||||||
|
}
|
||||||
|
int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, 0);
|
||||||
|
if (scanMode == SCAN_MODE_NONE) {
|
||||||
|
LOG.info("Scan mode: None");
|
||||||
|
} else if (scanMode == SCAN_MODE_CONNECTABLE) {
|
||||||
|
LOG.info("Scan mode: Connectable");
|
||||||
|
} else if (scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
|
||||||
|
LOG.info("Scan mode: Discoverable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BluetoothKeyAgreementListener extends KeyAgreementListener {
|
||||||
|
|
||||||
|
private final BluetoothServerSocket ss;
|
||||||
|
|
||||||
|
private BluetoothKeyAgreementListener(BdfList descriptor,
|
||||||
|
BluetoothServerSocket ss) {
|
||||||
|
super(descriptor);
|
||||||
|
this.ss = ss;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Callable<KeyAgreementConnection> listen() {
|
||||||
|
return () -> {
|
||||||
|
BluetoothSocket s = ss.accept();
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info(ID.getString() + ": Incoming connection");
|
||||||
|
return new KeyAgreementConnection(
|
||||||
|
new DroidtoothTransportConnection(
|
||||||
|
DroidtoothPlugin.this, s), ID);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
try {
|
||||||
|
ss.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.briarproject.bramble.plugin.bluetooth;
|
package org.briarproject.bramble.plugin.droidtooth;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
|
|||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
|
public class DroidtoothPluginFactory implements DuplexPluginFactory {
|
||||||
|
|
||||||
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
|
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
|
||||||
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
|
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
|
||||||
@@ -35,7 +35,7 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
|
|||||||
private final EventBus eventBus;
|
private final EventBus eventBus;
|
||||||
private final BackoffFactory backoffFactory;
|
private final BackoffFactory backoffFactory;
|
||||||
|
|
||||||
public AndroidBluetoothPluginFactory(Executor ioExecutor,
|
public DroidtoothPluginFactory(Executor ioExecutor,
|
||||||
AndroidExecutor androidExecutor, Context appContext,
|
AndroidExecutor androidExecutor, Context appContext,
|
||||||
SecureRandom secureRandom, EventBus eventBus,
|
SecureRandom secureRandom, EventBus eventBus,
|
||||||
BackoffFactory backoffFactory) {
|
BackoffFactory backoffFactory) {
|
||||||
@@ -61,7 +61,7 @@ public class AndroidBluetoothPluginFactory 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);
|
||||||
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(ioExecutor,
|
DroidtoothPlugin plugin = new DroidtoothPlugin(ioExecutor,
|
||||||
androidExecutor, appContext, secureRandom, backoff, callback,
|
androidExecutor, appContext, secureRandom, backoff, callback,
|
||||||
MAX_LATENCY);
|
MAX_LATENCY);
|
||||||
eventBus.addListener(plugin);
|
eventBus.addListener(plugin);
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.briarproject.bramble.plugin.bluetooth;
|
package org.briarproject.bramble.plugin.droidtooth;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothSocket;
|
import android.bluetooth.BluetoothSocket;
|
||||||
|
|
||||||
@@ -11,12 +11,11 @@ import java.io.InputStream;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class AndroidBluetoothTransportConnection
|
class DroidtoothTransportConnection extends AbstractDuplexTransportConnection {
|
||||||
extends AbstractDuplexTransportConnection {
|
|
||||||
|
|
||||||
private final BluetoothSocket socket;
|
private final BluetoothSocket socket;
|
||||||
|
|
||||||
AndroidBluetoothTransportConnection(Plugin plugin, BluetoothSocket socket) {
|
DroidtoothTransportConnection(Plugin plugin, BluetoothSocket socket) {
|
||||||
super(plugin);
|
super(plugin);
|
||||||
this.socket = socket;
|
this.socket = socket;
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,6 @@ import android.content.Intent;
|
|||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.net.NetworkInfo;
|
import android.net.NetworkInfo;
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.Backoff;
|
import org.briarproject.bramble.api.plugin.Backoff;
|
||||||
@@ -20,15 +19,10 @@ import javax.annotation.Nullable;
|
|||||||
import static android.content.Context.CONNECTIVITY_SERVICE;
|
import static android.content.Context.CONNECTIVITY_SERVICE;
|
||||||
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
||||||
import static android.net.ConnectivityManager.TYPE_WIFI;
|
import static android.net.ConnectivityManager.TYPE_WIFI;
|
||||||
import static android.net.wifi.WifiManager.EXTRA_WIFI_STATE;
|
|
||||||
import static java.util.logging.Level.INFO;
|
|
||||||
import static org.briarproject.bramble.util.AndroidUtils.logNetworkState;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class AndroidLanTcpPlugin extends LanTcpPlugin {
|
class AndroidLanTcpPlugin extends LanTcpPlugin {
|
||||||
|
|
||||||
private static final String WIFI_AP_STATE_ACTION =
|
|
||||||
"android.net.wifi.WIFI_AP_STATE_CHANGED";
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(AndroidLanTcpPlugin.class.getName());
|
Logger.getLogger(AndroidLanTcpPlugin.class.getName());
|
||||||
|
|
||||||
@@ -50,11 +44,8 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
|
|||||||
running = true;
|
running = true;
|
||||||
// Register to receive network status events
|
// Register to receive network status events
|
||||||
networkStateReceiver = new NetworkStateReceiver();
|
networkStateReceiver = new NetworkStateReceiver();
|
||||||
IntentFilter filter = new IntentFilter();
|
IntentFilter filter = new IntentFilter(CONNECTIVITY_ACTION);
|
||||||
filter.addAction(CONNECTIVITY_ACTION);
|
|
||||||
filter.addAction(WIFI_AP_STATE_ACTION);
|
|
||||||
appContext.registerReceiver(networkStateReceiver, filter);
|
appContext.registerReceiver(networkStateReceiver, filter);
|
||||||
if (LOG.isLoggable(INFO)) logNetworkState(appContext, LOG);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -70,27 +61,10 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
|
|||||||
@Override
|
@Override
|
||||||
public void onReceive(Context ctx, Intent i) {
|
public void onReceive(Context ctx, Intent i) {
|
||||||
if (!running) return;
|
if (!running) return;
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
if (CONNECTIVITY_ACTION.equals(i.getAction())) {
|
|
||||||
LOG.info("Connectivity change");
|
|
||||||
Bundle extras = i.getExtras();
|
|
||||||
if (extras != null) {
|
|
||||||
LOG.info("Extras:");
|
|
||||||
for (String key : extras.keySet())
|
|
||||||
LOG.info("\t" + key + ": " + extras.get(key));
|
|
||||||
}
|
|
||||||
} else if (WIFI_AP_STATE_ACTION.equals(i.getAction())) {
|
|
||||||
int state = i.getIntExtra(EXTRA_WIFI_STATE, 0);
|
|
||||||
if (state == 13) LOG.info("Wifi AP enabled");
|
|
||||||
else LOG.info("Wifi AP state " + state);
|
|
||||||
}
|
|
||||||
logNetworkState(appContext, LOG);
|
|
||||||
}
|
|
||||||
Object o = ctx.getSystemService(CONNECTIVITY_SERVICE);
|
Object o = ctx.getSystemService(CONNECTIVITY_SERVICE);
|
||||||
ConnectivityManager cm = (ConnectivityManager) o;
|
ConnectivityManager cm = (ConnectivityManager) o;
|
||||||
NetworkInfo net = cm.getActiveNetworkInfo();
|
NetworkInfo net = cm.getActiveNetworkInfo();
|
||||||
if (net != null && net.getType() == TYPE_WIFI
|
if (net != null && net.getType() == TYPE_WIFI && net.isConnected()) {
|
||||||
&& net.isConnected()) {
|
|
||||||
LOG.info("Connected to Wi-Fi");
|
LOG.info("Connected to Wi-Fi");
|
||||||
if (socket == null || socket.isClosed()) bind();
|
if (socket == null || socket.isClosed()) bind();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -59,12 +59,7 @@ import java.util.Map.Entry;
|
|||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.Future;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
import java.util.concurrent.locks.Lock;
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.zip.ZipInputStream;
|
import java.util.zip.ZipInputStream;
|
||||||
@@ -75,15 +70,10 @@ import javax.net.SocketFactory;
|
|||||||
import static android.content.Context.CONNECTIVITY_SERVICE;
|
import static android.content.Context.CONNECTIVITY_SERVICE;
|
||||||
import static android.content.Context.MODE_PRIVATE;
|
import static android.content.Context.MODE_PRIVATE;
|
||||||
import static android.content.Context.POWER_SERVICE;
|
import static android.content.Context.POWER_SERVICE;
|
||||||
import static android.content.Intent.ACTION_SCREEN_OFF;
|
|
||||||
import static android.content.Intent.ACTION_SCREEN_ON;
|
|
||||||
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
|
||||||
import static android.net.ConnectivityManager.TYPE_WIFI;
|
import static android.net.ConnectivityManager.TYPE_WIFI;
|
||||||
import static android.os.Build.VERSION.SDK_INT;
|
|
||||||
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
|
|
||||||
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
|
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
|
import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS;
|
||||||
@@ -112,7 +102,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
Logger.getLogger(TorPlugin.class.getName());
|
Logger.getLogger(TorPlugin.class.getName());
|
||||||
|
|
||||||
private final Executor ioExecutor;
|
private final Executor ioExecutor;
|
||||||
private final ScheduledExecutorService scheduler;
|
|
||||||
private final Context appContext;
|
private final Context appContext;
|
||||||
private final LocationUtils locationUtils;
|
private final LocationUtils locationUtils;
|
||||||
private final DevReporter reporter;
|
private final DevReporter reporter;
|
||||||
@@ -125,9 +114,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
private final File torDirectory, torFile, geoIpFile, configFile;
|
private final File torDirectory, torFile, geoIpFile, configFile;
|
||||||
private final File doneFile, cookieFile;
|
private final File doneFile, cookieFile;
|
||||||
private final PowerManager.WakeLock wakeLock;
|
private final PowerManager.WakeLock wakeLock;
|
||||||
private final Lock connectionStatusLock;
|
|
||||||
private final AtomicReference<Future<?>> connectivityCheck =
|
|
||||||
new AtomicReference<>();
|
|
||||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||||
|
|
||||||
private volatile boolean running = false;
|
private volatile boolean running = false;
|
||||||
@@ -136,13 +122,12 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
private volatile TorControlConnection controlConnection = null;
|
private volatile TorControlConnection controlConnection = null;
|
||||||
private volatile BroadcastReceiver networkStateReceiver = null;
|
private volatile BroadcastReceiver networkStateReceiver = null;
|
||||||
|
|
||||||
TorPlugin(Executor ioExecutor, ScheduledExecutorService scheduler,
|
TorPlugin(Executor ioExecutor, Context appContext,
|
||||||
Context appContext, LocationUtils locationUtils,
|
LocationUtils locationUtils, DevReporter reporter,
|
||||||
DevReporter reporter, SocketFactory torSocketFactory,
|
SocketFactory torSocketFactory, Backoff backoff,
|
||||||
Backoff backoff, DuplexPluginCallback callback,
|
DuplexPluginCallback callback, String architecture, int maxLatency,
|
||||||
String architecture, int maxLatency, int maxIdleTime) {
|
int maxIdleTime) {
|
||||||
this.ioExecutor = ioExecutor;
|
this.ioExecutor = ioExecutor;
|
||||||
this.scheduler = scheduler;
|
|
||||||
this.appContext = appContext;
|
this.appContext = appContext;
|
||||||
this.locationUtils = locationUtils;
|
this.locationUtils = locationUtils;
|
||||||
this.reporter = reporter;
|
this.reporter = reporter;
|
||||||
@@ -167,7 +152,6 @@ 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);
|
||||||
connectionStatusLock = new ReentrantLock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -220,11 +204,11 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
if (LOG.isLoggable(INFO)) {
|
if (LOG.isLoggable(INFO)) {
|
||||||
Scanner stdout = new Scanner(torProcess.getInputStream());
|
Scanner stdout = new Scanner(torProcess.getInputStream());
|
||||||
Scanner stderr = new Scanner(torProcess.getErrorStream());
|
Scanner stderr = new Scanner(torProcess.getErrorStream());
|
||||||
while (stdout.hasNextLine() || stderr.hasNextLine()) {
|
while (stdout.hasNextLine() || stderr.hasNextLine()){
|
||||||
if (stdout.hasNextLine()) {
|
if(stdout.hasNextLine()) {
|
||||||
LOG.info(stdout.nextLine());
|
LOG.info(stdout.nextLine());
|
||||||
}
|
}
|
||||||
if (stderr.hasNextLine()) {
|
if(stderr.hasNextLine()){
|
||||||
LOG.info(stderr.nextLine());
|
LOG.info(stderr.nextLine());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -273,11 +257,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
// Register to receive network status events
|
// Register to receive network status events
|
||||||
networkStateReceiver = new NetworkStateReceiver();
|
networkStateReceiver = new NetworkStateReceiver();
|
||||||
IntentFilter filter = new IntentFilter();
|
IntentFilter filter = new IntentFilter(CONNECTIVITY_ACTION);
|
||||||
filter.addAction(CONNECTIVITY_ACTION);
|
|
||||||
filter.addAction(ACTION_SCREEN_ON);
|
|
||||||
filter.addAction(ACTION_SCREEN_OFF);
|
|
||||||
if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
|
|
||||||
appContext.registerReceiver(networkStateReceiver, filter);
|
appContext.registerReceiver(networkStateReceiver, filter);
|
||||||
// Bind a server socket to receive incoming hidden service connections
|
// Bind a server socket to receive incoming hidden service connections
|
||||||
bind();
|
bind();
|
||||||
@@ -614,7 +594,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DuplexTransportConnection createKeyAgreementConnection(
|
public DuplexTransportConnection createKeyAgreementConnection(
|
||||||
byte[] commitment, BdfList descriptor) {
|
byte[] commitment, BdfList descriptor, long timeout) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -638,8 +618,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
@Override
|
@Override
|
||||||
public void orConnStatus(String status, String orName) {
|
public void orConnStatus(String status, String orName) {
|
||||||
if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
|
if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
|
||||||
if (status.equals("CLOSED") || status.equals("FAILED"))
|
|
||||||
updateConnectionStatus(); // Check whether we've lost connectivity
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -679,7 +657,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEvent(int event, @Nullable String path) {
|
public void onEvent(int event, String path) {
|
||||||
stopWatching();
|
stopWatching();
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
}
|
}
|
||||||
@@ -699,72 +677,53 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
|||||||
private void updateConnectionStatus() {
|
private void updateConnectionStatus() {
|
||||||
ioExecutor.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 {
|
||||||
connectionStatusLock.lock();
|
if (!online) {
|
||||||
updateConnectionStatusLocked();
|
LOG.info("Disabling network, device is offline");
|
||||||
} finally {
|
enableNetwork(false);
|
||||||
connectionStatusLock.unlock();
|
} else if (blocked) {
|
||||||
|
LOG.info("Disabling network, country is blocked");
|
||||||
|
enableNetwork(false);
|
||||||
|
} else if (network == PREF_TOR_NETWORK_NEVER
|
||||||
|
|| (network == PREF_TOR_NETWORK_WIFI && !wifi)) {
|
||||||
|
LOG.info("Disabling network due to data setting");
|
||||||
|
enableNetwork(false);
|
||||||
|
} else {
|
||||||
|
LOG.info("Enabling network");
|
||||||
|
enableNetwork(true);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locking: connectionStatusLock
|
|
||||||
private void updateConnectionStatusLocked() {
|
|
||||||
Object o = appContext.getSystemService(CONNECTIVITY_SERVICE);
|
|
||||||
ConnectivityManager cm = (ConnectivityManager) o;
|
|
||||||
NetworkInfo net = cm.getActiveNetworkInfo();
|
|
||||||
boolean online = net != null && net.isConnected();
|
|
||||||
boolean wifi = online && net.getType() == TYPE_WIFI;
|
|
||||||
String country = locationUtils.getCurrentCountry();
|
|
||||||
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(country);
|
|
||||||
Settings s = callback.getSettings();
|
|
||||||
int network = s.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_ALWAYS);
|
|
||||||
|
|
||||||
if (LOG.isLoggable(INFO)) {
|
|
||||||
LOG.info("Online: " + online + ", wifi: " + wifi);
|
|
||||||
if ("".equals(country)) LOG.info("Country code unknown");
|
|
||||||
else LOG.info("Country code: " + country);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!online) {
|
|
||||||
LOG.info("Disabling network, device is offline");
|
|
||||||
enableNetwork(false);
|
|
||||||
} else if (blocked) {
|
|
||||||
LOG.info("Disabling network, country is blocked");
|
|
||||||
enableNetwork(false);
|
|
||||||
} else if (network == PREF_TOR_NETWORK_NEVER
|
|
||||||
|| (network == PREF_TOR_NETWORK_WIFI && !wifi)) {
|
|
||||||
LOG.info("Disabling network due to data setting");
|
|
||||||
enableNetwork(false);
|
|
||||||
} else {
|
|
||||||
LOG.info("Enabling network");
|
|
||||||
enableNetwork(true);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void scheduleConnectionStatusUpdate() {
|
|
||||||
Future<?> newConnectivityCheck =
|
|
||||||
scheduler.schedule(this::updateConnectionStatus, 1, MINUTES);
|
|
||||||
Future<?> oldConnectivityCheck =
|
|
||||||
connectivityCheck.getAndSet(newConnectivityCheck);
|
|
||||||
if (oldConnectivityCheck != null) oldConnectivityCheck.cancel(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class NetworkStateReceiver extends BroadcastReceiver {
|
private class NetworkStateReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context ctx, Intent i) {
|
public void onReceive(Context ctx, Intent i) {
|
||||||
if (!running) return;
|
if (!running) return;
|
||||||
String action = i.getAction();
|
if (CONNECTIVITY_ACTION.equals(i.getAction())) {
|
||||||
if (LOG.isLoggable(INFO)) LOG.info("Received broadcast " + action);
|
LOG.info("Detected connectivity change");
|
||||||
updateConnectionStatus();
|
updateConnectionStatus();
|
||||||
if (ACTION_SCREEN_ON.equals(action)
|
|
||||||
|| ACTION_SCREEN_OFF.equals(action)) {
|
|
||||||
scheduleConnectionStatusUpdate();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import org.briarproject.bramble.api.system.LocationUtils;
|
|||||||
import org.briarproject.bramble.util.AndroidUtils;
|
import org.briarproject.bramble.util.AndroidUtils;
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
@@ -37,7 +36,6 @@ public class TorPluginFactory implements DuplexPluginFactory {
|
|||||||
private static final double BACKOFF_BASE = 1.2;
|
private static final double BACKOFF_BASE = 1.2;
|
||||||
|
|
||||||
private final Executor ioExecutor;
|
private final Executor ioExecutor;
|
||||||
private final ScheduledExecutorService scheduler;
|
|
||||||
private final Context appContext;
|
private final Context appContext;
|
||||||
private final LocationUtils locationUtils;
|
private final LocationUtils locationUtils;
|
||||||
private final DevReporter reporter;
|
private final DevReporter reporter;
|
||||||
@@ -45,13 +43,11 @@ public class TorPluginFactory implements DuplexPluginFactory {
|
|||||||
private final SocketFactory torSocketFactory;
|
private final SocketFactory torSocketFactory;
|
||||||
private final BackoffFactory backoffFactory;
|
private final BackoffFactory backoffFactory;
|
||||||
|
|
||||||
public TorPluginFactory(Executor ioExecutor,
|
public TorPluginFactory(Executor ioExecutor, Context appContext,
|
||||||
ScheduledExecutorService scheduler, Context appContext,
|
|
||||||
LocationUtils locationUtils, DevReporter reporter,
|
LocationUtils locationUtils, DevReporter reporter,
|
||||||
EventBus eventBus, SocketFactory torSocketFactory,
|
EventBus eventBus, SocketFactory torSocketFactory,
|
||||||
BackoffFactory backoffFactory) {
|
BackoffFactory backoffFactory) {
|
||||||
this.ioExecutor = ioExecutor;
|
this.ioExecutor = ioExecutor;
|
||||||
this.scheduler = scheduler;
|
|
||||||
this.appContext = appContext;
|
this.appContext = appContext;
|
||||||
this.locationUtils = locationUtils;
|
this.locationUtils = locationUtils;
|
||||||
this.reporter = reporter;
|
this.reporter = reporter;
|
||||||
@@ -93,9 +89,9 @@ public class TorPluginFactory implements DuplexPluginFactory {
|
|||||||
|
|
||||||
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
|
||||||
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
MAX_POLLING_INTERVAL, BACKOFF_BASE);
|
||||||
TorPlugin plugin = new TorPlugin(ioExecutor, scheduler, appContext,
|
TorPlugin plugin = new TorPlugin(ioExecutor, appContext, locationUtils,
|
||||||
locationUtils, reporter, torSocketFactory, backoff, callback,
|
reporter, torSocketFactory, backoff, callback, architecture,
|
||||||
architecture, MAX_LATENCY, MAX_IDLE_TIME);
|
MAX_LATENCY, MAX_IDLE_TIME);
|
||||||
eventBus.addListener(plugin);
|
eventBus.addListener(plugin);
|
||||||
return plugin;
|
return plugin;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,41 +1,18 @@
|
|||||||
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
|
||||||
@@ -46,7 +23,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 (SDK_INT >= 21) {
|
if (Build.VERSION.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);
|
||||||
@@ -90,123 +67,4 @@ public class AndroidUtils {
|
|||||||
public static File getReportDir(Context ctx) {
|
public static File getReportDir(Context ctx) {
|
||||||
return ctx.getDir(STORED_REPORTS, MODE_PRIVATE);
|
return ctx.getDir(STORED_REPORTS, MODE_PRIVATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void logNetworkState(Context ctx, Logger logger) {
|
|
||||||
if (!logger.isLoggable(INFO)) return;
|
|
||||||
|
|
||||||
Object o = ctx.getSystemService(CONNECTIVITY_SERVICE);
|
|
||||||
if (o == null) throw new AssertionError();
|
|
||||||
ConnectivityManager cm = (ConnectivityManager) o;
|
|
||||||
o = ctx.getApplicationContext().getSystemService(WIFI_SERVICE);
|
|
||||||
if (o == null) throw new AssertionError();
|
|
||||||
WifiManager wm = (WifiManager) o;
|
|
||||||
|
|
||||||
StringBuilder s = new StringBuilder();
|
|
||||||
logWifiInfo(s, wm.getConnectionInfo());
|
|
||||||
logNetworkInfo(s, cm.getActiveNetworkInfo(), true);
|
|
||||||
if (SDK_INT >= 21) {
|
|
||||||
for (Network network : cm.getAllNetworks())
|
|
||||||
logNetworkInfo(s, cm.getNetworkInfo(network), false);
|
|
||||||
} else {
|
|
||||||
for (NetworkInfo info : cm.getAllNetworkInfo())
|
|
||||||
logNetworkInfo(s, info, false);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
for (NetworkInterface iface : list(getNetworkInterfaces()))
|
|
||||||
logNetworkInterface(s, iface);
|
|
||||||
} catch (SocketException e) {
|
|
||||||
logger.log(WARNING, e.toString(), e);
|
|
||||||
}
|
|
||||||
logger.log(INFO, s.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void logWifiInfo(StringBuilder s, @Nullable WifiInfo info) {
|
|
||||||
if (info == null) {
|
|
||||||
s.append("Wifi info: null\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
s.append("Wifi info:\n");
|
|
||||||
s.append("\tSSID: ").append(info.getSSID()).append("\n");
|
|
||||||
s.append("\tBSSID: ").append(info.getBSSID()).append("\n");
|
|
||||||
s.append("\tMAC address: ").append(info.getMacAddress()).append("\n");
|
|
||||||
s.append("\tIP address: ")
|
|
||||||
.append(ipToString(info.getIpAddress())).append("\n");
|
|
||||||
s.append("\tSupplicant state: ")
|
|
||||||
.append(info.getSupplicantState()).append("\n");
|
|
||||||
s.append("\tNetwork ID: ").append(info.getNetworkId()).append("\n");
|
|
||||||
s.append("\tLink speed: ").append(info.getLinkSpeed()).append("\n");
|
|
||||||
s.append("\tRSSI: ").append(info.getRssi()).append("\n");
|
|
||||||
if (info.getHiddenSSID()) s.append("\tHidden SSID\n");
|
|
||||||
if (SDK_INT >= 21)
|
|
||||||
s.append("\tFrequency: ").append(info.getFrequency()).append("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void logNetworkInfo(StringBuilder s,
|
|
||||||
@Nullable NetworkInfo info, boolean active) {
|
|
||||||
if (info == null) {
|
|
||||||
if (active) s.append("Active network info: null\n");
|
|
||||||
else s.append("Network info: null\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (active) s.append("Active network info:\n");
|
|
||||||
else s.append("Network info:\n");
|
|
||||||
s.append("\tType: ").append(info.getTypeName())
|
|
||||||
.append(" (").append(info.getType()).append(")\n");
|
|
||||||
s.append("\tSubtype: ").append(info.getSubtypeName())
|
|
||||||
.append(" (").append(info.getSubtype()).append(")\n");
|
|
||||||
s.append("\tState: ").append(info.getState()).append("\n");
|
|
||||||
s.append("\tDetailed state: ")
|
|
||||||
.append(info.getDetailedState()).append("\n");
|
|
||||||
s.append("\tReason: ").append(info.getReason()).append("\n");
|
|
||||||
s.append("\tExtra info: ").append(info.getExtraInfo()).append("\n");
|
|
||||||
if (info.isAvailable()) s.append("\tAvailable\n");
|
|
||||||
if (info.isConnected()) s.append("\tConnected\n");
|
|
||||||
if (info.isConnectedOrConnecting())
|
|
||||||
s.append("\tConnected or connecting\n");
|
|
||||||
if (info.isFailover()) s.append("\tFailover\n");
|
|
||||||
if (info.isRoaming()) s.append("\tRoaming\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void logNetworkInterface(StringBuilder s,
|
|
||||||
NetworkInterface iface) throws SocketException {
|
|
||||||
s.append("Network interface:\n");
|
|
||||||
s.append("\tName: ").append(iface.getName()).append("\n");
|
|
||||||
s.append("\tDisplay name: ")
|
|
||||||
.append(iface.getDisplayName()).append("\n");
|
|
||||||
s.append("\tHardware address: ")
|
|
||||||
.append(hexOrNull(iface.getHardwareAddress())).append("\n");
|
|
||||||
if (iface.isLoopback()) s.append("\tLoopback\n");
|
|
||||||
if (iface.isPointToPoint()) s.append("\tPoint-to-point\n");
|
|
||||||
if (iface.isVirtual()) s.append("\tVirtual\n");
|
|
||||||
if (iface.isUp()) s.append("\tUp\n");
|
|
||||||
if (SDK_INT >= 19)
|
|
||||||
s.append("\tIndex: ").append(iface.getIndex()).append("\n");
|
|
||||||
for (InterfaceAddress addr : iface.getInterfaceAddresses()) {
|
|
||||||
s.append("\tInterface address:\n");
|
|
||||||
logInetAddress(s, addr.getAddress());
|
|
||||||
s.append("\t\tPrefix length: ")
|
|
||||||
.append(addr.getNetworkPrefixLength()).append("\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void logInetAddress(StringBuilder s, InetAddress addr) {
|
|
||||||
s.append("\t\tAddress: ")
|
|
||||||
.append(hexOrNull(addr.getAddress())).append("\n");
|
|
||||||
s.append("\t\tHost address: ")
|
|
||||||
.append(addr.getHostAddress()).append("\n");
|
|
||||||
if (addr.isLoopbackAddress()) s.append("\t\tLoopback\n");
|
|
||||||
if (addr.isLinkLocalAddress()) s.append("\t\tLink-local\n");
|
|
||||||
if (addr.isSiteLocalAddress()) s.append("\t\tSite-local\n");
|
|
||||||
if (addr.isAnyLocalAddress()) s.append("\t\tAny local (wildcard)\n");
|
|
||||||
if (addr.isMCNodeLocal()) s.append("\t\tMulticast node-local\n");
|
|
||||||
if (addr.isMCLinkLocal()) s.append("\t\tMulticast link-local\n");
|
|
||||||
if (addr.isMCSiteLocal()) s.append("\t\tMulticast site-local\n");
|
|
||||||
if (addr.isMCOrgLocal()) s.append("\t\tMulticast org-local\n");
|
|
||||||
if (addr.isMCGlobal()) s.append("\t\tMulticast global\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static String hexOrNull(@Nullable byte[] b) {
|
|
||||||
return b == null ? null : toHexString(b);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,101 +0,0 @@
|
|||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package org.briarproject.bramble.api;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An exception that indicates an unrecoverable version mismatch.
|
|
||||||
*/
|
|
||||||
public class UnsupportedVersionException extends IOException {
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,6 @@ 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;
|
||||||
@@ -94,13 +93,10 @@ 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,19 +12,18 @@ 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, int clientVersion);
|
Group createLocalGroup(ClientId clientId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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, int clientVersion,
|
Group createContactGroup(ClientId clientId, Contact contact);
|
||||||
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, int clientVersion,
|
Group createContactGroup(ClientId clientId, AuthorId authorId1,
|
||||||
AuthorId authorId1, AuthorId authorId2);
|
AuthorId authorId2);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,32 +12,6 @@ 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,13 +1,11 @@
|
|||||||
package org.briarproject.bramble.api.crypto;
|
package org.briarproject.bramble.api.crypto;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
|
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();
|
||||||
@@ -25,46 +23,127 @@ public interface CryptoComponent {
|
|||||||
KeyParser getMessageKeyParser();
|
KeyParser getMessageKeyParser();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Derives another secret key from the given secret key.
|
* Derives a stream header key from the given master secret.
|
||||||
*
|
* @param alice whether the key is for use by Alice or Bob.
|
||||||
* @param label a namespaced label indicating the purpose of the derived
|
|
||||||
* key, to prevent it from being repurposed or colliding with a key derived
|
|
||||||
* for another purpose
|
|
||||||
*/
|
*/
|
||||||
SecretKey deriveKey(String label, SecretKey k, byte[]... inputs);
|
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
|
||||||
|
* @return the commitment to the provided public key.
|
||||||
|
*/
|
||||||
|
byte[] deriveKeyCommitment(byte[] publicKey);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 label a namespaced label indicating the purpose of this shared
|
* @param theirPublicKey the ephemeral public key of the remote party
|
||||||
* secret, to prevent it from being repurposed or colliding with a shared
|
* @param ourKeyPair our ephemeral keypair
|
||||||
* secret derived for another purpose
|
* @param alice true if ourKeyPair belongs to Alice
|
||||||
* @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(String label, PublicKey theirPublicKey,
|
SecretKey deriveSharedSecret(byte[] theirPublicKey, KeyPair ourKeyPair,
|
||||||
KeyPair ourKeyPair, byte[]... inputs)
|
boolean alice) throws GeneralSecurityException;
|
||||||
throws GeneralSecurityException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signs the given byte[] with the given private key.
|
* Derives the content of a confirmation record.
|
||||||
|
* <p/>
|
||||||
|
* Part of BQP.
|
||||||
*
|
*
|
||||||
* @param label a namespaced label indicating the purpose of this
|
* @param sharedSecret the common shared secret
|
||||||
* signature, to prevent it from being repurposed or colliding with a
|
* @param theirPayload the commit payload from the remote party
|
||||||
* signature created for another purpose
|
* @param ourPayload the commit payload we sent
|
||||||
|
* @param theirPublicKey the ephemeral public key of the remote party
|
||||||
|
* @param ourKeyPair our ephemeral keypair
|
||||||
|
* @param alice true if ourKeyPair belongs to Alice
|
||||||
|
* @param aliceRecord true if the confirmation record is for use by Alice
|
||||||
|
* @return the confirmation record
|
||||||
|
*/
|
||||||
|
byte[] deriveConfirmationRecord(SecretKey sharedSecret,
|
||||||
|
byte[] theirPayload, byte[] ourPayload,
|
||||||
|
byte[] theirPublicKey, KeyPair ourKeyPair,
|
||||||
|
boolean alice, boolean aliceRecord);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derives a master secret from the given shared secret.
|
||||||
|
* <p/>
|
||||||
|
* Part of BQP.
|
||||||
|
*
|
||||||
|
* @param sharedSecret the common shared secret
|
||||||
|
* @return the master secret
|
||||||
|
*/
|
||||||
|
SecretKey deriveMasterSecret(SecretKey sharedSecret);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derives a master secret from two public keys and one of the corresponding
|
||||||
|
* private keys.
|
||||||
|
* <p/>
|
||||||
|
* This is a helper method that calls
|
||||||
|
* deriveMasterSecret(deriveSharedSecret(theirPublicKey, ourKeyPair, alice))
|
||||||
|
*
|
||||||
|
* @param theirPublicKey the ephemeral public key of the remote party
|
||||||
|
* @param ourKeyPair our ephemeral keypair
|
||||||
|
* @param alice true if ourKeyPair belongs to Alice
|
||||||
|
* @return the shared secret
|
||||||
|
* @throws GeneralSecurityException
|
||||||
|
*/
|
||||||
|
SecretKey deriveMasterSecret(byte[] theirPublicKey, KeyPair ourKeyPair,
|
||||||
|
boolean alice) throws GeneralSecurityException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derives initial transport keys for the given transport in the given
|
||||||
|
* rotation period from the given master secret.
|
||||||
|
* @param alice whether the keys are for use by Alice or Bob.
|
||||||
|
*/
|
||||||
|
TransportKeys deriveTransportKeys(TransportId t, SecretKey master,
|
||||||
|
long rotationPeriod, boolean alice);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotates the given transport keys to the given rotation period. If the
|
||||||
|
* keys are for a future rotation period they are not rotated.
|
||||||
|
*/
|
||||||
|
TransportKeys rotateTransportKeys(TransportKeys k, long rotationPeriod);
|
||||||
|
|
||||||
|
/** Encodes the pseudo-random tag that is used to recognise a stream. */
|
||||||
|
void encodeTag(byte[] tag, SecretKey tagKey, int protocolVersion,
|
||||||
|
long streamNumber);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signs the given byte[] with the given PrivateKey.
|
||||||
|
*
|
||||||
|
* @param label A label specific to this signature
|
||||||
|
* to ensure that the signature cannot be repurposed
|
||||||
*/
|
*/
|
||||||
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 signed data
|
* Verifies that the given signature is valid for the signedData
|
||||||
* and the given public key.
|
* and the given publicKey.
|
||||||
*
|
*
|
||||||
* @param label a namespaced label indicating the purpose of this
|
* @param label A label that was specific to this signature
|
||||||
* signature, to prevent it from being repurposed or colliding with a
|
* to ensure that the signature cannot be repurposed
|
||||||
* 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,
|
||||||
@@ -74,22 +153,23 @@ 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 namespaced label indicating the purpose of this hash, to
|
* @param label A label specific to this hash to ensure that hashes
|
||||||
* prevent it from being repurposed or colliding with a hash created for
|
* calculated for distinct purposes don't collide.
|
||||||
* 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(String label, SecretKey macKey, byte[]... inputs);
|
byte[] mac(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
|
||||||
@@ -105,7 +185,6 @@ 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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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,7 +0,0 @@
|
|||||||
package org.briarproject.bramble.api.db;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Thrown when the database uses a newer schema than the current code.
|
|
||||||
*/
|
|
||||||
public class DataTooNewException extends DbException {
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package org.briarproject.bramble.api.db;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Thrown when the database uses an older schema than the current code and
|
|
||||||
* cannot be migrated.
|
|
||||||
*/
|
|
||||||
public class DataTooOldException extends DbException {
|
|
||||||
}
|
|
||||||
@@ -37,13 +37,8 @@ public interface DatabaseComponent {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the database and returns true if the database already existed.
|
* Opens the database and returns true if the database already existed.
|
||||||
*
|
|
||||||
* @throws DataTooNewException if the data uses a newer schema than the
|
|
||||||
* current code
|
|
||||||
* @throws DataTooOldException if the data uses an older schema than the
|
|
||||||
* current code and cannot be migrated
|
|
||||||
*/
|
*/
|
||||||
boolean open(@Nullable MigrationListener listener) throws DbException;
|
boolean open() throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Waits for any open transactions to finish and closes the database.
|
* Waits for any open transactions to finish and closes the database.
|
||||||
@@ -259,30 +254,31 @@ public interface DatabaseComponent {
|
|||||||
Collection<LocalAuthor> getLocalAuthors(Transaction txn) throws DbException;
|
Collection<LocalAuthor> getLocalAuthors(Transaction txn) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IDs of any messages that need to be validated.
|
* Returns the IDs of any messages that need to be validated by the given
|
||||||
|
* client.
|
||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
Collection<MessageId> getMessagesToValidate(Transaction txn)
|
Collection<MessageId> getMessagesToValidate(Transaction txn, ClientId c)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IDs of any messages that are pending delivery due to
|
* Returns the IDs of any messages that are valid but pending delivery due
|
||||||
* dependencies on other messages.
|
* to dependencies on other messages for the given client.
|
||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
Collection<MessageId> getPendingMessages(Transaction txn)
|
Collection<MessageId> getPendingMessages(Transaction txn, ClientId c)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IDs of any messages that have shared dependents but have
|
* Returns the IDs of any messages from the given client
|
||||||
* not yet been shared themselves.
|
* that have a shared dependent, but are still not shared themselves.
|
||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
Collection<MessageId> getMessagesToShare(Transaction txn)
|
Collection<MessageId> getMessagesToShare(Transaction txn,
|
||||||
throws DbException;
|
ClientId c) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the message with the given ID, in serialised form, or null if
|
* Returns the message with the given ID, in serialised form, or null if
|
||||||
@@ -377,16 +373,6 @@ public interface DatabaseComponent {
|
|||||||
MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
|
MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/*
|
|
||||||
* Returns the next time (in milliseconds since the Unix epoch) when a
|
|
||||||
* message is due to be sent to the given contact. The returned value may
|
|
||||||
* be zero if a message is due to be sent immediately, or Long.MAX_VALUE if
|
|
||||||
* no messages are scheduled to be sent.
|
|
||||||
* <p/>
|
|
||||||
* Read-only.
|
|
||||||
*/
|
|
||||||
long getNextSendTime(Transaction txn, ContactId c) throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all settings in the given namespace.
|
* Returns all settings in the given namespace.
|
||||||
* <p/>
|
* <p/>
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
package org.briarproject.bramble.api.db;
|
|
||||||
|
|
||||||
public interface MigrationListener {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is called when a migration is started while opening the database.
|
|
||||||
* It will be called once for each migration being applied.
|
|
||||||
*/
|
|
||||||
void onMigrationRun();
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
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.
|
||||||
*/
|
*/
|
||||||
@@ -19,25 +17,20 @@ 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, int formatVersion, String name,
|
public Author(AuthorId id, String name, byte[] publicKey) {
|
||||||
byte[] publicKey) {
|
int length;
|
||||||
int nameLength = StringUtils.toUtf8(name).length;
|
try {
|
||||||
if (nameLength == 0 || nameLength > MAX_AUTHOR_NAME_LENGTH)
|
length = name.getBytes("UTF-8").length;
|
||||||
throw new IllegalArgumentException();
|
} catch (UnsupportedEncodingException e) {
|
||||||
if (publicKey.length == 0 || publicKey.length > MAX_PUBLIC_KEY_LENGTH)
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
@@ -49,13 +42,6 @@ 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,8 +1,5 @@
|
|||||||
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 {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -11,14 +8,26 @@ public interface AuthorConstants {
|
|||||||
int MAX_AUTHOR_NAME_LENGTH = 50;
|
int MAX_AUTHOR_NAME_LENGTH = 50;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The maximum length of a public key in bytes. This applies to the
|
* The maximum length of a public key in bytes.
|
||||||
* signature algorithm used by the current {@link Author format version}.
|
* <p>
|
||||||
|
* Public keys use SEC1 format: 0x04 x y, where x and y are unsigned
|
||||||
|
* big-endian integers.
|
||||||
|
* <p>
|
||||||
|
* For a 256-bit elliptic curve, the maximum length is 2 * 256 / 8 + 1.
|
||||||
*/
|
*/
|
||||||
int MAX_PUBLIC_KEY_LENGTH = MAX_SIGNATURE_PUBLIC_KEY_BYTES;
|
int MAX_PUBLIC_KEY_LENGTH = 65;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The maximum length of a signature in bytes. This applies to the
|
* The maximum length of a signature in bytes.
|
||||||
* signature algorithm used by the current {@link Author format version}.
|
* <p>
|
||||||
|
* A signature is an ASN.1 DER sequence containing two integers, r and s.
|
||||||
|
* The format is 0x30 len1 0x02 len2 r 0x02 len3 s, where len1 is
|
||||||
|
* len(0x02 len2 r 0x02 len3 s) as a DER length, len2 is len(r) as a DER
|
||||||
|
* length, len3 is len(s) as a DER length, and r and s are signed
|
||||||
|
* big-endian integers of minimal length.
|
||||||
|
* <p>
|
||||||
|
* For a 256-bit elliptic curve, the lengths are one byte each, so the
|
||||||
|
* maximum length is 2 * 256 / 8 + 8.
|
||||||
*/
|
*/
|
||||||
int MAX_SIGNATURE_LENGTH = MAX_SIGNATURE_BYTES;
|
int MAX_SIGNATURE_LENGTH = 72;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,27 +5,8 @@ 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, int formatVersion, String name,
|
public LocalAuthor(AuthorId id, String name, byte[] publicKey,
|
||||||
byte[] publicKey, byte[] privateKey, long created) {
|
byte[] privateKey, long created) {
|
||||||
super(id, formatVersion, name, publicKey);
|
super(id, 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. Version number 89 is reserved.
|
* The current version of the BQP protocol.
|
||||||
*/
|
*/
|
||||||
byte PROTOCOL_VERSION = 4;
|
byte PROTOCOL_VERSION = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The length of the record header in bytes.
|
* The length of the record header in bytes.
|
||||||
@@ -22,10 +22,7 @@ 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.
|
||||||
@@ -36,16 +33,4 @@ public interface KeyAgreementConstants {
|
|||||||
* The transport identifier for LAN.
|
* The transport identifier for LAN.
|
||||||
*/
|
*/
|
||||||
int TRANSPORT_ID_LAN = 1;
|
int TRANSPORT_ID_LAN = 1;
|
||||||
|
|
||||||
/**
|
|
||||||
* Label for deriving the shared secret.
|
|
||||||
*/
|
|
||||||
String SHARED_SECRET_LABEL =
|
|
||||||
"org.briarproject.bramble.keyagreement/SHARED_SECRET";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Label for deriving the master secret.
|
|
||||||
*/
|
|
||||||
String MASTER_SECRET_LABEL =
|
|
||||||
"org.briarproject.bramble.keyagreement/MASTER_SECRET";
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package org.briarproject.bramble.api.keyagreement;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An class for managing a particular key agreement listener.
|
* An class for managing a particular key agreement listener.
|
||||||
@@ -24,11 +24,11 @@ public abstract class KeyAgreementListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blocks until an incoming connection is received and returns it.
|
* Starts listening for incoming connections, and returns a Callable that
|
||||||
*
|
* will return a KeyAgreementConnection when an incoming connection is
|
||||||
* @throws IOException if an error occurs or {@link #close()} is called.
|
* received.
|
||||||
*/
|
*/
|
||||||
public abstract KeyAgreementConnection accept() throws IOException;
|
public abstract Callable<KeyAgreementConnection> listen();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes the underlying server socket.
|
* Closes the underlying server socket.
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package org.briarproject.bramble.api.keyagreement;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages tasks for conducting key agreements with remote peers.
|
||||||
|
*/
|
||||||
|
@NotNullByDefault
|
||||||
|
public interface KeyAgreementTaskFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current key agreement task.
|
||||||
|
*/
|
||||||
|
KeyAgreementTask createTask();
|
||||||
|
}
|
||||||
@@ -21,25 +21,7 @@ public interface LifecycleManager {
|
|||||||
* The result of calling {@link #startServices(String)}.
|
* The result of calling {@link #startServices(String)}.
|
||||||
*/
|
*/
|
||||||
enum StartResult {
|
enum StartResult {
|
||||||
ALREADY_RUNNING,
|
ALREADY_RUNNING, DB_ERROR, SERVICE_ERROR, SUCCESS
|
||||||
DB_ERROR,
|
|
||||||
DATA_TOO_OLD_ERROR,
|
|
||||||
DATA_TOO_NEW_ERROR,
|
|
||||||
SERVICE_ERROR,
|
|
||||||
SUCCESS
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The state the lifecycle can be in.
|
|
||||||
* Returned by {@link #getLifecycleState()}
|
|
||||||
*/
|
|
||||||
enum LifecycleState {
|
|
||||||
|
|
||||||
STARTING, MIGRATING_DATABASE, STARTING_SERVICES, RUNNING, STOPPING;
|
|
||||||
|
|
||||||
public boolean isAfter(LifecycleState state) {
|
|
||||||
return ordinal() > state.ordinal();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,10 +71,4 @@ public interface LifecycleManager {
|
|||||||
* the {@link DatabaseComponent} to be closed before returning.
|
* the {@link DatabaseComponent} to be closed before returning.
|
||||||
*/
|
*/
|
||||||
void waitForShutdown() throws InterruptedException;
|
void waitForShutdown() throws InterruptedException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current state of the lifecycle.
|
|
||||||
*/
|
|
||||||
LifecycleState getLifecycleState();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package org.briarproject.bramble.api.lifecycle.event;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.event.Event;
|
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An event that is broadcast when the app enters a new lifecycle state.
|
|
||||||
*/
|
|
||||||
public class LifecycleEvent extends Event {
|
|
||||||
|
|
||||||
private final LifecycleState state;
|
|
||||||
|
|
||||||
public LifecycleEvent(LifecycleState state) {
|
|
||||||
this.state = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LifecycleState getLifecycleState() {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package org.briarproject.bramble.api.lifecycle.event;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.event.Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event that is broadcast when the app is shutting down.
|
||||||
|
*/
|
||||||
|
public class ShutdownEvent extends Event {
|
||||||
|
}
|
||||||
@@ -36,9 +36,9 @@ public interface DuplexPlugin extends Plugin {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to connect to the remote peer specified in the given descriptor.
|
* Attempts to connect to the remote peer specified in the given descriptor.
|
||||||
* Returns null if no connection can be established.
|
* Returns null if no connection can be established within the given time.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
DuplexTransportConnection createKeyAgreementConnection(
|
DuplexTransportConnection createKeyAgreementConnection(
|
||||||
byte[] remoteCommitment, BdfList descriptor);
|
byte[] remoteCommitment, BdfList descriptor, long timeout);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
package org.briarproject.bramble.api.plugin.event;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.event.Event;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An event that informs the Bluetooth plugin that we have enabled the
|
|
||||||
* Bluetooth adapter.
|
|
||||||
*/
|
|
||||||
@Immutable
|
|
||||||
@NotNullByDefault
|
|
||||||
public class BluetoothEnabledEvent extends Event {
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An event that asks the Bluetooth plugin to enable the Bluetooth adapter.
|
* An event asks the Bluetooth plugin to enable the Bluetooth adapter.
|
||||||
*/
|
*/
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
|
|||||||
@@ -17,11 +17,6 @@ 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,8 +36,4 @@ 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, client version and descriptor.
|
* Creates a group with the given client ID and descriptor.
|
||||||
*/
|
*/
|
||||||
Group createGroup(ClientId c, int clientVersion, byte[] descriptor);
|
Group createGroup(ClientId c, 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 = 4;
|
int PROTOCOL_VERSION = 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The length of the pseudo-random tag in bytes.
|
* The length of the pseudo-random tag in bytes.
|
||||||
@@ -80,32 +80,4 @@ 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";
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,10 +126,6 @@ public class StringUtils {
|
|||||||
return toUtf8(s).length > maxLength;
|
return toUtf8(s).length > maxLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isValidMac(String mac) {
|
|
||||||
return MAC.matcher(mac).matches();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] macToBytes(String mac) {
|
public static byte[] macToBytes(String mac) {
|
||||||
if (!MAC.matcher(mac).matches()) throw new IllegalArgumentException();
|
if (!MAC.matcher(mac).matches()) throw new IllegalArgumentException();
|
||||||
return fromHexString(mac.replaceAll(":", ""));
|
return fromHexString(mac.replaceAll(":", ""));
|
||||||
@@ -146,14 +142,6 @@ 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++)
|
||||||
|
|||||||
@@ -2,32 +2,12 @@ package org.briarproject.bramble.test;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.UniqueId;
|
import org.briarproject.bramble.api.UniqueId;
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
|
||||||
import org.briarproject.bramble.api.sync.ClientId;
|
|
||||||
import org.briarproject.bramble.api.sync.Group;
|
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
|
||||||
import org.briarproject.bramble.util.IoUtils;
|
import org.briarproject.bramble.util.IoUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
|
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
|
||||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
|
||||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
|
||||||
|
|
||||||
public class TestUtils {
|
public class TestUtils {
|
||||||
|
|
||||||
private static final AtomicInteger nextTestDir =
|
private static final AtomicInteger nextTestDir =
|
||||||
@@ -58,85 +38,4 @@ public class TestUtils {
|
|||||||
return new SecretKey(getRandomBytes(SecretKey.LENGTH));
|
return new SecretKey(getRandomBytes(SecretKey.LENGTH));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LocalAuthor getLocalAuthor() {
|
|
||||||
return getLocalAuthor(1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static LocalAuthor getLocalAuthor(int nameLength) {
|
|
||||||
AuthorId id = new AuthorId(getRandomId());
|
|
||||||
String name = getRandomString(nameLength);
|
|
||||||
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
|
||||||
byte[] privateKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
|
||||||
long created = System.currentTimeMillis();
|
|
||||||
return new LocalAuthor(id, FORMAT_VERSION, name, publicKey, privateKey,
|
|
||||||
created);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Author getAuthor() {
|
|
||||||
return getAuthor(1 + random.nextInt(MAX_AUTHOR_NAME_LENGTH));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Author getAuthor(int nameLength) {
|
|
||||||
AuthorId id = new AuthorId(getRandomId());
|
|
||||||
String name = getRandomString(nameLength);
|
|
||||||
byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
|
||||||
return new Author(id, FORMAT_VERSION, name, publicKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Group getGroup(ClientId clientId) {
|
|
||||||
int descriptorLength = 1 + random.nextInt(MAX_GROUP_DESCRIPTOR_LENGTH);
|
|
||||||
return getGroup(clientId, descriptorLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Group getGroup(ClientId clientId, int descriptorLength) {
|
|
||||||
GroupId groupId = new GroupId(getRandomId());
|
|
||||||
byte[] descriptor = getRandomBytes(descriptorLength);
|
|
||||||
return new Group(groupId, clientId, descriptor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Message getMessage(GroupId groupId) {
|
|
||||||
int bodyLength = 1 + random.nextInt(MAX_MESSAGE_BODY_LENGTH);
|
|
||||||
return getMessage(groupId, MESSAGE_HEADER_LENGTH + bodyLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Message getMessage(GroupId groupId, int rawLength) {
|
|
||||||
MessageId id = new MessageId(getRandomId());
|
|
||||||
byte[] raw = getRandomBytes(rawLength);
|
|
||||||
long timestamp = System.currentTimeMillis();
|
|
||||||
return new Message(id, groupId, timestamp, raw);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double getMedian(Collection<? extends Number> samples) {
|
|
||||||
int size = samples.size();
|
|
||||||
if (size == 0) throw new IllegalArgumentException();
|
|
||||||
List<Double> sorted = new ArrayList<>(size);
|
|
||||||
for (Number n : samples) sorted.add(n.doubleValue());
|
|
||||||
Collections.sort(sorted);
|
|
||||||
if (size % 2 == 1) return sorted.get(size / 2);
|
|
||||||
double low = sorted.get(size / 2 - 1), high = sorted.get(size / 2);
|
|
||||||
return (low + high) / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double getMean(Collection<? extends Number> samples) {
|
|
||||||
if (samples.isEmpty()) throw new IllegalArgumentException();
|
|
||||||
double sum = 0;
|
|
||||||
for (Number n : samples) sum += n.doubleValue();
|
|
||||||
return sum / samples.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double getVariance(Collection<? extends Number> samples) {
|
|
||||||
if (samples.size() < 2) throw new IllegalArgumentException();
|
|
||||||
double mean = getMean(samples);
|
|
||||||
double sumSquareDiff = 0;
|
|
||||||
for (Number n : samples) {
|
|
||||||
double diff = n.doubleValue() - mean;
|
|
||||||
sumSquareDiff += diff * diff;
|
|
||||||
}
|
|
||||||
return sumSquareDiff / (samples.size() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double getStandardDeviation(
|
|
||||||
Collection<? extends Number> samples) {
|
|
||||||
return Math.sqrt(getVariance(samples));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,15 +9,12 @@ 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' // The last version that supports Java 1.6
|
implementation 'com.h2database:h2:1.4.192' // This is 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"
|
||||||
@@ -40,21 +37,18 @@ 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,8 +15,6 @@ 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;
|
||||||
@@ -34,12 +32,7 @@ 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
|
||||||
@@ -58,14 +51,12 @@ 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;
|
||||||
@@ -73,7 +64,6 @@ 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
|
||||||
@@ -351,11 +341,6 @@ 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 {
|
||||||
@@ -370,16 +355,4 @@ 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,6 +2,14 @@ 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;
|
||||||
@@ -10,14 +18,19 @@ import dagger.Provides;
|
|||||||
public class ClientModule {
|
public class ClientModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
ClientHelper provideClientHelper(ClientHelperImpl clientHelper) {
|
ClientHelper provideClientHelper(DatabaseComponent db,
|
||||||
return clientHelper;
|
MessageFactory messageFactory, BdfReaderFactory bdfReaderFactory,
|
||||||
|
BdfWriterFactory bdfWriterFactory, MetadataParser metadataParser,
|
||||||
|
MetadataEncoder metadataEncoder, CryptoComponent cryptoComponent) {
|
||||||
|
return new ClientHelperImpl(db, messageFactory, bdfReaderFactory,
|
||||||
|
bdfWriterFactory, metadataParser, metadataEncoder,
|
||||||
|
cryptoComponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
ContactGroupFactory provideContactGroupFactory(
|
ContactGroupFactory provideContactGroupFactory(GroupFactory groupFactory,
|
||||||
ContactGroupFactoryImpl contactGroupFactory) {
|
ClientHelper clientHelper) {
|
||||||
return contactGroupFactory;
|
return new ContactGroupFactoryImpl(groupFactory, clientHelper);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,25 +32,23 @@ class ContactGroupFactoryImpl implements ContactGroupFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Group createLocalGroup(ClientId clientId, int clientVersion) {
|
public Group createLocalGroup(ClientId clientId) {
|
||||||
return groupFactory.createGroup(clientId, clientVersion,
|
return groupFactory.createGroup(clientId, LOCAL_GROUP_DESCRIPTOR);
|
||||||
LOCAL_GROUP_DESCRIPTOR);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Group createContactGroup(ClientId clientId, int clientVersion,
|
public Group createContactGroup(ClientId clientId, Contact contact) {
|
||||||
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, clientVersion, descriptor);
|
return groupFactory.createGroup(clientId, descriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Group createContactGroup(ClientId clientId, int clientVersion,
|
public Group createContactGroup(ClientId clientId, AuthorId authorId1,
|
||||||
AuthorId authorId1, AuthorId authorId2) {
|
AuthorId authorId2) {
|
||||||
byte[] descriptor = createGroupDescriptor(authorId1, authorId2);
|
byte[] descriptor = createGroupDescriptor(authorId1, authorId2);
|
||||||
return groupFactory.createGroup(clientId, clientVersion, descriptor);
|
return groupFactory.createGroup(clientId, descriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] createGroupDescriptor(AuthorId local, AuthorId remote) {
|
private byte[] createGroupDescriptor(AuthorId local, AuthorId remote) {
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ 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;
|
||||||
@@ -142,10 +141,8 @@ 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.deriveKey(ALICE_KEY_LABEL,
|
SecretKey aliceHeaderKey = crypto.deriveHeaderKey(masterSecret, true);
|
||||||
masterSecret, new byte[] {PROTOCOL_VERSION});
|
SecretKey bobHeaderKey = crypto.deriveHeaderKey(masterSecret, false);
|
||||||
SecretKey bobHeaderKey = crypto.deriveKey(BOB_KEY_LABEL, masterSecret,
|
|
||||||
new byte[] {PROTOCOL_VERSION});
|
|
||||||
|
|
||||||
// Create the readers
|
// Create the readers
|
||||||
InputStream streamReader =
|
InputStream streamReader =
|
||||||
@@ -159,10 +156,8 @@ 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.mac(ALICE_NONCE_LABEL, masterSecret,
|
byte[] aliceNonce = crypto.deriveSignatureNonce(masterSecret, true);
|
||||||
new byte[] {PROTOCOL_VERSION});
|
byte[] bobNonce = crypto.deriveSignatureNonce(masterSecret, false);
|
||||||
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();
|
||||||
@@ -201,8 +196,8 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Add the contact
|
// Add the contact
|
||||||
ContactId contactId = addContact(remoteAuthor, timestamp,
|
ContactId contactId = addContact(remoteAuthor, masterSecret,
|
||||||
remoteProperties);
|
timestamp, alice, remoteProperties);
|
||||||
// Reuse the connection as a transport connection
|
// Reuse the connection as a transport connection
|
||||||
connectionManager.manageOutgoingConnection(contactId, transportId,
|
connectionManager.manageOutgoingConnection(contactId, transportId,
|
||||||
conn);
|
conn);
|
||||||
@@ -228,7 +223,6 @@ 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);
|
||||||
@@ -238,16 +232,11 @@ 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 format version, name, public key and signature
|
// Read the 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
|
||||||
@@ -256,7 +245,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
LOG.info("Invalid signature");
|
LOG.info("Invalid signature");
|
||||||
throw new GeneralSecurityException();
|
throw new GeneralSecurityException();
|
||||||
}
|
}
|
||||||
return authorFactory.createAuthor(formatVersion, name, publicKey);
|
return authorFactory.createAuthor(name, publicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendTimestamp(BdfWriter w, long timestamp)
|
private void sendTimestamp(BdfWriter w, long timestamp)
|
||||||
@@ -305,15 +294,15 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
return remote;
|
return remote;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ContactId addContact(Author remoteAuthor, long timestamp,
|
private ContactId addContact(Author remoteAuthor, SecretKey master,
|
||||||
|
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(), masterSecret, timestamp, alice,
|
localAuthor.getId(), master, timestamp, alice, true, true);
|
||||||
true, true);
|
|
||||||
transportPropertyManager.addRemoteProperties(txn, contactId,
|
transportPropertyManager.addRemoteProperties(txn, contactId,
|
||||||
remoteProperties);
|
remoteProperties);
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
@@ -323,7 +312,8 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
return contactId;
|
return contactId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tryToClose(DuplexTransportConnection conn, boolean exception) {
|
private void tryToClose(DuplexTransportConnection conn,
|
||||||
|
boolean exception) {
|
||||||
try {
|
try {
|
||||||
LOG.info("Closing connection");
|
LOG.info("Closing connection");
|
||||||
conn.getReader().dispose(exception, true);
|
conn.getReader().dispose(exception, true);
|
||||||
|
|||||||
@@ -0,0 +1,547 @@
|
|||||||
|
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,59 +1,107 @@
|
|||||||
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.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
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.digests.Blake2bDigest;
|
import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
|
||||||
import org.whispersystems.curve25519.Curve25519;
|
import org.spongycastle.crypto.digests.SHA256Digest;
|
||||||
import org.whispersystems.curve25519.Curve25519KeyPair;
|
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
|
||||||
|
import org.spongycastle.crypto.generators.PKCS5S2ParametersGenerator;
|
||||||
|
import org.spongycastle.crypto.params.ECKeyGenerationParameters;
|
||||||
|
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
|
||||||
|
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
||||||
|
import org.spongycastle.crypto.params.KeyParameter;
|
||||||
|
|
||||||
|
import 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_FORMAT_SCRYPT = 0;
|
private static final int PBKDF_TARGET_MILLIS = 500;
|
||||||
|
private static final int PBKDF_SAMPLES = 30;
|
||||||
|
private static final int HASH_SIZE = 256 / 8;
|
||||||
|
|
||||||
|
private static byte[] ascii(String s) {
|
||||||
|
return s.getBytes(Charset.forName("US-ASCII"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// KDF labels for contact exchange stream header key derivation
|
||||||
|
private static final byte[] A_INVITE = ascii("ALICE_INVITATION_KEY");
|
||||||
|
private static final byte[] B_INVITE = ascii("BOB_INVITATION_KEY");
|
||||||
|
// KDF labels for contact exchange signature nonce derivation
|
||||||
|
private static final byte[] A_SIG_NONCE = ascii("ALICE_SIGNATURE_NONCE");
|
||||||
|
private static final byte[] B_SIG_NONCE = ascii("BOB_SIGNATURE_NONCE");
|
||||||
|
// Hash label for BQP public key commitment derivation
|
||||||
|
private static final String COMMIT =
|
||||||
|
"org.briarproject.bramble.COMMIT";
|
||||||
|
// Hash label for shared secret derivation
|
||||||
|
private static final String SHARED_SECRET =
|
||||||
|
"org.briarproject.bramble.SHARED_SECRET";
|
||||||
|
// KDF label for BQP confirmation key derivation
|
||||||
|
private static final byte[] CONFIRMATION_KEY = ascii("CONFIRMATION_KEY");
|
||||||
|
// KDF label for master key derivation
|
||||||
|
private static final byte[] MASTER_KEY = ascii("MASTER_KEY");
|
||||||
|
// KDF labels for tag key derivation
|
||||||
|
private static final byte[] A_TAG = ascii("ALICE_TAG_KEY");
|
||||||
|
private static final byte[] B_TAG = ascii("BOB_TAG_KEY");
|
||||||
|
// KDF labels for header key derivation
|
||||||
|
private static final byte[] A_HEADER = ascii("ALICE_HEADER_KEY");
|
||||||
|
private static final byte[] B_HEADER = ascii("BOB_HEADER_KEY");
|
||||||
|
// KDF labels for MAC key derivation
|
||||||
|
private static final byte[] A_MAC = ascii("ALICE_MAC_KEY");
|
||||||
|
private static final byte[] B_MAC = ascii("BOB_MAC_KEY");
|
||||||
|
// KDF label for key rotation
|
||||||
|
private static final byte[] ROTATE = ascii("ROTATE");
|
||||||
|
|
||||||
private final SecureRandom secureRandom;
|
private final SecureRandom secureRandom;
|
||||||
private final PasswordBasedKdf passwordBasedKdf;
|
private final ECKeyPairGenerator agreementKeyPairGenerator;
|
||||||
private final Curve25519 curve25519;
|
private final ECKeyPairGenerator signatureKeyPairGenerator;
|
||||||
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();
|
||||||
@@ -73,13 +121,16 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
secureRandom = new SecureRandom();
|
secureRandom = new SecureRandom();
|
||||||
this.passwordBasedKdf = passwordBasedKdf;
|
ECKeyGenerationParameters params = new ECKeyGenerationParameters(
|
||||||
curve25519 = Curve25519.getInstance("java");
|
PARAMETERS, secureRandom);
|
||||||
signatureKeyPairGenerator = new KeyPairGenerator();
|
agreementKeyPairGenerator = new ECKeyPairGenerator();
|
||||||
signatureKeyPairGenerator.initialize(SIGNATURE_KEY_PAIR_BITS,
|
agreementKeyPairGenerator.init(params);
|
||||||
secureRandom);
|
signatureKeyPairGenerator = new ECKeyPairGenerator();
|
||||||
agreementKeyParser = new Curve25519KeyParser();
|
signatureKeyPairGenerator.init(params);
|
||||||
signatureKeyParser = new EdKeyParser();
|
agreementKeyParser = new Sec1KeyParser(PARAMETERS,
|
||||||
|
AGREEMENT_KEY_PAIR_BITS);
|
||||||
|
signatureKeyParser = new Sec1KeyParser(PARAMETERS,
|
||||||
|
SIGNATURE_KEY_PAIR_BITS);
|
||||||
messageEncrypter = new MessageEncrypter(secureRandom);
|
messageEncrypter = new MessageEncrypter(secureRandom);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,17 +174,16 @@ 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 Curve25519PrivateKey))
|
if (!(priv instanceof Sec1PrivateKey))
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
if (!(pub instanceof Curve25519PublicKey))
|
if (!(pub instanceof Sec1PublicKey))
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
|
ECPrivateKeyParameters ecPriv = ((Sec1PrivateKey) priv).getKey();
|
||||||
|
ECPublicKeyParameters ecPub = ((Sec1PublicKey) pub).getKey();
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
byte[] secret = curve25519.calculateAgreement(pub.getEncoded(),
|
ECDHCBasicAgreement agreement = new ECDHCBasicAgreement();
|
||||||
priv.getEncoded());
|
agreement.init(ecPriv);
|
||||||
// If the shared secret is all zeroes, the public key is invalid
|
byte[] secret = agreement.calculateAgreement(ecPub).toByteArray();
|
||||||
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");
|
||||||
@@ -142,10 +192,18 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyPair generateAgreementKeyPair() {
|
public KeyPair generateAgreementKeyPair() {
|
||||||
Curve25519KeyPair keyPair = curve25519.generateKeyPair();
|
AsymmetricCipherKeyPair keyPair =
|
||||||
PublicKey pub = new Curve25519PublicKey(keyPair.getPublicKey());
|
agreementKeyPairGenerator.generateKeyPair();
|
||||||
PrivateKey priv = new Curve25519PrivateKey(keyPair.getPrivateKey());
|
// Return a wrapper that uses the SEC 1 encoding
|
||||||
return new KeyPair(pub, priv);
|
ECPublicKeyParameters ecPublicKey =
|
||||||
|
(ECPublicKeyParameters) keyPair.getPublic();
|
||||||
|
PublicKey publicKey = new Sec1PublicKey(ecPublicKey
|
||||||
|
);
|
||||||
|
ECPrivateKeyParameters ecPrivateKey =
|
||||||
|
(ECPrivateKeyParameters) keyPair.getPrivate();
|
||||||
|
PrivateKey privateKey = new Sec1PrivateKey(ecPrivateKey,
|
||||||
|
AGREEMENT_KEY_PAIR_BITS);
|
||||||
|
return new KeyPair(publicKey, privateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -155,12 +213,17 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyPair generateSignatureKeyPair() {
|
public KeyPair generateSignatureKeyPair() {
|
||||||
java.security.KeyPair keyPair =
|
AsymmetricCipherKeyPair keyPair =
|
||||||
signatureKeyPairGenerator.generateKeyPair();
|
signatureKeyPairGenerator.generateKeyPair();
|
||||||
EdDSAPublicKey edPublicKey = (EdDSAPublicKey) keyPair.getPublic();
|
// Return a wrapper that uses the SEC 1 encoding
|
||||||
PublicKey publicKey = new EdPublicKey(edPublicKey.getAbyte());
|
ECPublicKeyParameters ecPublicKey =
|
||||||
EdDSAPrivateKey edPrivateKey = (EdDSAPrivateKey) keyPair.getPrivate();
|
(ECPublicKeyParameters) keyPair.getPublic();
|
||||||
PrivateKey privateKey = new EdPrivateKey(edPrivateKey.getSeed());
|
PublicKey publicKey = new Sec1PublicKey(ecPublicKey
|
||||||
|
);
|
||||||
|
ECPrivateKeyParameters ecPrivateKey =
|
||||||
|
(ECPrivateKeyParameters) keyPair.getPrivate();
|
||||||
|
PrivateKey privateKey = new Sec1PrivateKey(ecPrivateKey,
|
||||||
|
SIGNATURE_KEY_PAIR_BITS);
|
||||||
return new KeyPair(publicKey, privateKey);
|
return new KeyPair(publicKey, privateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,47 +238,205 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SecretKey deriveKey(String label, SecretKey k, byte[]... inputs) {
|
public SecretKey deriveHeaderKey(SecretKey master,
|
||||||
byte[] mac = mac(label, k, inputs);
|
boolean alice) {
|
||||||
if (mac.length != SecretKey.LENGTH) throw new IllegalStateException();
|
return new SecretKey(macKdf(master, alice ? A_INVITE : B_INVITE));
|
||||||
return new SecretKey(mac);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SecretKey deriveSharedSecret(String label, PublicKey theirPublicKey,
|
public SecretKey deriveMacKey(SecretKey master, boolean alice) {
|
||||||
KeyPair ourKeyPair, byte[]... inputs)
|
return new SecretKey(macKdf(master, alice ? A_MAC : B_MAC));
|
||||||
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();
|
||||||
byte[][] hashInputs = new byte[inputs.length + 1][];
|
PublicKey theirPub = agreementKeyParser.parsePublicKey(theirPublicKey);
|
||||||
hashInputs[0] = performRawKeyAgreement(ourPriv, theirPublicKey);
|
byte[] raw = performRawKeyAgreement(ourPriv, theirPub);
|
||||||
System.arraycopy(inputs, 0, hashInputs, 1, inputs.length);
|
byte[] alicePub, bobPub;
|
||||||
byte[] hash = hash(label, hashInputs);
|
if (alice) {
|
||||||
if (hash.length != SecretKey.LENGTH) throw new IllegalStateException();
|
alicePub = ourKeyPair.getPublic().getEncoded();
|
||||||
return new SecretKey(hash);
|
bobPub = theirPublicKey;
|
||||||
|
} else {
|
||||||
|
alicePub = theirPublicKey;
|
||||||
|
bobPub = ourKeyPair.getPublic().getEncoded();
|
||||||
|
}
|
||||||
|
return new SecretKey(hash(SHARED_SECRET, raw, alicePub, bobPub));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] deriveConfirmationRecord(SecretKey sharedSecret,
|
||||||
|
byte[] theirPayload, byte[] ourPayload, byte[] theirPublicKey,
|
||||||
|
KeyPair ourKeyPair, boolean alice, boolean aliceRecord) {
|
||||||
|
SecretKey ck = new SecretKey(macKdf(sharedSecret, CONFIRMATION_KEY));
|
||||||
|
byte[] alicePayload, alicePub, bobPayload, bobPub;
|
||||||
|
if (alice) {
|
||||||
|
alicePayload = ourPayload;
|
||||||
|
alicePub = ourKeyPair.getPublic().getEncoded();
|
||||||
|
bobPayload = theirPayload;
|
||||||
|
bobPub = theirPublicKey;
|
||||||
|
} else {
|
||||||
|
alicePayload = theirPayload;
|
||||||
|
alicePub = theirPublicKey;
|
||||||
|
bobPayload = ourPayload;
|
||||||
|
bobPub = ourKeyPair.getPublic().getEncoded();
|
||||||
|
}
|
||||||
|
if (aliceRecord)
|
||||||
|
return macKdf(ck, alicePayload, alicePub, bobPayload, bobPub);
|
||||||
|
else
|
||||||
|
return macKdf(ck, bobPayload, bobPub, alicePayload, alicePub);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecretKey deriveMasterSecret(SecretKey sharedSecret) {
|
||||||
|
return new SecretKey(macKdf(sharedSecret, MASTER_KEY));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecretKey deriveMasterSecret(byte[] theirPublicKey,
|
||||||
|
KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException {
|
||||||
|
return deriveMasterSecret(deriveSharedSecret(
|
||||||
|
theirPublicKey, ourKeyPair, alice));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransportKeys deriveTransportKeys(TransportId t,
|
||||||
|
SecretKey master, long rotationPeriod, boolean alice) {
|
||||||
|
// Keys for the previous period are derived from the master secret
|
||||||
|
SecretKey inTagPrev = deriveTagKey(master, t, !alice);
|
||||||
|
SecretKey inHeaderPrev = deriveHeaderKey(master, t, !alice);
|
||||||
|
SecretKey outTagPrev = deriveTagKey(master, t, alice);
|
||||||
|
SecretKey outHeaderPrev = deriveHeaderKey(master, t, alice);
|
||||||
|
// Derive the keys for the current and next periods
|
||||||
|
SecretKey inTagCurr = rotateKey(inTagPrev, rotationPeriod);
|
||||||
|
SecretKey inHeaderCurr = rotateKey(inHeaderPrev, rotationPeriod);
|
||||||
|
SecretKey inTagNext = rotateKey(inTagCurr, rotationPeriod + 1);
|
||||||
|
SecretKey inHeaderNext = rotateKey(inHeaderCurr, rotationPeriod + 1);
|
||||||
|
SecretKey outTagCurr = rotateKey(outTagPrev, rotationPeriod);
|
||||||
|
SecretKey outHeaderCurr = rotateKey(outHeaderPrev, rotationPeriod);
|
||||||
|
// Initialise the reordering windows and stream counters
|
||||||
|
IncomingKeys inPrev = new IncomingKeys(inTagPrev, inHeaderPrev,
|
||||||
|
rotationPeriod - 1);
|
||||||
|
IncomingKeys inCurr = new IncomingKeys(inTagCurr, inHeaderCurr,
|
||||||
|
rotationPeriod);
|
||||||
|
IncomingKeys inNext = new IncomingKeys(inTagNext, inHeaderNext,
|
||||||
|
rotationPeriod + 1);
|
||||||
|
OutgoingKeys outCurr = new OutgoingKeys(outTagCurr, outHeaderCurr,
|
||||||
|
rotationPeriod);
|
||||||
|
// Collect and return the keys
|
||||||
|
return new TransportKeys(t, inPrev, inCurr, inNext, outCurr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransportKeys rotateTransportKeys(TransportKeys k,
|
||||||
|
long rotationPeriod) {
|
||||||
|
if (k.getRotationPeriod() >= rotationPeriod) return k;
|
||||||
|
IncomingKeys inPrev = k.getPreviousIncomingKeys();
|
||||||
|
IncomingKeys inCurr = k.getCurrentIncomingKeys();
|
||||||
|
IncomingKeys inNext = k.getNextIncomingKeys();
|
||||||
|
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
|
||||||
|
long startPeriod = outCurr.getRotationPeriod();
|
||||||
|
// Rotate the keys
|
||||||
|
for (long p = startPeriod + 1; p <= rotationPeriod; p++) {
|
||||||
|
inPrev = inCurr;
|
||||||
|
inCurr = inNext;
|
||||||
|
SecretKey inNextTag = rotateKey(inNext.getTagKey(), p + 1);
|
||||||
|
SecretKey inNextHeader = rotateKey(inNext.getHeaderKey(), p + 1);
|
||||||
|
inNext = new IncomingKeys(inNextTag, inNextHeader, p + 1);
|
||||||
|
SecretKey outCurrTag = rotateKey(outCurr.getTagKey(), p);
|
||||||
|
SecretKey outCurrHeader = rotateKey(outCurr.getHeaderKey(), p);
|
||||||
|
outCurr = new OutgoingKeys(outCurrTag, outCurrHeader, p);
|
||||||
|
}
|
||||||
|
// Collect and return the keys
|
||||||
|
return new TransportKeys(k.getTransportId(), inPrev, inCurr, inNext,
|
||||||
|
outCurr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SecretKey rotateKey(SecretKey k, long rotationPeriod) {
|
||||||
|
byte[] period = new byte[INT_64_BYTES];
|
||||||
|
ByteUtils.writeUint64(rotationPeriod, period, 0);
|
||||||
|
return new SecretKey(macKdf(k, ROTATE, period));
|
||||||
|
}
|
||||||
|
|
||||||
|
private SecretKey deriveTagKey(SecretKey master, TransportId t,
|
||||||
|
boolean alice) {
|
||||||
|
byte[] id = StringUtils.toUtf8(t.getString());
|
||||||
|
return new SecretKey(macKdf(master, alice ? A_TAG : B_TAG, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
private SecretKey deriveHeaderKey(SecretKey master, TransportId t,
|
||||||
|
boolean alice) {
|
||||||
|
byte[] id = StringUtils.toUtf8(t.getString());
|
||||||
|
return new SecretKey(macKdf(master, alice ? A_HEADER : B_HEADER, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void encodeTag(byte[] tag, SecretKey tagKey, int protocolVersion,
|
||||||
|
long streamNumber) {
|
||||||
|
if (tag.length < TAG_LENGTH) throw new IllegalArgumentException();
|
||||||
|
if (protocolVersion < 0 || protocolVersion > MAX_16_BIT_UNSIGNED)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
if (streamNumber < 0 || streamNumber > MAX_32_BIT_UNSIGNED)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
// Initialise the PRF
|
||||||
|
Digest prf = new Blake2sDigest(tagKey.getBytes());
|
||||||
|
// The output of the PRF must be long enough to use as a tag
|
||||||
|
int macLength = prf.getDigestSize();
|
||||||
|
if (macLength < TAG_LENGTH) throw new IllegalStateException();
|
||||||
|
// The input is the protocol version as a 16-bit integer, followed by
|
||||||
|
// the stream number as a 64-bit integer
|
||||||
|
byte[] protocolVersionBytes = new byte[INT_16_BYTES];
|
||||||
|
ByteUtils.writeUint16(protocolVersion, protocolVersionBytes, 0);
|
||||||
|
prf.update(protocolVersionBytes, 0, protocolVersionBytes.length);
|
||||||
|
byte[] streamNumberBytes = new byte[INT_64_BYTES];
|
||||||
|
ByteUtils.writeUint64(streamNumber, streamNumberBytes, 0);
|
||||||
|
prf.update(streamNumberBytes, 0, streamNumberBytes.length);
|
||||||
|
byte[] mac = new byte[macLength];
|
||||||
|
prf.doFinal(mac, 0);
|
||||||
|
// The output is the first TAG_LENGTH bytes of the MAC
|
||||||
|
System.arraycopy(mac, 0, tag, 0, TAG_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] sign(String label, byte[] toSign, byte[] privateKey)
|
public byte[] sign(String label, byte[] toSign, byte[] privateKey)
|
||||||
throws GeneralSecurityException {
|
throws GeneralSecurityException {
|
||||||
PrivateKey key = signatureKeyParser.parsePrivateKey(privateKey);
|
Signature signature = new SignatureImpl(secureRandom);
|
||||||
Signature sig = new EdSignature();
|
KeyParser keyParser = getSignatureKeyParser();
|
||||||
sig.initSign(key);
|
PrivateKey key = keyParser.parsePrivateKey(privateKey);
|
||||||
updateSignature(sig, label, toSign);
|
signature.initSign(key);
|
||||||
return sig.sign();
|
updateSignature(signature, label, toSign);
|
||||||
|
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 {
|
||||||
PublicKey key = signatureKeyParser.parsePublicKey(publicKey);
|
Signature sig = new SignatureImpl(secureRandom);
|
||||||
Signature sig = new EdSignature();
|
KeyParser keyParser = getSignatureKeyParser();
|
||||||
|
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) throws GeneralSecurityException {
|
byte[] toSign) {
|
||||||
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);
|
||||||
@@ -229,7 +450,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 Blake2bDigest(256);
|
Digest digest = new Blake2sDigest();
|
||||||
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);
|
||||||
@@ -245,13 +466,14 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] mac(String label, SecretKey macKey, byte[]... inputs) {
|
public int getHashLength() {
|
||||||
byte[] labelBytes = StringUtils.toUtf8(label);
|
return HASH_SIZE;
|
||||||
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);
|
||||||
@@ -270,33 +492,23 @@ 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 cost = passwordBasedKdf.chooseCostParameter();
|
int iterations = chooseIterationCount(PBKDF_TARGET_MILLIS);
|
||||||
// Derive the key from the password
|
// Derive the key from the password
|
||||||
SecretKey key = passwordBasedKdf.deriveKey(password, salt, cost);
|
SecretKey key = new SecretKey(pbkdf2(password, salt, iterations));
|
||||||
// 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 format version, salt, cost parameter, IV,
|
// The output contains the salt, iterations, IV, ciphertext and MAC
|
||||||
// ciphertext and MAC
|
int outputLen = salt.length + INT_32_BYTES + iv.length + input.length
|
||||||
int outputLen = 1 + salt.length + INT_32_BYTES + iv.length
|
+ macBytes;
|
||||||
+ input.length + macBytes;
|
|
||||||
byte[] output = new byte[outputLen];
|
byte[] output = new byte[outputLen];
|
||||||
int outputOff = 0;
|
System.arraycopy(salt, 0, output, 0, salt.length);
|
||||||
// Format version
|
ByteUtils.writeUint32(iterations, output, salt.length);
|
||||||
output[outputOff] = PBKDF_FORMAT_SCRYPT;
|
System.arraycopy(iv, 0, output, salt.length + INT_32_BYTES, iv.length);
|
||||||
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) {
|
||||||
@@ -305,36 +517,22 @@ 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 format version, salt, cost parameter, IV,
|
// The input contains the salt, iterations, IV, ciphertext and MAC
|
||||||
// ciphertext and MAC
|
if (input.length < PBKDF_SALT_BYTES + INT_32_BYTES + STORAGE_IV_BYTES
|
||||||
if (input.length < 1 + PBKDF_SALT_BYTES + INT_32_BYTES
|
+ macBytes)
|
||||||
+ 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, inputOff, salt, 0, salt.length);
|
System.arraycopy(input, 0, salt, 0, salt.length);
|
||||||
inputOff += salt.length;
|
long iterations = ByteUtils.readUint32(input, salt.length);
|
||||||
// Cost parameter
|
if (iterations < 0 || iterations > Integer.MAX_VALUE)
|
||||||
long cost = ByteUtils.readUint32(input, inputOff);
|
return null; // Invalid iteration count
|
||||||
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, inputOff, iv, 0, iv.length);
|
System.arraycopy(input, salt.length + INT_32_BYTES, iv, 0, iv.length);
|
||||||
inputOff += iv.length;
|
|
||||||
// Derive the key from the password
|
// Derive the key from the password
|
||||||
SecretKey key = passwordBasedKdf.deriveKey(password, salt, (int) cost);
|
SecretKey key = new SecretKey(pbkdf2(password, salt, (int) iterations));
|
||||||
// Initialise the cipher
|
// Initialise the cipher
|
||||||
try {
|
try {
|
||||||
cipher.init(false, key, iv);
|
cipher.init(false, key, iv);
|
||||||
@@ -343,6 +541,7 @@ 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);
|
||||||
@@ -365,4 +564,88 @@ 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,11 +3,9 @@ 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;
|
||||||
|
|
||||||
@@ -67,9 +65,8 @@ public class CryptoModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
CryptoComponent provideCryptoComponent(
|
CryptoComponent provideCryptoComponent(
|
||||||
SecureRandomProvider secureRandomProvider,
|
SecureRandomProvider secureRandomProvider) {
|
||||||
ScryptKdf passwordBasedKdf) {
|
return new CryptoComponentImpl(secureRandomProvider);
|
||||||
return new CryptoComponentImpl(secureRandomProvider, passwordBasedKdf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -77,12 +74,6 @@ 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) {
|
||||||
@@ -90,17 +81,9 @@ public class CryptoModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
StreamEncrypterFactory provideStreamEncrypterFactory(
|
StreamEncrypterFactory provideStreamEncrypterFactory(CryptoComponent crypto,
|
||||||
CryptoComponent crypto, TransportCrypto transportCrypto,
|
|
||||||
Provider<AuthenticatedCipher> cipherProvider) {
|
Provider<AuthenticatedCipher> cipherProvider) {
|
||||||
return new StreamEncrypterFactoryImpl(crypto, transportCrypto,
|
return new StreamEncrypterFactoryImpl(crypto, cipherProvider);
|
||||||
cipherProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
KeyAgreementCrypto provideKeyAgreementCrypto(
|
|
||||||
KeyAgreementCryptoImpl keyAgreementCrypto) {
|
|
||||||
return keyAgreementCrypto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package org.briarproject.bramble.crypto;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
|
||||||
|
|
||||||
interface PasswordBasedKdf {
|
|
||||||
|
|
||||||
int chooseCostParameter();
|
|
||||||
|
|
||||||
SecretKey deriveKey(String password, byte[] salt, int cost);
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
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) throws GeneralSecurityException;
|
void update(byte b);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see {@link java.security.Signature#update(byte[])}
|
* @see {@link java.security.Signature#update(byte[])}
|
||||||
*/
|
*/
|
||||||
void update(byte[] b) throws GeneralSecurityException;
|
void update(byte[] b);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @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) throws GeneralSecurityException;
|
void update(byte[] b, int off, int len);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see {@link java.security.Signature#sign()}
|
* @see {@link java.security.Signature#sign()}
|
||||||
*/
|
*/
|
||||||
byte[] sign() throws GeneralSecurityException;
|
byte[] sign();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see {@link java.security.Signature#verify(byte[])}
|
* @see {@link java.security.Signature#verify(byte[])}
|
||||||
*/
|
*/
|
||||||
boolean verify(byte[] signature) throws GeneralSecurityException;
|
boolean verify(byte[] signature);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
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,7 +4,6 @@ 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;
|
||||||
|
|
||||||
@@ -23,15 +22,12 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,8 +37,7 @@ 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];
|
||||||
transportCrypto.encodeTag(tag, ctx.getTagKey(), PROTOCOL_VERSION,
|
crypto.encodeTag(tag, ctx.getTagKey(), PROTOCOL_VERSION, streamNumber);
|
||||||
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();
|
||||||
|
|||||||
@@ -1,136 +0,0 @@
|
|||||||
package org.briarproject.bramble.crypto;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
|
||||||
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
|
||||||
import org.briarproject.bramble.api.transport.IncomingKeys;
|
|
||||||
import org.briarproject.bramble.api.transport.OutgoingKeys;
|
|
||||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
|
||||||
import org.briarproject.bramble.util.ByteUtils;
|
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
|
||||||
import org.spongycastle.crypto.Digest;
|
|
||||||
import org.spongycastle.crypto.digests.Blake2bDigest;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.ALICE_HEADER_LABEL;
|
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.ALICE_TAG_LABEL;
|
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.BOB_HEADER_LABEL;
|
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.BOB_TAG_LABEL;
|
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.ROTATE_LABEL;
|
|
||||||
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
|
||||||
import static org.briarproject.bramble.util.ByteUtils.INT_16_BYTES;
|
|
||||||
import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
|
|
||||||
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
|
|
||||||
import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
|
||||||
|
|
||||||
class TransportCryptoImpl implements TransportCrypto {
|
|
||||||
|
|
||||||
private final CryptoComponent crypto;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
TransportCryptoImpl(CryptoComponent crypto) {
|
|
||||||
this.crypto = crypto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TransportKeys deriveTransportKeys(TransportId t,
|
|
||||||
SecretKey master, long rotationPeriod, boolean alice) {
|
|
||||||
// Keys for the previous period are derived from the master secret
|
|
||||||
SecretKey inTagPrev = deriveTagKey(master, t, !alice);
|
|
||||||
SecretKey inHeaderPrev = deriveHeaderKey(master, t, !alice);
|
|
||||||
SecretKey outTagPrev = deriveTagKey(master, t, alice);
|
|
||||||
SecretKey outHeaderPrev = deriveHeaderKey(master, t, alice);
|
|
||||||
// Derive the keys for the current and next periods
|
|
||||||
SecretKey inTagCurr = rotateKey(inTagPrev, rotationPeriod);
|
|
||||||
SecretKey inHeaderCurr = rotateKey(inHeaderPrev, rotationPeriod);
|
|
||||||
SecretKey inTagNext = rotateKey(inTagCurr, rotationPeriod + 1);
|
|
||||||
SecretKey inHeaderNext = rotateKey(inHeaderCurr, rotationPeriod + 1);
|
|
||||||
SecretKey outTagCurr = rotateKey(outTagPrev, rotationPeriod);
|
|
||||||
SecretKey outHeaderCurr = rotateKey(outHeaderPrev, rotationPeriod);
|
|
||||||
// Initialise the reordering windows and stream counters
|
|
||||||
IncomingKeys inPrev = new IncomingKeys(inTagPrev, inHeaderPrev,
|
|
||||||
rotationPeriod - 1);
|
|
||||||
IncomingKeys inCurr = new IncomingKeys(inTagCurr, inHeaderCurr,
|
|
||||||
rotationPeriod);
|
|
||||||
IncomingKeys inNext = new IncomingKeys(inTagNext, inHeaderNext,
|
|
||||||
rotationPeriod + 1);
|
|
||||||
OutgoingKeys outCurr = new OutgoingKeys(outTagCurr, outHeaderCurr,
|
|
||||||
rotationPeriod);
|
|
||||||
// Collect and return the keys
|
|
||||||
return new TransportKeys(t, inPrev, inCurr, inNext, outCurr);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TransportKeys rotateTransportKeys(TransportKeys k,
|
|
||||||
long rotationPeriod) {
|
|
||||||
if (k.getRotationPeriod() >= rotationPeriod) return k;
|
|
||||||
IncomingKeys inPrev = k.getPreviousIncomingKeys();
|
|
||||||
IncomingKeys inCurr = k.getCurrentIncomingKeys();
|
|
||||||
IncomingKeys inNext = k.getNextIncomingKeys();
|
|
||||||
OutgoingKeys outCurr = k.getCurrentOutgoingKeys();
|
|
||||||
long startPeriod = outCurr.getRotationPeriod();
|
|
||||||
// Rotate the keys
|
|
||||||
for (long p = startPeriod + 1; p <= rotationPeriod; p++) {
|
|
||||||
inPrev = inCurr;
|
|
||||||
inCurr = inNext;
|
|
||||||
SecretKey inNextTag = rotateKey(inNext.getTagKey(), p + 1);
|
|
||||||
SecretKey inNextHeader = rotateKey(inNext.getHeaderKey(), p + 1);
|
|
||||||
inNext = new IncomingKeys(inNextTag, inNextHeader, p + 1);
|
|
||||||
SecretKey outCurrTag = rotateKey(outCurr.getTagKey(), p);
|
|
||||||
SecretKey outCurrHeader = rotateKey(outCurr.getHeaderKey(), p);
|
|
||||||
outCurr = new OutgoingKeys(outCurrTag, outCurrHeader, p);
|
|
||||||
}
|
|
||||||
// Collect and return the keys
|
|
||||||
return new TransportKeys(k.getTransportId(), inPrev, inCurr, inNext,
|
|
||||||
outCurr);
|
|
||||||
}
|
|
||||||
|
|
||||||
private SecretKey rotateKey(SecretKey k, long rotationPeriod) {
|
|
||||||
byte[] period = new byte[INT_64_BYTES];
|
|
||||||
ByteUtils.writeUint64(rotationPeriod, period, 0);
|
|
||||||
return crypto.deriveKey(ROTATE_LABEL, k, period);
|
|
||||||
}
|
|
||||||
|
|
||||||
private SecretKey deriveTagKey(SecretKey master, TransportId t,
|
|
||||||
boolean alice) {
|
|
||||||
String label = alice ? ALICE_TAG_LABEL : BOB_TAG_LABEL;
|
|
||||||
byte[] id = StringUtils.toUtf8(t.getString());
|
|
||||||
return crypto.deriveKey(label, master, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private SecretKey deriveHeaderKey(SecretKey master, TransportId t,
|
|
||||||
boolean alice) {
|
|
||||||
String label = alice ? ALICE_HEADER_LABEL : BOB_HEADER_LABEL;
|
|
||||||
byte[] id = StringUtils.toUtf8(t.getString());
|
|
||||||
return crypto.deriveKey(label, master, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void encodeTag(byte[] tag, SecretKey tagKey, int protocolVersion,
|
|
||||||
long streamNumber) {
|
|
||||||
if (tag.length < TAG_LENGTH) throw new IllegalArgumentException();
|
|
||||||
if (protocolVersion < 0 || protocolVersion > MAX_16_BIT_UNSIGNED)
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
if (streamNumber < 0 || streamNumber > MAX_32_BIT_UNSIGNED)
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
// Initialise the PRF
|
|
||||||
Digest prf = new Blake2bDigest(tagKey.getBytes(), 32, null, null);
|
|
||||||
// The output of the PRF must be long enough to use as a tag
|
|
||||||
int macLength = prf.getDigestSize();
|
|
||||||
if (macLength < TAG_LENGTH) throw new IllegalStateException();
|
|
||||||
// The input is the protocol version as a 16-bit integer, followed by
|
|
||||||
// the stream number as a 64-bit integer
|
|
||||||
byte[] protocolVersionBytes = new byte[INT_16_BYTES];
|
|
||||||
ByteUtils.writeUint16(protocolVersion, protocolVersionBytes, 0);
|
|
||||||
prf.update(protocolVersionBytes, 0, protocolVersionBytes.length);
|
|
||||||
byte[] streamNumberBytes = new byte[INT_64_BYTES];
|
|
||||||
ByteUtils.writeUint64(streamNumber, streamNumberBytes, 0);
|
|
||||||
prf.update(streamNumberBytes, 0, streamNumberBytes.length);
|
|
||||||
byte[] mac = new byte[macLength];
|
|
||||||
prf.doFinal(mac, 0);
|
|
||||||
// The output is the first TAG_LENGTH bytes of the MAC
|
|
||||||
System.arraycopy(mac, 0, tag, 0, TAG_LENGTH);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,11 +2,8 @@ package org.briarproject.bramble.db;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.db.DataTooNewException;
|
|
||||||
import org.briarproject.bramble.api.db.DataTooOldException;
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.Metadata;
|
import org.briarproject.bramble.api.db.Metadata;
|
||||||
import org.briarproject.bramble.api.db.MigrationListener;
|
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||||
@@ -40,13 +37,8 @@ interface Database<T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the database and returns true if the database already existed.
|
* Opens the database and returns true if the database already existed.
|
||||||
*
|
|
||||||
* @throws DataTooNewException if the data uses a newer schema than the
|
|
||||||
* current code
|
|
||||||
* @throws DataTooOldException if the data uses an older schema than the
|
|
||||||
* current code and cannot be migrated
|
|
||||||
*/
|
*/
|
||||||
boolean open(@Nullable MigrationListener listener) throws DbException;
|
boolean open() throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prevents new transactions from starting, waits for all current
|
* Prevents new transactions from starting, waits for all current
|
||||||
@@ -97,12 +89,9 @@ interface Database<T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores a message.
|
* Stores a message.
|
||||||
*
|
|
||||||
* @param sender the contact from whom the message was received, or null
|
|
||||||
* if the message was created locally.
|
|
||||||
*/
|
*/
|
||||||
void addMessage(T txn, Message m, State state, boolean shared,
|
void addMessage(T txn, Message m, State state, boolean shared)
|
||||||
@Nullable ContactId sender) throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a dependency between two messages in the given group.
|
* Adds a dependency between two messages in the given group.
|
||||||
@@ -115,6 +104,16 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
void addOfferedMessage(T txn, ContactId c, MessageId m) throws DbException;
|
void addOfferedMessage(T txn, ContactId c, MessageId m) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises the status of the given message with respect to the given
|
||||||
|
* contact.
|
||||||
|
*
|
||||||
|
* @param ack whether the message needs to be acknowledged.
|
||||||
|
* @param seen whether the contact has seen the message.
|
||||||
|
*/
|
||||||
|
void addStatus(T txn, ContactId c, MessageId m, boolean ack, boolean seen)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores a transport.
|
* Stores a transport.
|
||||||
*/
|
*/
|
||||||
@@ -273,7 +272,7 @@ interface Database<T> {
|
|||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
Map<ContactId, Boolean> getGroupVisibility(T txn, GroupId g)
|
Collection<ContactId> getGroupVisibility(T txn, GroupId g)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -424,37 +423,31 @@ interface Database<T> {
|
|||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IDs of any messages that need to be validated.
|
* Returns the IDs of any messages that need to be validated by the given
|
||||||
|
* client.
|
||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
Collection<MessageId> getMessagesToValidate(T txn) throws DbException;
|
Collection<MessageId> getMessagesToValidate(T txn, ClientId c)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IDs of any messages that are pending delivery due to
|
* Returns the IDs of any messages that are still pending due to
|
||||||
* dependencies on other messages.
|
* dependencies to other messages for the given client.
|
||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
Collection<MessageId> getPendingMessages(T txn) throws DbException;
|
Collection<MessageId> getPendingMessages(T txn, ClientId c)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the IDs of any messages that have a shared dependent but have
|
* Returns the IDs of any messages from the given client
|
||||||
* not yet been shared themselves.
|
* that have a shared dependent, but are still not shared themselves.
|
||||||
* <p/>
|
* <p/>
|
||||||
* Read-only.
|
* Read-only.
|
||||||
*/
|
*/
|
||||||
Collection<MessageId> getMessagesToShare(T txn) throws DbException;
|
Collection<MessageId> getMessagesToShare(T txn, ClientId c)
|
||||||
|
throws DbException;
|
||||||
/**
|
|
||||||
* Returns the next time (in milliseconds since the Unix epoch) when a
|
|
||||||
* message is due to be sent to the given contact. The returned value may
|
|
||||||
* be zero if a message is due to be sent immediately, or Long.MAX_VALUE
|
|
||||||
* if no messages are scheduled to be sent.
|
|
||||||
* <p/>
|
|
||||||
* Read-only.
|
|
||||||
*/
|
|
||||||
long getNextSendTime(T txn, ContactId c) throws DbException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the message with the given ID, in serialised form, or null if
|
* Returns the message with the given ID, in serialised form, or null if
|
||||||
@@ -573,6 +566,13 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
void removeMessage(T txn, MessageId m) throws DbException;
|
void removeMessage(T txn, MessageId m) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an offered message that was offered by the given contact, or
|
||||||
|
* returns false if there is no such message.
|
||||||
|
*/
|
||||||
|
boolean removeOfferedMessage(T txn, ContactId c, MessageId m)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the given offered messages that were offered by the given
|
* Removes the given offered messages that were offered by the given
|
||||||
* contact.
|
* contact.
|
||||||
@@ -580,6 +580,12 @@ interface Database<T> {
|
|||||||
void removeOfferedMessages(T txn, ContactId c,
|
void removeOfferedMessages(T txn, ContactId c,
|
||||||
Collection<MessageId> requested) throws DbException;
|
Collection<MessageId> requested) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the status of the given message with respect to the given
|
||||||
|
* contact.
|
||||||
|
*/
|
||||||
|
void removeStatus(T txn, ContactId c, MessageId m) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a transport (and all associated state) from the database.
|
* Removes a transport (and all associated state) from the database.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import org.briarproject.bramble.api.db.ContactExistsException;
|
|||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.Metadata;
|
import org.briarproject.bramble.api.db.Metadata;
|
||||||
import org.briarproject.bramble.api.db.MigrationListener;
|
|
||||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||||
import org.briarproject.bramble.api.db.NoSuchGroupException;
|
import org.briarproject.bramble.api.db.NoSuchGroupException;
|
||||||
import org.briarproject.bramble.api.db.NoSuchLocalAuthorException;
|
import org.briarproject.bramble.api.db.NoSuchLocalAuthorException;
|
||||||
@@ -91,6 +90,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
private final ReentrantReadWriteLock lock =
|
private final ReentrantReadWriteLock lock =
|
||||||
new ReentrantReadWriteLock(true);
|
new ReentrantReadWriteLock(true);
|
||||||
|
|
||||||
|
private volatile int shutdownHandle = -1;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
DatabaseComponentImpl(Database<T> db, Class<T> txnClass, EventBus eventBus,
|
DatabaseComponentImpl(Database<T> db, Class<T> txnClass, EventBus eventBus,
|
||||||
ShutdownManager shutdown) {
|
ShutdownManager shutdown) {
|
||||||
@@ -101,22 +102,23 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean open(@Nullable MigrationListener listener)
|
public boolean open() throws DbException {
|
||||||
throws DbException {
|
Runnable shutdownHook = () -> {
|
||||||
boolean reopened = db.open(listener);
|
|
||||||
shutdown.addShutdownHook(() -> {
|
|
||||||
try {
|
try {
|
||||||
close();
|
close();
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
boolean reopened = db.open();
|
||||||
|
shutdownHandle = shutdown.addShutdownHook(shutdownHook);
|
||||||
return reopened;
|
return reopened;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws DbException {
|
public void close() throws DbException {
|
||||||
if (closed.getAndSet(true)) return;
|
if (closed.getAndSet(true)) return;
|
||||||
|
shutdown.removeShutdownHook(shutdownHandle);
|
||||||
db.close();
|
db.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,7 +217,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
if (!db.containsGroup(txn, m.getGroupId()))
|
if (!db.containsGroup(txn, m.getGroupId()))
|
||||||
throw new NoSuchGroupException();
|
throw new NoSuchGroupException();
|
||||||
if (!db.containsMessage(txn, m.getId())) {
|
if (!db.containsMessage(txn, m.getId())) {
|
||||||
db.addMessage(txn, m, DELIVERED, shared, null);
|
addMessage(txn, m, DELIVERED, shared, null);
|
||||||
transaction.attach(new MessageAddedEvent(m, null));
|
transaction.attach(new MessageAddedEvent(m, null));
|
||||||
transaction.attach(new MessageStateChangedEvent(m.getId(), true,
|
transaction.attach(new MessageStateChangedEvent(m.getId(), true,
|
||||||
DELIVERED));
|
DELIVERED));
|
||||||
@@ -224,6 +226,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
db.mergeMessageMetadata(txn, m.getId(), meta);
|
db.mergeMessageMetadata(txn, m.getId(), meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addMessage(T txn, Message m, State state, boolean shared,
|
||||||
|
@Nullable ContactId sender) throws DbException {
|
||||||
|
db.addMessage(txn, m, state, shared);
|
||||||
|
for (ContactId c : db.getGroupVisibility(txn, m.getGroupId())) {
|
||||||
|
boolean offered = db.removeOfferedMessage(txn, c, m.getId());
|
||||||
|
boolean seen = offered || (sender != null && c.equals(sender));
|
||||||
|
db.addStatus(txn, c, m.getId(), seen, seen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addTransport(Transaction transaction, TransportId t,
|
public void addTransport(Transaction transaction, TransportId t,
|
||||||
int maxLatency) throws DbException {
|
int maxLatency) throws DbException {
|
||||||
@@ -455,24 +467,24 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<MessageId> getMessagesToValidate(Transaction transaction)
|
public Collection<MessageId> getMessagesToValidate(Transaction transaction,
|
||||||
throws DbException {
|
ClientId c) throws DbException {
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
return db.getMessagesToValidate(txn);
|
return db.getMessagesToValidate(txn, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<MessageId> getPendingMessages(Transaction transaction)
|
public Collection<MessageId> getPendingMessages(Transaction transaction,
|
||||||
throws DbException {
|
ClientId c) throws DbException {
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
return db.getPendingMessages(txn);
|
return db.getPendingMessages(txn, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<MessageId> getMessagesToShare(Transaction transaction)
|
public Collection<MessageId> getMessagesToShare(
|
||||||
throws DbException {
|
Transaction transaction, ClientId c) throws DbException {
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
return db.getMessagesToShare(txn);
|
return db.getMessagesToShare(txn, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -571,13 +583,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return db.getMessageDependents(txn, m);
|
return db.getMessageDependents(txn, m);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getNextSendTime(Transaction transaction, ContactId c)
|
|
||||||
throws DbException {
|
|
||||||
T txn = unbox(transaction);
|
|
||||||
return db.getNextSendTime(txn, c);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Settings getSettings(Transaction transaction, String namespace)
|
public Settings getSettings(Transaction transaction, String namespace)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -672,7 +677,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
db.raiseSeenFlag(txn, c, m.getId());
|
db.raiseSeenFlag(txn, c, m.getId());
|
||||||
db.raiseAckFlag(txn, c, m.getId());
|
db.raiseAckFlag(txn, c, m.getId());
|
||||||
} else {
|
} else {
|
||||||
db.addMessage(txn, m, UNKNOWN, false, c);
|
addMessage(txn, m, UNKNOWN, false, c);
|
||||||
transaction.attach(new MessageAddedEvent(m, c));
|
transaction.attach(new MessageAddedEvent(m, c));
|
||||||
}
|
}
|
||||||
transaction.attach(new MessageToAckEvent(c));
|
transaction.attach(new MessageToAckEvent(c));
|
||||||
@@ -740,8 +745,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
GroupId id = g.getId();
|
GroupId id = g.getId();
|
||||||
if (!db.containsGroup(txn, id))
|
if (!db.containsGroup(txn, id))
|
||||||
throw new NoSuchGroupException();
|
throw new NoSuchGroupException();
|
||||||
Collection<ContactId> affected =
|
Collection<ContactId> affected = db.getGroupVisibility(txn, id);
|
||||||
db.getGroupVisibility(txn, id).keySet();
|
|
||||||
db.removeGroup(txn, id);
|
db.removeGroup(txn, id);
|
||||||
transaction.attach(new GroupRemovedEvent(g));
|
transaction.attach(new GroupRemovedEvent(g));
|
||||||
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
||||||
@@ -811,9 +815,19 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
throw new NoSuchGroupException();
|
throw new NoSuchGroupException();
|
||||||
Visibility old = db.getGroupVisibility(txn, c, g);
|
Visibility old = db.getGroupVisibility(txn, c, g);
|
||||||
if (old == v) return;
|
if (old == v) return;
|
||||||
if (old == INVISIBLE) db.addGroupVisibility(txn, c, g, v == SHARED);
|
if (old == INVISIBLE) {
|
||||||
else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g);
|
db.addGroupVisibility(txn, c, g, v == SHARED);
|
||||||
else db.setGroupVisibility(txn, c, g, v == SHARED);
|
for (MessageId m : db.getMessageIds(txn, g)) {
|
||||||
|
boolean seen = db.removeOfferedMessage(txn, c, m);
|
||||||
|
db.addStatus(txn, c, m, seen, seen);
|
||||||
|
}
|
||||||
|
} else if (v == INVISIBLE) {
|
||||||
|
db.removeGroupVisibility(txn, c, g);
|
||||||
|
for (MessageId m : db.getMessageIds(txn, g))
|
||||||
|
db.removeStatus(txn, c, m);
|
||||||
|
} else {
|
||||||
|
db.setGroupVisibility(txn, c, g, v == SHARED);
|
||||||
|
}
|
||||||
List<ContactId> affected = Collections.singletonList(c);
|
List<ContactId> affected = Collections.singletonList(c);
|
||||||
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,4 +23,10 @@ interface DatabaseConstants {
|
|||||||
*/
|
*/
|
||||||
String SCHEMA_VERSION_KEY = "schemaVersion";
|
String SCHEMA_VERSION_KEY = "schemaVersion";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Settings} key under which the minimum supported database
|
||||||
|
* schema version is stored.
|
||||||
|
*/
|
||||||
|
String MIN_SCHEMA_VERSION_KEY = "minSchemaVersion";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package org.briarproject.bramble.db;
|
|||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.api.db.DatabaseConfig;
|
import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.MigrationListener;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
@@ -14,7 +13,6 @@ import java.sql.DriverManager;
|
|||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,31 +22,28 @@ 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 STRING_TYPE = "VARCHAR";
|
private static final String SECRET_TYPE = "BINARY(32)";
|
||||||
|
|
||||||
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, SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE,
|
super(HASH_TYPE, BINARY_TYPE, COUNTER_TYPE, SECRET_TYPE, clock);
|
||||||
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";
|
+ ";WRITE_DELAY=0;DB_CLOSE_ON_EXIT=false";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean open(@Nullable MigrationListener listener)
|
public boolean open() throws DbException {
|
||||||
throws DbException {
|
|
||||||
boolean reopen = config.databaseExists();
|
boolean reopen = config.databaseExists();
|
||||||
if (!reopen) config.getDatabaseDirectory().mkdirs();
|
if (!reopen) config.getDatabaseDirectory().mkdirs();
|
||||||
super.open("org.h2.Driver", reopen, listener);
|
super.open("org.h2.Driver", reopen);
|
||||||
return reopen;
|
return reopen;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,10 +90,6 @@ 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(getUrl(), props);
|
return DriverManager.getConnection(url, props);
|
||||||
}
|
|
||||||
|
|
||||||
String getUrl() {
|
|
||||||
return url;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,101 +0,0 @@
|
|||||||
package org.briarproject.bramble.db;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseConfig;
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.db.MigrationListener;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.DriverManager;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.sql.Statement;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains all the HSQLDB-specific code for the database.
|
|
||||||
*/
|
|
||||||
@NotNullByDefault
|
|
||||||
class HyperSqlDatabase extends JdbcDatabase {
|
|
||||||
|
|
||||||
private static final String HASH_TYPE = "BINARY(32)";
|
|
||||||
private static final String SECRET_TYPE = "BINARY(32)";
|
|
||||||
private static final String BINARY_TYPE = "BINARY";
|
|
||||||
private static final String COUNTER_TYPE =
|
|
||||||
"INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY(START WITH 1)";
|
|
||||||
private static final String STRING_TYPE = "VARCHAR";
|
|
||||||
|
|
||||||
private final DatabaseConfig config;
|
|
||||||
private final String url;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
HyperSqlDatabase(DatabaseConfig config, Clock clock) {
|
|
||||||
super(HASH_TYPE, SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE,
|
|
||||||
clock);
|
|
||||||
this.config = config;
|
|
||||||
File dir = config.getDatabaseDirectory();
|
|
||||||
String path = new File(dir, "db").getAbsolutePath();
|
|
||||||
url = "jdbc:hsqldb:file:" + path
|
|
||||||
+ ";sql.enforce_size=false;allow_empty_batch=true"
|
|
||||||
+ ";encrypt_lobs=true;crypt_type=AES";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean open(@Nullable MigrationListener listener) throws DbException {
|
|
||||||
boolean reopen = config.databaseExists();
|
|
||||||
if (!reopen) config.getDatabaseDirectory().mkdirs();
|
|
||||||
super.open("org.hsqldb.jdbc.JDBCDriver", reopen, listener);
|
|
||||||
return reopen;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws DbException {
|
|
||||||
try {
|
|
||||||
super.closeAllConnections();
|
|
||||||
Connection c = createConnection();
|
|
||||||
Statement s = c.createStatement();
|
|
||||||
s.executeQuery("SHUTDOWN");
|
|
||||||
s.close();
|
|
||||||
c.close();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
throw new DbException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getFreeSpace() throws DbException {
|
|
||||||
File dir = config.getDatabaseDirectory();
|
|
||||||
long maxSize = config.getMaxSize();
|
|
||||||
long free = dir.getFreeSpace();
|
|
||||||
long used = getDiskSpace(dir);
|
|
||||||
long quota = maxSize - used;
|
|
||||||
return Math.min(free, quota);
|
|
||||||
}
|
|
||||||
|
|
||||||
private long getDiskSpace(File f) {
|
|
||||||
if (f.isDirectory()) {
|
|
||||||
long total = 0;
|
|
||||||
File[] children = f.listFiles();
|
|
||||||
if (children != null)
|
|
||||||
for (File child : children) total += getDiskSpace(child);
|
|
||||||
return total;
|
|
||||||
} else if (f.isFile()) {
|
|
||||||
return f.length();
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Connection createConnection() throws SQLException {
|
|
||||||
SecretKey key = config.getEncryptionKey();
|
|
||||||
if (key == null) throw new IllegalStateException();
|
|
||||||
String hex = StringUtils.toHexString(key.getBytes());
|
|
||||||
return DriverManager.getConnection(url + ";crypt_key=" + hex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,18 +0,0 @@
|
|||||||
package org.briarproject.bramble.db;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
|
|
||||||
interface Migration<T> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the schema version from which this migration starts.
|
|
||||||
*/
|
|
||||||
int getStartVersion();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the schema version at which this migration ends.
|
|
||||||
*/
|
|
||||||
int getEndVersion();
|
|
||||||
|
|
||||||
void migrate(T txn) throws DbException;
|
|
||||||
}
|
|
||||||
@@ -1,65 +1,61 @@
|
|||||||
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 org.briarproject.bramble.util.StringUtils;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
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, Clock clock) {
|
AuthorFactoryImpl(CryptoComponent crypto, BdfWriterFactory bdfWriterFactory,
|
||||||
|
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 createAuthor(FORMAT_VERSION, name, publicKey);
|
return new Author(getId(name, publicKey), 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 createLocalAuthor(FORMAT_VERSION, name, publicKey, privateKey);
|
return new LocalAuthor(getId(name, publicKey), name, publicKey,
|
||||||
|
privateKey, clock.currentTimeMillis());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private AuthorId getId(String name, byte[] publicKey) {
|
||||||
public LocalAuthor createLocalAuthor(int formatVersion, String name,
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
byte[] publicKey, byte[] privateKey) {
|
BdfWriter w = bdfWriterFactory.createWriter(out);
|
||||||
AuthorId id = getId(formatVersion, name, publicKey);
|
try {
|
||||||
return new LocalAuthor(id, formatVersion, name, publicKey, privateKey,
|
w.writeListStart();
|
||||||
clock.currentTimeMillis());
|
w.writeString(name);
|
||||||
}
|
w.writeRaw(publicKey);
|
||||||
|
w.writeListEnd();
|
||||||
private AuthorId getId(int formatVersion, String name, byte[] publicKey) {
|
} catch (IOException e) {
|
||||||
byte[] formatVersionBytes = new byte[INT_32_BYTES];
|
// Shouldn't happen with ByteArrayOutputStream
|
||||||
ByteUtils.writeUint32(formatVersion, formatVersionBytes, 0);
|
throw new RuntimeException(e);
|
||||||
return new AuthorId(crypto.hash(LABEL, formatVersionBytes,
|
}
|
||||||
StringUtils.toUtf8(name), publicKey));
|
return new AuthorId(crypto.hash(AuthorId.LABEL, out.toByteArray()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
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,7 +1,13 @@
|
|||||||
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;
|
||||||
@@ -18,14 +24,19 @@ public class IdentityModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
AuthorFactory provideAuthorFactory(AuthorFactoryImpl authorFactory) {
|
AuthorFactory provideAuthorFactory(CryptoComponent crypto,
|
||||||
return authorFactory;
|
BdfWriterFactory bdfWriterFactory, Clock clock) {
|
||||||
|
return new AuthorFactoryImpl(crypto, bdfWriterFactory, clock);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
IdentityManager provideIdentityManager(
|
IdentityManager provideIdentityModule(DatabaseComponent db) {
|
||||||
IdentityManagerImpl identityManager) {
|
return new IdentityManagerImpl(db);
|
||||||
return identityManager;
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
ObjectReader<Author> provideAuthorReader(AuthorFactory authorFactory) {
|
||||||
|
return new AuthorReader(authorFactory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
package org.briarproject.bramble.keyagreement;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
|
||||||
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
interface ConnectionChooser {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Submits a connection task to the chooser.
|
|
||||||
*/
|
|
||||||
void submit(Callable<KeyAgreementConnection> task);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a connection returned by any of the tasks submitted to the
|
|
||||||
* chooser, waiting up to the given amount of time for a connection if
|
|
||||||
* necessary. Returns null if the time elapses without a connection
|
|
||||||
* becoming available.
|
|
||||||
*
|
|
||||||
* @param timeout the timeout in milliseconds
|
|
||||||
* @throws InterruptedException if the thread is interrupted while waiting
|
|
||||||
* for a connection to become available
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
KeyAgreementConnection poll(long timeout) throws InterruptedException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops the chooser. Any connections already returned to the chooser are
|
|
||||||
* closed unless they have been removed from the chooser by calling
|
|
||||||
* {@link #poll(long)}. Any connections subsequently returned to the
|
|
||||||
* chooser will also be closed.
|
|
||||||
*/
|
|
||||||
void stop();
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
package org.briarproject.bramble.keyagreement;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
@ThreadSafe
|
|
||||||
class ConnectionChooserImpl implements ConnectionChooser {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
|
||||||
Logger.getLogger(ConnectionChooserImpl.class.getName());
|
|
||||||
|
|
||||||
private final Clock clock;
|
|
||||||
private final Executor ioExecutor;
|
|
||||||
private final Object lock = new Object();
|
|
||||||
|
|
||||||
// The following are locking: lock
|
|
||||||
private boolean stopped = false;
|
|
||||||
private final Queue<KeyAgreementConnection> results = new LinkedList<>();
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
ConnectionChooserImpl(Clock clock, @IoExecutor Executor ioExecutor) {
|
|
||||||
this.clock = clock;
|
|
||||||
this.ioExecutor = ioExecutor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void submit(Callable<KeyAgreementConnection> task) {
|
|
||||||
ioExecutor.execute(() -> {
|
|
||||||
try {
|
|
||||||
KeyAgreementConnection c = task.call();
|
|
||||||
if (c != null) addResult(c);
|
|
||||||
} catch (Exception e) {
|
|
||||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public KeyAgreementConnection poll(long timeout)
|
|
||||||
throws InterruptedException {
|
|
||||||
long now = clock.currentTimeMillis();
|
|
||||||
long end = now + timeout;
|
|
||||||
synchronized (lock) {
|
|
||||||
while (!stopped && results.isEmpty() && now < end) {
|
|
||||||
lock.wait(end - now);
|
|
||||||
now = clock.currentTimeMillis();
|
|
||||||
}
|
|
||||||
return results.poll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stop() {
|
|
||||||
List<KeyAgreementConnection> unused;
|
|
||||||
synchronized (lock) {
|
|
||||||
unused = new ArrayList<>(results);
|
|
||||||
results.clear();
|
|
||||||
stopped = true;
|
|
||||||
lock.notifyAll();
|
|
||||||
}
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Closing " + unused.size() + " unused connections");
|
|
||||||
for (KeyAgreementConnection c : unused) tryToClose(c.getConnection());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addResult(KeyAgreementConnection c) {
|
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Got connection for " + c.getTransportId());
|
|
||||||
boolean close = false;
|
|
||||||
synchronized (lock) {
|
|
||||||
if (stopped) {
|
|
||||||
close = true;
|
|
||||||
} else {
|
|
||||||
results.add(c);
|
|
||||||
lock.notifyAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (close) {
|
|
||||||
LOG.info("Already stopped");
|
|
||||||
tryToClose(c.getConnection());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tryToClose(DuplexTransportConnection conn) {
|
|
||||||
try {
|
|
||||||
conn.getReader().dispose(false, true);
|
|
||||||
conn.getWriter().dispose(false);
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package org.briarproject.bramble.keyagreement;
|
package org.briarproject.bramble.keyagreement;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
|
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.data.BdfList;
|
import org.briarproject.bramble.api.data.BdfList;
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||||
@@ -13,19 +13,23 @@ import org.briarproject.bramble.api.plugin.PluginManager;
|
|||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CompletionService;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.ExecutorCompletionService;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.CONNECTION_TIMEOUT;
|
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.CONNECTION_TIMEOUT;
|
||||||
@@ -41,31 +45,33 @@ class KeyAgreementConnector {
|
|||||||
Logger.getLogger(KeyAgreementConnector.class.getName());
|
Logger.getLogger(KeyAgreementConnector.class.getName());
|
||||||
|
|
||||||
private final Callbacks callbacks;
|
private final Callbacks callbacks;
|
||||||
private final KeyAgreementCrypto keyAgreementCrypto;
|
private final Clock clock;
|
||||||
|
private final CryptoComponent crypto;
|
||||||
private final PluginManager pluginManager;
|
private final PluginManager pluginManager;
|
||||||
private final ConnectionChooser connectionChooser;
|
private final CompletionService<KeyAgreementConnection> connect;
|
||||||
|
|
||||||
private final List<KeyAgreementListener> listeners =
|
private final List<KeyAgreementListener> listeners = new ArrayList<>();
|
||||||
new CopyOnWriteArrayList<>();
|
private final List<Future<KeyAgreementConnection>> pending =
|
||||||
private final CountDownLatch aliceLatch = new CountDownLatch(1);
|
new ArrayList<>();
|
||||||
private final AtomicBoolean waitingSent = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
private volatile boolean alice = false, stopped = false;
|
private volatile boolean connecting = false;
|
||||||
|
private volatile boolean alice = false;
|
||||||
|
|
||||||
KeyAgreementConnector(Callbacks callbacks,
|
KeyAgreementConnector(Callbacks callbacks, Clock clock,
|
||||||
KeyAgreementCrypto keyAgreementCrypto, PluginManager pluginManager,
|
CryptoComponent crypto, PluginManager pluginManager,
|
||||||
ConnectionChooser connectionChooser) {
|
Executor ioExecutor) {
|
||||||
this.callbacks = callbacks;
|
this.callbacks = callbacks;
|
||||||
this.keyAgreementCrypto = keyAgreementCrypto;
|
this.clock = clock;
|
||||||
|
this.crypto = crypto;
|
||||||
this.pluginManager = pluginManager;
|
this.pluginManager = pluginManager;
|
||||||
this.connectionChooser = connectionChooser;
|
connect = new ExecutorCompletionService<>(ioExecutor);
|
||||||
}
|
}
|
||||||
|
|
||||||
Payload listen(KeyPair localKeyPair) {
|
public Payload listen(KeyPair localKeyPair) {
|
||||||
LOG.info("Starting BQP listeners");
|
LOG.info("Starting BQP listeners");
|
||||||
// Derive commitment
|
// Derive commitment
|
||||||
byte[] commitment = keyAgreementCrypto.deriveKeyCommitment(
|
byte[] commitment = crypto.deriveKeyCommitment(
|
||||||
localKeyPair.getPublic());
|
localKeyPair.getPublic().getEncoded());
|
||||||
// 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()) {
|
||||||
@@ -74,9 +80,8 @@ class KeyAgreementConnector {
|
|||||||
if (l != null) {
|
if (l != null) {
|
||||||
TransportId id = plugin.getId();
|
TransportId id = plugin.getId();
|
||||||
descriptors.add(new TransportDescriptor(id, l.getDescriptor()));
|
descriptors.add(new TransportDescriptor(id, l.getDescriptor()));
|
||||||
if (LOG.isLoggable(INFO)) LOG.info("Listening via " + id);
|
pending.add(connect.submit(new ReadableTask(l.listen())));
|
||||||
listeners.add(l);
|
listeners.add(l);
|
||||||
connectionChooser.submit(new ReadableTask(l::accept));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new Payload(commitment, descriptors);
|
return new Payload(commitment, descriptors);
|
||||||
@@ -84,92 +89,125 @@ class KeyAgreementConnector {
|
|||||||
|
|
||||||
void stopListening() {
|
void stopListening() {
|
||||||
LOG.info("Stopping BQP listeners");
|
LOG.info("Stopping BQP listeners");
|
||||||
stopped = true;
|
for (KeyAgreementListener l : listeners) {
|
||||||
aliceLatch.countDown();
|
l.close();
|
||||||
for (KeyAgreementListener l : listeners) l.close();
|
}
|
||||||
connectionChooser.stop();
|
listeners.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public KeyAgreementTransport connect(Payload remotePayload, boolean alice) {
|
public KeyAgreementTransport connect(Payload remotePayload,
|
||||||
// Let the ReadableTasks know if we are Alice
|
boolean alice) {
|
||||||
|
// Let the listeners know if we are Alice
|
||||||
|
this.connecting = true;
|
||||||
this.alice = alice;
|
this.alice = alice;
|
||||||
aliceLatch.countDown();
|
long end = clock.currentTimeMillis() + CONNECTION_TIMEOUT;
|
||||||
|
|
||||||
// Start connecting over supported transports
|
// Start connecting over supported transports
|
||||||
if (LOG.isLoggable(INFO)) {
|
LOG.info("Starting outgoing BQP connections");
|
||||||
LOG.info("Starting outgoing BQP connections as "
|
|
||||||
+ (alice ? "Alice" : "Bob"));
|
|
||||||
}
|
|
||||||
for (TransportDescriptor d : remotePayload.getTransportDescriptors()) {
|
for (TransportDescriptor d : remotePayload.getTransportDescriptors()) {
|
||||||
Plugin p = pluginManager.getPlugin(d.getId());
|
Plugin p = pluginManager.getPlugin(d.getId());
|
||||||
if (p instanceof DuplexPlugin) {
|
if (p instanceof DuplexPlugin) {
|
||||||
if (LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Connecting via " + d.getId());
|
|
||||||
DuplexPlugin plugin = (DuplexPlugin) p;
|
DuplexPlugin plugin = (DuplexPlugin) p;
|
||||||
byte[] commitment = remotePayload.getCommitment();
|
pending.add(connect.submit(new ReadableTask(
|
||||||
BdfList descriptor = d.getDescriptor();
|
new ConnectorTask(plugin, remotePayload.getCommitment(),
|
||||||
connectionChooser.submit(new ReadableTask(
|
d.getDescriptor(), end))));
|
||||||
new ConnectorTask(plugin, commitment, descriptor)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get chosen connection
|
// Get chosen connection
|
||||||
|
KeyAgreementConnection chosen = null;
|
||||||
try {
|
try {
|
||||||
KeyAgreementConnection chosen =
|
long now = clock.currentTimeMillis();
|
||||||
connectionChooser.poll(CONNECTION_TIMEOUT);
|
Future<KeyAgreementConnection> f =
|
||||||
if (chosen == null) return null;
|
connect.poll(end - now, MILLISECONDS);
|
||||||
|
if (f == null)
|
||||||
|
return null; // No task completed within the timeout.
|
||||||
|
chosen = f.get();
|
||||||
return new KeyAgreementTransport(chosen);
|
return new KeyAgreementTransport(chosen);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
LOG.info("Interrupted while waiting for connection");
|
LOG.info("Interrupted while waiting for connection");
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
return null;
|
return null;
|
||||||
} catch (IOException e) {
|
} catch (ExecutionException | IOException e) {
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
return null;
|
return null;
|
||||||
} finally {
|
} finally {
|
||||||
stopListening();
|
stopListening();
|
||||||
|
// Close all other connections
|
||||||
|
closePending(chosen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void waitingForAlice() {
|
private void closePending(@Nullable KeyAgreementConnection chosen) {
|
||||||
if (!waitingSent.getAndSet(true)) callbacks.connectionWaiting();
|
for (Future<KeyAgreementConnection> f : pending) {
|
||||||
|
try {
|
||||||
|
if (f.cancel(true)) {
|
||||||
|
LOG.info("Cancelled task");
|
||||||
|
} else if (!f.isCancelled()) {
|
||||||
|
KeyAgreementConnection c = f.get();
|
||||||
|
if (c != null && c != chosen)
|
||||||
|
tryToClose(c.getConnection(), false);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LOG.info("Interrupted while closing sockets");
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
return;
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryToClose(DuplexTransportConnection conn, boolean exception) {
|
||||||
|
try {
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Closing connection, exception: " + exception);
|
||||||
|
conn.getReader().dispose(exception, true);
|
||||||
|
conn.getWriter().dispose(exception);
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ConnectorTask implements Callable<KeyAgreementConnection> {
|
private class ConnectorTask implements Callable<KeyAgreementConnection> {
|
||||||
|
|
||||||
private final byte[] commitment;
|
private final byte[] commitment;
|
||||||
private final BdfList descriptor;
|
private final BdfList descriptor;
|
||||||
|
private final long end;
|
||||||
private final DuplexPlugin plugin;
|
private final DuplexPlugin plugin;
|
||||||
|
|
||||||
private ConnectorTask(DuplexPlugin plugin, byte[] commitment,
|
private ConnectorTask(DuplexPlugin plugin, byte[] commitment,
|
||||||
BdfList descriptor) {
|
BdfList descriptor, long end) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.commitment = commitment;
|
this.commitment = commitment;
|
||||||
this.descriptor = descriptor;
|
this.descriptor = descriptor;
|
||||||
|
this.end = end;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
@Override
|
||||||
public KeyAgreementConnection call() throws Exception {
|
public KeyAgreementConnection call() throws Exception {
|
||||||
// Repeat attempts until we connect, get stopped, or get interrupted
|
// Repeat attempts until we connect, get interrupted, or time out
|
||||||
while (!stopped) {
|
while (true) {
|
||||||
|
long now = clock.currentTimeMillis();
|
||||||
|
if (now > end) throw new IOException();
|
||||||
DuplexTransportConnection conn =
|
DuplexTransportConnection conn =
|
||||||
plugin.createKeyAgreementConnection(commitment,
|
plugin.createKeyAgreementConnection(commitment,
|
||||||
descriptor);
|
descriptor, end - now);
|
||||||
if (conn != null) {
|
if (conn != null) {
|
||||||
if (LOG.isLoggable(INFO))
|
if (LOG.isLoggable(INFO))
|
||||||
LOG.info(plugin.getId() + ": Outgoing connection");
|
LOG.info(plugin.getId().getString() +
|
||||||
|
": Outgoing connection");
|
||||||
return new KeyAgreementConnection(conn, plugin.getId());
|
return new KeyAgreementConnection(conn, plugin.getId());
|
||||||
}
|
}
|
||||||
// Wait 2s before retry (to circumvent transient failures)
|
// Wait 2s before retry (to circumvent transient failures)
|
||||||
Thread.sleep(2000);
|
Thread.sleep(2000);
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ReadableTask implements Callable<KeyAgreementConnection> {
|
private class ReadableTask
|
||||||
|
implements Callable<KeyAgreementConnection> {
|
||||||
|
|
||||||
private final Callable<KeyAgreementConnection> connectionTask;
|
private final Callable<KeyAgreementConnection> connectionTask;
|
||||||
|
|
||||||
@@ -177,23 +215,24 @@ class KeyAgreementConnector {
|
|||||||
this.connectionTask = connectionTask;
|
this.connectionTask = connectionTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
@Override
|
||||||
public KeyAgreementConnection call() throws Exception {
|
public KeyAgreementConnection call() throws Exception {
|
||||||
KeyAgreementConnection c = connectionTask.call();
|
KeyAgreementConnection c = connectionTask.call();
|
||||||
if (c == null) return null;
|
|
||||||
aliceLatch.await();
|
|
||||||
if (alice || stopped) return c;
|
|
||||||
// Bob waits here for Alice to scan his QR code, determine her
|
|
||||||
// role, and send her key
|
|
||||||
InputStream in = c.getConnection().getReader().getInputStream();
|
InputStream in = c.getConnection().getReader().getInputStream();
|
||||||
while (!stopped && in.available() == 0) {
|
boolean waitingSent = false;
|
||||||
if (LOG.isLoggable(INFO))
|
while (!alice && in.available() == 0) {
|
||||||
LOG.info(c.getTransportId() + ": Waiting for data");
|
if (!waitingSent && connecting && !alice) {
|
||||||
waitingForAlice();
|
// Bob waits here until Alice obtains his payload.
|
||||||
Thread.sleep(500);
|
callbacks.connectionWaiting();
|
||||||
|
waitingSent = true;
|
||||||
|
}
|
||||||
|
if (LOG.isLoggable(INFO)) {
|
||||||
|
LOG.info(c.getTransportId().getString() +
|
||||||
|
": Waiting for connection");
|
||||||
|
}
|
||||||
|
Thread.sleep(1000);
|
||||||
}
|
}
|
||||||
if (!stopped && LOG.isLoggable(INFO))
|
if (!alice && LOG.isLoggable(INFO))
|
||||||
LOG.info(c.getTransportId().getString() + ": Data available");
|
LOG.info(c.getTransportId().getString() + ": Data available");
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
package org.briarproject.bramble.keyagreement;
|
package org.briarproject.bramble.keyagreement;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
import org.briarproject.bramble.api.data.BdfReaderFactory;
|
||||||
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
import org.briarproject.bramble.api.data.BdfWriterFactory;
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory;
|
||||||
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
||||||
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
import org.briarproject.bramble.api.keyagreement.PayloadParser;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
@@ -13,9 +22,13 @@ import dagger.Provides;
|
|||||||
public class KeyAgreementModule {
|
public class KeyAgreementModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
KeyAgreementTask provideKeyAgreementTask(
|
@Singleton
|
||||||
KeyAgreementTaskImpl keyAgreementTask) {
|
KeyAgreementTaskFactory provideKeyAgreementTaskFactory(Clock clock,
|
||||||
return keyAgreementTask;
|
CryptoComponent crypto, EventBus eventBus,
|
||||||
|
@IoExecutor Executor ioExecutor, PayloadEncoder payloadEncoder,
|
||||||
|
PluginManager pluginManager) {
|
||||||
|
return new KeyAgreementTaskFactoryImpl(clock, crypto, eventBus,
|
||||||
|
ioExecutor, payloadEncoder, pluginManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -27,10 +40,4 @@ public class KeyAgreementModule {
|
|||||||
PayloadParser providePayloadParser(BdfReaderFactory bdfReaderFactory) {
|
PayloadParser providePayloadParser(BdfReaderFactory bdfReaderFactory) {
|
||||||
return new PayloadParserImpl(bdfReaderFactory);
|
return new PayloadParserImpl(bdfReaderFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
ConnectionChooser provideConnectionChooser(
|
|
||||||
ConnectionChooserImpl connectionChooser) {
|
|
||||||
return connectionChooser;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +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.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;
|
||||||
@@ -14,10 +11,6 @@ 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/>
|
||||||
@@ -64,7 +57,6 @@ 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;
|
||||||
@@ -72,13 +64,11 @@ 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;
|
||||||
@@ -96,11 +86,10 @@ class KeyAgreementProtocol {
|
|||||||
*/
|
*/
|
||||||
SecretKey perform() throws AbortException, IOException {
|
SecretKey perform() throws AbortException, IOException {
|
||||||
try {
|
try {
|
||||||
PublicKey theirPublicKey;
|
byte[] theirPublicKey;
|
||||||
if (alice) {
|
if (alice) {
|
||||||
sendKey();
|
sendKey();
|
||||||
// Alice waits here for Bob to scan her QR code, determine his
|
// Alice waits here until Bob obtains her payload.
|
||||||
// role, receive her key and respond with his key
|
|
||||||
callbacks.connectionWaiting();
|
callbacks.connectionWaiting();
|
||||||
theirPublicKey = receiveKey();
|
theirPublicKey = receiveKey();
|
||||||
} else {
|
} else {
|
||||||
@@ -115,7 +104,7 @@ class KeyAgreementProtocol {
|
|||||||
receiveConfirm(s, theirPublicKey);
|
receiveConfirm(s, theirPublicKey);
|
||||||
sendConfirm(s, theirPublicKey);
|
sendConfirm(s, theirPublicKey);
|
||||||
}
|
}
|
||||||
return crypto.deriveKey(MASTER_SECRET_LABEL, s);
|
return crypto.deriveMasterSecret(s);
|
||||||
} catch (AbortException e) {
|
} catch (AbortException e) {
|
||||||
sendAbort(e.getCause() != null);
|
sendAbort(e.getCause() != null);
|
||||||
throw e;
|
throw e;
|
||||||
@@ -126,41 +115,27 @@ class KeyAgreementProtocol {
|
|||||||
transport.sendKey(ourKeyPair.getPublic().getEncoded());
|
transport.sendKey(ourKeyPair.getPublic().getEncoded());
|
||||||
}
|
}
|
||||||
|
|
||||||
private PublicKey receiveKey() throws AbortException {
|
private byte[] receiveKey() throws AbortException {
|
||||||
byte[] publicKeyBytes = transport.receiveKey();
|
byte[] publicKey = transport.receiveKey();
|
||||||
callbacks.initialRecordReceived();
|
callbacks.initialRecordReceived();
|
||||||
KeyParser keyParser = crypto.getAgreementKeyParser();
|
byte[] expected = crypto.deriveKeyCommitment(publicKey);
|
||||||
try {
|
if (!Arrays.equals(expected, theirPayload.getCommitment()))
|
||||||
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(PublicKey theirPublicKey)
|
private SecretKey deriveSharedSecret(byte[] theirPublicKey)
|
||||||
throws AbortException {
|
throws AbortException {
|
||||||
try {
|
try {
|
||||||
byte[] ourPublicKeyBytes = ourKeyPair.getPublic().getEncoded();
|
return crypto.deriveSharedSecret(theirPublicKey, ourKeyPair, alice);
|
||||||
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, PublicKey theirPublicKey)
|
private void sendConfirm(SecretKey s, byte[] theirPublicKey)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
byte[] confirm = keyAgreementCrypto.deriveConfirmationRecord(s,
|
byte[] confirm = crypto.deriveConfirmationRecord(s,
|
||||||
payloadEncoder.encode(theirPayload),
|
payloadEncoder.encode(theirPayload),
|
||||||
payloadEncoder.encode(ourPayload),
|
payloadEncoder.encode(ourPayload),
|
||||||
theirPublicKey, ourKeyPair,
|
theirPublicKey, ourKeyPair,
|
||||||
@@ -168,10 +143,10 @@ class KeyAgreementProtocol {
|
|||||||
transport.sendConfirm(confirm);
|
transport.sendConfirm(confirm);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void receiveConfirm(SecretKey s, PublicKey theirPublicKey)
|
private void receiveConfirm(SecretKey s, byte[] theirPublicKey)
|
||||||
throws AbortException {
|
throws AbortException {
|
||||||
byte[] confirm = transport.receiveConfirm();
|
byte[] confirm = transport.receiveConfirm();
|
||||||
byte[] expected = keyAgreementCrypto.deriveConfirmationRecord(s,
|
byte[] expected = crypto.deriveConfirmationRecord(s,
|
||||||
payloadEncoder.encode(theirPayload),
|
payloadEncoder.encode(theirPayload),
|
||||||
payloadEncoder.encode(ourPayload),
|
payloadEncoder.encode(ourPayload),
|
||||||
theirPublicKey, ourKeyPair,
|
theirPublicKey, ourKeyPair,
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package org.briarproject.bramble.keyagreement;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
|
||||||
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementTaskFactory;
|
||||||
|
import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
class KeyAgreementTaskFactoryImpl implements KeyAgreementTaskFactory {
|
||||||
|
|
||||||
|
private final Clock clock;
|
||||||
|
private final CryptoComponent crypto;
|
||||||
|
private final EventBus eventBus;
|
||||||
|
private final Executor ioExecutor;
|
||||||
|
private final PayloadEncoder payloadEncoder;
|
||||||
|
private final PluginManager pluginManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
KeyAgreementTaskFactoryImpl(Clock clock, CryptoComponent crypto,
|
||||||
|
EventBus eventBus, @IoExecutor Executor ioExecutor,
|
||||||
|
PayloadEncoder payloadEncoder, PluginManager pluginManager) {
|
||||||
|
this.clock = clock;
|
||||||
|
this.crypto = crypto;
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.payloadEncoder = payloadEncoder;
|
||||||
|
this.pluginManager = pluginManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyAgreementTask createTask() {
|
||||||
|
return new KeyAgreementTaskImpl(clock, crypto, eventBus, payloadEncoder,
|
||||||
|
pluginManager, ioExecutor);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +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.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;
|
||||||
@@ -18,24 +17,24 @@ import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent;
|
|||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||||
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
|
class KeyAgreementTaskImpl extends Thread implements
|
||||||
KeyAgreementProtocol.Callbacks, KeyAgreementConnector.Callbacks {
|
KeyAgreementTask, KeyAgreementConnector.Callbacks,
|
||||||
|
KeyAgreementProtocol.Callbacks {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(KeyAgreementTaskImpl.class.getName());
|
Logger.getLogger(KeyAgreementTaskImpl.class.getName());
|
||||||
|
|
||||||
private final CryptoComponent crypto;
|
private final CryptoComponent crypto;
|
||||||
private final KeyAgreementCrypto keyAgreementCrypto;
|
|
||||||
private final EventBus eventBus;
|
private final EventBus eventBus;
|
||||||
private final PayloadEncoder payloadEncoder;
|
private final PayloadEncoder payloadEncoder;
|
||||||
private final KeyPair localKeyPair;
|
private final KeyPair localKeyPair;
|
||||||
@@ -44,18 +43,15 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
|
|||||||
private Payload localPayload;
|
private Payload localPayload;
|
||||||
private Payload remotePayload;
|
private Payload remotePayload;
|
||||||
|
|
||||||
@Inject
|
KeyAgreementTaskImpl(Clock clock, CryptoComponent crypto,
|
||||||
KeyAgreementTaskImpl(CryptoComponent crypto,
|
EventBus eventBus, PayloadEncoder payloadEncoder,
|
||||||
KeyAgreementCrypto keyAgreementCrypto, EventBus eventBus,
|
PluginManager pluginManager, Executor ioExecutor) {
|
||||||
PayloadEncoder payloadEncoder, PluginManager pluginManager,
|
|
||||||
ConnectionChooser connectionChooser) {
|
|
||||||
this.crypto = crypto;
|
this.crypto = crypto;
|
||||||
this.keyAgreementCrypto = keyAgreementCrypto;
|
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
this.payloadEncoder = payloadEncoder;
|
this.payloadEncoder = payloadEncoder;
|
||||||
localKeyPair = crypto.generateAgreementKeyPair();
|
localKeyPair = crypto.generateAgreementKeyPair();
|
||||||
connector = new KeyAgreementConnector(this, keyAgreementCrypto,
|
connector = new KeyAgreementConnector(this, clock, crypto,
|
||||||
pluginManager, connectionChooser);
|
pluginManager, ioExecutor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -69,8 +65,10 @@ class KeyAgreementTaskImpl extends Thread implements KeyAgreementTask,
|
|||||||
@Override
|
@Override
|
||||||
public synchronized void stopListening() {
|
public synchronized void stopListening() {
|
||||||
if (localPayload != null) {
|
if (localPayload != null) {
|
||||||
if (remotePayload == null) connector.stopListening();
|
if (remotePayload == null)
|
||||||
else interrupt();
|
connector.stopListening();
|
||||||
|
else
|
||||||
|
interrupt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,8 +100,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,
|
||||||
keyAgreementCrypto, payloadEncoder, transport, remotePayload,
|
payloadEncoder, transport, remotePayload, localPayload,
|
||||||
localPayload, localKeyPair, alice);
|
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,7 +1,6 @@
|
|||||||
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;
|
||||||
@@ -40,22 +39,20 @@ 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.isEmpty()) throw new FormatException();
|
if (payload.size() < 2) throw new FormatException();
|
||||||
if (!r.eof()) throw new FormatException();
|
if (!r.eof()) throw new FormatException();
|
||||||
// First element: the public key commitment
|
// First element: the protocol version
|
||||||
byte[] commitment = payload.getRaw(0);
|
long protocolVersion = payload.getLong(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 = 1; i < payload.size(); i++) {
|
for (int i = 2; 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) {
|
||||||
|
|||||||
@@ -2,11 +2,8 @@ package org.briarproject.bramble.lifecycle;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
import org.briarproject.bramble.api.db.DataTooNewException;
|
|
||||||
import org.briarproject.bramble.api.db.DataTooOldException;
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.MigrationListener;
|
|
||||||
import org.briarproject.bramble.api.db.Transaction;
|
import org.briarproject.bramble.api.db.Transaction;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||||
@@ -15,7 +12,7 @@ import org.briarproject.bramble.api.identity.LocalAuthor;
|
|||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
import org.briarproject.bramble.api.lifecycle.Service;
|
import org.briarproject.bramble.api.lifecycle.Service;
|
||||||
import org.briarproject.bramble.api.lifecycle.ServiceException;
|
import org.briarproject.bramble.api.lifecycle.ServiceException;
|
||||||
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.Client;
|
import org.briarproject.bramble.api.sync.Client;
|
||||||
|
|
||||||
@@ -32,21 +29,14 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE;
|
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
|
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING;
|
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES;
|
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_NEW_ERROR;
|
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DATA_TOO_OLD_ERROR;
|
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DB_ERROR;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.DB_ERROR;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SERVICE_ERROR;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SERVICE_ERROR;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
|
||||||
|
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
class LifecycleManagerImpl implements LifecycleManager {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(LifecycleManagerImpl.class.getName());
|
Logger.getLogger(LifecycleManagerImpl.class.getName());
|
||||||
@@ -64,8 +54,6 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
private final CountDownLatch startupLatch = new CountDownLatch(1);
|
private final CountDownLatch startupLatch = new CountDownLatch(1);
|
||||||
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
|
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
|
||||||
|
|
||||||
private volatile LifecycleState state = STARTING;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
|
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
|
||||||
CryptoComponent crypto, AuthorFactory authorFactory,
|
CryptoComponent crypto, AuthorFactory authorFactory,
|
||||||
@@ -131,7 +119,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
LOG.info("Starting services");
|
LOG.info("Starting services");
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
boolean reopened = db.open(this);
|
boolean reopened = db.open();
|
||||||
long duration = System.currentTimeMillis() - start;
|
long duration = System.currentTimeMillis() - start;
|
||||||
if (LOG.isLoggable(INFO)) {
|
if (LOG.isLoggable(INFO)) {
|
||||||
if (reopened)
|
if (reopened)
|
||||||
@@ -143,10 +131,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
registerLocalAuthor(createLocalAuthor(nickname));
|
registerLocalAuthor(createLocalAuthor(nickname));
|
||||||
}
|
}
|
||||||
|
|
||||||
state = STARTING_SERVICES;
|
|
||||||
dbLatch.countDown();
|
dbLatch.countDown();
|
||||||
eventBus.broadcast(new LifecycleEvent(STARTING_SERVICES));
|
|
||||||
|
|
||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
for (Client c : clients) {
|
for (Client c : clients) {
|
||||||
@@ -172,17 +157,8 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
+ " took " + duration + " ms");
|
+ " took " + duration + " ms");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state = RUNNING;
|
|
||||||
startupLatch.countDown();
|
startupLatch.countDown();
|
||||||
eventBus.broadcast(new LifecycleEvent(RUNNING));
|
|
||||||
return SUCCESS;
|
return SUCCESS;
|
||||||
} catch (DataTooOldException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
return DATA_TOO_OLD_ERROR;
|
|
||||||
} catch (DataTooNewException e) {
|
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
return DATA_TOO_NEW_ERROR;
|
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
return DB_ERROR;
|
return DB_ERROR;
|
||||||
@@ -194,12 +170,6 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onMigrationRun() {
|
|
||||||
state = MIGRATING_DATABASE;
|
|
||||||
eventBus.broadcast(new LifecycleEvent(MIGRATING_DATABASE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stopServices() {
|
public void stopServices() {
|
||||||
try {
|
try {
|
||||||
@@ -210,8 +180,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
LOG.info("Stopping services");
|
LOG.info("Stopping services");
|
||||||
state = STOPPING;
|
eventBus.broadcast(new ShutdownEvent());
|
||||||
eventBus.broadcast(new LifecycleEvent(STOPPING));
|
|
||||||
for (Service s : services) {
|
for (Service s : services) {
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
s.stopService();
|
s.stopService();
|
||||||
@@ -256,8 +225,4 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
shutdownLatch.await();
|
shutdownLatch.await();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public LifecycleState getLifecycleState() {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
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;
|
||||||
@@ -37,14 +36,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, Multiset<ContactId>> connections;
|
private final Map<TransportId, Map<ContactId, Integer>> connections;
|
||||||
private final Multiset<ContactId> contactCounts;
|
private final Map<ContactId, Integer> contactCounts;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ConnectionRegistryImpl(EventBus eventBus) {
|
ConnectionRegistryImpl(EventBus eventBus) {
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
connections = new HashMap<>();
|
connections = new HashMap<>();
|
||||||
contactCounts = new Multiset<>();
|
contactCounts = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -57,13 +56,21 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
|
|||||||
boolean firstConnection = false;
|
boolean firstConnection = false;
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
Multiset<ContactId> m = connections.get(t);
|
Map<ContactId, Integer> m = connections.get(t);
|
||||||
if (m == null) {
|
if (m == null) {
|
||||||
m = new Multiset<>();
|
m = new HashMap<>();
|
||||||
connections.put(t, m);
|
connections.put(t, m);
|
||||||
}
|
}
|
||||||
m.add(c);
|
Integer count = m.get(c);
|
||||||
if (contactCounts.add(c) == 1) firstConnection = true;
|
if (count == null) m.put(c, 1);
|
||||||
|
else m.put(c, count + 1);
|
||||||
|
count = contactCounts.get(c);
|
||||||
|
if (count == null) {
|
||||||
|
firstConnection = true;
|
||||||
|
contactCounts.put(c, 1);
|
||||||
|
} else {
|
||||||
|
contactCounts.put(c, count + 1);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
@@ -84,10 +91,23 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
|
|||||||
boolean lastConnection = false;
|
boolean lastConnection = false;
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
Multiset<ContactId> m = connections.get(t);
|
Map<ContactId, Integer> m = connections.get(t);
|
||||||
if (m == null) throw new IllegalArgumentException();
|
if (m == null) throw new IllegalArgumentException();
|
||||||
m.remove(c);
|
Integer count = m.remove(c);
|
||||||
if (contactCounts.remove(c) == 0) lastConnection = true;
|
if (count == null) throw new IllegalArgumentException();
|
||||||
|
if (count == 1) {
|
||||||
|
if (m.isEmpty()) connections.remove(t);
|
||||||
|
} else {
|
||||||
|
m.put(c, count - 1);
|
||||||
|
}
|
||||||
|
count = contactCounts.get(c);
|
||||||
|
if (count == null) throw new IllegalArgumentException();
|
||||||
|
if (count == 1) {
|
||||||
|
lastConnection = true;
|
||||||
|
contactCounts.remove(c);
|
||||||
|
} else {
|
||||||
|
contactCounts.put(c, count - 1);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
@@ -102,7 +122,7 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
|
|||||||
public Collection<ContactId> getConnectedContacts(TransportId t) {
|
public Collection<ContactId> getConnectedContacts(TransportId t) {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
Multiset<ContactId> m = connections.get(t);
|
Map<ContactId, Integer> 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))
|
||||||
@@ -117,8 +137,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 {
|
||||||
Multiset<ContactId> m = connections.get(t);
|
Map<ContactId, Integer> m = connections.get(t);
|
||||||
return m != null && m.contains(c);
|
return m != null && m.containsKey(c);
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
@@ -128,7 +148,7 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
|
|||||||
public boolean isConnected(ContactId c) {
|
public boolean isConnected(ContactId c) {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
return contactCounts.contains(c);
|
return contactCounts.containsKey(c);
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
|
|||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
|
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
|
||||||
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
|
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
|
||||||
import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent;
|
|
||||||
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent;
|
import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent;
|
||||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
@@ -26,7 +25,6 @@ import java.security.SecureRandom;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.Future;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
@@ -52,7 +50,7 @@ class Poller implements EventListener {
|
|||||||
private final SecureRandom random;
|
private final SecureRandom random;
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
private final Lock lock;
|
private final Lock lock;
|
||||||
private final Map<TransportId, ScheduledPollTask> tasks; // Locking: lock
|
private final Map<TransportId, PollTask> tasks; // Locking: lock
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
Poller(@IoExecutor Executor ioExecutor,
|
Poller(@IoExecutor Executor ioExecutor,
|
||||||
@@ -95,10 +93,6 @@ class Poller implements EventListener {
|
|||||||
TransportEnabledEvent t = (TransportEnabledEvent) e;
|
TransportEnabledEvent t = (TransportEnabledEvent) e;
|
||||||
// Poll the newly enabled transport
|
// Poll the newly enabled transport
|
||||||
pollNow(t.getTransportId());
|
pollNow(t.getTransportId());
|
||||||
} else if (e instanceof TransportDisabledEvent) {
|
|
||||||
TransportDisabledEvent t = (TransportDisabledEvent) e;
|
|
||||||
// Cancel polling for the disabled transport
|
|
||||||
cancel(t.getTransportId());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,31 +151,18 @@ class Poller implements EventListener {
|
|||||||
TransportId t = p.getId();
|
TransportId t = p.getId();
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
ScheduledPollTask scheduled = tasks.get(t);
|
PollTask scheduled = tasks.get(t);
|
||||||
if (scheduled == null || due < scheduled.task.due) {
|
if (scheduled == null || due < scheduled.due) {
|
||||||
// If a later task exists, cancel it. If it's already started
|
|
||||||
// it will abort safely when it finds it's been replaced
|
|
||||||
if (scheduled != null) scheduled.future.cancel(false);
|
|
||||||
PollTask task = new PollTask(p, due, randomiseNext);
|
PollTask task = new PollTask(p, due, randomiseNext);
|
||||||
Future future = scheduler.schedule(
|
tasks.put(t, task);
|
||||||
|
scheduler.schedule(
|
||||||
() -> ioExecutor.execute(task), delay, MILLISECONDS);
|
() -> ioExecutor.execute(task), delay, MILLISECONDS);
|
||||||
tasks.put(t, new ScheduledPollTask(task, future));
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cancel(TransportId t) {
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
ScheduledPollTask scheduled = tasks.remove(t);
|
|
||||||
if (scheduled != null) scheduled.future.cancel(false);
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@IoExecutor
|
@IoExecutor
|
||||||
private void poll(Plugin p) {
|
private void poll(Plugin p) {
|
||||||
TransportId t = p.getId();
|
TransportId t = p.getId();
|
||||||
@@ -189,17 +170,6 @@ class Poller implements EventListener {
|
|||||||
p.poll(connectionRegistry.getConnectedContacts(t));
|
p.poll(connectionRegistry.getConnectedContacts(t));
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ScheduledPollTask {
|
|
||||||
|
|
||||||
private final PollTask task;
|
|
||||||
private final Future future;
|
|
||||||
|
|
||||||
private ScheduledPollTask(PollTask task, Future future) {
|
|
||||||
this.task = task;
|
|
||||||
this.future = future;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class PollTask implements Runnable {
|
private class PollTask implements Runnable {
|
||||||
|
|
||||||
private final Plugin plugin;
|
private final Plugin plugin;
|
||||||
@@ -218,9 +188,7 @@ class Poller implements EventListener {
|
|||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
TransportId t = plugin.getId();
|
TransportId t = plugin.getId();
|
||||||
ScheduledPollTask scheduled = tasks.get(t);
|
if (tasks.get(t) != this) return; // Replaced by another task
|
||||||
if (scheduled != null && scheduled.task != this)
|
|
||||||
return; // Replaced by another task
|
|
||||||
tasks.remove(t);
|
tasks.remove(t);
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
|
|||||||
@@ -23,8 +23,9 @@ import java.net.SocketAddress;
|
|||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
@@ -43,9 +44,6 @@ class LanTcpPlugin extends TcpPlugin {
|
|||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(LanTcpPlugin.class.getName());
|
Logger.getLogger(LanTcpPlugin.class.getName());
|
||||||
|
|
||||||
private static final LanAddressComparator ADDRESS_COMPARATOR =
|
|
||||||
new LanAddressComparator();
|
|
||||||
|
|
||||||
private static final int MAX_ADDRESSES = 4;
|
private static final int MAX_ADDRESSES = 4;
|
||||||
private static final String SEPARATOR = ",";
|
private static final String SEPARATOR = ",";
|
||||||
|
|
||||||
@@ -65,18 +63,19 @@ class LanTcpPlugin extends TcpPlugin {
|
|||||||
TransportProperties p = callback.getLocalProperties();
|
TransportProperties p = callback.getLocalProperties();
|
||||||
String oldIpPorts = p.get(PROP_IP_PORTS);
|
String oldIpPorts = p.get(PROP_IP_PORTS);
|
||||||
List<InetSocketAddress> olds = parseSocketAddresses(oldIpPorts);
|
List<InetSocketAddress> olds = parseSocketAddresses(oldIpPorts);
|
||||||
List<InetSocketAddress> locals = new ArrayList<>();
|
List<InetSocketAddress> locals = new LinkedList<>();
|
||||||
for (InetAddress local : getLocalIpAddresses()) {
|
for (InetAddress local : getLocalIpAddresses()) {
|
||||||
if (isAcceptableAddress(local)) {
|
if (isAcceptableAddress(local)) {
|
||||||
// If this is the old address, try to use the same port
|
// If this is the old address, try to use the same port
|
||||||
for (InetSocketAddress old : olds) {
|
for (InetSocketAddress old : olds) {
|
||||||
if (old.getAddress().equals(local))
|
if (old.getAddress().equals(local)) {
|
||||||
locals.add(new InetSocketAddress(local, old.getPort()));
|
int port = old.getPort();
|
||||||
|
locals.add(0, new InetSocketAddress(local, port));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
locals.add(new InetSocketAddress(local, 0));
|
locals.add(new InetSocketAddress(local, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Collections.sort(locals, ADDRESS_COMPARATOR);
|
|
||||||
return locals;
|
return locals;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,39 +153,17 @@ class LanTcpPlugin extends TcpPlugin {
|
|||||||
// Package access for testing
|
// Package access for testing
|
||||||
boolean addressesAreOnSameLan(byte[] localIp, byte[] remoteIp) {
|
boolean addressesAreOnSameLan(byte[] localIp, byte[] remoteIp) {
|
||||||
// 10.0.0.0/8
|
// 10.0.0.0/8
|
||||||
if (isPrefix10(localIp)) return isPrefix10(remoteIp);
|
if (localIp[0] == 10) return remoteIp[0] == 10;
|
||||||
// 172.16.0.0/12
|
// 172.16.0.0/12
|
||||||
if (isPrefix172(localIp)) return isPrefix172(remoteIp);
|
if (localIp[0] == (byte) 172 && (localIp[1] & 0xF0) == 16)
|
||||||
|
return remoteIp[0] == (byte) 172 && (remoteIp[1] & 0xF0) == 16;
|
||||||
// 192.168.0.0/16
|
// 192.168.0.0/16
|
||||||
if (isPrefix192(localIp)) return isPrefix192(remoteIp);
|
if (localIp[0] == (byte) 192 && localIp[1] == (byte) 168)
|
||||||
|
return remoteIp[0] == (byte) 192 && remoteIp[1] == (byte) 168;
|
||||||
// Unrecognised prefix - may be compatible
|
// Unrecognised prefix - may be compatible
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isPrefix10(byte[] ipv4) {
|
|
||||||
return ipv4[0] == 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isPrefix172(byte[] ipv4) {
|
|
||||||
return ipv4[0] == (byte) 172 && (ipv4[1] & 0xF0) == 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isPrefix192(byte[] ipv4) {
|
|
||||||
return ipv4[0] == (byte) 192 && ipv4[1] == (byte) 168;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the prefix length for an RFC 1918 address, or 0 for any other
|
|
||||||
// address
|
|
||||||
private static int getRfc1918PrefixLength(InetAddress addr) {
|
|
||||||
if (!(addr instanceof Inet4Address)) return 0;
|
|
||||||
if (!addr.isSiteLocalAddress()) return 0;
|
|
||||||
byte[] ipv4 = addr.getAddress();
|
|
||||||
if (isPrefix10(ipv4)) return 8;
|
|
||||||
if (isPrefix172(ipv4)) return 12;
|
|
||||||
if (isPrefix192(ipv4)) return 16;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsKeyAgreement() {
|
public boolean supportsKeyAgreement() {
|
||||||
return true;
|
return true;
|
||||||
@@ -223,7 +200,7 @@ class LanTcpPlugin extends TcpPlugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DuplexTransportConnection createKeyAgreementConnection(
|
public DuplexTransportConnection createKeyAgreementConnection(
|
||||||
byte[] commitment, BdfList descriptor) {
|
byte[] commitment, BdfList descriptor, long timeout) {
|
||||||
if (!isRunning()) return null;
|
if (!isRunning()) return null;
|
||||||
InetSocketAddress remote;
|
InetSocketAddress remote;
|
||||||
try {
|
try {
|
||||||
@@ -282,11 +259,14 @@ class LanTcpPlugin extends TcpPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyAgreementConnection accept() throws IOException {
|
public Callable<KeyAgreementConnection> listen() {
|
||||||
Socket s = ss.accept();
|
return () -> {
|
||||||
if (LOG.isLoggable(INFO)) LOG.info(ID + ": Incoming connection");
|
Socket s = ss.accept();
|
||||||
return new KeyAgreementConnection(new TcpTransportConnection(
|
if (LOG.isLoggable(INFO))
|
||||||
LanTcpPlugin.this, s), ID);
|
LOG.info(ID.getString() + ": Incoming connection");
|
||||||
|
return new KeyAgreementConnection(
|
||||||
|
new TcpTransportConnection(LanTcpPlugin.this, s), ID);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -298,19 +278,4 @@ class LanTcpPlugin extends TcpPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class LanAddressComparator implements Comparator<InetSocketAddress> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compare(InetSocketAddress a, InetSocketAddress b) {
|
|
||||||
// Prefer addresses with non-zero ports
|
|
||||||
int aPort = a.getPort(), bPort = b.getPort();
|
|
||||||
if (aPort > 0 && bPort == 0) return -1;
|
|
||||||
if (aPort == 0 && bPort > 0) return 1;
|
|
||||||
// Prefer addresses with longer RFC 1918 prefixes
|
|
||||||
int aPrefix = getRfc1918PrefixLength(a.getAddress());
|
|
||||||
int bPrefix = getRfc1918PrefixLength(b.getAddress());
|
|
||||||
return bPrefix - aPrefix;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ abstract class TcpPlugin implements DuplexPlugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DuplexTransportConnection createKeyAgreementConnection(
|
public DuplexTransportConnection createKeyAgreementConnection(
|
||||||
byte[] commitment, BdfList descriptor) {
|
byte[] commitment, BdfList descriptor, long timeout) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,13 +58,11 @@ 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
|
||||||
public void createLocalState(Transaction txn) throws DbException {
|
public void createLocalState(Transaction txn) throws DbException {
|
||||||
if (db.containsGroup(txn, localGroup.getId())) return;
|
|
||||||
db.addGroup(txn, localGroup);
|
db.addGroup(txn, localGroup);
|
||||||
// Ensure we've set things up for any pre-existing contacts
|
// Ensure we've set things up for any pre-existing contacts
|
||||||
for (Contact c : db.getContacts(txn)) addingContact(txn, c);
|
for (Contact c : db.getContacts(txn)) addingContact(txn, c);
|
||||||
@@ -131,7 +129,8 @@ 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;
|
||||||
Transaction txn = db.startTransaction(true);
|
// TODO: Transaction can be read-only when code is simplified
|
||||||
|
Transaction txn = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
local = getLocalProperties(txn);
|
local = getLocalProperties(txn);
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
@@ -166,7 +165,8 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
throws DbException {
|
throws DbException {
|
||||||
try {
|
try {
|
||||||
TransportProperties p = null;
|
TransportProperties p = null;
|
||||||
Transaction txn = db.startTransaction(true);
|
// TODO: Transaction can be read-only when code is simplified
|
||||||
|
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,
|
||||||
@@ -192,7 +192,8 @@ 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<>();
|
||||||
Transaction txn = db.startTransaction(true);
|
// TODO: Transaction can be read-only when code is simplified
|
||||||
|
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));
|
||||||
@@ -226,7 +227,8 @@ 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;
|
||||||
Transaction txn = db.startTransaction(true);
|
// TODO: Transaction can be read-only when code is simplified
|
||||||
|
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);
|
||||||
@@ -289,8 +291,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Group getContactGroup(Contact c) {
|
private Group getContactGroup(Contact c) {
|
||||||
return contactGroupFactory.createContactGroup(CLIENT_ID,
|
return contactGroupFactory.createContactGroup(CLIENT_ID, c);
|
||||||
CLIENT_VERSION, c);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void storeMessage(Transaction txn, GroupId g, TransportId t,
|
private void storeMessage(Transaction txn, GroupId g, TransportId t,
|
||||||
@@ -317,6 +318,7 @@ 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());
|
||||||
@@ -324,7 +326,17 @@ 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");
|
||||||
latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
|
LatestUpdate latest = latestUpdates.get(t);
|
||||||
|
if (latest == null) {
|
||||||
|
latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
|
||||||
|
} else if (version > latest.version) {
|
||||||
|
// This update is newer - delete the previous one
|
||||||
|
db.removeMessage(txn, latest.messageId);
|
||||||
|
latestUpdates.put(t, new LatestUpdate(e.getKey(), version));
|
||||||
|
} else {
|
||||||
|
// We've already found a newer update - delete this one
|
||||||
|
db.removeMessage(txn, e.getKey());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return latestUpdates;
|
return latestUpdates;
|
||||||
}
|
}
|
||||||
@@ -332,16 +344,38 @@ 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) {
|
||||||
return new LatestUpdate(e.getKey(), meta.getLong("version"));
|
long version = meta.getLong("version");
|
||||||
|
if (latest == null) {
|
||||||
|
latest = new LatestUpdate(e.getKey(), version);
|
||||||
|
} else if (version > latest.version) {
|
||||||
|
// This update is newer - delete the previous one
|
||||||
|
if (local) {
|
||||||
|
db.removeMessage(txn, latest.messageId);
|
||||||
|
} else {
|
||||||
|
db.deleteMessage(txn, latest.messageId);
|
||||||
|
db.deleteMessageMetadata(txn, latest.messageId);
|
||||||
|
}
|
||||||
|
latest = new LatestUpdate(e.getKey(), version);
|
||||||
|
} else {
|
||||||
|
// We've already found a newer update - delete this one
|
||||||
|
if (local) {
|
||||||
|
db.removeMessage(txn, e.getKey());
|
||||||
|
} else {
|
||||||
|
db.deleteMessage(txn, e.getKey());
|
||||||
|
db.deleteMessageMetadata(txn, e.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return latest;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TransportProperties parseProperties(BdfList message)
|
private TransportProperties parseProperties(BdfList message)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import org.briarproject.bramble.api.event.Event;
|
|||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.event.EventListener;
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.Ack;
|
import org.briarproject.bramble.api.sync.Ack;
|
||||||
import org.briarproject.bramble.api.sync.Offer;
|
import org.briarproject.bramble.api.sync.Offer;
|
||||||
@@ -29,8 +29,6 @@ import java.util.Collection;
|
|||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
@@ -38,7 +36,6 @@ import javax.annotation.concurrent.ThreadSafe;
|
|||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
|
||||||
|
|
||||||
@@ -52,14 +49,12 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD
|
|||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class DuplexOutgoingSession implements SyncSession, EventListener {
|
class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||||
|
|
||||||
|
// Check for retransmittable records once every 60 seconds
|
||||||
|
private static final int RETX_QUERY_INTERVAL = 60 * 1000;
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(DuplexOutgoingSession.class.getName());
|
Logger.getLogger(DuplexOutgoingSession.class.getName());
|
||||||
|
|
||||||
private static final ThrowingRunnable<IOException> CLOSE = () -> {
|
private static final ThrowingRunnable<IOException> CLOSE = () -> {};
|
||||||
};
|
|
||||||
private static final ThrowingRunnable<IOException>
|
|
||||||
NEXT_SEND_TIME_DECREASED = () -> {
|
|
||||||
};
|
|
||||||
|
|
||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
private final Executor dbExecutor;
|
private final Executor dbExecutor;
|
||||||
@@ -70,13 +65,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
private final RecordWriter recordWriter;
|
private final RecordWriter recordWriter;
|
||||||
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
|
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
|
||||||
|
|
||||||
private final AtomicBoolean generateAckQueued = new AtomicBoolean(false);
|
|
||||||
private final AtomicBoolean generateBatchQueued = new AtomicBoolean(false);
|
|
||||||
private final AtomicBoolean generateOfferQueued = new AtomicBoolean(false);
|
|
||||||
private final AtomicBoolean generateRequestQueued =
|
|
||||||
new AtomicBoolean(false);
|
|
||||||
private final AtomicLong nextSendTime = new AtomicLong(Long.MAX_VALUE);
|
|
||||||
|
|
||||||
private volatile boolean interrupted = false;
|
private volatile boolean interrupted = false;
|
||||||
|
|
||||||
DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
|
DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
|
||||||
@@ -99,21 +87,21 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
eventBus.addListener(this);
|
eventBus.addListener(this);
|
||||||
try {
|
try {
|
||||||
// Start a query for each type of record
|
// Start a query for each type of record
|
||||||
generateAck();
|
dbExecutor.execute(new GenerateAck());
|
||||||
generateBatch();
|
dbExecutor.execute(new GenerateBatch());
|
||||||
generateOffer();
|
dbExecutor.execute(new GenerateOffer());
|
||||||
generateRequest();
|
dbExecutor.execute(new GenerateRequest());
|
||||||
long now = clock.currentTimeMillis();
|
long now = clock.currentTimeMillis();
|
||||||
long nextKeepalive = now + maxIdleTime;
|
long nextKeepalive = now + maxIdleTime;
|
||||||
|
long nextRetxQuery = now + RETX_QUERY_INTERVAL;
|
||||||
boolean dataToFlush = true;
|
boolean dataToFlush = true;
|
||||||
// Write records until interrupted
|
// Write records until interrupted
|
||||||
try {
|
try {
|
||||||
while (!interrupted) {
|
while (!interrupted) {
|
||||||
// Work out how long we should wait for a record
|
// Work out how long we should wait for a record
|
||||||
now = clock.currentTimeMillis();
|
now = clock.currentTimeMillis();
|
||||||
long keepaliveWait = Math.max(0, nextKeepalive - now);
|
long wait = Math.min(nextKeepalive, nextRetxQuery) - now;
|
||||||
long sendWait = Math.max(0, nextSendTime.get() - now);
|
if (wait < 0) wait = 0;
|
||||||
long wait = Math.min(keepaliveWait, sendWait);
|
|
||||||
// Flush any unflushed data if we're going to wait
|
// Flush any unflushed data if we're going to wait
|
||||||
if (wait > 0 && dataToFlush && writerTasks.isEmpty()) {
|
if (wait > 0 && dataToFlush && writerTasks.isEmpty()) {
|
||||||
recordWriter.flush();
|
recordWriter.flush();
|
||||||
@@ -125,25 +113,20 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
MILLISECONDS);
|
MILLISECONDS);
|
||||||
if (task == null) {
|
if (task == null) {
|
||||||
now = clock.currentTimeMillis();
|
now = clock.currentTimeMillis();
|
||||||
if (now >= nextSendTime.get()) {
|
if (now >= nextRetxQuery) {
|
||||||
// Check for retransmittable messages
|
// Check for retransmittable records
|
||||||
LOG.info("Checking for retransmittable messages");
|
dbExecutor.execute(new GenerateBatch());
|
||||||
setNextSendTime(Long.MAX_VALUE);
|
dbExecutor.execute(new GenerateOffer());
|
||||||
generateBatch();
|
nextRetxQuery = now + RETX_QUERY_INTERVAL;
|
||||||
generateOffer();
|
|
||||||
}
|
}
|
||||||
if (now >= nextKeepalive) {
|
if (now >= nextKeepalive) {
|
||||||
// Flush the stream to keep it alive
|
// Flush the stream to keep it alive
|
||||||
LOG.info("Sending keepalive");
|
|
||||||
recordWriter.flush();
|
recordWriter.flush();
|
||||||
dataToFlush = false;
|
dataToFlush = false;
|
||||||
nextKeepalive = now + maxIdleTime;
|
nextKeepalive = now + maxIdleTime;
|
||||||
}
|
}
|
||||||
} else if (task == CLOSE) {
|
} else if (task == CLOSE) {
|
||||||
LOG.info("Closed");
|
|
||||||
break;
|
break;
|
||||||
} else if (task == NEXT_SEND_TIME_DECREASED) {
|
|
||||||
LOG.info("Next send time decreased");
|
|
||||||
} else {
|
} else {
|
||||||
task.run();
|
task.run();
|
||||||
dataToFlush = true;
|
dataToFlush = true;
|
||||||
@@ -159,31 +142,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generateAck() {
|
|
||||||
if (generateAckQueued.compareAndSet(false, true))
|
|
||||||
dbExecutor.execute(new GenerateAck());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void generateBatch() {
|
|
||||||
if (generateBatchQueued.compareAndSet(false, true))
|
|
||||||
dbExecutor.execute(new GenerateBatch());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void generateOffer() {
|
|
||||||
if (generateOfferQueued.compareAndSet(false, true))
|
|
||||||
dbExecutor.execute(new GenerateOffer());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void generateRequest() {
|
|
||||||
if (generateRequestQueued.compareAndSet(false, true))
|
|
||||||
dbExecutor.execute(new GenerateRequest());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setNextSendTime(long time) {
|
|
||||||
long old = nextSendTime.getAndSet(time);
|
|
||||||
if (time < old) writerTasks.add(NEXT_SEND_TIME_DECREASED);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void interrupt() {
|
public void interrupt() {
|
||||||
interrupted = true;
|
interrupted = true;
|
||||||
@@ -196,23 +154,22 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
||||||
if (c.getContactId().equals(contactId)) interrupt();
|
if (c.getContactId().equals(contactId)) interrupt();
|
||||||
} else if (e instanceof MessageSharedEvent) {
|
} else if (e instanceof MessageSharedEvent) {
|
||||||
generateOffer();
|
dbExecutor.execute(new GenerateOffer());
|
||||||
} else if (e instanceof GroupVisibilityUpdatedEvent) {
|
} else if (e instanceof GroupVisibilityUpdatedEvent) {
|
||||||
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
|
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
|
||||||
if (g.getAffectedContacts().contains(contactId))
|
if (g.getAffectedContacts().contains(contactId))
|
||||||
generateOffer();
|
dbExecutor.execute(new GenerateOffer());
|
||||||
} else if (e instanceof MessageRequestedEvent) {
|
} else if (e instanceof MessageRequestedEvent) {
|
||||||
if (((MessageRequestedEvent) e).getContactId().equals(contactId))
|
if (((MessageRequestedEvent) e).getContactId().equals(contactId))
|
||||||
generateBatch();
|
dbExecutor.execute(new GenerateBatch());
|
||||||
} else if (e instanceof MessageToAckEvent) {
|
} else if (e instanceof MessageToAckEvent) {
|
||||||
if (((MessageToAckEvent) e).getContactId().equals(contactId))
|
if (((MessageToAckEvent) e).getContactId().equals(contactId))
|
||||||
generateAck();
|
dbExecutor.execute(new GenerateAck());
|
||||||
} else if (e instanceof MessageToRequestEvent) {
|
} else if (e instanceof MessageToRequestEvent) {
|
||||||
if (((MessageToRequestEvent) e).getContactId().equals(contactId))
|
if (((MessageToRequestEvent) e).getContactId().equals(contactId))
|
||||||
generateRequest();
|
dbExecutor.execute(new GenerateRequest());
|
||||||
} else if (e instanceof LifecycleEvent) {
|
} else if (e instanceof ShutdownEvent) {
|
||||||
LifecycleEvent l = (LifecycleEvent) e;
|
interrupt();
|
||||||
if (l.getLifecycleState() == STOPPING) interrupt();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +179,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (interrupted) return;
|
if (interrupted) return;
|
||||||
if (!generateAckQueued.getAndSet(false)) throw new AssertionError();
|
|
||||||
try {
|
try {
|
||||||
Ack a;
|
Ack a;
|
||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
@@ -256,7 +212,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
if (interrupted) return;
|
if (interrupted) return;
|
||||||
recordWriter.writeAck(ack);
|
recordWriter.writeAck(ack);
|
||||||
LOG.info("Sent ack");
|
LOG.info("Sent ack");
|
||||||
generateAck();
|
dbExecutor.execute(new GenerateAck());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,15 +222,12 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (interrupted) return;
|
if (interrupted) return;
|
||||||
if (!generateBatchQueued.getAndSet(false))
|
|
||||||
throw new AssertionError();
|
|
||||||
try {
|
try {
|
||||||
Collection<byte[]> b;
|
Collection<byte[]> b;
|
||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
b = db.generateRequestedBatch(txn, contactId,
|
b = db.generateRequestedBatch(txn, contactId,
|
||||||
MAX_RECORD_PAYLOAD_LENGTH, maxLatency);
|
MAX_RECORD_PAYLOAD_LENGTH, maxLatency);
|
||||||
setNextSendTime(db.getNextSendTime(txn, contactId));
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(txn);
|
db.endTransaction(txn);
|
||||||
@@ -303,7 +256,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
if (interrupted) return;
|
if (interrupted) return;
|
||||||
for (byte[] raw : batch) recordWriter.writeMessage(raw);
|
for (byte[] raw : batch) recordWriter.writeMessage(raw);
|
||||||
LOG.info("Sent batch");
|
LOG.info("Sent batch");
|
||||||
generateBatch();
|
dbExecutor.execute(new GenerateBatch());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,15 +266,12 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (interrupted) return;
|
if (interrupted) return;
|
||||||
if (!generateOfferQueued.getAndSet(false))
|
|
||||||
throw new AssertionError();
|
|
||||||
try {
|
try {
|
||||||
Offer o;
|
Offer o;
|
||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
try {
|
try {
|
||||||
o = db.generateOffer(txn, contactId, MAX_MESSAGE_IDS,
|
o = db.generateOffer(txn, contactId, MAX_MESSAGE_IDS,
|
||||||
maxLatency);
|
maxLatency);
|
||||||
setNextSendTime(db.getNextSendTime(txn, contactId));
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(txn);
|
db.endTransaction(txn);
|
||||||
@@ -350,7 +300,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
if (interrupted) return;
|
if (interrupted) return;
|
||||||
recordWriter.writeOffer(offer);
|
recordWriter.writeOffer(offer);
|
||||||
LOG.info("Sent offer");
|
LOG.info("Sent offer");
|
||||||
generateOffer();
|
dbExecutor.execute(new GenerateOffer());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,8 +310,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (interrupted) return;
|
if (interrupted) return;
|
||||||
if (!generateRequestQueued.getAndSet(false))
|
|
||||||
throw new AssertionError();
|
|
||||||
try {
|
try {
|
||||||
Request r;
|
Request r;
|
||||||
Transaction txn = db.startTransaction(false);
|
Transaction txn = db.startTransaction(false);
|
||||||
@@ -395,7 +343,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
if (interrupted) return;
|
if (interrupted) return;
|
||||||
recordWriter.writeRequest(request);
|
recordWriter.writeRequest(request);
|
||||||
LOG.info("Sent request");
|
LOG.info("Sent request");
|
||||||
generateRequest();
|
dbExecutor.execute(new GenerateRequest());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,16 +6,11 @@ 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 {
|
||||||
@@ -28,12 +23,9 @@ class GroupFactoryImpl implements GroupFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Group createGroup(ClientId c, int clientVersion, byte[] descriptor) {
|
public Group createGroup(ClientId c, byte[] descriptor) {
|
||||||
byte[] clientVersionBytes = new byte[INT_32_BYTES];
|
byte[] hash = crypto.hash(GroupId.LABEL,
|
||||||
ByteUtils.writeUint32(clientVersion, clientVersionBytes, 0);
|
StringUtils.toUtf8(c.getString()), descriptor);
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import org.briarproject.bramble.api.event.Event;
|
|||||||
import org.briarproject.bramble.api.event.EventBus;
|
import org.briarproject.bramble.api.event.EventBus;
|
||||||
import org.briarproject.bramble.api.event.EventListener;
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
|
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.Ack;
|
import org.briarproject.bramble.api.sync.Ack;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
@@ -27,7 +27,6 @@ import java.util.logging.Logger;
|
|||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An incoming {@link SyncSession}.
|
* An incoming {@link SyncSession}.
|
||||||
@@ -97,9 +96,8 @@ class IncomingSession implements SyncSession, EventListener {
|
|||||||
if (e instanceof ContactRemovedEvent) {
|
if (e instanceof ContactRemovedEvent) {
|
||||||
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
||||||
if (c.getContactId().equals(contactId)) interrupt();
|
if (c.getContactId().equals(contactId)) interrupt();
|
||||||
} else if (e instanceof LifecycleEvent) {
|
} else if (e instanceof ShutdownEvent) {
|
||||||
LifecycleEvent l = (LifecycleEvent) e;
|
interrupt();
|
||||||
if (l.getLifecycleState() == STOPPING) interrupt();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user