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 new file mode 100644 index 000000000..0981158f0 --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactExchangeManager.java @@ -0,0 +1,24 @@ +package org.briarproject.bramble.api.contact; + +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; + +@NotNullByDefault +public interface ContactExchangeManager { + + /** + * Exchanges contact information with a remote peer. + * + * @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; +} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java index 99988094d..2b18a17a5 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java @@ -93,6 +93,11 @@ public interface ContactManager { */ Contact getContact(ContactId c) throws DbException; + /** + * Returns the contact with the given ID. + */ + Contact getContact(Transaction txn, ContactId c) throws DbException; + /** * Returns the contact with the given remoteAuthorId * that was added by the LocalAuthor with the given localAuthorId diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/event/ContactExchangeFailedEvent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/event/ContactExchangeFailedEvent.java deleted file mode 100644 index 0d300f72f..000000000 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/event/ContactExchangeFailedEvent.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.briarproject.bramble.api.contact.event; - -import org.briarproject.bramble.api.event.Event; -import org.briarproject.bramble.api.identity.Author; -import org.briarproject.bramble.api.nullsafety.NotNullByDefault; - -import javax.annotation.Nullable; - -@NotNullByDefault -public class ContactExchangeFailedEvent extends Event { - - @Nullable - private final Author duplicateRemoteAuthor; - - public ContactExchangeFailedEvent(@Nullable Author duplicateRemoteAuthor) { - this.duplicateRemoteAuthor = duplicateRemoteAuthor; - } - - public ContactExchangeFailedEvent() { - this(null); - } - - @Nullable - public Author getDuplicateRemoteAuthor() { - return duplicateRemoteAuthor; - } - - public boolean wasDuplicateContact() { - return duplicateRemoteAuthor != null; - } - -} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/event/ContactExchangeSucceededEvent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/contact/event/ContactExchangeSucceededEvent.java deleted file mode 100644 index 941a212af..000000000 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/event/ContactExchangeSucceededEvent.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.briarproject.bramble.api.contact.event; - -import org.briarproject.bramble.api.event.Event; -import org.briarproject.bramble.api.identity.Author; -import org.briarproject.bramble.api.nullsafety.NotNullByDefault; - -@NotNullByDefault -public class ContactExchangeSucceededEvent extends Event { - - private final Author remoteAuthor; - - public ContactExchangeSucceededEvent(Author remoteAuthor) { - this.remoteAuthor = remoteAuthor; - } - - public Author getRemoteAuthor() { - return remoteAuthor; - } - -} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/db/ContactExistsException.java b/bramble-api/src/main/java/org/briarproject/bramble/api/db/ContactExistsException.java index 6b51ce5b6..05b5821ef 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/db/ContactExistsException.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/db/ContactExistsException.java @@ -1,8 +1,27 @@ package org.briarproject.bramble.api.db; +import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.api.identity.AuthorId; + /** * Thrown when a duplicate contact is added to the database. This exception may * occur due to concurrent updates and does not indicate a database error. */ public class ContactExistsException extends DbException { + + private final AuthorId local; + private final Author remote; + + public ContactExistsException(AuthorId local, Author remote) { + this.local = local; + this.remote = remote; + } + + public AuthorId getLocalAuthorId() { + return local; + } + + public Author getRemoteAuthor() { + return remote; + } } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactExchangeTask.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeConstants.java similarity index 50% rename from bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactExchangeTask.java rename to bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeConstants.java index c72b0fa7e..9f39d3470 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactExchangeTask.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeConstants.java @@ -1,16 +1,6 @@ -package org.briarproject.bramble.api.contact; +package org.briarproject.bramble.contact; -import org.briarproject.bramble.api.crypto.SecretKey; -import org.briarproject.bramble.api.identity.LocalAuthor; -import org.briarproject.bramble.api.nullsafety.NotNullByDefault; -import org.briarproject.bramble.api.plugin.TransportId; -import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; - -/** - * A task for conducting a contact information exchange with a remote peer. - */ -@NotNullByDefault -public interface ContactExchangeTask { +interface ContactExchangeConstants { /** * The current version of the contact exchange protocol. @@ -39,9 +29,7 @@ public interface ContactExchangeTask { String BOB_NONCE_LABEL = "org.briarproject.bramble.contact/BOB_NONCE"; /** - * Exchanges contact information with a remote peer. + * Label for signing key binding nonces. */ - void startExchange(LocalAuthor localAuthor, SecretKey masterKey, - DuplexTransportConnection conn, TransportId transportId, - boolean alice); + String SIGNING_LABEL = "org.briarproject.briar.contact/EXCHANGE"; } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeCrypto.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeCrypto.java new file mode 100644 index 000000000..5c21eb23a --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeCrypto.java @@ -0,0 +1,35 @@ +package org.briarproject.bramble.contact; + +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.nullsafety.NotNullByDefault; + +@NotNullByDefault +interface ContactExchangeCrypto { + + /** + * Derives the header key for a contact exchange stream from the master key. + * + * @param alice Whether the header key is for the stream sent by Alice + */ + SecretKey deriveHeaderKey(SecretKey masterKey, boolean alice); + + /** + * Creates and returns a signature that proves ownership of a pseudonym. + * + * @param privateKey The pseudonym's signature private key + * @param alice Whether the pseudonym belongs to Alice + */ + byte[] sign(PrivateKey privateKey, SecretKey masterKey, boolean alice); + + /** + * Verifies a signature that proves ownership of a pseudonym. + * + * @param publicKey The pseudonym's signature public key + * @param alice Whether the pseudonym belongs to Alice + * @return True if the signature is valid + */ + boolean verify(PublicKey publicKey, SecretKey masterKey, boolean alice, + byte[] signature); +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeCryptoImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeCryptoImpl.java new file mode 100644 index 000000000..6a3a3c44a --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeCryptoImpl.java @@ -0,0 +1,66 @@ +package org.briarproject.bramble.contact; + +import org.briarproject.bramble.api.crypto.CryptoComponent; +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.nullsafety.NotNullByDefault; + +import java.security.GeneralSecurityException; + +import javax.inject.Inject; + +import static org.briarproject.bramble.contact.ContactExchangeConstants.ALICE_KEY_LABEL; +import static org.briarproject.bramble.contact.ContactExchangeConstants.ALICE_NONCE_LABEL; +import static org.briarproject.bramble.contact.ContactExchangeConstants.BOB_KEY_LABEL; +import static org.briarproject.bramble.contact.ContactExchangeConstants.BOB_NONCE_LABEL; +import static org.briarproject.bramble.contact.ContactExchangeConstants.PROTOCOL_VERSION; +import static org.briarproject.bramble.contact.ContactExchangeConstants.SIGNING_LABEL; + +@NotNullByDefault +class ContactExchangeCryptoImpl implements ContactExchangeCrypto { + + private static final byte[] PROTOCOL_VERSION_BYTES = + new byte[] {PROTOCOL_VERSION}; + + private final CryptoComponent crypto; + + @Inject + ContactExchangeCryptoImpl(CryptoComponent crypto) { + this.crypto = crypto; + } + + @Override + public SecretKey deriveHeaderKey(SecretKey masterKey, boolean alice) { + String label = alice ? ALICE_KEY_LABEL : BOB_KEY_LABEL; + return crypto.deriveKey(label, masterKey, PROTOCOL_VERSION_BYTES); + } + + @Override + public byte[] sign(PrivateKey privateKey, SecretKey masterKey, + boolean alice) { + byte[] nonce = deriveNonce(masterKey, alice); + try { + return crypto.sign(SIGNING_LABEL, nonce, privateKey); + } catch (GeneralSecurityException e) { + throw new AssertionError(); + } + } + + @Override + public boolean verify(PublicKey publicKey, + SecretKey masterKey, boolean alice, byte[] signature) { + byte[] nonce = deriveNonce(masterKey, alice); + try { + return crypto.verifySignature(signature, SIGNING_LABEL, nonce, + publicKey); + } catch (GeneralSecurityException e) { + return false; + } + } + + private byte[] deriveNonce(SecretKey masterKey, boolean alice) { + String label = alice ? ALICE_NONCE_LABEL : BOB_NONCE_LABEL; + return crypto.mac(label, masterKey, PROTOCOL_VERSION_BYTES); + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeTaskImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeManagerImpl.java similarity index 52% rename from bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeTaskImpl.java rename to bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeManagerImpl.java index a6c90699e..49ed255f2 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeTaskImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeManagerImpl.java @@ -3,24 +3,21 @@ package org.briarproject.bramble.contact; import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.Predicate; import org.briarproject.bramble.api.client.ClientHelper; -import org.briarproject.bramble.api.contact.ContactExchangeTask; +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.event.ContactExchangeFailedEvent; -import org.briarproject.bramble.api.contact.event.ContactExchangeSucceededEvent; -import org.briarproject.bramble.api.crypto.CryptoComponent; +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.ContactExistsException; import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DbException; -import org.briarproject.bramble.api.event.EventBus; 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.plugin.ConnectionManager; import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.properties.TransportProperties; @@ -39,28 +36,24 @@ 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.inject.Inject; -import static java.util.logging.Level.WARNING; +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.util.LogUtils.logException; +import static org.briarproject.bramble.contact.ContactExchangeConstants.PROTOCOL_VERSION; import static org.briarproject.bramble.util.ValidationUtils.checkLength; import static org.briarproject.bramble.util.ValidationUtils.checkSize; @MethodsNotNullByDefault @ParametersNotNullByDefault -class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask { +class ContactExchangeManagerImpl implements ContactExchangeManager { private static final Logger LOG = - Logger.getLogger(ContactExchangeTaskImpl.class.getName()); - - private static final String SIGNING_LABEL_EXCHANGE = - "org.briarproject.briar.contact/EXCHANGE"; + getLogger(ContactExchangeManagerImpl.class.getName()); // Accept records with current protocol version, known record type private static final Predicate ACCEPT = r -> @@ -80,190 +73,108 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask { private final ClientHelper clientHelper; private final RecordReaderFactory recordReaderFactory; private final RecordWriterFactory recordWriterFactory; - private final EventBus eventBus; private final Clock clock; - private final ConnectionManager connectionManager; private final ContactManager contactManager; + private final IdentityManager identityManager; private final TransportPropertyManager transportPropertyManager; - private final CryptoComponent crypto; + private final ContactExchangeCrypto contactExchangeCrypto; private final StreamReaderFactory streamReaderFactory; private final StreamWriterFactory streamWriterFactory; - private volatile LocalAuthor localAuthor; - private volatile DuplexTransportConnection conn; - private volatile TransportId transportId; - private volatile SecretKey masterKey; - private volatile boolean alice; - @Inject - ContactExchangeTaskImpl(DatabaseComponent db, ClientHelper clientHelper, + ContactExchangeManagerImpl(DatabaseComponent db, ClientHelper clientHelper, RecordReaderFactory recordReaderFactory, - RecordWriterFactory recordWriterFactory, EventBus eventBus, - Clock clock, ConnectionManager connectionManager, - ContactManager contactManager, + RecordWriterFactory recordWriterFactory, Clock clock, + ContactManager contactManager, IdentityManager identityManager, TransportPropertyManager transportPropertyManager, - CryptoComponent crypto, StreamReaderFactory streamReaderFactory, + ContactExchangeCrypto contactExchangeCrypto, + StreamReaderFactory streamReaderFactory, StreamWriterFactory streamWriterFactory) { this.db = db; this.clientHelper = clientHelper; this.recordReaderFactory = recordReaderFactory; this.recordWriterFactory = recordWriterFactory; - this.eventBus = eventBus; this.clock = clock; - this.connectionManager = connectionManager; this.contactManager = contactManager; + this.identityManager = identityManager; this.transportPropertyManager = transportPropertyManager; - this.crypto = crypto; + this.contactExchangeCrypto = contactExchangeCrypto; this.streamReaderFactory = streamReaderFactory; this.streamWriterFactory = streamWriterFactory; } @Override - public void startExchange(LocalAuthor localAuthor, SecretKey masterKey, - DuplexTransportConnection conn, TransportId transportId, - boolean alice) { - this.localAuthor = localAuthor; - this.conn = conn; - this.transportId = transportId; - this.masterKey = masterKey; - this.alice = alice; - start(); - } - - @Override - public void run() { + public Contact exchangeContacts(TransportId t, + DuplexTransportConnection conn, SecretKey masterKey, boolean alice) + throws IOException, DbException { // Get the transport connection's input and output streams - InputStream in; - OutputStream out; - try { - in = conn.getReader().getInputStream(); - out = conn.getWriter().getOutputStream(); - } catch (IOException e) { - logException(LOG, WARNING, e); - tryToClose(conn); - eventBus.broadcast(new ContactExchangeFailedEvent()); - return; - } + InputStream in = conn.getReader().getInputStream(); + OutputStream out = conn.getWriter().getOutputStream(); - // Get the local transport properties - Map localProperties; - try { - localProperties = transportPropertyManager.getLocalProperties(); - } catch (DbException e) { - logException(LOG, WARNING, e); - eventBus.broadcast(new ContactExchangeFailedEvent()); - tryToClose(conn); - return; - } + // Get the local author and transport properties + LocalAuthor localAuthor = identityManager.getLocalAuthor(); + Map localProperties = + transportPropertyManager.getLocalProperties(); // Derive the header keys for the transport streams - SecretKey aliceHeaderKey = crypto.deriveKey(ALICE_KEY_LABEL, masterKey, - new byte[] {PROTOCOL_VERSION}); - SecretKey bobHeaderKey = crypto.deriveKey(BOB_KEY_LABEL, masterKey, - new byte[] {PROTOCOL_VERSION}); + SecretKey localHeaderKey = + contactExchangeCrypto.deriveHeaderKey(masterKey, alice); + SecretKey remoteHeaderKey = + contactExchangeCrypto.deriveHeaderKey(masterKey, !alice); // Create the readers - InputStream streamReader = - streamReaderFactory.createContactExchangeStreamReader(in, - alice ? bobHeaderKey : aliceHeaderKey); + InputStream streamReader = streamReaderFactory + .createContactExchangeStreamReader(in, remoteHeaderKey); RecordReader recordReader = recordReaderFactory.createRecordReader(streamReader); // Create the writers - StreamWriter streamWriter = - streamWriterFactory.createContactExchangeStreamWriter(out, - alice ? aliceHeaderKey : bobHeaderKey); - RecordWriter recordWriter = - recordWriterFactory - .createRecordWriter(streamWriter.getOutputStream()); + StreamWriter streamWriter = streamWriterFactory + .createContactExchangeStreamWriter(out, localHeaderKey); + RecordWriter recordWriter = recordWriterFactory + .createRecordWriter(streamWriter.getOutputStream()); - // Derive the nonces to be signed - byte[] aliceNonce = crypto.mac(ALICE_NONCE_LABEL, masterKey, - new byte[] {PROTOCOL_VERSION}); - byte[] bobNonce = crypto.mac(BOB_NONCE_LABEL, masterKey, - new byte[] {PROTOCOL_VERSION}); - byte[] localNonce = alice ? aliceNonce : bobNonce; - byte[] remoteNonce = alice ? bobNonce : aliceNonce; - - // Sign the nonce - byte[] localSignature = sign(localAuthor, localNonce); + // Create our signature + byte[] localSignature = contactExchangeCrypto + .sign(localAuthor.getPrivateKey(), masterKey, alice); // Exchange contact info long localTimestamp = clock.currentTimeMillis(); ContactInfo remoteInfo; - try { - if (alice) { - sendContactInfo(recordWriter, localAuthor, localProperties, - localSignature, localTimestamp); - recordWriter.flush(); - remoteInfo = receiveContactInfo(recordReader); - } else { - remoteInfo = receiveContactInfo(recordReader); - sendContactInfo(recordWriter, localAuthor, localProperties, - localSignature, localTimestamp); - recordWriter.flush(); - } - // Send EOF on the outgoing stream - streamWriter.sendEndOfStream(); - // Skip any remaining records from the incoming stream - recordReader.readRecord(r -> false, IGNORE); - } catch (IOException e) { - logException(LOG, WARNING, e); - eventBus.broadcast(new ContactExchangeFailedEvent()); - tryToClose(conn); - return; + if (alice) { + sendContactInfo(recordWriter, localAuthor, localProperties, + localSignature, localTimestamp); + remoteInfo = receiveContactInfo(recordReader); + } else { + remoteInfo = receiveContactInfo(recordReader); + sendContactInfo(recordWriter, localAuthor, localProperties, + localSignature, localTimestamp); } + // Send EOF on the outgoing stream + streamWriter.sendEndOfStream(); + + // Skip any remaining records from the incoming stream + recordReader.readRecord(r -> false, IGNORE); + // Verify the contact's signature - if (!verify(remoteInfo.author, remoteNonce, remoteInfo.signature)) { + PublicKey remotePublicKey = remoteInfo.author.getPublicKey(); + if (!contactExchangeCrypto.verify(remotePublicKey, + masterKey, !alice, remoteInfo.signature)) { LOG.warning("Invalid signature"); - eventBus.broadcast(new ContactExchangeFailedEvent()); - tryToClose(conn); - return; + throw new FormatException(); } // The agreed timestamp is the minimum of the peers' timestamps long timestamp = Math.min(localTimestamp, remoteInfo.timestamp); - try { - // Add the contact - ContactId contactId = addContact(remoteInfo.author, timestamp, - remoteInfo.properties); - // Reuse the connection as a transport connection - connectionManager.manageOutgoingConnection(contactId, transportId, - conn); - // Pseudonym exchange succeeded - LOG.info("Pseudonym exchange succeeded"); - eventBus.broadcast( - new ContactExchangeSucceededEvent(remoteInfo.author)); - } catch (ContactExistsException e) { - logException(LOG, WARNING, e); - tryToClose(conn); - eventBus.broadcast( - new ContactExchangeFailedEvent(remoteInfo.author)); - } catch (DbException e) { - logException(LOG, WARNING, e); - tryToClose(conn); - eventBus.broadcast(new ContactExchangeFailedEvent()); - } - } + // Add the contact + Contact contact = addContact(remoteInfo.author, localAuthor, + masterKey, timestamp, alice, remoteInfo.properties); - private byte[] sign(LocalAuthor author, byte[] nonce) { - try { - return crypto.sign(SIGNING_LABEL_EXCHANGE, nonce, - author.getPrivateKey()); - } catch (GeneralSecurityException e) { - throw new AssertionError(); - } - } - - private boolean verify(Author author, byte[] nonce, byte[] signature) { - try { - return crypto.verifySignature(signature, SIGNING_LABEL_EXCHANGE, - nonce, author.getPublicKey()); - } catch (GeneralSecurityException e) { - return false; - } + // Contact exchange succeeded + LOG.info("Contact exchange succeeded"); + return contact; } private void sendContactInfo(RecordWriter recordWriter, Author author, @@ -274,6 +185,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask { BdfList payload = BdfList.of(authorList, props, signature, timestamp); recordWriter.writeRecord(new Record(PROTOCOL_VERSION, CONTACT_INFO, clientHelper.toByteArray(payload))); + recordWriter.flush(); LOG.info("Sent contact info"); } @@ -295,7 +207,8 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask { return new ContactInfo(author, properties, signature, timestamp); } - private ContactId addContact(Author remoteAuthor, long timestamp, + private Contact addContact(Author remoteAuthor, LocalAuthor localAuthor, + SecretKey masterKey, long timestamp, boolean alice, Map remoteProperties) throws DbException { return db.transactionWithResult(false, txn -> { @@ -304,20 +217,10 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask { true, true); transportPropertyManager.addRemoteProperties(txn, contactId, remoteProperties); - return contactId; + return contactManager.getContact(txn, contactId); }); } - private void tryToClose(DuplexTransportConnection conn) { - try { - LOG.info("Closing connection"); - conn.getReader().dispose(true, true); - conn.getWriter().dispose(true); - } catch (IOException e) { - logException(LOG, WARNING, e); - } - } - private static class ContactInfo { private final Author author; 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 d6e2d0782..510be77a0 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 @@ -137,6 +137,11 @@ class ContactManagerImpl implements ContactManager { return db.transactionWithResult(true, txn -> db.getContact(txn, c)); } + @Override + public Contact getContact(Transaction txn, ContactId c) throws DbException { + return db.getContact(txn, c); + } + @Override public Contact getContact(AuthorId remoteAuthorId, AuthorId localAuthorId) throws DbException { 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 f31ce108a..97af570b6 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 @@ -1,6 +1,6 @@ package org.briarproject.bramble.contact; -import org.briarproject.bramble.api.contact.ContactExchangeTask; +import org.briarproject.bramble.api.contact.ContactExchangeManager; import org.briarproject.bramble.api.contact.ContactManager; import javax.inject.Inject; @@ -19,14 +19,14 @@ public class ContactModule { @Provides @Singleton - ContactManager getContactManager(ContactManagerImpl contactManager) { + ContactManager provideContactManager(ContactManagerImpl contactManager) { return contactManager; } @Provides - ContactExchangeTask provideContactExchangeTask( - ContactExchangeTaskImpl contactExchangeTask) { - return contactExchangeTask; + ContactExchangeManager provideContactExchangeManager( + ContactExchangeManagerImpl contactExchangeManager) { + return contactExchangeManager; } @Provides @@ -34,4 +34,10 @@ public class ContactModule { PendingContactFactoryImpl pendingContactFactory) { return pendingContactFactory; } + + @Provides + ContactExchangeCrypto provideContactExchangeCrypto( + ContactExchangeCryptoImpl contactExchangeCrypto) { + return contactExchangeCrypto; + } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java index c4aeb15c5..275d04147 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/db/DatabaseComponentImpl.java @@ -234,16 +234,15 @@ class DatabaseComponentImpl implements DatabaseComponent { @Override public ContactId addContact(Transaction transaction, Author remote, - AuthorId local, boolean verified) - throws DbException { + AuthorId local, boolean verified) throws DbException { if (transaction.isReadOnly()) throw new IllegalArgumentException(); T txn = unbox(transaction); if (!db.containsIdentity(txn, local)) throw new NoSuchIdentityException(); if (db.containsIdentity(txn, remote.getId())) - throw new ContactExistsException(); + throw new ContactExistsException(local, remote); if (db.containsContact(txn, remote.getId(), local)) - throw new ContactExistsException(); + throw new ContactExistsException(local, remote); ContactId c = db.addContact(txn, remote, local, verified); transaction.attach(new ContactAddedEvent(c)); return c; diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java index a77acbdd6..24c363e22 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java @@ -1459,7 +1459,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase { true)); fail(); } catch (ContactExistsException expected) { - // Expected + assertEquals(localAuthor.getId(), expected.getLocalAuthorId()); + assertEquals(author, expected.getRemoteAuthor()); } } @@ -1488,7 +1489,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase { true)); fail(); } catch (ContactExistsException expected) { - // Expected + assertEquals(localAuthor.getId(), expected.getLocalAuthorId()); + assertEquals(author, expected.getRemoteAuthor()); } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java index 90a76e6da..e2473e846 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java @@ -8,7 +8,7 @@ import org.briarproject.bramble.BrambleCoreEagerSingletons; import org.briarproject.bramble.BrambleCoreModule; import org.briarproject.bramble.account.BriarAccountModule; import org.briarproject.bramble.api.account.AccountManager; -import org.briarproject.bramble.api.contact.ContactExchangeTask; +import org.briarproject.bramble.api.contact.ContactExchangeManager; import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.crypto.CryptoExecutor; import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator; @@ -128,7 +128,7 @@ public interface AndroidComponent SettingsManager settingsManager(); - ContactExchangeTask contactExchangeTask(); + ContactExchangeManager contactExchangeManager(); KeyAgreementTask keyAgreementTask(); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java index c3ccaa9cc..55e966549 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java @@ -33,6 +33,7 @@ import org.briarproject.bramble.plugin.tor.CircumventionProvider; import org.briarproject.bramble.util.AndroidUtils; import org.briarproject.bramble.util.StringUtils; import org.briarproject.briar.android.account.LockManagerImpl; +import org.briarproject.briar.android.keyagreement.ContactExchangeModule; import org.briarproject.briar.android.viewmodel.ViewModelModule; import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.DozeWatchdog; @@ -59,7 +60,7 @@ import static java.util.Collections.emptyList; import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_ONION_ADDRESS; import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_PUBLIC_KEY_HEX; -@Module(includes = ViewModelModule.class) +@Module(includes = {ContactExchangeModule.class, ViewModelModule.class}) public class AppModule { static class EagerSingletons { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeActivity.java index cf4329518..7372ae2ae 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeActivity.java @@ -1,46 +1,32 @@ package org.briarproject.briar.android.keyagreement; +import android.arch.lifecycle.ViewModelProvider; +import android.arch.lifecycle.ViewModelProviders; import android.os.Bundle; import android.support.annotation.UiThread; import android.widget.Toast; -import org.briarproject.bramble.api.contact.ContactExchangeTask; -import org.briarproject.bramble.api.contact.event.ContactExchangeFailedEvent; -import org.briarproject.bramble.api.contact.event.ContactExchangeSucceededEvent; -import org.briarproject.bramble.api.db.DbException; -import org.briarproject.bramble.api.event.Event; -import org.briarproject.bramble.api.event.EventListener; 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.keyagreement.KeyAgreementResult; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; -import java.util.logging.Logger; - import javax.annotation.Nullable; import javax.inject.Inject; import static android.widget.Toast.LENGTH_LONG; -import static java.util.logging.Level.WARNING; -import static org.briarproject.bramble.util.LogUtils.logException; +import static java.util.Objects.requireNonNull; @MethodsNotNullByDefault @ParametersNotNullByDefault -public class ContactExchangeActivity extends KeyAgreementActivity implements - EventListener { +public class ContactExchangeActivity extends KeyAgreementActivity { - private static final Logger LOG = - Logger.getLogger(ContactExchangeActivity.class.getName()); + @Inject + ViewModelProvider.Factory viewModelFactory; - // Fields that are accessed from background threads must be volatile - @Inject - volatile ContactExchangeTask contactExchangeTask; - @Inject - volatile IdentityManager identityManager; + private ContactExchangeViewModel viewModel; @Override public void injectActivity(ActivityComponent component) { @@ -50,83 +36,50 @@ public class ContactExchangeActivity extends KeyAgreementActivity implements @Override public void onCreate(@Nullable Bundle state) { super.onCreate(state); - getSupportActionBar().setTitle(R.string.add_contact_title); - } - - @Override - public void onStart() { - super.onStart(); - // Listen to updates from contactExchangeTask - eventBus.addListener(this); - } - - @Override - protected void onStop() { - super.onStop(); - // Stop listen to updates from contactExchangeTask - eventBus.addListener(this); + requireNonNull(getSupportActionBar()) + .setTitle(R.string.add_contact_title); + viewModel = ViewModelProviders.of(this, viewModelFactory) + .get(ContactExchangeViewModel.class); } private void startContactExchange(KeyAgreementResult result) { - runOnDbThread(() -> { - LocalAuthor localAuthor; - // Load the local pseudonym - try { - localAuthor = identityManager.getLocalAuthor(); - } catch (DbException e) { - logException(LOG, WARNING, e); - contactExchangeFailed(); - return; - } - - // Exchange contact details - contactExchangeTask.startExchange(localAuthor, - result.getMasterKey(), result.getConnection(), - result.getTransportId(), result.wasAlice()); - }); - } - - @Override - public void eventOccurred(Event e) { - if (e instanceof ContactExchangeSucceededEvent) { - contactExchangeSucceeded( - ((ContactExchangeSucceededEvent) e).getRemoteAuthor()); - } else if (e instanceof ContactExchangeFailedEvent) { - ContactExchangeFailedEvent fe = (ContactExchangeFailedEvent) e; - if (fe.wasDuplicateContact()) { - duplicateContact(fe.getDuplicateRemoteAuthor()); + viewModel.getSucceeded().observe(this, succeeded -> { + if (succeeded == null) return; + if (succeeded) { + Author remote = requireNonNull(viewModel.getRemoteAuthor()); + contactExchangeSucceeded(remote); } else { - contactExchangeFailed(); + Author duplicate = viewModel.getDuplicateAuthor(); + if (duplicate == null) contactExchangeFailed(); + else duplicateContact(duplicate); } - } + }); + viewModel.startContactExchange(result.getTransportId(), + result.getConnection(), result.getMasterKey(), + result.wasAlice()); } + @UiThread private void contactExchangeSucceeded(Author remoteAuthor) { - runOnUiThreadUnlessDestroyed(() -> { - String contactName = remoteAuthor.getName(); - String format = getString(R.string.contact_added_toast); - String text = String.format(format, contactName); - Toast.makeText(ContactExchangeActivity.this, text, LENGTH_LONG) - .show(); - supportFinishAfterTransition(); - }); + String contactName = remoteAuthor.getName(); + String format = getString(R.string.contact_added_toast); + String text = String.format(format, contactName); + Toast.makeText(this, text, LENGTH_LONG).show(); + supportFinishAfterTransition(); } + @UiThread private void duplicateContact(Author remoteAuthor) { - runOnUiThreadUnlessDestroyed(() -> { - String contactName = remoteAuthor.getName(); - String format = getString(R.string.contact_already_exists); - String text = String.format(format, contactName); - Toast.makeText(ContactExchangeActivity.this, text, LENGTH_LONG) - .show(); - finish(); - }); + String contactName = remoteAuthor.getName(); + String format = getString(R.string.contact_already_exists); + String text = String.format(format, contactName); + Toast.makeText(this, text, LENGTH_LONG).show(); + finish(); } + @UiThread private void contactExchangeFailed() { - runOnUiThreadUnlessDestroyed(() -> { - showErrorFragment(R.string.connection_error_explanation); - }); + showErrorFragment(R.string.connection_error_explanation); } @UiThread diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeModule.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeModule.java new file mode 100644 index 000000000..65baf1e70 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeModule.java @@ -0,0 +1,20 @@ +package org.briarproject.briar.android.keyagreement; + +import android.arch.lifecycle.ViewModel; + +import org.briarproject.briar.android.viewmodel.ViewModelKey; + +import dagger.Binds; +import dagger.Module; +import dagger.multibindings.IntoMap; + +@Module +public abstract class ContactExchangeModule { + + @Binds + @IntoMap + @ViewModelKey(ContactExchangeViewModel.class) + abstract ViewModel bindContactExchangeViewModel( + ContactExchangeViewModel contactExchangeViewModel); + +} 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 new file mode 100644 index 000000000..a7ada2cbe --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/ContactExchangeViewModel.java @@ -0,0 +1,104 @@ +package org.briarproject.briar.android.keyagreement; + +import android.app.Application; +import android.arch.lifecycle.AndroidViewModel; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.MutableLiveData; +import android.support.annotation.UiThread; + +import org.briarproject.bramble.api.contact.Contact; +import org.briarproject.bramble.api.contact.ContactExchangeManager; +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.identity.Author; +import org.briarproject.bramble.api.lifecycle.IoExecutor; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.ConnectionManager; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; + +import java.io.IOException; +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.util.LogUtils.logException; + +@NotNullByDefault +class ContactExchangeViewModel extends AndroidViewModel { + + private static final Logger LOG = + getLogger(ContactExchangeViewModel.class.getName()); + + private final Executor ioExecutor; + private final ContactExchangeManager contactExchangeManager; + private final ConnectionManager connectionManager; + private final MutableLiveData succeeded = new MutableLiveData<>(); + + @Nullable + private volatile Author remoteAuthor, duplicateAuthor; + + @Inject + ContactExchangeViewModel(Application app, @IoExecutor Executor ioExecutor, + ContactExchangeManager contactExchangeManager, + ConnectionManager connectionManager) { + super(app); + this.ioExecutor = ioExecutor; + this.contactExchangeManager = contactExchangeManager; + this.connectionManager = connectionManager; + } + + @UiThread + void startContactExchange(TransportId t, DuplexTransportConnection conn, + SecretKey masterKey, boolean alice) { + ioExecutor.execute(() -> { + try { + Contact contact = contactExchangeManager.exchangeContacts(t, + conn, masterKey, alice); + // Reuse the connection as a transport connection + connectionManager.manageOutgoingConnection(contact.getId(), + t, conn); + remoteAuthor = contact.getAuthor(); + succeeded.postValue(true); + } catch (ContactExistsException e) { + tryToClose(conn); + duplicateAuthor = e.getRemoteAuthor(); + succeeded.postValue(false); + } catch (DbException | IOException e) { + tryToClose(conn); + logException(LOG, WARNING, e); + succeeded.postValue(false); + } + }); + } + + private void tryToClose(DuplexTransportConnection conn) { + try { + conn.getReader().dispose(true, true); + conn.getWriter().dispose(true); + } catch (IOException e) { + logException(LOG, WARNING, e); + } + } + + @UiThread + @Nullable + Author getRemoteAuthor() { + return remoteAuthor; + } + + @UiThread + @Nullable + Author getDuplicateAuthor() { + return duplicateAuthor; + } + + LiveData getSucceeded() { + return succeeded; + } +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelKey.java b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelKey.java index bd4f3d650..66288e9bb 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelKey.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/viewmodel/ViewModelKey.java @@ -14,6 +14,6 @@ import dagger.MapKey; @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @MapKey -@interface ViewModelKey { +public @interface ViewModelKey { Class value(); }