mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
A real working implementation of the invitation protocol.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
package net.sf.briar.api.crypto;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Signature;
|
||||
|
||||
@@ -32,8 +32,8 @@ public interface CryptoComponent {
|
||||
* corresponding private keys.
|
||||
* @param alice indicates whether the private key belongs to Alice or Bob.
|
||||
*/
|
||||
byte[] deriveInitialSecret(byte[] ourPublicKey, byte[] theirPublicKey,
|
||||
PrivateKey ourPrivateKey, boolean alice);
|
||||
byte[] deriveInitialSecret(byte[] theirPublicKey, KeyPair ourKeyPair,
|
||||
boolean alice) throws GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* Generates a random invitation code.
|
||||
|
||||
@@ -2,11 +2,11 @@ package net.sf.briar.api.plugins;
|
||||
|
||||
public interface InvitationConstants {
|
||||
|
||||
long INVITATION_TIMEOUT = 60 * 1000; // 1 minute
|
||||
long INVITATION_TIMEOUT = 30 * 1000; // Milliseconds
|
||||
|
||||
int CODE_BITS = 19; // Codes must fit into six decimal digits
|
||||
|
||||
int MAX_CODE = (1 << CODE_BITS) - 1;
|
||||
int MAX_CODE = (1 << CODE_BITS) - 1; // 524287
|
||||
|
||||
int HASH_LENGTH = 48; // Bytes
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@ import java.util.Map;
|
||||
|
||||
public interface Writer {
|
||||
|
||||
void flush() throws IOException;
|
||||
void close() throws IOException;
|
||||
|
||||
void addConsumer(Consumer c);
|
||||
void removeConsumer(Consumer c);
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import static net.sf.briar.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Security;
|
||||
@@ -142,38 +141,34 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] deriveInitialSecret(byte[] ourPublicKey,
|
||||
byte[] theirPublicKey, PrivateKey ourPrivateKey, boolean alice) {
|
||||
try {
|
||||
PublicKey theirPublic = agreementKeyParser.parsePublicKey(
|
||||
theirPublicKey);
|
||||
MessageDigest messageDigest = getMessageDigest();
|
||||
byte[] ourHash = messageDigest.digest(ourPublicKey);
|
||||
byte[] theirHash = messageDigest.digest(theirPublicKey);
|
||||
byte[] aliceInfo, bobInfo;
|
||||
if(alice) {
|
||||
aliceInfo = ourHash;
|
||||
bobInfo = theirHash;
|
||||
} else {
|
||||
aliceInfo = theirHash;
|
||||
bobInfo = ourHash;
|
||||
}
|
||||
// The raw secret comes from the key agreement algorithm
|
||||
KeyAgreement keyAgreement = KeyAgreement.getInstance(AGREEMENT_ALGO,
|
||||
PROVIDER);
|
||||
keyAgreement.init(ourPrivateKey);
|
||||
keyAgreement.doPhase(theirPublic, true);
|
||||
byte[] rawSecret = keyAgreement.generateSecret();
|
||||
// Derive the cooked secret from the raw secret using the
|
||||
// concatenation KDF
|
||||
byte[] cookedSecret = concatenationKdf(rawSecret, FIRST, aliceInfo,
|
||||
bobInfo);
|
||||
ByteUtils.erase(rawSecret);
|
||||
return cookedSecret;
|
||||
} catch(GeneralSecurityException e) {
|
||||
// FIXME: Throw instead of returning null?
|
||||
return null;
|
||||
public byte[] deriveInitialSecret(byte[] theirPublicKey,
|
||||
KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException {
|
||||
PublicKey theirPublic = agreementKeyParser.parsePublicKey(
|
||||
theirPublicKey);
|
||||
MessageDigest messageDigest = getMessageDigest();
|
||||
byte[] ourPublicKey = ourKeyPair.getPublic().getEncoded();
|
||||
byte[] ourHash = messageDigest.digest(ourPublicKey);
|
||||
byte[] theirHash = messageDigest.digest(theirPublicKey);
|
||||
byte[] aliceInfo, bobInfo;
|
||||
if(alice) {
|
||||
aliceInfo = ourHash;
|
||||
bobInfo = theirHash;
|
||||
} else {
|
||||
aliceInfo = theirHash;
|
||||
bobInfo = ourHash;
|
||||
}
|
||||
// The raw secret comes from the key agreement algorithm
|
||||
KeyAgreement keyAgreement = KeyAgreement.getInstance(AGREEMENT_ALGO,
|
||||
PROVIDER);
|
||||
keyAgreement.init(ourKeyPair.getPrivate());
|
||||
keyAgreement.doPhase(theirPublic, true);
|
||||
byte[] rawSecret = keyAgreement.generateSecret();
|
||||
// Derive the cooked secret from the raw secret using the
|
||||
// concatenation KDF
|
||||
byte[] cookedSecret = concatenationKdf(rawSecret, FIRST, aliceInfo,
|
||||
bobInfo);
|
||||
ByteUtils.erase(rawSecret);
|
||||
return cookedSecret;
|
||||
}
|
||||
|
||||
// Key derivation function based on a hash function - see NIST SP 800-56A,
|
||||
|
||||
@@ -2,48 +2,47 @@ package net.sf.briar.invitation;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static net.sf.briar.api.plugins.InvitationConstants.HASH_LENGTH;
|
||||
import static net.sf.briar.api.plugins.InvitationConstants.INVITATION_TIMEOUT;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.sf.briar.api.crypto.CryptoComponent;
|
||||
import net.sf.briar.api.crypto.PseudoRandom;
|
||||
import net.sf.briar.api.invitation.ConnectionCallback;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
|
||||
import net.sf.briar.api.serial.Reader;
|
||||
import net.sf.briar.api.serial.ReaderFactory;
|
||||
import net.sf.briar.api.serial.Writer;
|
||||
import net.sf.briar.api.serial.WriterFactory;
|
||||
|
||||
class AliceConnector extends Thread {
|
||||
class AliceConnector extends Connector {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(AliceConnector.class.getName());
|
||||
|
||||
private final DuplexPlugin plugin;
|
||||
private final PseudoRandom random;
|
||||
private final ConnectionCallback callback;
|
||||
private final AtomicBoolean connected, succeeded;
|
||||
private final String pluginName;
|
||||
|
||||
AliceConnector(DuplexPlugin plugin, PseudoRandom random,
|
||||
ConnectionCallback callback, AtomicBoolean connected,
|
||||
AtomicBoolean succeeded) {
|
||||
this.plugin = plugin;
|
||||
this.random = random;
|
||||
this.callback = callback;
|
||||
this.connected = connected;
|
||||
this.succeeded = succeeded;
|
||||
pluginName = plugin.getClass().getName();
|
||||
AliceConnector(CryptoComponent crypto, ReaderFactory readerFactory,
|
||||
WriterFactory writerFactory, DuplexPlugin plugin,
|
||||
PseudoRandom random, ConnectionCallback callback,
|
||||
AtomicBoolean connected, AtomicBoolean succeeded) {
|
||||
super(crypto, readerFactory, writerFactory, plugin, random, callback,
|
||||
connected, succeeded);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Try an outgoing connection first, then an incoming connection
|
||||
long halfTime = System.currentTimeMillis() + INVITATION_TIMEOUT / 2;
|
||||
DuplexTransportConnection conn = makeOutgoingConnection();
|
||||
if(conn == null) conn = acceptIncomingConnection(halfTime);
|
||||
if(conn == null) {
|
||||
waitForHalfTime(halfTime);
|
||||
conn = acceptIncomingConnection();
|
||||
}
|
||||
if(conn == null) return;
|
||||
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " connected");
|
||||
// Don't proceed with more than one connection
|
||||
@@ -52,34 +51,42 @@ class AliceConnector extends Thread {
|
||||
tryToClose(conn, false);
|
||||
return;
|
||||
}
|
||||
// FIXME: Carry out the real invitation protocol
|
||||
// Carry out the key agreement protocol
|
||||
InputStream in;
|
||||
OutputStream out;
|
||||
Reader r;
|
||||
Writer w;
|
||||
byte[] secret;
|
||||
try {
|
||||
in = conn.getInputStream();
|
||||
OutputStream out = conn.getOutputStream();
|
||||
byte[] hash = random.nextBytes(HASH_LENGTH);
|
||||
out.write(hash);
|
||||
out.flush();
|
||||
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " sent hash");
|
||||
int offset = 0;
|
||||
while(offset < hash.length) {
|
||||
int read = in.read(hash, offset, hash.length - offset);
|
||||
if(read == -1) break;
|
||||
offset += read;
|
||||
}
|
||||
if(offset < HASH_LENGTH) throw new EOFException();
|
||||
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " received hash");
|
||||
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " succeeded");
|
||||
succeeded.set(true);
|
||||
callback.connectionEstablished(123456, 123456,
|
||||
new ConfirmationSender(out));
|
||||
out = conn.getOutputStream();
|
||||
r = readerFactory.createReader(in);
|
||||
w = writerFactory.createWriter(out);
|
||||
// Alice goes first
|
||||
sendPublicKeyHash(w);
|
||||
byte[] hash = receivePublicKeyHash(r);
|
||||
sendPublicKey(w);
|
||||
byte[] key = receivePublicKey(r);
|
||||
secret = deriveSharedSecret(hash, key, true);
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
} catch(GeneralSecurityException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
}
|
||||
// The key agreement succeeded
|
||||
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " succeeded");
|
||||
succeeded.set(true);
|
||||
// Derive the confirmation codes
|
||||
int[] codes = crypto.deriveConfirmationCodes(secret);
|
||||
callback.connectionEstablished(codes[0], codes[1],
|
||||
new ConfirmationSender(w));
|
||||
// Check whether the remote peer's confirmation codes matched
|
||||
try {
|
||||
if(in.read() == 1) callback.codesMatch();
|
||||
if(r.readBoolean()) callback.codesMatch();
|
||||
else callback.codesDoNotMatch();
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
|
||||
@@ -87,35 +94,4 @@ class AliceConnector extends Thread {
|
||||
callback.codesDoNotMatch();
|
||||
}
|
||||
}
|
||||
|
||||
private DuplexTransportConnection makeOutgoingConnection() {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " making outgoing connection");
|
||||
return plugin.sendInvitation(random, INVITATION_TIMEOUT / 2);
|
||||
}
|
||||
|
||||
private DuplexTransportConnection acceptIncomingConnection(long halfTime) {
|
||||
long now = System.currentTimeMillis();
|
||||
if(now < halfTime) {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " sleeping until half-time");
|
||||
try {
|
||||
Thread.sleep(halfTime - now);
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Interrupted while sleeping");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " accepting incoming connection");
|
||||
return plugin.acceptInvitation(random, INVITATION_TIMEOUT / 2);
|
||||
}
|
||||
|
||||
private void tryToClose(DuplexTransportConnection conn, boolean exception) {
|
||||
try {
|
||||
conn.dispose(exception, true);
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,84 +2,85 @@ package net.sf.briar.invitation;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static net.sf.briar.api.plugins.InvitationConstants.HASH_LENGTH;
|
||||
import static net.sf.briar.api.plugins.InvitationConstants.INVITATION_TIMEOUT;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.sf.briar.api.crypto.CryptoComponent;
|
||||
import net.sf.briar.api.crypto.PseudoRandom;
|
||||
import net.sf.briar.api.invitation.ConnectionCallback;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
|
||||
import net.sf.briar.api.serial.Reader;
|
||||
import net.sf.briar.api.serial.ReaderFactory;
|
||||
import net.sf.briar.api.serial.Writer;
|
||||
import net.sf.briar.api.serial.WriterFactory;
|
||||
|
||||
class BobConnector extends Thread {
|
||||
class BobConnector extends Connector {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BobConnector.class.getName());
|
||||
|
||||
private final DuplexPlugin plugin;
|
||||
private final PseudoRandom random;
|
||||
private final ConnectionCallback callback;
|
||||
private final AtomicBoolean connected, succeeded;
|
||||
private final String pluginName;
|
||||
|
||||
BobConnector(DuplexPlugin plugin, PseudoRandom random,
|
||||
ConnectionCallback callback, AtomicBoolean connected,
|
||||
AtomicBoolean succeeded) {
|
||||
this.plugin = plugin;
|
||||
this.random = random;
|
||||
this.callback = callback;
|
||||
this.connected = connected;
|
||||
this.succeeded = succeeded;
|
||||
pluginName = plugin.getClass().getName();
|
||||
BobConnector(CryptoComponent crypto, ReaderFactory readerFactory,
|
||||
WriterFactory writerFactory, DuplexPlugin plugin,
|
||||
PseudoRandom random, ConnectionCallback callback,
|
||||
AtomicBoolean connected, AtomicBoolean succeeded) {
|
||||
super(crypto, readerFactory, writerFactory, plugin, random, callback,
|
||||
connected, succeeded);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Try an incoming connection first, then an outgoing connection
|
||||
long halfTime = System.currentTimeMillis() + INVITATION_TIMEOUT / 2;
|
||||
DuplexTransportConnection conn = acceptIncomingConnection();
|
||||
if(conn == null) conn = makeOutgoingConnection(halfTime);
|
||||
if(conn == null) {
|
||||
waitForHalfTime(halfTime);
|
||||
conn = makeOutgoingConnection();
|
||||
}
|
||||
if(conn == null) return;
|
||||
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " connected");
|
||||
// FIXME: Carry out the real invitation protocol
|
||||
// Carry out the key agreement protocol
|
||||
InputStream in;
|
||||
OutputStream out;
|
||||
Reader r;
|
||||
Writer w;
|
||||
byte[] secret;
|
||||
try {
|
||||
in = conn.getInputStream();
|
||||
OutputStream out = conn.getOutputStream();
|
||||
byte[] hash = new byte[HASH_LENGTH];
|
||||
int offset = 0;
|
||||
while(offset < hash.length) {
|
||||
int read = in.read(hash, offset, hash.length - offset);
|
||||
if(read == -1) break;
|
||||
offset += read;
|
||||
}
|
||||
if(offset < HASH_LENGTH) throw new EOFException();
|
||||
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " received hash");
|
||||
// Don't proceed with more than one connection
|
||||
if(connected.getAndSet(true)) {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " redundant");
|
||||
tryToClose(conn, false);
|
||||
return;
|
||||
}
|
||||
out.write(hash);
|
||||
out.flush();
|
||||
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " sent hash");
|
||||
succeeded.set(true);
|
||||
callback.connectionEstablished(123456, 123456,
|
||||
new ConfirmationSender(out));
|
||||
out = conn.getOutputStream();
|
||||
r = readerFactory.createReader(in);
|
||||
w = writerFactory.createWriter(out);
|
||||
// Alice goes first
|
||||
byte[] hash = receivePublicKeyHash(r);
|
||||
sendPublicKeyHash(w);
|
||||
byte[] key = receivePublicKey(r);
|
||||
sendPublicKey(w);
|
||||
secret = deriveSharedSecret(hash, key, false);
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
} catch(GeneralSecurityException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
|
||||
tryToClose(conn, true);
|
||||
return;
|
||||
}
|
||||
// The key agreement succeeded
|
||||
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " succeeded");
|
||||
succeeded.set(true);
|
||||
// Derive the confirmation codes
|
||||
int[] codes = crypto.deriveConfirmationCodes(secret);
|
||||
callback.connectionEstablished(codes[1], codes[0],
|
||||
new ConfirmationSender(w));
|
||||
// Check whether the remote peer's confirmation codes matched
|
||||
try {
|
||||
if(in.read() == 1) callback.codesMatch();
|
||||
if(r.readBoolean()) callback.codesMatch();
|
||||
else callback.codesDoNotMatch();
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
|
||||
@@ -87,35 +88,4 @@ class BobConnector extends Thread {
|
||||
callback.codesDoNotMatch();
|
||||
}
|
||||
}
|
||||
|
||||
private DuplexTransportConnection acceptIncomingConnection() {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " accepting incoming connection");
|
||||
return plugin.acceptInvitation(random, INVITATION_TIMEOUT / 2);
|
||||
}
|
||||
|
||||
private DuplexTransportConnection makeOutgoingConnection(long halfTime) {
|
||||
long now = System.currentTimeMillis();
|
||||
if(now < halfTime) {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " sleeping until half-time");
|
||||
try {
|
||||
Thread.sleep(halfTime - now);
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Interrupted while sleeping");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " making outgoing connection");
|
||||
return plugin.sendInvitation(random, INVITATION_TIMEOUT / 2);
|
||||
}
|
||||
|
||||
private void tryToClose(DuplexTransportConnection conn, boolean exception) {
|
||||
try {
|
||||
conn.dispose(exception, true);
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package net.sf.briar.invitation;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.sf.briar.api.invitation.ConfirmationCallback;
|
||||
|
||||
class ConfirmationSender implements ConfirmationCallback {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ConfirmationSender.class.getName());
|
||||
|
||||
private final OutputStream out;
|
||||
|
||||
ConfirmationSender(OutputStream out) {
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
public void codesMatch() {
|
||||
write(1);
|
||||
}
|
||||
|
||||
public void codesDoNotMatch() {
|
||||
write(0);
|
||||
}
|
||||
|
||||
private void write(int b) {
|
||||
try {
|
||||
out.write(b);
|
||||
out.flush();
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
169
src/net/sf/briar/invitation/Connector.java
Normal file
169
src/net/sf/briar/invitation/Connector.java
Normal file
@@ -0,0 +1,169 @@
|
||||
package net.sf.briar.invitation;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static net.sf.briar.api.plugins.InvitationConstants.HASH_LENGTH;
|
||||
import static net.sf.briar.api.plugins.InvitationConstants.INVITATION_TIMEOUT;
|
||||
import static net.sf.briar.api.plugins.InvitationConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.sf.briar.api.FormatException;
|
||||
import net.sf.briar.api.crypto.CryptoComponent;
|
||||
import net.sf.briar.api.crypto.KeyParser;
|
||||
import net.sf.briar.api.crypto.MessageDigest;
|
||||
import net.sf.briar.api.crypto.PseudoRandom;
|
||||
import net.sf.briar.api.invitation.ConfirmationCallback;
|
||||
import net.sf.briar.api.invitation.ConnectionCallback;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
|
||||
import net.sf.briar.api.serial.Reader;
|
||||
import net.sf.briar.api.serial.ReaderFactory;
|
||||
import net.sf.briar.api.serial.Writer;
|
||||
import net.sf.briar.api.serial.WriterFactory;
|
||||
|
||||
abstract class Connector extends Thread {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(Connector.class.getName());
|
||||
|
||||
protected final CryptoComponent crypto;
|
||||
protected final ReaderFactory readerFactory;
|
||||
protected final WriterFactory writerFactory;
|
||||
protected final DuplexPlugin plugin;
|
||||
protected final PseudoRandom random;
|
||||
protected final ConnectionCallback callback;
|
||||
protected final AtomicBoolean connected, succeeded;
|
||||
protected final String pluginName;
|
||||
|
||||
private final KeyPair keyPair;
|
||||
private final KeyParser keyParser;
|
||||
private final MessageDigest messageDigest;
|
||||
|
||||
Connector(CryptoComponent crypto, ReaderFactory readerFactory,
|
||||
WriterFactory writerFactory, DuplexPlugin plugin,
|
||||
PseudoRandom random, ConnectionCallback callback,
|
||||
AtomicBoolean connected, AtomicBoolean succeeded) {
|
||||
this.crypto = crypto;
|
||||
this.readerFactory = readerFactory;
|
||||
this.writerFactory = writerFactory;
|
||||
this.plugin = plugin;
|
||||
this.random = random;
|
||||
this.callback = callback;
|
||||
this.connected = connected;
|
||||
this.succeeded = succeeded;
|
||||
pluginName = plugin.getClass().getName();
|
||||
keyPair = crypto.generateAgreementKeyPair();
|
||||
keyParser = crypto.getAgreementKeyParser();
|
||||
messageDigest = crypto.getMessageDigest();
|
||||
}
|
||||
|
||||
protected DuplexTransportConnection acceptIncomingConnection() {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " accepting incoming connection");
|
||||
return plugin.acceptInvitation(random, INVITATION_TIMEOUT / 2);
|
||||
}
|
||||
|
||||
protected DuplexTransportConnection makeOutgoingConnection() {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " making outgoing connection");
|
||||
return plugin.sendInvitation(random, INVITATION_TIMEOUT / 2);
|
||||
}
|
||||
|
||||
protected void waitForHalfTime(long halfTime) {
|
||||
long now = System.currentTimeMillis();
|
||||
if(now < halfTime) {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " sleeping until half-time");
|
||||
try {
|
||||
Thread.sleep(halfTime - now);
|
||||
} catch(InterruptedException e) {
|
||||
if(LOG.isLoggable(INFO)) LOG.info("Interrupted while sleeping");
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void tryToClose(DuplexTransportConnection conn,
|
||||
boolean exception) {
|
||||
try {
|
||||
conn.dispose(exception, true);
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
protected void sendPublicKeyHash(Writer w) throws IOException {
|
||||
w.writeBytes(messageDigest.digest(keyPair.getPublic().getEncoded()));
|
||||
w.flush();
|
||||
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " sent hash");
|
||||
}
|
||||
|
||||
protected byte[] receivePublicKeyHash(Reader r) throws IOException {
|
||||
byte[] b = r.readBytes(HASH_LENGTH);
|
||||
if(b.length != HASH_LENGTH) throw new FormatException();
|
||||
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " received hash");
|
||||
return b;
|
||||
}
|
||||
|
||||
protected void sendPublicKey(Writer w) throws IOException {
|
||||
w.writeBytes(keyPair.getPublic().getEncoded());
|
||||
w.flush();
|
||||
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " sent key");
|
||||
}
|
||||
|
||||
protected byte[] receivePublicKey(Reader r) throws IOException {
|
||||
byte[] b = r.readBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||
try {
|
||||
keyParser.parsePublicKey(b);
|
||||
} catch(GeneralSecurityException e) {
|
||||
throw new FormatException();
|
||||
}
|
||||
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " received hash");
|
||||
return b;
|
||||
}
|
||||
|
||||
protected byte[] deriveSharedSecret(byte[] hash, byte[] key, boolean alice)
|
||||
throws GeneralSecurityException {
|
||||
// Check that the hash matches the key
|
||||
if(!Arrays.equals(hash, messageDigest.digest(key))) {
|
||||
if(LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " hash does not match key");
|
||||
throw new GeneralSecurityException();
|
||||
}
|
||||
// Derive the shared secret
|
||||
return crypto.deriveInitialSecret(key, keyPair, alice);
|
||||
}
|
||||
|
||||
protected static class ConfirmationSender implements ConfirmationCallback {
|
||||
|
||||
private final Writer writer;
|
||||
|
||||
protected ConfirmationSender(Writer writer) {
|
||||
this.writer = writer;
|
||||
}
|
||||
|
||||
public void codesMatch() {
|
||||
write(true);
|
||||
}
|
||||
|
||||
public void codesDoNotMatch() {
|
||||
write(false);
|
||||
}
|
||||
|
||||
private void write(boolean match) {
|
||||
try {
|
||||
writer.writeBoolean(match);
|
||||
writer.flush();
|
||||
} catch(IOException e) {
|
||||
if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,17 +10,24 @@ import net.sf.briar.api.invitation.ConnectionCallback;
|
||||
import net.sf.briar.api.invitation.InvitationManager;
|
||||
import net.sf.briar.api.plugins.PluginManager;
|
||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||
import net.sf.briar.api.serial.ReaderFactory;
|
||||
import net.sf.briar.api.serial.WriterFactory;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
class InvitationManagerImpl implements InvitationManager {
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
private final ReaderFactory readerFactory;
|
||||
private final WriterFactory writerFactory;
|
||||
private final PluginManager pluginManager;
|
||||
|
||||
@Inject
|
||||
InvitationManagerImpl(CryptoComponent crypto, PluginManager pluginManager) {
|
||||
InvitationManagerImpl(CryptoComponent crypto, ReaderFactory readerFactory,
|
||||
WriterFactory writerFactory, PluginManager pluginManager) {
|
||||
this.crypto = crypto;
|
||||
this.readerFactory = readerFactory;
|
||||
this.writerFactory = writerFactory;
|
||||
this.pluginManager = pluginManager;
|
||||
}
|
||||
|
||||
@@ -41,7 +48,8 @@ class InvitationManagerImpl implements InvitationManager {
|
||||
Collection<Thread> workers = new ArrayList<Thread>();
|
||||
for(DuplexPlugin p : plugins) {
|
||||
PseudoRandom r = crypto.getPseudoRandom(localCode, remoteCode);
|
||||
Thread worker = new AliceConnector(p, r, c, connected, succeeded);
|
||||
Thread worker = new AliceConnector(crypto, readerFactory,
|
||||
writerFactory, p, r, c, connected, succeeded);
|
||||
workers.add(worker);
|
||||
worker.start();
|
||||
}
|
||||
@@ -55,7 +63,8 @@ class InvitationManagerImpl implements InvitationManager {
|
||||
Collection<Thread> workers = new ArrayList<Thread>();
|
||||
for(DuplexPlugin p : plugins) {
|
||||
PseudoRandom r = crypto.getPseudoRandom(remoteCode, localCode);
|
||||
Thread worker = new BobConnector(p, r, c, connected, succeeded);
|
||||
Thread worker = new BobConnector(crypto, readerFactory,
|
||||
writerFactory, p, r, c, connected, succeeded);
|
||||
workers.add(worker);
|
||||
worker.start();
|
||||
}
|
||||
|
||||
@@ -22,6 +22,14 @@ class WriterImpl implements Writer {
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
public void flush() throws IOException {
|
||||
out.flush();
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
out.close();
|
||||
}
|
||||
|
||||
public void addConsumer(Consumer c) {
|
||||
consumers.add(c);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user