Changed the root package from net.sf.briar to org.briarproject.

This commit is contained in:
akwizgran
2014-01-08 16:18:30 +00:00
parent dce70f487c
commit 832476412c
427 changed files with 2507 additions and 2507 deletions

View File

@@ -0,0 +1,186 @@
package org.briarproject.invitation;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
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 org.briarproject.api.Author;
import org.briarproject.api.AuthorFactory;
import org.briarproject.api.LocalAuthor;
import org.briarproject.api.TransportId;
import org.briarproject.api.TransportProperties;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.KeyManager;
import org.briarproject.api.crypto.PseudoRandom;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException;
import org.briarproject.api.messaging.GroupFactory;
import org.briarproject.api.plugins.duplex.DuplexPlugin;
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
import org.briarproject.api.serial.Reader;
import org.briarproject.api.serial.ReaderFactory;
import org.briarproject.api.serial.Writer;
import org.briarproject.api.serial.WriterFactory;
import org.briarproject.api.system.Clock;
import org.briarproject.api.transport.ConnectionDispatcher;
import org.briarproject.api.transport.ConnectionReader;
import org.briarproject.api.transport.ConnectionReaderFactory;
import org.briarproject.api.transport.ConnectionWriter;
import org.briarproject.api.transport.ConnectionWriterFactory;
/** A connection thread for the peer being Alice in the invitation protocol. */
class AliceConnector extends Connector {
private static final Logger LOG =
Logger.getLogger(AliceConnector.class.getName());
AliceConnector(CryptoComponent crypto, DatabaseComponent db,
ReaderFactory readerFactory, WriterFactory writerFactory,
ConnectionReaderFactory connectionReaderFactory,
ConnectionWriterFactory connectionWriterFactory,
AuthorFactory authorFactory, GroupFactory groupFactory,
KeyManager keyManager, ConnectionDispatcher connectionDispatcher,
Clock clock, ConnectorGroup group, DuplexPlugin plugin,
LocalAuthor localAuthor,
Map<TransportId, TransportProperties> localProps,
PseudoRandom random) {
super(crypto, db, readerFactory, writerFactory, connectionReaderFactory,
connectionWriterFactory, authorFactory, groupFactory,
keyManager, connectionDispatcher, clock, group, plugin,
localAuthor, localProps, random);
}
@Override
public void run() {
// Create an incoming or outgoing connection
DuplexTransportConnection conn = createInvitationConnection();
if(conn == null) return;
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " connected");
// Don't proceed with more than one connection
if(group.getAndSetConnected()) {
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " redundant");
tryToClose(conn, false);
return;
}
// Carry out the key agreement protocol
InputStream in;
OutputStream out;
Reader r;
Writer w;
byte[] secret;
try {
in = conn.getInputStream();
out = conn.getOutputStream();
r = readerFactory.createReader(in);
w = writerFactory.createWriter(out);
// Alice goes first
sendPublicKeyHash(w);
byte[] hash = receivePublicKeyHash(r);
sendPublicKey(w);
byte[] key = receivePublicKey(r);
secret = deriveMasterSecret(hash, key, true);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
group.keyAgreementFailed();
tryToClose(conn, true);
return;
} catch(GeneralSecurityException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
group.keyAgreementFailed();
tryToClose(conn, true);
return;
}
// The key agreement succeeded - derive the confirmation codes
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " agreement succeeded");
int[] codes = crypto.deriveConfirmationCodes(secret);
int aliceCode = codes[0], bobCode = codes[1];
group.keyAgreementSucceeded(aliceCode, bobCode);
// Exchange confirmation results
boolean localMatched, remoteMatched;
try {
localMatched = group.waitForLocalConfirmationResult();
sendConfirmation(w, localMatched);
remoteMatched = receiveConfirmation(r);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
group.remoteConfirmationFailed();
tryToClose(conn, true);
return;
} catch(InterruptedException e) {
if(LOG.isLoggable(WARNING))
LOG.warning("Interrupted while waiting for confirmation");
group.remoteConfirmationFailed();
tryToClose(conn, true);
Thread.currentThread().interrupt();
return;
}
if(remoteMatched) group.remoteConfirmationSucceeded();
else group.remoteConfirmationFailed();
if(!(localMatched && remoteMatched)) {
tryToClose(conn, false);
return;
}
// The timestamp is taken after exhanging confirmation results
long localTimestamp = clock.currentTimeMillis();
// Confirmation succeeded - upgrade to a secure connection
if(LOG.isLoggable(INFO))
LOG.info(pluginName + " confirmation succeeded");
int maxFrameLength = conn.getMaxFrameLength();
ConnectionReader connectionReader =
connectionReaderFactory.createInvitationConnectionReader(in,
maxFrameLength, secret, false);
r = readerFactory.createReader(connectionReader.getInputStream());
ConnectionWriter connectionWriter =
connectionWriterFactory.createInvitationConnectionWriter(out,
maxFrameLength, secret, true);
w = writerFactory.createWriter(connectionWriter.getOutputStream());
// Derive the invitation nonces
byte[][] nonces = crypto.deriveInvitationNonces(secret);
byte[] aliceNonce = nonces[0], bobNonce = nonces[1];
// Exchange pseudonyms, signed nonces, timestamps and transports
Author remoteAuthor;
long remoteTimestamp;
Map<TransportId, TransportProperties> remoteProps;
try {
sendPseudonym(w, aliceNonce);
sendTimestamp(w, localTimestamp);
sendTransportProperties(w);
remoteAuthor = receivePseudonym(r, bobNonce);
remoteTimestamp = receiveTimestamp(r);
remoteProps = receiveTransportProperties(r);
} catch(GeneralSecurityException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
group.pseudonymExchangeFailed();
tryToClose(conn, true);
return;
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
group.pseudonymExchangeFailed();
tryToClose(conn, true);
return;
}
// The epoch is the minimum of the peers' timestamps
long epoch = Math.min(localTimestamp, remoteTimestamp);
// Add the contact and store the transports
try {
addContact(remoteAuthor, remoteProps, secret, epoch, true);
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
tryToClose(conn, true);
group.pseudonymExchangeFailed();
return;
}
// Pseudonym exchange succeeded
if(LOG.isLoggable(INFO))
LOG.info(pluginName + " pseudonym exchange succeeded");
group.pseudonymExchangeSucceeded(remoteAuthor);
// Reuse the connection as an outgoing BTP connection
reuseConnection(conn, true);
}
}

