A real working implementation of the invitation protocol.

This commit is contained in:
akwizgran
2012-11-13 12:26:33 +00:00
parent 54ca7decbf
commit f69f6b3d43
10 changed files with 314 additions and 222 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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);

View File

@@ -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,

View File

@@ -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());
}
}
} }

View File

@@ -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());
}
}
} }

View File

@@ -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());
}
}
}

View 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());
}
}
}
}

View File

@@ -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();
} }

View File

@@ -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);
} }