mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-18 13:49:53 +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;
|
package net.sf.briar.api.crypto;
|
||||||
|
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.PrivateKey;
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.Signature;
|
import java.security.Signature;
|
||||||
|
|
||||||
@@ -32,8 +32,8 @@ public interface CryptoComponent {
|
|||||||
* corresponding private keys.
|
* corresponding private keys.
|
||||||
* @param alice indicates whether the private key belongs to Alice or Bob.
|
* @param alice indicates whether the private key belongs to Alice or Bob.
|
||||||
*/
|
*/
|
||||||
byte[] deriveInitialSecret(byte[] ourPublicKey, byte[] theirPublicKey,
|
byte[] deriveInitialSecret(byte[] theirPublicKey, KeyPair ourKeyPair,
|
||||||
PrivateKey ourPrivateKey, boolean alice);
|
boolean alice) throws GeneralSecurityException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a random invitation code.
|
* Generates a random invitation code.
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ package net.sf.briar.api.plugins;
|
|||||||
|
|
||||||
public interface InvitationConstants {
|
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 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
|
int HASH_LENGTH = 48; // Bytes
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import java.util.Map;
|
|||||||
|
|
||||||
public interface Writer {
|
public interface Writer {
|
||||||
|
|
||||||
|
void flush() throws IOException;
|
||||||
|
void close() throws IOException;
|
||||||
|
|
||||||
void addConsumer(Consumer c);
|
void addConsumer(Consumer c);
|
||||||
void removeConsumer(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.GeneralSecurityException;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.KeyPairGenerator;
|
import java.security.KeyPairGenerator;
|
||||||
import java.security.PrivateKey;
|
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
@@ -142,38 +141,34 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] deriveInitialSecret(byte[] ourPublicKey,
|
public byte[] deriveInitialSecret(byte[] theirPublicKey,
|
||||||
byte[] theirPublicKey, PrivateKey ourPrivateKey, boolean alice) {
|
KeyPair ourKeyPair, boolean alice) throws GeneralSecurityException {
|
||||||
try {
|
PublicKey theirPublic = agreementKeyParser.parsePublicKey(
|
||||||
PublicKey theirPublic = agreementKeyParser.parsePublicKey(
|
theirPublicKey);
|
||||||
theirPublicKey);
|
MessageDigest messageDigest = getMessageDigest();
|
||||||
MessageDigest messageDigest = getMessageDigest();
|
byte[] ourPublicKey = ourKeyPair.getPublic().getEncoded();
|
||||||
byte[] ourHash = messageDigest.digest(ourPublicKey);
|
byte[] ourHash = messageDigest.digest(ourPublicKey);
|
||||||
byte[] theirHash = messageDigest.digest(theirPublicKey);
|
byte[] theirHash = messageDigest.digest(theirPublicKey);
|
||||||
byte[] aliceInfo, bobInfo;
|
byte[] aliceInfo, bobInfo;
|
||||||
if(alice) {
|
if(alice) {
|
||||||
aliceInfo = ourHash;
|
aliceInfo = ourHash;
|
||||||
bobInfo = theirHash;
|
bobInfo = theirHash;
|
||||||
} else {
|
} else {
|
||||||
aliceInfo = theirHash;
|
aliceInfo = theirHash;
|
||||||
bobInfo = ourHash;
|
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;
|
|
||||||
}
|
}
|
||||||
|
// 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,
|
// 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.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
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.INVITATION_TIMEOUT;
|
||||||
|
|
||||||
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.io.OutputStream;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import net.sf.briar.api.crypto.CryptoComponent;
|
||||||
import net.sf.briar.api.crypto.PseudoRandom;
|
import net.sf.briar.api.crypto.PseudoRandom;
|
||||||
import net.sf.briar.api.invitation.ConnectionCallback;
|
import net.sf.briar.api.invitation.ConnectionCallback;
|
||||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||||
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
|
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 =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(AliceConnector.class.getName());
|
Logger.getLogger(AliceConnector.class.getName());
|
||||||
|
|
||||||
private final DuplexPlugin plugin;
|
AliceConnector(CryptoComponent crypto, ReaderFactory readerFactory,
|
||||||
private final PseudoRandom random;
|
WriterFactory writerFactory, DuplexPlugin plugin,
|
||||||
private final ConnectionCallback callback;
|
PseudoRandom random, ConnectionCallback callback,
|
||||||
private final AtomicBoolean connected, succeeded;
|
AtomicBoolean connected, AtomicBoolean succeeded) {
|
||||||
private final String pluginName;
|
super(crypto, readerFactory, writerFactory, plugin, random, callback,
|
||||||
|
connected, succeeded);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
// Try an outgoing connection first, then an incoming connection
|
||||||
long halfTime = System.currentTimeMillis() + INVITATION_TIMEOUT / 2;
|
long halfTime = System.currentTimeMillis() + INVITATION_TIMEOUT / 2;
|
||||||
DuplexTransportConnection conn = makeOutgoingConnection();
|
DuplexTransportConnection conn = makeOutgoingConnection();
|
||||||
if(conn == null) conn = acceptIncomingConnection(halfTime);
|
if(conn == null) {
|
||||||
|
waitForHalfTime(halfTime);
|
||||||
|
conn = acceptIncomingConnection();
|
||||||
|
}
|
||||||
if(conn == null) return;
|
if(conn == null) return;
|
||||||
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " connected");
|
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " connected");
|
||||||
// Don't proceed with more than one connection
|
// Don't proceed with more than one connection
|
||||||
@@ -52,34 +51,42 @@ class AliceConnector extends Thread {
|
|||||||
tryToClose(conn, false);
|
tryToClose(conn, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// FIXME: Carry out the real invitation protocol
|
// Carry out the key agreement protocol
|
||||||
InputStream in;
|
InputStream in;
|
||||||
|
OutputStream out;
|
||||||
|
Reader r;
|
||||||
|
Writer w;
|
||||||
|
byte[] secret;
|
||||||
try {
|
try {
|
||||||
in = conn.getInputStream();
|
in = conn.getInputStream();
|
||||||
OutputStream out = conn.getOutputStream();
|
out = conn.getOutputStream();
|
||||||
byte[] hash = random.nextBytes(HASH_LENGTH);
|
r = readerFactory.createReader(in);
|
||||||
out.write(hash);
|
w = writerFactory.createWriter(out);
|
||||||
out.flush();
|
// Alice goes first
|
||||||
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " sent hash");
|
sendPublicKeyHash(w);
|
||||||
int offset = 0;
|
byte[] hash = receivePublicKeyHash(r);
|
||||||
while(offset < hash.length) {
|
sendPublicKey(w);
|
||||||
int read = in.read(hash, offset, hash.length - offset);
|
byte[] key = receivePublicKey(r);
|
||||||
if(read == -1) break;
|
secret = deriveSharedSecret(hash, key, true);
|
||||||
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));
|
|
||||||
} catch(IOException e) {
|
} catch(IOException e) {
|
||||||
if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
|
if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
|
||||||
tryToClose(conn, true);
|
tryToClose(conn, true);
|
||||||
return;
|
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 {
|
try {
|
||||||
if(in.read() == 1) callback.codesMatch();
|
if(r.readBoolean()) callback.codesMatch();
|
||||||
else callback.codesDoNotMatch();
|
else callback.codesDoNotMatch();
|
||||||
} catch(IOException e) {
|
} catch(IOException e) {
|
||||||
if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
|
if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
|
||||||
@@ -87,35 +94,4 @@ class AliceConnector extends Thread {
|
|||||||
callback.codesDoNotMatch();
|
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.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
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.INVITATION_TIMEOUT;
|
||||||
|
|
||||||
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.io.OutputStream;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import net.sf.briar.api.crypto.CryptoComponent;
|
||||||
import net.sf.briar.api.crypto.PseudoRandom;
|
import net.sf.briar.api.crypto.PseudoRandom;
|
||||||
import net.sf.briar.api.invitation.ConnectionCallback;
|
import net.sf.briar.api.invitation.ConnectionCallback;
|
||||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||||
import net.sf.briar.api.plugins.duplex.DuplexTransportConnection;
|
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 =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(BobConnector.class.getName());
|
Logger.getLogger(BobConnector.class.getName());
|
||||||
|
|
||||||
private final DuplexPlugin plugin;
|
BobConnector(CryptoComponent crypto, ReaderFactory readerFactory,
|
||||||
private final PseudoRandom random;
|
WriterFactory writerFactory, DuplexPlugin plugin,
|
||||||
private final ConnectionCallback callback;
|
PseudoRandom random, ConnectionCallback callback,
|
||||||
private final AtomicBoolean connected, succeeded;
|
AtomicBoolean connected, AtomicBoolean succeeded) {
|
||||||
private final String pluginName;
|
super(crypto, readerFactory, writerFactory, plugin, random, callback,
|
||||||
|
connected, succeeded);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
// Try an incoming connection first, then an outgoing connection
|
||||||
long halfTime = System.currentTimeMillis() + INVITATION_TIMEOUT / 2;
|
long halfTime = System.currentTimeMillis() + INVITATION_TIMEOUT / 2;
|
||||||
DuplexTransportConnection conn = acceptIncomingConnection();
|
DuplexTransportConnection conn = acceptIncomingConnection();
|
||||||
if(conn == null) conn = makeOutgoingConnection(halfTime);
|
if(conn == null) {
|
||||||
|
waitForHalfTime(halfTime);
|
||||||
|
conn = makeOutgoingConnection();
|
||||||
|
}
|
||||||
if(conn == null) return;
|
if(conn == null) return;
|
||||||
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " connected");
|
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " connected");
|
||||||
// FIXME: Carry out the real invitation protocol
|
// Carry out the key agreement protocol
|
||||||
InputStream in;
|
InputStream in;
|
||||||
|
OutputStream out;
|
||||||
|
Reader r;
|
||||||
|
Writer w;
|
||||||
|
byte[] secret;
|
||||||
try {
|
try {
|
||||||
in = conn.getInputStream();
|
in = conn.getInputStream();
|
||||||
OutputStream out = conn.getOutputStream();
|
out = conn.getOutputStream();
|
||||||
byte[] hash = new byte[HASH_LENGTH];
|
r = readerFactory.createReader(in);
|
||||||
int offset = 0;
|
w = writerFactory.createWriter(out);
|
||||||
while(offset < hash.length) {
|
// Alice goes first
|
||||||
int read = in.read(hash, offset, hash.length - offset);
|
byte[] hash = receivePublicKeyHash(r);
|
||||||
if(read == -1) break;
|
sendPublicKeyHash(w);
|
||||||
offset += read;
|
byte[] key = receivePublicKey(r);
|
||||||
}
|
sendPublicKey(w);
|
||||||
if(offset < HASH_LENGTH) throw new EOFException();
|
secret = deriveSharedSecret(hash, key, false);
|
||||||
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));
|
|
||||||
} catch(IOException e) {
|
} catch(IOException e) {
|
||||||
if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
|
if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
|
||||||
tryToClose(conn, true);
|
tryToClose(conn, true);
|
||||||
return;
|
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 {
|
try {
|
||||||
if(in.read() == 1) callback.codesMatch();
|
if(r.readBoolean()) callback.codesMatch();
|
||||||
else callback.codesDoNotMatch();
|
else callback.codesDoNotMatch();
|
||||||
} catch(IOException e) {
|
} catch(IOException e) {
|
||||||
if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
|
if(LOG.isLoggable(WARNING)) LOG.warning(e.toString());
|
||||||
@@ -87,35 +88,4 @@ class BobConnector extends Thread {
|
|||||||
callback.codesDoNotMatch();
|
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.invitation.InvitationManager;
|
||||||
import net.sf.briar.api.plugins.PluginManager;
|
import net.sf.briar.api.plugins.PluginManager;
|
||||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
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;
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
class InvitationManagerImpl implements InvitationManager {
|
class InvitationManagerImpl implements InvitationManager {
|
||||||
|
|
||||||
private final CryptoComponent crypto;
|
private final CryptoComponent crypto;
|
||||||
|
private final ReaderFactory readerFactory;
|
||||||
|
private final WriterFactory writerFactory;
|
||||||
private final PluginManager pluginManager;
|
private final PluginManager pluginManager;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
InvitationManagerImpl(CryptoComponent crypto, PluginManager pluginManager) {
|
InvitationManagerImpl(CryptoComponent crypto, ReaderFactory readerFactory,
|
||||||
|
WriterFactory writerFactory, PluginManager pluginManager) {
|
||||||
this.crypto = crypto;
|
this.crypto = crypto;
|
||||||
|
this.readerFactory = readerFactory;
|
||||||
|
this.writerFactory = writerFactory;
|
||||||
this.pluginManager = pluginManager;
|
this.pluginManager = pluginManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +48,8 @@ class InvitationManagerImpl implements InvitationManager {
|
|||||||
Collection<Thread> workers = new ArrayList<Thread>();
|
Collection<Thread> workers = new ArrayList<Thread>();
|
||||||
for(DuplexPlugin p : plugins) {
|
for(DuplexPlugin p : plugins) {
|
||||||
PseudoRandom r = crypto.getPseudoRandom(localCode, remoteCode);
|
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);
|
workers.add(worker);
|
||||||
worker.start();
|
worker.start();
|
||||||
}
|
}
|
||||||
@@ -55,7 +63,8 @@ class InvitationManagerImpl implements InvitationManager {
|
|||||||
Collection<Thread> workers = new ArrayList<Thread>();
|
Collection<Thread> workers = new ArrayList<Thread>();
|
||||||
for(DuplexPlugin p : plugins) {
|
for(DuplexPlugin p : plugins) {
|
||||||
PseudoRandom r = crypto.getPseudoRandom(remoteCode, localCode);
|
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);
|
workers.add(worker);
|
||||||
worker.start();
|
worker.start();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,14 @@ class WriterImpl implements Writer {
|
|||||||
this.out = out;
|
this.out = out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void flush() throws IOException {
|
||||||
|
out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() throws IOException {
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
|
||||||
public void addConsumer(Consumer c) {
|
public void addConsumer(Consumer c) {
|
||||||
consumers.add(c);
|
consumers.add(c);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user