mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
WIP: Add handshake support for outgoing connections.
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
* <p/>
|
||||
* {@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<TransportId, KeySetId> 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<PublicKey, KeyPair> 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);
|
||||
|
||||
@@ -101,9 +101,9 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<TransportId, KeySetId> addRotationKeys(
|
||||
Transaction txn, ContactId c, SecretKey rootKey, long timestamp,
|
||||
boolean alice, boolean active) throws DbException {
|
||||
public Map<TransportId, KeySetId> addRotationKeys(Transaction txn,
|
||||
ContactId c, SecretKey rootKey, long timestamp, boolean alice,
|
||||
boolean active) throws DbException {
|
||||
Map<TransportId, KeySetId> ids = new HashMap<>();
|
||||
for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) {
|
||||
TransportId t = e.getKey();
|
||||
@@ -114,6 +114,14 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
|
||||
return ids;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<TransportId, KeySetId> 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<TransportId, KeySetId> 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);
|
||||
|
||||
Reference in New Issue
Block a user