Extract contact exchange protocol from BT introduction protocol

This commit is contained in:
str4d
2016-02-25 22:01:15 +00:00
parent d7c7a72710
commit 701cfdba48
8 changed files with 317 additions and 6 deletions

View File

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

View File

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

View File

@@ -47,7 +47,7 @@ public interface CryptoComponent {
* sign.
* @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.

View File

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

View File

@@ -1,8 +1,18 @@
package org.briarproject.contact;
import org.briarproject.api.contact.ContactExchangeTask;
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.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.Singleton;
@@ -25,4 +35,18 @@ public class ContactModule {
identityManager.registerRemoveIdentityHook(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);
}
}

View File

@@ -214,7 +214,7 @@ class CryptoComponentImpl implements CryptoComponent {
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);
}

View File

@@ -138,8 +138,8 @@ class AliceConnector extends Connector {
aliceHeaderKey);
w = bdfWriterFactory.createWriter(streamWriter);
// Derive the invitation nonces
byte[] aliceNonce = crypto.deriveBTSignatureNonce(master, true);
byte[] bobNonce = crypto.deriveBTSignatureNonce(master, false);
byte[] aliceNonce = crypto.deriveSignatureNonce(master, true);
byte[] bobNonce = crypto.deriveSignatureNonce(master, false);
// Exchange pseudonyms, signed nonces, and timestamps
Author remoteAuthor;
long remoteTimestamp;

View File

@@ -138,8 +138,8 @@ class BobConnector extends Connector {
bobHeaderKey);
w = bdfWriterFactory.createWriter(streamWriter);
// Derive the nonces
byte[] aliceNonce = crypto.deriveBTSignatureNonce(master, true);
byte[] bobNonce = crypto.deriveBTSignatureNonce(master, false);
byte[] aliceNonce = crypto.deriveSignatureNonce(master, true);
byte[] bobNonce = crypto.deriveSignatureNonce(master, false);
// Exchange pseudonyms, signed nonces and timestamps
Author remoteAuthor;
long remoteTimestamp;