mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-11 18:29:05 +01:00
Merge branch '1232-handshake-manager' into 'master'
Implement handshake protocol See merge request briar/briar!1118
This commit is contained in:
@@ -4,7 +4,6 @@ import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.db.ContactExistsException;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -13,12 +12,26 @@ import java.io.IOException;
|
||||
public interface ContactExchangeManager {
|
||||
|
||||
/**
|
||||
* Exchanges contact information with a remote peer.
|
||||
* Exchanges contact information with a remote peer and adds the peer
|
||||
* as a contact.
|
||||
*
|
||||
* @param alice Whether the local peer takes the role of Alice
|
||||
* @return The newly added contact
|
||||
* @throws ContactExistsException If the contact already exists
|
||||
*/
|
||||
Contact exchangeContacts(TransportId t, DuplexTransportConnection conn,
|
||||
SecretKey masterKey, boolean alice) throws IOException, DbException;
|
||||
Contact exchangeContacts(DuplexTransportConnection conn,
|
||||
SecretKey masterKey, boolean alice, boolean verified)
|
||||
throws IOException, DbException;
|
||||
|
||||
/**
|
||||
* Exchanges contact information with a remote peer and adds the peer
|
||||
* as a contact, replacing the given pending contact.
|
||||
*
|
||||
* @param alice Whether the local peer takes the role of Alice
|
||||
* @return The newly added contact
|
||||
* @throws ContactExistsException If the contact already exists
|
||||
*/
|
||||
Contact exchangeContacts(PendingContactId p, DuplexTransportConnection conn,
|
||||
SecretKey masterKey, boolean alice, boolean verified)
|
||||
throws IOException, DbException;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.briarproject.bramble.api.contact;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.transport.StreamWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface HandshakeManager {
|
||||
|
||||
/**
|
||||
* Handshakes with the given pending contact. Returns an ephemeral master
|
||||
* key authenticated with both parties' handshake key pairs and a flag
|
||||
* indicating whether the local peer is Alice or Bob.
|
||||
*
|
||||
* @param in An incoming stream for the handshake, which must be secured in
|
||||
* handshake mode
|
||||
* @param out An outgoing stream for the handshake, which must be secured
|
||||
* in handshake mode
|
||||
*/
|
||||
HandshakeResult handshake(PendingContactId p, InputStream in,
|
||||
StreamWriter out) throws DbException, IOException;
|
||||
|
||||
class HandshakeResult {
|
||||
|
||||
private final SecretKey masterKey;
|
||||
private final boolean alice;
|
||||
|
||||
public HandshakeResult(SecretKey masterKey, boolean alice) {
|
||||
this.masterKey = masterKey;
|
||||
this.alice = alice;
|
||||
}
|
||||
|
||||
public SecretKey getMasterKey() {
|
||||
return masterKey;
|
||||
}
|
||||
|
||||
public boolean isAlice() {
|
||||
return alice;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,17 +7,18 @@ import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactExchangeManager;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.contact.PendingContactId;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
@@ -36,20 +37,23 @@ import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.api.contact.RecordTypes.CONTACT_INFO;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||
import static org.briarproject.bramble.contact.ContactExchangeConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.contact.ContactExchangeRecordTypes.CONTACT_INFO;
|
||||
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class ContactExchangeManagerImpl implements ContactExchangeManager {
|
||||
|
||||
private static final Logger LOG =
|
||||
@@ -104,9 +108,22 @@ class ContactExchangeManagerImpl implements ContactExchangeManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Contact exchangeContacts(TransportId t,
|
||||
DuplexTransportConnection conn, SecretKey masterKey, boolean alice)
|
||||
throws IOException, DbException {
|
||||
public Contact exchangeContacts(DuplexTransportConnection conn,
|
||||
SecretKey masterKey, boolean alice,
|
||||
boolean verified) throws IOException, DbException {
|
||||
return exchange(null, conn, masterKey, alice, verified);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Contact exchangeContacts(PendingContactId p,
|
||||
DuplexTransportConnection conn, SecretKey masterKey, boolean alice,
|
||||
boolean verified) throws IOException, DbException {
|
||||
return exchange(p, conn, masterKey, alice, verified);
|
||||
}
|
||||
|
||||
private Contact exchange(@Nullable PendingContactId p,
|
||||
DuplexTransportConnection conn, SecretKey masterKey, boolean alice,
|
||||
boolean verified) throws IOException, DbException {
|
||||
// Get the transport connection's input and output streams
|
||||
InputStream in = conn.getReader().getInputStream();
|
||||
OutputStream out = conn.getWriter().getOutputStream();
|
||||
@@ -169,8 +186,8 @@ class ContactExchangeManagerImpl implements ContactExchangeManager {
|
||||
long timestamp = Math.min(localTimestamp, remoteInfo.timestamp);
|
||||
|
||||
// Add the contact
|
||||
Contact contact = addContact(remoteInfo.author, localAuthor,
|
||||
masterKey, timestamp, alice, remoteInfo.properties);
|
||||
Contact contact = addContact(p, remoteInfo.author, localAuthor,
|
||||
masterKey, timestamp, alice, verified, remoteInfo.properties);
|
||||
|
||||
// Contact exchange succeeded
|
||||
LOG.info("Contact exchange succeeded");
|
||||
@@ -207,18 +224,34 @@ class ContactExchangeManagerImpl implements ContactExchangeManager {
|
||||
return new ContactInfo(author, properties, signature, timestamp);
|
||||
}
|
||||
|
||||
private Contact addContact(Author remoteAuthor, LocalAuthor localAuthor,
|
||||
SecretKey masterKey, long timestamp, boolean alice,
|
||||
private Contact addContact(@Nullable PendingContactId pendingContactId,
|
||||
Author remoteAuthor, LocalAuthor localAuthor, SecretKey masterKey,
|
||||
long timestamp, boolean alice, boolean verified,
|
||||
Map<TransportId, TransportProperties> remoteProperties)
|
||||
throws DbException {
|
||||
return db.transactionWithResult(false, txn -> {
|
||||
ContactId contactId = contactManager.addContact(txn, remoteAuthor,
|
||||
localAuthor.getId(), masterKey, timestamp, alice,
|
||||
true, true);
|
||||
throws DbException, FormatException {
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
ContactId contactId;
|
||||
if (pendingContactId == null) {
|
||||
contactId = contactManager.addContact(txn, remoteAuthor,
|
||||
localAuthor.getId(), masterKey, timestamp, alice,
|
||||
verified, true);
|
||||
} else {
|
||||
contactId = contactManager.addContact(txn, pendingContactId,
|
||||
remoteAuthor, localAuthor.getId(), masterKey,
|
||||
timestamp, alice, verified, true);
|
||||
}
|
||||
transportPropertyManager.addRemoteProperties(txn, contactId,
|
||||
remoteProperties);
|
||||
return contactManager.getContact(txn, contactId);
|
||||
});
|
||||
Contact contact = contactManager.getContact(txn, contactId);
|
||||
db.commitTransaction(txn);
|
||||
return contact;
|
||||
} catch (GeneralSecurityException e) {
|
||||
// Pending contact's public key is invalid
|
||||
throw new FormatException();
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ContactInfo {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package org.briarproject.bramble.api.contact;
|
||||
package org.briarproject.bramble.contact;
|
||||
|
||||
/**
|
||||
* Record types for the contact exchange protocol.
|
||||
*/
|
||||
public interface RecordTypes {
|
||||
interface ContactExchangeRecordTypes {
|
||||
|
||||
byte CONTACT_INFO = 0;
|
||||
}
|
||||
@@ -33,23 +33,18 @@ import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.BASE32_LINK_BYTES;
|
||||
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
|
||||
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNKNOWN;
|
||||
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNVERIFIED;
|
||||
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomBase32String;
|
||||
import static org.briarproject.bramble.util.StringUtils.toUtf8;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class ContactManagerImpl implements ContactManager {
|
||||
|
||||
private static final String REMOTE_CONTACT_LINK =
|
||||
"briar://" + getRandomBase32String(BASE32_LINK_BYTES);
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final KeyManager keyManager;
|
||||
private final IdentityManager identityManager;
|
||||
@@ -120,9 +115,10 @@ class ContactManagerImpl implements ContactManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandshakeLink() {
|
||||
// TODO replace with real implementation
|
||||
return REMOTE_CONTACT_LINK;
|
||||
public String getHandshakeLink() throws DbException {
|
||||
KeyPair keyPair = db.transactionWithResult(true,
|
||||
identityManager::getHandshakeKeys);
|
||||
return pendingContactFactory.createHandshakeLink(keyPair.getPublic());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.briarproject.bramble.contact;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactExchangeManager;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.contact.HandshakeManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -40,4 +41,17 @@ public class ContactModule {
|
||||
ContactExchangeCryptoImpl contactExchangeCrypto) {
|
||||
return contactExchangeCrypto;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
HandshakeManager provideHandshakeManager(
|
||||
HandshakeManagerImpl handshakeManager) {
|
||||
return handshakeManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
HandshakeCrypto provideHandshakeCrypto(
|
||||
HandshakeCryptoImpl handshakeCrypto) {
|
||||
return handshakeCrypto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.briarproject.bramble.contact;
|
||||
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAC_BYTES;
|
||||
|
||||
interface HandshakeConstants {
|
||||
|
||||
/**
|
||||
* The current version of the handshake protocol.
|
||||
*/
|
||||
byte PROTOCOL_VERSION = 0;
|
||||
|
||||
/**
|
||||
* Label for deriving the master key.
|
||||
*/
|
||||
String MASTER_KEY_LABEL = "org.briarproject.bramble.handshake/MASTER_KEY";
|
||||
|
||||
/**
|
||||
* Label for deriving Alice's proof of ownership from the master key.
|
||||
*/
|
||||
String ALICE_PROOF_LABEL = "org.briarproject.bramble.handshake/ALICE_PROOF";
|
||||
|
||||
/**
|
||||
* Label for deriving Bob's proof of ownership from the master key.
|
||||
*/
|
||||
String BOB_PROOF_LABEL = "org.briarproject.bramble.handshake/BOB_PROOF";
|
||||
|
||||
/**
|
||||
* The length of the proof of ownership in bytes.
|
||||
*/
|
||||
int PROOF_BYTES = MAC_BYTES;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.briarproject.bramble.contact;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
@NotNullByDefault
|
||||
interface HandshakeCrypto {
|
||||
|
||||
KeyPair generateEphemeralKeyPair();
|
||||
|
||||
/**
|
||||
* Derives the master key from the given static and ephemeral keys.
|
||||
*
|
||||
* @param alice Whether the local peer is Alice
|
||||
*/
|
||||
SecretKey deriveMasterKey(PublicKey theirStaticPublicKey,
|
||||
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
|
||||
KeyPair ourEphemeralKeyPair, boolean alice)
|
||||
throws GeneralSecurityException;
|
||||
|
||||
/**
|
||||
* Returns proof that the local peer knows the master key and therefore
|
||||
* owns the static and ephemeral public keys sent by the local peer.
|
||||
*
|
||||
* @param alice Whether the proof is being created by Alice
|
||||
*/
|
||||
byte[] proveOwnership(SecretKey masterKey, boolean alice);
|
||||
|
||||
/**
|
||||
* Verifies the given proof that the remote peer knows the master key and
|
||||
* therefore owns the static and ephemeral keys sent by the remote peer.
|
||||
*
|
||||
* @param alice Whether the proof was created by Alice
|
||||
*/
|
||||
boolean verifyOwnership(SecretKey masterKey, boolean alice, byte[] proof);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.briarproject.bramble.contact;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.ALICE_PROOF_LABEL;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.BOB_PROOF_LABEL;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.MASTER_KEY_LABEL;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class HandshakeCryptoImpl implements HandshakeCrypto {
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
|
||||
@Inject
|
||||
HandshakeCryptoImpl(CryptoComponent crypto) {
|
||||
this.crypto = crypto;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyPair generateEphemeralKeyPair() {
|
||||
return crypto.generateAgreementKeyPair();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKey deriveMasterKey(PublicKey theirStaticPublicKey,
|
||||
PublicKey theirEphemeralPublicKey, KeyPair ourStaticKeyPair,
|
||||
KeyPair ourEphemeralKeyPair, boolean alice) throws
|
||||
GeneralSecurityException {
|
||||
byte[] theirStatic = theirStaticPublicKey.getEncoded();
|
||||
byte[] theirEphemeral = theirEphemeralPublicKey.getEncoded();
|
||||
byte[] ourStatic = ourStaticKeyPair.getPublic().getEncoded();
|
||||
byte[] ourEphemeral = ourEphemeralKeyPair.getPublic().getEncoded();
|
||||
byte[][] inputs = {
|
||||
alice ? ourStatic : theirStatic,
|
||||
alice ? theirStatic : ourStatic,
|
||||
alice ? ourEphemeral : theirEphemeral,
|
||||
alice ? theirEphemeral : ourEphemeral
|
||||
};
|
||||
return crypto.deriveSharedSecret(MASTER_KEY_LABEL, theirStaticPublicKey,
|
||||
theirEphemeralPublicKey, ourStaticKeyPair, ourEphemeralKeyPair,
|
||||
alice, inputs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] proveOwnership(SecretKey masterKey, boolean alice) {
|
||||
String label = alice ? ALICE_PROOF_LABEL : BOB_PROOF_LABEL;
|
||||
return crypto.mac(label, masterKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyOwnership(SecretKey masterKey, boolean alice,
|
||||
byte[] proof) {
|
||||
String label = alice ? ALICE_PROOF_LABEL : BOB_PROOF_LABEL;
|
||||
return crypto.verifyMac(proof, label, masterKey);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package org.briarproject.bramble.contact;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.Pair;
|
||||
import org.briarproject.bramble.api.Predicate;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.contact.HandshakeManager;
|
||||
import org.briarproject.bramble.api.contact.PendingContact;
|
||||
import org.briarproject.bramble.api.contact.PendingContactId;
|
||||
import org.briarproject.bramble.api.crypto.AgreementPublicKey;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.crypto.TransportCrypto;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.TransactionManager;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.record.Record;
|
||||
import org.briarproject.bramble.api.record.RecordReader;
|
||||
import org.briarproject.bramble.api.record.RecordReaderFactory;
|
||||
import org.briarproject.bramble.api.record.RecordWriter;
|
||||
import org.briarproject.bramble.api.record.RecordWriterFactory;
|
||||
import org.briarproject.bramble.api.transport.StreamWriter;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.PROOF_BYTES;
|
||||
import static org.briarproject.bramble.contact.HandshakeConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.contact.HandshakeRecordTypes.EPHEMERAL_PUBLIC_KEY;
|
||||
import static org.briarproject.bramble.contact.HandshakeRecordTypes.PROOF_OF_OWNERSHIP;
|
||||
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class HandshakeManagerImpl implements HandshakeManager {
|
||||
|
||||
// Ignore records with current protocol version, unknown record type
|
||||
private static final Predicate<Record> IGNORE = r ->
|
||||
r.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||
!isKnownRecordType(r.getRecordType());
|
||||
|
||||
private static boolean isKnownRecordType(byte type) {
|
||||
return type == EPHEMERAL_PUBLIC_KEY || type == PROOF_OF_OWNERSHIP;
|
||||
}
|
||||
|
||||
private final TransactionManager db;
|
||||
private final IdentityManager identityManager;
|
||||
private final ContactManager contactManager;
|
||||
private final TransportCrypto transportCrypto;
|
||||
private final HandshakeCrypto handshakeCrypto;
|
||||
private final RecordReaderFactory recordReaderFactory;
|
||||
private final RecordWriterFactory recordWriterFactory;
|
||||
|
||||
@Inject
|
||||
HandshakeManagerImpl(DatabaseComponent db,
|
||||
IdentityManager identityManager,
|
||||
ContactManager contactManager,
|
||||
TransportCrypto transportCrypto,
|
||||
HandshakeCrypto handshakeCrypto,
|
||||
RecordReaderFactory recordReaderFactory,
|
||||
RecordWriterFactory recordWriterFactory) {
|
||||
this.db = db;
|
||||
this.identityManager = identityManager;
|
||||
this.contactManager = contactManager;
|
||||
this.transportCrypto = transportCrypto;
|
||||
this.handshakeCrypto = handshakeCrypto;
|
||||
this.recordReaderFactory = recordReaderFactory;
|
||||
this.recordWriterFactory = recordWriterFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandshakeResult handshake(PendingContactId p, InputStream in,
|
||||
StreamWriter out) throws DbException, IOException {
|
||||
Pair<PublicKey, KeyPair> keys = db.transactionWithResult(true, txn -> {
|
||||
PendingContact pendingContact =
|
||||
contactManager.getPendingContact(txn, p);
|
||||
KeyPair keyPair = identityManager.getHandshakeKeys(txn);
|
||||
return new Pair<>(pendingContact.getPublicKey(), keyPair);
|
||||
});
|
||||
PublicKey theirStaticPublicKey = keys.getFirst();
|
||||
KeyPair ourStaticKeyPair = keys.getSecond();
|
||||
boolean alice = transportCrypto.isAlice(theirStaticPublicKey,
|
||||
ourStaticKeyPair);
|
||||
RecordReader recordReader = recordReaderFactory.createRecordReader(in);
|
||||
RecordWriter recordWriter = recordWriterFactory
|
||||
.createRecordWriter(out.getOutputStream());
|
||||
KeyPair ourEphemeralKeyPair =
|
||||
handshakeCrypto.generateEphemeralKeyPair();
|
||||
PublicKey theirEphemeralPublicKey;
|
||||
if (alice) {
|
||||
sendPublicKey(recordWriter, ourEphemeralKeyPair.getPublic());
|
||||
theirEphemeralPublicKey = receivePublicKey(recordReader);
|
||||
} else {
|
||||
theirEphemeralPublicKey = receivePublicKey(recordReader);
|
||||
sendPublicKey(recordWriter, ourEphemeralKeyPair.getPublic());
|
||||
}
|
||||
SecretKey masterKey;
|
||||
try {
|
||||
masterKey = handshakeCrypto.deriveMasterKey(theirStaticPublicKey,
|
||||
theirEphemeralPublicKey, ourStaticKeyPair,
|
||||
ourEphemeralKeyPair, alice);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new FormatException();
|
||||
}
|
||||
byte[] ourProof = handshakeCrypto.proveOwnership(masterKey, alice);
|
||||
byte[] theirProof;
|
||||
if (alice) {
|
||||
sendProof(recordWriter, ourProof);
|
||||
theirProof = receiveProof(recordReader);
|
||||
} else {
|
||||
theirProof = receiveProof(recordReader);
|
||||
sendProof(recordWriter, ourProof);
|
||||
}
|
||||
out.sendEndOfStream();
|
||||
recordReader.readRecord(r -> false, IGNORE);
|
||||
if (!handshakeCrypto.verifyOwnership(masterKey, !alice, theirProof))
|
||||
throw new FormatException();
|
||||
return new HandshakeResult(masterKey, alice);
|
||||
}
|
||||
|
||||
private void sendPublicKey(RecordWriter w, PublicKey k) throws IOException {
|
||||
w.writeRecord(new Record(PROTOCOL_VERSION, EPHEMERAL_PUBLIC_KEY,
|
||||
k.getEncoded()));
|
||||
w.flush();
|
||||
}
|
||||
|
||||
private PublicKey receivePublicKey(RecordReader r) throws IOException {
|
||||
byte[] key = readRecord(r, EPHEMERAL_PUBLIC_KEY).getPayload();
|
||||
checkLength(key, 1, MAX_AGREEMENT_PUBLIC_KEY_BYTES);
|
||||
return new AgreementPublicKey(key);
|
||||
}
|
||||
|
||||
private void sendProof(RecordWriter w, byte[] proof) throws IOException {
|
||||
w.writeRecord(new Record(PROTOCOL_VERSION, PROOF_OF_OWNERSHIP, proof));
|
||||
w.flush();
|
||||
}
|
||||
|
||||
private byte[] receiveProof(RecordReader r) throws IOException {
|
||||
byte[] proof = readRecord(r, PROOF_OF_OWNERSHIP).getPayload();
|
||||
checkLength(proof, PROOF_BYTES, PROOF_BYTES);
|
||||
return proof;
|
||||
}
|
||||
|
||||
private Record readRecord(RecordReader r, byte expectedType)
|
||||
throws IOException {
|
||||
// Accept records with current protocol version, expected type only
|
||||
Predicate<Record> accept = rec ->
|
||||
rec.getProtocolVersion() == PROTOCOL_VERSION &&
|
||||
rec.getRecordType() == expectedType;
|
||||
Record rec = r.readRecord(accept, IGNORE);
|
||||
if (rec == null) throw new EOFException();
|
||||
return rec;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.briarproject.bramble.contact;
|
||||
|
||||
/**
|
||||
* Record types for the handshake protocol.
|
||||
*/
|
||||
interface HandshakeRecordTypes {
|
||||
|
||||
byte EPHEMERAL_PUBLIC_KEY = 0;
|
||||
|
||||
byte PROOF_OF_OWNERSHIP = 1;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.bramble.contact;
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.UnsupportedVersionException;
|
||||
import org.briarproject.bramble.api.contact.PendingContact;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
|
||||
interface PendingContactFactory {
|
||||
|
||||
@@ -15,4 +16,9 @@ interface PendingContactFactory {
|
||||
*/
|
||||
PendingContact createPendingContact(String link, String alias)
|
||||
throws FormatException;
|
||||
|
||||
/**
|
||||
* Creates a handshake link from the given public key.
|
||||
*/
|
||||
String createHandshakeLink(PublicKey k);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.FORMAT
|
||||
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.ID_LABEL;
|
||||
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.LINK_REGEX;
|
||||
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.RAW_LINK_BYTES;
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_AGREEMENT;
|
||||
|
||||
class PendingContactFactoryImpl implements PendingContactFactory {
|
||||
|
||||
@@ -41,18 +42,31 @@ class PendingContactFactoryImpl implements PendingContactFactory {
|
||||
return new PendingContact(id, publicKey, alias, timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createHandshakeLink(PublicKey k) {
|
||||
if (!k.getKeyType().equals(KEY_TYPE_AGREEMENT))
|
||||
throw new IllegalArgumentException();
|
||||
byte[] encoded = k.getEncoded();
|
||||
if (encoded.length != RAW_LINK_BYTES - 1)
|
||||
throw new IllegalArgumentException();
|
||||
byte[] raw = new byte[RAW_LINK_BYTES];
|
||||
raw[0] = FORMAT_VERSION;
|
||||
arraycopy(encoded, 0, raw, 1, encoded.length);
|
||||
return "briar://" + Base32.encode(raw).toLowerCase();
|
||||
}
|
||||
|
||||
private PublicKey parseHandshakeLink(String link) throws FormatException {
|
||||
Matcher matcher = LINK_REGEX.matcher(link);
|
||||
if (!matcher.find()) throw new FormatException();
|
||||
// Discard 'briar://' and anything before or after the link
|
||||
link = matcher.group(2);
|
||||
byte[] base32 = Base32.decode(link, false);
|
||||
if (base32.length != RAW_LINK_BYTES) throw new AssertionError();
|
||||
byte version = base32[0];
|
||||
byte[] raw = Base32.decode(link, false);
|
||||
if (raw.length != RAW_LINK_BYTES) throw new AssertionError();
|
||||
byte version = raw[0];
|
||||
if (version != FORMAT_VERSION)
|
||||
throw new UnsupportedVersionException(version < FORMAT_VERSION);
|
||||
byte[] publicKeyBytes = new byte[base32.length - 1];
|
||||
arraycopy(base32, 1, publicKeyBytes, 0, publicKeyBytes.length);
|
||||
byte[] publicKeyBytes = new byte[raw.length - 1];
|
||||
arraycopy(raw, 1, publicKeyBytes, 0, publicKeyBytes.length);
|
||||
try {
|
||||
KeyParser parser = crypto.getAgreementKeyParser();
|
||||
return parser.parsePublicKey(publicKeyBytes);
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
package org.briarproject.bramble.contact;
|
||||
|
||||
import org.briarproject.bramble.api.Pair;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.contact.HandshakeManager.HandshakeResult;
|
||||
import org.briarproject.bramble.api.contact.PendingContact;
|
||||
import org.briarproject.bramble.api.contact.PendingContactState;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.identity.Identity;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestDatabaseConfigModule;
|
||||
import org.briarproject.bramble.test.TestDuplexTransportConnection;
|
||||
import org.briarproject.bramble.test.TestStreamWriter;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PipedInputStream;
|
||||
import java.io.PipedOutputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static junit.framework.TestCase.assertNotNull;
|
||||
import static junit.framework.TestCase.assertNull;
|
||||
import static junit.framework.TestCase.fail;
|
||||
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
|
||||
import static org.briarproject.bramble.test.TestDuplexTransportConnection.createPair;
|
||||
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
|
||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class ContactExchangeIntegrationTest extends BrambleTestCase {
|
||||
|
||||
private static final int TIMEOUT = 15_000;
|
||||
|
||||
private final File testDir = getTestDirectory();
|
||||
private final File aliceDir = new File(testDir, "alice");
|
||||
private final File bobDir = new File(testDir, "bob");
|
||||
private final SecretKey masterKey = getSecretKey();
|
||||
private final Random random = new Random();
|
||||
|
||||
private ContactExchangeIntegrationTestComponent alice, bob;
|
||||
private Identity aliceIdentity, bobIdentity;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
assertTrue(testDir.mkdirs());
|
||||
// Create the devices
|
||||
alice = DaggerContactExchangeIntegrationTestComponent.builder()
|
||||
.testDatabaseConfigModule(
|
||||
new TestDatabaseConfigModule(aliceDir)).build();
|
||||
alice.injectBrambleCoreEagerSingletons();
|
||||
bob = DaggerContactExchangeIntegrationTestComponent.builder()
|
||||
.testDatabaseConfigModule(new TestDatabaseConfigModule(bobDir))
|
||||
.build();
|
||||
bob.injectBrambleCoreEagerSingletons();
|
||||
// Set up the devices and get the identities
|
||||
aliceIdentity = setUp(alice, "Alice");
|
||||
bobIdentity = setUp(bob, "Bob");
|
||||
}
|
||||
|
||||
private Identity setUp(ContactExchangeIntegrationTestComponent device,
|
||||
String name) throws Exception {
|
||||
// Add an identity for the user
|
||||
IdentityManager identityManager = device.getIdentityManager();
|
||||
Identity identity = identityManager.createIdentity(name);
|
||||
identityManager.registerIdentity(identity);
|
||||
// Start the lifecycle manager
|
||||
LifecycleManager lifecycleManager = device.getLifecycleManager();
|
||||
lifecycleManager.startServices(getSecretKey());
|
||||
lifecycleManager.waitForStartup();
|
||||
// Check the initial conditions
|
||||
ContactManager contactManager = device.getContactManager();
|
||||
assertEquals(0, contactManager.getPendingContacts().size());
|
||||
assertEquals(0, contactManager.getContacts().size());
|
||||
return identity;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExchangeContacts() throws Exception {
|
||||
TestDuplexTransportConnection[] pair = createPair();
|
||||
TestDuplexTransportConnection aliceConnection = pair[0];
|
||||
TestDuplexTransportConnection bobConnection = pair[1];
|
||||
CountDownLatch aliceFinished = new CountDownLatch(1);
|
||||
CountDownLatch bobFinished = new CountDownLatch(1);
|
||||
boolean verified = random.nextBoolean();
|
||||
|
||||
alice.getIoExecutor().execute(() -> {
|
||||
try {
|
||||
alice.getContactExchangeManager().exchangeContacts(
|
||||
aliceConnection, masterKey, true, verified);
|
||||
aliceFinished.countDown();
|
||||
} catch (Exception e) {
|
||||
fail();
|
||||
}
|
||||
});
|
||||
bob.getIoExecutor().execute(() -> {
|
||||
try {
|
||||
bob.getContactExchangeManager().exchangeContacts(bobConnection,
|
||||
masterKey, false, verified);
|
||||
bobFinished.countDown();
|
||||
} catch (Exception e) {
|
||||
fail();
|
||||
}
|
||||
});
|
||||
aliceFinished.await(TIMEOUT, MILLISECONDS);
|
||||
bobFinished.await(TIMEOUT, MILLISECONDS);
|
||||
assertContacts(verified, false);
|
||||
assertNoPendingContacts();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExchangeContactsFromPendingContacts() throws Exception {
|
||||
PendingContact bobFromAlice = addPendingContact(alice, bob);
|
||||
PendingContact aliceFromBob = addPendingContact(bob, alice);
|
||||
assertPendingContacts();
|
||||
|
||||
TestDuplexTransportConnection[] pair = createPair();
|
||||
TestDuplexTransportConnection aliceConnection = pair[0];
|
||||
TestDuplexTransportConnection bobConnection = pair[1];
|
||||
CountDownLatch aliceFinished = new CountDownLatch(1);
|
||||
CountDownLatch bobFinished = new CountDownLatch(1);
|
||||
boolean verified = random.nextBoolean();
|
||||
|
||||
alice.getIoExecutor().execute(() -> {
|
||||
try {
|
||||
alice.getContactExchangeManager().exchangeContacts(
|
||||
bobFromAlice.getId(), aliceConnection, masterKey, true,
|
||||
verified);
|
||||
aliceFinished.countDown();
|
||||
} catch (Exception e) {
|
||||
fail();
|
||||
}
|
||||
});
|
||||
bob.getIoExecutor().execute(() -> {
|
||||
try {
|
||||
bob.getContactExchangeManager().exchangeContacts(
|
||||
aliceFromBob.getId(), bobConnection, masterKey, false,
|
||||
verified);
|
||||
bobFinished.countDown();
|
||||
} catch (Exception e) {
|
||||
fail();
|
||||
}
|
||||
});
|
||||
aliceFinished.await(TIMEOUT, MILLISECONDS);
|
||||
bobFinished.await(TIMEOUT, MILLISECONDS);
|
||||
assertContacts(verified, true);
|
||||
assertNoPendingContacts();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandshakeAndExchangeContactsFromPendingContacts()
|
||||
throws Exception {
|
||||
PendingContact bobFromAlice = addPendingContact(alice, bob);
|
||||
PendingContact aliceFromBob = addPendingContact(bob, alice);
|
||||
assertPendingContacts();
|
||||
|
||||
PipedInputStream aliceHandshakeIn = new PipedInputStream();
|
||||
PipedInputStream bobHandshakeIn = new PipedInputStream();
|
||||
OutputStream aliceHandshakeOut = new PipedOutputStream(bobHandshakeIn);
|
||||
OutputStream bobHandshakeOut = new PipedOutputStream(aliceHandshakeIn);
|
||||
AtomicReference<HandshakeResult> aliceResult = new AtomicReference<>();
|
||||
AtomicReference<HandshakeResult> bobResult = new AtomicReference<>();
|
||||
|
||||
TestDuplexTransportConnection[] pair = createPair();
|
||||
TestDuplexTransportConnection aliceConnection = pair[0];
|
||||
TestDuplexTransportConnection bobConnection = pair[1];
|
||||
CountDownLatch aliceFinished = new CountDownLatch(1);
|
||||
CountDownLatch bobFinished = new CountDownLatch(1);
|
||||
boolean verified = random.nextBoolean();
|
||||
|
||||
alice.getIoExecutor().execute(() -> {
|
||||
try {
|
||||
HandshakeResult result = alice.getHandshakeManager().handshake(
|
||||
bobFromAlice.getId(), aliceHandshakeIn,
|
||||
new TestStreamWriter(aliceHandshakeOut));
|
||||
aliceResult.set(result);
|
||||
alice.getContactExchangeManager().exchangeContacts(
|
||||
bobFromAlice.getId(), aliceConnection,
|
||||
result.getMasterKey(), result.isAlice(), verified);
|
||||
aliceFinished.countDown();
|
||||
} catch (Exception e) {
|
||||
fail();
|
||||
}
|
||||
});
|
||||
bob.getIoExecutor().execute(() -> {
|
||||
try {
|
||||
HandshakeResult result = bob.getHandshakeManager().handshake(
|
||||
aliceFromBob.getId(), bobHandshakeIn,
|
||||
new TestStreamWriter(bobHandshakeOut));
|
||||
bobResult.set(result);
|
||||
bob.getContactExchangeManager().exchangeContacts(
|
||||
aliceFromBob.getId(), bobConnection,
|
||||
result.getMasterKey(), result.isAlice(), verified);
|
||||
bobFinished.countDown();
|
||||
} catch (Exception e) {
|
||||
fail();
|
||||
}
|
||||
});
|
||||
aliceFinished.await(TIMEOUT, MILLISECONDS);
|
||||
bobFinished.await(TIMEOUT, MILLISECONDS);
|
||||
assertArrayEquals(aliceResult.get().getMasterKey().getBytes(),
|
||||
bobResult.get().getMasterKey().getBytes());
|
||||
assertNotEquals(aliceResult.get().isAlice(), bobResult.get().isAlice());
|
||||
assertContacts(verified, true);
|
||||
assertNoPendingContacts();
|
||||
}
|
||||
|
||||
private PendingContact addPendingContact(
|
||||
ContactExchangeIntegrationTestComponent local,
|
||||
ContactExchangeIntegrationTestComponent remote) throws Exception {
|
||||
String link = remote.getContactManager().getHandshakeLink();
|
||||
String alias = remote.getIdentityManager().getLocalAuthor().getName();
|
||||
return local.getContactManager().addPendingContact(link, alias);
|
||||
}
|
||||
|
||||
private void assertContacts(boolean verified,
|
||||
boolean withHandshakeKeys) throws Exception {
|
||||
assertContact(alice, bobIdentity, verified, withHandshakeKeys);
|
||||
assertContact(bob, aliceIdentity, verified, withHandshakeKeys);
|
||||
}
|
||||
|
||||
private void assertContact(ContactExchangeIntegrationTestComponent local,
|
||||
Identity expectedIdentity, boolean verified,
|
||||
boolean withHandshakeKey) throws Exception {
|
||||
Collection<Contact> contacts = local.getContactManager().getContacts();
|
||||
assertEquals(1, contacts.size());
|
||||
Contact contact = contacts.iterator().next();
|
||||
assertEquals(expectedIdentity.getLocalAuthor(), contact.getAuthor());
|
||||
assertEquals(verified, contact.isVerified());
|
||||
PublicKey expectedPublicKey = expectedIdentity.getHandshakePublicKey();
|
||||
PublicKey actualPublicKey = contact.getHandshakePublicKey();
|
||||
assertNotNull(expectedPublicKey);
|
||||
if (withHandshakeKey) {
|
||||
assertNotNull(actualPublicKey);
|
||||
assertArrayEquals(expectedPublicKey.getEncoded(),
|
||||
actualPublicKey.getEncoded());
|
||||
} else {
|
||||
assertNull(actualPublicKey);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertNoPendingContacts() throws Exception {
|
||||
assertEquals(0, alice.getContactManager().getPendingContacts().size());
|
||||
assertEquals(0, bob.getContactManager().getPendingContacts().size());
|
||||
}
|
||||
|
||||
private void assertPendingContacts() throws Exception {
|
||||
assertPendingContact(alice, bobIdentity);
|
||||
assertPendingContact(bob, aliceIdentity);
|
||||
}
|
||||
|
||||
private void assertPendingContact(
|
||||
ContactExchangeIntegrationTestComponent local,
|
||||
Identity expectedIdentity) throws Exception {
|
||||
Collection<Pair<PendingContact, PendingContactState>> pairs =
|
||||
local.getContactManager().getPendingContacts();
|
||||
assertEquals(1, pairs.size());
|
||||
Pair<PendingContact, PendingContactState> pair =
|
||||
pairs.iterator().next();
|
||||
assertEquals(WAITING_FOR_CONNECTION, pair.getSecond());
|
||||
PendingContact pendingContact = pair.getFirst();
|
||||
assertEquals(expectedIdentity.getLocalAuthor().getName(),
|
||||
pendingContact.getAlias());
|
||||
PublicKey expectedPublicKey = expectedIdentity.getHandshakePublicKey();
|
||||
assertNotNull(expectedPublicKey);
|
||||
assertArrayEquals(expectedPublicKey.getEncoded(),
|
||||
pendingContact.getPublicKey().getEncoded());
|
||||
}
|
||||
|
||||
private void tearDown(ContactExchangeIntegrationTestComponent device)
|
||||
throws Exception {
|
||||
// Stop the lifecycle manager
|
||||
LifecycleManager lifecycleManager = device.getLifecycleManager();
|
||||
lifecycleManager.stopServices();
|
||||
lifecycleManager.waitForShutdown();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
tearDown(alice);
|
||||
tearDown(bob);
|
||||
deleteTestDirectory(testDir);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package org.briarproject.bramble.contact;
|
||||
|
||||
import org.briarproject.bramble.BrambleCoreEagerSingletons;
|
||||
import org.briarproject.bramble.BrambleCoreModule;
|
||||
import org.briarproject.bramble.api.contact.ContactExchangeManager;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.contact.HandshakeManager;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.test.BrambleCoreIntegrationTestModule;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Component;
|
||||
|
||||
@Singleton
|
||||
@Component(modules = {
|
||||
BrambleCoreIntegrationTestModule.class,
|
||||
BrambleCoreModule.class
|
||||
})
|
||||
interface ContactExchangeIntegrationTestComponent
|
||||
extends BrambleCoreEagerSingletons {
|
||||
|
||||
ContactExchangeManager getContactExchangeManager();
|
||||
|
||||
ContactManager getContactManager();
|
||||
|
||||
HandshakeManager getHandshakeManager();
|
||||
|
||||
IdentityManager getIdentityManager();
|
||||
|
||||
@IoExecutor
|
||||
Executor getIoExecutor();
|
||||
|
||||
LifecycleManager getLifecycleManager();
|
||||
}
|
||||
@@ -3,6 +3,9 @@ package org.briarproject.bramble.contact;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
@@ -17,7 +20,6 @@ import org.briarproject.bramble.api.transport.KeyManager;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.DbExpectations;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collection;
|
||||
@@ -25,16 +27,20 @@ import java.util.Random;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.BASE32_LINK_BYTES;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
|
||||
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNKNOWN;
|
||||
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNVERIFIED;
|
||||
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.VERIFIED;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAgreementPrivateKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAuthor;
|
||||
import static org.briarproject.bramble.test.TestUtils.getContact;
|
||||
import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomBase32String;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
@@ -42,7 +48,6 @@ import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class ContactManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
private final Mockery context = new Mockery();
|
||||
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
private final KeyManager keyManager = context.mock(KeyManager.class);
|
||||
private final IdentityManager identityManager =
|
||||
@@ -196,7 +201,6 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
||||
oneOf(identityManager).getLocalAuthor(txn);
|
||||
will(returnValue(localAuthor));
|
||||
oneOf(db).getContactsByAuthorId(txn, remote.getId());
|
||||
@@ -258,4 +262,22 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetHandshakeLink() throws Exception {
|
||||
Transaction txn = new Transaction(null, true);
|
||||
PublicKey publicKey = getAgreementPublicKey();
|
||||
PrivateKey privateKey = getAgreementPrivateKey();
|
||||
KeyPair keyPair = new KeyPair(publicKey, privateKey);
|
||||
String link = "briar://" + getRandomBase32String(BASE32_LINK_BYTES);
|
||||
|
||||
context.checking(new DbExpectations() {{
|
||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
||||
oneOf(identityManager).getHandshakeKeys(txn);
|
||||
will(returnValue(keyPair));
|
||||
oneOf(pendingContactFactory).createHandshakeLink(publicKey);
|
||||
will(returnValue(link));
|
||||
}});
|
||||
|
||||
assertEquals(link, contactManager.getHandshakeLink());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,12 @@ import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.BASE32
|
||||
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.FORMAT_VERSION;
|
||||
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.ID_LABEL;
|
||||
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.RAW_LINK_BYTES;
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_AGREEMENT;
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_SIGNATURE;
|
||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_AGREEMENT_PUBLIC_KEY_BYTES;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
import static org.briarproject.bramble.test.TestUtils.getAgreementPublicKey;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
@@ -110,6 +114,57 @@ public class PendingContactFactoryImplTest extends BrambleMockTestCase {
|
||||
assertEquals(timestamp, p.getTimestamp());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testCreateHandshakeLinkRejectsInvalidKeyType() {
|
||||
PublicKey invalidPublicKey = context.mock(PublicKey.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(invalidPublicKey).getKeyType();
|
||||
will(returnValue(KEY_TYPE_SIGNATURE));
|
||||
}});
|
||||
|
||||
pendingContactFactory.createHandshakeLink(invalidPublicKey);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testCreateHandshakeLinkRejectsInvalidKeyLength() {
|
||||
PublicKey invalidPublicKey = context.mock(PublicKey.class);
|
||||
byte[] invalidPublicKeyBytes =
|
||||
getRandomBytes(MAX_AGREEMENT_PUBLIC_KEY_BYTES + 1);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(invalidPublicKey).getKeyType();
|
||||
will(returnValue(KEY_TYPE_AGREEMENT));
|
||||
oneOf(invalidPublicKey).getEncoded();
|
||||
will(returnValue(invalidPublicKeyBytes));
|
||||
}});
|
||||
|
||||
pendingContactFactory.createHandshakeLink(invalidPublicKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateAndParseLink() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(crypto).getAgreementKeyParser();
|
||||
will(returnValue(keyParser));
|
||||
oneOf(keyParser).parsePublicKey(publicKey.getEncoded());
|
||||
will(returnValue(publicKey));
|
||||
oneOf(crypto).hash(ID_LABEL, publicKey.getEncoded());
|
||||
will(returnValue(idBytes));
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(timestamp));
|
||||
}});
|
||||
|
||||
String link = pendingContactFactory.createHandshakeLink(publicKey);
|
||||
PendingContact p =
|
||||
pendingContactFactory.createPendingContact(link, alias);
|
||||
assertArrayEquals(idBytes, p.getId().getBytes());
|
||||
assertArrayEquals(publicKey.getEncoded(),
|
||||
p.getPublicKey().getEncoded());
|
||||
assertEquals(alias, p.getAlias());
|
||||
assertEquals(timestamp, p.getTimestamp());
|
||||
}
|
||||
|
||||
private String encodeLink() {
|
||||
return encodeLink(FORMAT_VERSION);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package org.briarproject.briar.test;
|
||||
package org.briarproject.bramble.test;
|
||||
|
||||
import org.briarproject.bramble.api.transport.StreamWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
class TestStreamWriter implements StreamWriter {
|
||||
public class TestStreamWriter implements StreamWriter {
|
||||
|
||||
private final OutputStream out;
|
||||
|
||||
TestStreamWriter(OutputStream out) {
|
||||
public TestStreamWriter(OutputStream out) {
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
@@ -21,5 +21,6 @@ class TestStreamWriter implements StreamWriter {
|
||||
@Override
|
||||
public void sendEndOfStream() throws IOException {
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@ public class AddContactViewModel extends AndroidViewModel {
|
||||
handshakeLink.postValue(contactManager.getHandshakeLink());
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
// the UI should stay disable in this case,
|
||||
// the UI should stay disabled in this case,
|
||||
// leaving the user unable to proceed
|
||||
}
|
||||
});
|
||||
|
||||
@@ -58,8 +58,8 @@ class ContactExchangeViewModel extends AndroidViewModel {
|
||||
SecretKey masterKey, boolean alice) {
|
||||
ioExecutor.execute(() -> {
|
||||
try {
|
||||
Contact contact = contactExchangeManager.exchangeContacts(t,
|
||||
conn, masterKey, alice);
|
||||
Contact contact = contactExchangeManager.exchangeContacts(conn,
|
||||
masterKey, alice, true);
|
||||
// Reuse the connection as a transport connection
|
||||
connectionManager.manageOutgoingConnection(contact.getId(),
|
||||
t, conn);
|
||||
|
||||
Reference in New Issue
Block a user