diff --git a/src/net/sf/briar/api/crypto/CryptoComponent.java b/src/net/sf/briar/api/crypto/CryptoComponent.java index c8cbace86e982bf4fe53972fc69ce3c9a4c9be23..0c792b068d3723da04da7590095321a73739723a 100644 --- a/src/net/sf/briar/api/crypto/CryptoComponent.java +++ b/src/net/sf/briar/api/crypto/CryptoComponent.java @@ -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. diff --git a/src/net/sf/briar/api/plugins/InvitationConstants.java b/src/net/sf/briar/api/plugins/InvitationConstants.java index f27d35b825690f62a5d3e79c7231ddaf6e104dcf..8e76a32a9225e4e48e7ed18af75324940b7e3988 100644 --- a/src/net/sf/briar/api/plugins/InvitationConstants.java +++ b/src/net/sf/briar/api/plugins/InvitationConstants.java @@ -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 diff --git a/src/net/sf/briar/api/serial/Writer.java b/src/net/sf/briar/api/serial/Writer.java index 1bf72c21f7825820ba2a909dc3076a7bb00d1573..d13b711c6156ca2f981194654b230ff29b615bcc 100644 --- a/src/net/sf/briar/api/serial/Writer.java +++ b/src/net/sf/briar/api/serial/Writer.java @@ -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); diff --git a/src/net/sf/briar/crypto/CryptoComponentImpl.java b/src/net/sf/briar/crypto/CryptoComponentImpl.java index 7e41e8c1c661d8ecb1ae48c0b9754b52b877ba54..8783c0ca8dcc6c13cef1b864cc4d6d37c2ac9a0b 100644 --- a/src/net/sf/briar/crypto/CryptoComponentImpl.java +++ b/src/net/sf/briar/crypto/CryptoComponentImpl.java @@ -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, diff --git a/src/net/sf/briar/invitation/AliceConnector.java b/src/net/sf/briar/invitation/AliceConnector.java index 0d8bd5b43f55bce09b3b75d3279de40aa365e5f0..984e3e643a5590887f045f1589e6e949498ecfce 100644 --- a/src/net/sf/briar/invitation/AliceConnector.java +++ b/src/net/sf/briar/invitation/AliceConnector.java @@ -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()); - } - } } \ No newline at end of file diff --git a/src/net/sf/briar/invitation/BobConnector.java b/src/net/sf/briar/invitation/BobConnector.java index a895d638ecbf66fcef674f9eefe9707521024ee2..7b75dc3211c39231ed8dbf8e8ae4f870665aff8c 100644 --- a/src/net/sf/briar/invitation/BobConnector.java +++ b/src/net/sf/briar/invitation/BobConnector.java @@ -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()); - } - } } diff --git a/src/net/sf/briar/invitation/ConfirmationSender.java b/src/net/sf/briar/invitation/ConfirmationSender.java deleted file mode 100644 index 7542b49f02c4ee4e77b2a473c96ad43fdb20f837..0000000000000000000000000000000000000000 --- a/src/net/sf/briar/invitation/ConfirmationSender.java +++ /dev/null @@ -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()); - } - } -} diff --git a/src/net/sf/briar/invitation/Connector.java b/src/net/sf/briar/invitation/Connector.java new file mode 100644 index 0000000000000000000000000000000000000000..ce269ab8d5cd8dd1467b2772a33e033dbaad7546 --- /dev/null +++ b/src/net/sf/briar/invitation/Connector.java @@ -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()); + } + } + } +} diff --git a/src/net/sf/briar/invitation/InvitationManagerImpl.java b/src/net/sf/briar/invitation/InvitationManagerImpl.java index a098add93ac3e36fa86b0dd0ee8deb5a4fd76a14..ade452233bba4752f08fd271c97e7258492f880c 100644 --- a/src/net/sf/briar/invitation/InvitationManagerImpl.java +++ b/src/net/sf/briar/invitation/InvitationManagerImpl.java @@ -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(); } diff --git a/src/net/sf/briar/serial/WriterImpl.java b/src/net/sf/briar/serial/WriterImpl.java index 239e15fc3c42051a8e4f68ef81ba707ef9851f79..8fdd7467b0fff34d90145cd94a6ae5bfb04ad4df 100644 --- a/src/net/sf/briar/serial/WriterImpl.java +++ b/src/net/sf/briar/serial/WriterImpl.java @@ -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); }