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