mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-19 14:19:53 +01:00
Extract contact exchange protocol from BT introduction protocol
This commit is contained in:
@@ -0,0 +1,14 @@
|
|||||||
|
package org.briarproject.api.contact;
|
||||||
|
|
||||||
|
import org.briarproject.api.identity.Author;
|
||||||
|
|
||||||
|
public interface ContactExchangeListener {
|
||||||
|
|
||||||
|
void contactExchangeSucceeded(Author remoteAuthor);
|
||||||
|
|
||||||
|
/** The exchange failed because the contact already exists. */
|
||||||
|
void duplicateContact(Author remoteAuthor);
|
||||||
|
|
||||||
|
/** A general failure. */
|
||||||
|
void contactExchangeFailed();
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package org.briarproject.api.contact;
|
||||||
|
|
||||||
|
import org.briarproject.api.TransportId;
|
||||||
|
import org.briarproject.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.api.identity.LocalAuthor;
|
||||||
|
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A task for conducting a contact information exchange with a remote peer.
|
||||||
|
*/
|
||||||
|
public interface ContactExchangeTask {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exchange contact information with a remote peer.
|
||||||
|
*/
|
||||||
|
void startExchange(ContactExchangeListener listener,
|
||||||
|
LocalAuthor localAuthor, SecretKey masterSecret,
|
||||||
|
DuplexTransportConnection conn, TransportId transportId,
|
||||||
|
boolean alice, boolean reuseConnection);
|
||||||
|
}
|
||||||
@@ -47,7 +47,7 @@ public interface CryptoComponent {
|
|||||||
* sign.
|
* sign.
|
||||||
* @param alice whether the nonce is for use by Alice or Bob.
|
* @param alice whether the nonce is for use by Alice or Bob.
|
||||||
*/
|
*/
|
||||||
byte[] deriveBTSignatureNonce(SecretKey master, boolean alice);
|
byte[] deriveSignatureNonce(SecretKey master, boolean alice);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Derives a commitment to the provided public key.
|
* Derives a commitment to the provided public key.
|
||||||
|
|||||||
@@ -0,0 +1,253 @@
|
|||||||
|
package org.briarproject.contact;
|
||||||
|
|
||||||
|
import org.briarproject.api.FormatException;
|
||||||
|
import org.briarproject.api.TransportId;
|
||||||
|
import org.briarproject.api.contact.ContactExchangeListener;
|
||||||
|
import org.briarproject.api.contact.ContactExchangeTask;
|
||||||
|
import org.briarproject.api.contact.ContactId;
|
||||||
|
import org.briarproject.api.contact.ContactManager;
|
||||||
|
import org.briarproject.api.crypto.CryptoComponent;
|
||||||
|
import org.briarproject.api.crypto.KeyParser;
|
||||||
|
import org.briarproject.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.api.crypto.Signature;
|
||||||
|
import org.briarproject.api.data.BdfReader;
|
||||||
|
import org.briarproject.api.data.BdfReaderFactory;
|
||||||
|
import org.briarproject.api.data.BdfWriter;
|
||||||
|
import org.briarproject.api.data.BdfWriterFactory;
|
||||||
|
import org.briarproject.api.db.ContactExistsException;
|
||||||
|
import org.briarproject.api.db.DbException;
|
||||||
|
import org.briarproject.api.identity.Author;
|
||||||
|
import org.briarproject.api.identity.AuthorFactory;
|
||||||
|
import org.briarproject.api.identity.LocalAuthor;
|
||||||
|
import org.briarproject.api.plugins.ConnectionManager;
|
||||||
|
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
|
||||||
|
import org.briarproject.api.system.Clock;
|
||||||
|
import org.briarproject.api.transport.KeyManager;
|
||||||
|
import org.briarproject.api.transport.StreamReaderFactory;
|
||||||
|
import org.briarproject.api.transport.StreamWriterFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||||
|
import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||||
|
import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||||
|
|
||||||
|
public class ContactExchangeTaskImpl extends Thread
|
||||||
|
implements ContactExchangeTask {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
Logger.getLogger(ContactExchangeTaskImpl.class.getName());
|
||||||
|
|
||||||
|
private final AuthorFactory authorFactory;
|
||||||
|
private final BdfReaderFactory bdfReaderFactory;
|
||||||
|
private final BdfWriterFactory bdfWriterFactory;
|
||||||
|
private final Clock clock;
|
||||||
|
private final ConnectionManager connectionManager;
|
||||||
|
private final ContactManager contactManager;
|
||||||
|
private final CryptoComponent crypto;
|
||||||
|
private final KeyManager keyManager;
|
||||||
|
private final StreamReaderFactory streamReaderFactory;
|
||||||
|
private final StreamWriterFactory streamWriterFactory;
|
||||||
|
|
||||||
|
private ContactExchangeListener listener;
|
||||||
|
private LocalAuthor localAuthor;
|
||||||
|
private DuplexTransportConnection conn;
|
||||||
|
private TransportId transportId;
|
||||||
|
private SecretKey masterSecret;
|
||||||
|
private boolean alice;
|
||||||
|
private boolean reuseConnection;
|
||||||
|
|
||||||
|
public ContactExchangeTaskImpl(AuthorFactory authorFactory,
|
||||||
|
BdfReaderFactory bdfReaderFactory,
|
||||||
|
BdfWriterFactory bdfWriterFactory, Clock clock,
|
||||||
|
ConnectionManager connectionManager, ContactManager contactManager,
|
||||||
|
CryptoComponent crypto, KeyManager keyManager,
|
||||||
|
StreamReaderFactory streamReaderFactory,
|
||||||
|
StreamWriterFactory streamWriterFactory) {
|
||||||
|
this.authorFactory = authorFactory;
|
||||||
|
this.bdfReaderFactory = bdfReaderFactory;
|
||||||
|
this.bdfWriterFactory = bdfWriterFactory;
|
||||||
|
this.clock = clock;
|
||||||
|
this.connectionManager = connectionManager;
|
||||||
|
this.contactManager = contactManager;
|
||||||
|
this.crypto = crypto;
|
||||||
|
this.keyManager = keyManager;
|
||||||
|
this.streamReaderFactory = streamReaderFactory;
|
||||||
|
this.streamWriterFactory = streamWriterFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startExchange(ContactExchangeListener listener,
|
||||||
|
LocalAuthor localAuthor, SecretKey masterSecret,
|
||||||
|
DuplexTransportConnection conn, TransportId transportId,
|
||||||
|
boolean alice, boolean reuseConnection) {
|
||||||
|
this.listener = listener;
|
||||||
|
this.localAuthor = localAuthor;
|
||||||
|
this.conn = conn;
|
||||||
|
this.transportId = transportId;
|
||||||
|
this.masterSecret = masterSecret;
|
||||||
|
this.alice = alice;
|
||||||
|
this.reuseConnection = reuseConnection;
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
BdfReader r;
|
||||||
|
BdfWriter w;
|
||||||
|
try {
|
||||||
|
// Create the readers
|
||||||
|
InputStream streamReader =
|
||||||
|
streamReaderFactory.createInvitationStreamReader(
|
||||||
|
conn.getReader().getInputStream(),
|
||||||
|
masterSecret);
|
||||||
|
r = bdfReaderFactory.createReader(streamReader);
|
||||||
|
// Create the writers
|
||||||
|
OutputStream streamWriter =
|
||||||
|
streamWriterFactory.createInvitationStreamWriter(
|
||||||
|
conn.getWriter().getOutputStream(),
|
||||||
|
masterSecret);
|
||||||
|
w = bdfWriterFactory.createWriter(streamWriter);
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
|
listener.contactExchangeFailed();
|
||||||
|
tryToClose(conn, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive the invitation nonces
|
||||||
|
byte[] aliceNonce = crypto.deriveSignatureNonce(masterSecret, true);
|
||||||
|
byte[] bobNonce = crypto.deriveSignatureNonce(masterSecret, false);
|
||||||
|
|
||||||
|
// Exchange pseudonyms, signed nonces, and timestamps
|
||||||
|
long localTimestamp = clock.currentTimeMillis();
|
||||||
|
Author remoteAuthor;
|
||||||
|
long remoteTimestamp;
|
||||||
|
try {
|
||||||
|
if (alice) {
|
||||||
|
sendPseudonym(w, aliceNonce);
|
||||||
|
sendTimestamp(w, localTimestamp);
|
||||||
|
remoteAuthor = receivePseudonym(r, bobNonce);
|
||||||
|
remoteTimestamp = receiveTimestamp(r);
|
||||||
|
} else {
|
||||||
|
remoteAuthor = receivePseudonym(r, aliceNonce);
|
||||||
|
remoteTimestamp = receiveTimestamp(r);
|
||||||
|
sendPseudonym(w, bobNonce);
|
||||||
|
sendTimestamp(w, localTimestamp);
|
||||||
|
}
|
||||||
|
// Close the outgoing stream and expect EOF on the incoming stream
|
||||||
|
w.close();
|
||||||
|
if (!r.eof()) LOG.warning("Unexpected data at end of connection");
|
||||||
|
} catch (GeneralSecurityException e) {
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
|
listener.contactExchangeFailed();
|
||||||
|
tryToClose(conn, true);
|
||||||
|
return;
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
|
listener.contactExchangeFailed();
|
||||||
|
tryToClose(conn, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The agreed timestamp is the minimum of the peers' timestamps
|
||||||
|
long timestamp = Math.min(localTimestamp, remoteTimestamp);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Add the contact
|
||||||
|
ContactId contactId =
|
||||||
|
addContact(remoteAuthor, masterSecret, timestamp, true);
|
||||||
|
// Reuse the connection as a transport connection
|
||||||
|
if (reuseConnection)
|
||||||
|
connectionManager
|
||||||
|
.manageOutgoingConnection(contactId, transportId, conn);
|
||||||
|
// Pseudonym exchange succeeded
|
||||||
|
LOG.info("Pseudonym exchange succeeded");
|
||||||
|
listener.contactExchangeSucceeded(remoteAuthor);
|
||||||
|
} catch (ContactExistsException e) {
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
|
tryToClose(conn, true);
|
||||||
|
listener.duplicateContact(remoteAuthor);
|
||||||
|
} catch (DbException e) {
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
|
tryToClose(conn, true);
|
||||||
|
listener.contactExchangeFailed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendPseudonym(BdfWriter w, byte[] nonce)
|
||||||
|
throws GeneralSecurityException, IOException {
|
||||||
|
// Sign the nonce
|
||||||
|
Signature signature = crypto.getSignature();
|
||||||
|
KeyParser keyParser = crypto.getSignatureKeyParser();
|
||||||
|
byte[] privateKey = localAuthor.getPrivateKey();
|
||||||
|
signature.initSign(keyParser.parsePrivateKey(privateKey));
|
||||||
|
signature.update(nonce);
|
||||||
|
byte[] sig = signature.sign();
|
||||||
|
// Write the name, public key and signature
|
||||||
|
w.writeString(localAuthor.getName());
|
||||||
|
w.writeRaw(localAuthor.getPublicKey());
|
||||||
|
w.writeRaw(sig);
|
||||||
|
w.flush();
|
||||||
|
LOG.info("Sent pseudonym");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Author receivePseudonym(BdfReader r, byte[] nonce)
|
||||||
|
throws GeneralSecurityException, IOException {
|
||||||
|
// Read the name, public key and signature
|
||||||
|
String name = r.readString(MAX_AUTHOR_NAME_LENGTH);
|
||||||
|
byte[] publicKey = r.readRaw(MAX_PUBLIC_KEY_LENGTH);
|
||||||
|
byte[] sig = r.readRaw(MAX_SIGNATURE_LENGTH);
|
||||||
|
LOG.info("Received pseudonym");
|
||||||
|
// Verify the signature
|
||||||
|
Signature signature = crypto.getSignature();
|
||||||
|
KeyParser keyParser = crypto.getSignatureKeyParser();
|
||||||
|
signature.initVerify(keyParser.parsePublicKey(publicKey));
|
||||||
|
signature.update(nonce);
|
||||||
|
if (!signature.verify(sig)) {
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Invalid signature");
|
||||||
|
throw new GeneralSecurityException();
|
||||||
|
}
|
||||||
|
return authorFactory.createAuthor(name, publicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendTimestamp(BdfWriter w, long timestamp)
|
||||||
|
throws IOException {
|
||||||
|
w.writeLong(timestamp);
|
||||||
|
w.flush();
|
||||||
|
LOG.info("Sent timestamp");
|
||||||
|
}
|
||||||
|
|
||||||
|
private long receiveTimestamp(BdfReader r) throws IOException {
|
||||||
|
long timestamp = r.readLong();
|
||||||
|
if (timestamp < 0) throw new FormatException();
|
||||||
|
LOG.info("Received timestamp");
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ContactId addContact(Author remoteAuthor, SecretKey master,
|
||||||
|
long timestamp, boolean alice) throws DbException {
|
||||||
|
// Add the contact to the database
|
||||||
|
ContactId contactId = contactManager.addContact(remoteAuthor,
|
||||||
|
localAuthor.getId(), master, timestamp, alice, true);
|
||||||
|
return contactId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryToClose(DuplexTransportConnection conn,
|
||||||
|
boolean exception) {
|
||||||
|
try {
|
||||||
|
LOG.info("Closing connection");
|
||||||
|
conn.getReader().dispose(exception, true);
|
||||||
|
conn.getWriter().dispose(exception);
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,18 @@
|
|||||||
package org.briarproject.contact;
|
package org.briarproject.contact;
|
||||||
|
|
||||||
|
import org.briarproject.api.contact.ContactExchangeTask;
|
||||||
import org.briarproject.api.contact.ContactManager;
|
import org.briarproject.api.contact.ContactManager;
|
||||||
|
import org.briarproject.api.crypto.CryptoComponent;
|
||||||
|
import org.briarproject.api.data.BdfReaderFactory;
|
||||||
|
import org.briarproject.api.data.BdfWriterFactory;
|
||||||
|
import org.briarproject.api.identity.AuthorFactory;
|
||||||
import org.briarproject.api.identity.IdentityManager;
|
import org.briarproject.api.identity.IdentityManager;
|
||||||
import org.briarproject.api.lifecycle.LifecycleManager;
|
import org.briarproject.api.lifecycle.LifecycleManager;
|
||||||
|
import org.briarproject.api.plugins.ConnectionManager;
|
||||||
|
import org.briarproject.api.system.Clock;
|
||||||
|
import org.briarproject.api.transport.KeyManager;
|
||||||
|
import org.briarproject.api.transport.StreamReaderFactory;
|
||||||
|
import org.briarproject.api.transport.StreamWriterFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
@@ -25,4 +35,18 @@ public class ContactModule {
|
|||||||
identityManager.registerRemoveIdentityHook(contactManager);
|
identityManager.registerRemoveIdentityHook(contactManager);
|
||||||
return contactManager;
|
return contactManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
ContactExchangeTask provideContactExchangeTask(
|
||||||
|
AuthorFactory authorFactory, BdfReaderFactory bdfReaderFactory,
|
||||||
|
BdfWriterFactory bdfWriterFactory, Clock clock,
|
||||||
|
ConnectionManager connectionManager, ContactManager contactManager,
|
||||||
|
CryptoComponent crypto, KeyManager keyManager,
|
||||||
|
StreamReaderFactory streamReaderFactory,
|
||||||
|
StreamWriterFactory streamWriterFactory) {
|
||||||
|
return new ContactExchangeTaskImpl(authorFactory,
|
||||||
|
bdfReaderFactory, bdfWriterFactory, clock, connectionManager,
|
||||||
|
contactManager, crypto, keyManager, streamReaderFactory,
|
||||||
|
streamWriterFactory);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
return new SecretKey(macKdf(master, alice ? BT_A_INVITE : BT_B_INVITE));
|
return new SecretKey(macKdf(master, alice ? BT_A_INVITE : BT_B_INVITE));
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] deriveBTSignatureNonce(SecretKey master, boolean alice) {
|
public byte[] deriveSignatureNonce(SecretKey master, boolean alice) {
|
||||||
return macKdf(master, alice ? BT_A_NONCE : BT_B_NONCE);
|
return macKdf(master, alice ? BT_A_NONCE : BT_B_NONCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -138,8 +138,8 @@ class AliceConnector extends Connector {
|
|||||||
aliceHeaderKey);
|
aliceHeaderKey);
|
||||||
w = bdfWriterFactory.createWriter(streamWriter);
|
w = bdfWriterFactory.createWriter(streamWriter);
|
||||||
// Derive the invitation nonces
|
// Derive the invitation nonces
|
||||||
byte[] aliceNonce = crypto.deriveBTSignatureNonce(master, true);
|
byte[] aliceNonce = crypto.deriveSignatureNonce(master, true);
|
||||||
byte[] bobNonce = crypto.deriveBTSignatureNonce(master, false);
|
byte[] bobNonce = crypto.deriveSignatureNonce(master, false);
|
||||||
// Exchange pseudonyms, signed nonces, and timestamps
|
// Exchange pseudonyms, signed nonces, and timestamps
|
||||||
Author remoteAuthor;
|
Author remoteAuthor;
|
||||||
long remoteTimestamp;
|
long remoteTimestamp;
|
||||||
|
|||||||
@@ -138,8 +138,8 @@ class BobConnector extends Connector {
|
|||||||
bobHeaderKey);
|
bobHeaderKey);
|
||||||
w = bdfWriterFactory.createWriter(streamWriter);
|
w = bdfWriterFactory.createWriter(streamWriter);
|
||||||
// Derive the nonces
|
// Derive the nonces
|
||||||
byte[] aliceNonce = crypto.deriveBTSignatureNonce(master, true);
|
byte[] aliceNonce = crypto.deriveSignatureNonce(master, true);
|
||||||
byte[] bobNonce = crypto.deriveBTSignatureNonce(master, false);
|
byte[] bobNonce = crypto.deriveSignatureNonce(master, false);
|
||||||
// Exchange pseudonyms, signed nonces and timestamps
|
// Exchange pseudonyms, signed nonces and timestamps
|
||||||
Author remoteAuthor;
|
Author remoteAuthor;
|
||||||
long remoteTimestamp;
|
long remoteTimestamp;
|
||||||
|
|||||||
Reference in New Issue
Block a user