View File

@@ -0,0 +1,186 @@
package org.briarproject.invitation;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
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 org.briarproject.api.Author;
import org.briarproject.api.AuthorFactory;
import org.briarproject.api.LocalAuthor;
import org.briarproject.api.TransportId;
import org.briarproject.api.TransportProperties;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.KeyManager;
import org.briarproject.api.crypto.PseudoRandom;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException;
import org.briarproject.api.messaging.GroupFactory;
import org.briarproject.api.plugins.duplex.DuplexPlugin;
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
import org.briarproject.api.serial.Reader;
import org.briarproject.api.serial.ReaderFactory;
import org.briarproject.api.serial.Writer;
import org.briarproject.api.serial.WriterFactory;
import org.briarproject.api.system.Clock;
import org.briarproject.api.transport.ConnectionDispatcher;
import org.briarproject.api.transport.ConnectionReader;
import org.briarproject.api.transport.ConnectionReaderFactory;
import org.briarproject.api.transport.ConnectionWriter;
import org.briarproject.api.transport.ConnectionWriterFactory;
/** A connection thread for the peer being Bob in the invitation protocol. */
class BobConnector extends Connector {
private static final Logger LOG =
Logger.getLogger(BobConnector.class.getName());
BobConnector(CryptoComponent crypto, DatabaseComponent db,
ReaderFactory readerFactory, WriterFactory writerFactory,
ConnectionReaderFactory connectionReaderFactory,
ConnectionWriterFactory connectionWriterFactory,
AuthorFactory authorFactory, GroupFactory groupFactory,
KeyManager keyManager, ConnectionDispatcher connectionDispatcher,
Clock clock, ConnectorGroup group, DuplexPlugin plugin,
LocalAuthor localAuthor,
Map<TransportId, TransportProperties> localProps,
PseudoRandom random) {
super(crypto, db, readerFactory, writerFactory, connectionReaderFactory,
connectionWriterFactory, authorFactory, groupFactory,
keyManager, connectionDispatcher, clock, group, plugin,
localAuthor, localProps, random);
}
@Override
public void run() {
// Create an incoming or outgoing connection
DuplexTransportConnection conn = createInvitationConnection();
if(conn == null) return;
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " connected");
// Carry out the key agreement protocol
InputStream in;
OutputStream out;
Reader r;
Writer w;
byte[] secret;
try {
in = conn.getInputStream();
out = conn.getOutputStream();
r = readerFactory.createReader(in);
w = writerFactory.createWriter(out);
// Alice goes first
byte[] hash = receivePublicKeyHash(r);
// Don't proceed with more than one connection
if(group.getAndSetConnected()) {
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " redundant");
tryToClose(conn, false);
return;
}
sendPublicKeyHash(w);
byte[] key = receivePublicKey(r);
sendPublicKey(w);
secret = deriveMasterSecret(hash, key, false);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
group.keyAgreementFailed();
tryToClose(conn, true);
return;
} catch(GeneralSecurityException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
group.keyAgreementFailed();
tryToClose(conn, true);
return;
}
// The key agreement succeeded - derive the confirmation codes
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " agreement succeeded");
int[] codes = crypto.deriveConfirmationCodes(secret);
int aliceCode = codes[0], bobCode = codes[1];
group.keyAgreementSucceeded(bobCode, aliceCode);
// Exchange confirmation results
boolean localMatched, remoteMatched;
try {
remoteMatched = receiveConfirmation(r);
localMatched = group.waitForLocalConfirmationResult();
sendConfirmation(w, localMatched);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
group.remoteConfirmationFailed();
tryToClose(conn, true);
return;
} catch(InterruptedException e) {
if(LOG.isLoggable(WARNING))
LOG.warning("Interrupted while waiting for confirmation");
group.remoteConfirmationFailed();
tryToClose(conn, true);
Thread.currentThread().interrupt();
return;
}
if(remoteMatched) group.remoteConfirmationSucceeded();
else group.remoteConfirmationFailed();
if(!(localMatched && remoteMatched)) {
tryToClose(conn, false);
return;
}
// The timestamp is taken after exhanging confirmation results
long localTimestamp = clock.currentTimeMillis();
// Confirmation succeeded - upgrade to a secure connection
if(LOG.isLoggable(INFO))
LOG.info(pluginName + " confirmation succeeded");
int maxFrameLength = conn.getMaxFrameLength();
ConnectionReader connectionReader =
connectionReaderFactory.createInvitationConnectionReader(in,
maxFrameLength, secret, true);
r = readerFactory.createReader(connectionReader.getInputStream());
ConnectionWriter connectionWriter =
connectionWriterFactory.createInvitationConnectionWriter(out,
maxFrameLength, secret, false);
w = writerFactory.createWriter(connectionWriter.getOutputStream());
// Derive the nonces
byte[][] nonces = crypto.deriveInvitationNonces(secret);
byte[] aliceNonce = nonces[0], bobNonce = nonces[1];
// Exchange pseudonyms, signed nonces, timestamps and transports
Author remoteAuthor;
long remoteTimestamp;
Map<TransportId, TransportProperties> remoteProps;
try {
remoteAuthor = receivePseudonym(r, aliceNonce);
remoteTimestamp = receiveTimestamp(r);
remoteProps = receiveTransportProperties(r);
sendPseudonym(w, bobNonce);
sendTimestamp(w, localTimestamp);
sendTransportProperties(w);
} catch(GeneralSecurityException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
group.pseudonymExchangeFailed();
tryToClose(conn, true);
return;
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
group.pseudonymExchangeFailed();
tryToClose(conn, true);
return;
}
// The epoch is the minimum of the peers' timestamps
long epoch = Math.min(localTimestamp, remoteTimestamp);
// Add the contact and store the transports
try {
addContact(remoteAuthor, remoteProps, secret, epoch, false);
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
tryToClose(conn, true);
group.pseudonymExchangeFailed();
return;
}
// Pseudonym exchange succeeded
if(LOG.isLoggable(INFO))
LOG.info(pluginName + " pseudonym exchange succeeded");
group.pseudonymExchangeSucceeded(remoteAuthor);
// Reuse the connection as an incoming BTP connection
reuseConnection(conn, false);
}
}

