Pull-Merge of latest changes from main repo

This commit is contained in:
Abraham Kiggundu
2015-01-08 11:54:47 +03:00
118 changed files with 2538 additions and 1820 deletions

View File

@@ -93,8 +93,8 @@
</plurals> </plurals>
<string name="settings_title">Settings</string> <string name="settings_title">Settings</string>
<string name="bluetooth_setting_title">BLUETOOTH</string> <string name="bluetooth_setting_title">BLUETOOTH</string>
<string name="bluetooth_setting">Turn on Bluetooth</string> <string name="bluetooth_setting">Connect via Bluetooth</string>
<string name="bluetooth_setting_enabled">While signed in</string> <string name="bluetooth_setting_enabled">Whenever contacts are nearby</string>
<string name="bluetooth_setting_disabled">Only when adding contacts</string> <string name="bluetooth_setting_disabled">Only when adding contacts</string>
<string name="notification_settings_title">NOTIFICATIONS</string> <string name="notification_settings_title">NOTIFICATIONS</string>
<string name="notify_private_messages_setting">Show alerts for private messages</string> <string name="notify_private_messages_setting">Show alerts for private messages</string>

View File

@@ -133,10 +133,7 @@ public class PasswordActivity extends RoboActivity {
continueButton.setVisibility(GONE); continueButton.setVisibility(GONE);
progress.setVisibility(VISIBLE); progress.setVisibility(VISIBLE);
// Decrypt the database key in a background thread // Decrypt the database key in a background thread
int length = e.length(); final String password = e.toString();
final char[] password = new char[length];
e.getChars(0, length, password, 0);
e.delete(0, length);
cryptoExecutor.execute(new Runnable() { cryptoExecutor.execute(new Runnable() {
public void run() { public void run() {
byte[] key = crypto.decryptWithPassword(encrypted, password); byte[] key = crypto.decryptWithPassword(encrypted, password);

View File

@@ -19,7 +19,6 @@ import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP;
import static org.briarproject.api.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.api.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.api.crypto.PasswordStrengthEstimator.WEAK; import static org.briarproject.api.crypto.PasswordStrengthEstimator.WEAK;
import java.util.Arrays;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -43,7 +42,6 @@ import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor; import android.content.SharedPreferences.Editor;
import android.os.Bundle; import android.os.Bundle;
import android.text.Editable;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
@@ -187,18 +185,16 @@ OnEditorActionListener {
else strengthMeter.setVisibility(INVISIBLE); else strengthMeter.setVisibility(INVISIBLE);
String nickname = nicknameEntry.getText().toString(); String nickname = nicknameEntry.getText().toString();
int nicknameLength = StringUtils.toUtf8(nickname).length; int nicknameLength = StringUtils.toUtf8(nickname).length;
char[] firstPassword = getChars(passwordEntry.getText()); String firstPassword = passwordEntry.getText().toString();
char[] secondPassword = getChars(passwordConfirmation.getText()); String secondPassword = passwordConfirmation.getText().toString();
boolean passwordsMatch = Arrays.equals(firstPassword, secondPassword); boolean passwordsMatch = firstPassword.equals(secondPassword);
float strength = strengthEstimator.estimateStrength(firstPassword); float strength = strengthEstimator.estimateStrength(firstPassword);
for(int i = 0; i < firstPassword.length; i++) firstPassword[i] = 0;
for(int i = 0; i < secondPassword.length; i++) secondPassword[i] = 0;
strengthMeter.setStrength(strength); strengthMeter.setStrength(strength);
if(nicknameLength > MAX_AUTHOR_NAME_LENGTH) { if(nicknameLength > MAX_AUTHOR_NAME_LENGTH) {
feedback.setText(R.string.name_too_long); feedback.setText(R.string.name_too_long);
} else if(firstPassword.length == 0) { } else if(firstPassword.length() == 0) {
feedback.setText(""); feedback.setText("");
} else if(secondPassword.length == 0 || passwordsMatch) { } else if(secondPassword.length() == 0 || passwordsMatch) {
if(strength < PasswordStrengthEstimator.WEAK) if(strength < PasswordStrengthEstimator.WEAK)
feedback.setText(R.string.password_too_weak); feedback.setText(R.string.password_too_weak);
else feedback.setText(""); else feedback.setText("");
@@ -212,13 +208,6 @@ OnEditorActionListener {
&& passwordsMatch && strength >= WEAK); && passwordsMatch && strength >= WEAK);
} }
private char[] getChars(Editable e) {
int length = e.length();
char[] c = new char[length];
e.getChars(0, length, c, 0);
return c;
}
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
// Hide the soft keyboard // Hide the soft keyboard
Object o = getSystemService(INPUT_METHOD_SERVICE); Object o = getSystemService(INPUT_METHOD_SERVICE);
@@ -231,18 +220,14 @@ OnEditorActionListener {
feedback.setVisibility(GONE); feedback.setVisibility(GONE);
continueButton.setVisibility(GONE); continueButton.setVisibility(GONE);
progress.setVisibility(VISIBLE); progress.setVisibility(VISIBLE);
// Copy the passwords and erase the originals
final String nickname = nicknameEntry.getText().toString(); final String nickname = nicknameEntry.getText().toString();
final char[] password = getChars(passwordEntry.getText()); final String password = passwordEntry.getText().toString();
delete(passwordEntry.getText());
delete(passwordConfirmation.getText());
// Store the DB key and create the identity in a background thread // Store the DB key and create the identity in a background thread
cryptoExecutor.execute(new Runnable() { cryptoExecutor.execute(new Runnable() {
public void run() { public void run() {
byte[] key = crypto.generateSecretKey().getEncoded(); byte[] key = crypto.generateSecretKey().getBytes();
databaseConfig.setEncryptionKey(key); databaseConfig.setEncryptionKey(key);
byte[] encrypted = encryptDatabaseKey(key, password); byte[] encrypted = encryptDatabaseKey(key, password);
for(int i = 0; i < password.length; i++) password[i] = 0;
storeEncryptedDatabaseKey(encrypted); storeEncryptedDatabaseKey(encrypted);
LocalAuthor localAuthor = createLocalAuthor(nickname); LocalAuthor localAuthor = createLocalAuthor(nickname);
showDashboard(referenceManager.putReference(localAuthor, showDashboard(referenceManager.putReference(localAuthor,
@@ -251,10 +236,6 @@ OnEditorActionListener {
}); });
} }
private void delete(Editable e) {
e.delete(0, e.length());
}
private void storeEncryptedDatabaseKey(final byte[] encrypted) { private void storeEncryptedDatabaseKey(final byte[] encrypted) {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
SharedPreferences prefs = getSharedPreferences("db", MODE_PRIVATE); SharedPreferences prefs = getSharedPreferences("db", MODE_PRIVATE);
@@ -266,7 +247,7 @@ OnEditorActionListener {
LOG.info("Key storage took " + duration + " ms"); LOG.info("Key storage took " + duration + " ms");
} }
private byte[] encryptDatabaseKey(byte[] key, char[] password) { private byte[] encryptDatabaseKey(byte[] key, String password) {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
byte[] encrypted = crypto.encryptWithPassword(key, password); byte[] encrypted = crypto.encryptWithPassword(key, password);
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;

View File

@@ -69,8 +69,7 @@ class DroidtoothPlugin implements DuplexPlugin {
private final SecureRandom secureRandom; private final SecureRandom secureRandom;
private final Clock clock; private final Clock clock;
private final DuplexPluginCallback callback; private final DuplexPluginCallback callback;
private final int maxFrameLength; private final int maxLatency, pollingInterval;
private final long maxLatency, pollingInterval;
private volatile boolean running = false; private volatile boolean running = false;
private volatile boolean wasDisabled = false; private volatile boolean wasDisabled = false;
@@ -82,15 +81,14 @@ class DroidtoothPlugin implements DuplexPlugin {
DroidtoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor, DroidtoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor,
Context appContext, SecureRandom secureRandom, Clock clock, Context appContext, SecureRandom secureRandom, Clock clock,
DuplexPluginCallback callback, int maxFrameLength, long maxLatency, DuplexPluginCallback callback, int maxLatency,
long pollingInterval) { int pollingInterval) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.androidExecutor = androidExecutor; this.androidExecutor = androidExecutor;
this.appContext = appContext; this.appContext = appContext;
this.secureRandom = secureRandom; this.secureRandom = secureRandom;
this.clock = clock; this.clock = clock;
this.callback = callback; this.callback = callback;
this.maxFrameLength = maxFrameLength;
this.maxLatency = maxLatency; this.maxLatency = maxLatency;
this.pollingInterval = pollingInterval; this.pollingInterval = pollingInterval;
} }
@@ -99,12 +97,13 @@ class DroidtoothPlugin implements DuplexPlugin {
return ID; return ID;
} }
public int getMaxFrameLength() { public int getMaxLatency() {
return maxFrameLength; return maxLatency;
} }
public long getMaxLatency() { public int getMaxIdleTime() {
return maxLatency; // Bluetooth detects dead connections so we don't need keepalives
return Integer.MAX_VALUE;
} }
public boolean start() throws IOException { public boolean start() throws IOException {
@@ -240,7 +239,7 @@ class DroidtoothPlugin implements DuplexPlugin {
return true; return true;
} }
public long getPollingInterval() { public int getPollingInterval() {
return pollingInterval; return pollingInterval;
} }
@@ -361,6 +360,7 @@ class DroidtoothPlugin implements DuplexPlugin {
private class BluetoothStateReceiver extends BroadcastReceiver { private class BluetoothStateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context ctx, Intent intent) { public void onReceive(Context ctx, Intent intent) {
int state = intent.getIntExtra(EXTRA_STATE, 0); int state = intent.getIntExtra(EXTRA_STATE, 0);
if(state == STATE_ON) { if(state == STATE_ON) {

View File

@@ -15,9 +15,8 @@ import android.content.Context;
public class DroidtoothPluginFactory implements DuplexPluginFactory { public class DroidtoothPluginFactory implements DuplexPluginFactory {
private static final int MAX_FRAME_LENGTH = 1024; private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final long MAX_LATENCY = 60 * 1000; // 1 minute private static final int POLLING_INTERVAL = 3 * 60 * 1000; // 3 minutes
private static final long POLLING_INTERVAL = 3 * 60 * 1000; // 3 minutes
private final Executor ioExecutor; private final Executor ioExecutor;
private final AndroidExecutor androidExecutor; private final AndroidExecutor androidExecutor;
@@ -41,7 +40,6 @@ public class DroidtoothPluginFactory implements DuplexPluginFactory {
public DuplexPlugin createPlugin(DuplexPluginCallback callback) { public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
return new DroidtoothPlugin(ioExecutor, androidExecutor, appContext, return new DroidtoothPlugin(ioExecutor, androidExecutor, appContext,
secureRandom, clock, callback, MAX_FRAME_LENGTH, MAX_LATENCY, secureRandom, clock, callback, MAX_LATENCY, POLLING_INTERVAL);
POLLING_INTERVAL);
} }
} }

View File

@@ -39,10 +39,6 @@ class DroidtoothTransportConnection implements DuplexTransportConnection {
private class Reader implements TransportConnectionReader { private class Reader implements TransportConnectionReader {
public int getMaxFrameLength() {
return plugin.getMaxFrameLength();
}
public long getMaxLatency() { public long getMaxLatency() {
return plugin.getMaxLatency(); return plugin.getMaxLatency();
} }
@@ -60,12 +56,12 @@ class DroidtoothTransportConnection implements DuplexTransportConnection {
private class Writer implements TransportConnectionWriter { private class Writer implements TransportConnectionWriter {
public int getMaxFrameLength() { public int getMaxLatency() {
return plugin.getMaxFrameLength(); return plugin.getMaxLatency();
} }
public long getMaxLatency() { public int getMaxIdleTime() {
return plugin.getMaxLatency(); return plugin.getMaxIdleTime();
} }
public long getCapacity() { public long getCapacity() {

View File

@@ -26,10 +26,9 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
private volatile BroadcastReceiver networkStateReceiver = null; private volatile BroadcastReceiver networkStateReceiver = null;
AndroidLanTcpPlugin(Executor ioExecutor, Context appContext, AndroidLanTcpPlugin(Executor ioExecutor, Context appContext,
DuplexPluginCallback callback, int maxFrameLength, long maxLatency, DuplexPluginCallback callback, int maxLatency,
long pollingInterval) { int maxIdleTime, int pollingInterval) {
super(ioExecutor, callback, maxFrameLength, maxLatency, super(ioExecutor, callback, maxLatency, maxIdleTime, pollingInterval);
pollingInterval);
this.appContext = appContext; this.appContext = appContext;
} }

View File

@@ -11,9 +11,9 @@ import android.content.Context;
public class AndroidLanTcpPluginFactory implements DuplexPluginFactory { public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
private static final int MAX_FRAME_LENGTH = 1024; private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final long MAX_LATENCY = 60 * 1000; // 1 minute private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
private static final long POLLING_INTERVAL = 60 * 1000; // 1 minute private static final int POLLING_INTERVAL = 3 * 60 * 1000; // 3 minutes
private final Executor ioExecutor; private final Executor ioExecutor;
private final Context appContext; private final Context appContext;
@@ -29,6 +29,6 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
public DuplexPlugin createPlugin(DuplexPluginCallback callback) { public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
return new AndroidLanTcpPlugin(ioExecutor, appContext, callback, return new AndroidLanTcpPlugin(ioExecutor, appContext, callback,
MAX_FRAME_LENGTH, MAX_LATENCY, POLLING_INTERVAL); MAX_LATENCY, MAX_IDLE_TIME, POLLING_INTERVAL);
} }
} }

View File

@@ -75,8 +75,7 @@ class TorPlugin implements DuplexPlugin, EventHandler {
private final Context appContext; private final Context appContext;
private final LocationUtils locationUtils; private final LocationUtils locationUtils;
private final DuplexPluginCallback callback; private final DuplexPluginCallback callback;
private final int maxFrameLength; private final int maxLatency, maxIdleTime, pollingInterval, socketTimeout;
private final long maxLatency, pollingInterval;
private final File torDirectory, torFile, geoIpFile, configFile, doneFile; private final File torDirectory, torFile, geoIpFile, configFile, doneFile;
private final File cookieFile, hostnameFile; private final File cookieFile, hostnameFile;
private final AtomicBoolean circuitBuilt; private final AtomicBoolean circuitBuilt;
@@ -90,14 +89,17 @@ class TorPlugin implements DuplexPlugin, EventHandler {
TorPlugin(Executor ioExecutor, Context appContext, TorPlugin(Executor ioExecutor, Context appContext,
LocationUtils locationUtils, DuplexPluginCallback callback, LocationUtils locationUtils, DuplexPluginCallback callback,
int maxFrameLength, long maxLatency, long pollingInterval) { int maxLatency, int maxIdleTime, int pollingInterval) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.appContext = appContext; this.appContext = appContext;
this.locationUtils = locationUtils; this.locationUtils = locationUtils;
this.callback = callback; this.callback = callback;
this.maxFrameLength = maxFrameLength;
this.maxLatency = maxLatency; this.maxLatency = maxLatency;
this.maxIdleTime = maxIdleTime;
this.pollingInterval = pollingInterval; this.pollingInterval = pollingInterval;
if(maxIdleTime > Integer.MAX_VALUE / 2)
socketTimeout = Integer.MAX_VALUE;
else socketTimeout = maxIdleTime * 2;
torDirectory = appContext.getDir("tor", MODE_PRIVATE); torDirectory = appContext.getDir("tor", MODE_PRIVATE);
torFile = new File(torDirectory, "tor"); torFile = new File(torDirectory, "tor");
geoIpFile = new File(torDirectory, "geoip"); geoIpFile = new File(torDirectory, "geoip");
@@ -112,12 +114,12 @@ class TorPlugin implements DuplexPlugin, EventHandler {
return ID; return ID;
} }
public int getMaxFrameLength() { public int getMaxLatency() {
return maxFrameLength; return maxLatency;
} }
public long getMaxLatency() { public int getMaxIdleTime() {
return maxLatency; return maxIdleTime;
} }
public boolean start() throws IOException { public boolean start() throws IOException {
@@ -446,6 +448,7 @@ class TorPlugin implements DuplexPlugin, EventHandler {
Socket s; Socket s;
try { try {
s = ss.accept(); s = ss.accept();
s.setSoTimeout(socketTimeout);
} catch(IOException e) { } catch(IOException e) {
// This is expected when the socket is closed // This is expected when the socket is closed
if(LOG.isLoggable(INFO)) LOG.info(e.toString()); if(LOG.isLoggable(INFO)) LOG.info(e.toString());
@@ -494,7 +497,7 @@ class TorPlugin implements DuplexPlugin, EventHandler {
return true; return true;
} }
public long getPollingInterval() { public int getPollingInterval() {
return pollingInterval; return pollingInterval;
} }
@@ -529,6 +532,7 @@ class TorPlugin implements DuplexPlugin, EventHandler {
Socks5Proxy proxy = new Socks5Proxy("127.0.0.1", SOCKS_PORT); Socks5Proxy proxy = new Socks5Proxy("127.0.0.1", SOCKS_PORT);
proxy.resolveAddrLocally(false); proxy.resolveAddrLocally(false);
Socket s = new SocksSocket(proxy, onion, 80); Socket s = new SocksSocket(proxy, onion, 80);
s.setSoTimeout(socketTimeout);
if(LOG.isLoggable(INFO)) LOG.info("Connected to " + onion); if(LOG.isLoggable(INFO)) LOG.info("Connected to " + onion);
return new TorTransportConnection(this, s); return new TorTransportConnection(this, s);
} catch(IOException e) { } catch(IOException e) {

View File

@@ -17,9 +17,9 @@ public class TorPluginFactory implements DuplexPluginFactory {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(TorPluginFactory.class.getName()); Logger.getLogger(TorPluginFactory.class.getName());
private static final int MAX_FRAME_LENGTH = 1024; private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final long MAX_LATENCY = 60 * 1000; // 1 minute private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
private static final long POLLING_INTERVAL = 3 * 60 * 1000; // 3 minutes private static final int POLLING_INTERVAL = 3 * 60 * 1000; // 3 minutes
private final Executor ioExecutor; private final Executor ioExecutor;
private final Context appContext; private final Context appContext;
@@ -43,6 +43,6 @@ public class TorPluginFactory implements DuplexPluginFactory {
return null; return null;
} }
return new TorPlugin(ioExecutor,appContext, locationUtils, callback, return new TorPlugin(ioExecutor,appContext, locationUtils, callback,
MAX_FRAME_LENGTH, MAX_LATENCY, POLLING_INTERVAL); MAX_LATENCY, MAX_IDLE_TIME, POLLING_INTERVAL);
} }
} }

View File

@@ -38,10 +38,6 @@ class TorTransportConnection implements DuplexTransportConnection {
private class Reader implements TransportConnectionReader { private class Reader implements TransportConnectionReader {
public int getMaxFrameLength() {
return plugin.getMaxFrameLength();
}
public long getMaxLatency() { public long getMaxLatency() {
return plugin.getMaxLatency(); return plugin.getMaxLatency();
} }
@@ -59,12 +55,12 @@ class TorTransportConnection implements DuplexTransportConnection {
private class Writer implements TransportConnectionWriter { private class Writer implements TransportConnectionWriter {
public int getMaxFrameLength() { public int getMaxLatency() {
return plugin.getMaxFrameLength(); return plugin.getMaxLatency();
} }
public long getMaxLatency() { public int getMaxIdleTime() {
return plugin.getMaxLatency(); return plugin.getMaxIdleTime();
} }
public long getCapacity() { public long getCapacity() {

View File

@@ -2,7 +2,7 @@ package org.briarproject.api.crypto;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
/** An authenticated cipher that support additional authenticated data. */ /** An authenticated cipher that supports additional authenticated data. */
public interface AuthenticatedCipher { public interface AuthenticatedCipher {
/** /**
@@ -13,10 +13,10 @@ public interface AuthenticatedCipher {
throws GeneralSecurityException; throws GeneralSecurityException;
/** Encrypts or decrypts data in a single-part operation. */ /** Encrypts or decrypts data in a single-part operation. */
int doFinal(byte[] input, int inputOff, int len, byte[] output, int process(byte[] input, int inputOff, int len, byte[] output,
int outputOff) throws GeneralSecurityException; int outputOff) throws GeneralSecurityException;
/** Returns the length of the message authenticated code (MAC) in bytes. */ /** Returns the length of the message authentication code (MAC) in bytes. */
int getMacLength(); int getMacLength();
/** Returns the block size of the cipher in bytes. */ /** Returns the block size of the cipher in bytes. */

View File

@@ -65,7 +65,7 @@ public interface CryptoComponent {
/** /**
* Derives a tag key from the given temporary secret. * Derives a tag key from the given temporary secret.
* @param alice indicates whether the key is for connections initiated by * @param alice indicates whether the key is for streams initiated by
* Alice or Bob. * Alice or Bob.
*/ */
SecretKey deriveTagKey(byte[] secret, boolean alice); SecretKey deriveTagKey(byte[] secret, boolean alice);
@@ -89,7 +89,7 @@ public interface CryptoComponent {
* given password. The ciphertext will be decryptable using the same * given password. The ciphertext will be decryptable using the same
* password after the app restarts. * password after the app restarts.
*/ */
byte[] encryptWithPassword(byte[] plaintext, char[] password); byte[] encryptWithPassword(byte[] plaintext, String password);
/** /**
* Decrypts and authenticates the given ciphertext that has been read from * Decrypts and authenticates the given ciphertext that has been read from
@@ -97,5 +97,5 @@ 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).
*/ */
byte[] decryptWithPassword(byte[] ciphertext, char[] password); byte[] decryptWithPassword(byte[] ciphertext, String password);
} }

View File

@@ -16,9 +16,6 @@ public interface KeyManager extends Service {
*/ */
StreamContext getStreamContext(ContactId c, TransportId t); StreamContext getStreamContext(ContactId c, TransportId t);
/** /** Called whenever an endpoint has been added. */
* Called whenever an endpoint has been added. The initial secret is erased void endpointAdded(Endpoint ep, int maxLatency, byte[] initialSecret);
* before returning.
*/
void endpointAdded(Endpoint ep, long maxLatency, byte[] initialSecret);
} }

View File

@@ -12,5 +12,5 @@ public interface PasswordStrengthEstimator {
* Returns an estimate between 0 (weakest) and 1 (strongest), inclusive, * Returns an estimate between 0 (weakest) and 1 (strongest), inclusive,
* of the strength of the given password. * of the strength of the given password.
*/ */
float estimateStrength(char[] password); float estimateStrength(String password);
} }

View File

@@ -1,21 +1,15 @@
package org.briarproject.api.crypto; package org.briarproject.api.crypto;
/** A secret key used for encryption and/or authentication. */ /** A secret key used for encryption and/or authentication. */
public interface SecretKey { public class SecretKey {
/** Returns the encoded representation of this key. */ private final byte[] key;
byte[] getEncoded();
/** public SecretKey(byte[] key) {
* Returns a copy of this key - erasing this key will erase the copy and this.key = key;
* vice versa. }
*/
SecretKey copy();
/** public byte[] getBytes() {
* Erases this key from memory. Any copies derived from this key via the return key;
* {@link #copy()} method, and any keys from which this key was derived via }
* the {@link #copy()} method, are also erased.
*/
void erase();
} }

View File

@@ -0,0 +1,14 @@
package org.briarproject.api.crypto;
import java.io.IOException;
public interface StreamDecrypter {
/**
* Reads a frame, decrypts its payload into the given buffer and returns
* the payload length, or -1 if no more frames can be read from the stream.
* @throws java.io.IOException if an error occurs while reading the frame,
* or if authenticated decryption fails.
*/
int readFrame(byte[] payload) throws IOException;
}

View File

@@ -0,0 +1,17 @@
package org.briarproject.api.crypto;
import java.io.InputStream;
import org.briarproject.api.transport.StreamContext;
public interface StreamDecrypterFactory {
/** Creates a {@link StreamDecrypter} for decrypting a transport stream. */
StreamDecrypter createStreamDecrypter(InputStream in, StreamContext ctx);
/**
* Creates a {@link StreamDecrypter} for decrypting an invitation stream.
*/
StreamDecrypter createInvitationStreamDecrypter(InputStream in,
byte[] secret, boolean alice);
}

View File

@@ -0,0 +1,13 @@
package org.briarproject.api.crypto;
import java.io.IOException;
public interface StreamEncrypter {
/** Encrypts the given frame and writes it to the stream. */
void writeFrame(byte[] payload, int payloadLength, int paddingLength,
boolean finalFrame) throws IOException;
/** Flushes the stream. */
void flush() throws IOException;
}

View File

@@ -0,0 +1,17 @@
package org.briarproject.api.crypto;
import java.io.OutputStream;
import org.briarproject.api.transport.StreamContext;
public interface StreamEncrypterFactory {
/** Creates a {@link StreamEncrypter} for encrypting a transport stream. */
StreamEncrypter createStreamEncrypter(OutputStream out, StreamContext ctx);
/**
* Creates a {@link StreamEncrypter} for encrypting an invitation stream.
*/
StreamEncrypter createInvitationStreamEncrypter(OutputStream out,
byte[] secret, boolean alice);
}

View File

@@ -73,7 +73,7 @@ public interface DatabaseComponent {
* Stores a transport and returns true if the transport was not previously * Stores a transport and returns true if the transport was not previously
* in the database. * in the database.
*/ */
boolean addTransport(TransportId t, long maxLatency) throws DbException; boolean addTransport(TransportId t, int maxLatency) throws DbException;
/** /**
* Returns an acknowledgement for the given contact, or null if there are * Returns an acknowledgement for the given contact, or null if there are
@@ -88,14 +88,14 @@ public interface DatabaseComponent {
* sendable messages that fit in the given length. * sendable messages that fit in the given length.
*/ */
Collection<byte[]> generateBatch(ContactId c, int maxLength, Collection<byte[]> generateBatch(ContactId c, int maxLength,
long maxLatency) throws DbException; int maxLatency) throws DbException;
/** /**
* Returns an offer for the given contact for transmission over a * Returns an offer for the given contact for transmission over a
* transport with the given maximum latency, or null if there are no * transport with the given maximum latency, or null if there are no
* messages to offer. * messages to offer.
*/ */
Offer generateOffer(ContactId c, int maxMessages, long maxLatency) Offer generateOffer(ContactId c, int maxMessages, int maxLatency)
throws DbException; throws DbException;
/** /**
@@ -112,7 +112,7 @@ public interface DatabaseComponent {
* sendable messages that fit in the given length. * sendable messages that fit in the given length.
*/ */
Collection<byte[]> generateRequestedBatch(ContactId c, int maxLength, Collection<byte[]> generateRequestedBatch(ContactId c, int maxLength,
long maxLatency) throws DbException; int maxLatency) throws DbException;
/** /**
* Returns a retention ack for the given contact, or null if no retention * Returns a retention ack for the given contact, or null if no retention
@@ -125,7 +125,7 @@ public interface DatabaseComponent {
* over a transport with the given latency. Returns null if no update is * over a transport with the given latency. Returns null if no update is
* due. * due.
*/ */
RetentionUpdate generateRetentionUpdate(ContactId c, long maxLatency) RetentionUpdate generateRetentionUpdate(ContactId c, int maxLatency)
throws DbException; throws DbException;
/** /**
@@ -139,7 +139,7 @@ public interface DatabaseComponent {
* over a transport with the given latency. Returns null if no update is * over a transport with the given latency. Returns null if no update is
* due. * due.
*/ */
SubscriptionUpdate generateSubscriptionUpdate(ContactId c, long maxLatency) SubscriptionUpdate generateSubscriptionUpdate(ContactId c, int maxLatency)
throws DbException; throws DbException;
/** /**
@@ -155,7 +155,7 @@ public interface DatabaseComponent {
* updates are due. * updates are due.
*/ */
Collection<TransportUpdate> generateTransportUpdates(ContactId c, Collection<TransportUpdate> generateTransportUpdates(ContactId c,
long maxLatency) throws DbException; int maxLatency) throws DbException;
/** /**
* Returns the status of all groups to which the user subscribes or can * Returns the status of all groups to which the user subscribes or can
@@ -227,8 +227,8 @@ public interface DatabaseComponent {
/** Returns all contacts who subscribe to the given group. */ /** Returns all contacts who subscribe to the given group. */
Collection<Contact> getSubscribers(GroupId g) throws DbException; Collection<Contact> getSubscribers(GroupId g) throws DbException;
/** Returns the maximum latencies of all local transports. */ /** Returns the maximum latencies of all supported transports. */
Map<TransportId, Long> getTransportLatencies() throws DbException; Map<TransportId, Integer> getTransportLatencies() throws DbException;
/** Returns the number of unread messages in each subscribed group. */ /** Returns the number of unread messages in each subscribed group. */
Map<GroupId, Integer> getUnreadMessageCounts() throws DbException; Map<GroupId, Integer> getUnreadMessageCounts() throws DbException;

View File

@@ -6,9 +6,9 @@ import org.briarproject.api.TransportId;
public class TransportAddedEvent extends Event { public class TransportAddedEvent extends Event {
private final TransportId transportId; private final TransportId transportId;
private final long maxLatency; private final int maxLatency;
public TransportAddedEvent(TransportId transportId, long maxLatency) { public TransportAddedEvent(TransportId transportId, int maxLatency) {
this.transportId = transportId; this.transportId = transportId;
this.maxLatency = maxLatency; this.maxLatency = maxLatency;
} }
@@ -17,7 +17,7 @@ public class TransportAddedEvent extends Event {
return transportId; return transportId;
} }
public long getMaxLatency() { public int getMaxLatency() {
return maxLatency; return maxLatency;
} }
} }

View File

@@ -11,6 +11,9 @@ public interface MessagingSessionFactory {
MessagingSession createIncomingSession(ContactId c, TransportId t, MessagingSession createIncomingSession(ContactId c, TransportId t,
InputStream in); InputStream in);
MessagingSession createOutgoingSession(ContactId c, TransportId t, MessagingSession createSimplexOutgoingSession(ContactId c, TransportId t,
long maxLatency, boolean duplex, OutputStream out); int maxLatency, OutputStream out);
MessagingSession createDuplexOutgoingSession(ContactId c, TransportId t,
int maxLatency, int maxIdleTime, OutputStream out);
} }

View File

@@ -29,4 +29,6 @@ public interface PacketWriter {
void writeTransportAck(TransportAck a) throws IOException; void writeTransportAck(TransportAck a) throws IOException;
void writeTransportUpdate(TransportUpdate u) throws IOException; void writeTransportUpdate(TransportUpdate u) throws IOException;
void flush() throws IOException;
} }

View File

@@ -11,11 +11,11 @@ public interface Plugin {
/** Returns the plugin's transport identifier. */ /** Returns the plugin's transport identifier. */
TransportId getId(); TransportId getId();
/** Returns the transport's maximum frame length in bytes. */
int getMaxFrameLength();
/** Returns the transport's maximum latency in milliseconds. */ /** Returns the transport's maximum latency in milliseconds. */
long getMaxLatency(); int getMaxLatency();
/** Returns the transport's maximum idle time in milliseconds. */
int getMaxIdleTime();
/** Starts the plugin and returns true if it started successfully. */ /** Starts the plugin and returns true if it started successfully. */
boolean start() throws IOException; boolean start() throws IOException;
@@ -36,7 +36,7 @@ public interface Plugin {
* Returns the desired interval in milliseconds between calls to the * Returns the desired interval in milliseconds between calls to the
* plugin's {@link #poll(Collection)} method. * plugin's {@link #poll(Collection)} method.
*/ */
long getPollingInterval(); int getPollingInterval();
/** /**
* Attempts to establish connections to contacts, passing any created * Attempts to establish connections to contacts, passing any created

View File

@@ -9,9 +9,6 @@ import java.io.InputStream;
*/ */
public interface TransportConnectionReader { public interface TransportConnectionReader {
/** Returns the maximum frame length of the transport in bytes. */
int getMaxFrameLength();
/** Returns the maximum latency of the transport in milliseconds. */ /** Returns the maximum latency of the transport in milliseconds. */
long getMaxLatency(); long getMaxLatency();

View File

@@ -9,11 +9,11 @@ import java.io.OutputStream;
*/ */
public interface TransportConnectionWriter { public interface TransportConnectionWriter {
/** Returns the maximum frame length of the transport in bytes. */
int getMaxFrameLength();
/** Returns the maximum latency of the transport in milliseconds. */ /** Returns the maximum latency of the transport in milliseconds. */
long getMaxLatency(); int getMaxLatency();
/** Returns the maximum idle time of the transport in milliseconds. */
int getMaxIdleTime();
/** Returns the capacity of the transport connection in bytes. */ /** Returns the capacity of the transport connection in bytes. */
long getCapacity(); long getCapacity();

View File

@@ -1,13 +0,0 @@
package org.briarproject.api.transport;
import java.io.InputStream;
/** Decrypts and authenticates data received over an underlying transport. */
public interface StreamReader {
/**
* Returns an input stream from which the decrypted, authenticated data can
* be read.
*/
InputStream getInputStream();
}

View File

@@ -4,11 +4,16 @@ import java.io.InputStream;
public interface StreamReaderFactory { public interface StreamReaderFactory {
/** Creates a {@link StreamReader} for a transport connection. */ /**
StreamReader createStreamReader(InputStream in, int maxFrameLength, * Creates an {@link java.io.InputStream InputStream} for reading from a
StreamContext ctx); * transport stream.
*/
InputStream createStreamReader(InputStream in, StreamContext ctx);
/** Creates a {@link StreamReader} for an invitation connection. */ /**
StreamReader createInvitationStreamReader(InputStream in, * Creates an {@link java.io.InputStream InputStream} for reading from an
int maxFrameLength, byte[] secret, boolean alice); * invitation stream.
*/
InputStream createInvitationStreamReader(InputStream in,
byte[] secret, boolean alice);
} }

View File

@@ -1,13 +0,0 @@
package org.briarproject.api.transport;
import java.io.OutputStream;
/** Encrypts and authenticates data to be sent over an underlying transport. */
public interface StreamWriter {
/**
* Returns an output stream to which unencrypted, unauthenticated data can
* be written.
*/
OutputStream getOutputStream();
}

View File

@@ -4,11 +4,16 @@ import java.io.OutputStream;
public interface StreamWriterFactory { public interface StreamWriterFactory {
/** Creates a {@link StreamWriter} for a transport connection. */ /**
StreamWriter createStreamWriter(OutputStream out, int maxFrameLength, * Creates an {@link java.io.OutputStream OutputStream} for writing to a
StreamContext ctx); * transport stream
*/
OutputStream createStreamWriter(OutputStream out, StreamContext ctx);
/** Creates a {@link StreamWriter} for an invitation connection. */ /**
StreamWriter createInvitationStreamWriter(OutputStream out, * Creates an {@link java.io.OutputStream OutputStream} for writing to an
int maxFrameLength, byte[] secret, boolean alice); * invitation stream.
*/
OutputStream createInvitationStreamWriter(OutputStream out,
byte[] secret, boolean alice);
} }

View File

@@ -1,25 +1,26 @@
package org.briarproject.api.transport; package org.briarproject.api.transport;
public interface TransportConstants { public interface TransportConstants {
/** The length of the pseudo-random tag in bytes. */ /** The length of the pseudo-random tag in bytes. */
int TAG_LENGTH = 16; int TAG_LENGTH = 16;
/** The maximum length of a frame in bytes, including the header and MAC. */ /** The maximum length of a frame in bytes, including the header and MAC. */
int MAX_FRAME_LENGTH = 32768; // 2^15, 32 KiB int MAX_FRAME_LENGTH = 1024;
/** The length of the initalisation vector (IV) in bytes. */
int IV_LENGTH = 12;
/** The length of the additional authenticated data (AAD) in bytes. */
int AAD_LENGTH = 6;
/** The length of the frame header in bytes. */
int HEADER_LENGTH = 2;
/** The length of the message authentication code (MAC) in bytes. */ /** The length of the message authentication code (MAC) in bytes. */
int MAC_LENGTH = 16; int MAC_LENGTH = 16;
/** The length of the frame header in bytes. */
int HEADER_LENGTH = 4 + MAC_LENGTH;
/** The maximum total length of the frame payload and padding in bytes. */
int MAX_PAYLOAD_LENGTH = MAX_FRAME_LENGTH - HEADER_LENGTH - MAC_LENGTH;
/** The length of the initalisation vector (IV) in bytes. */
int IV_LENGTH = 12;
/** /**
* The minimum stream length in bytes that all transport plugins must * The minimum stream length in bytes that all transport plugins must
* support. Streams may be shorter than this length, but all transport * support. Streams may be shorter than this length, but all transport

View File

@@ -20,7 +20,7 @@ class AuthenticatedCipherImpl implements AuthenticatedCipher {
this.macLength = macLength; this.macLength = macLength;
} }
public int doFinal(byte[] input, int inputOff, int len, byte[] output, public int process(byte[] input, int inputOff, int len, byte[] output,
int outputOff) throws GeneralSecurityException { int outputOff) throws GeneralSecurityException {
int processed = 0; int processed = 0;
if(len != 0) { if(len != 0) {
@@ -38,7 +38,7 @@ class AuthenticatedCipherImpl implements AuthenticatedCipher {
public void init(boolean encrypt, SecretKey key, byte[] iv, byte[] aad) public void init(boolean encrypt, SecretKey key, byte[] iv, byte[] aad)
throws GeneralSecurityException { throws GeneralSecurityException {
KeyParameter k = new KeyParameter(key.getEncoded()); KeyParameter k = new KeyParameter(key.getBytes());
AEADParameters params = new AEADParameters(k, macLength * 8, iv, aad); AEADParameters params = new AEADParameters(k, macLength * 8, iv, aad);
try { try {
cipher.init(encrypt, params); cipher.init(encrypt, params);

View File

@@ -7,8 +7,6 @@ import static org.briarproject.crypto.EllipticCurveConstants.P;
import static org.briarproject.crypto.EllipticCurveConstants.PARAMETERS; import static org.briarproject.crypto.EllipticCurveConstants.PARAMETERS;
import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED; import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.ArrayList; import java.util.ArrayList;
@@ -31,6 +29,7 @@ import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.crypto.Signature; import org.briarproject.api.crypto.Signature;
import org.briarproject.api.system.SeedProvider; import org.briarproject.api.system.SeedProvider;
import org.briarproject.util.ByteUtils; import org.briarproject.util.ByteUtils;
import org.briarproject.util.StringUtils;
import org.spongycastle.crypto.AsymmetricCipherKeyPair; import org.spongycastle.crypto.AsymmetricCipherKeyPair;
import org.spongycastle.crypto.BlockCipher; import org.spongycastle.crypto.BlockCipher;
import org.spongycastle.crypto.CipherParameters; import org.spongycastle.crypto.CipherParameters;
@@ -44,11 +43,11 @@ import org.spongycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.spongycastle.crypto.macs.HMac; import org.spongycastle.crypto.macs.HMac;
import org.spongycastle.crypto.modes.AEADBlockCipher; import org.spongycastle.crypto.modes.AEADBlockCipher;
import org.spongycastle.crypto.modes.GCMBlockCipher; import org.spongycastle.crypto.modes.GCMBlockCipher;
import org.spongycastle.crypto.modes.gcm.BasicGCMMultiplier;
import org.spongycastle.crypto.params.ECKeyGenerationParameters; import org.spongycastle.crypto.params.ECKeyGenerationParameters;
import org.spongycastle.crypto.params.ECPrivateKeyParameters; import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters; import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.crypto.params.KeyParameter; import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.util.Strings;
class CryptoComponentImpl implements CryptoComponent { class CryptoComponentImpl implements CryptoComponent {
@@ -114,7 +113,7 @@ class CryptoComponentImpl implements CryptoComponent {
public SecretKey generateSecretKey() { public SecretKey generateSecretKey() {
byte[] b = new byte[CIPHER_KEY_BYTES]; byte[] b = new byte[CIPHER_KEY_BYTES];
secureRandom.nextBytes(b); secureRandom.nextBytes(b);
return new SecretKeyImpl(b); return new SecretKey(b);
} }
public MessageDigest getMessageDigest() { public MessageDigest getMessageDigest() {
@@ -188,8 +187,6 @@ class CryptoComponentImpl implements CryptoComponent {
int[] codes = new int[2]; int[] codes = new int[2];
codes[0] = ByteUtils.readUint(alice, CODE_BITS); codes[0] = ByteUtils.readUint(alice, CODE_BITS);
codes[1] = ByteUtils.readUint(bob, CODE_BITS); codes[1] = ByteUtils.readUint(bob, CODE_BITS);
ByteUtils.erase(alice);
ByteUtils.erase(bob);
return codes; return codes;
} }
@@ -223,9 +220,7 @@ class CryptoComponentImpl implements CryptoComponent {
byte[] raw = deriveSharedSecret(ourPriv, theirPub); byte[] raw = deriveSharedSecret(ourPriv, theirPub);
// Derive the cooked secret from the raw secret using the // Derive the cooked secret from the raw secret using the
// concatenation KDF // concatenation KDF
byte[] cooked = concatenationKdf(raw, MASTER, aliceInfo, bobInfo); return concatenationKdf(raw, MASTER, aliceInfo, bobInfo);
ByteUtils.erase(raw);
return cooked;
} }
// Package access for testing // Package access for testing
@@ -296,12 +291,16 @@ class CryptoComponentImpl implements CryptoComponent {
} }
private SecretKey deriveKey(byte[] secret, byte[] label, long context) { private SecretKey deriveKey(byte[] secret, byte[] label, long context) {
byte[] key = counterModeKdf(secret, label, context); return new SecretKey(counterModeKdf(secret, label, context));
return new SecretKeyImpl(key);
} }
public AuthenticatedCipher getFrameCipher() { public AuthenticatedCipher getFrameCipher() {
AEADBlockCipher a = new GCMBlockCipher(new AESLightEngine()); return getAuthenticatedCipher();
}
private AuthenticatedCipher getAuthenticatedCipher() {
AEADBlockCipher a = new GCMBlockCipher(new AESLightEngine(),
new BasicGCMMultiplier());
return new AuthenticatedCipherImpl(a, MAC_BYTES); return new AuthenticatedCipherImpl(a, MAC_BYTES);
} }
@@ -313,21 +312,19 @@ class CryptoComponentImpl implements CryptoComponent {
ByteUtils.writeUint32(streamNumber, tag, 0); ByteUtils.writeUint32(streamNumber, tag, 0);
BlockCipher cipher = new AESLightEngine(); BlockCipher cipher = new AESLightEngine();
assert cipher.getBlockSize() == TAG_LENGTH; assert cipher.getBlockSize() == TAG_LENGTH;
KeyParameter k = new KeyParameter(tagKey.getEncoded()); KeyParameter k = new KeyParameter(tagKey.getBytes());
cipher.init(true, k); cipher.init(true, k);
cipher.processBlock(tag, 0, tag, 0); cipher.processBlock(tag, 0, tag, 0);
ByteUtils.erase(k.getKey());
} }
public byte[] encryptWithPassword(byte[] input, char[] password) { public byte[] encryptWithPassword(byte[] input, String password) {
// Generate a random salt // Generate a random salt
byte[] salt = new byte[PBKDF_SALT_BYTES]; byte[] salt = new byte[PBKDF_SALT_BYTES];
secureRandom.nextBytes(salt); secureRandom.nextBytes(salt);
// Calibrate the KDF // Calibrate the KDF
int iterations = chooseIterationCount(PBKDF_TARGET_MILLIS); int iterations = chooseIterationCount(PBKDF_TARGET_MILLIS);
// Derive the key from the password // Derive the key from the password
byte[] keyBytes = pbkdf2(password, salt, iterations); SecretKey key = new SecretKey(pbkdf2(password, salt, iterations));
SecretKey key = new SecretKeyImpl(keyBytes);
// 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);
@@ -338,22 +335,18 @@ class CryptoComponentImpl implements CryptoComponent {
ByteUtils.writeUint32(iterations, output, salt.length); ByteUtils.writeUint32(iterations, output, salt.length);
System.arraycopy(iv, 0, output, salt.length + 4, iv.length); System.arraycopy(iv, 0, output, salt.length + 4, iv.length);
// Initialise the cipher and encrypt the plaintext // Initialise the cipher and encrypt the plaintext
AuthenticatedCipher cipher = getAuthenticatedCipher();
try { try {
AEADBlockCipher a = new GCMBlockCipher(new AESLightEngine());
AuthenticatedCipher cipher = new AuthenticatedCipherImpl(a,
MAC_BYTES);
cipher.init(true, key, iv, null); cipher.init(true, key, iv, null);
int outputOff = salt.length + 4 + iv.length; int outputOff = salt.length + 4 + iv.length;
cipher.doFinal(input, 0, input.length, output, outputOff); cipher.process(input, 0, input.length, output, outputOff);
return output; return output;
} catch(GeneralSecurityException e) { } catch(GeneralSecurityException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} finally {
key.erase();
} }
} }
public byte[] decryptWithPassword(byte[] input, char[] password) { public byte[] decryptWithPassword(byte[] input, String password) {
// The input contains the salt, iterations, IV, ciphertext and MAC // The input contains the salt, iterations, IV, ciphertext and MAC
if(input.length < PBKDF_SALT_BYTES + 4 + STORAGE_IV_BYTES + MAC_BYTES) if(input.length < PBKDF_SALT_BYTES + 4 + STORAGE_IV_BYTES + MAC_BYTES)
return null; // Invalid return null; // Invalid
@@ -365,16 +358,12 @@ class CryptoComponentImpl implements CryptoComponent {
byte[] iv = new byte[STORAGE_IV_BYTES]; byte[] iv = new byte[STORAGE_IV_BYTES];
System.arraycopy(input, salt.length + 4, iv, 0, iv.length); System.arraycopy(input, salt.length + 4, iv, 0, iv.length);
// Derive the key from the password // Derive the key from the password
byte[] keyBytes = pbkdf2(password, salt, (int) iterations); SecretKey key = new SecretKey(pbkdf2(password, salt, (int) iterations));
SecretKey key = new SecretKeyImpl(keyBytes);
// Initialise the cipher // Initialise the cipher
AuthenticatedCipher cipher; AuthenticatedCipher cipher = getAuthenticatedCipher();
try { try {
AEADBlockCipher a = new GCMBlockCipher(new AESLightEngine());
cipher = new AuthenticatedCipherImpl(a, MAC_BYTES);
cipher.init(false, key, iv, null); cipher.init(false, key, iv, null);
} catch(GeneralSecurityException e) { } catch(GeneralSecurityException e) {
key.erase();
throw new RuntimeException(e); throw new RuntimeException(e);
} }
// Try to decrypt the ciphertext (may be invalid) // Try to decrypt the ciphertext (may be invalid)
@@ -382,12 +371,10 @@ class CryptoComponentImpl implements CryptoComponent {
int inputOff = salt.length + 4 + iv.length; int inputOff = salt.length + 4 + iv.length;
int inputLen = input.length - inputOff; int inputLen = input.length - inputOff;
byte[] output = new byte[inputLen - MAC_BYTES]; byte[] output = new byte[inputLen - MAC_BYTES];
cipher.doFinal(input, inputOff, inputLen, output, 0); cipher.process(input, inputOff, inputLen, output, 0);
return output; return output;
} catch(GeneralSecurityException e) { } catch(GeneralSecurityException e) {
return null; // Invalid return null; // Invalid ciphertext
} finally {
key.erase();
} }
} }
@@ -417,7 +404,6 @@ class CryptoComponentImpl implements CryptoComponent {
// The secret is the first CIPHER_KEY_BYTES bytes of the hash // The secret is the first CIPHER_KEY_BYTES bytes of the hash
byte[] output = new byte[CIPHER_KEY_BYTES]; byte[] output = new byte[CIPHER_KEY_BYTES];
System.arraycopy(hash, 0, output, 0, output.length); System.arraycopy(hash, 0, output, 0, output.length);
ByteUtils.erase(hash);
return output; return output;
} }
@@ -447,20 +433,17 @@ class CryptoComponentImpl implements CryptoComponent {
prf.update((byte) CIPHER_KEY_BYTES); // Output length prf.update((byte) CIPHER_KEY_BYTES); // Output length
prf.doFinal(mac, 0); prf.doFinal(mac, 0);
System.arraycopy(mac, 0, output, 0, output.length); System.arraycopy(mac, 0, output, 0, output.length);
ByteUtils.erase(mac);
ByteUtils.erase(k.getKey());
return output; return output;
} }
// Password-based key derivation function - see PKCS#5 v2.1, section 5.2 // Password-based key derivation function - see PKCS#5 v2.1, section 5.2
private byte[] pbkdf2(char[] password, byte[] salt, int iterations) { private byte[] pbkdf2(String password, byte[] salt, int iterations) {
byte[] utf8 = toUtf8ByteArray(password); byte[] utf8 = StringUtils.toUtf8(password);
Digest digest = new SHA384Digest(); Digest digest = new SHA384Digest();
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(digest); PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(digest);
gen.init(utf8, salt, iterations); gen.init(utf8, salt, iterations);
int keyLengthInBits = CIPHER_KEY_BYTES * 8; int keyLengthInBits = CIPHER_KEY_BYTES * 8;
CipherParameters p = gen.generateDerivedParameters(keyLengthInBits); CipherParameters p = gen.generateDerivedParameters(keyLengthInBits);
ByteUtils.erase(utf8);
return ((KeyParameter) p).getKey(); return ((KeyParameter) p).getKey();
} }
@@ -512,18 +495,4 @@ class CryptoComponentImpl implements CryptoComponent {
if(size % 2 == 1) return list.get(size / 2); if(size % 2 == 1) return list.get(size / 2);
return list.get(size / 2 - 1) + list.get(size / 2) / 2; return list.get(size / 2 - 1) + list.get(size / 2) / 2;
} }
private byte[] toUtf8ByteArray(char[] c) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
Strings.toUTF8ByteArray(c, out);
byte[] utf8 = out.toByteArray();
// Erase the output stream's buffer
out.reset();
out.write(new byte[utf8.length]);
return utf8;
} catch(IOException e) {
throw new RuntimeException(e);
}
}
} }

View File

@@ -14,6 +14,8 @@ import javax.inject.Singleton;
import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.CryptoExecutor; import org.briarproject.api.crypto.CryptoExecutor;
import org.briarproject.api.crypto.PasswordStrengthEstimator; import org.briarproject.api.crypto.PasswordStrengthEstimator;
import org.briarproject.api.crypto.StreamDecrypterFactory;
import org.briarproject.api.crypto.StreamEncrypterFactory;
import org.briarproject.api.lifecycle.LifecycleManager; import org.briarproject.api.lifecycle.LifecycleManager;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
@@ -44,6 +46,8 @@ public class CryptoModule extends AbstractModule {
CryptoComponentImpl.class).in(Singleton.class); CryptoComponentImpl.class).in(Singleton.class);
bind(PasswordStrengthEstimator.class).to( bind(PasswordStrengthEstimator.class).to(
PasswordStrengthEstimatorImpl.class); PasswordStrengthEstimatorImpl.class);
bind(StreamDecrypterFactory.class).to(StreamDecrypterFactoryImpl.class);
bind(StreamEncrypterFactory.class).to(StreamEncrypterFactoryImpl.class);
} }
@Provides @Singleton @CryptoExecutor @Provides @Singleton @CryptoExecutor

View File

@@ -1,44 +1,33 @@
package org.briarproject.transport; package org.briarproject.crypto;
import static org.briarproject.api.transport.TransportConstants.AAD_LENGTH;
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH; import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH;
import static org.briarproject.api.transport.TransportConstants.IV_LENGTH; import static org.briarproject.api.transport.TransportConstants.IV_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH; import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED; import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
import org.briarproject.util.ByteUtils; import org.briarproject.util.ByteUtils;
class FrameEncoder { class FrameEncoder {
static void encodeIv(byte[] iv, long frameNumber) { static void encodeIv(byte[] iv, long frameNumber, boolean header) {
if(iv.length < IV_LENGTH) throw new IllegalArgumentException(); if(iv.length < IV_LENGTH) throw new IllegalArgumentException();
if(frameNumber < 0 || frameNumber > MAX_32_BIT_UNSIGNED) if(frameNumber < 0 || frameNumber > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
ByteUtils.writeUint32(frameNumber, iv, 0); ByteUtils.writeUint32(frameNumber, iv, 0);
for(int i = 4; i < IV_LENGTH; i++) iv[i] = 0; if(header) iv[4] = 1;
} else iv[4] = 0;
for(int i = 5; i < IV_LENGTH; i++) iv[i] = 0;
static void encodeAad(byte[] aad, long frameNumber, int plaintextLength) {
if(aad.length < AAD_LENGTH) throw new IllegalArgumentException();
if(frameNumber < 0 || frameNumber > MAX_32_BIT_UNSIGNED)
throw new IllegalArgumentException();
if(plaintextLength < HEADER_LENGTH)
throw new IllegalArgumentException();
if(plaintextLength > MAX_FRAME_LENGTH - MAC_LENGTH)
throw new IllegalArgumentException();
ByteUtils.writeUint32(frameNumber, aad, 0);
ByteUtils.writeUint16(plaintextLength, aad, 4);
} }
static void encodeHeader(byte[] header, boolean finalFrame, static void encodeHeader(byte[] header, boolean finalFrame,
int payloadLength) { int payloadLength, int paddingLength) {
if(header.length < HEADER_LENGTH) throw new IllegalArgumentException(); if(header.length < HEADER_LENGTH) throw new IllegalArgumentException();
if(payloadLength < 0) if(payloadLength < 0) throw new IllegalArgumentException();
throw new IllegalArgumentException(); if(paddingLength < 0) throw new IllegalArgumentException();
if(payloadLength > MAX_FRAME_LENGTH - HEADER_LENGTH - MAC_LENGTH) if(payloadLength + paddingLength > MAX_PAYLOAD_LENGTH)
throw new IllegalArgumentException(); throw new IllegalArgumentException();
ByteUtils.writeUint16(payloadLength, header, 0); ByteUtils.writeUint16(payloadLength, header, 0);
ByteUtils.writeUint16(paddingLength, header, 2);
if(finalFrame) header[0] |= 0x80; if(finalFrame) header[0] |= 0x80;
} }
@@ -51,4 +40,9 @@ class FrameEncoder {
if(header.length < HEADER_LENGTH) throw new IllegalArgumentException(); if(header.length < HEADER_LENGTH) throw new IllegalArgumentException();
return ByteUtils.readUint16(header, 0) & 0x7FFF; return ByteUtils.readUint16(header, 0) & 0x7FFF;
} }
static int getPaddingLength(byte[] header) {
if(header.length < HEADER_LENGTH) throw new IllegalArgumentException();
return ByteUtils.readUint16(header, 2);
}
} }

View File

@@ -13,9 +13,10 @@ class PasswordStrengthEstimatorImpl implements PasswordStrengthEstimator {
private static final double STRONG = Math.log(Math.pow(LOWER + UPPER + private static final double STRONG = Math.log(Math.pow(LOWER + UPPER +
DIGIT + OTHER, 10)); DIGIT + OTHER, 10));
public float estimateStrength(char[] password) { public float estimateStrength(String password) {
HashSet<Character> unique = new HashSet<Character>(); HashSet<Character> unique = new HashSet<Character>();
for(char c : password) unique.add(c); int length = password.length();
for(int i = 0; i < length; i++) unique.add(password.charAt(i));
boolean lower = false, upper = false, digit = false, other = false; boolean lower = false, upper = false, digit = false, other = false;
for(char c : unique) { for(char c : unique) {
if(Character.isLowerCase(c)) lower = true; if(Character.isLowerCase(c)) lower = true;

View File

@@ -1,48 +0,0 @@
package org.briarproject.crypto;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.util.ByteUtils;
class SecretKeyImpl implements SecretKey {
private final byte[] key;
private boolean erased = false;
private final Lock synchLock = new ReentrantLock();
SecretKeyImpl(byte[] key) {
this.key = key;
}
public byte[] getEncoded() {
synchLock.lock();
try{
if(erased) throw new IllegalStateException();
return key;
}
finally{
synchLock.unlock();
}
}
public SecretKey copy() {
return new SecretKeyImpl(key.clone());
}
public void erase() {
synchLock.lock();
try{
if(erased) throw new IllegalStateException();
ByteUtils.erase(key);
erased = true;
}
finally{
synchLock.unlock();
}
}
}

View File

@@ -0,0 +1,40 @@
package org.briarproject.crypto;
import java.io.InputStream;
import javax.inject.Inject;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.crypto.StreamDecrypter;
import org.briarproject.api.crypto.StreamDecrypterFactory;
import org.briarproject.api.transport.StreamContext;
class StreamDecrypterFactoryImpl implements StreamDecrypterFactory {
private final CryptoComponent crypto;
@Inject
StreamDecrypterFactoryImpl(CryptoComponent crypto) {
this.crypto = crypto;
}
public StreamDecrypter createStreamDecrypter(InputStream in,
StreamContext ctx) {
// Derive the frame key
byte[] secret = ctx.getSecret();
long streamNumber = ctx.getStreamNumber();
boolean alice = !ctx.getAlice();
SecretKey frameKey = crypto.deriveFrameKey(secret, streamNumber, alice);
// Create the decrypter
return new StreamDecrypterImpl(in, crypto.getFrameCipher(), frameKey);
}
public StreamDecrypter createInvitationStreamDecrypter(InputStream in,
byte[] secret, boolean alice) {
// Derive the frame key
SecretKey frameKey = crypto.deriveFrameKey(secret, 0, alice);
// Create the decrypter
return new StreamDecrypterImpl(in, crypto.getFrameCipher(), frameKey);
}
}

View File

@@ -0,0 +1,97 @@
package org.briarproject.crypto;
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH;
import static org.briarproject.api.transport.TransportConstants.IV_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import org.briarproject.api.FormatException;
import org.briarproject.api.crypto.AuthenticatedCipher;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.crypto.StreamDecrypter;
class StreamDecrypterImpl implements StreamDecrypter {
private final InputStream in;
private final AuthenticatedCipher frameCipher;
private final SecretKey frameKey;
private final byte[] iv, aad, header, ciphertext;
private long frameNumber;
private boolean finalFrame;
StreamDecrypterImpl(InputStream in, AuthenticatedCipher frameCipher,
SecretKey frameKey) {
this.in = in;
this.frameCipher = frameCipher;
this.frameKey = frameKey;
iv = new byte[IV_LENGTH];
aad = new byte[IV_LENGTH];
header = new byte[HEADER_LENGTH];
ciphertext = new byte[MAX_FRAME_LENGTH];
frameNumber = 0;
finalFrame = false;
}
public int readFrame(byte[] payload) throws IOException {
if(payload.length < MAX_PAYLOAD_LENGTH)
throw new IllegalArgumentException();
if(finalFrame) return -1;
// Read the header
int offset = 0;
while(offset < HEADER_LENGTH) {
int read = in.read(ciphertext, offset, HEADER_LENGTH - offset);
if(read == -1) throw new EOFException();
offset += read;
}
// Decrypt and authenticate the header
FrameEncoder.encodeIv(iv, frameNumber, true);
FrameEncoder.encodeIv(aad, frameNumber, true);
try {
frameCipher.init(false, frameKey, iv, aad);
int decrypted = frameCipher.process(ciphertext, 0, HEADER_LENGTH,
header, 0);
if(decrypted != HEADER_LENGTH - MAC_LENGTH)
throw new RuntimeException();
} catch(GeneralSecurityException e) {
throw new FormatException();
}
// Decode and validate the header
finalFrame = FrameEncoder.isFinalFrame(header);
int payloadLength = FrameEncoder.getPayloadLength(header);
int paddingLength = FrameEncoder.getPaddingLength(header);
if(payloadLength + paddingLength > MAX_PAYLOAD_LENGTH)
throw new FormatException();
// Read the payload and padding
int frameLength = HEADER_LENGTH + payloadLength + paddingLength
+ MAC_LENGTH;
while(offset < frameLength) {
int read = in.read(ciphertext, offset, frameLength - offset);
if(read == -1) throw new EOFException();
offset += read;
}
// Decrypt and authenticate the payload and padding
FrameEncoder.encodeIv(iv, frameNumber, false);
FrameEncoder.encodeIv(aad, frameNumber, false);
try {
frameCipher.init(false, frameKey, iv, aad);
int decrypted = frameCipher.process(ciphertext, HEADER_LENGTH,
payloadLength + paddingLength + MAC_LENGTH, payload, 0);
if(decrypted != payloadLength + paddingLength)
throw new RuntimeException();
} catch(GeneralSecurityException e) {
throw new FormatException();
}
// If there's any padding it must be all zeroes
for(int i = 0; i < paddingLength; i++)
if(payload[payloadLength + i] != 0) throw new FormatException();
frameNumber++;
return payloadLength;
}
}

View File

@@ -0,0 +1,48 @@
package org.briarproject.crypto;
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
import java.io.OutputStream;
import javax.inject.Inject;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.crypto.StreamEncrypter;
import org.briarproject.api.crypto.StreamEncrypterFactory;
import org.briarproject.api.transport.StreamContext;
class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
private final CryptoComponent crypto;
@Inject
StreamEncrypterFactoryImpl(CryptoComponent crypto) {
this.crypto = crypto;
}
public StreamEncrypter createStreamEncrypter(OutputStream out,
StreamContext ctx) {
byte[] secret = ctx.getSecret();
long streamNumber = ctx.getStreamNumber();
boolean alice = ctx.getAlice();
// Encode the tag
byte[] tag = new byte[TAG_LENGTH];
SecretKey tagKey = crypto.deriveTagKey(secret, alice);
crypto.encodeTag(tag, tagKey, streamNumber);
// Derive the frame key
SecretKey frameKey = crypto.deriveFrameKey(secret, streamNumber, alice);
// Create the encrypter
return new StreamEncrypterImpl(out, crypto.getFrameCipher(), frameKey,
tag);
}
public StreamEncrypter createInvitationStreamEncrypter(OutputStream out,
byte[] secret, boolean alice) {
// Derive the frame key
SecretKey frameKey = crypto.deriveFrameKey(secret, 0, alice);
// Create the encrypter
return new StreamEncrypterImpl(out, crypto.getFrameCipher(), frameKey,
null);
}
}

View File

@@ -0,0 +1,97 @@
package org.briarproject.crypto;
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH;
import static org.briarproject.api.transport.TransportConstants.IV_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
import java.io.IOException;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import org.briarproject.api.crypto.AuthenticatedCipher;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.crypto.StreamEncrypter;
class StreamEncrypterImpl implements StreamEncrypter {
private final OutputStream out;
private final AuthenticatedCipher frameCipher;
private final SecretKey frameKey;
private final byte[] tag, iv, aad, plaintext, ciphertext;
private long frameNumber;
private boolean writeTag;
StreamEncrypterImpl(OutputStream out, AuthenticatedCipher frameCipher,
SecretKey frameKey, byte[] tag) {
this.out = out;
this.frameCipher = frameCipher;
this.frameKey = frameKey;
this.tag = tag;
iv = new byte[IV_LENGTH];
aad = new byte[IV_LENGTH];
plaintext = new byte[HEADER_LENGTH + MAX_PAYLOAD_LENGTH];
ciphertext = new byte[MAX_FRAME_LENGTH];
frameNumber = 0;
writeTag = (tag != null);
}
public void writeFrame(byte[] payload, int payloadLength,
int paddingLength, boolean finalFrame) throws IOException {
if(payloadLength + paddingLength > MAX_PAYLOAD_LENGTH)
throw new IllegalArgumentException();
// Don't allow the frame counter to wrap
if(frameNumber > MAX_32_BIT_UNSIGNED) throw new IOException();
// Write the tag if required
if(writeTag) {
out.write(tag, 0, tag.length);
writeTag = false;
}
// Encode the header
FrameEncoder.encodeHeader(plaintext, finalFrame, payloadLength,
paddingLength);
// Encrypt and authenticate the header
FrameEncoder.encodeIv(iv, frameNumber, true);
FrameEncoder.encodeIv(aad, frameNumber, true);
try {
frameCipher.init(true, frameKey, iv, aad);
int encrypted = frameCipher.process(plaintext, 0,
HEADER_LENGTH - MAC_LENGTH, ciphertext, 0);
if(encrypted != HEADER_LENGTH) throw new RuntimeException();
} catch(GeneralSecurityException badCipher) {
throw new RuntimeException(badCipher);
}
// Combine the payload and padding
System.arraycopy(payload, 0, plaintext, HEADER_LENGTH, payloadLength);
for(int i = 0; i < paddingLength; i++)
plaintext[HEADER_LENGTH + payloadLength + i] = 0;
// Encrypt and authenticate the payload and padding
FrameEncoder.encodeIv(iv, frameNumber, false);
FrameEncoder.encodeIv(aad, frameNumber, false);
try {
frameCipher.init(true, frameKey, iv, aad);
int encrypted = frameCipher.process(plaintext, HEADER_LENGTH,
payloadLength + paddingLength, ciphertext, HEADER_LENGTH);
if(encrypted != payloadLength + paddingLength + MAC_LENGTH)
throw new RuntimeException();
} catch(GeneralSecurityException badCipher) {
throw new RuntimeException(badCipher);
}
// Write the frame
out.write(ciphertext, 0, HEADER_LENGTH + payloadLength + paddingLength
+ MAC_LENGTH);
frameNumber++;
}
public void flush() throws IOException {
// Write the tag if required
if(writeTag) {
out.write(tag, 0, tag.length);
writeTag = false;
}
out.flush();
}
}

View File

@@ -152,7 +152,7 @@ interface Database<T> {
* <p> * <p>
* Locking: write. * Locking: write.
*/ */
boolean addTransport(T txn, TransportId t, long maxLatency) boolean addTransport(T txn, TransportId t, int maxLatency)
throws DbException; throws DbException;
/** /**
@@ -460,7 +460,7 @@ interface Database<T> {
* <p> * <p>
* Locking: write. * Locking: write.
*/ */
RetentionUpdate getRetentionUpdate(T txn, ContactId c, long maxLatency) RetentionUpdate getRetentionUpdate(T txn, ContactId c, int maxLatency)
throws DbException; throws DbException;
/** /**
@@ -499,7 +499,7 @@ interface Database<T> {
* Locking: write. * Locking: write.
*/ */
SubscriptionUpdate getSubscriptionUpdate(T txn, ContactId c, SubscriptionUpdate getSubscriptionUpdate(T txn, ContactId c,
long maxLatency) throws DbException; int maxLatency) throws DbException;
/** /**
* Returns a collection of transport acks for the given contact, or null if * Returns a collection of transport acks for the given contact, or null if
@@ -511,11 +511,11 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Returns the maximum latencies of all local transports. * Returns the maximum latencies of all supported transports.
* <p> * <p>
* Locking: read. * Locking: read.
*/ */
Map<TransportId, Long> getTransportLatencies(T txn) throws DbException; Map<TransportId, Integer> getTransportLatencies(T txn) throws DbException;
/** /**
* Returns a collection of transport updates for the given contact and * Returns a collection of transport updates for the given contact and
@@ -525,7 +525,7 @@ interface Database<T> {
* Locking: write. * Locking: write.
*/ */
Collection<TransportUpdate> getTransportUpdates(T txn, ContactId c, Collection<TransportUpdate> getTransportUpdates(T txn, ContactId c,
long maxLatency) throws DbException; int maxLatency) throws DbException;
/** /**
* Returns the number of unread messages in each subscribed group. * Returns the number of unread messages in each subscribed group.
@@ -798,6 +798,6 @@ interface Database<T> {
* <p> * <p>
* Locking: write. * Locking: write.
*/ */
void updateExpiryTime(T txn, ContactId c, MessageId m, long maxLatency) void updateExpiryTime(T txn, ContactId c, MessageId m, int maxLatency)
throws DbException; throws DbException;
} }

View File

@@ -314,7 +314,7 @@ DatabaseCleaner.Callback {
} }
} }
public boolean addTransport(TransportId t, long maxLatency) public boolean addTransport(TransportId t, int maxLatency)
throws DbException { throws DbException {
boolean added; boolean added;
lock.writeLock().lock(); lock.writeLock().lock();
@@ -357,7 +357,7 @@ DatabaseCleaner.Callback {
} }
public Collection<byte[]> generateBatch(ContactId c, int maxLength, public Collection<byte[]> generateBatch(ContactId c, int maxLength,
long maxLatency) throws DbException { int maxLatency) throws DbException {
Collection<MessageId> ids; Collection<MessageId> ids;
List<byte[]> messages = new ArrayList<byte[]>(); List<byte[]> messages = new ArrayList<byte[]>();
lock.writeLock().lock(); lock.writeLock().lock();
@@ -384,7 +384,7 @@ DatabaseCleaner.Callback {
return Collections.unmodifiableList(messages); return Collections.unmodifiableList(messages);
} }
public Offer generateOffer(ContactId c, int maxMessages, long maxLatency) public Offer generateOffer(ContactId c, int maxMessages, int maxLatency)
throws DbException { throws DbException {
Collection<MessageId> ids; Collection<MessageId> ids;
lock.writeLock().lock(); lock.writeLock().lock();
@@ -432,7 +432,7 @@ DatabaseCleaner.Callback {
} }
public Collection<byte[]> generateRequestedBatch(ContactId c, int maxLength, public Collection<byte[]> generateRequestedBatch(ContactId c, int maxLength,
long maxLatency) throws DbException { int maxLatency) throws DbException {
Collection<MessageId> ids; Collection<MessageId> ids;
List<byte[]> messages = new ArrayList<byte[]>(); List<byte[]> messages = new ArrayList<byte[]>();
lock.writeLock().lock(); lock.writeLock().lock();
@@ -478,7 +478,7 @@ DatabaseCleaner.Callback {
} }
} }
public RetentionUpdate generateRetentionUpdate(ContactId c, long maxLatency) public RetentionUpdate generateRetentionUpdate(ContactId c, int maxLatency)
throws DbException { throws DbException {
lock.writeLock().lock(); lock.writeLock().lock();
try { try {
@@ -519,7 +519,7 @@ DatabaseCleaner.Callback {
} }
public SubscriptionUpdate generateSubscriptionUpdate(ContactId c, public SubscriptionUpdate generateSubscriptionUpdate(ContactId c,
long maxLatency) throws DbException { int maxLatency) throws DbException {
lock.writeLock().lock(); lock.writeLock().lock();
try { try {
T txn = db.startTransaction(); T txn = db.startTransaction();
@@ -560,7 +560,7 @@ DatabaseCleaner.Callback {
} }
public Collection<TransportUpdate> generateTransportUpdates(ContactId c, public Collection<TransportUpdate> generateTransportUpdates(ContactId c,
long maxLatency) throws DbException { int maxLatency) throws DbException {
lock.writeLock().lock(); lock.writeLock().lock();
try { try {
T txn = db.startTransaction(); T txn = db.startTransaction();
@@ -932,12 +932,13 @@ DatabaseCleaner.Callback {
} }
} }
public Map<TransportId, Long> getTransportLatencies() throws DbException { public Map<TransportId, Integer> getTransportLatencies()
throws DbException {
lock.readLock().lock(); lock.readLock().lock();
try { try {
T txn = db.startTransaction(); T txn = db.startTransaction();
try { try {
Map<TransportId, Long> latencies = Map<TransportId, Integer> latencies =
db.getTransportLatencies(txn); db.getTransportLatencies(txn);
db.commitTransaction(txn); db.commitTransaction(txn);
return latencies; return latencies;

View File

@@ -11,13 +11,12 @@ class ExponentialBackoff {
* transmissions increases exponentially. If the expiry time would * transmissions increases exponentially. If the expiry time would
* be greater than Long.MAX_VALUE, Long.MAX_VALUE is returned. * be greater than Long.MAX_VALUE, Long.MAX_VALUE is returned.
*/ */
static long calculateExpiry(long now, long maxLatency, int txCount) { static long calculateExpiry(long now, int maxLatency, int txCount) {
if(now < 0) throw new IllegalArgumentException(); if(now < 0) throw new IllegalArgumentException();
if(maxLatency <= 0) throw new IllegalArgumentException(); if(maxLatency <= 0) throw new IllegalArgumentException();
if(txCount < 0) throw new IllegalArgumentException(); if(txCount < 0) throw new IllegalArgumentException();
// The maximum round-trip time is twice the maximum latency // The maximum round-trip time is twice the maximum latency
long roundTrip = maxLatency * 2; long roundTrip = maxLatency * 2L;
if(roundTrip < 0) return Long.MAX_VALUE;
// The interval between transmissions is roundTrip * 2 ^ txCount // The interval between transmissions is roundTrip * 2 ^ txCount
for(int i = 0; i < txCount; i++) { for(int i = 0; i < txCount; i++) {
roundTrip <<= 1; roundTrip <<= 1;

View File

@@ -81,34 +81,18 @@ class H2Database extends JdbcDatabase {
} }
} }
@Override
protected Connection createConnection() throws SQLException { protected Connection createConnection() throws SQLException {
byte[] key = config.getEncryptionKey(); byte[] key = config.getEncryptionKey();
if(key == null) throw new IllegalStateException(); if(key == null) throw new IllegalStateException();
char[] password = encodePassword(key);
Properties props = new Properties(); Properties props = new Properties();
props.setProperty("user", "user"); props.setProperty("user", "user");
props.put("password", password); // Separate the file password from the user password with a space
try { props.put("password", StringUtils.toHexString(key) + " password");
return DriverManager.getConnection(url, props); return DriverManager.getConnection(url, props);
} finally {
for(int i = 0; i < password.length; i++) password[i] = 0;
}
}
private char[] encodePassword(byte[] key) {
// The database password is the hex-encoded key
char[] hex = StringUtils.toHexChars(key);
// Separate the database password from the user password with a space
char[] user = "password".toCharArray();
char[] combined = new char[hex.length + 1 + user.length];
System.arraycopy(hex, 0, combined, 0, hex.length);
combined[hex.length] = ' ';
System.arraycopy(user, 0, combined, hex.length + 1, user.length);
// Erase the hex-encoded key
for(int i = 0; i < hex.length; i++) hex[i] = 0;
return combined;
} }
@Override
protected void flushBuffersToDisk(Statement s) throws SQLException { protected void flushBuffersToDisk(Statement s) throws SQLException {
// FIXME: Remove this after implementing BTPv2? // FIXME: Remove this after implementing BTPv2?
s.execute("CHECKPOINT SYNC"); s.execute("CHECKPOINT SYNC");

View File

@@ -65,8 +65,8 @@ import org.briarproject.api.transport.TemporarySecret;
*/ */
abstract class JdbcDatabase implements Database<Connection> { abstract class JdbcDatabase implements Database<Connection> {
private static final int SCHEMA_VERSION = 6; private static final int SCHEMA_VERSION = 7;
private static final int MIN_SCHEMA_VERSION = 5; private static final int MIN_SCHEMA_VERSION = 7;
private static final String CREATE_SETTINGS = private static final String CREATE_SETTINGS =
"CREATE TABLE settings" "CREATE TABLE settings"
@@ -216,7 +216,7 @@ abstract class JdbcDatabase implements Database<Connection> {
private static final String CREATE_TRANSPORTS = private static final String CREATE_TRANSPORTS =
"CREATE TABLE transports" "CREATE TABLE transports"
+ " (transportId VARCHAR NOT NULL," + " (transportId VARCHAR NOT NULL,"
+ " maxLatency BIGINT NOT NULL," + " maxLatency INT NOT NULL,"
+ " PRIMARY KEY (transportId))"; + " PRIMARY KEY (transportId))";
private static final String CREATE_TRANSPORT_CONFIGS = private static final String CREATE_TRANSPORT_CONFIGS =
@@ -897,7 +897,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
public boolean addTransport(Connection txn, TransportId t, long maxLatency) public boolean addTransport(Connection txn, TransportId t, int maxLatency)
throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
@@ -2055,7 +2055,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
public RetentionUpdate getRetentionUpdate(Connection txn, ContactId c, public RetentionUpdate getRetentionUpdate(Connection txn, ContactId c,
long maxLatency) throws DbException { int maxLatency) throws DbException {
long now = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
@@ -2233,7 +2233,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
public SubscriptionUpdate getSubscriptionUpdate(Connection txn, ContactId c, public SubscriptionUpdate getSubscriptionUpdate(Connection txn, ContactId c,
long maxLatency) throws DbException { int maxLatency) throws DbException {
long now = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
@@ -2327,7 +2327,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
} }
public Map<TransportId, Long> getTransportLatencies(Connection txn) public Map<TransportId, Integer> getTransportLatencies(Connection txn)
throws DbException { throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
@@ -2335,10 +2335,11 @@ abstract class JdbcDatabase implements Database<Connection> {
String sql = "SELECT transportId, maxLatency FROM transports"; String sql = "SELECT transportId, maxLatency FROM transports";
ps = txn.prepareStatement(sql); ps = txn.prepareStatement(sql);
rs = ps.executeQuery(); rs = ps.executeQuery();
Map<TransportId, Long> latencies = new HashMap<TransportId, Long>(); Map<TransportId, Integer> latencies =
new HashMap<TransportId, Integer>();
while(rs.next()){ while(rs.next()){
TransportId id = new TransportId(rs.getString(1)); TransportId id = new TransportId(rs.getString(1));
latencies.put(id, rs.getLong(2)); latencies.put(id, rs.getInt(2));
} }
rs.close(); rs.close();
ps.close(); ps.close();
@@ -2351,7 +2352,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
public Collection<TransportUpdate> getTransportUpdates(Connection txn, public Collection<TransportUpdate> getTransportUpdates(Connection txn,
ContactId c, long maxLatency) throws DbException { ContactId c, int maxLatency) throws DbException {
long now = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
@@ -3332,7 +3333,7 @@ abstract class JdbcDatabase implements Database<Connection> {
} }
public void updateExpiryTime(Connection txn, ContactId c, MessageId m, public void updateExpiryTime(Connection txn, ContactId c, MessageId m,
long maxLatency) throws DbException { int maxLatency) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
ResultSet rs = null; ResultSet rs = null;
try { try {

View File

@@ -29,9 +29,7 @@ import org.briarproject.api.serial.ReaderFactory;
import org.briarproject.api.serial.Writer; import org.briarproject.api.serial.Writer;
import org.briarproject.api.serial.WriterFactory; import org.briarproject.api.serial.WriterFactory;
import org.briarproject.api.system.Clock; import org.briarproject.api.system.Clock;
import org.briarproject.api.transport.StreamReader;
import org.briarproject.api.transport.StreamReaderFactory; import org.briarproject.api.transport.StreamReaderFactory;
import org.briarproject.api.transport.StreamWriter;
import org.briarproject.api.transport.StreamWriterFactory; import org.briarproject.api.transport.StreamWriterFactory;
/** A connection thread for the peer being Alice in the invitation protocol. */ /** A connection thread for the peer being Alice in the invitation protocol. */
@@ -51,9 +49,9 @@ class AliceConnector extends Connector {
Map<TransportId, TransportProperties> localProps, Map<TransportId, TransportProperties> localProps,
PseudoRandom random) { PseudoRandom random) {
super(crypto, db, readerFactory, writerFactory, streamReaderFactory, super(crypto, db, readerFactory, writerFactory, streamReaderFactory,
streamWriterFactory, authorFactory, groupFactory, streamWriterFactory, authorFactory, groupFactory, keyManager,
keyManager, connectionManager, clock, reuseConnection, group, connectionManager, clock, reuseConnection, group, plugin,
plugin, localAuthor, localProps, random); localAuthor, localProps, random);
} }
@Override @Override
@@ -130,15 +128,16 @@ class AliceConnector extends Connector {
// Confirmation succeeded - upgrade to a secure connection // Confirmation succeeded - upgrade to a secure connection
if(LOG.isLoggable(INFO)) if(LOG.isLoggable(INFO))
LOG.info(pluginName + " confirmation succeeded"); LOG.info(pluginName + " confirmation succeeded");
int maxFrameLength = conn.getReader().getMaxFrameLength(); // Create the readers
StreamReader streamReader = InputStream streamReader =
streamReaderFactory.createInvitationStreamReader(in, streamReaderFactory.createInvitationStreamReader(in,
maxFrameLength, secret, false); // Bob's stream secret, false); // Bob's stream
r = readerFactory.createReader(streamReader.getInputStream()); r = readerFactory.createReader(streamReader);
StreamWriter streamWriter = // Create the writers
OutputStream streamWriter =
streamWriterFactory.createInvitationStreamWriter(out, streamWriterFactory.createInvitationStreamWriter(out,
maxFrameLength, secret, true); // Alice's stream secret, true); // Alice's stream
w = writerFactory.createWriter(streamWriter.getOutputStream()); w = writerFactory.createWriter(streamWriter);
// Derive the invitation nonces // Derive the invitation nonces
byte[][] nonces = crypto.deriveInvitationNonces(secret); byte[][] nonces = crypto.deriveInvitationNonces(secret);
byte[] aliceNonce = nonces[0], bobNonce = nonces[1]; byte[] aliceNonce = nonces[0], bobNonce = nonces[1];

View File

@@ -29,9 +29,7 @@ import org.briarproject.api.serial.ReaderFactory;
import org.briarproject.api.serial.Writer; import org.briarproject.api.serial.Writer;
import org.briarproject.api.serial.WriterFactory; import org.briarproject.api.serial.WriterFactory;
import org.briarproject.api.system.Clock; import org.briarproject.api.system.Clock;
import org.briarproject.api.transport.StreamReader;
import org.briarproject.api.transport.StreamReaderFactory; import org.briarproject.api.transport.StreamReaderFactory;
import org.briarproject.api.transport.StreamWriter;
import org.briarproject.api.transport.StreamWriterFactory; import org.briarproject.api.transport.StreamWriterFactory;
/** A connection thread for the peer being Bob in the invitation protocol. */ /** A connection thread for the peer being Bob in the invitation protocol. */
@@ -51,9 +49,9 @@ class BobConnector extends Connector {
Map<TransportId, TransportProperties> localProps, Map<TransportId, TransportProperties> localProps,
PseudoRandom random) { PseudoRandom random) {
super(crypto, db, readerFactory, writerFactory, streamReaderFactory, super(crypto, db, readerFactory, writerFactory, streamReaderFactory,
streamWriterFactory, authorFactory, groupFactory, streamWriterFactory, authorFactory, groupFactory, keyManager,
keyManager, connectionManager, clock, reuseConnection, group, connectionManager, clock, reuseConnection, group, plugin,
plugin, localAuthor, localProps, random); localAuthor, localProps, random);
} }
@Override @Override
@@ -130,15 +128,16 @@ class BobConnector extends Connector {
// Confirmation succeeded - upgrade to a secure connection // Confirmation succeeded - upgrade to a secure connection
if(LOG.isLoggable(INFO)) if(LOG.isLoggable(INFO))
LOG.info(pluginName + " confirmation succeeded"); LOG.info(pluginName + " confirmation succeeded");
int maxFrameLength = conn.getReader().getMaxFrameLength(); // Create the readers
StreamReader streamReader = InputStream streamReader =
streamReaderFactory.createInvitationStreamReader(in, streamReaderFactory.createInvitationStreamReader(in,
maxFrameLength, secret, true); // Alice's stream secret, true); // Alice's stream
r = readerFactory.createReader(streamReader.getInputStream()); r = readerFactory.createReader(streamReader);
StreamWriter streamWriter = // Create the writers
OutputStream streamWriter =
streamWriterFactory.createInvitationStreamWriter(out, streamWriterFactory.createInvitationStreamWriter(out,
maxFrameLength, secret, false); // Bob's stream secret, false); // Bob's stream
w = writerFactory.createWriter(streamWriter.getOutputStream()); w = writerFactory.createWriter(streamWriter);
// Derive the nonces // Derive the nonces
byte[][] nonces = crypto.deriveInvitationNonces(secret); byte[][] nonces = crypto.deriveInvitationNonces(secret);
byte[] aliceNonce = nonces[0], bobNonce = nonces[1]; byte[] aliceNonce = nonces[0], bobNonce = nonces[1];

View File

@@ -285,7 +285,7 @@ abstract class Connector extends Thread {
db.setRemoteProperties(contactId, remoteProps); db.setRemoteProperties(contactId, remoteProps);
// Create an endpoint for each transport shared with the contact // Create an endpoint for each transport shared with the contact
List<TransportId> ids = new ArrayList<TransportId>(); List<TransportId> ids = new ArrayList<TransportId>();
Map<TransportId, Long> latencies = db.getTransportLatencies(); Map<TransportId, Integer> latencies = db.getTransportLatencies();
for(TransportId id : localProps.keySet()) { for(TransportId id : localProps.keySet()) {
if(latencies.containsKey(id) && remoteProps.containsKey(id)) if(latencies.containsKey(id) && remoteProps.containsKey(id))
ids.add(id); ids.add(id);
@@ -296,7 +296,7 @@ abstract class Connector extends Thread {
for(int i = 0; i < size; i++) { for(int i = 0; i < size; i++) {
TransportId id = ids.get(i); TransportId id = ids.get(i);
Endpoint ep = new Endpoint(contactId, id, epoch, alice); Endpoint ep = new Endpoint(contactId, id, epoch, alice);
long maxLatency = latencies.get(id); int maxLatency = latencies.get(id);
try { try {
db.addEndpoint(ep); db.addEndpoint(ep);
} catch(NoSuchTransportException e) { } catch(NoSuchTransportException e) {

View File

@@ -1,11 +1,11 @@
package org.briarproject.messaging; package org.briarproject.messaging;
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.api.messaging.MessagingConstants.MAX_PACKET_LENGTH; import static org.briarproject.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -36,7 +36,6 @@ import org.briarproject.api.messaging.Ack;
import org.briarproject.api.messaging.MessagingSession; import org.briarproject.api.messaging.MessagingSession;
import org.briarproject.api.messaging.Offer; import org.briarproject.api.messaging.Offer;
import org.briarproject.api.messaging.PacketWriter; import org.briarproject.api.messaging.PacketWriter;
import org.briarproject.api.messaging.PacketWriterFactory;
import org.briarproject.api.messaging.Request; import org.briarproject.api.messaging.Request;
import org.briarproject.api.messaging.RetentionAck; import org.briarproject.api.messaging.RetentionAck;
import org.briarproject.api.messaging.RetentionUpdate; import org.briarproject.api.messaging.RetentionUpdate;
@@ -44,16 +43,18 @@ import org.briarproject.api.messaging.SubscriptionAck;
import org.briarproject.api.messaging.SubscriptionUpdate; import org.briarproject.api.messaging.SubscriptionUpdate;
import org.briarproject.api.messaging.TransportAck; import org.briarproject.api.messaging.TransportAck;
import org.briarproject.api.messaging.TransportUpdate; import org.briarproject.api.messaging.TransportUpdate;
import org.briarproject.api.system.Clock;
/** /**
* An outgoing {@link org.briarproject.api.messaging.MessagingSession * An outgoing {@link org.briarproject.api.messaging.MessagingSession
* MessagingSession} suitable for duplex transports. The session offers * MessagingSession} suitable for duplex transports. The session offers
* messages before sending them, keeps its output stream open when there are no * messages before sending them, keeps its output stream open when there are no
* more packets to send, and reacts to events that make packets available to * packets to send, and reacts to events that make packets available to send.
* send.
*/ */
class DuplexOutgoingSession implements MessagingSession, EventListener { class DuplexOutgoingSession implements MessagingSession, EventListener {
// Check for retransmittable packets 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());
@@ -65,27 +66,32 @@ class DuplexOutgoingSession implements MessagingSession, EventListener {
private final DatabaseComponent db; private final DatabaseComponent db;
private final Executor dbExecutor; private final Executor dbExecutor;
private final EventBus eventBus; private final EventBus eventBus;
private final Clock clock;
private final ContactId contactId; private final ContactId contactId;
private final TransportId transportId; private final TransportId transportId;
private final long maxLatency; private final int maxLatency, maxIdleTime;
private final OutputStream out;
private final PacketWriter packetWriter; private final PacketWriter packetWriter;
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks; private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
// The following must only be accessed on the writer thread
private long nextKeepalive = 0, nextRetxQuery = 0;
private boolean dataToFlush = true;
private volatile boolean interrupted = false; private volatile boolean interrupted = false;
DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor, DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
EventBus eventBus, PacketWriterFactory packetWriterFactory, EventBus eventBus, Clock clock, ContactId contactId,
ContactId contactId, TransportId transportId, long maxLatency, TransportId transportId, int maxLatency, int maxIdleTime,
OutputStream out) { PacketWriter packetWriter) {
this.db = db; this.db = db;
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
this.eventBus = eventBus; this.eventBus = eventBus;
this.clock = clock;
this.contactId = contactId; this.contactId = contactId;
this.transportId = transportId; this.transportId = transportId;
this.maxLatency = maxLatency; this.maxLatency = maxLatency;
this.out = out; this.maxIdleTime = maxIdleTime;
packetWriter = packetWriterFactory.createPacketWriter(out); this.packetWriter = packetWriter;
writerTasks = new LinkedBlockingQueue<ThrowingRunnable<IOException>>(); writerTasks = new LinkedBlockingQueue<ThrowingRunnable<IOException>>();
} }
@@ -103,16 +109,50 @@ class DuplexOutgoingSession implements MessagingSession, EventListener {
dbExecutor.execute(new GenerateBatch()); dbExecutor.execute(new GenerateBatch());
dbExecutor.execute(new GenerateOffer()); dbExecutor.execute(new GenerateOffer());
dbExecutor.execute(new GenerateRequest()); dbExecutor.execute(new GenerateRequest());
long now = clock.currentTimeMillis();
nextKeepalive = now + maxIdleTime;
nextRetxQuery = now + RETX_QUERY_INTERVAL;
// Write packets until interrupted // Write packets until interrupted
try { try {
while(!interrupted) { while(!interrupted) {
// Flush the stream if it's going to be idle // Work out how long we should wait for a packet
if(writerTasks.isEmpty()) out.flush(); now = clock.currentTimeMillis();
ThrowingRunnable<IOException> task = writerTasks.take(); long wait = Math.min(nextKeepalive, nextRetxQuery) - now;
if(task == CLOSE) break; if(wait < 0) wait = 0;
task.run(); // Flush any unflushed data if we're going to wait
if(wait > 0 && dataToFlush && writerTasks.isEmpty()) {
packetWriter.flush();
dataToFlush = false;
nextKeepalive = now + maxIdleTime;
}
// Wait for a packet
ThrowingRunnable<IOException> task = writerTasks.poll(wait,
MILLISECONDS);
if(task == null) {
now = clock.currentTimeMillis();
if(now >= nextRetxQuery) {
// Check for retransmittable packets
dbExecutor.execute(new GenerateTransportUpdates());
dbExecutor.execute(new GenerateSubscriptionUpdate());
dbExecutor.execute(new GenerateRetentionUpdate());
dbExecutor.execute(new GenerateBatch());
dbExecutor.execute(new GenerateOffer());
nextRetxQuery = now + RETX_QUERY_INTERVAL;
}
if(now >= nextKeepalive) {
// Flush the stream to keep it alive
packetWriter.flush();
dataToFlush = false;
nextKeepalive = now + maxIdleTime;
}
} else if(task == CLOSE) {
break;
} else {
task.run();
dataToFlush = true;
}
} }
out.flush(); if(dataToFlush) packetWriter.flush();
} catch(InterruptedException e) { } catch(InterruptedException e) {
LOG.info("Interrupted while waiting for a packet to write"); LOG.info("Interrupted while waiting for a packet to write");
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();

View File

@@ -3,7 +3,6 @@ package org.briarproject.messaging;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -25,7 +24,6 @@ import org.briarproject.api.messaging.MessageVerifier;
import org.briarproject.api.messaging.MessagingSession; import org.briarproject.api.messaging.MessagingSession;
import org.briarproject.api.messaging.Offer; import org.briarproject.api.messaging.Offer;
import org.briarproject.api.messaging.PacketReader; import org.briarproject.api.messaging.PacketReader;
import org.briarproject.api.messaging.PacketReaderFactory;
import org.briarproject.api.messaging.Request; import org.briarproject.api.messaging.Request;
import org.briarproject.api.messaging.RetentionAck; import org.briarproject.api.messaging.RetentionAck;
import org.briarproject.api.messaging.RetentionUpdate; import org.briarproject.api.messaging.RetentionUpdate;
@@ -56,9 +54,8 @@ class IncomingSession implements MessagingSession, EventListener {
IncomingSession(DatabaseComponent db, Executor dbExecutor, IncomingSession(DatabaseComponent db, Executor dbExecutor,
Executor cryptoExecutor, EventBus eventBus, Executor cryptoExecutor, EventBus eventBus,
MessageVerifier messageVerifier, MessageVerifier messageVerifier, ContactId contactId,
PacketReaderFactory packetReaderFactory, ContactId contactId, TransportId transportId, PacketReader packetReader) {
TransportId transportId, InputStream in) {
this.db = db; this.db = db;
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
this.cryptoExecutor = cryptoExecutor; this.cryptoExecutor = cryptoExecutor;
@@ -66,7 +63,7 @@ class IncomingSession implements MessagingSession, EventListener {
this.messageVerifier = messageVerifier; this.messageVerifier = messageVerifier;
this.contactId = contactId; this.contactId = contactId;
this.transportId = transportId; this.transportId = transportId;
packetReader = packetReaderFactory.createPacketReader(in); this.packetReader = packetReader;
} }
public void run() throws IOException { public void run() throws IOException {

View File

@@ -15,8 +15,11 @@ import org.briarproject.api.event.EventBus;
import org.briarproject.api.messaging.MessageVerifier; import org.briarproject.api.messaging.MessageVerifier;
import org.briarproject.api.messaging.MessagingSession; import org.briarproject.api.messaging.MessagingSession;
import org.briarproject.api.messaging.MessagingSessionFactory; import org.briarproject.api.messaging.MessagingSessionFactory;
import org.briarproject.api.messaging.PacketReader;
import org.briarproject.api.messaging.PacketReaderFactory; import org.briarproject.api.messaging.PacketReaderFactory;
import org.briarproject.api.messaging.PacketWriter;
import org.briarproject.api.messaging.PacketWriterFactory; import org.briarproject.api.messaging.PacketWriterFactory;
import org.briarproject.api.system.Clock;
class MessagingSessionFactoryImpl implements MessagingSessionFactory { class MessagingSessionFactoryImpl implements MessagingSessionFactory {
@@ -24,6 +27,7 @@ class MessagingSessionFactoryImpl implements MessagingSessionFactory {
private final Executor dbExecutor, cryptoExecutor; private final Executor dbExecutor, cryptoExecutor;
private final MessageVerifier messageVerifier; private final MessageVerifier messageVerifier;
private final EventBus eventBus; private final EventBus eventBus;
private final Clock clock;
private final PacketReaderFactory packetReaderFactory; private final PacketReaderFactory packetReaderFactory;
private final PacketWriterFactory packetWriterFactory; private final PacketWriterFactory packetWriterFactory;
@@ -31,7 +35,7 @@ class MessagingSessionFactoryImpl implements MessagingSessionFactory {
MessagingSessionFactoryImpl(DatabaseComponent db, MessagingSessionFactoryImpl(DatabaseComponent db,
@DatabaseExecutor Executor dbExecutor, @DatabaseExecutor Executor dbExecutor,
@CryptoExecutor Executor cryptoExecutor, @CryptoExecutor Executor cryptoExecutor,
MessageVerifier messageVerifier, EventBus eventBus, MessageVerifier messageVerifier, EventBus eventBus, Clock clock,
PacketReaderFactory packetReaderFactory, PacketReaderFactory packetReaderFactory,
PacketWriterFactory packetWriterFactory) { PacketWriterFactory packetWriterFactory) {
this.db = db; this.db = db;
@@ -39,21 +43,29 @@ class MessagingSessionFactoryImpl implements MessagingSessionFactory {
this.cryptoExecutor = cryptoExecutor; this.cryptoExecutor = cryptoExecutor;
this.messageVerifier = messageVerifier; this.messageVerifier = messageVerifier;
this.eventBus = eventBus; this.eventBus = eventBus;
this.clock = clock;
this.packetReaderFactory = packetReaderFactory; this.packetReaderFactory = packetReaderFactory;
this.packetWriterFactory = packetWriterFactory; this.packetWriterFactory = packetWriterFactory;
} }
public MessagingSession createIncomingSession(ContactId c, TransportId t, public MessagingSession createIncomingSession(ContactId c, TransportId t,
InputStream in) { InputStream in) {
PacketReader packetReader = packetReaderFactory.createPacketReader(in);
return new IncomingSession(db, dbExecutor, cryptoExecutor, eventBus, return new IncomingSession(db, dbExecutor, cryptoExecutor, eventBus,
messageVerifier, packetReaderFactory, c, t, in); messageVerifier, c, t, packetReader);
} }
public MessagingSession createOutgoingSession(ContactId c, TransportId t, public MessagingSession createSimplexOutgoingSession(ContactId c,
long maxLatency, boolean duplex, OutputStream out) { TransportId t, int maxLatency, OutputStream out) {
if(duplex) return new DuplexOutgoingSession(db, dbExecutor, eventBus, PacketWriter packetWriter = packetWriterFactory.createPacketWriter(out);
packetWriterFactory, c, t, maxLatency, out); return new SimplexOutgoingSession(db, dbExecutor, eventBus, c, t,
else return new SimplexOutgoingSession(db, dbExecutor, eventBus, maxLatency, packetWriter);
packetWriterFactory, c, t, maxLatency, out); }
public MessagingSession createDuplexOutgoingSession(ContactId c,
TransportId t, int maxLatency, int maxIdleTime, OutputStream out) {
PacketWriter packetWriter = packetWriterFactory.createPacketWriter(out);
return new DuplexOutgoingSession(db, dbExecutor, eventBus, clock, c, t,
maxLatency, maxIdleTime, packetWriter);
} }
} }

View File

@@ -143,4 +143,8 @@ class PacketWriterImpl implements PacketWriter {
w.writeInteger(u.getVersion()); w.writeInteger(u.getVersion());
w.writeStructEnd(); w.writeStructEnd();
} }
public void flush() throws IOException {
out.flush();
}
} }

View File

@@ -5,7 +5,6 @@ import static java.util.logging.Level.WARNING;
import static org.briarproject.api.messaging.MessagingConstants.MAX_PACKET_LENGTH; import static org.briarproject.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -26,7 +25,6 @@ import org.briarproject.api.event.TransportRemovedEvent;
import org.briarproject.api.messaging.Ack; import org.briarproject.api.messaging.Ack;
import org.briarproject.api.messaging.MessagingSession; import org.briarproject.api.messaging.MessagingSession;
import org.briarproject.api.messaging.PacketWriter; import org.briarproject.api.messaging.PacketWriter;
import org.briarproject.api.messaging.PacketWriterFactory;
import org.briarproject.api.messaging.RetentionAck; import org.briarproject.api.messaging.RetentionAck;
import org.briarproject.api.messaging.RetentionUpdate; import org.briarproject.api.messaging.RetentionUpdate;
import org.briarproject.api.messaging.SubscriptionAck; import org.briarproject.api.messaging.SubscriptionAck;
@@ -55,8 +53,7 @@ class SimplexOutgoingSession implements MessagingSession, EventListener {
private final EventBus eventBus; private final EventBus eventBus;
private final ContactId contactId; private final ContactId contactId;
private final TransportId transportId; private final TransportId transportId;
private final long maxLatency; private final int maxLatency;
private final OutputStream out;
private final PacketWriter packetWriter; private final PacketWriter packetWriter;
private final AtomicInteger outstandingQueries; private final AtomicInteger outstandingQueries;
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks; private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
@@ -64,17 +61,15 @@ class SimplexOutgoingSession implements MessagingSession, EventListener {
private volatile boolean interrupted = false; private volatile boolean interrupted = false;
SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor, SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
EventBus eventBus, PacketWriterFactory packetWriterFactory, EventBus eventBus, ContactId contactId, TransportId transportId,
ContactId contactId, TransportId transportId, long maxLatency, int maxLatency, PacketWriter packetWriter) {
OutputStream out) {
this.db = db; this.db = db;
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
this.eventBus = eventBus; this.eventBus = eventBus;
this.contactId = contactId; this.contactId = contactId;
this.transportId = transportId; this.transportId = transportId;
this.maxLatency = maxLatency; this.maxLatency = maxLatency;
this.out = out; this.packetWriter = packetWriter;
packetWriter = packetWriterFactory.createPacketWriter(out);
outstandingQueries = new AtomicInteger(8); // One per type of packet outstandingQueries = new AtomicInteger(8); // One per type of packet
writerTasks = new LinkedBlockingQueue<ThrowingRunnable<IOException>>(); writerTasks = new LinkedBlockingQueue<ThrowingRunnable<IOException>>();
} }
@@ -98,7 +93,7 @@ class SimplexOutgoingSession implements MessagingSession, EventListener {
if(task == CLOSE) break; if(task == CLOSE) break;
task.run(); task.run();
} }
out.flush(); packetWriter.flush();
} catch(InterruptedException e) { } catch(InterruptedException e) {
LOG.info("Interrupted while waiting for a packet to write"); LOG.info("Interrupted while waiting for a packet to write");
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();

View File

@@ -6,6 +6,7 @@ import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -24,12 +25,9 @@ import org.briarproject.api.plugins.TransportConnectionReader;
import org.briarproject.api.plugins.TransportConnectionWriter; import org.briarproject.api.plugins.TransportConnectionWriter;
import org.briarproject.api.plugins.duplex.DuplexTransportConnection; import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
import org.briarproject.api.transport.StreamContext; import org.briarproject.api.transport.StreamContext;
import org.briarproject.api.transport.StreamReader;
import org.briarproject.api.transport.StreamReaderFactory; import org.briarproject.api.transport.StreamReaderFactory;
import org.briarproject.api.transport.StreamWriter;
import org.briarproject.api.transport.StreamWriterFactory; import org.briarproject.api.transport.StreamWriterFactory;
import org.briarproject.api.transport.TagRecogniser; import org.briarproject.api.transport.TagRecogniser;
import org.briarproject.util.ByteUtils;
class ConnectionManagerImpl implements ConnectionManager { class ConnectionManagerImpl implements ConnectionManager {
@@ -96,28 +94,28 @@ class ConnectionManagerImpl implements ConnectionManager {
private MessagingSession createIncomingSession(StreamContext ctx, private MessagingSession createIncomingSession(StreamContext ctx,
TransportConnectionReader r) throws IOException { TransportConnectionReader r) throws IOException {
try { InputStream streamReader = streamReaderFactory.createStreamReader(
StreamReader streamReader = streamReaderFactory.createStreamReader( r.getInputStream(), ctx);
r.getInputStream(), r.getMaxFrameLength(), ctx); return messagingSessionFactory.createIncomingSession(
return messagingSessionFactory.createIncomingSession( ctx.getContactId(), ctx.getTransportId(), streamReader);
ctx.getContactId(), ctx.getTransportId(),
streamReader.getInputStream());
} finally {
ByteUtils.erase(ctx.getSecret());
}
} }
private MessagingSession createOutgoingSession(StreamContext ctx, private MessagingSession createSimplexOutgoingSession(StreamContext ctx,
TransportConnectionWriter w, boolean duplex) throws IOException { TransportConnectionWriter w) throws IOException {
try { OutputStream streamWriter = streamWriterFactory.createStreamWriter(
StreamWriter streamWriter = streamWriterFactory.createStreamWriter( w.getOutputStream(), ctx);
w.getOutputStream(), w.getMaxFrameLength(), ctx); return messagingSessionFactory.createSimplexOutgoingSession(
return messagingSessionFactory.createOutgoingSession( ctx.getContactId(), ctx.getTransportId(), w.getMaxLatency(),
ctx.getContactId(), ctx.getTransportId(), w.getMaxLatency(), streamWriter);
duplex, streamWriter.getOutputStream()); }
} finally {
ByteUtils.erase(ctx.getSecret()); private MessagingSession createDuplexOutgoingSession(StreamContext ctx,
} TransportConnectionWriter w) throws IOException {
OutputStream streamWriter = streamWriterFactory.createStreamWriter(
w.getOutputStream(), ctx);
return messagingSessionFactory.createDuplexOutgoingSession(
ctx.getContactId(), ctx.getTransportId(), w.getMaxLatency(),
w.getMaxIdleTime(), streamWriter);
} }
private class ManageIncomingSimplexConnection implements Runnable { private class ManageIncomingSimplexConnection implements Runnable {
@@ -199,7 +197,7 @@ class ConnectionManagerImpl implements ConnectionManager {
connectionRegistry.registerConnection(contactId, transportId); connectionRegistry.registerConnection(contactId, transportId);
try { try {
// Create and run the outgoing session // Create and run the outgoing session
createOutgoingSession(ctx, writer, false).run(); createSimplexOutgoingSession(ctx, writer).run();
disposeWriter(false); disposeWriter(false);
} catch(IOException e) { } catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
@@ -287,7 +285,7 @@ class ConnectionManagerImpl implements ConnectionManager {
} }
try { try {
// Create and run the outgoing session // Create and run the outgoing session
outgoingSession = createOutgoingSession(ctx, writer, true); outgoingSession = createDuplexOutgoingSession(ctx, writer);
outgoingSession.run(); outgoingSession.run();
disposeWriter(false); disposeWriter(false);
} catch(IOException e) { } catch(IOException e) {
@@ -353,7 +351,7 @@ class ConnectionManagerImpl implements ConnectionManager {
}); });
try { try {
// Create and run the outgoing session // Create and run the outgoing session
outgoingSession = createOutgoingSession(ctx, writer, true); outgoingSession = createDuplexOutgoingSession(ctx, writer);
outgoingSession.run(); outgoingSession.run();
disposeWriter(false); disposeWriter(false);
} catch(IOException e) { } catch(IOException e) {

View File

@@ -28,8 +28,7 @@ public abstract class FilePlugin implements SimplexPlugin {
protected final Executor ioExecutor; protected final Executor ioExecutor;
protected final FileUtils fileUtils; protected final FileUtils fileUtils;
protected final SimplexPluginCallback callback; protected final SimplexPluginCallback callback;
protected final int maxFrameLength; protected final int maxLatency;
protected final long maxLatency;
protected volatile boolean running = false; protected volatile boolean running = false;
@@ -39,21 +38,19 @@ public abstract class FilePlugin implements SimplexPlugin {
protected abstract void readerFinished(File f); protected abstract void readerFinished(File f);
protected FilePlugin(Executor ioExecutor, FileUtils fileUtils, protected FilePlugin(Executor ioExecutor, FileUtils fileUtils,
SimplexPluginCallback callback, int maxFrameLength, SimplexPluginCallback callback, int maxLatency) {
long maxLatency) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.fileUtils = fileUtils; this.fileUtils = fileUtils;
this.callback = callback; this.callback = callback;
this.maxFrameLength = maxFrameLength;
this.maxLatency = maxLatency; this.maxLatency = maxLatency;
} }
public int getMaxFrameLength() { public int getMaxLatency() {
return maxFrameLength; return maxLatency;
} }
public long getMaxLatency() { public int getMaxIdleTime() {
return maxLatency; return Integer.MAX_VALUE; // We don't need keepalives
} }
public boolean isRunning() { public boolean isRunning() {

View File

@@ -24,10 +24,6 @@ class FileTransportReader implements TransportConnectionReader {
this.plugin = plugin; this.plugin = plugin;
} }
public int getMaxFrameLength() {
return plugin.getMaxFrameLength();
}
public long getMaxLatency() { public long getMaxLatency() {
return plugin.getMaxLatency(); return plugin.getMaxLatency();
} }

View File

@@ -27,12 +27,12 @@ class FileTransportWriter implements TransportConnectionWriter {
this.plugin = plugin; this.plugin = plugin;
} }
public int getMaxFrameLength() { public int getMaxLatency() {
return plugin.getMaxFrameLength(); return plugin.getMaxLatency();
} }
public long getMaxLatency() { public int getMaxIdleTime() {
return plugin.getMaxLatency(); return plugin.getMaxIdleTime();
} }
public long getCapacity() { public long getCapacity() {

View File

@@ -17,9 +17,8 @@ class LanTcpPlugin extends TcpPlugin {
static final TransportId ID = new TransportId("lan"); static final TransportId ID = new TransportId("lan");
LanTcpPlugin(Executor ioExecutor, DuplexPluginCallback callback, LanTcpPlugin(Executor ioExecutor, DuplexPluginCallback callback,
int maxFrameLength, long maxLatency, long pollingInterval) { int maxLatency, int maxIdleTime, int pollingInterval) {
super(ioExecutor, callback, maxFrameLength, maxLatency, super(ioExecutor, callback, maxLatency, maxIdleTime, pollingInterval);
pollingInterval);
} }
public TransportId getId() { public TransportId getId() {

View File

@@ -9,9 +9,9 @@ import org.briarproject.api.plugins.duplex.DuplexPluginFactory;
public class LanTcpPluginFactory implements DuplexPluginFactory { public class LanTcpPluginFactory implements DuplexPluginFactory {
private static final int MAX_FRAME_LENGTH = 1024; private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final long MAX_LATENCY = 60 * 1000; // 1 minute private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
private static final long POLLING_INTERVAL = 60 * 1000; // 1 minute private static final int POLLING_INTERVAL = 3 * 60 * 1000; // 3 minutes
private final Executor ioExecutor; private final Executor ioExecutor;
@@ -24,7 +24,7 @@ public class LanTcpPluginFactory implements DuplexPluginFactory {
} }
public DuplexPlugin createPlugin(DuplexPluginCallback callback) { public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
return new LanTcpPlugin(ioExecutor, callback, MAX_FRAME_LENGTH, return new LanTcpPlugin(ioExecutor, callback, MAX_LATENCY,
MAX_LATENCY, POLLING_INTERVAL); MAX_IDLE_TIME, POLLING_INTERVAL);
} }
} }

View File

@@ -37,8 +37,7 @@ abstract class TcpPlugin implements DuplexPlugin {
protected final Executor ioExecutor; protected final Executor ioExecutor;
protected final DuplexPluginCallback callback; protected final DuplexPluginCallback callback;
protected final int maxFrameLength; protected final int maxLatency, maxIdleTime, pollingInterval, socketTimeout;
protected final long maxLatency, pollingInterval;
protected volatile boolean running = false; protected volatile boolean running = false;
protected volatile ServerSocket socket = null; protected volatile ServerSocket socket = null;
@@ -53,22 +52,25 @@ abstract class TcpPlugin implements DuplexPlugin {
protected abstract boolean isConnectable(InetSocketAddress remote); protected abstract boolean isConnectable(InetSocketAddress remote);
protected TcpPlugin(Executor ioExecutor, DuplexPluginCallback callback, protected TcpPlugin(Executor ioExecutor, DuplexPluginCallback callback,
int maxFrameLength, long maxLatency, long pollingInterval) { int maxLatency, int maxIdleTime, int pollingInterval) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.callback = callback; this.callback = callback;
this.maxFrameLength = maxFrameLength;
this.maxLatency = maxLatency; this.maxLatency = maxLatency;
this.maxIdleTime = maxIdleTime;
this.pollingInterval = pollingInterval; this.pollingInterval = pollingInterval;
if(maxIdleTime > Integer.MAX_VALUE / 2)
socketTimeout = Integer.MAX_VALUE;
else socketTimeout = maxIdleTime * 2;
} }
public int getMaxFrameLength() { public int getMaxLatency() {
return maxFrameLength;
}
public long getMaxLatency() {
return maxLatency; return maxLatency;
} }
public int getMaxIdleTime() {
return maxIdleTime;
}
public boolean start() { public boolean start() {
running = true; running = true;
bind(); bind();
@@ -136,6 +138,7 @@ abstract class TcpPlugin implements DuplexPlugin {
Socket s; Socket s;
try { try {
s = socket.accept(); s = socket.accept();
s.setSoTimeout(socketTimeout);
} catch(IOException e) { } catch(IOException e) {
// This is expected when the socket is closed // This is expected when the socket is closed
if(LOG.isLoggable(INFO)) LOG.info(e.toString()); if(LOG.isLoggable(INFO)) LOG.info(e.toString());
@@ -161,7 +164,7 @@ abstract class TcpPlugin implements DuplexPlugin {
return true; return true;
} }
public long getPollingInterval() { public int getPollingInterval() {
return pollingInterval; return pollingInterval;
} }
@@ -195,6 +198,7 @@ abstract class TcpPlugin implements DuplexPlugin {
try { try {
if(LOG.isLoggable(INFO)) LOG.info("Connecting to " + remote); if(LOG.isLoggable(INFO)) LOG.info("Connecting to " + remote);
s.connect(remote); s.connect(remote);
s.setSoTimeout(socketTimeout);
if(LOG.isLoggable(INFO)) LOG.info("Connected to " + remote); if(LOG.isLoggable(INFO)) LOG.info("Connected to " + remote);
return new TcpTransportConnection(this, s); return new TcpTransportConnection(this, s);
} catch(IOException e) { } catch(IOException e) {

View File

@@ -38,10 +38,6 @@ class TcpTransportConnection implements DuplexTransportConnection {
private class Reader implements TransportConnectionReader { private class Reader implements TransportConnectionReader {
public int getMaxFrameLength() {
return plugin.getMaxFrameLength();
}
public long getMaxLatency() { public long getMaxLatency() {
return plugin.getMaxLatency(); return plugin.getMaxLatency();
} }
@@ -59,12 +55,12 @@ class TcpTransportConnection implements DuplexTransportConnection {
private class Writer implements TransportConnectionWriter { private class Writer implements TransportConnectionWriter {
public int getMaxFrameLength() { public int getMaxLatency() {
return plugin.getMaxFrameLength(); return plugin.getMaxLatency();
} }
public long getMaxLatency() { public int getMaxIdleTime() {
return plugin.getMaxLatency(); return plugin.getMaxIdleTime();
} }
public long getCapacity() { public long getCapacity() {

View File

@@ -20,11 +20,10 @@ class WanTcpPlugin extends TcpPlugin {
private volatile MappingResult mappingResult; private volatile MappingResult mappingResult;
WanTcpPlugin(Executor ioExecutor, DuplexPluginCallback callback, WanTcpPlugin(Executor ioExecutor, PortMapper portMapper,
int maxFrameLength, long maxLatency, long pollingInterval, DuplexPluginCallback callback, int maxLatency, int maxIdleTime,
PortMapper portMapper) { int pollingInterval) {
super(ioExecutor, callback, maxFrameLength, maxLatency, super(ioExecutor, callback, maxLatency, maxIdleTime, pollingInterval);
pollingInterval);
this.portMapper = portMapper; this.portMapper = portMapper;
} }

View File

@@ -10,9 +10,9 @@ import org.briarproject.api.plugins.duplex.DuplexPluginFactory;
public class WanTcpPluginFactory implements DuplexPluginFactory { public class WanTcpPluginFactory implements DuplexPluginFactory {
private static final int MAX_FRAME_LENGTH = 1024; private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final long MAX_LATENCY = 60 * 1000; // 1 minute private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
private static final long POLLING_INTERVAL = 5 * 60 * 1000; // 5 minutes private static final int POLLING_INTERVAL = 5 * 60 * 1000; // 5 minutes
private final Executor ioExecutor; private final Executor ioExecutor;
private final ShutdownManager shutdownManager; private final ShutdownManager shutdownManager;
@@ -28,8 +28,7 @@ public class WanTcpPluginFactory implements DuplexPluginFactory {
} }
public DuplexPlugin createPlugin(DuplexPluginCallback callback) { public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
return new WanTcpPlugin(ioExecutor, callback, MAX_FRAME_LENGTH, return new WanTcpPlugin(ioExecutor, new PortMapperImpl(shutdownManager),
MAX_LATENCY, POLLING_INTERVAL, callback, MAX_LATENCY, MAX_IDLE_TIME, POLLING_INTERVAL);
new PortMapperImpl(shutdownManager));
} }
} }

View File

@@ -1,12 +0,0 @@
package org.briarproject.transport;
import java.io.IOException;
interface FrameReader {
/**
* Reads a frame into the given buffer and returns its payload length, or
* -1 if no more frames can be read from the connection.
*/
int readFrame(byte[] frame) throws IOException;
}

View File

@@ -1,13 +0,0 @@
package org.briarproject.transport;
import java.io.IOException;
interface FrameWriter {
/** Writes the given frame. */
void writeFrame(byte[] frame, int payloadLength, boolean finalFrame)
throws IOException;
/** Flushes the stream. */
void flush() throws IOException;
}

View File

@@ -1,83 +0,0 @@
package org.briarproject.transport;
import static org.briarproject.api.transport.TransportConstants.AAD_LENGTH;
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH;
import static org.briarproject.api.transport.TransportConstants.IV_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import org.briarproject.api.FormatException;
import org.briarproject.api.crypto.AuthenticatedCipher;
import org.briarproject.api.crypto.SecretKey;
class IncomingEncryptionLayer implements FrameReader {
private final InputStream in;
private final AuthenticatedCipher frameCipher;
private final SecretKey frameKey;
private final byte[] iv, aad, ciphertext;
private final int frameLength;
private long frameNumber;
private boolean finalFrame;
IncomingEncryptionLayer(InputStream in, AuthenticatedCipher frameCipher,
SecretKey frameKey, int frameLength) {
this.in = in;
this.frameCipher = frameCipher;
this.frameKey = frameKey;
this.frameLength = frameLength;
iv = new byte[IV_LENGTH];
aad = new byte[AAD_LENGTH];
ciphertext = new byte[frameLength];
frameNumber = 0;
finalFrame = false;
}
public int readFrame(byte[] frame) throws IOException {
if(finalFrame) return -1;
// Read the frame
int ciphertextLength = 0;
try {
while(ciphertextLength < frameLength) {
int read = in.read(ciphertext, ciphertextLength,
frameLength - ciphertextLength);
if(read == -1) break; // We'll check the length later
ciphertextLength += read;
}
} catch(IOException e) {
frameKey.erase();
throw e;
}
int plaintextLength = ciphertextLength - MAC_LENGTH;
if(plaintextLength < HEADER_LENGTH) throw new EOFException();
// Decrypt and authenticate the frame
FrameEncoder.encodeIv(iv, frameNumber);
FrameEncoder.encodeAad(aad, frameNumber, plaintextLength);
try {
frameCipher.init(false, frameKey, iv, aad);
int decrypted = frameCipher.doFinal(ciphertext, 0, ciphertextLength,
frame, 0);
if(decrypted != plaintextLength) throw new RuntimeException();
} catch(GeneralSecurityException e) {
throw new FormatException();
}
// Decode and validate the header
finalFrame = FrameEncoder.isFinalFrame(frame);
if(!finalFrame && ciphertextLength < frameLength)
throw new FormatException();
int payloadLength = FrameEncoder.getPayloadLength(frame);
if(payloadLength > plaintextLength - HEADER_LENGTH)
throw new FormatException();
// If there's any padding it must be all zeroes
for(int i = HEADER_LENGTH + payloadLength; i < plaintextLength; i++) {
if(frame[i] != 0) throw new FormatException();
}
frameNumber++;
return payloadLength;
}
}

View File

@@ -35,7 +35,6 @@ import org.briarproject.api.transport.Endpoint;
import org.briarproject.api.transport.StreamContext; import org.briarproject.api.transport.StreamContext;
import org.briarproject.api.transport.TagRecogniser; import org.briarproject.api.transport.TagRecogniser;
import org.briarproject.api.transport.TemporarySecret; import org.briarproject.api.transport.TemporarySecret;
import org.briarproject.util.ByteUtils;
// FIXME: Don't make alien calls with a lock held // FIXME: Don't make alien calls with a lock held
class KeyManagerImpl extends TimerTask implements KeyManager, EventListener { class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
@@ -52,7 +51,7 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
private final Clock clock; private final Clock clock;
private final Timer timer; private final Timer timer;
private final Map<TransportId, Long> maxLatencies; private final Map<TransportId, Integer> maxLatencies;
private final Map<EndpointKey, TemporarySecret> oldSecrets; private final Map<EndpointKey, TemporarySecret> oldSecrets;
private final Map<EndpointKey, TemporarySecret> currentSecrets; private final Map<EndpointKey, TemporarySecret> currentSecrets;
private final Map<EndpointKey, TemporarySecret> newSecrets; private final Map<EndpointKey, TemporarySecret> newSecrets;
@@ -69,7 +68,7 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
this.tagRecogniser = tagRecogniser; this.tagRecogniser = tagRecogniser;
this.clock = clock; this.clock = clock;
this.timer = timer; this.timer = timer;
maxLatencies = new HashMap<TransportId, Long>(); maxLatencies = new HashMap<TransportId, Integer>();
oldSecrets = new HashMap<EndpointKey, TemporarySecret>(); oldSecrets = new HashMap<EndpointKey, TemporarySecret>();
currentSecrets = new HashMap<EndpointKey, TemporarySecret>(); currentSecrets = new HashMap<EndpointKey, TemporarySecret>();
newSecrets = new HashMap<EndpointKey, TemporarySecret>(); newSecrets = new HashMap<EndpointKey, TemporarySecret>();
@@ -124,10 +123,9 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
Collection<TemporarySecret> dead = new ArrayList<TemporarySecret>(); Collection<TemporarySecret> dead = new ArrayList<TemporarySecret>();
for(TemporarySecret s : secrets) { for(TemporarySecret s : secrets) {
// Discard the secret if the transport has been removed // Discard the secret if the transport has been removed
Long maxLatency = maxLatencies.get(s.getTransportId()); Integer maxLatency = maxLatencies.get(s.getTransportId());
if(maxLatency == null) { if(maxLatency == null) {
LOG.info("Discarding obsolete secret"); LOG.info("Discarding obsolete secret");
ByteUtils.erase(s.getSecret());
continue; continue;
} }
long rotation = maxLatency + MAX_CLOCK_DIFFERENCE; long rotation = maxLatency + MAX_CLOCK_DIFFERENCE;
@@ -151,7 +149,7 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
return dead; return dead;
} }
// Replaces and erases the given secrets and returns any secrets created // Replaces the given secrets and returns any secrets created
private Collection<TemporarySecret> replaceDeadSecrets(long now, private Collection<TemporarySecret> replaceDeadSecrets(long now,
Collection<TemporarySecret> dead) { Collection<TemporarySecret> dead) {
// If there are several dead secrets for an endpoint, use the newest // If there are several dead secrets for an endpoint, use the newest
@@ -164,18 +162,16 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
// There's no other secret for this endpoint // There's no other secret for this endpoint
newest.put(k, s); newest.put(k, s);
} else if(exists.getPeriod() < s.getPeriod()) { } else if(exists.getPeriod() < s.getPeriod()) {
// There's an older secret - erase it and use this one instead // There's an older secret - use this one instead
ByteUtils.erase(exists.getSecret());
newest.put(k, s); newest.put(k, s);
} else { } else {
// There's a newer secret - erase this one // There's a newer secret - keep using it
ByteUtils.erase(s.getSecret());
} }
} }
Collection<TemporarySecret> created = new ArrayList<TemporarySecret>(); Collection<TemporarySecret> created = new ArrayList<TemporarySecret>();
for(Entry<EndpointKey, TemporarySecret> e : newest.entrySet()) { for(Entry<EndpointKey, TemporarySecret> e : newest.entrySet()) {
TemporarySecret s = e.getValue(); TemporarySecret s = e.getValue();
Long maxLatency = maxLatencies.get(s.getTransportId()); Integer maxLatency = maxLatencies.get(s.getTransportId());
if(maxLatency == null) throw new IllegalStateException(); if(maxLatency == null) throw new IllegalStateException();
// Work out which rotation period we're in // Work out which rotation period we're in
long elapsed = now - s.getEpoch(); long elapsed = now - s.getEpoch();
@@ -186,34 +182,23 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
throw new IllegalStateException(); throw new IllegalStateException();
// Derive the old, current and new secrets // Derive the old, current and new secrets
byte[] b1 = s.getSecret(); byte[] b1 = s.getSecret();
for(long p = s.getPeriod() + 1; p < period; p++) { for(long p = s.getPeriod() + 1; p < period; p++)
byte[] temp = crypto.deriveNextSecret(b1, p); b1 = crypto.deriveNextSecret(b1, p);
ByteUtils.erase(b1);
b1 = temp;
}
byte[] b2 = crypto.deriveNextSecret(b1, period); byte[] b2 = crypto.deriveNextSecret(b1, period);
byte[] b3 = crypto.deriveNextSecret(b2, period + 1); byte[] b3 = crypto.deriveNextSecret(b2, period + 1);
// Add the secrets to their respective maps - copies may already // Add the secrets to their respective maps if not already present
// exist, in which case erase the new copies (the old copies are
// referenced by the connection recogniser)
EndpointKey k = e.getKey(); EndpointKey k = e.getKey();
if(oldSecrets.containsKey(k)) { if(!oldSecrets.containsKey(k)) {
ByteUtils.erase(b1);
} else {
TemporarySecret s1 = new TemporarySecret(s, period - 1, b1); TemporarySecret s1 = new TemporarySecret(s, period - 1, b1);
oldSecrets.put(k, s1); oldSecrets.put(k, s1);
created.add(s1); created.add(s1);
} }
if(currentSecrets.containsKey(k)) { if(!currentSecrets.containsKey(k)) {
ByteUtils.erase(b2);
} else {
TemporarySecret s2 = new TemporarySecret(s, period, b2); TemporarySecret s2 = new TemporarySecret(s, period, b2);
currentSecrets.put(k, s2); currentSecrets.put(k, s2);
created.add(s2); created.add(s2);
} }
if(newSecrets.containsKey(k)) { if(!newSecrets.containsKey(k)) {
ByteUtils.erase(b3);
} else {
TemporarySecret s3 = new TemporarySecret(s, period + 1, b3); TemporarySecret s3 = new TemporarySecret(s, period + 1, b3);
newSecrets.put(k, s3); newSecrets.put(k, s3);
created.add(s3); created.add(s3);
@@ -229,9 +214,9 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
timer.cancel(); timer.cancel();
tagRecogniser.removeSecrets(); tagRecogniser.removeSecrets();
maxLatencies.clear(); maxLatencies.clear();
removeAndEraseSecrets(oldSecrets); oldSecrets.clear();
removeAndEraseSecrets(currentSecrets); currentSecrets.clear();
removeAndEraseSecrets(newSecrets); newSecrets.clear();
return true; return true;
} }
finally{ finally{
@@ -239,11 +224,6 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
} }
} }
private void removeAndEraseSecrets(Map<?, TemporarySecret> m) {
for(TemporarySecret s : m.values()) ByteUtils.erase(s.getSecret());
m.clear();
}
public StreamContext getStreamContext(ContactId c, public StreamContext getStreamContext(ContactId c,
TransportId t) { TransportId t) {
synchLock.lock(); synchLock.lock();
@@ -264,8 +244,7 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return null; return null;
} }
// Clone the secret - the original will be erased byte[] secret = s.getSecret();
byte[] secret = s.getSecret().clone();
return new StreamContext(c, t, secret, streamNumber, s.getAlice()); return new StreamContext(c, t, secret, streamNumber, s.getAlice());
} }
finally{ finally{
@@ -273,7 +252,7 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
} }
} }
public void endpointAdded(Endpoint ep, long maxLatency, public synchronized void endpointAdded(Endpoint ep, int maxLatency,
byte[] initialSecret) { byte[] initialSecret) {
synchLock.lock(); synchLock.lock();
try{ try{
@@ -285,11 +264,8 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
if(period < 1) throw new IllegalStateException(); if(period < 1) throw new IllegalStateException();
// Derive the old, current and new secrets // Derive the old, current and new secrets
byte[] b1 = initialSecret; byte[] b1 = initialSecret;
for(long p = 0; p < period; p++) { for(long p = 0; p < period; p++)
byte[] temp = crypto.deriveNextSecret(b1, p); b1 = crypto.deriveNextSecret(b1, p);
ByteUtils.erase(b1);
b1 = temp;
}
byte[] b2 = crypto.deriveNextSecret(b1, period); byte[] b2 = crypto.deriveNextSecret(b1, period);
byte[] b3 = crypto.deriveNextSecret(b2, period + 1); byte[] b3 = crypto.deriveNextSecret(b2, period + 1);
TemporarySecret s1 = new TemporarySecret(ep, period - 1, b1); TemporarySecret s1 = new TemporarySecret(ep, period - 1, b1);
@@ -370,27 +346,16 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
} }
} }
private void removeAndEraseSecrets(ContactId c, Map<?, TemporarySecret> m) { private void removeSecrets(ContactId c, Map<?, TemporarySecret> m) {
Iterator<TemporarySecret> it = m.values().iterator(); Iterator<TemporarySecret> it = m.values().iterator();
while(it.hasNext()) { while(it.hasNext())
TemporarySecret s = it.next(); if(it.next().getContactId().equals(c)) it.remove();
if(s.getContactId().equals(c)) {
ByteUtils.erase(s.getSecret());
it.remove();
}
}
} }
private void removeAndEraseSecrets(TransportId t, private void removeSecrets(TransportId t, Map<?, TemporarySecret> m) {
Map<?, TemporarySecret> m) {
Iterator<TemporarySecret> it = m.values().iterator(); Iterator<TemporarySecret> it = m.values().iterator();
while(it.hasNext()) { while(it.hasNext())
TemporarySecret s = it.next(); if(it.next().getTransportId().equals(t)) it.remove();
if(s.getTransportId().equals(t)) {
ByteUtils.erase(s.getSecret());
it.remove();
}
}
} }
private static class EndpointKey { private static class EndpointKey {
@@ -436,10 +401,10 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
ContactId c = event.getContactId(); ContactId c = event.getContactId();
tagRecogniser.removeSecrets(c); tagRecogniser.removeSecrets(c);
synchLock.lock(); synchLock.lock();
try { try{
removeAndEraseSecrets(c, oldSecrets); removeSecrets(c, oldSecrets);
removeAndEraseSecrets(c, currentSecrets); removeSecrets(c, currentSecrets);
removeAndEraseSecrets(c, newSecrets); removeSecrets(c, newSecrets);
} }
finally{ finally{
synchLock.unlock(); synchLock.unlock();
@@ -482,9 +447,9 @@ class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
synchLock.lock(); synchLock.lock();
try { try {
maxLatencies.remove(t); maxLatencies.remove(t);
removeAndEraseSecrets(t, oldSecrets); removeSecrets(t, oldSecrets);
removeAndEraseSecrets(t, currentSecrets); removeSecrets(t, currentSecrets);
removeAndEraseSecrets(t, newSecrets); removeSecrets(t, newSecrets);
} }
finally{ finally{
synchLock.unlock(); synchLock.unlock();

View File

@@ -0,0 +1,546 @@
package org.briarproject.transport;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TimerTask;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
import javax.inject.Inject;
import org.briarproject.api.ContactId;
import org.briarproject.api.TransportId;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.KeyManager;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException;
import org.briarproject.api.event.ContactRemovedEvent;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventBus;
import org.briarproject.api.event.EventListener;
import org.briarproject.api.event.TransportAddedEvent;
import org.briarproject.api.event.TransportRemovedEvent;
import org.briarproject.api.system.Clock;
import org.briarproject.api.system.Timer;
import org.briarproject.api.transport.Endpoint;
import org.briarproject.api.transport.StreamContext;
import org.briarproject.api.transport.TagRecogniser;
import org.briarproject.api.transport.TemporarySecret;
// FIXME: Don't make alien calls with a lock held
class KeyManagerImpl extends TimerTask implements KeyManager, EventListener {
private static final int MS_BETWEEN_CHECKS = 60 * 1000;
private static final Logger LOG =
Logger.getLogger(KeyManagerImpl.class.getName());
private final CryptoComponent crypto;
private final DatabaseComponent db;
private final EventBus eventBus;
private final TagRecogniser tagRecogniser;
private final Clock clock;
private final Timer timer;
<<<<<<< HEAD
private final Map<TransportId, Long> maxLatencies;
=======
// All of the following are locking: this
private final Map<TransportId, Integer> maxLatencies;
>>>>>>> theSource
private final Map<EndpointKey, TemporarySecret> oldSecrets;
private final Map<EndpointKey, TemporarySecret> currentSecrets;
private final Map<EndpointKey, TemporarySecret> newSecrets;
private final Lock synchLock = new ReentrantLock();
@Inject
KeyManagerImpl(CryptoComponent crypto, DatabaseComponent db,
EventBus eventBus, TagRecogniser tagRecogniser, Clock clock,
Timer timer) {
this.crypto = crypto;
this.db = db;
this.eventBus = eventBus;
this.tagRecogniser = tagRecogniser;
this.clock = clock;
this.timer = timer;
maxLatencies = new HashMap<TransportId, Integer>();
oldSecrets = new HashMap<EndpointKey, TemporarySecret>();
currentSecrets = new HashMap<EndpointKey, TemporarySecret>();
newSecrets = new HashMap<EndpointKey, TemporarySecret>();
}
public boolean start() {
synchLock.lock();
try {
eventBus.addListener(this);
// Load the temporary secrets and transport latencies from the database
Collection<TemporarySecret> secrets;
try {
secrets = db.getSecrets();
maxLatencies.putAll(db.getTransportLatencies());
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return false;
}
// Work out what phase of its lifecycle each secret is in
long now = clock.currentTimeMillis();
Collection<TemporarySecret> dead = assignSecretsToMaps(now, secrets);
// Replace any dead secrets
Collection<TemporarySecret> created = replaceDeadSecrets(now, dead);
if(!created.isEmpty()) {
// Store any secrets that have been created, removing any dead ones
try {
db.addSecrets(created);
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return false;
}
}
// Pass the old, current and new secrets to the recogniser
for(TemporarySecret s : oldSecrets.values())
tagRecogniser.addSecret(s);
for(TemporarySecret s : currentSecrets.values())
tagRecogniser.addSecret(s);
for(TemporarySecret s : newSecrets.values())
tagRecogniser.addSecret(s);
// Schedule periodic key rotation
timer.scheduleAtFixedRate(this, MS_BETWEEN_CHECKS, MS_BETWEEN_CHECKS);
return true;
}
finally{
synchLock.unlock();
}
}
// Assigns secrets to the appropriate maps and returns any dead secrets
private Collection<TemporarySecret> assignSecretsToMaps(long now,
Collection<TemporarySecret> secrets) {
Collection<TemporarySecret> dead = new ArrayList<TemporarySecret>();
for(TemporarySecret s : secrets) {
// Discard the secret if the transport has been removed
Integer maxLatency = maxLatencies.get(s.getTransportId());
if(maxLatency == null) {
LOG.info("Discarding obsolete secret");
continue;
}
long rotation = maxLatency + MAX_CLOCK_DIFFERENCE;
long creationTime = s.getEpoch() + rotation * (s.getPeriod() - 2);
long activationTime = creationTime + rotation;
long deactivationTime = activationTime + rotation;
long destructionTime = deactivationTime + rotation;
if(now >= destructionTime) {
dead.add(s);
} else if(now >= deactivationTime) {
oldSecrets.put(new EndpointKey(s), s);
} else if(now >= activationTime) {
currentSecrets.put(new EndpointKey(s), s);
} else if(now >= creationTime) {
newSecrets.put(new EndpointKey(s), s);
} else {
// FIXME: Work out what to do here
throw new Error("Clock has moved backwards");
}
}
return dead;
}
<<<<<<< HEAD
// Replaces and erases the given secrets and returns any secrets created
=======
// Replaces the given secrets and returns any secrets created
// Locking: this
>>>>>>> theSource
private Collection<TemporarySecret> replaceDeadSecrets(long now,
Collection<TemporarySecret> dead) {
// If there are several dead secrets for an endpoint, use the newest
Map<EndpointKey, TemporarySecret> newest =
new HashMap<EndpointKey, TemporarySecret>();
for(TemporarySecret s : dead) {
EndpointKey k = new EndpointKey(s);
TemporarySecret exists = newest.get(k);
if(exists == null) {
// There's no other secret for this endpoint
newest.put(k, s);
} else if(exists.getPeriod() < s.getPeriod()) {
// There's an older secret - use this one instead
newest.put(k, s);
} else {
// There's a newer secret - keep using it
}
}
Collection<TemporarySecret> created = new ArrayList<TemporarySecret>();
for(Entry<EndpointKey, TemporarySecret> e : newest.entrySet()) {
TemporarySecret s = e.getValue();
Integer maxLatency = maxLatencies.get(s.getTransportId());
if(maxLatency == null) throw new IllegalStateException();
// Work out which rotation period we're in
long elapsed = now - s.getEpoch();
long rotation = maxLatency + MAX_CLOCK_DIFFERENCE;
long period = (elapsed / rotation) + 1;
if(period < 1) throw new IllegalStateException();
if(period - s.getPeriod() < 2)
throw new IllegalStateException();
// Derive the old, current and new secrets
byte[] b1 = s.getSecret();
for(long p = s.getPeriod() + 1; p < period; p++)
b1 = crypto.deriveNextSecret(b1, p);
byte[] b2 = crypto.deriveNextSecret(b1, period);
byte[] b3 = crypto.deriveNextSecret(b2, period + 1);
// Add the secrets to their respective maps if not already present
EndpointKey k = e.getKey();
if(!oldSecrets.containsKey(k)) {
TemporarySecret s1 = new TemporarySecret(s, period - 1, b1);
oldSecrets.put(k, s1);
created.add(s1);
}
if(!currentSecrets.containsKey(k)) {
TemporarySecret s2 = new TemporarySecret(s, period, b2);
currentSecrets.put(k, s2);
created.add(s2);
}
if(!newSecrets.containsKey(k)) {
TemporarySecret s3 = new TemporarySecret(s, period + 1, b3);
newSecrets.put(k, s3);
created.add(s3);
}
}
return created;
}
<<<<<<< HEAD
public boolean stop() {
synchLock.lock();
try{
eventBus.removeListener(this);
timer.cancel();
tagRecogniser.removeSecrets();
maxLatencies.clear();
removeAndEraseSecrets(oldSecrets);
removeAndEraseSecrets(currentSecrets);
removeAndEraseSecrets(newSecrets);
return true;
}
finally{
synchLock.unlock();
}
}
private void removeAndEraseSecrets(Map<?, TemporarySecret> m) {
for(TemporarySecret s : m.values()) ByteUtils.erase(s.getSecret());
m.clear();
}
public StreamContext getStreamContext(ContactId c,
=======
public synchronized boolean stop() {
eventBus.removeListener(this);
timer.cancel();
tagRecogniser.removeSecrets();
maxLatencies.clear();
oldSecrets.clear();
currentSecrets.clear();
newSecrets.clear();
return true;
}
public synchronized StreamContext getStreamContext(ContactId c,
>>>>>>> theSource
TransportId t) {
synchLock.lock();
try{
TemporarySecret s = currentSecrets.get(new EndpointKey(c, t));
if(s == null) {
LOG.info("No secret for endpoint");
return null;
}
long streamNumber;
try {
streamNumber = db.incrementStreamCounter(c, t, s.getPeriod());
if(streamNumber == -1) {
LOG.info("No counter for period");
return null;
}
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return null;
}
// Clone the secret - the original will be erased
byte[] secret = s.getSecret().clone();
return new StreamContext(c, t, secret, streamNumber, s.getAlice());
}
finally{
synchLock.unlock();
}
<<<<<<< HEAD
}
public void endpointAdded(Endpoint ep, long maxLatency,
byte[] initialSecret) {
synchLock.lock();
try{
maxLatencies.put(ep.getTransportId(), maxLatency);
// Work out which rotation period we're in
long elapsed = clock.currentTimeMillis() - ep.getEpoch();
long rotation = maxLatency + MAX_CLOCK_DIFFERENCE;
long period = (elapsed / rotation) + 1;
if(period < 1) throw new IllegalStateException();
// Derive the old, current and new secrets
byte[] b1 = initialSecret;
for(long p = 0; p < period; p++) {
byte[] temp = crypto.deriveNextSecret(b1, p);
ByteUtils.erase(b1);
b1 = temp;
}
byte[] b2 = crypto.deriveNextSecret(b1, period);
byte[] b3 = crypto.deriveNextSecret(b2, period + 1);
TemporarySecret s1 = new TemporarySecret(ep, period - 1, b1);
TemporarySecret s2 = new TemporarySecret(ep, period, b2);
TemporarySecret s3 = new TemporarySecret(ep, period + 1, b3);
// Add the incoming secrets to their respective maps
EndpointKey k = new EndpointKey(ep);
oldSecrets.put(k, s1);
currentSecrets.put(k, s2);
newSecrets.put(k, s3);
// Store the new secrets
try {
db.addSecrets(Arrays.asList(s1, s2, s3));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return;
}
// Pass the new secrets to the recogniser
tagRecogniser.addSecret(s1);
tagRecogniser.addSecret(s2);
tagRecogniser.addSecret(s3);
}
finally{
synchLock.unlock();
=======
byte[] secret = s.getSecret();
return new StreamContext(c, t, secret, streamNumber, s.getAlice());
}
public synchronized void endpointAdded(Endpoint ep, int maxLatency,
byte[] initialSecret) {
maxLatencies.put(ep.getTransportId(), maxLatency);
// Work out which rotation period we're in
long elapsed = clock.currentTimeMillis() - ep.getEpoch();
long rotation = maxLatency + MAX_CLOCK_DIFFERENCE;
long period = (elapsed / rotation) + 1;
if(period < 1) throw new IllegalStateException();
// Derive the old, current and new secrets
byte[] b1 = initialSecret;
for(long p = 0; p < period; p++)
b1 = crypto.deriveNextSecret(b1, p);
byte[] b2 = crypto.deriveNextSecret(b1, period);
byte[] b3 = crypto.deriveNextSecret(b2, period + 1);
TemporarySecret s1 = new TemporarySecret(ep, period - 1, b1);
TemporarySecret s2 = new TemporarySecret(ep, period, b2);
TemporarySecret s3 = new TemporarySecret(ep, period + 1, b3);
// Add the incoming secrets to their respective maps
EndpointKey k = new EndpointKey(ep);
oldSecrets.put(k, s1);
currentSecrets.put(k, s2);
newSecrets.put(k, s3);
// Store the new secrets
try {
db.addSecrets(Arrays.asList(s1, s2, s3));
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return;
>>>>>>> theSource
}
}
@Override
public void run() {
synchLock.lock();
try{
// Rebuild the maps because we may be running a whole period late
Collection<TemporarySecret> secrets = new ArrayList<TemporarySecret>();
secrets.addAll(oldSecrets.values());
secrets.addAll(currentSecrets.values());
secrets.addAll(newSecrets.values());
oldSecrets.clear();
currentSecrets.clear();
newSecrets.clear();
// Work out what phase of its lifecycle each secret is in
long now = clock.currentTimeMillis();
Collection<TemporarySecret> dead = assignSecretsToMaps(now, secrets);
// Remove any dead secrets from the recogniser
for(TemporarySecret s : dead) {
ContactId c = s.getContactId();
TransportId t = s.getTransportId();
long period = s.getPeriod();
tagRecogniser.removeSecret(c, t, period);
}
// Replace any dead secrets
Collection<TemporarySecret> created = replaceDeadSecrets(now, dead);
if(!created.isEmpty()) {
// Store any secrets that have been created
try {
db.addSecrets(created);
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
// Pass any secrets that have been created to the recogniser
for(TemporarySecret s : created) tagRecogniser.addSecret(s);
}
}
finally{
synchLock.unlock();
}
}
public void eventOccurred(Event e) {
if(e instanceof ContactRemovedEvent) {
ContactRemovedEvent c = (ContactRemovedEvent) e;
timer.schedule(new ContactRemovedTask(c), 0);
} else if(e instanceof TransportAddedEvent) {
TransportAddedEvent t = (TransportAddedEvent) e;
timer.schedule(new TransportAddedTask(t), 0);
} else if(e instanceof TransportRemovedEvent) {
TransportRemovedEvent t = (TransportRemovedEvent) e;
timer.schedule(new TransportRemovedTask(t), 0);
}
}
<<<<<<< HEAD
private void removeAndEraseSecrets(ContactId c, Map<?, TemporarySecret> m) {
=======
// Locking: this
private void removeSecrets(ContactId c, Map<?, TemporarySecret> m) {
>>>>>>> theSource
Iterator<TemporarySecret> it = m.values().iterator();
while(it.hasNext())
if(it.next().getContactId().equals(c)) it.remove();
}
<<<<<<< HEAD
private void removeAndEraseSecrets(TransportId t,
Map<?, TemporarySecret> m) {
=======
// Locking: this
private void removeSecrets(TransportId t, Map<?, TemporarySecret> m) {
>>>>>>> theSource
Iterator<TemporarySecret> it = m.values().iterator();
while(it.hasNext())
if(it.next().getTransportId().equals(t)) it.remove();
}
private static class EndpointKey {
private final ContactId contactId;
private final TransportId transportId;
private EndpointKey(ContactId contactId, TransportId transportId) {
this.contactId = contactId;
this.transportId = transportId;
}
private EndpointKey(Endpoint ep) {
this(ep.getContactId(), ep.getTransportId());
}
@Override
public int hashCode() {
return contactId.hashCode() ^ transportId.hashCode();
}
@Override
public boolean equals(Object o) {
if(o instanceof EndpointKey) {
EndpointKey k = (EndpointKey) o;
return contactId.equals(k.contactId) &&
transportId.equals(k.transportId);
}
return false;
}
}
private class ContactRemovedTask extends TimerTask {
private final ContactRemovedEvent event;
private ContactRemovedTask(ContactRemovedEvent event) {
this.event = event;
}
@Override
public void run() {
ContactId c = event.getContactId();
tagRecogniser.removeSecrets(c);
<<<<<<< HEAD
synchLock.lock();
try {
removeAndEraseSecrets(c, oldSecrets);
removeAndEraseSecrets(c, currentSecrets);
removeAndEraseSecrets(c, newSecrets);
=======
synchronized(KeyManagerImpl.this) {
removeSecrets(c, oldSecrets);
removeSecrets(c, currentSecrets);
removeSecrets(c, newSecrets);
>>>>>>> theSource
}
finally{
synchLock.unlock();
}
}
}
private class TransportAddedTask extends TimerTask {
private final TransportAddedEvent event;
private TransportAddedTask(TransportAddedEvent event) {
this.event = event;
}
@Override
public void run() {
synchLock.lock();
try {
maxLatencies.put(event.getTransportId(), event.getMaxLatency());
}
finally{
synchLock.unlock();
}
}
}
private class TransportRemovedTask extends TimerTask {
private TransportRemovedEvent event;
private TransportRemovedTask(TransportRemovedEvent event) {
this.event = event;
}
@Override
public void run() {
TransportId t = event.getTransportId();
tagRecogniser.removeSecrets(t);
synchLock.lock();
try {
maxLatencies.remove(t);
removeSecrets(t, oldSecrets);
removeSecrets(t, currentSecrets);
removeSecrets(t, newSecrets);
}
finally{
synchLock.unlock();
}
}
}
}

View File

@@ -1,103 +0,0 @@
package org.briarproject.transport;
import static org.briarproject.api.transport.TransportConstants.AAD_LENGTH;
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH;
import static org.briarproject.api.transport.TransportConstants.IV_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
import static org.briarproject.util.ByteUtils.MAX_32_BIT_UNSIGNED;
import java.io.IOException;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import org.briarproject.api.crypto.AuthenticatedCipher;
import org.briarproject.api.crypto.SecretKey;
class OutgoingEncryptionLayer implements FrameWriter {
private final OutputStream out;
private final AuthenticatedCipher frameCipher;
private final SecretKey frameKey;
private final byte[] tag, iv, aad, ciphertext;
private final int frameLength;
private long frameNumber;
private boolean writeTag;
OutgoingEncryptionLayer(OutputStream out, AuthenticatedCipher frameCipher,
SecretKey frameKey, int frameLength, byte[] tag) {
this.out = out;
this.frameCipher = frameCipher;
this.frameKey = frameKey;
this.frameLength = frameLength;
this.tag = tag;
iv = new byte[IV_LENGTH];
aad = new byte[AAD_LENGTH];
ciphertext = new byte[frameLength];
frameNumber = 0;
writeTag = (tag != null);
}
public void writeFrame(byte[] frame, int payloadLength, boolean finalFrame)
throws IOException {
if(frameNumber > MAX_32_BIT_UNSIGNED) throw new IllegalStateException();
// Write the tag if required
if(writeTag) {
try {
out.write(tag, 0, tag.length);
} catch(IOException e) {
frameKey.erase();
throw e;
}
writeTag = false;
}
// Encode the header
FrameEncoder.encodeHeader(frame, finalFrame, payloadLength);
// Don't pad the final frame
int plaintextLength, ciphertextLength;
if(finalFrame) {
plaintextLength = HEADER_LENGTH + payloadLength;
ciphertextLength = plaintextLength + MAC_LENGTH;
} else {
plaintextLength = frameLength - MAC_LENGTH;
ciphertextLength = frameLength;
}
// If there's any padding it must all be zeroes
for(int i = HEADER_LENGTH + payloadLength; i < plaintextLength; i++) {
frame[i] = 0;
}
// Encrypt and authenticate the frame
FrameEncoder.encodeIv(iv, frameNumber);
FrameEncoder.encodeAad(aad, frameNumber, plaintextLength);
try {
frameCipher.init(true, frameKey, iv, aad);
int encrypted = frameCipher.doFinal(frame, 0, plaintextLength,
ciphertext, 0);
if(encrypted != ciphertextLength) throw new RuntimeException();
} catch(GeneralSecurityException badCipher) {
throw new RuntimeException(badCipher);
}
// Write the frame
try {
out.write(ciphertext, 0, ciphertextLength);
} catch(IOException e) {
frameKey.erase();
throw e;
}
frameNumber++;
}
public void flush() throws IOException {
// Write the tag if required
if(writeTag) {
try {
out.write(tag, 0, tag.length);
} catch(IOException e) {
frameKey.erase();
throw e;
}
writeTag = false;
}
out.flush();
}
}

View File

@@ -4,37 +4,28 @@ import java.io.InputStream;
import javax.inject.Inject; import javax.inject.Inject;
import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.crypto.StreamDecrypterFactory;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.transport.StreamContext; import org.briarproject.api.transport.StreamContext;
import org.briarproject.api.transport.StreamReader;
import org.briarproject.api.transport.StreamReaderFactory; import org.briarproject.api.transport.StreamReaderFactory;
class StreamReaderFactoryImpl implements StreamReaderFactory { class StreamReaderFactoryImpl implements StreamReaderFactory {
private final CryptoComponent crypto; private final StreamDecrypterFactory streamDecrypterFactory;
@Inject @Inject
StreamReaderFactoryImpl(CryptoComponent crypto) { StreamReaderFactoryImpl(StreamDecrypterFactory streamDecrypterFactory) {
this.crypto = crypto; this.streamDecrypterFactory = streamDecrypterFactory;
} }
public StreamReader createStreamReader(InputStream in, public InputStream createStreamReader(InputStream in, StreamContext ctx) {
int maxFrameLength, StreamContext ctx) { return new StreamReaderImpl(
byte[] secret = ctx.getSecret(); streamDecrypterFactory.createStreamDecrypter(in, ctx));
long streamNumber = ctx.getStreamNumber();
boolean alice = !ctx.getAlice();
SecretKey frameKey = crypto.deriveFrameKey(secret, streamNumber, alice);
FrameReader frameReader = new IncomingEncryptionLayer(in,
crypto.getFrameCipher(), frameKey, maxFrameLength);
return new StreamReaderImpl(frameReader, maxFrameLength);
} }
public StreamReader createInvitationStreamReader(InputStream in, public InputStream createInvitationStreamReader(InputStream in,
int maxFrameLength, byte[] secret, boolean alice) { byte[] secret, boolean alice) {
SecretKey frameKey = crypto.deriveFrameKey(secret, 0, alice); return new StreamReaderImpl(
FrameReader frameReader = new IncomingEncryptionLayer(in, streamDecrypterFactory.createInvitationStreamDecrypter(in,
crypto.getFrameCipher(), frameKey, maxFrameLength); secret, alice));
return new StreamReaderImpl(frameReader, maxFrameLength);
} }
} }

View File

@@ -1,27 +1,22 @@
package org.briarproject.transport; package org.briarproject.transport;
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH; import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import org.briarproject.api.transport.StreamReader; import org.briarproject.api.crypto.StreamDecrypter;
class StreamReaderImpl extends InputStream implements StreamReader { class StreamReaderImpl extends InputStream {
private final FrameReader in; private final StreamDecrypter decrypter;
private final byte[] frame; private final byte[] payload;
private int offset = 0, length = 0; private int offset = 0, length = 0;
StreamReaderImpl(FrameReader in, int frameLength) { StreamReaderImpl(StreamDecrypter decrypter) {
this.in = in; this.decrypter = decrypter;
frame = new byte[frameLength - MAC_LENGTH]; payload = new byte[MAX_PAYLOAD_LENGTH];
}
public InputStream getInputStream() {
return this;
} }
@Override @Override
@@ -30,7 +25,7 @@ class StreamReaderImpl extends InputStream implements StreamReader {
if(length == -1) return -1; if(length == -1) return -1;
readFrame(); readFrame();
} }
int b = frame[offset] & 0xff; int b = payload[offset] & 0xff;
offset++; offset++;
length--; length--;
return b; return b;
@@ -48,7 +43,7 @@ class StreamReaderImpl extends InputStream implements StreamReader {
readFrame(); readFrame();
} }
len = Math.min(len, length); len = Math.min(len, length);
System.arraycopy(frame, offset, b, off, len); System.arraycopy(payload, offset, b, off, len);
offset += len; offset += len;
length -= len; length -= len;
return len; return len;
@@ -56,7 +51,7 @@ class StreamReaderImpl extends InputStream implements StreamReader {
private void readFrame() throws IOException { private void readFrame() throws IOException {
assert length == 0; assert length == 0;
offset = HEADER_LENGTH; offset = 0;
length = in.readFrame(frame); length = decrypter.readFrame(payload);
} }
} }

View File

@@ -1,46 +1,32 @@
package org.briarproject.transport; package org.briarproject.transport;
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
import java.io.OutputStream; import java.io.OutputStream;
import javax.inject.Inject; import javax.inject.Inject;
import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.crypto.StreamEncrypterFactory;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.transport.StreamContext; import org.briarproject.api.transport.StreamContext;
import org.briarproject.api.transport.StreamWriter;
import org.briarproject.api.transport.StreamWriterFactory; import org.briarproject.api.transport.StreamWriterFactory;
class StreamWriterFactoryImpl implements StreamWriterFactory { class StreamWriterFactoryImpl implements StreamWriterFactory {
private final CryptoComponent crypto; private final StreamEncrypterFactory streamEncrypterFactory;
@Inject @Inject
StreamWriterFactoryImpl(CryptoComponent crypto) { StreamWriterFactoryImpl(StreamEncrypterFactory streamEncrypterFactory) {
this.crypto = crypto; this.streamEncrypterFactory = streamEncrypterFactory;
} }
public StreamWriter createStreamWriter(OutputStream out, public OutputStream createStreamWriter(OutputStream out,
int maxFrameLength, StreamContext ctx) { StreamContext ctx) {
byte[] secret = ctx.getSecret(); return new StreamWriterImpl(
long streamNumber = ctx.getStreamNumber(); streamEncrypterFactory.createStreamEncrypter(out, ctx));
boolean alice = ctx.getAlice();
byte[] tag = new byte[TAG_LENGTH];
SecretKey tagKey = crypto.deriveTagKey(secret, alice);
crypto.encodeTag(tag, tagKey, streamNumber);
tagKey.erase();
SecretKey frameKey = crypto.deriveFrameKey(secret, streamNumber, alice);
FrameWriter frameWriter = new OutgoingEncryptionLayer(out,
crypto.getFrameCipher(), frameKey, maxFrameLength, tag);
return new StreamWriterImpl(frameWriter, maxFrameLength);
} }
public StreamWriter createInvitationStreamWriter(OutputStream out, public OutputStream createInvitationStreamWriter(OutputStream out,
int maxFrameLength, byte[] secret, boolean alice) { byte[] secret, boolean alice) {
SecretKey frameKey = crypto.deriveFrameKey(secret, 0, alice); return new StreamWriterImpl(
FrameWriter frameWriter = new OutgoingEncryptionLayer(out, streamEncrypterFactory.createInvitationStreamEncrypter(out,
crypto.getFrameCipher(), frameKey, maxFrameLength, null); secret, alice));
return new StreamWriterImpl(frameWriter, maxFrameLength);
} }
} }

View File

@@ -1,12 +1,11 @@
package org.briarproject.transport; package org.briarproject.transport;
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH; import static org.briarproject.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import org.briarproject.api.transport.StreamWriter; import org.briarproject.api.crypto.StreamEncrypter;
/** /**
* A {@link org.briarproject.api.transport.StreamWriter StreamWriter} that * A {@link org.briarproject.api.transport.StreamWriter StreamWriter} that
@@ -15,43 +14,36 @@ import org.briarproject.api.transport.StreamWriter;
* <p> * <p>
* This class is not thread-safe. * This class is not thread-safe.
*/ */
class StreamWriterImpl extends OutputStream implements StreamWriter { class StreamWriterImpl extends OutputStream {
private final FrameWriter out; private final StreamEncrypter encrypter;
private final byte[] frame; private final byte[] payload;
private final int frameLength;
private int length = 0; private int length = 0;
StreamWriterImpl(FrameWriter out, int frameLength) { StreamWriterImpl(StreamEncrypter encrypter) {
this.out = out; this.encrypter = encrypter;
this.frameLength = frameLength; payload = new byte[MAX_PAYLOAD_LENGTH];
frame = new byte[frameLength - MAC_LENGTH];
}
public OutputStream getOutputStream() {
return this;
} }
@Override @Override
public void close() throws IOException { public void close() throws IOException {
writeFrame(true); writeFrame(true);
out.flush(); encrypter.flush();
super.close(); super.close();
} }
@Override @Override
public void flush() throws IOException { public void flush() throws IOException {
if(length > 0) writeFrame(false); writeFrame(false);
out.flush(); encrypter.flush();
} }
@Override @Override
public void write(int b) throws IOException { public void write(int b) throws IOException {
frame[HEADER_LENGTH + length] = (byte) b; payload[length] = (byte) b;
length++; length++;
if(HEADER_LENGTH + length + MAC_LENGTH == frameLength) if(length == payload.length) writeFrame(false);
writeFrame(false);
} }
@Override @Override
@@ -61,21 +53,21 @@ class StreamWriterImpl extends OutputStream implements StreamWriter {
@Override @Override
public void write(byte[] b, int off, int len) throws IOException { public void write(byte[] b, int off, int len) throws IOException {
int available = frameLength - HEADER_LENGTH - length - MAC_LENGTH; int available = payload.length - length;
while(available <= len) { while(available <= len) {
System.arraycopy(b, off, frame, HEADER_LENGTH + length, available); System.arraycopy(b, off, payload, length, available);
length += available; length += available;
writeFrame(false); writeFrame(false);
off += available; off += available;
len -= available; len -= available;
available = frameLength - HEADER_LENGTH - length - MAC_LENGTH; available = payload.length - length;
} }
System.arraycopy(b, off, frame, HEADER_LENGTH + length, len); System.arraycopy(b, off, payload, length, len);
length += len; length += len;
} }
private void writeFrame(boolean finalFrame) throws IOException { private void writeFrame(boolean finalFrame) throws IOException {
out.writeFrame(frame, length, finalFrame); encrypter.writeFrame(payload, length, 0, finalFrame);
length = 0; length = 0;
} }
} }

View File

@@ -62,13 +62,10 @@ class TransportTagRecogniser {
assert duplicate == null; assert duplicate == null;
} }
} }
key.erase();
// Store the updated reordering window in the DB // Store the updated reordering window in the DB
db.setReorderingWindow(t.contactId, transportId, t.period, db.setReorderingWindow(t.contactId, transportId, t.period,
t.window.getCentre(), t.window.getBitmap()); t.window.getCentre(), t.window.getBitmap());
// Clone the secret - the key manager will erase the original return new StreamContext(t.contactId, transportId, t.secret,
byte[] secret = t.secret.clone();
return new StreamContext(t.contactId, transportId, secret,
t.streamNumber, t.alice); t.streamNumber, t.alice);
} }
finally{ finally{
@@ -96,7 +93,6 @@ class TransportTagRecogniser {
TagContext duplicate = tagMap.put(new Bytes(tag), added); TagContext duplicate = tagMap.put(new Bytes(tag), added);
assert duplicate == null; assert duplicate == null;
} }
key.erase();
// Create a removal context to remove the window and the tags later // Create a removal context to remove the window and the tags later
RemovalContext r = new RemovalContext(window, secret, alice); RemovalContext r = new RemovalContext(window, secret, alice);
removalMap.put(new RemovalKey(contactId, period), r); removalMap.put(new RemovalKey(contactId, period), r);
@@ -128,7 +124,6 @@ class TransportTagRecogniser {
TagContext removed = tagMap.remove(new Bytes(tag)); TagContext removed = tagMap.remove(new Bytes(tag));
assert removed != null; assert removed != null;
} }
key.erase();
} }
void removeSecrets(ContactId c) { void removeSecrets(ContactId c) {

View File

@@ -0,0 +1,235 @@
package org.briarproject.transport;
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.briarproject.api.Bytes;
import org.briarproject.api.ContactId;
import org.briarproject.api.TransportId;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException;
import org.briarproject.api.transport.StreamContext;
import org.briarproject.api.transport.TemporarySecret;
// FIXME: Don't make alien calls with a lock held
/**
* A {@link org.briarproject.api.transport.TagRecogniser TagRecogniser} for a
* specific transport.
*/
class TransportTagRecogniser {
private final CryptoComponent crypto;
private final DatabaseComponent db;
private final TransportId transportId;
private final Map<Bytes, TagContext> tagMap;
private final Map<RemovalKey, RemovalContext> removalMap;
private final Lock synchLock = new ReentrantLock();
TransportTagRecogniser(CryptoComponent crypto, DatabaseComponent db,
TransportId transportId) {
this.crypto = crypto;
this.db = db;
this.transportId = transportId;
tagMap = new HashMap<Bytes, TagContext>();
removalMap = new HashMap<RemovalKey, RemovalContext>();
}
StreamContext recogniseTag(byte[] tag) throws DbException {
synchLock.lock();
try{
TagContext t = tagMap.remove(new Bytes(tag));
if(t == null) return null; // The tag was not expected
// Update the reordering window and the expected tags
SecretKey key = crypto.deriveTagKey(t.secret, !t.alice);
for(long streamNumber : t.window.setSeen(t.streamNumber)) {
byte[] tag1 = new byte[TAG_LENGTH];
crypto.encodeTag(tag1, key, streamNumber);
if(streamNumber < t.streamNumber) {
TagContext removed = tagMap.remove(new Bytes(tag1));
assert removed != null;
} else {
TagContext added = new TagContext(t, streamNumber);
TagContext duplicate = tagMap.put(new Bytes(tag1), added);
assert duplicate == null;
}
}
key.erase();
// Store the updated reordering window in the DB
db.setReorderingWindow(t.contactId, transportId, t.period,
t.window.getCentre(), t.window.getBitmap());
// Clone the secret - the key manager will erase the original
byte[] secret = t.secret.clone();
return new StreamContext(t.contactId, transportId, secret,
t.streamNumber, t.alice);
}
finally{
synchLock.unlock();
}
<<<<<<< HEAD
=======
// Store the updated reordering window in the DB
db.setReorderingWindow(t.contactId, transportId, t.period,
t.window.getCentre(), t.window.getBitmap());
return new StreamContext(t.contactId, transportId, t.secret,
t.streamNumber, t.alice);
>>>>>>> theSource
}
void addSecret(TemporarySecret s) {
synchLock.lock();
try{
ContactId contactId = s.getContactId();
boolean alice = s.getAlice();
long period = s.getPeriod();
byte[] secret = s.getSecret();
long centre = s.getWindowCentre();
byte[] bitmap = s.getWindowBitmap();
// Create the reordering window and the expected tags
SecretKey key = crypto.deriveTagKey(secret, !alice);
ReorderingWindow window = new ReorderingWindow(centre, bitmap);
for(long streamNumber : window.getUnseen()) {
byte[] tag = new byte[TAG_LENGTH];
crypto.encodeTag(tag, key, streamNumber);
TagContext added = new TagContext(contactId, alice, period,
secret, window, streamNumber);
TagContext duplicate = tagMap.put(new Bytes(tag), added);
assert duplicate == null;
}
key.erase();
// Create a removal context to remove the window and the tags later
RemovalContext r = new RemovalContext(window, secret, alice);
removalMap.put(new RemovalKey(contactId, period), r);
}
finally{
synchLock.unlock();
}
<<<<<<< HEAD
=======
// Create a removal context to remove the window and the tags later
RemovalContext r = new RemovalContext(window, secret, alice);
removalMap.put(new RemovalKey(contactId, period), r);
>>>>>>> theSource
}
void removeSecret(ContactId contactId, long period) {
synchLock.lock();
try{
RemovalKey k = new RemovalKey(contactId, period);
RemovalContext removed = removalMap.remove(k);
if(removed == null) throw new IllegalArgumentException();
removeSecret(removed);
}
finally{
synchLock.unlock();
}
}
private void removeSecret(RemovalContext r) {
// Remove the expected tags
SecretKey key = crypto.deriveTagKey(r.secret, !r.alice);
byte[] tag = new byte[TAG_LENGTH];
for(long streamNumber : r.window.getUnseen()) {
crypto.encodeTag(tag, key, streamNumber);
TagContext removed = tagMap.remove(new Bytes(tag));
assert removed != null;
}
}
void removeSecrets(ContactId c) {
synchLock.lock();
try{
Collection<RemovalKey> keysToRemove = new ArrayList<RemovalKey>();
for(RemovalKey k : removalMap.keySet())
if(k.contactId.equals(c)) keysToRemove.add(k);
for(RemovalKey k : keysToRemove) removeSecret(k.contactId, k.period);
}
finally{
synchLock.unlock();
}
}
void removeSecrets() {
synchLock.lock();
try{
for(RemovalContext r : removalMap.values()) removeSecret(r);
assert tagMap.isEmpty();
removalMap.clear();
}
finally{
synchLock.unlock();
}
}
private static class TagContext {
private final ContactId contactId;
private final boolean alice;
private final long period;
private final byte[] secret;
private final ReorderingWindow window;
private final long streamNumber;
private TagContext(ContactId contactId, boolean alice, long period,
byte[] secret, ReorderingWindow window, long streamNumber) {
this.contactId = contactId;
this.alice = alice;
this.period = period;
this.secret = secret;
this.window = window;
this.streamNumber = streamNumber;
}
private TagContext(TagContext t, long streamNumber) {
this(t.contactId, t.alice, t.period, t.secret, t.window,
streamNumber);
}
}
private static class RemovalKey {
private final ContactId contactId;
private final long period;
private RemovalKey(ContactId contactId, long period) {
this.contactId = contactId;
this.period = period;
}
@Override
public int hashCode() {
return contactId.hashCode() ^ (int) (period ^ (period >>> 32));
}
@Override
public boolean equals(Object o) {
if(o instanceof RemovalKey) {
RemovalKey k = (RemovalKey) o;
return contactId.equals(k.contactId) && period == k.period;
}
return false;
}
}
private static class RemovalContext {
private final ReorderingWindow window;
private final byte[] secret;
private final boolean alice;
private RemovalContext(ReorderingWindow window, byte[] secret,
boolean alice) {
this.window = window;
this.secret = secret;
this.alice = alice;
}
}
}

View File

@@ -48,10 +48,6 @@ public class ByteUtils {
| ((b[offset + 2] & 0xFFL) << 8) | (b[offset + 3] & 0xFFL); | ((b[offset + 2] & 0xFFL) << 8) | (b[offset + 3] & 0xFFL);
} }
public static void erase(byte[] b) {
for(int i = 0; i < b.length; i++) b[i] = 0;
}
public static int readUint(byte[] b, int bits) { public static int readUint(byte[] b, int bits) {
if(b.length << 3 < bits) throw new IllegalArgumentException(); if(b.length << 3 < bits) throw new IllegalArgumentException();
int result = 0; int result = 0;

View File

@@ -46,8 +46,7 @@ class BluetoothPlugin implements DuplexPlugin {
private final Clock clock; private final Clock clock;
private final SecureRandom secureRandom; private final SecureRandom secureRandom;
private final DuplexPluginCallback callback; private final DuplexPluginCallback callback;
private final int maxFrameLength; private final int maxLatency, pollingInterval;
private final long maxLatency, pollingInterval;
private final Semaphore discoverySemaphore = new Semaphore(1); private final Semaphore discoverySemaphore = new Semaphore(1);
private volatile boolean running = false; private volatile boolean running = false;
@@ -55,13 +54,12 @@ class BluetoothPlugin implements DuplexPlugin {
private volatile LocalDevice localDevice = null; private volatile LocalDevice localDevice = null;
BluetoothPlugin(Executor ioExecutor, Clock clock, SecureRandom secureRandom, BluetoothPlugin(Executor ioExecutor, Clock clock, SecureRandom secureRandom,
DuplexPluginCallback callback, int maxFrameLength, long maxLatency, DuplexPluginCallback callback, int maxLatency,
long pollingInterval) { int pollingInterval) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.clock = clock; this.clock = clock;
this.secureRandom = secureRandom; this.secureRandom = secureRandom;
this.callback = callback; this.callback = callback;
this.maxFrameLength = maxFrameLength;
this.maxLatency = maxLatency; this.maxLatency = maxLatency;
this.pollingInterval = pollingInterval; this.pollingInterval = pollingInterval;
} }
@@ -70,12 +68,13 @@ class BluetoothPlugin implements DuplexPlugin {
return ID; return ID;
} }
public int getMaxFrameLength() { public int getMaxLatency() {
return maxFrameLength; return maxLatency;
} }
public long getMaxLatency() { public int getMaxIdleTime() {
return maxLatency; // Bluetooth detects dead connections so we don't need keepalives
return Integer.MAX_VALUE;
} }
public boolean start() throws IOException { public boolean start() throws IOException {
@@ -181,7 +180,7 @@ class BluetoothPlugin implements DuplexPlugin {
return true; return true;
} }
public long getPollingInterval() { public int getPollingInterval() {
return pollingInterval; return pollingInterval;
} }

View File

@@ -12,9 +12,8 @@ import org.briarproject.system.SystemClock;
public class BluetoothPluginFactory implements DuplexPluginFactory { public class BluetoothPluginFactory implements DuplexPluginFactory {
private static final int MAX_FRAME_LENGTH = 1024; private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final long MAX_LATENCY = 60 * 1000; // 1 minute private static final int POLLING_INTERVAL = 3 * 60 * 1000; // 3 minutes
private static final long POLLING_INTERVAL = 3 * 60 * 1000; // 3 minutes
private final Executor ioExecutor; private final Executor ioExecutor;
private final SecureRandom secureRandom; private final SecureRandom secureRandom;
@@ -33,6 +32,6 @@ public class BluetoothPluginFactory implements DuplexPluginFactory {
public DuplexPlugin createPlugin(DuplexPluginCallback callback) { public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
return new BluetoothPlugin(ioExecutor, clock, secureRandom, callback, return new BluetoothPlugin(ioExecutor, clock, secureRandom, callback,
MAX_FRAME_LENGTH, MAX_LATENCY, POLLING_INTERVAL); MAX_LATENCY, POLLING_INTERVAL);
} }
} }

View File

@@ -39,10 +39,6 @@ class BluetoothTransportConnection implements DuplexTransportConnection {
private class Reader implements TransportConnectionReader { private class Reader implements TransportConnectionReader {
public int getMaxFrameLength() {
return plugin.getMaxFrameLength();
}
public long getMaxLatency() { public long getMaxLatency() {
return plugin.getMaxLatency(); return plugin.getMaxLatency();
} }
@@ -60,12 +56,12 @@ class BluetoothTransportConnection implements DuplexTransportConnection {
private class Writer implements TransportConnectionWriter { private class Writer implements TransportConnectionWriter {
public int getMaxFrameLength() { public int getMaxLatency() {
return plugin.getMaxFrameLength(); return plugin.getMaxLatency();
} }
public long getMaxLatency() { public int getMaxIdleTime() {
return plugin.getMaxLatency(); return plugin.getMaxIdleTime();
} }
public long getCapacity() { public long getCapacity() {

View File

@@ -17,7 +17,7 @@ class PollingRemovableDriveMonitor implements RemovableDriveMonitor, Runnable {
private final Executor ioExecutor; private final Executor ioExecutor;
private final RemovableDriveFinder finder; private final RemovableDriveFinder finder;
private final long pollingInterval; private final int pollingInterval;
private volatile boolean running = false; private volatile boolean running = false;
private volatile Callback callback = null; private volatile Callback callback = null;
@@ -27,7 +27,7 @@ class PollingRemovableDriveMonitor implements RemovableDriveMonitor, Runnable {
public PollingRemovableDriveMonitor(Executor ioExecutor, public PollingRemovableDriveMonitor(Executor ioExecutor,
RemovableDriveFinder finder, long pollingInterval) { RemovableDriveFinder finder, int pollingInterval) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.finder = finder; this.finder = finder;
this.pollingInterval = pollingInterval; this.pollingInterval = pollingInterval;

View File

@@ -0,0 +1,83 @@
package org.briarproject.plugins.file;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
class PollingRemovableDriveMonitor implements RemovableDriveMonitor, Runnable {
private static final Logger LOG =
Logger.getLogger(PollingRemovableDriveMonitor.class.getName());
private final Executor ioExecutor;
private final RemovableDriveFinder finder;
<<<<<<< HEAD
private final long pollingInterval;
=======
private final int pollingInterval;
private final Object pollingLock = new Object();
>>>>>>> theSource
private volatile boolean running = false;
private volatile Callback callback = null;
private final Lock pollingLock = new ReentrantLock();
private final Condition stopPolling = pollingLock.newCondition();
public PollingRemovableDriveMonitor(Executor ioExecutor,
RemovableDriveFinder finder, int pollingInterval) {
this.ioExecutor = ioExecutor;
this.finder = finder;
this.pollingInterval = pollingInterval;
}
public void start(Callback callback) throws IOException {
this.callback = callback;
running = true;
ioExecutor.execute(this);
}
public void stop() throws IOException {
running = false;
pollingLock.lock();
try {
stopPolling.signalAll();
}
finally {
pollingLock.unlock();
}
}
public void run() {
try {
Collection<File> drives = finder.findRemovableDrives();
while(running) {
pollingLock.lock();
try {
stopPolling.await(pollingInterval, TimeUnit.MILLISECONDS);
}
finally{
pollingLock.unlock();
}
if(!running) return;
Collection<File> newDrives = finder.findRemovableDrives();
for(File f : newDrives) {
if(!drives.contains(f)) callback.driveInserted(f);
}
drives = newDrives;
}
} catch(InterruptedException e) {
LOG.warning("Interrupted while waiting to poll");
Thread.currentThread().interrupt();
} catch(IOException e) {
callback.exceptionThrown(e);
}
}
}

View File

@@ -29,9 +29,8 @@ implements RemovableDriveMonitor.Callback {
RemovableDrivePlugin(Executor ioExecutor, FileUtils fileUtils, RemovableDrivePlugin(Executor ioExecutor, FileUtils fileUtils,
SimplexPluginCallback callback, RemovableDriveFinder finder, SimplexPluginCallback callback, RemovableDriveFinder finder,
RemovableDriveMonitor monitor, int maxFrameLength, RemovableDriveMonitor monitor, int maxLatency) {
long maxLatency) { super(ioExecutor, fileUtils, callback, maxLatency);
super(ioExecutor, fileUtils, callback, maxFrameLength, maxLatency);
this.finder = finder; this.finder = finder;
this.monitor = monitor; this.monitor = monitor;
} }
@@ -55,7 +54,7 @@ implements RemovableDriveMonitor.Callback {
return false; return false;
} }
public long getPollingInterval() { public int getPollingInterval() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }

View File

@@ -1,7 +1,5 @@
package org.briarproject.plugins.file; package org.briarproject.plugins.file;
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import org.briarproject.api.TransportId; import org.briarproject.api.TransportId;
@@ -14,8 +12,8 @@ import org.briarproject.util.OsUtils;
public class RemovableDrivePluginFactory implements SimplexPluginFactory { public class RemovableDrivePluginFactory implements SimplexPluginFactory {
// Maximum latency 14 days (Royal Mail or lackadaisical carrier pigeon) // Maximum latency 14 days (Royal Mail or lackadaisical carrier pigeon)
private static final long MAX_LATENCY = 14 * 24 * 60 * 60 * 1000; private static final int MAX_LATENCY = 14 * 24 * 60 * 60 * 1000;
private static final long POLLING_INTERVAL = 10 * 1000; // 10 seconds private static final int POLLING_INTERVAL = 10 * 1000; // 10 seconds
private final Executor ioExecutor; private final Executor ioExecutor;
private final FileUtils fileUtils; private final FileUtils fileUtils;
@@ -52,6 +50,6 @@ public class RemovableDrivePluginFactory implements SimplexPluginFactory {
return null; return null;
} }
return new RemovableDrivePlugin(ioExecutor, fileUtils, callback, return new RemovableDrivePlugin(ioExecutor, fileUtils, callback,
finder, monitor, MAX_FRAME_LENGTH, MAX_LATENCY); finder, monitor, MAX_LATENCY);
} }
} }

View File

@@ -32,33 +32,30 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
private final ModemFactory modemFactory; private final ModemFactory modemFactory;
private final SerialPortList serialPortList; private final SerialPortList serialPortList;
private final DuplexPluginCallback callback; private final DuplexPluginCallback callback;
private final int maxFrameLength; private final int maxLatency;
private final long maxLatency, pollingInterval;
private volatile boolean running = false; private volatile boolean running = false;
private volatile Modem modem = null; private volatile Modem modem = null;
ModemPlugin(ModemFactory modemFactory, SerialPortList serialPortList, ModemPlugin(ModemFactory modemFactory, SerialPortList serialPortList,
DuplexPluginCallback callback, int maxFrameLength, long maxLatency, DuplexPluginCallback callback, int maxLatency) {
long pollingInterval) {
this.modemFactory = modemFactory; this.modemFactory = modemFactory;
this.serialPortList = serialPortList; this.serialPortList = serialPortList;
this.callback = callback; this.callback = callback;
this.maxFrameLength = maxFrameLength;
this.maxLatency = maxLatency; this.maxLatency = maxLatency;
this.pollingInterval = pollingInterval;
} }
public TransportId getId() { public TransportId getId() {
return ID; return ID;
} }
public int getMaxFrameLength() { public int getMaxLatency() {
return maxFrameLength; return maxLatency;
} }
public long getMaxLatency() { public int getMaxIdleTime() {
return maxLatency; // FIXME: Do we need keepalives for this transport?
return Integer.MAX_VALUE;
} }
public boolean start() { public boolean start() {
@@ -98,8 +95,8 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
return false; return false;
} }
public long getPollingInterval() { public int getPollingInterval() {
return pollingInterval; throw new UnsupportedOperationException();
} }
public void poll(Collection<ContactId> connected) { public void poll(Collection<ContactId> connected) {
@@ -197,10 +194,6 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
private class Reader implements TransportConnectionReader { private class Reader implements TransportConnectionReader {
public int getMaxFrameLength() {
return maxFrameLength;
}
public long getMaxLatency() { public long getMaxLatency() {
return maxLatency; return maxLatency;
} }
@@ -217,12 +210,12 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
private class Writer implements TransportConnectionWriter { private class Writer implements TransportConnectionWriter {
public int getMaxFrameLength() { public int getMaxLatency() {
return maxFrameLength; return getMaxLatency();
} }
public long getMaxLatency() { public int getMaxIdleTime() {
return maxLatency; return getMaxIdleTime();
} }
public long getCapacity() { public long getCapacity() {

View File

@@ -11,9 +11,7 @@ import org.briarproject.util.StringUtils;
public class ModemPluginFactory implements DuplexPluginFactory { public class ModemPluginFactory implements DuplexPluginFactory {
private static final int MAX_FRAME_LENGTH = 1024; private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final long MAX_LATENCY = 60 * 1000; // 1 minute
private static final long POLLING_INTERVAL = 60 * 60 * 1000; // 1 hour
private final ModemFactory modemFactory; private final ModemFactory modemFactory;
private final SerialPortList serialPortList; private final SerialPortList serialPortList;
@@ -33,6 +31,6 @@ public class ModemPluginFactory implements DuplexPluginFactory {
String enabled = callback.getConfig().get("enabled"); String enabled = callback.getConfig().get("enabled");
if(StringUtils.isNullOrEmpty(enabled)) return null; if(StringUtils.isNullOrEmpty(enabled)) return null;
return new ModemPlugin(modemFactory, serialPortList, callback, return new ModemPlugin(modemFactory, serialPortList, callback,
MAX_FRAME_LENGTH, MAX_LATENCY, POLLING_INTERVAL); MAX_LATENCY);
} }
} }

View File

@@ -100,8 +100,9 @@
<test name='org.briarproject.crypto.KeyDerivationTest'/> <test name='org.briarproject.crypto.KeyDerivationTest'/>
<test name='org.briarproject.crypto.KeyEncodingAndParsingTest'/> <test name='org.briarproject.crypto.KeyEncodingAndParsingTest'/>
<test name="org.briarproject.crypto.PasswordBasedKdfTest"/> <test name="org.briarproject.crypto.PasswordBasedKdfTest"/>
<test name="org.briarproject.crypto.PasswordStrengthEstimatorTest"/> <test name="org.briarproject.crypto.PasswordStrengthEstimatorImplTest"/>
<test name='org.briarproject.crypto.SecretKeyImplTest'/> <test name='org.briarproject.crypto.StreamDecrypterImplTest'/>
<test name='org.briarproject.crypto.StreamEncrypterImplTest'/>
<test name='org.briarproject.db.BasicH2Test'/> <test name='org.briarproject.db.BasicH2Test'/>
<test name='org.briarproject.db.DatabaseCleanerImplTest'/> <test name='org.briarproject.db.DatabaseCleanerImplTest'/>
<test name='org.briarproject.db.DatabaseComponentImplTest'/> <test name='org.briarproject.db.DatabaseComponentImplTest'/>
@@ -126,10 +127,8 @@
<test name='org.briarproject.serial.ReaderImplTest'/> <test name='org.briarproject.serial.ReaderImplTest'/>
<test name='org.briarproject.serial.WriterImplTest'/> <test name='org.briarproject.serial.WriterImplTest'/>
<test name='org.briarproject.system.LinuxSeedProviderTest'/> <test name='org.briarproject.system.LinuxSeedProviderTest'/>
<test name='org.briarproject.transport.IncomingEncryptionLayerTest'/>
<test name='org.briarproject.transport.KeyManagerImplTest'/> <test name='org.briarproject.transport.KeyManagerImplTest'/>
<test name='org.briarproject.transport.KeyRotationIntegrationTest'/> <test name='org.briarproject.transport.KeyRotationIntegrationTest'/>
<test name='org.briarproject.transport.OutgoingEncryptionLayerTest'/>
<test name='org.briarproject.transport.ReorderingWindowTest'/> <test name='org.briarproject.transport.ReorderingWindowTest'/>
<test name='org.briarproject.transport.StreamReaderImplTest'/> <test name='org.briarproject.transport.StreamReaderImplTest'/>
<test name='org.briarproject.transport.StreamWriterImplTest'/> <test name='org.briarproject.transport.StreamWriterImplTest'/>

View File

@@ -1,12 +1,12 @@
package org.briarproject; package org.briarproject;
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH; import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@@ -36,9 +36,7 @@ import org.briarproject.api.messaging.SubscriptionUpdate;
import org.briarproject.api.messaging.TransportUpdate; import org.briarproject.api.messaging.TransportUpdate;
import org.briarproject.api.messaging.UnverifiedMessage; import org.briarproject.api.messaging.UnverifiedMessage;
import org.briarproject.api.transport.StreamContext; import org.briarproject.api.transport.StreamContext;
import org.briarproject.api.transport.StreamReader;
import org.briarproject.api.transport.StreamReaderFactory; import org.briarproject.api.transport.StreamReaderFactory;
import org.briarproject.api.transport.StreamWriter;
import org.briarproject.api.transport.StreamWriterFactory; import org.briarproject.api.transport.StreamWriterFactory;
import org.briarproject.crypto.CryptoModule; import org.briarproject.crypto.CryptoModule;
import org.briarproject.db.DatabaseModule; import org.briarproject.db.DatabaseModule;
@@ -117,12 +115,12 @@ public class ProtocolIntegrationTest extends BriarTestCase {
private byte[] write() throws Exception { private byte[] write() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamContext ctx = new StreamContext(contactId, transportId, StreamContext ctx = new StreamContext(contactId, transportId, secret,
secret.clone(), 0, true); 0, true);
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(out, OutputStream streamWriter =
MAX_FRAME_LENGTH, ctx); streamWriterFactory.createStreamWriter(out, ctx);
PacketWriter packetWriter = packetWriterFactory.createPacketWriter( PacketWriter packetWriter = packetWriterFactory.createPacketWriter(
streamWriter.getOutputStream()); streamWriter);
packetWriter.writeAck(new Ack(messageIds)); packetWriter.writeAck(new Ack(messageIds));
@@ -140,7 +138,7 @@ public class ProtocolIntegrationTest extends BriarTestCase {
transportProperties, 1); transportProperties, 1);
packetWriter.writeTransportUpdate(tu); packetWriter.writeTransportUpdate(tu);
streamWriter.getOutputStream().flush(); streamWriter.flush();
return out.toByteArray(); return out.toByteArray();
} }
@@ -149,12 +147,12 @@ public class ProtocolIntegrationTest extends BriarTestCase {
byte[] tag = new byte[TAG_LENGTH]; byte[] tag = new byte[TAG_LENGTH];
assertEquals(TAG_LENGTH, in.read(tag, 0, TAG_LENGTH)); assertEquals(TAG_LENGTH, in.read(tag, 0, TAG_LENGTH));
// FIXME: Check that the expected tag was received // FIXME: Check that the expected tag was received
StreamContext ctx = new StreamContext(contactId, transportId, StreamContext ctx = new StreamContext(contactId, transportId, secret,
secret.clone(), 0, false); 0, false);
StreamReader streamReader = streamReaderFactory.createStreamReader(in, InputStream streamReader =
MAX_FRAME_LENGTH, ctx); streamReaderFactory.createStreamReader(in, ctx);
PacketReader packetReader = packetReaderFactory.createPacketReader( PacketReader packetReader = packetReaderFactory.createPacketReader(
streamReader.getInputStream()); streamReader);
// Read the ack // Read the ack
assertTrue(packetReader.hasAck()); assertTrue(packetReader.hasAck());

View File

@@ -30,9 +30,9 @@ public class KeyDerivationTest extends BriarTestCase {
keys.add(crypto.deriveTagKey(secret, true)); keys.add(crypto.deriveTagKey(secret, true));
keys.add(crypto.deriveTagKey(secret, false)); keys.add(crypto.deriveTagKey(secret, false));
for(int i = 0; i < 4; i++) { for(int i = 0; i < 4; i++) {
byte[] keyI = keys.get(i).getEncoded(); byte[] keyI = keys.get(i).getBytes();
for(int j = 0; j < 4; j++) { for(int j = 0; j < 4; j++) {
byte[] keyJ = keys.get(j).getEncoded(); byte[] keyJ = keys.get(j).getBytes();
assertEquals(i == j, Arrays.equals(keyI, keyJ)); assertEquals(i == j, Arrays.equals(keyI, keyJ));
} }
} }
@@ -59,9 +59,8 @@ public class KeyDerivationTest extends BriarTestCase {
@Test @Test
public void testStreamNumberAffectsDerivation() { public void testStreamNumberAffectsDerivation() {
List<byte[]> secrets = new ArrayList<byte[]>(); List<byte[]> secrets = new ArrayList<byte[]>();
for(int i = 0; i < 20; i++) { for(int i = 0; i < 20; i++)
secrets.add(crypto.deriveNextSecret(secret.clone(), i)); secrets.add(crypto.deriveNextSecret(secret, i));
}
for(int i = 0; i < 20; i++) { for(int i = 0; i < 20; i++) {
byte[] secretI = secrets.get(i); byte[] secretI = secrets.get(i);
for(int j = 0; j < 20; j++) { for(int j = 0; j < 20; j++) {

View File

@@ -18,7 +18,7 @@ public class PasswordBasedKdfTest extends BriarTestCase {
Random random = new Random(); Random random = new Random();
byte[] input = new byte[1234]; byte[] input = new byte[1234];
random.nextBytes(input); random.nextBytes(input);
char[] password = "password".toCharArray(); String password = "password";
byte[] ciphertext = crypto.encryptWithPassword(input, password); byte[] ciphertext = crypto.encryptWithPassword(input, password);
byte[] output = crypto.decryptWithPassword(ciphertext, password); byte[] output = crypto.decryptWithPassword(ciphertext, password);
assertArrayEquals(input, output); assertArrayEquals(input, output);
@@ -29,7 +29,7 @@ public class PasswordBasedKdfTest extends BriarTestCase {
Random random = new Random(); Random random = new Random();
byte[] input = new byte[1234]; byte[] input = new byte[1234];
random.nextBytes(input); random.nextBytes(input);
char[] password = "password".toCharArray(); String password = "password";
byte[] ciphertext = crypto.encryptWithPassword(input, password); byte[] ciphertext = crypto.encryptWithPassword(input, password);
// Modify the ciphertext // Modify the ciphertext
int position = random.nextInt(ciphertext.length); int position = random.nextInt(ciphertext.length);

View File

@@ -6,24 +6,23 @@ import org.briarproject.BriarTestCase;
import org.briarproject.api.crypto.PasswordStrengthEstimator; import org.briarproject.api.crypto.PasswordStrengthEstimator;
import org.junit.Test; import org.junit.Test;
public class PasswordStrengthEstimatorTest extends BriarTestCase { public class PasswordStrengthEstimatorImplTest extends BriarTestCase {
@Test @Test
public void testWeakPasswords() { public void testWeakPasswords() {
PasswordStrengthEstimator e = new PasswordStrengthEstimatorImpl(); PasswordStrengthEstimator e = new PasswordStrengthEstimatorImpl();
assertTrue(e.estimateStrength("".toCharArray()) < QUITE_STRONG); assertTrue(e.estimateStrength("") < QUITE_STRONG);
assertTrue(e.estimateStrength("password".toCharArray()) < QUITE_STRONG); assertTrue(e.estimateStrength("password") < QUITE_STRONG);
assertTrue(e.estimateStrength("letmein".toCharArray()) < QUITE_STRONG); assertTrue(e.estimateStrength("letmein") < QUITE_STRONG);
assertTrue(e.estimateStrength("123456".toCharArray()) < QUITE_STRONG); assertTrue(e.estimateStrength("123456") < QUITE_STRONG);
} }
@Test @Test
public void testStrongPasswords() { public void testStrongPasswords() {
PasswordStrengthEstimator e = new PasswordStrengthEstimatorImpl(); PasswordStrengthEstimator e = new PasswordStrengthEstimatorImpl();
// Industry standard // Industry standard
assertTrue(e.estimateStrength("Tr0ub4dor&3".toCharArray()) assertTrue(e.estimateStrength("Tr0ub4dor&3") > QUITE_STRONG);
> QUITE_STRONG); assertTrue(e.estimateStrength("correcthorsebatterystaple")
assertTrue(e.estimateStrength("correcthorsebatterystaple".toCharArray())
> QUITE_STRONG); > QUITE_STRONG);
} }
} }

View File

@@ -1,27 +0,0 @@
package org.briarproject.crypto;
import static org.junit.Assert.assertArrayEquals;
import java.util.Random;
import org.briarproject.BriarTestCase;
import org.briarproject.api.crypto.SecretKey;
import org.junit.Test;
public class SecretKeyImplTest extends BriarTestCase {
private static final int KEY_BYTES = 32; // 256 bits
@Test
public void testCopiesAreErased() {
byte[] master = new byte[KEY_BYTES];
new Random().nextBytes(master);
SecretKey k = new SecretKeyImpl(master);
byte[] copy = k.getEncoded();
assertArrayEquals(master, copy);
k.erase();
byte[] blank = new byte[KEY_BYTES];
assertArrayEquals(blank, master);
assertArrayEquals(blank, copy);
}
}

View File

@@ -0,0 +1,37 @@
package org.briarproject.crypto;
import org.briarproject.BriarTestCase;
import org.junit.Test;
public class StreamDecrypterImplTest extends BriarTestCase {
@Test
public void testReadValidFrames() throws Exception {
// FIXME
}
@Test
public void testTruncatedFrameThrowsException() throws Exception {
// FIXME
}
@Test
public void testModifiedFrameThrowsException() throws Exception {
// FIXME
}
@Test
public void testInvalidPayloadLengthThrowsException() throws Exception {
// FIXME
}
@Test
public void testNonZeroPaddingThrowsException() throws Exception {
// FIXME
}
@Test
public void testCannotReadBeyondFinalFrame() throws Exception {
// FIXME
}
}

View File

@@ -0,0 +1,255 @@
package org.briarproject.crypto;
import static org.briarproject.api.transport.TransportConstants.HEADER_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
import static org.junit.Assert.assertArrayEquals;
import java.io.ByteArrayOutputStream;
import java.util.Random;
import org.briarproject.BriarTestCase;
import org.briarproject.api.crypto.AuthenticatedCipher;
import org.briarproject.api.crypto.SecretKey;
import org.junit.Test;
public class StreamEncrypterImplTest extends BriarTestCase {
private final AuthenticatedCipher frameCipher;
private final SecretKey frameKey;
private final byte[] tag;
public StreamEncrypterImplTest() {
frameCipher = new TestAuthenticatedCipher();
frameKey = new SecretKey(new byte[32]);
tag = new byte[TAG_LENGTH];
new Random().nextBytes(tag);
}
@Test
public void testWriteUnpaddedNonFinalFrameWithTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, frameCipher,
frameKey, tag);
int payloadLength = 123;
byte[] payload = new byte[payloadLength];
new Random().nextBytes(payload);
s.writeFrame(payload, payloadLength, 0, false);
byte[] header = new byte[HEADER_LENGTH];
FrameEncoder.encodeHeader(header, false, payloadLength, 0);
byte[] expected = new byte[TAG_LENGTH + HEADER_LENGTH + payloadLength
+ MAC_LENGTH];
System.arraycopy(tag, 0, expected, 0, TAG_LENGTH);
System.arraycopy(header, 0, expected, TAG_LENGTH, HEADER_LENGTH);
System.arraycopy(payload, 0, expected, TAG_LENGTH + HEADER_LENGTH,
payloadLength);
assertArrayEquals(expected, out.toByteArray());
}
@Test
public void testWriteUnpaddedFinalFrameWithTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, frameCipher,
frameKey, tag);
int payloadLength = 123;
byte[] payload = new byte[payloadLength];
new Random().nextBytes(payload);
s.writeFrame(payload, payloadLength, 0, true);
byte[] header = new byte[HEADER_LENGTH];
FrameEncoder.encodeHeader(header, true, payloadLength, 0);
byte[] expected = new byte[TAG_LENGTH + HEADER_LENGTH + payloadLength
+ MAC_LENGTH];
System.arraycopy(tag, 0, expected, 0, TAG_LENGTH);
System.arraycopy(header, 0, expected, TAG_LENGTH, HEADER_LENGTH);
System.arraycopy(payload, 0, expected, TAG_LENGTH + HEADER_LENGTH,
payloadLength);
assertArrayEquals(expected, out.toByteArray());
}
@Test
public void testWriteUnpaddedNonFinalFrameWithoutTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, frameCipher,
frameKey, null);
int payloadLength = 123;
byte[] payload = new byte[payloadLength];
new Random().nextBytes(payload);
s.writeFrame(payload, payloadLength, 0, false);
byte[] header = new byte[HEADER_LENGTH];
FrameEncoder.encodeHeader(header, false, payloadLength, 0);
byte[] expected = new byte[HEADER_LENGTH + payloadLength + MAC_LENGTH];
System.arraycopy(header, 0, expected, 0, HEADER_LENGTH);
System.arraycopy(payload, 0, expected, HEADER_LENGTH, payloadLength);
assertArrayEquals(expected, out.toByteArray());
}
@Test
public void testWriteUnpaddedFinalFrameWithoutTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, frameCipher,
frameKey, null);
int payloadLength = 123;
byte[] payload = new byte[payloadLength];
new Random().nextBytes(payload);
s.writeFrame(payload, payloadLength, 0, true);
byte[] header = new byte[HEADER_LENGTH];
FrameEncoder.encodeHeader(header, true, payloadLength, 0);
byte[] expected = new byte[HEADER_LENGTH + payloadLength + MAC_LENGTH];
System.arraycopy(header, 0, expected, 0, HEADER_LENGTH);
System.arraycopy(payload, 0, expected, HEADER_LENGTH, payloadLength);
assertArrayEquals(expected, out.toByteArray());
}
@Test
public void testWritePaddedNonFinalFrameWithTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, frameCipher,
frameKey, tag);
int payloadLength = 123, paddingLength = 234;
byte[] payload = new byte[payloadLength];
new Random().nextBytes(payload);
s.writeFrame(payload, payloadLength, paddingLength, false);
byte[] header = new byte[HEADER_LENGTH];
FrameEncoder.encodeHeader(header, false, payloadLength, paddingLength);
byte[] expected = new byte[TAG_LENGTH + HEADER_LENGTH + payloadLength
+ paddingLength + MAC_LENGTH];
System.arraycopy(tag, 0, expected, 0, TAG_LENGTH);
System.arraycopy(header, 0, expected, TAG_LENGTH, HEADER_LENGTH);
System.arraycopy(payload, 0, expected, TAG_LENGTH + HEADER_LENGTH,
payloadLength);
assertArrayEquals(expected, out.toByteArray());
}
@Test
public void testWritePaddedFinalFrameWithTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, frameCipher,
frameKey, tag);
int payloadLength = 123, paddingLength = 234;
byte[] payload = new byte[payloadLength];
new Random().nextBytes(payload);
s.writeFrame(payload, payloadLength, paddingLength, true);
byte[] header = new byte[HEADER_LENGTH];
FrameEncoder.encodeHeader(header, true, payloadLength, paddingLength);
byte[] expected = new byte[TAG_LENGTH + HEADER_LENGTH + payloadLength
+ paddingLength + MAC_LENGTH];
System.arraycopy(tag, 0, expected, 0, TAG_LENGTH);
System.arraycopy(header, 0, expected, TAG_LENGTH, HEADER_LENGTH);
System.arraycopy(payload, 0, expected, TAG_LENGTH + HEADER_LENGTH,
payloadLength);
assertArrayEquals(expected, out.toByteArray());
}
@Test
public void testWritePaddedNonFinalFrameWithoutTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, frameCipher,
frameKey, null);
int payloadLength = 123, paddingLength = 234;
byte[] payload = new byte[payloadLength];
new Random().nextBytes(payload);
s.writeFrame(payload, payloadLength, paddingLength, false);
byte[] header = new byte[HEADER_LENGTH];
FrameEncoder.encodeHeader(header, false, payloadLength, paddingLength);
byte[] expected = new byte[HEADER_LENGTH + payloadLength
+ paddingLength + MAC_LENGTH];
System.arraycopy(header, 0, expected, 0, HEADER_LENGTH);
System.arraycopy(payload, 0, expected, HEADER_LENGTH, payloadLength);
assertArrayEquals(expected, out.toByteArray());
}
@Test
public void testWritePaddedFinalFrameWithoutTag() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, frameCipher,
frameKey, null);
int payloadLength = 123, paddingLength = 234;
byte[] payload = new byte[payloadLength];
new Random().nextBytes(payload);
s.writeFrame(payload, payloadLength, paddingLength, true);
byte[] header = new byte[HEADER_LENGTH];
FrameEncoder.encodeHeader(header, true, payloadLength, paddingLength);
byte[] expected = new byte[HEADER_LENGTH + payloadLength
+ paddingLength + MAC_LENGTH];
System.arraycopy(header, 0, expected, 0, HEADER_LENGTH);
System.arraycopy(payload, 0, expected, HEADER_LENGTH, payloadLength);
assertArrayEquals(expected, out.toByteArray());
}
@Test
public void testWriteTwoFrames() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, frameCipher,
frameKey, null);
int payloadLength = 123, paddingLength = 234;
byte[] payload = new byte[payloadLength];
new Random().nextBytes(payload);
int payloadLength1 = 345, paddingLength1 = 456;
byte[] payload1 = new byte[payloadLength1];
new Random().nextBytes(payload1);
s.writeFrame(payload, payloadLength, paddingLength, false);
s.writeFrame(payload1, payloadLength1, paddingLength1, true);
byte[] header = new byte[HEADER_LENGTH];
FrameEncoder.encodeHeader(header, false, payloadLength, paddingLength);
byte[] header1 = new byte[HEADER_LENGTH];
FrameEncoder.encodeHeader(header1, true, payloadLength1,
paddingLength1);
byte[] expected = new byte[HEADER_LENGTH + payloadLength
+ paddingLength + MAC_LENGTH
+ HEADER_LENGTH + payloadLength1
+ paddingLength1 + MAC_LENGTH];
System.arraycopy(header, 0, expected, 0, HEADER_LENGTH);
System.arraycopy(payload, 0, expected, HEADER_LENGTH, payloadLength);
System.arraycopy(header1, 0, expected, HEADER_LENGTH + payloadLength
+ paddingLength + MAC_LENGTH, HEADER_LENGTH);
System.arraycopy(payload1, 0, expected, HEADER_LENGTH + payloadLength
+ paddingLength + MAC_LENGTH + HEADER_LENGTH, payloadLength1);
assertArrayEquals(expected, out.toByteArray());
}
@Test
public void testFlushWritesTagIfNotAlreadyWritten() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, frameCipher,
frameKey, tag);
s.flush();
assertArrayEquals(tag, out.toByteArray());
}
@Test
public void testFlushDoesNotWriteTagIfAlreadyWritten() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, frameCipher,
frameKey, tag);
s.flush();
s.flush();
assertArrayEquals(tag, out.toByteArray());
}
@Test
public void testFlushDoesNotWriteTagIfNull() throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamEncrypterImpl s = new StreamEncrypterImpl(out, frameCipher,
frameKey, null);
s.flush();
assertEquals(0, out.size());
}
}

View File

@@ -0,0 +1,45 @@
package org.briarproject.crypto;
import static org.briarproject.api.transport.TransportConstants.MAC_LENGTH;
import java.security.GeneralSecurityException;
import org.briarproject.api.crypto.AuthenticatedCipher;
import org.briarproject.api.crypto.SecretKey;
class TestAuthenticatedCipher implements AuthenticatedCipher {
private static final int BLOCK_BYTES = 16;
private boolean encrypt = false;
public void init(boolean encrypt, SecretKey key, byte[] iv, byte[] aad)
throws GeneralSecurityException {
this.encrypt = encrypt;
}
public int process(byte[] input, int inputOff, int len, byte[] output,
int outputOff) throws GeneralSecurityException {
if(encrypt) {
System.arraycopy(input, inputOff, output, outputOff, len);
for(int i = 0; i < MAC_LENGTH; i++)
output[outputOff + len + i] = 0;
return len + MAC_LENGTH;
} else {
for(int i = 0; i < MAC_LENGTH; i++)
if(input[inputOff + len - MAC_LENGTH + i] != 0)
throw new GeneralSecurityException();
System.arraycopy(input, inputOff, output, outputOff,
len - MAC_LENGTH);
return len - MAC_LENGTH;
}
}
public int getMacLength() {
return MAC_LENGTH;
}
public int getBlockSize() {
return BLOCK_BYTES;
}
}

View File

@@ -75,6 +75,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
protected final Message message, message1; protected final Message message, message1;
protected final TransportId transportId; protected final TransportId transportId;
protected final TransportProperties transportProperties; protected final TransportProperties transportProperties;
protected final int maxLatency;
protected final ContactId contactId; protected final ContactId contactId;
protected final Contact contact; protected final Contact contact;
protected final Endpoint endpoint; protected final Endpoint endpoint;
@@ -102,6 +103,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
transportId = new TransportId("id"); transportId = new TransportId("id");
transportProperties = new TransportProperties(Collections.singletonMap( transportProperties = new TransportProperties(Collections.singletonMap(
"bar", "baz")); "bar", "baz"));
maxLatency = Integer.MAX_VALUE;
contactId = new ContactId(234); contactId = new ContactId(234);
contact = new Contact(contactId, author, localAuthorId); contact = new Contact(contactId, author, localAuthorId);
endpoint = new Endpoint(contactId, transportId, 123, true); endpoint = new Endpoint(contactId, transportId, 123, true);
@@ -691,11 +693,11 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
oneOf(database).getRawMessage(txn, messageId); oneOf(database).getRawMessage(txn, messageId);
will(returnValue(raw)); will(returnValue(raw));
oneOf(database).updateExpiryTime(txn, contactId, messageId, oneOf(database).updateExpiryTime(txn, contactId, messageId,
Long.MAX_VALUE); maxLatency);
oneOf(database).getRawMessage(txn, messageId1); oneOf(database).getRawMessage(txn, messageId1);
will(returnValue(raw1)); will(returnValue(raw1));
oneOf(database).updateExpiryTime(txn, contactId, messageId1, oneOf(database).updateExpiryTime(txn, contactId, messageId1,
Long.MAX_VALUE); maxLatency);
oneOf(database).lowerRequestedFlag(txn, contactId, ids); oneOf(database).lowerRequestedFlag(txn, contactId, ids);
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
@@ -703,7 +705,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
eventBus, shutdown); eventBus, shutdown);
assertEquals(messages, db.generateBatch(contactId, size * 2, assertEquals(messages, db.generateBatch(contactId, size * 2,
Long.MAX_VALUE)); maxLatency));
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -726,15 +728,15 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
oneOf(database).getMessagesToOffer(txn, contactId, 123); oneOf(database).getMessagesToOffer(txn, contactId, 123);
will(returnValue(ids)); will(returnValue(ids));
oneOf(database).updateExpiryTime(txn, contactId, messageId, oneOf(database).updateExpiryTime(txn, contactId, messageId,
Long.MAX_VALUE); maxLatency);
oneOf(database).updateExpiryTime(txn, contactId, messageId1, oneOf(database).updateExpiryTime(txn, contactId, messageId1,
Long.MAX_VALUE); maxLatency);
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, cleaner, DatabaseComponent db = createDatabaseComponent(database, cleaner,
eventBus, shutdown); eventBus, shutdown);
Offer o = db.generateOffer(contactId, 123, Long.MAX_VALUE); Offer o = db.generateOffer(contactId, 123, maxLatency);
assertEquals(ids, o.getMessageIds()); assertEquals(ids, o.getMessageIds());
context.assertIsSatisfied(); context.assertIsSatisfied();
@@ -791,11 +793,11 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
oneOf(database).getRawMessage(txn, messageId); oneOf(database).getRawMessage(txn, messageId);
will(returnValue(raw)); will(returnValue(raw));
oneOf(database).updateExpiryTime(txn, contactId, messageId, oneOf(database).updateExpiryTime(txn, contactId, messageId,
Long.MAX_VALUE); maxLatency);
oneOf(database).getRawMessage(txn, messageId1); oneOf(database).getRawMessage(txn, messageId1);
will(returnValue(raw1)); will(returnValue(raw1));
oneOf(database).updateExpiryTime(txn, contactId, messageId1, oneOf(database).updateExpiryTime(txn, contactId, messageId1,
Long.MAX_VALUE); maxLatency);
oneOf(database).lowerRequestedFlag(txn, contactId, ids); oneOf(database).lowerRequestedFlag(txn, contactId, ids);
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
@@ -803,7 +805,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
eventBus, shutdown); eventBus, shutdown);
assertEquals(messages, db.generateRequestedBatch(contactId, size * 2, assertEquals(messages, db.generateRequestedBatch(contactId, size * 2,
Long.MAX_VALUE)); maxLatency));
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -821,14 +823,14 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
will(returnValue(txn)); will(returnValue(txn));
oneOf(database).containsContact(txn, contactId); oneOf(database).containsContact(txn, contactId);
will(returnValue(true)); will(returnValue(true));
oneOf(database).getRetentionUpdate(txn, contactId, Long.MAX_VALUE); oneOf(database).getRetentionUpdate(txn, contactId, maxLatency);
will(returnValue(null)); will(returnValue(null));
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, cleaner, DatabaseComponent db = createDatabaseComponent(database, cleaner,
eventBus, shutdown); eventBus, shutdown);
assertNull(db.generateRetentionUpdate(contactId, Long.MAX_VALUE)); assertNull(db.generateRetentionUpdate(contactId, maxLatency));
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -846,15 +848,14 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
will(returnValue(txn)); will(returnValue(txn));
oneOf(database).containsContact(txn, contactId); oneOf(database).containsContact(txn, contactId);
will(returnValue(true)); will(returnValue(true));
oneOf(database).getRetentionUpdate(txn, contactId, Long.MAX_VALUE); oneOf(database).getRetentionUpdate(txn, contactId, maxLatency);
will(returnValue(new RetentionUpdate(0, 1))); will(returnValue(new RetentionUpdate(0, 1)));
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, cleaner, DatabaseComponent db = createDatabaseComponent(database, cleaner,
eventBus, shutdown); eventBus, shutdown);
RetentionUpdate u = db.generateRetentionUpdate(contactId, RetentionUpdate u = db.generateRetentionUpdate(contactId, maxLatency);
Long.MAX_VALUE);
assertEquals(0, u.getRetentionTime()); assertEquals(0, u.getRetentionTime());
assertEquals(1, u.getVersion()); assertEquals(1, u.getVersion());
@@ -874,15 +875,14 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
will(returnValue(txn)); will(returnValue(txn));
oneOf(database).containsContact(txn, contactId); oneOf(database).containsContact(txn, contactId);
will(returnValue(true)); will(returnValue(true));
oneOf(database).getSubscriptionUpdate(txn, contactId, oneOf(database).getSubscriptionUpdate(txn, contactId, maxLatency);
Long.MAX_VALUE);
will(returnValue(null)); will(returnValue(null));
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, cleaner, DatabaseComponent db = createDatabaseComponent(database, cleaner,
eventBus, shutdown); eventBus, shutdown);
assertNull(db.generateSubscriptionUpdate(contactId, Long.MAX_VALUE)); assertNull(db.generateSubscriptionUpdate(contactId, maxLatency));
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -900,8 +900,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
will(returnValue(txn)); will(returnValue(txn));
oneOf(database).containsContact(txn, contactId); oneOf(database).containsContact(txn, contactId);
will(returnValue(true)); will(returnValue(true));
oneOf(database).getSubscriptionUpdate(txn, contactId, oneOf(database).getSubscriptionUpdate(txn, contactId, maxLatency);
Long.MAX_VALUE);
will(returnValue(new SubscriptionUpdate(Arrays.asList(group), 1))); will(returnValue(new SubscriptionUpdate(Arrays.asList(group), 1)));
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
@@ -909,7 +908,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
eventBus, shutdown); eventBus, shutdown);
SubscriptionUpdate u = db.generateSubscriptionUpdate(contactId, SubscriptionUpdate u = db.generateSubscriptionUpdate(contactId,
Long.MAX_VALUE); maxLatency);
assertEquals(Arrays.asList(group), u.getGroups()); assertEquals(Arrays.asList(group), u.getGroups());
assertEquals(1, u.getVersion()); assertEquals(1, u.getVersion());
@@ -929,14 +928,14 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
will(returnValue(txn)); will(returnValue(txn));
oneOf(database).containsContact(txn, contactId); oneOf(database).containsContact(txn, contactId);
will(returnValue(true)); will(returnValue(true));
oneOf(database).getTransportUpdates(txn, contactId, Long.MAX_VALUE); oneOf(database).getTransportUpdates(txn, contactId, maxLatency);
will(returnValue(null)); will(returnValue(null));
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, cleaner, DatabaseComponent db = createDatabaseComponent(database, cleaner,
eventBus, shutdown); eventBus, shutdown);
assertNull(db.generateTransportUpdates(contactId, Long.MAX_VALUE)); assertNull(db.generateTransportUpdates(contactId, maxLatency));
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -954,7 +953,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
will(returnValue(txn)); will(returnValue(txn));
oneOf(database).containsContact(txn, contactId); oneOf(database).containsContact(txn, contactId);
will(returnValue(true)); will(returnValue(true));
oneOf(database).getTransportUpdates(txn, contactId, Long.MAX_VALUE); oneOf(database).getTransportUpdates(txn, contactId, maxLatency);
will(returnValue(Arrays.asList(new TransportUpdate(transportId, will(returnValue(Arrays.asList(new TransportUpdate(transportId,
transportProperties, 1)))); transportProperties, 1))));
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
@@ -963,7 +962,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
eventBus, shutdown); eventBus, shutdown);
Collection<TransportUpdate> updates = Collection<TransportUpdate> updates =
db.generateTransportUpdates(contactId, Long.MAX_VALUE); db.generateTransportUpdates(contactId, maxLatency);
assertNotNull(updates); assertNotNull(updates);
assertEquals(1, updates.size()); assertEquals(1, updates.size());
TransportUpdate u = updates.iterator().next(); TransportUpdate u = updates.iterator().next();

View File

@@ -32,32 +32,30 @@ public class ExponentialBackoffTest extends BriarTestCase {
assertEquals(now, fromNow - fromZero); assertEquals(now, fromNow - fromZero);
} }
@Test
public void testRoundTripTimeOverflow() {
long maxLatency = Long.MAX_VALUE / 2 + 1; // RTT will overflow
long expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 0);
assertEquals(Long.MAX_VALUE, expiry); // Overflow caught
}
@Test @Test
public void testTransmissionCountOverflow() { public void testTransmissionCountOverflow() {
long maxLatency = (Long.MAX_VALUE - 1) / 2; // RTT will not overflow int maxLatency = Integer.MAX_VALUE; // RTT will not overflow
long expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 0); long expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 0);
assertEquals(Long.MAX_VALUE - 1, expiry); // No overflow assertEquals(Integer.MAX_VALUE * 2L, expiry); // No overflow
expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 1); expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 31);
assertEquals(Integer.MAX_VALUE * (2L << 31), expiry); // No overflow
expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 32);
assertEquals(Long.MAX_VALUE, expiry); // Overflow caught assertEquals(Long.MAX_VALUE, expiry); // Overflow caught
expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 2); expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 33);
assertEquals(Long.MAX_VALUE, expiry); // Overflow caught assertEquals(Long.MAX_VALUE, expiry); // Overflow caught
} }
@Test @Test
public void testCurrentTimeOverflow() { public void testCurrentTimeOverflow() {
long maxLatency = (Long.MAX_VALUE - 1) / 2; // RTT will not overflow int maxLatency = Integer.MAX_VALUE; // RTT will not overflow
long expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 0); long now = Long.MAX_VALUE - (Integer.MAX_VALUE * (2L << 31));
long expiry = ExponentialBackoff.calculateExpiry(now, maxLatency, 0);
assertEquals(now + Integer.MAX_VALUE * 2L, expiry); // No overflow
expiry = ExponentialBackoff.calculateExpiry(now - 1, maxLatency, 31);
assertEquals(Long.MAX_VALUE - 1, expiry); // No overflow assertEquals(Long.MAX_VALUE - 1, expiry); // No overflow
expiry = ExponentialBackoff.calculateExpiry(1, maxLatency, 0); expiry = ExponentialBackoff.calculateExpiry(now, maxLatency, 31);
assertEquals(Long.MAX_VALUE, expiry); // No overflow assertEquals(Long.MAX_VALUE, expiry); // No overflow
expiry = ExponentialBackoff.calculateExpiry(2, maxLatency, 0); expiry = ExponentialBackoff.calculateExpiry(now + 1, maxLatency, 32);
assertEquals(Long.MAX_VALUE, expiry); // Overflow caught assertEquals(Long.MAX_VALUE, expiry); // Overflow caught
} }
} }

View File

@@ -381,7 +381,7 @@ public class H2DatabaseTest extends BriarTestCase {
assertTrue(it.hasNext()); assertTrue(it.hasNext());
assertEquals(messageId, it.next()); assertEquals(messageId, it.next());
assertFalse(it.hasNext()); assertFalse(it.hasNext());
db.updateExpiryTime(txn, contactId, messageId, Long.MAX_VALUE); db.updateExpiryTime(txn, contactId, messageId, Integer.MAX_VALUE);
// The message should no longer be sendable // The message should no longer be sendable
it = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE).iterator(); it = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE).iterator();
@@ -1109,7 +1109,8 @@ public class H2DatabaseTest extends BriarTestCase {
@Test @Test
public void testTemporarySecrets() throws Exception { public void testTemporarySecrets() throws Exception {
// Create an endpoint and four consecutive temporary secrets // Create an endpoint and four consecutive temporary secrets
long epoch = 123, latency = 234; long epoch = 123;
int latency = 234;
boolean alice = false; boolean alice = false;
long outgoing1 = 345, centre1 = 456; long outgoing1 = 345, centre1 = 456;
long outgoing2 = 567, centre2 = 678; long outgoing2 = 567, centre2 = 678;
@@ -1235,7 +1236,8 @@ public class H2DatabaseTest extends BriarTestCase {
@Test @Test
public void testIncrementStreamCounter() throws Exception { public void testIncrementStreamCounter() throws Exception {
// Create an endpoint and a temporary secret // Create an endpoint and a temporary secret
long epoch = 123, latency = 234; long epoch = 123;
int latency = 234;
boolean alice = false; boolean alice = false;
long period = 345, outgoing = 456, centre = 567; long period = 345, outgoing = 456, centre = 567;
Endpoint ep = new Endpoint(contactId, transportId, epoch, alice); Endpoint ep = new Endpoint(contactId, transportId, epoch, alice);
@@ -1290,7 +1292,8 @@ public class H2DatabaseTest extends BriarTestCase {
@Test @Test
public void testSetReorderingWindow() throws Exception { public void testSetReorderingWindow() throws Exception {
// Create an endpoint and a temporary secret // Create an endpoint and a temporary secret
long epoch = 123, latency = 234; long epoch = 123;
int latency = 234;
boolean alice = false; boolean alice = false;
long period = 345, outgoing = 456, centre = 567; long period = 345, outgoing = 456, centre = 567;
Endpoint ep = new Endpoint(contactId, transportId, epoch, alice); Endpoint ep = new Endpoint(contactId, transportId, epoch, alice);
@@ -1359,8 +1362,8 @@ public class H2DatabaseTest extends BriarTestCase {
@Test @Test
public void testEndpoints() throws Exception { public void testEndpoints() throws Exception {
// Create some endpoints // Create some endpoints
long epoch1 = 123, latency1 = 234; long epoch1 = 123, epoch2 = 234;
long epoch2 = 345, latency2 = 456; int latency1 = 345, latency2 = 456;
boolean alice1 = true, alice2 = false; boolean alice1 = true, alice2 = false;
TransportId transportId1 = new TransportId("bar"); TransportId transportId1 = new TransportId("bar");
TransportId transportId2 = new TransportId("baz"); TransportId transportId2 = new TransportId("baz");

View File

@@ -2,12 +2,14 @@ package org.briarproject.messaging;
import static org.briarproject.api.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.api.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.api.messaging.MessagingConstants.GROUP_SALT_LENGTH; import static org.briarproject.api.messaging.MessagingConstants.GROUP_SALT_LENGTH;
import static org.briarproject.api.transport.TransportConstants.MAX_FRAME_LENGTH; import static org.briarproject.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH; import static org.briarproject.api.transport.TransportConstants.TAG_LENGTH;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Random; import java.util.Random;
import org.briarproject.BriarTestCase; import org.briarproject.BriarTestCase;
@@ -32,13 +34,13 @@ import org.briarproject.api.messaging.Message;
import org.briarproject.api.messaging.MessageFactory; import org.briarproject.api.messaging.MessageFactory;
import org.briarproject.api.messaging.MessageVerifier; import org.briarproject.api.messaging.MessageVerifier;
import org.briarproject.api.messaging.MessagingSession; import org.briarproject.api.messaging.MessagingSession;
import org.briarproject.api.messaging.PacketReader;
import org.briarproject.api.messaging.PacketReaderFactory; import org.briarproject.api.messaging.PacketReaderFactory;
import org.briarproject.api.messaging.PacketWriter;
import org.briarproject.api.messaging.PacketWriterFactory; import org.briarproject.api.messaging.PacketWriterFactory;
import org.briarproject.api.transport.Endpoint; import org.briarproject.api.transport.Endpoint;
import org.briarproject.api.transport.StreamContext; import org.briarproject.api.transport.StreamContext;
import org.briarproject.api.transport.StreamReader;
import org.briarproject.api.transport.StreamReaderFactory; import org.briarproject.api.transport.StreamReaderFactory;
import org.briarproject.api.transport.StreamWriter;
import org.briarproject.api.transport.StreamWriterFactory; import org.briarproject.api.transport.StreamWriterFactory;
import org.briarproject.api.transport.TagRecogniser; import org.briarproject.api.transport.TagRecogniser;
import org.briarproject.crypto.CryptoModule; import org.briarproject.crypto.CryptoModule;
@@ -56,8 +58,9 @@ import com.google.inject.Injector;
public class SimplexMessagingIntegrationTest extends BriarTestCase { public class SimplexMessagingIntegrationTest extends BriarTestCase {
private static final long CLOCK_DIFFERENCE = 60 * 1000; private static final int MAX_LATENCY = 2 * 60 * 1000; // 2 minutes
private static final long LATENCY = 60 * 1000; private static final int ROTATION_PERIOD =
MAX_CLOCK_DIFFERENCE + MAX_LATENCY;
private final File testDir = TestUtils.getTestDirectory(); private final File testDir = TestUtils.getTestDirectory();
private final File aliceDir = new File(testDir, "alice"); private final File aliceDir = new File(testDir, "alice");
@@ -73,8 +76,7 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
// Create matching secrets for Alice and Bob // Create matching secrets for Alice and Bob
initialSecret = new byte[32]; initialSecret = new byte[32];
new Random().nextBytes(initialSecret); new Random().nextBytes(initialSecret);
long rotationPeriod = 2 * CLOCK_DIFFERENCE + LATENCY; epoch = System.currentTimeMillis() - 2 * ROTATION_PERIOD;
epoch = System.currentTimeMillis() - 2 * rotationPeriod;
} }
@Override @Override
@@ -121,10 +123,10 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
db.addGroup(group); db.addGroup(group);
db.setInboxGroup(contactId, group); db.setInboxGroup(contactId, group);
// Add the transport and the endpoint // Add the transport and the endpoint
db.addTransport(transportId, LATENCY); db.addTransport(transportId, MAX_LATENCY);
Endpoint ep = new Endpoint(contactId, transportId, epoch, true); Endpoint ep = new Endpoint(contactId, transportId, epoch, true);
db.addEndpoint(ep); db.addEndpoint(ep);
keyManager.endpointAdded(ep, LATENCY, initialSecret.clone()); keyManager.endpointAdded(ep, MAX_LATENCY, initialSecret);
// Send Bob a message // Send Bob a message
String contentType = "text/plain"; String contentType = "text/plain";
long timestamp = System.currentTimeMillis(); long timestamp = System.currentTimeMillis();
@@ -133,25 +135,27 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
Message message = messageFactory.createAnonymousMessage(null, group, Message message = messageFactory.createAnonymousMessage(null, group,
contentType, timestamp, body); contentType, timestamp, body);
db.addLocalMessage(message); db.addLocalMessage(message);
// Get a stream context
StreamContext ctx = keyManager.getStreamContext(contactId, transportId);
assertNotNull(ctx);
// Create a stream writer // Create a stream writer
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamWriterFactory streamWriterFactory = StreamWriterFactory streamWriterFactory =
alice.getInstance(StreamWriterFactory.class); alice.getInstance(StreamWriterFactory.class);
StreamContext ctx = keyManager.getStreamContext(contactId, transportId); OutputStream streamWriter =
assertNotNull(ctx); streamWriterFactory.createStreamWriter(out, ctx);
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(out,
MAX_FRAME_LENGTH, ctx);
// Create an outgoing messaging session // Create an outgoing messaging session
EventBus eventBus = alice.getInstance(EventBus.class); EventBus eventBus = alice.getInstance(EventBus.class);
PacketWriterFactory packetWriterFactory = PacketWriterFactory packetWriterFactory =
alice.getInstance(PacketWriterFactory.class); alice.getInstance(PacketWriterFactory.class);
PacketWriter packetWriter = packetWriterFactory.createPacketWriter(
streamWriter);
MessagingSession session = new SimplexOutgoingSession(db, MessagingSession session = new SimplexOutgoingSession(db,
new ImmediateExecutor(), eventBus, packetWriterFactory, new ImmediateExecutor(), eventBus, contactId, transportId,
contactId, transportId, Long.MAX_VALUE, MAX_LATENCY, packetWriter);
streamWriter.getOutputStream());
// Write whatever needs to be written // Write whatever needs to be written
session.run(); session.run();
streamWriter.getOutputStream().close(); streamWriter.close();
// Clean up // Clean up
keyManager.stop(); keyManager.stop();
db.close(); db.close();
@@ -182,10 +186,10 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
db.addGroup(group); db.addGroup(group);
db.setInboxGroup(contactId, group); db.setInboxGroup(contactId, group);
// Add the transport and the endpoint // Add the transport and the endpoint
db.addTransport(transportId, LATENCY); db.addTransport(transportId, MAX_LATENCY);
Endpoint ep = new Endpoint(contactId, transportId, epoch, false); Endpoint ep = new Endpoint(contactId, transportId, epoch, false);
db.addEndpoint(ep); db.addEndpoint(ep);
keyManager.endpointAdded(ep, LATENCY, initialSecret.clone()); keyManager.endpointAdded(ep, MAX_LATENCY, initialSecret);
// Set up an event listener // Set up an event listener
MessageListener listener = new MessageListener(); MessageListener listener = new MessageListener();
bob.getInstance(EventBus.class).addListener(listener); bob.getInstance(EventBus.class).addListener(listener);
@@ -200,23 +204,24 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
// Create a stream reader // Create a stream reader
StreamReaderFactory streamReaderFactory = StreamReaderFactory streamReaderFactory =
bob.getInstance(StreamReaderFactory.class); bob.getInstance(StreamReaderFactory.class);
StreamReader streamReader = streamReaderFactory.createStreamReader(in, InputStream streamReader =
MAX_FRAME_LENGTH, ctx); streamReaderFactory.createStreamReader(in, ctx);
// Create an incoming messaging session // Create an incoming messaging session
EventBus eventBus = bob.getInstance(EventBus.class); EventBus eventBus = bob.getInstance(EventBus.class);
MessageVerifier messageVerifier = MessageVerifier messageVerifier =
bob.getInstance(MessageVerifier.class); bob.getInstance(MessageVerifier.class);
PacketReaderFactory packetReaderFactory = PacketReaderFactory packetReaderFactory =
bob.getInstance(PacketReaderFactory.class); bob.getInstance(PacketReaderFactory.class);
PacketReader packetReader = packetReaderFactory.createPacketReader(
streamReader);
MessagingSession session = new IncomingSession(db, MessagingSession session = new IncomingSession(db,
new ImmediateExecutor(), new ImmediateExecutor(), eventBus, new ImmediateExecutor(), new ImmediateExecutor(), eventBus,
messageVerifier, packetReaderFactory, contactId, transportId, messageVerifier, contactId, transportId, packetReader);
streamReader.getInputStream());
// No messages should have been added yet // No messages should have been added yet
assertFalse(listener.messageAdded); assertFalse(listener.messageAdded);
// Read whatever needs to be read // Read whatever needs to be read
session.run(); session.run();
streamReader.getInputStream().close(); streamReader.close();
// The private message from Alice should have been added // The private message from Alice should have been added
assertTrue(listener.messageAdded); assertTrue(listener.messageAdded);
// Clean up // Clean up

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