Return a contact, encapsulate contact exchange crypto.

This commit is contained in:
akwizgran
2019-05-24 11:40:12 +01:00
parent 5be0e928c4
commit f1e5c2dd66
8 changed files with 164 additions and 70 deletions

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.api.contact;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.ContactExistsException; import org.briarproject.bramble.api.db.ContactExistsException;
import org.briarproject.bramble.api.db.DbException; 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
@@ -16,9 +15,10 @@ public interface ContactExchangeManager {
/** /**
* Exchanges contact information with a remote peer. * 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 * @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; SecretKey masterKey, boolean alice) throws IOException, DbException;
} }

View File

@@ -93,6 +93,11 @@ public interface ContactManager {
*/ */
Contact getContact(ContactId c) throws DbException; 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 * Returns the contact with the given remoteAuthorId
* that was added by the LocalAuthor with the given localAuthorId * that was added by the LocalAuthor with the given localAuthorId

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -3,10 +3,11 @@ package org.briarproject.bramble.contact;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Predicate; import org.briarproject.bramble.api.Predicate;
import org.briarproject.bramble.api.client.ClientHelper; 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.ContactExchangeManager;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager; 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.crypto.SecretKey;
import org.briarproject.bramble.api.data.BdfDictionary; import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfList; 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.identity.LocalAuthor;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; 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.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.api.properties.TransportProperties;
@@ -36,7 +36,6 @@ import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.util.Map; import java.util.Map;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -45,12 +44,7 @@ import javax.inject.Inject;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.contact.RecordTypes.CONTACT_INFO; 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.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.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.checkLength;
import static org.briarproject.bramble.util.ValidationUtils.checkSize; import static org.briarproject.bramble.util.ValidationUtils.checkSize;
@@ -80,39 +74,37 @@ class ContactExchangeManagerImpl implements ContactExchangeManager {
private final RecordReaderFactory recordReaderFactory; private final RecordReaderFactory recordReaderFactory;
private final RecordWriterFactory recordWriterFactory; private final RecordWriterFactory recordWriterFactory;
private final Clock clock; private final Clock clock;
private final ConnectionManager connectionManager;
private final ContactManager contactManager; private final ContactManager contactManager;
private final IdentityManager identityManager; private final IdentityManager identityManager;
private final TransportPropertyManager transportPropertyManager; private final TransportPropertyManager transportPropertyManager;
private final CryptoComponent crypto; private final ContactExchangeCrypto contactExchangeCrypto;
private final StreamReaderFactory streamReaderFactory; private final StreamReaderFactory streamReaderFactory;
private final StreamWriterFactory streamWriterFactory; private final StreamWriterFactory streamWriterFactory;
@Inject @Inject
ContactExchangeManagerImpl(DatabaseComponent db, ClientHelper clientHelper, ContactExchangeManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
RecordReaderFactory recordReaderFactory, RecordReaderFactory recordReaderFactory,
RecordWriterFactory recordWriterFactory, RecordWriterFactory recordWriterFactory, Clock clock,
Clock clock, ConnectionManager connectionManager,
ContactManager contactManager, IdentityManager identityManager, ContactManager contactManager, IdentityManager identityManager,
TransportPropertyManager transportPropertyManager, TransportPropertyManager transportPropertyManager,
CryptoComponent crypto, StreamReaderFactory streamReaderFactory, ContactExchangeCrypto contactExchangeCrypto,
StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory) { StreamWriterFactory streamWriterFactory) {
this.db = db; this.db = db;
this.clientHelper = clientHelper; this.clientHelper = clientHelper;
this.recordReaderFactory = recordReaderFactory; this.recordReaderFactory = recordReaderFactory;
this.recordWriterFactory = recordWriterFactory; this.recordWriterFactory = recordWriterFactory;
this.clock = clock; this.clock = clock;
this.connectionManager = connectionManager;
this.contactManager = contactManager; this.contactManager = contactManager;
this.identityManager = identityManager; this.identityManager = identityManager;
this.transportPropertyManager = transportPropertyManager; this.transportPropertyManager = transportPropertyManager;
this.crypto = crypto; this.contactExchangeCrypto = contactExchangeCrypto;
this.streamReaderFactory = streamReaderFactory; this.streamReaderFactory = streamReaderFactory;
this.streamWriterFactory = streamWriterFactory; this.streamWriterFactory = streamWriterFactory;
} }
@Override @Override
public Author exchangeContacts(TransportId t, public Contact exchangeContacts(TransportId t,
DuplexTransportConnection conn, SecretKey masterKey, boolean alice) DuplexTransportConnection conn, SecretKey masterKey, boolean alice)
throws IOException, DbException { throws IOException, DbException {
// Get the transport connection's input and output streams // Get the transport connection's input and output streams
@@ -125,36 +117,26 @@ class ContactExchangeManagerImpl implements ContactExchangeManager {
transportPropertyManager.getLocalProperties(); transportPropertyManager.getLocalProperties();
// Derive the header keys for the transport streams // Derive the header keys for the transport streams
SecretKey aliceHeaderKey = crypto.deriveKey(ALICE_KEY_LABEL, masterKey, SecretKey localHeaderKey =
new byte[] {PROTOCOL_VERSION}); contactExchangeCrypto.deriveHeaderKey(masterKey, alice);
SecretKey bobHeaderKey = crypto.deriveKey(BOB_KEY_LABEL, masterKey, SecretKey remoteHeaderKey =
new byte[] {PROTOCOL_VERSION}); contactExchangeCrypto.deriveHeaderKey(masterKey, !alice);
// Create the readers // Create the readers
InputStream streamReader = InputStream streamReader = streamReaderFactory
streamReaderFactory.createContactExchangeStreamReader(in, .createContactExchangeStreamReader(in, remoteHeaderKey);
alice ? bobHeaderKey : aliceHeaderKey);
RecordReader recordReader = RecordReader recordReader =
recordReaderFactory.createRecordReader(streamReader); recordReaderFactory.createRecordReader(streamReader);
// Create the writers // Create the writers
StreamWriter streamWriter = StreamWriter streamWriter = streamWriterFactory
streamWriterFactory.createContactExchangeStreamWriter(out, .createContactExchangeStreamWriter(out, localHeaderKey);
alice ? aliceHeaderKey : bobHeaderKey); RecordWriter recordWriter = recordWriterFactory
RecordWriter recordWriter = .createRecordWriter(streamWriter.getOutputStream());
recordWriterFactory
.createRecordWriter(streamWriter.getOutputStream());
// Derive the nonces to be signed // Create our signature
byte[] aliceNonce = crypto.mac(ALICE_NONCE_LABEL, masterKey, byte[] localSignature = contactExchangeCrypto
new byte[] {PROTOCOL_VERSION}); .sign(localAuthor.getPrivateKey(), masterKey, alice);
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);
// Exchange contact info // Exchange contact info
long localTimestamp = clock.currentTimeMillis(); long localTimestamp = clock.currentTimeMillis();
@@ -168,13 +150,17 @@ class ContactExchangeManagerImpl implements ContactExchangeManager {
sendContactInfo(recordWriter, localAuthor, localProperties, sendContactInfo(recordWriter, localAuthor, localProperties,
localSignature, localTimestamp); localSignature, localTimestamp);
} }
// Send EOF on the outgoing stream // Send EOF on the outgoing stream
streamWriter.sendEndOfStream(); streamWriter.sendEndOfStream();
// Skip any remaining records from the incoming stream // Skip any remaining records from the incoming stream
recordReader.readRecord(r -> false, IGNORE); recordReader.readRecord(r -> false, IGNORE);
// Verify the contact's signature // 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"); LOG.warning("Invalid signature");
throw new FormatException(); throw new FormatException();
} }
@@ -183,30 +169,12 @@ class ContactExchangeManagerImpl implements ContactExchangeManager {
long timestamp = Math.min(localTimestamp, remoteInfo.timestamp); long timestamp = Math.min(localTimestamp, remoteInfo.timestamp);
// Add the contact // Add the contact
ContactId contactId = addContact(remoteInfo.author, localAuthor, Contact contact = addContact(remoteInfo.author, localAuthor,
masterKey, timestamp, alice, remoteInfo.properties); 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) { // Contact exchange succeeded
try { LOG.info("Contact exchange succeeded");
return crypto.sign(SIGNING_LABEL, nonce, author.getPrivateKey()); return contact;
} 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;
}
} }
private void sendContactInfo(RecordWriter recordWriter, Author author, private void sendContactInfo(RecordWriter recordWriter, Author author,
@@ -239,7 +207,7 @@ class ContactExchangeManagerImpl implements ContactExchangeManager {
return new ContactInfo(author, properties, signature, timestamp); 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, SecretKey masterKey, long timestamp, boolean alice,
Map<TransportId, TransportProperties> remoteProperties) Map<TransportId, TransportProperties> remoteProperties)
throws DbException { throws DbException {
@@ -249,7 +217,7 @@ class ContactExchangeManagerImpl implements ContactExchangeManager {
true, true); true, true);
transportPropertyManager.addRemoteProperties(txn, contactId, transportPropertyManager.addRemoteProperties(txn, contactId,
remoteProperties); remoteProperties);
return contactId; return contactManager.getContact(txn, contactId);
}); });
} }