View File

@@ -0,0 +1,342 @@
package org.briarproject.invitation;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.api.AuthorConstants.MAX_SIGNATURE_LENGTH;
import static org.briarproject.api.TransportPropertyConstants.MAX_PROPERTIES_PER_TRANSPORT;
import static org.briarproject.api.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
import static org.briarproject.api.invitation.InvitationConstants.CONNECTION_TIMEOUT;
import static org.briarproject.api.invitation.InvitationConstants.HASH_LENGTH;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;
import org.briarproject.api.Author;
import org.briarproject.api.AuthorConstants;
import org.briarproject.api.AuthorFactory;
import org.briarproject.api.ContactId;
import org.briarproject.api.FormatException;
import org.briarproject.api.LocalAuthor;
import org.briarproject.api.TransportId;
import org.briarproject.api.TransportProperties;
import org.briarproject.api.UniqueId;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.KeyManager;
import org.briarproject.api.crypto.KeyPair;
import org.briarproject.api.crypto.KeyParser;
import org.briarproject.api.crypto.MessageDigest;
import org.briarproject.api.crypto.PseudoRandom;
import org.briarproject.api.crypto.Signature;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.NoSuchTransportException;
import org.briarproject.api.invitation.InvitationConstants;
import org.briarproject.api.messaging.Group;
import org.briarproject.api.messaging.GroupFactory;
import org.briarproject.api.plugins.duplex.DuplexPlugin;
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
import org.briarproject.api.serial.Reader;
import org.briarproject.api.serial.ReaderFactory;
import org.briarproject.api.serial.Writer;
import org.briarproject.api.serial.WriterFactory;
import org.briarproject.api.system.Clock;
import org.briarproject.api.transport.ConnectionDispatcher;
import org.briarproject.api.transport.ConnectionReaderFactory;
import org.briarproject.api.transport.ConnectionWriterFactory;
import org.briarproject.api.transport.Endpoint;
abstract class Connector extends Thread {
private static final Logger LOG =
Logger.getLogger(Connector.class.getName());
protected final CryptoComponent crypto;
protected final DatabaseComponent db;
protected final ReaderFactory readerFactory;
protected final WriterFactory writerFactory;
protected final ConnectionReaderFactory connectionReaderFactory;
protected final ConnectionWriterFactory connectionWriterFactory;
protected final AuthorFactory authorFactory;
protected final GroupFactory groupFactory;
protected final KeyManager keyManager;
protected final ConnectionDispatcher connectionDispatcher;
protected final Clock clock;
protected final ConnectorGroup group;
protected final DuplexPlugin plugin;
protected final LocalAuthor localAuthor;
protected final Map<TransportId, TransportProperties> localProps;
protected final PseudoRandom random;
protected final String pluginName;
private final KeyPair keyPair;
private final KeyParser keyParser;
private final MessageDigest messageDigest;
private volatile ContactId contactId = null;
Connector(CryptoComponent crypto, DatabaseComponent db,
ReaderFactory readerFactory, WriterFactory writerFactory,
ConnectionReaderFactory connectionReaderFactory,
ConnectionWriterFactory connectionWriterFactory,
AuthorFactory authorFactory, GroupFactory groupFactory,
KeyManager keyManager, ConnectionDispatcher connectionDispatcher,
Clock clock, ConnectorGroup group, DuplexPlugin plugin,
LocalAuthor localAuthor,
Map<TransportId, TransportProperties> localProps,
PseudoRandom random) {
super("Connector");
this.crypto = crypto;
this.db = db;
this.readerFactory = readerFactory;
this.writerFactory = writerFactory;
this.connectionReaderFactory = connectionReaderFactory;
this.connectionWriterFactory = connectionWriterFactory;
this.authorFactory = authorFactory;
this.groupFactory = groupFactory;
this.keyManager = keyManager;
this.connectionDispatcher = connectionDispatcher;
this.clock = clock;
this.group = group;
this.plugin = plugin;
this.localAuthor = localAuthor;
this.localProps = localProps;
this.random = random;
pluginName = plugin.getClass().getName();
keyPair = crypto.generateAgreementKeyPair();
keyParser = crypto.getAgreementKeyParser();
messageDigest = crypto.getMessageDigest();
}
protected DuplexTransportConnection createInvitationConnection() {
if(LOG.isLoggable(INFO))
LOG.info(pluginName + " creating invitation connection");
return plugin.createInvitationConnection(random, CONNECTION_TIMEOUT);
}
protected void sendPublicKeyHash(Writer w) throws IOException {
w.writeBytes(messageDigest.digest(keyPair.getPublic().getEncoded()));
w.flush();
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " sent hash");
}
protected byte[] receivePublicKeyHash(Reader r) throws IOException {
byte[] b = r.readBytes(HASH_LENGTH);
if(b.length < HASH_LENGTH) throw new FormatException();
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " received hash");
return b;
}
protected void sendPublicKey(Writer w) throws IOException {
byte[] key = keyPair.getPublic().getEncoded();
w.writeBytes(key);
w.flush();
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " sent key");
}
protected byte[] receivePublicKey(Reader r) throws GeneralSecurityException,
IOException {
byte[] b = r.readBytes(InvitationConstants.MAX_PUBLIC_KEY_LENGTH);
keyParser.parsePublicKey(b);
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " received key");
return b;
}
protected byte[] deriveMasterSecret(byte[] hash, byte[] key, boolean alice)
throws GeneralSecurityException {
// Check that the hash matches the key
if(!Arrays.equals(hash, messageDigest.digest(key))) {
if(LOG.isLoggable(INFO))
LOG.info(pluginName + " hash does not match key");
throw new GeneralSecurityException();
}
// Derive the master secret
if(LOG.isLoggable(INFO))
LOG.info(pluginName + " deriving master secret");
return crypto.deriveMasterSecret(key, keyPair, alice);
}
protected void sendConfirmation(Writer w, boolean matched)
throws IOException {
w.writeBoolean(matched);
w.flush();
if(LOG.isLoggable(INFO))
LOG.info(pluginName + " sent confirmation: " + matched);
}
protected boolean receiveConfirmation(Reader r) throws IOException {
boolean matched = r.readBoolean();
if(LOG.isLoggable(INFO))
LOG.info(pluginName + " received confirmation: " + matched);
return matched;
}
protected void sendPseudonym(Writer 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.writeBytes(localAuthor.getPublicKey());
w.writeBytes(sig);
w.flush();
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " sent pseudonym");
}
protected Author receivePseudonym(Reader r, byte[] nonce)
throws GeneralSecurityException, IOException {
// Read the name, public key and signature
String name = r.readString(MAX_AUTHOR_NAME_LENGTH);
byte[] publicKey = r.readBytes(AuthorConstants.MAX_PUBLIC_KEY_LENGTH);
byte[] sig = r.readBytes(MAX_SIGNATURE_LENGTH);
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " 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(pluginName + " invalid signature");
throw new GeneralSecurityException();
}
return authorFactory.createAuthor(name, publicKey);
}
protected void sendTimestamp(Writer w, long timestamp) throws IOException {
w.writeInt64(timestamp);
w.flush();
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " sent timestamp");
}
protected long receiveTimestamp(Reader r) throws IOException {
long timestamp = r.readInt64();
if(timestamp < 0) throw new FormatException();
if(LOG.isLoggable(INFO)) LOG.info(pluginName + " received timestamp");
return timestamp;
}
protected void sendTransportProperties(Writer w) throws IOException {
w.writeListStart();
for(Entry<TransportId, TransportProperties> e : localProps.entrySet()) {
w.writeBytes(e.getKey().getBytes());
w.writeMap(e.getValue());
}
w.writeListEnd();
w.flush();
if(LOG.isLoggable(INFO))
LOG.info(pluginName + " sent transport properties");
}
protected Map<TransportId, TransportProperties> receiveTransportProperties(
Reader r) throws IOException {
Map<TransportId, TransportProperties> remoteProps =
new HashMap<TransportId, TransportProperties>();
r.readListStart();
while(!r.hasListEnd()) {
byte[] b = r.readBytes(UniqueId.LENGTH);
if(b.length != UniqueId.LENGTH) throw new FormatException();
TransportId id = new TransportId(b);
Map<String, String> p = new HashMap<String, String>();
r.readMapStart();
for(int i = 0; !r.hasMapEnd(); i++) {
if(i == MAX_PROPERTIES_PER_TRANSPORT)
throw new FormatException();
String key = r.readString(MAX_PROPERTY_LENGTH);
String value = r.readString(MAX_PROPERTY_LENGTH);
p.put(key, value);
}
r.readMapEnd();
remoteProps.put(id, new TransportProperties(p));
}
r.readListEnd();
if(LOG.isLoggable(INFO))
LOG.info(pluginName + " received transport properties");
return remoteProps;
}
protected void addContact(Author remoteAuthor,
Map<TransportId, TransportProperties> remoteProps, byte[] secret,
long epoch, boolean alice) throws DbException {
// Add the contact to the database
contactId = db.addContact(remoteAuthor, localAuthor.getId());
// Create and store the inbox group
byte[] salt = crypto.deriveGroupSalt(secret);
Group inbox = groupFactory.createGroup("Inbox", salt);
db.addGroup(inbox);
db.setInboxGroup(contactId, inbox);
// Store the remote transport properties
db.setRemoteProperties(contactId, remoteProps);
// Create an endpoint for each transport shared with the contact
List<TransportId> ids = new ArrayList<TransportId>();
Map<TransportId, Long> latencies = db.getTransportLatencies();
for(TransportId id : localProps.keySet()) {
if(latencies.containsKey(id) && remoteProps.containsKey(id))
ids.add(id);
}
// Assign indices to the transports deterministically and derive keys
Collections.sort(ids, TransportIdComparator.INSTANCE);
int size = ids.size();
for(int i = 0; i < size; i++) {
TransportId id = ids.get(i);
Endpoint ep = new Endpoint(contactId, id, epoch, alice);
long maxLatency = latencies.get(id);
try {
db.addEndpoint(ep);
} catch(NoSuchTransportException e) {
continue;
}
byte[] initialSecret = crypto.deriveInitialSecret(secret, i);
keyManager.endpointAdded(ep, maxLatency, initialSecret);
}
}
protected void tryToClose(DuplexTransportConnection conn,
boolean exception) {
try {
if(LOG.isLoggable(INFO)) LOG.info("Closing connection");
conn.dispose(exception, true);
} catch(IOException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
protected void reuseConnection(DuplexTransportConnection conn,
boolean alice) {
if(contactId == null) throw new IllegalStateException();
TransportId t = plugin.getId();
if(alice)
connectionDispatcher.dispatchOutgoingConnection(contactId, t, conn);
else connectionDispatcher.dispatchIncomingConnection(t, conn);
}
private static class TransportIdComparator
implements Comparator<TransportId> {
private static final TransportIdComparator INSTANCE =
new TransportIdComparator();
public int compare(TransportId t1, TransportId t2) {
byte[] b1 = t1.getBytes(), b2 = t2.getBytes();
for(int i = 0; i < UniqueId.LENGTH; i++) {
if((b1[i] & 0xff) < (b2[i] & 0xff)) return -1;
if((b1[i] & 0xff) > (b2[i] & 0xff)) return 1;
}
return 0;
}
}
}

View File

@@ -0,0 +1,263 @@
package org.briarproject.invitation;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.invitation.InvitationConstants.CONFIRMATION_TIMEOUT;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import org.briarproject.api.Author;
import org.briarproject.api.AuthorFactory;
import org.briarproject.api.AuthorId;
import org.briarproject.api.LocalAuthor;
import org.briarproject.api.TransportId;
import org.briarproject.api.TransportProperties;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.KeyManager;
import org.briarproject.api.crypto.PseudoRandom;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException;
import org.briarproject.api.invitation.InvitationListener;
import org.briarproject.api.invitation.InvitationState;
import org.briarproject.api.invitation.InvitationTask;
import org.briarproject.api.messaging.GroupFactory;
import org.briarproject.api.plugins.PluginManager;
import org.briarproject.api.plugins.duplex.DuplexPlugin;
import org.briarproject.api.serial.ReaderFactory;
import org.briarproject.api.serial.WriterFactory;
import org.briarproject.api.system.Clock;
import org.briarproject.api.transport.ConnectionDispatcher;
import org.briarproject.api.transport.ConnectionReaderFactory;
import org.briarproject.api.transport.ConnectionWriterFactory;
/** A task consisting of one or more parallel connection attempts. */
class ConnectorGroup extends Thread implements InvitationTask {
private static final Logger LOG =
Logger.getLogger(ConnectorGroup.class.getName());
private final CryptoComponent crypto;
private final DatabaseComponent db;
private final ReaderFactory readerFactory;
private final WriterFactory writerFactory;
private final ConnectionReaderFactory connectionReaderFactory;
private final ConnectionWriterFactory connectionWriterFactory;
private final AuthorFactory authorFactory;
private final GroupFactory groupFactory;
private final KeyManager keyManager;
private final ConnectionDispatcher connectionDispatcher;
private final Clock clock;
private final PluginManager pluginManager;
private final AuthorId localAuthorId;
private final int localInvitationCode, remoteInvitationCode;
private final Collection<InvitationListener> listeners;
private final AtomicBoolean connected;
private final CountDownLatch localConfirmationLatch;
/*
* All of the following require locking: this. We don't want to call the
* listeners with a lock held, but we need to avoid a race condition in
* addListener(), so the state that's accessed there after calling
* listeners.add() must be guarded by a lock.
*/
private int localConfirmationCode = -1, remoteConfirmationCode = -1;
private boolean connectionFailed = false;
private boolean localCompared = false, remoteCompared = false;
private boolean localMatched = false, remoteMatched = false;
private String remoteName = null;
ConnectorGroup(CryptoComponent crypto, DatabaseComponent db,
ReaderFactory readerFactory, WriterFactory writerFactory,
ConnectionReaderFactory connectionReaderFactory,
ConnectionWriterFactory connectionWriterFactory,
AuthorFactory authorFactory, GroupFactory groupFactory,
KeyManager keyManager, ConnectionDispatcher connectionDispatcher,
Clock clock, PluginManager pluginManager, AuthorId localAuthorId,
int localInvitationCode, int remoteInvitationCode) {
super("ConnectorGroup");
this.crypto = crypto;
this.db = db;
this.readerFactory = readerFactory;
this.writerFactory = writerFactory;
this.connectionReaderFactory = connectionReaderFactory;
this.connectionWriterFactory = connectionWriterFactory;
this.authorFactory = authorFactory;
this.groupFactory = groupFactory;
this.keyManager = keyManager;
this.connectionDispatcher = connectionDispatcher;
this.clock = clock;
this.pluginManager = pluginManager;
this.localAuthorId = localAuthorId;
this.localInvitationCode = localInvitationCode;
this.remoteInvitationCode = remoteInvitationCode;
listeners = new CopyOnWriteArrayList<InvitationListener>();
connected = new AtomicBoolean(false);
localConfirmationLatch = new CountDownLatch(1);
}
public synchronized InvitationState addListener(InvitationListener l) {
listeners.add(l);
return new InvitationState(localInvitationCode, remoteInvitationCode,
localConfirmationCode, remoteConfirmationCode, connected.get(),
connectionFailed, localCompared, remoteCompared, localMatched,
remoteMatched, remoteName);
}
public void removeListener(InvitationListener l) {
listeners.remove(l);
}
public void connect() {
start();
}
@Override
public void run() {
LocalAuthor localAuthor;
Map<TransportId, TransportProperties> localProps;
// Load the local pseudonym and transport properties
try {
localAuthor = db.getLocalAuthor(localAuthorId);
localProps = db.getLocalProperties();
} catch(DbException e) {
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
synchronized(this) {
connectionFailed = true;
}
for(InvitationListener l : listeners) l.connectionFailed();
return;
}
// Start the connection threads
Collection<Connector> connectors = new ArrayList<Connector>();
// Alice is the party with the smaller invitation code
if(localInvitationCode < remoteInvitationCode) {
for(DuplexPlugin plugin : pluginManager.getInvitationPlugins()) {
Connector c = createAliceConnector(plugin, localAuthor,
localProps);
connectors.add(c);
c.start();
}
} else {
for(DuplexPlugin plugin: pluginManager.getInvitationPlugins()) {
Connector c = createBobConnector(plugin, localAuthor,
localProps);
connectors.add(c);
c.start();
}
}
// Wait for the connection threads to finish
try {
for(Connector c : connectors) c.join();
} catch(InterruptedException e) {
if(LOG.isLoggable(WARNING))
LOG.warning("Interrupted while waiting for connectors");
}
// If none of the threads connected, inform the listeners
if(!connected.get()) {
synchronized(this) {
connectionFailed = true;
}
for(InvitationListener l : listeners) l.connectionFailed();
}
}
private Connector createAliceConnector(DuplexPlugin plugin,
LocalAuthor localAuthor,
Map<TransportId, TransportProperties> localProps) {
PseudoRandom random = crypto.getPseudoRandom(localInvitationCode,
remoteInvitationCode);
return new AliceConnector(crypto, db, readerFactory, writerFactory,
connectionReaderFactory, connectionWriterFactory, authorFactory,
groupFactory, keyManager, connectionDispatcher, clock, this,
plugin, localAuthor, localProps, random);
}
private Connector createBobConnector(DuplexPlugin plugin,
LocalAuthor localAuthor,
Map<TransportId, TransportProperties> localProps) {
PseudoRandom random = crypto.getPseudoRandom(remoteInvitationCode,
localInvitationCode);
return new BobConnector(crypto, db, readerFactory, writerFactory,
connectionReaderFactory, connectionWriterFactory, authorFactory,
groupFactory, keyManager, connectionDispatcher, clock, this,
plugin, localAuthor, localProps, random);
}
public void localConfirmationSucceeded() {
synchronized(this) {
localCompared = true;
localMatched = true;
}
localConfirmationLatch.countDown();
}
public void localConfirmationFailed() {
synchronized(this) {
localCompared = true;
localMatched = false;
}
localConfirmationLatch.countDown();
}
boolean getAndSetConnected() {
boolean redundant = connected.getAndSet(true);
if(!redundant)
for(InvitationListener l : listeners) l.connectionSucceeded();
return redundant;
}
void keyAgreementSucceeded(int localCode, int remoteCode) {
synchronized(this) {
localConfirmationCode = localCode;
remoteConfirmationCode = remoteCode;
}
for(InvitationListener l : listeners)
l.keyAgreementSucceeded(localCode, remoteCode);
}
void keyAgreementFailed() {
for(InvitationListener l : listeners) l.keyAgreementFailed();
}
boolean waitForLocalConfirmationResult() throws InterruptedException {
localConfirmationLatch.await(CONFIRMATION_TIMEOUT, MILLISECONDS);
synchronized(this) {
return localMatched;
}
}
void remoteConfirmationSucceeded() {
synchronized(this) {
remoteCompared = true;
remoteMatched = true;
}
for(InvitationListener l : listeners) l.remoteConfirmationSucceeded();
}
void remoteConfirmationFailed() {
synchronized(this) {
remoteCompared = true;
remoteMatched = false;
}
for(InvitationListener l : listeners) l.remoteConfirmationFailed();
}
void pseudonymExchangeSucceeded(Author remoteAuthor) {
String name = remoteAuthor.getName();
synchronized(this) {
remoteName = name;
}
for(InvitationListener l : listeners)
l.pseudonymExchangeSucceeded(name);
}
void pseudonymExchangeFailed() {
for(InvitationListener l : listeners) l.pseudonymExchangeFailed();
}
}

View File

@@ -0,0 +1,15 @@
package org.briarproject.invitation;
import javax.inject.Singleton;
import org.briarproject.api.invitation.InvitationTaskFactory;
import com.google.inject.AbstractModule;
public class InvitationModule extends AbstractModule {
protected void configure() {
bind(InvitationTaskFactory.class).to(
InvitationTaskFactoryImpl.class).in(Singleton.class);
}
}

View File

@@ -0,0 +1,65 @@
package org.briarproject.invitation;
import javax.inject.Inject;
import org.briarproject.api.AuthorFactory;
import org.briarproject.api.AuthorId;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.KeyManager;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.invitation.InvitationTask;
import org.briarproject.api.invitation.InvitationTaskFactory;
import org.briarproject.api.messaging.GroupFactory;
import org.briarproject.api.plugins.PluginManager;
import org.briarproject.api.serial.ReaderFactory;
import org.briarproject.api.serial.WriterFactory;
import org.briarproject.api.system.Clock;
import org.briarproject.api.transport.ConnectionDispatcher;
import org.briarproject.api.transport.ConnectionReaderFactory;
import org.briarproject.api.transport.ConnectionWriterFactory;
class InvitationTaskFactoryImpl implements InvitationTaskFactory {
private final CryptoComponent crypto;
private final DatabaseComponent db;
private final ReaderFactory readerFactory;
private final WriterFactory writerFactory;
private final ConnectionReaderFactory connectionReaderFactory;
private final ConnectionWriterFactory connectionWriterFactory;
private final AuthorFactory authorFactory;
private final GroupFactory groupFactory;
private final KeyManager keyManager;
private final ConnectionDispatcher connectionDispatcher;
private final Clock clock;
private final PluginManager pluginManager;
@Inject
InvitationTaskFactoryImpl(CryptoComponent crypto, DatabaseComponent db,
ReaderFactory readerFactory, WriterFactory writerFactory,
ConnectionReaderFactory connectionReaderFactory,
ConnectionWriterFactory connectionWriterFactory,
AuthorFactory authorFactory, GroupFactory groupFactory,
KeyManager keyManager, ConnectionDispatcher connectionDispatcher,
Clock clock, PluginManager pluginManager) {
this.crypto = crypto;
this.db = db;
this.readerFactory = readerFactory;
this.writerFactory = writerFactory;
this.connectionReaderFactory = connectionReaderFactory;
this.connectionWriterFactory = connectionWriterFactory;
this.authorFactory = authorFactory;
this.groupFactory = groupFactory;
this.keyManager = keyManager;
this.connectionDispatcher = connectionDispatcher;
this.clock = clock;
this.pluginManager = pluginManager;
}
public InvitationTask createTask(AuthorId localAuthorId, int localCode,
int remoteCode) {
return new ConnectorGroup(crypto, db, readerFactory, writerFactory,
connectionReaderFactory, connectionWriterFactory,
authorFactory, groupFactory, keyManager, connectionDispatcher,
clock, pluginManager, localAuthorId, localCode, remoteCode);
}
}