From 701cfdba4820b560999803a2ceeee2ba78a5414e Mon Sep 17 00:00:00 2001 From: str4d <str4d@mail.i2p> Date: Thu, 25 Feb 2016 22:01:15 +0000 Subject: [PATCH] Extract contact exchange protocol from BT introduction protocol --- .../api/contact/ContactExchangeListener.java | 14 + .../api/contact/ContactExchangeTask.java | 20 ++ .../api/crypto/CryptoComponent.java | 2 +- .../contact/ContactExchangeTaskImpl.java | 253 ++++++++++++++++++ .../briarproject/contact/ContactModule.java | 24 ++ .../crypto/CryptoComponentImpl.java | 2 +- .../invitation/AliceConnector.java | 4 +- .../briarproject/invitation/BobConnector.java | 4 +- 8 files changed, 317 insertions(+), 6 deletions(-) create mode 100644 briar-api/src/org/briarproject/api/contact/ContactExchangeListener.java create mode 100644 briar-api/src/org/briarproject/api/contact/ContactExchangeTask.java create mode 100644 briar-core/src/org/briarproject/contact/ContactExchangeTaskImpl.java diff --git a/briar-api/src/org/briarproject/api/contact/ContactExchangeListener.java b/briar-api/src/org/briarproject/api/contact/ContactExchangeListener.java new file mode 100644 index 0000000000..50bf543c71 --- /dev/null +++ b/briar-api/src/org/briarproject/api/contact/ContactExchangeListener.java @@ -0,0 +1,14 @@ +package org.briarproject.api.contact; + +import org.briarproject.api.identity.Author; + +public interface ContactExchangeListener { + + void contactExchangeSucceeded(Author remoteAuthor); + + /** The exchange failed because the contact already exists. */ + void duplicateContact(Author remoteAuthor); + + /** A general failure. */ + void contactExchangeFailed(); +} diff --git a/briar-api/src/org/briarproject/api/contact/ContactExchangeTask.java b/briar-api/src/org/briarproject/api/contact/ContactExchangeTask.java new file mode 100644 index 0000000000..fe00530903 --- /dev/null +++ b/briar-api/src/org/briarproject/api/contact/ContactExchangeTask.java @@ -0,0 +1,20 @@ +package org.briarproject.api.contact; + +import org.briarproject.api.TransportId; +import org.briarproject.api.crypto.SecretKey; +import org.briarproject.api.identity.LocalAuthor; +import org.briarproject.api.plugins.duplex.DuplexTransportConnection; + +/** + * A task for conducting a contact information exchange with a remote peer. + */ +public interface ContactExchangeTask { + + /** + * Exchange contact information with a remote peer. + */ + void startExchange(ContactExchangeListener listener, + LocalAuthor localAuthor, SecretKey masterSecret, + DuplexTransportConnection conn, TransportId transportId, + boolean alice, boolean reuseConnection); +} diff --git a/briar-api/src/org/briarproject/api/crypto/CryptoComponent.java b/briar-api/src/org/briarproject/api/crypto/CryptoComponent.java index 0ba779202b..8ffd06213e 100644 --- a/briar-api/src/org/briarproject/api/crypto/CryptoComponent.java +++ b/briar-api/src/org/briarproject/api/crypto/CryptoComponent.java @@ -47,7 +47,7 @@ public interface CryptoComponent { * sign. * @param alice whether the nonce is for use by Alice or Bob. */ - byte[] deriveBTSignatureNonce(SecretKey master, boolean alice); + byte[] deriveSignatureNonce(SecretKey master, boolean alice); /** * Derives a commitment to the provided public key. diff --git a/briar-core/src/org/briarproject/contact/ContactExchangeTaskImpl.java b/briar-core/src/org/briarproject/contact/ContactExchangeTaskImpl.java new file mode 100644 index 0000000000..53f0fab7b0 --- /dev/null +++ b/briar-core/src/org/briarproject/contact/ContactExchangeTaskImpl.java @@ -0,0 +1,253 @@ +package org.briarproject.contact; + +import org.briarproject.api.FormatException; +import org.briarproject.api.TransportId; +import org.briarproject.api.contact.ContactExchangeListener; +import org.briarproject.api.contact.ContactExchangeTask; +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.contact.ContactManager; +import org.briarproject.api.crypto.CryptoComponent; +import org.briarproject.api.crypto.KeyParser; +import org.briarproject.api.crypto.SecretKey; +import org.briarproject.api.crypto.Signature; +import org.briarproject.api.data.BdfReader; +import org.briarproject.api.data.BdfReaderFactory; +import org.briarproject.api.data.BdfWriter; +import org.briarproject.api.data.BdfWriterFactory; +import org.briarproject.api.db.ContactExistsException; +import org.briarproject.api.db.DbException; +import org.briarproject.api.identity.Author; +import org.briarproject.api.identity.AuthorFactory; +import org.briarproject.api.identity.LocalAuthor; +import org.briarproject.api.plugins.ConnectionManager; +import org.briarproject.api.plugins.duplex.DuplexTransportConnection; +import org.briarproject.api.system.Clock; +import org.briarproject.api.transport.KeyManager; +import org.briarproject.api.transport.StreamReaderFactory; +import org.briarproject.api.transport.StreamWriterFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.GeneralSecurityException; +import java.util.logging.Logger; + +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; +import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; +import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; +import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH; + +public class ContactExchangeTaskImpl extends Thread + implements ContactExchangeTask { + + private static final Logger LOG = + Logger.getLogger(ContactExchangeTaskImpl.class.getName()); + + private final AuthorFactory authorFactory; + private final BdfReaderFactory bdfReaderFactory; + private final BdfWriterFactory bdfWriterFactory; + private final Clock clock; + private final ConnectionManager connectionManager; + private final ContactManager contactManager; + private final CryptoComponent crypto; + private final KeyManager keyManager; + private final StreamReaderFactory streamReaderFactory; + private final StreamWriterFactory streamWriterFactory; + + private ContactExchangeListener listener; + private LocalAuthor localAuthor; + private DuplexTransportConnection conn; + private TransportId transportId; + private SecretKey masterSecret; + private boolean alice; + private boolean reuseConnection; + + public ContactExchangeTaskImpl(AuthorFactory authorFactory, + BdfReaderFactory bdfReaderFactory, + BdfWriterFactory bdfWriterFactory, Clock clock, + ConnectionManager connectionManager, ContactManager contactManager, + CryptoComponent crypto, KeyManager keyManager, + StreamReaderFactory streamReaderFactory, + StreamWriterFactory streamWriterFactory) { + this.authorFactory = authorFactory; + this.bdfReaderFactory = bdfReaderFactory; + this.bdfWriterFactory = bdfWriterFactory; + this.clock = clock; + this.connectionManager = connectionManager; + this.contactManager = contactManager; + this.crypto = crypto; + this.keyManager = keyManager; + this.streamReaderFactory = streamReaderFactory; + this.streamWriterFactory = streamWriterFactory; + } + + @Override + public void startExchange(ContactExchangeListener listener, + LocalAuthor localAuthor, SecretKey masterSecret, + DuplexTransportConnection conn, TransportId transportId, + boolean alice, boolean reuseConnection) { + this.listener = listener; + this.localAuthor = localAuthor; + this.conn = conn; + this.transportId = transportId; + this.masterSecret = masterSecret; + this.alice = alice; + this.reuseConnection = reuseConnection; + start(); + } + + @Override + public void run() { + BdfReader r; + BdfWriter w; + try { + // Create the readers + InputStream streamReader = + streamReaderFactory.createInvitationStreamReader( + conn.getReader().getInputStream(), + masterSecret); + r = bdfReaderFactory.createReader(streamReader); + // Create the writers + OutputStream streamWriter = + streamWriterFactory.createInvitationStreamWriter( + conn.getWriter().getOutputStream(), + masterSecret); + w = bdfWriterFactory.createWriter(streamWriter); + } catch (IOException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + listener.contactExchangeFailed(); + tryToClose(conn, true); + return; + } + + // Derive the invitation nonces + byte[] aliceNonce = crypto.deriveSignatureNonce(masterSecret, true); + byte[] bobNonce = crypto.deriveSignatureNonce(masterSecret, false); + + // Exchange pseudonyms, signed nonces, and timestamps + long localTimestamp = clock.currentTimeMillis(); + Author remoteAuthor; + long remoteTimestamp; + try { + if (alice) { + sendPseudonym(w, aliceNonce); + sendTimestamp(w, localTimestamp); + remoteAuthor = receivePseudonym(r, bobNonce); + remoteTimestamp = receiveTimestamp(r); + } else { + remoteAuthor = receivePseudonym(r, aliceNonce); + remoteTimestamp = receiveTimestamp(r); + sendPseudonym(w, bobNonce); + sendTimestamp(w, localTimestamp); + } + // Close the outgoing stream and expect EOF on the incoming stream + w.close(); + if (!r.eof()) LOG.warning("Unexpected data at end of connection"); + } catch (GeneralSecurityException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + listener.contactExchangeFailed(); + tryToClose(conn, true); + return; + } catch (IOException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + listener.contactExchangeFailed(); + tryToClose(conn, true); + return; + } + + // The agreed timestamp is the minimum of the peers' timestamps + long timestamp = Math.min(localTimestamp, remoteTimestamp); + + try { + // Add the contact + ContactId contactId = + addContact(remoteAuthor, masterSecret, timestamp, true); + // Reuse the connection as a transport connection + if (reuseConnection) + connectionManager + .manageOutgoingConnection(contactId, transportId, conn); + // Pseudonym exchange succeeded + LOG.info("Pseudonym exchange succeeded"); + listener.contactExchangeSucceeded(remoteAuthor); + } catch (ContactExistsException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + tryToClose(conn, true); + listener.duplicateContact(remoteAuthor); + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + tryToClose(conn, true); + listener.contactExchangeFailed(); + } + } + + private void sendPseudonym(BdfWriter w, byte[] nonce) + throws GeneralSecurityException, IOException { + // Sign the nonce + Signature signature = crypto.getSignature(); + KeyParser keyParser = crypto.getSignatureKeyParser(); + byte[] privateKey = localAuthor.getPrivateKey(); + signature.initSign(keyParser.parsePrivateKey(privateKey)); + signature.update(nonce); + byte[] sig = signature.sign(); + // Write the name, public key and signature + w.writeString(localAuthor.getName()); + w.writeRaw(localAuthor.getPublicKey()); + w.writeRaw(sig); + w.flush(); + LOG.info("Sent pseudonym"); + } + + private Author receivePseudonym(BdfReader r, byte[] nonce) + throws GeneralSecurityException, IOException { + // Read the name, public key and signature + String name = r.readString(MAX_AUTHOR_NAME_LENGTH); + byte[] publicKey = r.readRaw(MAX_PUBLIC_KEY_LENGTH); + byte[] sig = r.readRaw(MAX_SIGNATURE_LENGTH); + LOG.info("Received pseudonym"); + // Verify the signature + Signature signature = crypto.getSignature(); + KeyParser keyParser = crypto.getSignatureKeyParser(); + signature.initVerify(keyParser.parsePublicKey(publicKey)); + signature.update(nonce); + if (!signature.verify(sig)) { + if (LOG.isLoggable(INFO)) + LOG.info("Invalid signature"); + throw new GeneralSecurityException(); + } + return authorFactory.createAuthor(name, publicKey); + } + + private void sendTimestamp(BdfWriter w, long timestamp) + throws IOException { + w.writeLong(timestamp); + w.flush(); + LOG.info("Sent timestamp"); + } + + private long receiveTimestamp(BdfReader r) throws IOException { + long timestamp = r.readLong(); + if (timestamp < 0) throw new FormatException(); + LOG.info("Received timestamp"); + return timestamp; + } + + private ContactId addContact(Author remoteAuthor, SecretKey master, + long timestamp, boolean alice) throws DbException { + // Add the contact to the database + ContactId contactId = contactManager.addContact(remoteAuthor, + localAuthor.getId(), master, timestamp, alice, true); + return contactId; + } + + private void tryToClose(DuplexTransportConnection conn, + boolean exception) { + try { + LOG.info("Closing connection"); + conn.getReader().dispose(exception, true); + conn.getWriter().dispose(exception); + } catch (IOException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } + } +} diff --git a/briar-core/src/org/briarproject/contact/ContactModule.java b/briar-core/src/org/briarproject/contact/ContactModule.java index bd8e2d3afa..77da9366fc 100644 --- a/briar-core/src/org/briarproject/contact/ContactModule.java +++ b/briar-core/src/org/briarproject/contact/ContactModule.java @@ -1,8 +1,18 @@ package org.briarproject.contact; +import org.briarproject.api.contact.ContactExchangeTask; import org.briarproject.api.contact.ContactManager; +import org.briarproject.api.crypto.CryptoComponent; +import org.briarproject.api.data.BdfReaderFactory; +import org.briarproject.api.data.BdfWriterFactory; +import org.briarproject.api.identity.AuthorFactory; import org.briarproject.api.identity.IdentityManager; import org.briarproject.api.lifecycle.LifecycleManager; +import org.briarproject.api.plugins.ConnectionManager; +import org.briarproject.api.system.Clock; +import org.briarproject.api.transport.KeyManager; +import org.briarproject.api.transport.StreamReaderFactory; +import org.briarproject.api.transport.StreamWriterFactory; import javax.inject.Inject; import javax.inject.Singleton; @@ -25,4 +35,18 @@ public class ContactModule { identityManager.registerRemoveIdentityHook(contactManager); return contactManager; } + + @Provides + ContactExchangeTask provideContactExchangeTask( + AuthorFactory authorFactory, BdfReaderFactory bdfReaderFactory, + BdfWriterFactory bdfWriterFactory, Clock clock, + ConnectionManager connectionManager, ContactManager contactManager, + CryptoComponent crypto, KeyManager keyManager, + StreamReaderFactory streamReaderFactory, + StreamWriterFactory streamWriterFactory) { + return new ContactExchangeTaskImpl(authorFactory, + bdfReaderFactory, bdfWriterFactory, clock, connectionManager, + contactManager, crypto, keyManager, streamReaderFactory, + streamWriterFactory); + } } diff --git a/briar-core/src/org/briarproject/crypto/CryptoComponentImpl.java b/briar-core/src/org/briarproject/crypto/CryptoComponentImpl.java index 2a779f3fe2..c17f679010 100644 --- a/briar-core/src/org/briarproject/crypto/CryptoComponentImpl.java +++ b/briar-core/src/org/briarproject/crypto/CryptoComponentImpl.java @@ -214,7 +214,7 @@ class CryptoComponentImpl implements CryptoComponent { return new SecretKey(macKdf(master, alice ? BT_A_INVITE : BT_B_INVITE)); } - public byte[] deriveBTSignatureNonce(SecretKey master, boolean alice) { + public byte[] deriveSignatureNonce(SecretKey master, boolean alice) { return macKdf(master, alice ? BT_A_NONCE : BT_B_NONCE); } diff --git a/briar-core/src/org/briarproject/invitation/AliceConnector.java b/briar-core/src/org/briarproject/invitation/AliceConnector.java index 210d173d9e..fc02774043 100644 --- a/briar-core/src/org/briarproject/invitation/AliceConnector.java +++ b/briar-core/src/org/briarproject/invitation/AliceConnector.java @@ -138,8 +138,8 @@ class AliceConnector extends Connector { aliceHeaderKey); w = bdfWriterFactory.createWriter(streamWriter); // Derive the invitation nonces - byte[] aliceNonce = crypto.deriveBTSignatureNonce(master, true); - byte[] bobNonce = crypto.deriveBTSignatureNonce(master, false); + byte[] aliceNonce = crypto.deriveSignatureNonce(master, true); + byte[] bobNonce = crypto.deriveSignatureNonce(master, false); // Exchange pseudonyms, signed nonces, and timestamps Author remoteAuthor; long remoteTimestamp; diff --git a/briar-core/src/org/briarproject/invitation/BobConnector.java b/briar-core/src/org/briarproject/invitation/BobConnector.java index f2c6e26196..2f1cfc2e8d 100644 --- a/briar-core/src/org/briarproject/invitation/BobConnector.java +++ b/briar-core/src/org/briarproject/invitation/BobConnector.java @@ -138,8 +138,8 @@ class BobConnector extends Connector { bobHeaderKey); w = bdfWriterFactory.createWriter(streamWriter); // Derive the nonces - byte[] aliceNonce = crypto.deriveBTSignatureNonce(master, true); - byte[] bobNonce = crypto.deriveBTSignatureNonce(master, false); + byte[] aliceNonce = crypto.deriveSignatureNonce(master, true); + byte[] bobNonce = crypto.deriveSignatureNonce(master, false); // Exchange pseudonyms, signed nonces and timestamps Author remoteAuthor; long remoteTimestamp; -- GitLab