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.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;
}

View File

@@ -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

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.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<TransportId, TransportProperties> 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);
});
}

View File

@@ -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 {

View File

@@ -34,4 +34,10 @@ public class ContactModule {
PendingContactFactoryImpl 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.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<Boolean> 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();