mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-20 22:59:54 +01:00
Merge branch 'contact-exchange-refactoring' into 'master'
Contact exchange refactoring See merge request briar/briar!1106
This commit is contained in:
@@ -0,0 +1,24 @@
|
|||||||
|
package org.briarproject.bramble.api.contact;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.bramble.api.db.ContactExistsException;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public interface ContactExchangeManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exchanges contact information with a remote peer.
|
||||||
|
*
|
||||||
|
* @param alice Whether the local peer takes the role of Alice
|
||||||
|
* @return The newly added contact
|
||||||
|
* @throws ContactExistsException If the contact already exists
|
||||||
|
*/
|
||||||
|
Contact exchangeContacts(TransportId t, DuplexTransportConnection conn,
|
||||||
|
SecretKey masterKey, boolean alice) throws IOException, DbException;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
package org.briarproject.bramble.api.contact.event;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.event.Event;
|
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
public class ContactExchangeFailedEvent extends Event {
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private final Author duplicateRemoteAuthor;
|
|
||||||
|
|
||||||
public ContactExchangeFailedEvent(@Nullable Author duplicateRemoteAuthor) {
|
|
||||||
this.duplicateRemoteAuthor = duplicateRemoteAuthor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ContactExchangeFailedEvent() {
|
|
||||||
this(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public Author getDuplicateRemoteAuthor() {
|
|
||||||
return duplicateRemoteAuthor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean wasDuplicateContact() {
|
|
||||||
return duplicateRemoteAuthor != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package org.briarproject.bramble.api.contact.event;
|
|
||||||
|
|
||||||
import org.briarproject.bramble.api.event.Event;
|
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
|
|
||||||
@NotNullByDefault
|
|
||||||
public class ContactExchangeSucceededEvent extends Event {
|
|
||||||
|
|
||||||
private final Author remoteAuthor;
|
|
||||||
|
|
||||||
public ContactExchangeSucceededEvent(Author remoteAuthor) {
|
|
||||||
this.remoteAuthor = remoteAuthor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Author getRemoteAuthor() {
|
|
||||||
return remoteAuthor;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,27 @@
|
|||||||
package org.briarproject.bramble.api.db;
|
package org.briarproject.bramble.api.db;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thrown when a duplicate contact is added to the database. This exception may
|
* Thrown when a duplicate contact is added to the database. This exception may
|
||||||
* occur due to concurrent updates and does not indicate a database error.
|
* occur due to concurrent updates and does not indicate a database error.
|
||||||
*/
|
*/
|
||||||
public class ContactExistsException extends DbException {
|
public class ContactExistsException extends DbException {
|
||||||
|
|
||||||
|
private final AuthorId local;
|
||||||
|
private final Author remote;
|
||||||
|
|
||||||
|
public ContactExistsException(AuthorId local, Author remote) {
|
||||||
|
this.local = local;
|
||||||
|
this.remote = remote;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthorId getLocalAuthorId() {
|
||||||
|
return local;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Author getRemoteAuthor() {
|
||||||
|
return remote;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,6 @@
|
|||||||
package org.briarproject.bramble.api.contact;
|
package org.briarproject.bramble.contact;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
interface ContactExchangeConstants {
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
|
||||||
import org.briarproject.bramble.api.plugin.TransportId;
|
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A task for conducting a contact information exchange with a remote peer.
|
|
||||||
*/
|
|
||||||
@NotNullByDefault
|
|
||||||
public interface ContactExchangeTask {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current version of the contact exchange protocol.
|
* The current version of the contact exchange protocol.
|
||||||
@@ -39,9 +29,7 @@ public interface ContactExchangeTask {
|
|||||||
String BOB_NONCE_LABEL = "org.briarproject.bramble.contact/BOB_NONCE";
|
String BOB_NONCE_LABEL = "org.briarproject.bramble.contact/BOB_NONCE";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exchanges contact information with a remote peer.
|
* Label for signing key binding nonces.
|
||||||
*/
|
*/
|
||||||
void startExchange(LocalAuthor localAuthor, SecretKey masterKey,
|
String SIGNING_LABEL = "org.briarproject.briar.contact/EXCHANGE";
|
||||||
DuplexTransportConnection conn, TransportId transportId,
|
|
||||||
boolean alice);
|
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,24 +3,21 @@ 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.ContactExchangeTask;
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactExchangeManager;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
import org.briarproject.bramble.api.contact.event.ContactExchangeFailedEvent;
|
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||||
import org.briarproject.bramble.api.contact.event.ContactExchangeSucceededEvent;
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
|
||||||
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;
|
||||||
import org.briarproject.bramble.api.db.ContactExistsException;
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.event.EventBus;
|
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
|
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;
|
||||||
@@ -39,28 +36,24 @@ 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;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.api.contact.RecordTypes.CONTACT_INFO;
|
import static org.briarproject.bramble.api.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.util.LogUtils.logException;
|
import static org.briarproject.bramble.contact.ContactExchangeConstants.PROTOCOL_VERSION;
|
||||||
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
|
||||||
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
class ContactExchangeManagerImpl implements ContactExchangeManager {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(ContactExchangeTaskImpl.class.getName());
|
getLogger(ContactExchangeManagerImpl.class.getName());
|
||||||
|
|
||||||
private static final String SIGNING_LABEL_EXCHANGE =
|
|
||||||
"org.briarproject.briar.contact/EXCHANGE";
|
|
||||||
|
|
||||||
// Accept records with current protocol version, known record type
|
// Accept records with current protocol version, known record type
|
||||||
private static final Predicate<Record> ACCEPT = r ->
|
private static final Predicate<Record> ACCEPT = r ->
|
||||||
@@ -80,190 +73,108 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
private final ClientHelper clientHelper;
|
private final ClientHelper clientHelper;
|
||||||
private final RecordReaderFactory recordReaderFactory;
|
private final RecordReaderFactory recordReaderFactory;
|
||||||
private final RecordWriterFactory recordWriterFactory;
|
private final RecordWriterFactory recordWriterFactory;
|
||||||
private final EventBus eventBus;
|
|
||||||
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 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;
|
||||||
|
|
||||||
private volatile LocalAuthor localAuthor;
|
|
||||||
private volatile DuplexTransportConnection conn;
|
|
||||||
private volatile TransportId transportId;
|
|
||||||
private volatile SecretKey masterKey;
|
|
||||||
private volatile boolean alice;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ContactExchangeTaskImpl(DatabaseComponent db, ClientHelper clientHelper,
|
ContactExchangeManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
|
||||||
RecordReaderFactory recordReaderFactory,
|
RecordReaderFactory recordReaderFactory,
|
||||||
RecordWriterFactory recordWriterFactory, EventBus eventBus,
|
RecordWriterFactory recordWriterFactory, Clock clock,
|
||||||
Clock clock, ConnectionManager connectionManager,
|
ContactManager contactManager, IdentityManager identityManager,
|
||||||
ContactManager contactManager,
|
|
||||||
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.eventBus = eventBus;
|
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
this.connectionManager = connectionManager;
|
|
||||||
this.contactManager = contactManager;
|
this.contactManager = contactManager;
|
||||||
|
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 void startExchange(LocalAuthor localAuthor, SecretKey masterKey,
|
public Contact exchangeContacts(TransportId t,
|
||||||
DuplexTransportConnection conn, TransportId transportId,
|
DuplexTransportConnection conn, SecretKey masterKey, boolean alice)
|
||||||
boolean alice) {
|
throws IOException, DbException {
|
||||||
this.localAuthor = localAuthor;
|
|
||||||
this.conn = conn;
|
|
||||||
this.transportId = transportId;
|
|
||||||
this.masterKey = masterKey;
|
|
||||||
this.alice = alice;
|
|
||||||
start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
// Get the transport connection's input and output streams
|
// Get the transport connection's input and output streams
|
||||||
InputStream in;
|
InputStream in = conn.getReader().getInputStream();
|
||||||
OutputStream out;
|
OutputStream out = conn.getWriter().getOutputStream();
|
||||||
try {
|
|
||||||
in = conn.getReader().getInputStream();
|
|
||||||
out = conn.getWriter().getOutputStream();
|
|
||||||
} catch (IOException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
tryToClose(conn);
|
|
||||||
eventBus.broadcast(new ContactExchangeFailedEvent());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the local transport properties
|
// Get the local author and transport properties
|
||||||
Map<TransportId, TransportProperties> localProperties;
|
LocalAuthor localAuthor = identityManager.getLocalAuthor();
|
||||||
try {
|
Map<TransportId, TransportProperties> localProperties =
|
||||||
localProperties = transportPropertyManager.getLocalProperties();
|
transportPropertyManager.getLocalProperties();
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
eventBus.broadcast(new ContactExchangeFailedEvent());
|
|
||||||
tryToClose(conn);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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();
|
||||||
ContactInfo remoteInfo;
|
ContactInfo remoteInfo;
|
||||||
try {
|
if (alice) {
|
||||||
if (alice) {
|
sendContactInfo(recordWriter, localAuthor, localProperties,
|
||||||
sendContactInfo(recordWriter, localAuthor, localProperties,
|
localSignature, localTimestamp);
|
||||||
localSignature, localTimestamp);
|
remoteInfo = receiveContactInfo(recordReader);
|
||||||
recordWriter.flush();
|
} else {
|
||||||
remoteInfo = receiveContactInfo(recordReader);
|
remoteInfo = receiveContactInfo(recordReader);
|
||||||
} else {
|
sendContactInfo(recordWriter, localAuthor, localProperties,
|
||||||
remoteInfo = receiveContactInfo(recordReader);
|
localSignature, localTimestamp);
|
||||||
sendContactInfo(recordWriter, localAuthor, localProperties,
|
|
||||||
localSignature, localTimestamp);
|
|
||||||
recordWriter.flush();
|
|
||||||
}
|
|
||||||
// Send EOF on the outgoing stream
|
|
||||||
streamWriter.sendEndOfStream();
|
|
||||||
// Skip any remaining records from the incoming stream
|
|
||||||
recordReader.readRecord(r -> false, IGNORE);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
eventBus.broadcast(new ContactExchangeFailedEvent());
|
|
||||||
tryToClose(conn);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
// 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");
|
||||||
eventBus.broadcast(new ContactExchangeFailedEvent());
|
throw new FormatException();
|
||||||
tryToClose(conn);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The agreed timestamp is the minimum of the peers' timestamps
|
// The agreed timestamp is the minimum of the peers' timestamps
|
||||||
long timestamp = Math.min(localTimestamp, remoteInfo.timestamp);
|
long timestamp = Math.min(localTimestamp, remoteInfo.timestamp);
|
||||||
|
|
||||||
try {
|
// Add the contact
|
||||||
// Add the contact
|
Contact contact = addContact(remoteInfo.author, localAuthor,
|
||||||
ContactId contactId = addContact(remoteInfo.author, timestamp,
|
masterKey, timestamp, alice, remoteInfo.properties);
|
||||||
remoteInfo.properties);
|
|
||||||
// Reuse the connection as a transport connection
|
|
||||||
connectionManager.manageOutgoingConnection(contactId, transportId,
|
|
||||||
conn);
|
|
||||||
// Pseudonym exchange succeeded
|
|
||||||
LOG.info("Pseudonym exchange succeeded");
|
|
||||||
eventBus.broadcast(
|
|
||||||
new ContactExchangeSucceededEvent(remoteInfo.author));
|
|
||||||
} catch (ContactExistsException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
tryToClose(conn);
|
|
||||||
eventBus.broadcast(
|
|
||||||
new ContactExchangeFailedEvent(remoteInfo.author));
|
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
tryToClose(conn);
|
|
||||||
eventBus.broadcast(new ContactExchangeFailedEvent());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] sign(LocalAuthor author, byte[] nonce) {
|
// Contact exchange succeeded
|
||||||
try {
|
LOG.info("Contact exchange succeeded");
|
||||||
return crypto.sign(SIGNING_LABEL_EXCHANGE, nonce,
|
return contact;
|
||||||
author.getPrivateKey());
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
throw new AssertionError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean verify(Author author, byte[] nonce, byte[] signature) {
|
|
||||||
try {
|
|
||||||
return crypto.verifySignature(signature, SIGNING_LABEL_EXCHANGE,
|
|
||||||
nonce, author.getPublicKey());
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendContactInfo(RecordWriter recordWriter, Author author,
|
private void sendContactInfo(RecordWriter recordWriter, Author author,
|
||||||
@@ -274,6 +185,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
BdfList payload = BdfList.of(authorList, props, signature, timestamp);
|
BdfList payload = BdfList.of(authorList, props, signature, timestamp);
|
||||||
recordWriter.writeRecord(new Record(PROTOCOL_VERSION, CONTACT_INFO,
|
recordWriter.writeRecord(new Record(PROTOCOL_VERSION, CONTACT_INFO,
|
||||||
clientHelper.toByteArray(payload)));
|
clientHelper.toByteArray(payload)));
|
||||||
|
recordWriter.flush();
|
||||||
LOG.info("Sent contact info");
|
LOG.info("Sent contact info");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,7 +207,8 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
return new ContactInfo(author, properties, signature, timestamp);
|
return new ContactInfo(author, properties, signature, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ContactId addContact(Author remoteAuthor, long timestamp,
|
private Contact addContact(Author remoteAuthor, LocalAuthor localAuthor,
|
||||||
|
SecretKey masterKey, long timestamp, boolean alice,
|
||||||
Map<TransportId, TransportProperties> remoteProperties)
|
Map<TransportId, TransportProperties> remoteProperties)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
return db.transactionWithResult(false, txn -> {
|
return db.transactionWithResult(false, txn -> {
|
||||||
@@ -304,20 +217,10 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
|
|||||||
true, true);
|
true, true);
|
||||||
transportPropertyManager.addRemoteProperties(txn, contactId,
|
transportPropertyManager.addRemoteProperties(txn, contactId,
|
||||||
remoteProperties);
|
remoteProperties);
|
||||||
return contactId;
|
return contactManager.getContact(txn, contactId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tryToClose(DuplexTransportConnection conn) {
|
|
||||||
try {
|
|
||||||
LOG.info("Closing connection");
|
|
||||||
conn.getReader().dispose(true, true);
|
|
||||||
conn.getWriter().dispose(true);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ContactInfo {
|
private static class ContactInfo {
|
||||||
|
|
||||||
private final Author author;
|
private final Author author;
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package org.briarproject.bramble.contact;
|
package org.briarproject.bramble.contact;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactExchangeTask;
|
import org.briarproject.bramble.api.contact.ContactExchangeManager;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -19,14 +19,14 @@ public class ContactModule {
|
|||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
ContactManager getContactManager(ContactManagerImpl contactManager) {
|
ContactManager provideContactManager(ContactManagerImpl contactManager) {
|
||||||
return contactManager;
|
return contactManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
ContactExchangeTask provideContactExchangeTask(
|
ContactExchangeManager provideContactExchangeManager(
|
||||||
ContactExchangeTaskImpl contactExchangeTask) {
|
ContactExchangeManagerImpl contactExchangeManager) {
|
||||||
return contactExchangeTask;
|
return contactExchangeManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@@ -34,4 +34,10 @@ public class ContactModule {
|
|||||||
PendingContactFactoryImpl pendingContactFactory) {
|
PendingContactFactoryImpl pendingContactFactory) {
|
||||||
return pendingContactFactory;
|
return pendingContactFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
ContactExchangeCrypto provideContactExchangeCrypto(
|
||||||
|
ContactExchangeCryptoImpl contactExchangeCrypto) {
|
||||||
|
return contactExchangeCrypto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -234,16 +234,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ContactId addContact(Transaction transaction, Author remote,
|
public ContactId addContact(Transaction transaction, Author remote,
|
||||||
AuthorId local, boolean verified)
|
AuthorId local, boolean verified) throws DbException {
|
||||||
throws DbException {
|
|
||||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
if (!db.containsIdentity(txn, local))
|
if (!db.containsIdentity(txn, local))
|
||||||
throw new NoSuchIdentityException();
|
throw new NoSuchIdentityException();
|
||||||
if (db.containsIdentity(txn, remote.getId()))
|
if (db.containsIdentity(txn, remote.getId()))
|
||||||
throw new ContactExistsException();
|
throw new ContactExistsException(local, remote);
|
||||||
if (db.containsContact(txn, remote.getId(), local))
|
if (db.containsContact(txn, remote.getId(), local))
|
||||||
throw new ContactExistsException();
|
throw new ContactExistsException(local, remote);
|
||||||
ContactId c = db.addContact(txn, remote, local, verified);
|
ContactId c = db.addContact(txn, remote, local, verified);
|
||||||
transaction.attach(new ContactAddedEvent(c));
|
transaction.attach(new ContactAddedEvent(c));
|
||||||
return c;
|
return c;
|
||||||
|
|||||||
@@ -1459,7 +1459,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
true));
|
true));
|
||||||
fail();
|
fail();
|
||||||
} catch (ContactExistsException expected) {
|
} catch (ContactExistsException expected) {
|
||||||
// Expected
|
assertEquals(localAuthor.getId(), expected.getLocalAuthorId());
|
||||||
|
assertEquals(author, expected.getRemoteAuthor());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1488,7 +1489,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
true));
|
true));
|
||||||
fail();
|
fail();
|
||||||
} catch (ContactExistsException expected) {
|
} catch (ContactExistsException expected) {
|
||||||
// Expected
|
assertEquals(localAuthor.getId(), expected.getLocalAuthorId());
|
||||||
|
assertEquals(author, expected.getRemoteAuthor());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import org.briarproject.bramble.BrambleCoreEagerSingletons;
|
|||||||
import org.briarproject.bramble.BrambleCoreModule;
|
import org.briarproject.bramble.BrambleCoreModule;
|
||||||
import org.briarproject.bramble.account.BriarAccountModule;
|
import org.briarproject.bramble.account.BriarAccountModule;
|
||||||
import org.briarproject.bramble.api.account.AccountManager;
|
import org.briarproject.bramble.api.account.AccountManager;
|
||||||
import org.briarproject.bramble.api.contact.ContactExchangeTask;
|
import org.briarproject.bramble.api.contact.ContactExchangeManager;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
||||||
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
|
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
|
||||||
@@ -128,7 +128,7 @@ public interface AndroidComponent
|
|||||||
|
|
||||||
SettingsManager settingsManager();
|
SettingsManager settingsManager();
|
||||||
|
|
||||||
ContactExchangeTask contactExchangeTask();
|
ContactExchangeManager contactExchangeManager();
|
||||||
|
|
||||||
KeyAgreementTask keyAgreementTask();
|
KeyAgreementTask keyAgreementTask();
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import org.briarproject.bramble.plugin.tor.CircumventionProvider;
|
|||||||
import org.briarproject.bramble.util.AndroidUtils;
|
import org.briarproject.bramble.util.AndroidUtils;
|
||||||
import org.briarproject.bramble.util.StringUtils;
|
import org.briarproject.bramble.util.StringUtils;
|
||||||
import org.briarproject.briar.android.account.LockManagerImpl;
|
import org.briarproject.briar.android.account.LockManagerImpl;
|
||||||
|
import org.briarproject.briar.android.keyagreement.ContactExchangeModule;
|
||||||
import org.briarproject.briar.android.viewmodel.ViewModelModule;
|
import org.briarproject.briar.android.viewmodel.ViewModelModule;
|
||||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||||
import org.briarproject.briar.api.android.DozeWatchdog;
|
import org.briarproject.briar.api.android.DozeWatchdog;
|
||||||
@@ -59,7 +60,7 @@ import static java.util.Collections.emptyList;
|
|||||||
import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_ONION_ADDRESS;
|
import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_ONION_ADDRESS;
|
||||||
import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_PUBLIC_KEY_HEX;
|
import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_PUBLIC_KEY_HEX;
|
||||||
|
|
||||||
@Module(includes = ViewModelModule.class)
|
@Module(includes = {ContactExchangeModule.class, ViewModelModule.class})
|
||||||
public class AppModule {
|
public class AppModule {
|
||||||
|
|
||||||
static class EagerSingletons {
|
static class EagerSingletons {
|
||||||
|
|||||||
@@ -1,46 +1,32 @@
|
|||||||
package org.briarproject.briar.android.keyagreement;
|
package org.briarproject.briar.android.keyagreement;
|
||||||
|
|
||||||
|
import android.arch.lifecycle.ViewModelProvider;
|
||||||
|
import android.arch.lifecycle.ViewModelProviders;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.contact.ContactExchangeTask;
|
|
||||||
import org.briarproject.bramble.api.contact.event.ContactExchangeFailedEvent;
|
|
||||||
import org.briarproject.bramble.api.contact.event.ContactExchangeSucceededEvent;
|
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
|
||||||
import org.briarproject.bramble.api.event.Event;
|
|
||||||
import org.briarproject.bramble.api.event.EventListener;
|
|
||||||
import org.briarproject.bramble.api.identity.Author;
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
|
||||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
|
||||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementResult;
|
import org.briarproject.bramble.api.keyagreement.KeyAgreementResult;
|
||||||
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.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static android.widget.Toast.LENGTH_LONG;
|
import static android.widget.Toast.LENGTH_LONG;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.Objects.requireNonNull;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
|
||||||
|
|
||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
public class ContactExchangeActivity extends KeyAgreementActivity implements
|
public class ContactExchangeActivity extends KeyAgreementActivity {
|
||||||
EventListener {
|
|
||||||
|
|
||||||
private static final Logger LOG =
|
@Inject
|
||||||
Logger.getLogger(ContactExchangeActivity.class.getName());
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
|
|
||||||
// Fields that are accessed from background threads must be volatile
|
private ContactExchangeViewModel viewModel;
|
||||||
@Inject
|
|
||||||
volatile ContactExchangeTask contactExchangeTask;
|
|
||||||
@Inject
|
|
||||||
volatile IdentityManager identityManager;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void injectActivity(ActivityComponent component) {
|
public void injectActivity(ActivityComponent component) {
|
||||||
@@ -50,83 +36,50 @@ public class ContactExchangeActivity extends KeyAgreementActivity implements
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle state) {
|
public void onCreate(@Nullable Bundle state) {
|
||||||
super.onCreate(state);
|
super.onCreate(state);
|
||||||
getSupportActionBar().setTitle(R.string.add_contact_title);
|
requireNonNull(getSupportActionBar())
|
||||||
}
|
.setTitle(R.string.add_contact_title);
|
||||||
|
viewModel = ViewModelProviders.of(this, viewModelFactory)
|
||||||
@Override
|
.get(ContactExchangeViewModel.class);
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
// Listen to updates from contactExchangeTask
|
|
||||||
eventBus.addListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStop() {
|
|
||||||
super.onStop();
|
|
||||||
// Stop listen to updates from contactExchangeTask
|
|
||||||
eventBus.addListener(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startContactExchange(KeyAgreementResult result) {
|
private void startContactExchange(KeyAgreementResult result) {
|
||||||
runOnDbThread(() -> {
|
viewModel.getSucceeded().observe(this, succeeded -> {
|
||||||
LocalAuthor localAuthor;
|
if (succeeded == null) return;
|
||||||
// Load the local pseudonym
|
if (succeeded) {
|
||||||
try {
|
Author remote = requireNonNull(viewModel.getRemoteAuthor());
|
||||||
localAuthor = identityManager.getLocalAuthor();
|
contactExchangeSucceeded(remote);
|
||||||
} catch (DbException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
contactExchangeFailed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exchange contact details
|
|
||||||
contactExchangeTask.startExchange(localAuthor,
|
|
||||||
result.getMasterKey(), result.getConnection(),
|
|
||||||
result.getTransportId(), result.wasAlice());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void eventOccurred(Event e) {
|
|
||||||
if (e instanceof ContactExchangeSucceededEvent) {
|
|
||||||
contactExchangeSucceeded(
|
|
||||||
((ContactExchangeSucceededEvent) e).getRemoteAuthor());
|
|
||||||
} else if (e instanceof ContactExchangeFailedEvent) {
|
|
||||||
ContactExchangeFailedEvent fe = (ContactExchangeFailedEvent) e;
|
|
||||||
if (fe.wasDuplicateContact()) {
|
|
||||||
duplicateContact(fe.getDuplicateRemoteAuthor());
|
|
||||||
} else {
|
} else {
|
||||||
contactExchangeFailed();
|
Author duplicate = viewModel.getDuplicateAuthor();
|
||||||
|
if (duplicate == null) contactExchangeFailed();
|
||||||
|
else duplicateContact(duplicate);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
viewModel.startContactExchange(result.getTransportId(),
|
||||||
|
result.getConnection(), result.getMasterKey(),
|
||||||
|
result.wasAlice());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
private void contactExchangeSucceeded(Author remoteAuthor) {
|
private void contactExchangeSucceeded(Author remoteAuthor) {
|
||||||
runOnUiThreadUnlessDestroyed(() -> {
|
String contactName = remoteAuthor.getName();
|
||||||
String contactName = remoteAuthor.getName();
|
String format = getString(R.string.contact_added_toast);
|
||||||
String format = getString(R.string.contact_added_toast);
|
String text = String.format(format, contactName);
|
||||||
String text = String.format(format, contactName);
|
Toast.makeText(this, text, LENGTH_LONG).show();
|
||||||
Toast.makeText(ContactExchangeActivity.this, text, LENGTH_LONG)
|
supportFinishAfterTransition();
|
||||||
.show();
|
|
||||||
supportFinishAfterTransition();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
private void duplicateContact(Author remoteAuthor) {
|
private void duplicateContact(Author remoteAuthor) {
|
||||||
runOnUiThreadUnlessDestroyed(() -> {
|
String contactName = remoteAuthor.getName();
|
||||||
String contactName = remoteAuthor.getName();
|
String format = getString(R.string.contact_already_exists);
|
||||||
String format = getString(R.string.contact_already_exists);
|
String text = String.format(format, contactName);
|
||||||
String text = String.format(format, contactName);
|
Toast.makeText(this, text, LENGTH_LONG).show();
|
||||||
Toast.makeText(ContactExchangeActivity.this, text, LENGTH_LONG)
|
finish();
|
||||||
.show();
|
|
||||||
finish();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
private void contactExchangeFailed() {
|
private void contactExchangeFailed() {
|
||||||
runOnUiThreadUnlessDestroyed(() -> {
|
showErrorFragment(R.string.connection_error_explanation);
|
||||||
showErrorFragment(R.string.connection_error_explanation);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package org.briarproject.briar.android.keyagreement;
|
||||||
|
|
||||||
|
import android.arch.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import org.briarproject.briar.android.viewmodel.ViewModelKey;
|
||||||
|
|
||||||
|
import dagger.Binds;
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.multibindings.IntoMap;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
public abstract class ContactExchangeModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@ViewModelKey(ContactExchangeViewModel.class)
|
||||||
|
abstract ViewModel bindContactExchangeViewModel(
|
||||||
|
ContactExchangeViewModel contactExchangeViewModel);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package org.briarproject.briar.android.keyagreement;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.arch.lifecycle.AndroidViewModel;
|
||||||
|
import android.arch.lifecycle.LiveData;
|
||||||
|
import android.arch.lifecycle.MutableLiveData;
|
||||||
|
import android.support.annotation.UiThread;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
|
import org.briarproject.bramble.api.contact.ContactExchangeManager;
|
||||||
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.bramble.api.db.ContactExistsException;
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.identity.Author;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.plugin.ConnectionManager;
|
||||||
|
import org.briarproject.bramble.api.plugin.TransportId;
|
||||||
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class ContactExchangeViewModel extends AndroidViewModel {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(ContactExchangeViewModel.class.getName());
|
||||||
|
|
||||||
|
private final Executor ioExecutor;
|
||||||
|
private final ContactExchangeManager contactExchangeManager;
|
||||||
|
private final ConnectionManager connectionManager;
|
||||||
|
private final MutableLiveData<Boolean> succeeded = new MutableLiveData<>();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private volatile Author remoteAuthor, duplicateAuthor;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ContactExchangeViewModel(Application app, @IoExecutor Executor ioExecutor,
|
||||||
|
ContactExchangeManager contactExchangeManager,
|
||||||
|
ConnectionManager connectionManager) {
|
||||||
|
super(app);
|
||||||
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.contactExchangeManager = contactExchangeManager;
|
||||||
|
this.connectionManager = connectionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
void startContactExchange(TransportId t, DuplexTransportConnection conn,
|
||||||
|
SecretKey masterKey, boolean alice) {
|
||||||
|
ioExecutor.execute(() -> {
|
||||||
|
try {
|
||||||
|
Contact contact = contactExchangeManager.exchangeContacts(t,
|
||||||
|
conn, masterKey, alice);
|
||||||
|
// Reuse the connection as a transport connection
|
||||||
|
connectionManager.manageOutgoingConnection(contact.getId(),
|
||||||
|
t, conn);
|
||||||
|
remoteAuthor = contact.getAuthor();
|
||||||
|
succeeded.postValue(true);
|
||||||
|
} catch (ContactExistsException e) {
|
||||||
|
tryToClose(conn);
|
||||||
|
duplicateAuthor = e.getRemoteAuthor();
|
||||||
|
succeeded.postValue(false);
|
||||||
|
} catch (DbException | IOException e) {
|
||||||
|
tryToClose(conn);
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
succeeded.postValue(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryToClose(DuplexTransportConnection conn) {
|
||||||
|
try {
|
||||||
|
conn.getReader().dispose(true, true);
|
||||||
|
conn.getWriter().dispose(true);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
@Nullable
|
||||||
|
Author getRemoteAuthor() {
|
||||||
|
return remoteAuthor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
@Nullable
|
||||||
|
Author getDuplicateAuthor() {
|
||||||
|
return duplicateAuthor;
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveData<Boolean> getSucceeded() {
|
||||||
|
return succeeded;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,6 @@ import dagger.MapKey;
|
|||||||
@Target({ElementType.METHOD})
|
@Target({ElementType.METHOD})
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@MapKey
|
@MapKey
|
||||||
@interface ViewModelKey {
|
public @interface ViewModelKey {
|
||||||
Class<? extends ViewModel> value();
|
Class<? extends ViewModel> value();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user