diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/HandshakeManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/HandshakeManager.java
index 1f586ada5..538d775c9 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/HandshakeManager.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/HandshakeManager.java
@@ -24,6 +24,19 @@ public interface HandshakeManager {
HandshakeResult handshake(PendingContactId p, InputStream in,
StreamWriter out) throws DbException, IOException;
+ /**
+ * Handshakes with the given contact. Returns an ephemeral master key
+ * authenticated with both parties' handshake key pairs and a flag
+ * indicating whether the local peer is Alice or Bob.
+ *
+ * @param in An incoming stream for the handshake, which must be secured in
+ * handshake mode
+ * @param out An outgoing stream for the handshake, which must be secured
+ * in handshake mode
+ */
+ HandshakeResult handshake(ContactId c, InputStream in, StreamWriter out)
+ throws DbException, IOException;
+
class HandshakeResult {
private final SecretKey masterKey;
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java
index 50f7d8aa0..b23bbb96a 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java
@@ -35,6 +35,20 @@ public interface KeyManager {
ContactId c, SecretKey rootKey, long timestamp, boolean alice,
boolean active) throws DbException;
+ /**
+ * Derives and stores a set of rotation mode transport keys for
+ * communicating with the given contact over each transport and returns the
+ * key set IDs.
+ *
+ * {@link StreamContext StreamContexts} for the contact can be created
+ * after this method has returned.
+ *
+ * @param alice True if the local party is Alice
+ * @param active Whether the derived keys can be used for outgoing streams
+ */
+ Map addRotationKeys(ContactId c, SecretKey rootKey,
+ long timestamp, boolean alice, boolean active) throws DbException;
+
/**
* Informs the key manager that a new contact has been added. Derives and
* stores a set of handshake mode transport keys for communicating with the
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/connection/ConnectionManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/connection/ConnectionManagerImpl.java
index 2a50033b1..08542cac7 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/connection/ConnectionManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/connection/ConnectionManagerImpl.java
@@ -101,7 +101,7 @@ class ConnectionManagerImpl implements ConnectionManager {
ioExecutor.execute(new OutgoingDuplexSyncConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory,
syncSessionFactory, transportPropertyManager, ioExecutor,
- secureRandom, c, t, d));
+ secureRandom, handshakeManager, c, t, d));
}
@Override
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/connection/OutgoingDuplexSyncConnection.java b/bramble-core/src/main/java/org/briarproject/bramble/connection/OutgoingDuplexSyncConnection.java
index d7f777b7b..9ffb974c6 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/connection/OutgoingDuplexSyncConnection.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/connection/OutgoingDuplexSyncConnection.java
@@ -2,6 +2,7 @@ package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactId;
+import org.briarproject.bramble.api.contact.HandshakeManager;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
@@ -14,9 +15,11 @@ import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
+import org.briarproject.bramble.api.transport.StreamWriter;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException;
+import java.io.InputStream;
import java.security.SecureRandom;
import java.util.concurrent.Executor;
@@ -28,21 +31,31 @@ import static org.briarproject.bramble.util.LogUtils.logException;
class OutgoingDuplexSyncConnection extends DuplexSyncConnection
implements Runnable {
+ // FIXME: Exchange timestamp as part of handshake protocol?
+ private static final long TIMESTAMP = 1617235200; // 1 April 2021 00:00 UTC
+
private final SecureRandom secureRandom;
+ private final HandshakeManager handshakeManager;
private final ContactId contactId;
- OutgoingDuplexSyncConnection(KeyManager keyManager,
+ OutgoingDuplexSyncConnection(
+ KeyManager keyManager,
ConnectionRegistry connectionRegistry,
StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory,
SyncSessionFactory syncSessionFactory,
TransportPropertyManager transportPropertyManager,
- Executor ioExecutor, SecureRandom secureRandom, ContactId contactId,
- TransportId transportId, DuplexTransportConnection connection) {
+ Executor ioExecutor,
+ SecureRandom secureRandom,
+ HandshakeManager handshakeManager,
+ ContactId contactId,
+ TransportId transportId,
+ DuplexTransportConnection connection) {
super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory, syncSessionFactory,
transportPropertyManager, ioExecutor, transportId, connection);
this.secureRandom = secureRandom;
+ this.handshakeManager = handshakeManager;
this.contactId = contactId;
}
@@ -56,10 +69,22 @@ class OutgoingDuplexSyncConnection extends DuplexSyncConnection
return;
}
if (ctx.isHandshakeMode()) {
- // TODO: Support handshake mode for contacts
- LOG.warning("Cannot use handshake mode stream context");
- onWriteError();
- return;
+ if (!performHandshake(ctx)) {
+ LOG.warning("Handshake failed");
+ return;
+ }
+ // Allocate a rotation mode stream context
+ ctx = allocateStreamContext(contactId, transportId);
+ if (ctx == null) {
+ LOG.warning("Could not allocate stream context");
+ onWriteError();
+ return;
+ }
+ if (ctx.isHandshakeMode()) {
+ LOG.warning("Got handshake mode context after handshaking");
+ onWriteError();
+ return;
+ }
}
// Start the incoming session on another thread
Priority priority = generatePriority();
@@ -127,6 +152,57 @@ class OutgoingDuplexSyncConnection extends DuplexSyncConnection
}
}
+ private boolean performHandshake(StreamContext ctxOut) {
+ // Flush the output stream to send the outgoing stream header
+ StreamWriter out;
+ try {
+ out = streamWriterFactory.createStreamWriter(
+ writer.getOutputStream(), ctxOut);
+ out.getOutputStream().flush();
+ } catch (IOException e) {
+ logException(LOG, WARNING, e);
+ onWriteError();
+ return false;
+ }
+ // Read and recognise the tag
+ StreamContext ctxIn = recogniseTag(reader, transportId);
+ // Unrecognised tags are suspicious in this case
+ if (ctxIn == null) {
+ LOG.warning("Unrecognised tag for returning stream");
+ onReadError();
+ return false;
+ }
+ // Check that the stream comes from the expected contact
+ ContactId inContactId = ctxIn.getContactId();
+ if (contactId == null) {
+ LOG.warning("Expected contact tag, got rendezvous tag");
+ onReadError();
+ return false;
+ }
+ if (!inContactId.equals(contactId)) {
+ LOG.warning("Wrong contact ID for returning stream");
+ onReadError();
+ return false;
+ }
+ // TODO: Register the connection, close it if it's redundant
+ // Handshake and exchange contacts
+ try {
+ InputStream in = streamReaderFactory.createStreamReader(
+ reader.getInputStream(), ctxIn);
+ HandshakeManager.HandshakeResult result =
+ handshakeManager.handshake(contactId, in, out);
+ keyManager.addRotationKeys(contactId, result.getMasterKey(),
+ TIMESTAMP, result.isAlice(), true);
+ return true;
+ } catch (IOException | DbException e) {
+ logException(LOG, WARNING, e);
+ onWriteError();
+ return false;
+ } finally {
+ // TODO: Unregister the connection
+ }
+ }
+
private void onReadError() {
// 'Recognised' is always true for outgoing connections
onReadError(true);
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeManagerImpl.java
index 7edeaf786..068685ff7 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeManagerImpl.java
@@ -3,6 +3,8 @@ package org.briarproject.bramble.contact;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.Predicate;
+import org.briarproject.bramble.api.contact.Contact;
+import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.HandshakeManager;
import org.briarproject.bramble.api.contact.PendingContact;
@@ -88,6 +90,27 @@ class HandshakeManagerImpl implements HandshakeManager {
});
PublicKey theirStaticPublicKey = keys.getFirst();
KeyPair ourStaticKeyPair = keys.getSecond();
+ return handshake(theirStaticPublicKey, ourStaticKeyPair, in, out);
+ }
+
+ @Override
+ public HandshakeResult handshake(ContactId c, InputStream in,
+ StreamWriter out) throws DbException, IOException {
+ Pair keys = db.transactionWithResult(true, txn -> {
+ Contact contact = contactManager.getContact(txn, c);
+ PublicKey handshakePublicKey = contact.getHandshakePublicKey();
+ if (handshakePublicKey == null) throw new DbException();
+ KeyPair keyPair = identityManager.getHandshakeKeys(txn);
+ return new Pair<>(handshakePublicKey, keyPair);
+ });
+ PublicKey theirStaticPublicKey = keys.getFirst();
+ KeyPair ourStaticKeyPair = keys.getSecond();
+ return handshake(theirStaticPublicKey, ourStaticKeyPair, in, out);
+ }
+
+ private HandshakeResult handshake(PublicKey theirStaticPublicKey,
+ KeyPair ourStaticKeyPair, InputStream in, StreamWriter out)
+ throws IOException {
boolean alice = transportCrypto.isAlice(theirStaticPublicKey,
ourStaticKeyPair);
RecordReader recordReader = recordReaderFactory.createRecordReader(in);
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java
index 58aeb7cea..d240d644f 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java
@@ -101,9 +101,9 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
}
@Override
- public Map addRotationKeys(
- Transaction txn, ContactId c, SecretKey rootKey, long timestamp,
- boolean alice, boolean active) throws DbException {
+ public Map addRotationKeys(Transaction txn,
+ ContactId c, SecretKey rootKey, long timestamp, boolean alice,
+ boolean active) throws DbException {
Map ids = new HashMap<>();
for (Entry e : managers.entrySet()) {
TransportId t = e.getKey();
@@ -114,6 +114,14 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
return ids;
}
+ @Override
+ public Map addRotationKeys(ContactId c,
+ SecretKey rootKey, long timestamp, boolean alice, boolean active)
+ throws DbException {
+ return db.transactionWithResult(false, txn ->
+ addRotationKeys(txn, c, rootKey, timestamp, alice, active));
+ }
+
@Override
public Map addContact(Transaction txn, ContactId c,
PublicKey theirPublicKey, KeyPair ourKeyPair)
@@ -137,7 +145,7 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
PendingContactId p, PublicKey theirPublicKey, KeyPair ourKeyPair)
throws DbException, GeneralSecurityException {
SecretKey staticMasterKey = transportCrypto
- .deriveStaticMasterKey(theirPublicKey, ourKeyPair);
+ .deriveStaticMasterKey(theirPublicKey, ourKeyPair);
SecretKey rootKey =
transportCrypto.deriveHandshakeRootKey(staticMasterKey, true);
boolean alice = transportCrypto.isAlice(theirPublicKey, ourKeyPair);