From 60155f146acecfe3b217e0f949d3c2b1108ec907 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Mon, 27 May 2019 14:53:25 +0100 Subject: [PATCH 01/12] Add contact exchange method for pending contacts. --- .../api/contact/ContactExchangeManager.java | 21 ++++-- .../contact/ContactExchangeManagerImpl.java | 71 ++++++++++++++----- .../contact/ContactExchangeRecordTypes.java | 4 +- .../ContactExchangeViewModel.java | 4 +- 4 files changed, 73 insertions(+), 27 deletions(-) rename bramble-api/src/main/java/org/briarproject/bramble/api/contact/RecordTypes.java => bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeRecordTypes.java (51%) diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactExchangeManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactExchangeManager.java index 0981158f0..dad6ae6b1 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactExchangeManager.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactExchangeManager.java @@ -4,7 +4,6 @@ import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.db.ContactExistsException; import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import java.io.IOException; @@ -13,12 +12,26 @@ import java.io.IOException; public interface ContactExchangeManager { /** - * Exchanges contact information with a remote peer. + * Exchanges contact information with a remote peer and adds the peer + * as a contact. * * @param alice Whether the local peer takes the role of Alice * @return The newly added contact * @throws ContactExistsException If the contact already exists */ - Contact exchangeContacts(TransportId t, DuplexTransportConnection conn, - SecretKey masterKey, boolean alice) throws IOException, DbException; + Contact exchangeContacts(DuplexTransportConnection conn, + SecretKey masterKey, boolean alice, boolean verified) + throws IOException, DbException; + + /** + * Exchanges contact information with a remote peer and adds the peer + * as a contact, replacing the given pending contact. + * + * @param alice Whether the local peer takes the role of Alice + * @return The newly added contact + * @throws ContactExistsException If the contact already exists + */ + Contact exchangeContacts(PendingContactId p, DuplexTransportConnection conn, + SecretKey masterKey, boolean alice, boolean verified) + throws IOException, DbException; } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeManagerImpl.java index 49ed255f2..9e3bd03a7 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeManagerImpl.java @@ -7,17 +7,18 @@ import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.ContactExchangeManager; import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.contact.PendingContactId; import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.LocalAuthor; -import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; -import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.properties.TransportProperties; @@ -36,20 +37,23 @@ import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.security.GeneralSecurityException; import java.util.Map; import java.util.logging.Logger; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; import javax.inject.Inject; import static java.util.logging.Logger.getLogger; -import static org.briarproject.bramble.api.contact.RecordTypes.CONTACT_INFO; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH; import static org.briarproject.bramble.contact.ContactExchangeConstants.PROTOCOL_VERSION; +import static org.briarproject.bramble.contact.ContactExchangeRecordTypes.CONTACT_INFO; import static org.briarproject.bramble.util.ValidationUtils.checkLength; import static org.briarproject.bramble.util.ValidationUtils.checkSize; -@MethodsNotNullByDefault -@ParametersNotNullByDefault +@Immutable +@NotNullByDefault class ContactExchangeManagerImpl implements ContactExchangeManager { private static final Logger LOG = @@ -104,9 +108,22 @@ class ContactExchangeManagerImpl implements ContactExchangeManager { } @Override - public Contact exchangeContacts(TransportId t, - DuplexTransportConnection conn, SecretKey masterKey, boolean alice) - throws IOException, DbException { + public Contact exchangeContacts(DuplexTransportConnection conn, + SecretKey masterKey, boolean alice, + boolean verified) throws IOException, DbException { + return exchange(null, conn, masterKey, alice, verified); + } + + @Override + public Contact exchangeContacts(PendingContactId p, + DuplexTransportConnection conn, SecretKey masterKey, boolean alice, + boolean verified) throws IOException, DbException { + return exchange(p, conn, masterKey, alice, verified); + } + + private Contact exchange(@Nullable PendingContactId p, + DuplexTransportConnection conn, SecretKey masterKey, boolean alice, + boolean verified) throws IOException, DbException { // Get the transport connection's input and output streams InputStream in = conn.getReader().getInputStream(); OutputStream out = conn.getWriter().getOutputStream(); @@ -169,8 +186,8 @@ class ContactExchangeManagerImpl implements ContactExchangeManager { long timestamp = Math.min(localTimestamp, remoteInfo.timestamp); // Add the contact - Contact contact = addContact(remoteInfo.author, localAuthor, - masterKey, timestamp, alice, remoteInfo.properties); + Contact contact = addContact(p, remoteInfo.author, localAuthor, + masterKey, timestamp, alice, verified, remoteInfo.properties); // Contact exchange succeeded LOG.info("Contact exchange succeeded"); @@ -207,18 +224,34 @@ class ContactExchangeManagerImpl implements ContactExchangeManager { return new ContactInfo(author, properties, signature, timestamp); } - private Contact addContact(Author remoteAuthor, LocalAuthor localAuthor, - SecretKey masterKey, long timestamp, boolean alice, + private Contact addContact(@Nullable PendingContactId pendingContactId, + Author remoteAuthor, LocalAuthor localAuthor, SecretKey masterKey, + long timestamp, boolean alice, boolean verified, Map remoteProperties) - throws DbException { - return db.transactionWithResult(false, txn -> { - ContactId contactId = contactManager.addContact(txn, remoteAuthor, - localAuthor.getId(), masterKey, timestamp, alice, - true, true); + throws DbException, FormatException { + Transaction txn = db.startTransaction(false); + try { + ContactId contactId; + if (pendingContactId == null) { + contactId = contactManager.addContact(txn, remoteAuthor, + localAuthor.getId(), masterKey, timestamp, alice, + verified, true); + } else { + contactId = contactManager.addContact(txn, pendingContactId, + remoteAuthor, localAuthor.getId(), masterKey, + timestamp, alice, verified, true); + } transportPropertyManager.addRemoteProperties(txn, contactId, remoteProperties); - return contactManager.getContact(txn, contactId); - }); + Contact contact = contactManager.getContact(txn, contactId); + db.commitTransaction(txn); + return contact; + } catch (GeneralSecurityException e) { + // Pending contact's public key is invalid + throw new FormatException(); + } finally { + db.endTransaction(txn); + } } private static class ContactInfo { diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/RecordTypes.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeRecordTypes.java similarity index 51% rename from bramble-api/src/main/java/org/briarproject/bramble/api/contact/RecordTypes.java rename to bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeRecordTypes.java index bd24dcf67..750cd7e53 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/RecordTypes.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeRecordTypes.java @@ -1,9 +1,9 @@ -package org.briarproject.bramble.api.contact; +package org.briarproject.bramble.contact; /** * Record types for the contact exchange protocol. */ -public interface RecordTypes { +interface ContactExchangeRecordTypes { byte CONTACT_INFO = 0; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeViewModel.java index a7ada2cbe..7a274bdc9 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeViewModel.java @@ -58,8 +58,8 @@ class ContactExchangeViewModel extends AndroidViewModel { SecretKey masterKey, boolean alice) { ioExecutor.execute(() -> { try { - Contact contact = contactExchangeManager.exchangeContacts(t, - conn, masterKey, alice); + Contact contact = contactExchangeManager.exchangeContacts(conn, + masterKey, alice, true); // Reuse the connection as a transport connection connectionManager.manageOutgoingConnection(contact.getId(), t, conn); From 24f1b7eecad3176d88c1ebd8aec36d2824519796 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Mon, 27 May 2019 14:54:05 +0100 Subject: [PATCH 02/12] Implement handshake manager. --- .../bramble/api/contact/HandshakeManager.java | 27 +++ .../bramble/contact/ContactModule.java | 14 ++ .../bramble/contact/HandshakeConstants.java | 25 +++ .../bramble/contact/HandshakeCrypto.java | 43 +++++ .../bramble/contact/HandshakeCryptoImpl.java | 75 ++++++++ .../bramble/contact/HandshakeManagerImpl.java | 165 ++++++++++++++++++ .../bramble/contact/HandshakeRecordTypes.java | 11 ++ 7 files changed, 360 insertions(+) create mode 100644 bramble-api/src/main/java/org/briarproject/bramble/api/contact/HandshakeManager.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeConstants.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeCrypto.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeCryptoImpl.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeManagerImpl.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeRecordTypes.java 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 new file mode 100644 index 000000000..6a9eb6eda --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/HandshakeManager.java @@ -0,0 +1,27 @@ +package org.briarproject.bramble.api.contact; + +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; +import org.briarproject.bramble.api.transport.StreamWriter; + +import java.io.IOException; +import java.io.InputStream; + +@NotNullByDefault +public interface HandshakeManager { + + /** + * Handshakes and exchanges pseudonyms with the given pending contact, + * converts the pending contact to a contact and returns the contact. + * + * @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 + * @param conn The connection to use for the handshake and contact exchange + */ + Contact handshakeAndAddContact(PendingContactId p, + InputStream in, StreamWriter out, DuplexTransportConnection conn) + throws DbException, IOException; +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactModule.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactModule.java index 97af570b6..f4d6df9f3 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactModule.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactModule.java @@ -2,6 +2,7 @@ package org.briarproject.bramble.contact; import org.briarproject.bramble.api.contact.ContactExchangeManager; import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.contact.HandshakeManager; import javax.inject.Inject; import javax.inject.Singleton; @@ -40,4 +41,17 @@ public class ContactModule { ContactExchangeCryptoImpl contactExchangeCrypto) { return contactExchangeCrypto; } + + @Provides + @Singleton + HandshakeManager provideHandshakeManager( + HandshakeManagerImpl handshakeManager) { + return handshakeManager; + } + + @Provides + HandshakeCrypto provideHandshakeCrypto( + HandshakeCryptoImpl handshakeCrypto) { + return handshakeCrypto; + } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeConstants.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeConstants.java new file mode 100644 index 000000000..1fba77722 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeConstants.java @@ -0,0 +1,25 @@ +package org.briarproject.bramble.contact; + +interface HandshakeConstants { + + /** + * The current version of the handshake protocol. + */ + byte PROTOCOL_VERSION = 0; + + /** + * Label for deriving the master key. + */ + String MASTER_KEY_LABEL = "org.briarproject.bramble.handshake/MASTER_KEY"; + + /** + * Label for deriving Alice's proof of ownership from the master key. + */ + String ALICE_PROOF_LABEL = "org.briarproject.bramble.handshake/ALICE_PROOF"; + + /** + * Label for deriving Bob's proof of ownership from the master key. + */ + String BOB_PROOF_LABEL = "org.briarproject.bramble.handshake/BOB_PROOF"; + +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeCrypto.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeCrypto.java new file mode 100644 index 000000000..88d5a6fd5 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeCrypto.java @@ -0,0 +1,43 @@ +package org.briarproject.bramble.contact; + +import org.briarproject.bramble.api.crypto.KeyPair; +import org.briarproject.bramble.api.crypto.PublicKey; +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import java.security.GeneralSecurityException; + +@NotNullByDefault +interface HandshakeCrypto { + + boolean isLocalPeerAlice(PublicKey theirStaticPublicKey, + KeyPair ourStaticKeyPair); + + KeyPair generateEphemeralKeyPair(); + + /** + * Derives the master key from the given static and ephemeral keys. + * + * @param alice Whether the local peer is Alice + */ + SecretKey deriveMasterKey(PublicKey theirStaticPublicKey, + PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair, + KeyPair ourEphemeralKeyPair, boolean alice) + throws GeneralSecurityException; + + /** + * Returns proof that the local peer knows the master key and therefore + * owns the static and ephemeral public keys sent by the local peer. + * + * @param alice Whether the proof is being created by Alice + */ + byte[] proveOwnership(SecretKey masterKey, boolean alice); + + /** + * Verifies the given proof that the remote peer knows the master key and + * therefore owns the static and ephemeral keys sent by the remote peer. + * + * @param alice Whether the proof was created by Alice + */ + boolean verifyOwnership(SecretKey masterKey, boolean alice, byte[] proof); +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeCryptoImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeCryptoImpl.java new file mode 100644 index 000000000..d01c6f1a2 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeCryptoImpl.java @@ -0,0 +1,75 @@ +package org.briarproject.bramble.contact; + +import org.briarproject.bramble.api.Bytes; +import org.briarproject.bramble.api.crypto.CryptoComponent; +import org.briarproject.bramble.api.crypto.KeyPair; +import org.briarproject.bramble.api.crypto.PublicKey; +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import java.security.GeneralSecurityException; + +import javax.annotation.concurrent.Immutable; +import javax.inject.Inject; + +import static org.briarproject.bramble.contact.HandshakeConstants.ALICE_PROOF_LABEL; +import static org.briarproject.bramble.contact.HandshakeConstants.BOB_PROOF_LABEL; +import static org.briarproject.bramble.contact.HandshakeConstants.MASTER_KEY_LABEL; + +@Immutable +@NotNullByDefault +class HandshakeCryptoImpl implements HandshakeCrypto { + + private final CryptoComponent crypto; + + @Inject + HandshakeCryptoImpl(CryptoComponent crypto) { + this.crypto = crypto; + } + + @Override + public boolean isLocalPeerAlice(PublicKey theirStaticPublicKey, + KeyPair ourStaticKeyPair) { + byte[] ours = ourStaticKeyPair.getPublic().getEncoded(); + byte[] theirs = theirStaticPublicKey.getEncoded(); + return new Bytes(ours).compareTo(new Bytes(theirs)) < 0; + } + + @Override + public KeyPair generateEphemeralKeyPair() { + return crypto.generateAgreementKeyPair(); + } + + @Override + public SecretKey deriveMasterKey(PublicKey theirStaticPublicKey, + PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair, + KeyPair ourEphemeralKeyPair, boolean alice) throws + GeneralSecurityException { + byte[] theirStatic = theirStaticPublicKey.getEncoded(); + byte[] theirEphemeral = theirEphemeralPublicKey.getEncoded(); + byte[] ourStatic = ourStaticKeyPair.getPublic().getEncoded(); + byte[] ourEphemeral = ourEphemeralKeyPair.getPublic().getEncoded(); + byte[][] inputs = { + alice ? ourStatic : theirStatic, + alice ? theirStatic : ourStatic, + alice ? ourEphemeral : theirEphemeral, + alice ? theirEphemeral : ourEphemeral + }; + return crypto.deriveSharedSecret(MASTER_KEY_LABEL, theirStaticPublicKey, + theirEphemeralPublicKey, ourStaticKeyPair, ourEphemeralKeyPair, + alice, inputs); + } + + @Override + public byte[] proveOwnership(SecretKey masterKey, boolean alice) { + String label = alice ? ALICE_PROOF_LABEL : BOB_PROOF_LABEL; + return crypto.mac(label, masterKey); + } + + @Override + public boolean verifyOwnership(SecretKey masterKey, boolean alice, + byte[] proof) { + String label = alice ? ALICE_PROOF_LABEL : BOB_PROOF_LABEL; + return crypto.verifyMac(proof, label, masterKey); + } +} 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 new file mode 100644 index 000000000..bb4cb5dbb --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeManagerImpl.java @@ -0,0 +1,165 @@ +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.ContactExchangeManager; +import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.contact.HandshakeManager; +import org.briarproject.bramble.api.contact.PendingContact; +import org.briarproject.bramble.api.contact.PendingContactId; +import org.briarproject.bramble.api.crypto.AgreementPublicKey; +import org.briarproject.bramble.api.crypto.KeyPair; +import org.briarproject.bramble.api.crypto.PublicKey; +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.db.DatabaseComponent; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.db.TransactionManager; +import org.briarproject.bramble.api.identity.IdentityManager; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; +import org.briarproject.bramble.api.record.Record; +import org.briarproject.bramble.api.record.RecordReader; +import org.briarproject.bramble.api.record.RecordReaderFactory; +import org.briarproject.bramble.api.record.RecordWriter; +import org.briarproject.bramble.api.record.RecordWriterFactory; +import org.briarproject.bramble.api.transport.StreamWriter; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.security.GeneralSecurityException; + +import javax.annotation.concurrent.Immutable; +import javax.inject.Inject; + +import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES; +import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_VERSION; +import static org.briarproject.bramble.contact.HandshakeRecordTypes.EPHEMERAL_PUBLIC_KEY; +import static org.briarproject.bramble.contact.HandshakeRecordTypes.PROOF_OF_OWNERSHIP; + +@Immutable +@NotNullByDefault +class HandshakeManagerImpl implements HandshakeManager { + + // Ignore records with current protocol version, unknown record type + private static final Predicate IGNORE = r -> + r.getProtocolVersion() == PROTOCOL_VERSION && + !isKnownRecordType(r.getRecordType()); + + private static boolean isKnownRecordType(byte type) { + return type == EPHEMERAL_PUBLIC_KEY || type == PROOF_OF_OWNERSHIP; + } + + private final TransactionManager db; + private final IdentityManager identityManager; + private final ContactManager contactManager; + private final HandshakeCrypto handshakeCrypto; + private final RecordReaderFactory recordReaderFactory; + private final RecordWriterFactory recordWriterFactory; + private final ContactExchangeManager contactExchangeManager; + + @Inject + HandshakeManagerImpl(DatabaseComponent db, + IdentityManager identityManager, + ContactManager contactManager, + HandshakeCrypto handshakeCrypto, + RecordReaderFactory recordReaderFactory, + RecordWriterFactory recordWriterFactory, + ContactExchangeManager contactExchangeManager) { + this.db = db; + this.identityManager = identityManager; + this.contactManager = contactManager; + this.handshakeCrypto = handshakeCrypto; + this.recordReaderFactory = recordReaderFactory; + this.recordWriterFactory = recordWriterFactory; + this.contactExchangeManager = contactExchangeManager; + } + + @Override + public Contact handshakeAndAddContact(PendingContactId p, + InputStream in, StreamWriter out, DuplexTransportConnection conn) + throws DbException, IOException { + Pair keys = db.transactionWithResult(true, txn -> { + PendingContact pendingContact = + contactManager.getPendingContact(txn, p); + KeyPair keyPair = identityManager.getHandshakeKeys(txn); + return new Pair<>(pendingContact.getPublicKey(), keyPair); + }); + PublicKey theirStaticPublicKey = keys.getFirst(); + KeyPair ourStaticKeyPair = keys.getSecond(); + boolean alice = handshakeCrypto.isLocalPeerAlice(theirStaticPublicKey, + ourStaticKeyPair); + RecordReader recordReader = recordReaderFactory.createRecordReader(in); + RecordWriter recordWriter = recordWriterFactory + .createRecordWriter(out.getOutputStream()); + KeyPair ourEphemeralKeyPair = + handshakeCrypto.generateEphemeralKeyPair(); + PublicKey theirEphemeralPublicKey; + if (alice) { + sendPublicKey(recordWriter, ourEphemeralKeyPair.getPublic()); + theirEphemeralPublicKey = receivePublicKey(recordReader); + } else { + theirEphemeralPublicKey = receivePublicKey(recordReader); + sendPublicKey(recordWriter, ourEphemeralKeyPair.getPublic()); + } + SecretKey masterKey; + try { + masterKey = handshakeCrypto.deriveMasterKey(theirStaticPublicKey, + theirEphemeralPublicKey, ourStaticKeyPair, + ourEphemeralKeyPair, alice); + } catch (GeneralSecurityException e) { + throw new FormatException(); + } + byte[] ourProof = handshakeCrypto.proveOwnership(masterKey, alice); + byte[] theirProof; + if (alice) { + sendProof(recordWriter, ourProof); + theirProof = receiveProof(recordReader); + } else { + theirProof = receiveProof(recordReader); + sendProof(recordWriter, ourProof); + } + out.sendEndOfStream(); + recordReader.readRecord(r -> false, IGNORE); + if (!handshakeCrypto.verifyOwnership(masterKey, !alice, theirProof)) + throw new FormatException(); + return contactExchangeManager.exchangeContacts(p, conn, masterKey, + alice, false); + } + + private void sendPublicKey(RecordWriter w, PublicKey k) throws IOException { + w.writeRecord(new Record(PROTOCOL_VERSION, EPHEMERAL_PUBLIC_KEY, + k.getEncoded())); + w.flush(); + } + + private PublicKey receivePublicKey(RecordReader r) throws IOException { + Record rec = readRecord(r, EPHEMERAL_PUBLIC_KEY); + int length = rec.getPayload().length; + if (length == 0 || length > MAX_AGREEMENT_PUBLIC_KEY_BYTES) + throw new FormatException(); + return new AgreementPublicKey(rec.getPayload()); + } + + private void sendProof(RecordWriter w, byte[] proof) throws IOException { + w.writeRecord(new Record(PROTOCOL_VERSION, PROOF_OF_OWNERSHIP, proof)); + w.flush(); + } + + private byte[] receiveProof(RecordReader r) throws IOException { + return readRecord(r, PROOF_OF_OWNERSHIP).getPayload(); + } + + private Record readRecord(RecordReader r, byte expectedType) + throws IOException { + // Accept records with current protocol version, expected type only + Predicate accept = rec -> + rec.getProtocolVersion() == PROTOCOL_VERSION && + rec.getRecordType() == expectedType; + Record rec = r.readRecord(accept, IGNORE); + if (rec == null) throw new EOFException(); + return rec; + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeRecordTypes.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeRecordTypes.java new file mode 100644 index 000000000..7c38d9a84 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeRecordTypes.java @@ -0,0 +1,11 @@ +package org.briarproject.bramble.contact; + +/** + * Record types for the handshake protocol. + */ +interface HandshakeRecordTypes { + + byte EPHEMERAL_PUBLIC_KEY = 0; + + byte PROOF_OF_OWNERSHIP = 1; +} From 643270e2471c7eb9ef26784c49c105fc92376cef Mon Sep 17 00:00:00 2001 From: akwizgran Date: Fri, 31 May 2019 12:08:03 +0100 Subject: [PATCH 03/12] Add integration test for ContactExchangeManager. --- .../ContactExchangeIntegrationTest.java | 132 ++++++++++++++++++ ...ntactExchangeIntegrationTestComponent.java | 36 +++++ 2 files changed, 168 insertions(+) create mode 100644 bramble-core/src/test/java/org/briarproject/bramble/contact/ContactExchangeIntegrationTest.java create mode 100644 bramble-core/src/test/java/org/briarproject/bramble/contact/ContactExchangeIntegrationTestComponent.java diff --git a/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactExchangeIntegrationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactExchangeIntegrationTest.java new file mode 100644 index 000000000..cb6adf27f --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactExchangeIntegrationTest.java @@ -0,0 +1,132 @@ +package org.briarproject.bramble.contact; + +import org.briarproject.bramble.api.contact.Contact; +import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.api.identity.Identity; +import org.briarproject.bramble.api.identity.IdentityManager; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.test.BrambleTestCase; +import org.briarproject.bramble.test.TestDatabaseConfigModule; +import org.briarproject.bramble.test.TestDuplexTransportConnection; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.util.Collection; +import java.util.concurrent.CountDownLatch; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static junit.framework.TestCase.fail; +import static org.briarproject.bramble.test.TestDuplexTransportConnection.createPair; +import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory; +import static org.briarproject.bramble.test.TestUtils.getSecretKey; +import static org.briarproject.bramble.test.TestUtils.getTestDirectory; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class ContactExchangeIntegrationTest extends BrambleTestCase { + + private static final int TIMEOUT = 15_000; + + private final File testDir = getTestDirectory(); + private final File aliceDir = new File(testDir, "alice"); + private final File bobDir = new File(testDir, "bob"); + private final SecretKey masterKey = getSecretKey(); + + private ContactExchangeIntegrationTestComponent alice, bob; + private Author aliceAuthor, bobAuthor; + + @Before + public void setUp() throws Exception { + assertTrue(testDir.mkdirs()); + // Create the devices + alice = DaggerContactExchangeIntegrationTestComponent.builder() + .testDatabaseConfigModule( + new TestDatabaseConfigModule(aliceDir)).build(); + alice.injectBrambleCoreEagerSingletons(); + bob = DaggerContactExchangeIntegrationTestComponent.builder() + .testDatabaseConfigModule(new TestDatabaseConfigModule(bobDir)) + .build(); + bob.injectBrambleCoreEagerSingletons(); + // Set up the devices and get the identities + aliceAuthor = setUp(alice, "Alice"); + bobAuthor = setUp(bob, "Bob"); + } + + private Author setUp(ContactExchangeIntegrationTestComponent device, + String name) throws Exception { + // Add an identity for the user + IdentityManager identityManager = device.getIdentityManager(); + Identity identity = identityManager.createIdentity(name); + identityManager.registerIdentity(identity); + // Start the lifecycle manager + LifecycleManager lifecycleManager = device.getLifecycleManager(); + lifecycleManager.startServices(getSecretKey()); + lifecycleManager.waitForStartup(); + // Check the initial conditions + ContactManager contactManager = device.getContactManager(); + assertEquals(0, contactManager.getPendingContacts().size()); + assertEquals(0, contactManager.getContacts().size()); + return identity.getLocalAuthor(); + } + + @Test + public void testExchangeContacts() throws Exception { + TestDuplexTransportConnection[] pair = createPair(); + TestDuplexTransportConnection aliceConnection = pair[0]; + TestDuplexTransportConnection bobConnection = pair[1]; + CountDownLatch aliceFinished = new CountDownLatch(1); + alice.getIoExecutor().execute(() -> { + try { + alice.getContactExchangeManager().exchangeContacts( + aliceConnection, masterKey, true, true); + aliceFinished.countDown(); + } catch (Exception e) { + fail(); + } + }); + CountDownLatch bobFinished = new CountDownLatch(1); + bob.getIoExecutor().execute(() -> { + try { + bob.getContactExchangeManager().exchangeContacts(bobConnection, + masterKey, false, true); + bobFinished.countDown(); + } catch (Exception e) { + fail(); + } + }); + aliceFinished.await(TIMEOUT, MILLISECONDS); + bobFinished.await(TIMEOUT, MILLISECONDS); + assertContactsExchanged(); + } + + private void assertContactsExchanged() throws Exception { + Collection aliceContacts = + alice.getContactManager().getContacts(); + assertEquals(1, aliceContacts.size()); + Contact bobFromAlice = aliceContacts.iterator().next(); + assertEquals(bobAuthor, bobFromAlice.getAuthor()); + Collection bobContacts = bob.getContactManager().getContacts(); + assertEquals(1, bobContacts.size()); + Contact aliceFromBob = bobContacts.iterator().next(); + assertEquals(aliceAuthor, aliceFromBob.getAuthor()); + } + + private void tearDown(ContactExchangeIntegrationTestComponent device) + throws Exception { + // Stop the lifecycle manager + LifecycleManager lifecycleManager = device.getLifecycleManager(); + lifecycleManager.stopServices(); + lifecycleManager.waitForShutdown(); + } + + @After + public void tearDown() throws Exception { + tearDown(alice); + tearDown(bob); + deleteTestDirectory(testDir); + } +} diff --git a/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactExchangeIntegrationTestComponent.java b/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactExchangeIntegrationTestComponent.java new file mode 100644 index 000000000..c0a1e9a4a --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactExchangeIntegrationTestComponent.java @@ -0,0 +1,36 @@ +package org.briarproject.bramble.contact; + +import org.briarproject.bramble.BrambleCoreEagerSingletons; +import org.briarproject.bramble.BrambleCoreModule; +import org.briarproject.bramble.api.contact.ContactExchangeManager; +import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.identity.IdentityManager; +import org.briarproject.bramble.api.lifecycle.IoExecutor; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.test.BrambleCoreIntegrationTestModule; + +import java.util.concurrent.Executor; + +import javax.inject.Singleton; + +import dagger.Component; + +@Singleton +@Component(modules = { + BrambleCoreIntegrationTestModule.class, + BrambleCoreModule.class +}) +interface ContactExchangeIntegrationTestComponent + extends BrambleCoreEagerSingletons { + + ContactExchangeManager getContactExchangeManager(); + + ContactManager getContactManager(); + + IdentityManager getIdentityManager(); + + @IoExecutor + Executor getIoExecutor(); + + LifecycleManager getLifecycleManager(); +} From af8b7f11305dd1eec5f2a41f4b46596e37663820 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Wed, 22 May 2019 13:33:36 +0100 Subject: [PATCH 04/12] Implement getHandshakeLink(). --- .../bramble/contact/ContactManagerImpl.java | 12 ++-- .../contact/PendingContactFactory.java | 6 ++ .../contact/PendingContactFactoryImpl.java | 24 ++++++-- .../contact/ContactManagerImplTest.java | 28 +++++++++- .../PendingContactFactoryImplTest.java | 55 +++++++++++++++++++ .../add/remote/AddContactViewModel.java | 2 +- 6 files changed, 110 insertions(+), 17 deletions(-) diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java index 56a0694b0..821cfdc26 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java @@ -33,23 +33,18 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; import javax.inject.Inject; -import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.BASE32_LINK_BYTES; import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNKNOWN; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNVERIFIED; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED; -import static org.briarproject.bramble.util.StringUtils.getRandomBase32String; import static org.briarproject.bramble.util.StringUtils.toUtf8; @ThreadSafe @NotNullByDefault class ContactManagerImpl implements ContactManager { - private static final String REMOTE_CONTACT_LINK = - "briar://" + getRandomBase32String(BASE32_LINK_BYTES); - private final DatabaseComponent db; private final KeyManager keyManager; private final IdentityManager identityManager; @@ -120,9 +115,10 @@ class ContactManagerImpl implements ContactManager { } @Override - public String getHandshakeLink() { - // TODO replace with real implementation - return REMOTE_CONTACT_LINK; + public String getHandshakeLink() throws DbException { + KeyPair keyPair = db.transactionWithResult(true, + identityManager::getHandshakeKeys); + return pendingContactFactory.createHandshakeLink(keyPair.getPublic()); } @Override diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/PendingContactFactory.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/PendingContactFactory.java index cfa555565..a5e0bc675 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/contact/PendingContactFactory.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/PendingContactFactory.java @@ -3,6 +3,7 @@ package org.briarproject.bramble.contact; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.UnsupportedVersionException; import org.briarproject.bramble.api.contact.PendingContact; +import org.briarproject.bramble.api.crypto.PublicKey; interface PendingContactFactory { @@ -15,4 +16,9 @@ interface PendingContactFactory { */ PendingContact createPendingContact(String link, String alias) throws FormatException; + + /** + * Creates a handshake link from the given public key. + */ + String createHandshakeLink(PublicKey k); } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/PendingContactFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/PendingContactFactoryImpl.java index ae9d1c0cb..68dd24cca 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/contact/PendingContactFactoryImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/PendingContactFactoryImpl.java @@ -20,6 +20,7 @@ import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.FORMAT import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.ID_LABEL; import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.LINK_REGEX; import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.RAW_LINK_BYTES; +import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_AGREEMENT; class PendingContactFactoryImpl implements PendingContactFactory { @@ -41,18 +42,31 @@ class PendingContactFactoryImpl implements PendingContactFactory { return new PendingContact(id, publicKey, alias, timestamp); } + @Override + public String createHandshakeLink(PublicKey k) { + if (!k.getKeyType().equals(KEY_TYPE_AGREEMENT)) + throw new IllegalArgumentException(); + byte[] encoded = k.getEncoded(); + if (encoded.length != RAW_LINK_BYTES - 1) + throw new IllegalArgumentException(); + byte[] raw = new byte[RAW_LINK_BYTES]; + raw[0] = FORMAT_VERSION; + arraycopy(encoded, 0, raw, 1, encoded.length); + return "briar://" + Base32.encode(raw).toLowerCase(); + } + private PublicKey parseHandshakeLink(String link) throws FormatException { Matcher matcher = LINK_REGEX.matcher(link); if (!matcher.find()) throw new FormatException(); // Discard 'briar://' and anything before or after the link link = matcher.group(2); - byte[] base32 = Base32.decode(link, false); - if (base32.length != RAW_LINK_BYTES) throw new AssertionError(); - byte version = base32[0]; + byte[] raw = Base32.decode(link, false); + if (raw.length != RAW_LINK_BYTES) throw new AssertionError(); + byte version = raw[0]; if (version != FORMAT_VERSION) throw new UnsupportedVersionException(version < FORMAT_VERSION); - byte[] publicKeyBytes = new byte[base32.length - 1]; - arraycopy(base32, 1, publicKeyBytes, 0, publicKeyBytes.length); + byte[] publicKeyBytes = new byte[raw.length - 1]; + arraycopy(raw, 1, publicKeyBytes, 0, publicKeyBytes.length); try { KeyParser parser = crypto.getAgreementKeyParser(); return parser.parsePublicKey(publicKeyBytes); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactManagerImplTest.java index 195de4ef3..1eab88e88 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactManagerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactManagerImplTest.java @@ -3,6 +3,9 @@ package org.briarproject.bramble.contact; 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.crypto.KeyPair; +import org.briarproject.bramble.api.crypto.PrivateKey; +import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DbException; @@ -17,7 +20,6 @@ import org.briarproject.bramble.api.transport.KeyManager; import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.DbExpectations; import org.jmock.Expectations; -import org.jmock.Mockery; import org.junit.Test; import java.util.Collection; @@ -25,16 +27,20 @@ import java.util.Random; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; +import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.BASE32_LINK_BYTES; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNKNOWN; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNVERIFIED; import static org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED; +import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey; +import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey; import static org.briarproject.bramble.test.TestUtils.getAuthor; import static org.briarproject.bramble.test.TestUtils.getContact; import static org.briarproject.bramble.test.TestUtils.getLocalAuthor; import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getSecretKey; +import static org.briarproject.bramble.util.StringUtils.getRandomBase32String; import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -42,7 +48,6 @@ import static org.junit.Assert.assertTrue; public class ContactManagerImplTest extends BrambleMockTestCase { - private final Mockery context = new Mockery(); private final DatabaseComponent db = context.mock(DatabaseComponent.class); private final KeyManager keyManager = context.mock(KeyManager.class); private final IdentityManager identityManager = @@ -196,7 +201,6 @@ public class ContactManagerImplTest extends BrambleMockTestCase { Transaction txn = new Transaction(null, true); context.checking(new DbExpectations() {{ - oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); oneOf(identityManager).getLocalAuthor(txn); will(returnValue(localAuthor)); oneOf(db).getContactsByAuthorId(txn, remote.getId()); @@ -258,4 +262,22 @@ public class ContactManagerImplTest extends BrambleMockTestCase { }}); } + @Test + public void testGetHandshakeLink() throws Exception { + Transaction txn = new Transaction(null, true); + PublicKey publicKey = getAgreementPublicKey(); + PrivateKey privateKey = getAgreementPrivateKey(); + KeyPair keyPair = new KeyPair(publicKey, privateKey); + String link = "briar://" + getRandomBase32String(BASE32_LINK_BYTES); + + context.checking(new DbExpectations() {{ + oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); + oneOf(identityManager).getHandshakeKeys(txn); + will(returnValue(keyPair)); + oneOf(pendingContactFactory).createHandshakeLink(publicKey); + will(returnValue(link)); + }}); + + assertEquals(link, contactManager.getHandshakeLink()); + } } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/contact/PendingContactFactoryImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/contact/PendingContactFactoryImplTest.java index 39ee19324..879bd5db1 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/contact/PendingContactFactoryImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/contact/PendingContactFactoryImplTest.java @@ -19,8 +19,12 @@ import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.BASE32 import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.FORMAT_VERSION; import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.ID_LABEL; import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.RAW_LINK_BYTES; +import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_AGREEMENT; +import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_SIGNATURE; +import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey; +import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.junit.Assert.assertArrayEquals; @@ -110,6 +114,57 @@ public class PendingContactFactoryImplTest extends BrambleMockTestCase { assertEquals(timestamp, p.getTimestamp()); } + @Test(expected = IllegalArgumentException.class) + public void testCreateHandshakeLinkRejectsInvalidKeyType() { + PublicKey invalidPublicKey = context.mock(PublicKey.class); + + context.checking(new Expectations() {{ + oneOf(invalidPublicKey).getKeyType(); + will(returnValue(KEY_TYPE_SIGNATURE)); + }}); + + pendingContactFactory.createHandshakeLink(invalidPublicKey); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateHandshakeLinkRejectsInvalidKeyLength() { + PublicKey invalidPublicKey = context.mock(PublicKey.class); + byte[] invalidPublicKeyBytes = + getRandomBytes(MAX_AGREEMENT_PUBLIC_KEY_BYTES + 1); + + context.checking(new Expectations() {{ + oneOf(invalidPublicKey).getKeyType(); + will(returnValue(KEY_TYPE_AGREEMENT)); + oneOf(invalidPublicKey).getEncoded(); + will(returnValue(invalidPublicKeyBytes)); + }}); + + pendingContactFactory.createHandshakeLink(invalidPublicKey); + } + + @Test + public void testCreateAndParseLink() throws Exception { + context.checking(new Expectations() {{ + oneOf(crypto).getAgreementKeyParser(); + will(returnValue(keyParser)); + oneOf(keyParser).parsePublicKey(publicKey.getEncoded()); + will(returnValue(publicKey)); + oneOf(crypto).hash(ID_LABEL, publicKey.getEncoded()); + will(returnValue(idBytes)); + oneOf(clock).currentTimeMillis(); + will(returnValue(timestamp)); + }}); + + String link = pendingContactFactory.createHandshakeLink(publicKey); + PendingContact p = + pendingContactFactory.createPendingContact(link, alias); + assertArrayEquals(idBytes, p.getId().getBytes()); + assertArrayEquals(publicKey.getEncoded(), + p.getPublicKey().getEncoded()); + assertEquals(alias, p.getAlias()); + assertEquals(timestamp, p.getTimestamp()); + } + private String encodeLink() { return encodeLink(FORMAT_VERSION); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/remote/AddContactViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/remote/AddContactViewModel.java index 00a9da2a9..d51f1547b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/contact/add/remote/AddContactViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/contact/add/remote/AddContactViewModel.java @@ -62,7 +62,7 @@ public class AddContactViewModel extends AndroidViewModel { handshakeLink.postValue(contactManager.getHandshakeLink()); } catch (DbException e) { logException(LOG, WARNING, e); - // the UI should stay disable in this case, + // the UI should stay disabled in this case, // leaving the user unable to proceed } }); From 4640651714460b94dfc7646c23e8db64e88ae776 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Mon, 3 Jun 2019 11:21:01 +0100 Subject: [PATCH 05/12] Add integration test for converting pending contacts. --- .../ContactExchangeIntegrationTest.java | 137 +++++++++++++++--- 1 file changed, 118 insertions(+), 19 deletions(-) diff --git a/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactExchangeIntegrationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactExchangeIntegrationTest.java index cb6adf27f..e2fbc7348 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactExchangeIntegrationTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactExchangeIntegrationTest.java @@ -1,9 +1,12 @@ package org.briarproject.bramble.contact; +import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.contact.PendingContact; +import org.briarproject.bramble.api.contact.PendingContactState; +import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.SecretKey; -import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Identity; import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager; @@ -16,14 +19,19 @@ import org.junit.Test; import java.io.File; import java.util.Collection; +import java.util.Random; import java.util.concurrent.CountDownLatch; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static junit.framework.TestCase.assertNotNull; +import static junit.framework.TestCase.assertNull; import static junit.framework.TestCase.fail; +import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION; import static org.briarproject.bramble.test.TestDuplexTransportConnection.createPair; import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory; import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.briarproject.bramble.test.TestUtils.getTestDirectory; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -35,9 +43,10 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase { private final File aliceDir = new File(testDir, "alice"); private final File bobDir = new File(testDir, "bob"); private final SecretKey masterKey = getSecretKey(); + private final Random random = new Random(); private ContactExchangeIntegrationTestComponent alice, bob; - private Author aliceAuthor, bobAuthor; + private Identity aliceIdentity, bobIdentity; @Before public void setUp() throws Exception { @@ -52,11 +61,11 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase { .build(); bob.injectBrambleCoreEagerSingletons(); // Set up the devices and get the identities - aliceAuthor = setUp(alice, "Alice"); - bobAuthor = setUp(bob, "Bob"); + aliceIdentity = setUp(alice, "Alice"); + bobIdentity = setUp(bob, "Bob"); } - private Author setUp(ContactExchangeIntegrationTestComponent device, + private Identity setUp(ContactExchangeIntegrationTestComponent device, String name) throws Exception { // Add an identity for the user IdentityManager identityManager = device.getIdentityManager(); @@ -70,7 +79,7 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase { ContactManager contactManager = device.getContactManager(); assertEquals(0, contactManager.getPendingContacts().size()); assertEquals(0, contactManager.getContacts().size()); - return identity.getLocalAuthor(); + return identity; } @Test @@ -79,10 +88,12 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase { TestDuplexTransportConnection aliceConnection = pair[0]; TestDuplexTransportConnection bobConnection = pair[1]; CountDownLatch aliceFinished = new CountDownLatch(1); + boolean verified = random.nextBoolean(); + alice.getIoExecutor().execute(() -> { try { alice.getContactExchangeManager().exchangeContacts( - aliceConnection, masterKey, true, true); + aliceConnection, masterKey, true, verified); aliceFinished.countDown(); } catch (Exception e) { fail(); @@ -92,7 +103,7 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase { bob.getIoExecutor().execute(() -> { try { bob.getContactExchangeManager().exchangeContacts(bobConnection, - masterKey, false, true); + masterKey, false, verified); bobFinished.countDown(); } catch (Exception e) { fail(); @@ -100,19 +111,107 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase { }); aliceFinished.await(TIMEOUT, MILLISECONDS); bobFinished.await(TIMEOUT, MILLISECONDS); - assertContactsExchanged(); + assertContacts(verified, false); + assertNoPendingContacts(); } - private void assertContactsExchanged() throws Exception { - Collection aliceContacts = - alice.getContactManager().getContacts(); - assertEquals(1, aliceContacts.size()); - Contact bobFromAlice = aliceContacts.iterator().next(); - assertEquals(bobAuthor, bobFromAlice.getAuthor()); - Collection bobContacts = bob.getContactManager().getContacts(); - assertEquals(1, bobContacts.size()); - Contact aliceFromBob = bobContacts.iterator().next(); - assertEquals(aliceAuthor, aliceFromBob.getAuthor()); + @Test + public void testExchangeContactsFromPendingContacts() throws Exception { + ContactManager aliceContactManager = alice.getContactManager(); + ContactManager bobContactManager = bob.getContactManager(); + String aliceLink = aliceContactManager.getHandshakeLink(); + String bobLink = bobContactManager.getHandshakeLink(); + PendingContact bobFromAlice = + aliceContactManager.addPendingContact(bobLink, "Bob"); + PendingContact aliceFromBob = + bobContactManager.addPendingContact(aliceLink, "Alice"); + assertPendingContacts(); + + TestDuplexTransportConnection[] pair = createPair(); + TestDuplexTransportConnection aliceConnection = pair[0]; + TestDuplexTransportConnection bobConnection = pair[1]; + CountDownLatch aliceFinished = new CountDownLatch(1); + boolean verified = random.nextBoolean(); + + alice.getIoExecutor().execute(() -> { + try { + alice.getContactExchangeManager().exchangeContacts( + bobFromAlice.getId(), aliceConnection, masterKey, true, + verified); + aliceFinished.countDown(); + } catch (Exception e) { + fail(); + } + }); + CountDownLatch bobFinished = new CountDownLatch(1); + bob.getIoExecutor().execute(() -> { + try { + bob.getContactExchangeManager().exchangeContacts( + aliceFromBob.getId(), bobConnection, masterKey, false, + verified); + bobFinished.countDown(); + } catch (Exception e) { + fail(); + } + }); + aliceFinished.await(TIMEOUT, MILLISECONDS); + bobFinished.await(TIMEOUT, MILLISECONDS); + assertContacts(verified, true); + assertNoPendingContacts(); + } + + private void assertContacts(boolean verified, + boolean withHandshakeKeys) throws Exception { + assertContact(alice, bobIdentity, verified, withHandshakeKeys); + assertContact(bob, aliceIdentity, verified, withHandshakeKeys); + } + + private void assertContact(ContactExchangeIntegrationTestComponent device, + Identity expectedIdentity, boolean verified, + boolean withHandshakeKeys) throws Exception { + Collection contacts = device.getContactManager().getContacts(); + assertEquals(1, contacts.size()); + Contact contact = contacts.iterator().next(); + assertEquals(expectedIdentity.getLocalAuthor(), contact.getAuthor()); + assertEquals(verified, contact.isVerified()); + PublicKey expectedPublicKey = expectedIdentity.getHandshakePublicKey(); + PublicKey actualPublicKey = contact.getHandshakePublicKey(); + assertNotNull(expectedPublicKey); + if (withHandshakeKeys) { + assertNotNull(actualPublicKey); + assertArrayEquals(expectedPublicKey.getEncoded(), + actualPublicKey.getEncoded()); + } else { + assertNull(actualPublicKey); + } + } + + private void assertNoPendingContacts() throws Exception { + assertEquals(0, alice.getContactManager().getPendingContacts().size()); + assertEquals(0, bob.getContactManager().getPendingContacts().size()); + } + + private void assertPendingContacts() throws Exception { + assertPendingContact(alice, bobIdentity); + assertPendingContact(bob, aliceIdentity); + } + + private void assertPendingContact( + ContactExchangeIntegrationTestComponent device, + Identity expectedIdentity) throws Exception { + Collection> pairs = + device.getContactManager().getPendingContacts(); + assertEquals(1, pairs.size()); + Pair pair = + pairs.iterator().next(); + assertEquals(WAITING_FOR_CONNECTION, pair.getSecond()); + PendingContact pendingContact = pair.getFirst(); + assertEquals(expectedIdentity.getLocalAuthor().getName(), + pendingContact.getAlias()); + PublicKey expectedPublicKey = expectedIdentity.getHandshakePublicKey(); + assertNotNull(expectedPublicKey); + assertArrayEquals(expectedPublicKey.getEncoded(), + pendingContact.getPublicKey().getEncoded()); } private void tearDown(ContactExchangeIntegrationTestComponent device) From 89cbdc824cb63531bc6e0047b00dac66c3411766 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Mon, 3 Jun 2019 11:49:34 +0100 Subject: [PATCH 06/12] Add integration test for handshaking with pending contact. --- .../ContactExchangeIntegrationTest.java | 84 +++++++++++++++---- ...ntactExchangeIntegrationTestComponent.java | 3 + .../bramble}/test/TestStreamWriter.java | 7 +- 3 files changed, 75 insertions(+), 19 deletions(-) rename {briar-core/src/test/java/org/briarproject/briar => bramble-core/src/test/java/org/briarproject/bramble}/test/TestStreamWriter.java (68%) diff --git a/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactExchangeIntegrationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactExchangeIntegrationTest.java index e2fbc7348..a674c52a4 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactExchangeIntegrationTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactExchangeIntegrationTest.java @@ -13,11 +13,15 @@ import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.TestDatabaseConfigModule; import org.briarproject.bramble.test.TestDuplexTransportConnection; +import org.briarproject.bramble.test.TestStreamWriter; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.File; +import java.io.OutputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; import java.util.Collection; import java.util.Random; import java.util.concurrent.CountDownLatch; @@ -88,6 +92,7 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase { TestDuplexTransportConnection aliceConnection = pair[0]; TestDuplexTransportConnection bobConnection = pair[1]; CountDownLatch aliceFinished = new CountDownLatch(1); + CountDownLatch bobFinished = new CountDownLatch(1); boolean verified = random.nextBoolean(); alice.getIoExecutor().execute(() -> { @@ -99,7 +104,6 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase { fail(); } }); - CountDownLatch bobFinished = new CountDownLatch(1); bob.getIoExecutor().execute(() -> { try { bob.getContactExchangeManager().exchangeContacts(bobConnection, @@ -117,20 +121,15 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase { @Test public void testExchangeContactsFromPendingContacts() throws Exception { - ContactManager aliceContactManager = alice.getContactManager(); - ContactManager bobContactManager = bob.getContactManager(); - String aliceLink = aliceContactManager.getHandshakeLink(); - String bobLink = bobContactManager.getHandshakeLink(); - PendingContact bobFromAlice = - aliceContactManager.addPendingContact(bobLink, "Bob"); - PendingContact aliceFromBob = - bobContactManager.addPendingContact(aliceLink, "Alice"); + PendingContact bobFromAlice = addPendingContact(alice, bob); + PendingContact aliceFromBob = addPendingContact(bob, alice); assertPendingContacts(); TestDuplexTransportConnection[] pair = createPair(); TestDuplexTransportConnection aliceConnection = pair[0]; TestDuplexTransportConnection bobConnection = pair[1]; CountDownLatch aliceFinished = new CountDownLatch(1); + CountDownLatch bobFinished = new CountDownLatch(1); boolean verified = random.nextBoolean(); alice.getIoExecutor().execute(() -> { @@ -143,7 +142,6 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase { fail(); } }); - CountDownLatch bobFinished = new CountDownLatch(1); bob.getIoExecutor().execute(() -> { try { bob.getContactExchangeManager().exchangeContacts( @@ -160,16 +158,70 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase { assertNoPendingContacts(); } + @Test + public void testHandshakeAndExchangeContactsFromPendingContacts() + throws Exception { + PendingContact bobFromAlice = addPendingContact(alice, bob); + PendingContact aliceFromBob = addPendingContact(bob, alice); + assertPendingContacts(); + + PipedInputStream aliceHandshakeIn = new PipedInputStream(); + PipedInputStream bobHandshakeIn = new PipedInputStream(); + OutputStream aliceHandshakeOut = new PipedOutputStream(bobHandshakeIn); + OutputStream bobHandshakeOut = new PipedOutputStream(aliceHandshakeIn); + + TestDuplexTransportConnection[] pair = createPair(); + TestDuplexTransportConnection aliceConnection = pair[0]; + TestDuplexTransportConnection bobConnection = pair[1]; + CountDownLatch aliceFinished = new CountDownLatch(1); + CountDownLatch bobFinished = new CountDownLatch(1); + + alice.getIoExecutor().execute(() -> { + try { + alice.getHandshakeManager().handshakeAndAddContact( + bobFromAlice.getId(), aliceHandshakeIn, + new TestStreamWriter(aliceHandshakeOut), + aliceConnection); + aliceFinished.countDown(); + } catch (Exception e) { + fail(); + } + }); + bob.getIoExecutor().execute(() -> { + try { + bob.getHandshakeManager().handshakeAndAddContact( + aliceFromBob.getId(), bobHandshakeIn, + new TestStreamWriter(bobHandshakeOut), + bobConnection); + bobFinished.countDown(); + } catch (Exception e) { + fail(); + } + }); + aliceFinished.await(TIMEOUT, MILLISECONDS); + bobFinished.await(TIMEOUT, MILLISECONDS); + assertContacts(false, true); + assertNoPendingContacts(); + } + + private PendingContact addPendingContact( + ContactExchangeIntegrationTestComponent local, + ContactExchangeIntegrationTestComponent remote) throws Exception { + String link = remote.getContactManager().getHandshakeLink(); + String alias = remote.getIdentityManager().getLocalAuthor().getName(); + return local.getContactManager().addPendingContact(link, alias); + } + private void assertContacts(boolean verified, boolean withHandshakeKeys) throws Exception { assertContact(alice, bobIdentity, verified, withHandshakeKeys); assertContact(bob, aliceIdentity, verified, withHandshakeKeys); } - private void assertContact(ContactExchangeIntegrationTestComponent device, + private void assertContact(ContactExchangeIntegrationTestComponent local, Identity expectedIdentity, boolean verified, - boolean withHandshakeKeys) throws Exception { - Collection contacts = device.getContactManager().getContacts(); + boolean withHandshakeKey) throws Exception { + Collection contacts = local.getContactManager().getContacts(); assertEquals(1, contacts.size()); Contact contact = contacts.iterator().next(); assertEquals(expectedIdentity.getLocalAuthor(), contact.getAuthor()); @@ -177,7 +229,7 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase { PublicKey expectedPublicKey = expectedIdentity.getHandshakePublicKey(); PublicKey actualPublicKey = contact.getHandshakePublicKey(); assertNotNull(expectedPublicKey); - if (withHandshakeKeys) { + if (withHandshakeKey) { assertNotNull(actualPublicKey); assertArrayEquals(expectedPublicKey.getEncoded(), actualPublicKey.getEncoded()); @@ -197,10 +249,10 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase { } private void assertPendingContact( - ContactExchangeIntegrationTestComponent device, + ContactExchangeIntegrationTestComponent local, Identity expectedIdentity) throws Exception { Collection> pairs = - device.getContactManager().getPendingContacts(); + local.getContactManager().getPendingContacts(); assertEquals(1, pairs.size()); Pair pair = pairs.iterator().next(); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactExchangeIntegrationTestComponent.java b/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactExchangeIntegrationTestComponent.java index c0a1e9a4a..d3d7dae2d 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactExchangeIntegrationTestComponent.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactExchangeIntegrationTestComponent.java @@ -4,6 +4,7 @@ import org.briarproject.bramble.BrambleCoreEagerSingletons; import org.briarproject.bramble.BrambleCoreModule; import org.briarproject.bramble.api.contact.ContactExchangeManager; import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.contact.HandshakeManager; import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.LifecycleManager; @@ -27,6 +28,8 @@ interface ContactExchangeIntegrationTestComponent ContactManager getContactManager(); + HandshakeManager getHandshakeManager(); + IdentityManager getIdentityManager(); @IoExecutor diff --git a/briar-core/src/test/java/org/briarproject/briar/test/TestStreamWriter.java b/bramble-core/src/test/java/org/briarproject/bramble/test/TestStreamWriter.java similarity index 68% rename from briar-core/src/test/java/org/briarproject/briar/test/TestStreamWriter.java rename to bramble-core/src/test/java/org/briarproject/bramble/test/TestStreamWriter.java index ba95dbb9c..12d325ff5 100644 --- a/briar-core/src/test/java/org/briarproject/briar/test/TestStreamWriter.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/test/TestStreamWriter.java @@ -1,15 +1,15 @@ -package org.briarproject.briar.test; +package org.briarproject.bramble.test; import org.briarproject.bramble.api.transport.StreamWriter; import java.io.IOException; import java.io.OutputStream; -class TestStreamWriter implements StreamWriter { +public class TestStreamWriter implements StreamWriter { private final OutputStream out; - TestStreamWriter(OutputStream out) { + public TestStreamWriter(OutputStream out) { this.out = out; } @@ -21,5 +21,6 @@ class TestStreamWriter implements StreamWriter { @Override public void sendEndOfStream() throws IOException { out.flush(); + out.close(); } } From eed8d25120946f78c53cfb20665656c1b08d3f96 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Mon, 3 Jun 2019 17:44:38 +0100 Subject: [PATCH 07/12] Decouple HandshakeManager from ContactExchangeManager. --- .../bramble/api/contact/HandshakeManager.java | 32 +++++++++++++++---- .../bramble/contact/HandshakeManagerImpl.java | 16 +++------- .../ContactExchangeIntegrationTest.java | 29 +++++++++++++---- 3 files changed, 51 insertions(+), 26 deletions(-) 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 6a9eb6eda..d77287de1 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 @@ -1,8 +1,8 @@ package org.briarproject.bramble.api.contact; +import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.transport.StreamWriter; import java.io.IOException; @@ -12,16 +12,34 @@ import java.io.InputStream; public interface HandshakeManager { /** - * Handshakes and exchanges pseudonyms with the given pending contact, - * converts the pending contact to a contact and returns the contact. + * Handshakes with the given pending 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 - * @param conn The connection to use for the handshake and contact exchange */ - Contact handshakeAndAddContact(PendingContactId p, - InputStream in, StreamWriter out, DuplexTransportConnection conn) - throws DbException, IOException; + HandshakeResult handshake(PendingContactId p, + InputStream in, StreamWriter out) throws DbException, IOException; + + class HandshakeResult { + + private final SecretKey masterKey; + private final boolean alice; + + public HandshakeResult(SecretKey masterKey, boolean alice) { + this.masterKey = masterKey; + this.alice = alice; + } + + public SecretKey getMasterKey() { + return masterKey; + } + + public boolean isAlice() { + return alice; + } + } } 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 bb4cb5dbb..3e6abcc4f 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,8 +3,6 @@ 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.ContactExchangeManager; import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.HandshakeManager; import org.briarproject.bramble.api.contact.PendingContact; @@ -18,7 +16,6 @@ import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.TransactionManager; import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.record.Record; import org.briarproject.bramble.api.record.RecordReader; import org.briarproject.bramble.api.record.RecordReaderFactory; @@ -58,7 +55,6 @@ class HandshakeManagerImpl implements HandshakeManager { private final HandshakeCrypto handshakeCrypto; private final RecordReaderFactory recordReaderFactory; private final RecordWriterFactory recordWriterFactory; - private final ContactExchangeManager contactExchangeManager; @Inject HandshakeManagerImpl(DatabaseComponent db, @@ -66,21 +62,18 @@ class HandshakeManagerImpl implements HandshakeManager { ContactManager contactManager, HandshakeCrypto handshakeCrypto, RecordReaderFactory recordReaderFactory, - RecordWriterFactory recordWriterFactory, - ContactExchangeManager contactExchangeManager) { + RecordWriterFactory recordWriterFactory) { this.db = db; this.identityManager = identityManager; this.contactManager = contactManager; this.handshakeCrypto = handshakeCrypto; this.recordReaderFactory = recordReaderFactory; this.recordWriterFactory = recordWriterFactory; - this.contactExchangeManager = contactExchangeManager; } @Override - public Contact handshakeAndAddContact(PendingContactId p, - InputStream in, StreamWriter out, DuplexTransportConnection conn) - throws DbException, IOException { + public HandshakeResult handshake(PendingContactId p, InputStream in, + StreamWriter out) throws DbException, IOException { Pair keys = db.transactionWithResult(true, txn -> { PendingContact pendingContact = contactManager.getPendingContact(txn, p); @@ -125,8 +118,7 @@ class HandshakeManagerImpl implements HandshakeManager { recordReader.readRecord(r -> false, IGNORE); if (!handshakeCrypto.verifyOwnership(masterKey, !alice, theirProof)) throw new FormatException(); - return contactExchangeManager.exchangeContacts(p, conn, masterKey, - alice, false); + return new HandshakeResult(masterKey, alice); } private void sendPublicKey(RecordWriter w, PublicKey k) throws IOException { diff --git a/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactExchangeIntegrationTest.java b/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactExchangeIntegrationTest.java index a674c52a4..8b461c669 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactExchangeIntegrationTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactExchangeIntegrationTest.java @@ -3,6 +3,7 @@ package org.briarproject.bramble.contact; import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.contact.HandshakeManager.HandshakeResult; import org.briarproject.bramble.api.contact.PendingContact; import org.briarproject.bramble.api.contact.PendingContactState; import org.briarproject.bramble.api.crypto.PublicKey; @@ -25,6 +26,7 @@ import java.io.PipedOutputStream; import java.util.Collection; import java.util.Random; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static junit.framework.TestCase.assertNotNull; @@ -37,6 +39,7 @@ import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.briarproject.bramble.test.TestUtils.getTestDirectory; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; public class ContactExchangeIntegrationTest extends BrambleTestCase { @@ -169,19 +172,25 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase { PipedInputStream bobHandshakeIn = new PipedInputStream(); OutputStream aliceHandshakeOut = new PipedOutputStream(bobHandshakeIn); OutputStream bobHandshakeOut = new PipedOutputStream(aliceHandshakeIn); + AtomicReference aliceResult = new AtomicReference<>(); + AtomicReference bobResult = new AtomicReference<>(); TestDuplexTransportConnection[] pair = createPair(); TestDuplexTransportConnection aliceConnection = pair[0]; TestDuplexTransportConnection bobConnection = pair[1]; CountDownLatch aliceFinished = new CountDownLatch(1); CountDownLatch bobFinished = new CountDownLatch(1); + boolean verified = random.nextBoolean(); alice.getIoExecutor().execute(() -> { try { - alice.getHandshakeManager().handshakeAndAddContact( + HandshakeResult result = alice.getHandshakeManager().handshake( bobFromAlice.getId(), aliceHandshakeIn, - new TestStreamWriter(aliceHandshakeOut), - aliceConnection); + new TestStreamWriter(aliceHandshakeOut)); + aliceResult.set(result); + alice.getContactExchangeManager().exchangeContacts( + bobFromAlice.getId(), aliceConnection, + result.getMasterKey(), result.isAlice(), verified); aliceFinished.countDown(); } catch (Exception e) { fail(); @@ -189,10 +198,13 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase { }); bob.getIoExecutor().execute(() -> { try { - bob.getHandshakeManager().handshakeAndAddContact( + HandshakeResult result = bob.getHandshakeManager().handshake( aliceFromBob.getId(), bobHandshakeIn, - new TestStreamWriter(bobHandshakeOut), - bobConnection); + new TestStreamWriter(bobHandshakeOut)); + bobResult.set(result); + bob.getContactExchangeManager().exchangeContacts( + aliceFromBob.getId(), bobConnection, + result.getMasterKey(), result.isAlice(), verified); bobFinished.countDown(); } catch (Exception e) { fail(); @@ -200,7 +212,10 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase { }); aliceFinished.await(TIMEOUT, MILLISECONDS); bobFinished.await(TIMEOUT, MILLISECONDS); - assertContacts(false, true); + assertArrayEquals(aliceResult.get().getMasterKey().getBytes(), + bobResult.get().getMasterKey().getBytes()); + assertNotEquals(aliceResult.get().isAlice(), bobResult.get().isAlice()); + assertContacts(verified, true); assertNoPendingContacts(); } From 4d8e0baeb4945961b1cd6f847d9af808e40e071c Mon Sep 17 00:00:00 2001 From: akwizgran Date: Mon, 3 Jun 2019 17:58:31 +0100 Subject: [PATCH 08/12] Rewrap a line. --- .../briarproject/bramble/api/contact/HandshakeManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 d77287de1..1f586ada5 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 @@ -21,8 +21,8 @@ public interface HandshakeManager { * @param out An outgoing stream for the handshake, which must be secured * in handshake mode */ - HandshakeResult handshake(PendingContactId p, - InputStream in, StreamWriter out) throws DbException, IOException; + HandshakeResult handshake(PendingContactId p, InputStream in, + StreamWriter out) throws DbException, IOException; class HandshakeResult { From d3c7ecdef4207022bf0380fe026b31d6a7d9b2c7 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Mon, 3 Jun 2019 18:02:19 +0100 Subject: [PATCH 09/12] Use static comparison method. --- .../org/briarproject/bramble/contact/HandshakeCryptoImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeCryptoImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeCryptoImpl.java index d01c6f1a2..5f77187e4 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeCryptoImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeCryptoImpl.java @@ -1,6 +1,5 @@ package org.briarproject.bramble.contact; -import org.briarproject.bramble.api.Bytes; import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.PublicKey; @@ -12,6 +11,7 @@ import java.security.GeneralSecurityException; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; +import static org.briarproject.bramble.api.Bytes.compare; import static org.briarproject.bramble.contact.HandshakeConstants.ALICE_PROOF_LABEL; import static org.briarproject.bramble.contact.HandshakeConstants.BOB_PROOF_LABEL; import static org.briarproject.bramble.contact.HandshakeConstants.MASTER_KEY_LABEL; @@ -32,7 +32,7 @@ class HandshakeCryptoImpl implements HandshakeCrypto { KeyPair ourStaticKeyPair) { byte[] ours = ourStaticKeyPair.getPublic().getEncoded(); byte[] theirs = theirStaticPublicKey.getEncoded(); - return new Bytes(ours).compareTo(new Bytes(theirs)) < 0; + return compare(ours, theirs) < 0; } @Override From d939fe80bd04d5b0e4dcc3eb14eacec16f7ae682 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Tue, 4 Jun 2019 12:17:10 +0100 Subject: [PATCH 10/12] Explicitly check length of proof of ownership. --- .../bramble/contact/HandshakeConstants.java | 4 ++++ .../bramble/contact/HandshakeManagerImpl.java | 14 ++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeConstants.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeConstants.java index 1fba77722..99076adca 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeConstants.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeConstants.java @@ -22,4 +22,8 @@ interface HandshakeConstants { */ String BOB_PROOF_LABEL = "org.briarproject.bramble.handshake/BOB_PROOF"; + /** + * The length of the proof of ownership in bytes. + */ + int PROOF_BYTES = 32; } 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 3e6abcc4f..73be7bf2d 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 @@ -32,9 +32,11 @@ import javax.annotation.concurrent.Immutable; import javax.inject.Inject; import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES; +import static org.briarproject.bramble.contact.HandshakeConstants.PROOF_BYTES; import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_VERSION; import static org.briarproject.bramble.contact.HandshakeRecordTypes.EPHEMERAL_PUBLIC_KEY; import static org.briarproject.bramble.contact.HandshakeRecordTypes.PROOF_OF_OWNERSHIP; +import static org.briarproject.bramble.util.ValidationUtils.checkLength; @Immutable @NotNullByDefault @@ -128,11 +130,9 @@ class HandshakeManagerImpl implements HandshakeManager { } private PublicKey receivePublicKey(RecordReader r) throws IOException { - Record rec = readRecord(r, EPHEMERAL_PUBLIC_KEY); - int length = rec.getPayload().length; - if (length == 0 || length > MAX_AGREEMENT_PUBLIC_KEY_BYTES) - throw new FormatException(); - return new AgreementPublicKey(rec.getPayload()); + byte[] key = readRecord(r, EPHEMERAL_PUBLIC_KEY).getPayload(); + checkLength(key, 1, MAX_AGREEMENT_PUBLIC_KEY_BYTES); + return new AgreementPublicKey(key); } private void sendProof(RecordWriter w, byte[] proof) throws IOException { @@ -141,7 +141,9 @@ class HandshakeManagerImpl implements HandshakeManager { } private byte[] receiveProof(RecordReader r) throws IOException { - return readRecord(r, PROOF_OF_OWNERSHIP).getPayload(); + byte[] proof = readRecord(r, PROOF_OF_OWNERSHIP).getPayload(); + checkLength(proof, PROOF_BYTES, PROOF_BYTES); + return proof; } private Record readRecord(RecordReader r, byte expectedType) From 34a5b69100a9a363b680a3b6f67370a1395368c9 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Tue, 4 Jun 2019 12:21:17 +0100 Subject: [PATCH 11/12] Reuse TransportCrypto#isAlice(). --- .../briarproject/bramble/contact/HandshakeCrypto.java | 3 --- .../bramble/contact/HandshakeCryptoImpl.java | 9 --------- .../bramble/contact/HandshakeManagerImpl.java | 6 +++++- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeCrypto.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeCrypto.java index 88d5a6fd5..bab35e6d1 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeCrypto.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeCrypto.java @@ -10,9 +10,6 @@ import java.security.GeneralSecurityException; @NotNullByDefault interface HandshakeCrypto { - boolean isLocalPeerAlice(PublicKey theirStaticPublicKey, - KeyPair ourStaticKeyPair); - KeyPair generateEphemeralKeyPair(); /** diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeCryptoImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeCryptoImpl.java index 5f77187e4..199bf16af 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeCryptoImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeCryptoImpl.java @@ -11,7 +11,6 @@ import java.security.GeneralSecurityException; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; -import static org.briarproject.bramble.api.Bytes.compare; import static org.briarproject.bramble.contact.HandshakeConstants.ALICE_PROOF_LABEL; import static org.briarproject.bramble.contact.HandshakeConstants.BOB_PROOF_LABEL; import static org.briarproject.bramble.contact.HandshakeConstants.MASTER_KEY_LABEL; @@ -27,14 +26,6 @@ class HandshakeCryptoImpl implements HandshakeCrypto { this.crypto = crypto; } - @Override - public boolean isLocalPeerAlice(PublicKey theirStaticPublicKey, - KeyPair ourStaticKeyPair) { - byte[] ours = ourStaticKeyPair.getPublic().getEncoded(); - byte[] theirs = theirStaticPublicKey.getEncoded(); - return compare(ours, theirs) < 0; - } - @Override public KeyPair generateEphemeralKeyPair() { return crypto.generateAgreementKeyPair(); 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 73be7bf2d..7edeaf786 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 @@ -11,6 +11,7 @@ import org.briarproject.bramble.api.crypto.AgreementPublicKey; import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.crypto.TransportCrypto; import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.TransactionManager; @@ -54,6 +55,7 @@ class HandshakeManagerImpl implements HandshakeManager { private final TransactionManager db; private final IdentityManager identityManager; private final ContactManager contactManager; + private final TransportCrypto transportCrypto; private final HandshakeCrypto handshakeCrypto; private final RecordReaderFactory recordReaderFactory; private final RecordWriterFactory recordWriterFactory; @@ -62,12 +64,14 @@ class HandshakeManagerImpl implements HandshakeManager { HandshakeManagerImpl(DatabaseComponent db, IdentityManager identityManager, ContactManager contactManager, + TransportCrypto transportCrypto, HandshakeCrypto handshakeCrypto, RecordReaderFactory recordReaderFactory, RecordWriterFactory recordWriterFactory) { this.db = db; this.identityManager = identityManager; this.contactManager = contactManager; + this.transportCrypto = transportCrypto; this.handshakeCrypto = handshakeCrypto; this.recordReaderFactory = recordReaderFactory; this.recordWriterFactory = recordWriterFactory; @@ -84,7 +88,7 @@ class HandshakeManagerImpl implements HandshakeManager { }); PublicKey theirStaticPublicKey = keys.getFirst(); KeyPair ourStaticKeyPair = keys.getSecond(); - boolean alice = handshakeCrypto.isLocalPeerAlice(theirStaticPublicKey, + boolean alice = transportCrypto.isAlice(theirStaticPublicKey, ourStaticKeyPair); RecordReader recordReader = recordReaderFactory.createRecordReader(in); RecordWriter recordWriter = recordWriterFactory From 0951508af779128d8b283f7afcba7946ec0251c4 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Tue, 4 Jun 2019 12:38:21 +0100 Subject: [PATCH 12/12] Define PROOF_BYTES as MAC_BYTES. --- .../org/briarproject/bramble/contact/HandshakeConstants.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeConstants.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeConstants.java index 99076adca..37e7a41d3 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeConstants.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/HandshakeConstants.java @@ -1,5 +1,7 @@ package org.briarproject.bramble.contact; +import static org.briarproject.bramble.api.crypto.CryptoConstants.MAC_BYTES; + interface HandshakeConstants { /** @@ -25,5 +27,5 @@ interface HandshakeConstants { /** * The length of the proof of ownership in bytes. */ - int PROOF_BYTES = 32; + int PROOF_BYTES = MAC_BYTES; }