View File

@@ -137,6 +137,11 @@ class ContactManagerImpl implements ContactManager {
return db.transactionWithResult(true, txn -> db.getContact(txn, c)); 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 @Override
public Contact getContact(AuthorId remoteAuthorId, AuthorId localAuthorId) public Contact getContact(AuthorId remoteAuthorId, AuthorId localAuthorId)
throws DbException { throws DbException {

View File

@@ -34,4 +34,10 @@ public class ContactModule {
PendingContactFactoryImpl pendingContactFactory) { PendingContactFactoryImpl pendingContactFactory) {
return pendingContactFactory; return pendingContactFactory;
} }
@Provides
ContactExchangeCrypto provideContactExchangeCrypto(
ContactExchangeCryptoImpl contactExchangeCrypto) {
return contactExchangeCrypto;
}
} }

View File

@@ -6,6 +6,7 @@ import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData; import android.arch.lifecycle.MutableLiveData;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactExchangeManager; import org.briarproject.bramble.api.contact.ContactExchangeManager;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.ContactExistsException; 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.identity.Author;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; 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.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
@@ -35,6 +37,7 @@ class ContactExchangeViewModel extends AndroidViewModel {
private final Executor ioExecutor; private final Executor ioExecutor;
private final ContactExchangeManager contactExchangeManager; private final ContactExchangeManager contactExchangeManager;
private final ConnectionManager connectionManager;
private final MutableLiveData<Boolean> succeeded = new MutableLiveData<>(); private final MutableLiveData<Boolean> succeeded = new MutableLiveData<>();
@Nullable @Nullable
@@ -42,10 +45,12 @@ class ContactExchangeViewModel extends AndroidViewModel {
@Inject @Inject
ContactExchangeViewModel(Application app, @IoExecutor Executor ioExecutor, ContactExchangeViewModel(Application app, @IoExecutor Executor ioExecutor,
ContactExchangeManager contactExchangeManager) { ContactExchangeManager contactExchangeManager,
ConnectionManager connectionManager) {
super(app); super(app);
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.contactExchangeManager = contactExchangeManager; this.contactExchangeManager = contactExchangeManager;
this.connectionManager = connectionManager;
} }
@UiThread @UiThread
@@ -53,8 +58,12 @@ class ContactExchangeViewModel extends AndroidViewModel {
SecretKey masterKey, boolean alice) { SecretKey masterKey, boolean alice) {
ioExecutor.execute(() -> { ioExecutor.execute(() -> {
try { try {
remoteAuthor = contactExchangeManager.exchangeContacts(t, conn, Contact contact = contactExchangeManager.exchangeContacts(t,
masterKey, alice); conn, masterKey, alice);
// Reuse the connection as a transport connection
connectionManager.manageOutgoingConnection(contact.getId(),
t, conn);
remoteAuthor = contact.getAuthor();
succeeded.postValue(true); succeeded.postValue(true);
} catch (ContactExistsException e) { } catch (ContactExistsException e) {
duplicateAuthor = e.getRemoteAuthor(); duplicateAuthor = e.getRemoteAuthor();