From f1e5c2dd6635d5fffcabdfbcb745995d86af818b Mon Sep 17 00:00:00 2001 From: akwizgran Date: Fri, 24 May 2019 11:40:12 +0100 Subject: [PATCH] Return a contact, encapsulate contact exchange crypto. --- .../api/contact/ContactExchangeManager.java | 6 +- .../bramble/api/contact/ContactManager.java | 5 + .../contact/ContactExchangeCrypto.java | 35 +++++++ .../contact/ContactExchangeCryptoImpl.java | 66 +++++++++++++ .../contact/ContactExchangeManagerImpl.java | 96 +++++++------------ .../bramble/contact/ContactManagerImpl.java | 5 + .../bramble/contact/ContactModule.java | 6 ++ .../ContactExchangeViewModel.java | 15 ++- 8 files changed, 164 insertions(+), 70 deletions(-) create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeCrypto.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeCryptoImpl.java 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 ef7442014..0981158f0 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 @@ -3,7 +3,6 @@ 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.identity.Author; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; @@ -16,9 +15,10 @@ public interface ContactExchangeManager { /** * Exchanges contact information with a remote peer. * - * @return The contact's pseudonym + * @param alice Whether the local peer takes the role of Alice + * @return The newly added contact * @throws ContactExistsException If the contact already exists */ - Author exchangeContacts(TransportId t, DuplexTransportConnection conn, + 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-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/ContactExchangeManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeManagerImpl.java index 7c6ba360f..49ed255f2 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 @@ -3,10 +3,11 @@ 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.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.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; @@ -17,7 +18,6 @@ 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; @@ -36,7 +36,6 @@ 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; @@ -45,12 +44,7 @@ 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.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; import static org.briarproject.bramble.util.ValidationUtils.checkLength; import static org.briarproject.bramble.util.ValidationUtils.checkSize; @@ -80,39 +74,37 @@ class ContactExchangeManagerImpl implements ContactExchangeManager { private final RecordReaderFactory recordReaderFactory; private final RecordWriterFactory recordWriterFactory; 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; @Inject ContactExchangeManagerImpl(DatabaseComponent db, ClientHelper clientHelper, RecordReaderFactory recordReaderFactory, - RecordWriterFactory recordWriterFactory, - Clock clock, ConnectionManager connectionManager, + 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.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 Author exchangeContacts(TransportId t, + public Contact exchangeContacts(TransportId t, DuplexTransportConnection conn, SecretKey masterKey, boolean alice) throws IOException, DbException { // Get the transport connection's input and output streams @@ -125,36 +117,26 @@ class ContactExchangeManagerImpl implements ContactExchangeManager { 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(); @@ -168,13 +150,17 @@ class ContactExchangeManagerImpl implements ContactExchangeManager { 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"); throw new FormatException(); } @@ -183,30 +169,12 @@ class ContactExchangeManagerImpl implements ContactExchangeManager { long timestamp = Math.min(localTimestamp, remoteInfo.timestamp); // Add the contact - ContactId contactId = addContact(remoteInfo.author, localAuthor, + Contact contact = addContact(remoteInfo.author, localAuthor, masterKey, timestamp, alice, remoteInfo.properties); - // Reuse the connection as a transport connection - connectionManager.manageOutgoingConnection(contactId, t, conn); - // Pseudonym exchange succeeded - LOG.info("Pseudonym exchange succeeded"); - return remoteInfo.author; - } - private byte[] sign(LocalAuthor author, byte[] nonce) { - try { - return crypto.sign(SIGNING_LABEL, 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, 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, @@ -239,7 +207,7 @@ class ContactExchangeManagerImpl implements ContactExchangeManager { return new ContactInfo(author, properties, signature, timestamp); } - private ContactId addContact(Author remoteAuthor, LocalAuthor localAuthor, + private Contact addContact(Author remoteAuthor, LocalAuthor localAuthor, SecretKey masterKey, long timestamp, boolean alice, Map remoteProperties) throws DbException { @@ -249,7 +217,7 @@ class ContactExchangeManagerImpl implements ContactExchangeManager { true, true); transportPropertyManager.addRemoteProperties(txn, contactId, remoteProperties); - return contactId; + return contactManager.getContact(txn, contactId); }); } 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 614edd4ea..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 @@ -34,4 +34,10 @@ public class ContactModule { PendingContactFactoryImpl pendingContactFactory) { return pendingContactFactory; } + + @Provides + ContactExchangeCrypto provideContactExchangeCrypto( + ContactExchangeCryptoImpl contactExchangeCrypto) { + return contactExchangeCrypto; + } } 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 149f92b8d..259e43f68 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 @@ -6,6 +6,7 @@ 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; @@ -13,6 +14,7 @@ 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; @@ -35,6 +37,7 @@ class ContactExchangeViewModel extends AndroidViewModel { private final Executor ioExecutor; private final ContactExchangeManager contactExchangeManager; + private final ConnectionManager connectionManager; private final MutableLiveData succeeded = new MutableLiveData<>(); @Nullable @@ -42,10 +45,12 @@ class ContactExchangeViewModel extends AndroidViewModel { @Inject ContactExchangeViewModel(Application app, @IoExecutor Executor ioExecutor, - ContactExchangeManager contactExchangeManager) { + ContactExchangeManager contactExchangeManager, + ConnectionManager connectionManager) { super(app); this.ioExecutor = ioExecutor; this.contactExchangeManager = contactExchangeManager; + this.connectionManager = connectionManager; } @UiThread @@ -53,8 +58,12 @@ class ContactExchangeViewModel extends AndroidViewModel { SecretKey masterKey, boolean alice) { ioExecutor.execute(() -> { try { - remoteAuthor = contactExchangeManager.exchangeContacts(t, conn, - masterKey, alice); + 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) { duplicateAuthor = e.getRemoteAuthor();