mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-14 11:49:04 +01:00
Merge branch '117-qr-contacts' into 'master'
BQP with QR codes This MR implements BQP for key agreement over short-range transports. It also implements the Android UI for using BQP with QR codes. Closes #117. See merge request !84
This commit is contained in:
@@ -9,6 +9,7 @@ import org.briarproject.event.EventModule;
|
||||
import org.briarproject.forum.ForumModule;
|
||||
import org.briarproject.identity.IdentityModule;
|
||||
import org.briarproject.invitation.InvitationModule;
|
||||
import org.briarproject.keyagreement.KeyAgreementModule;
|
||||
import org.briarproject.lifecycle.LifecycleModule;
|
||||
import org.briarproject.messaging.MessagingModule;
|
||||
import org.briarproject.plugins.PluginsModule;
|
||||
@@ -23,7 +24,8 @@ import dagger.Module;
|
||||
|
||||
@Module(includes = {DatabaseModule.class,
|
||||
CryptoModule.class, LifecycleModule.class, ReliabilityModule.class,
|
||||
MessagingModule.class, InvitationModule.class, ForumModule.class,
|
||||
MessagingModule.class, InvitationModule.class, KeyAgreementModule.class,
|
||||
ForumModule.class,
|
||||
IdentityModule.class, EventModule.class, DataModule.class,
|
||||
ContactModule.class, PropertiesModule.class, TransportModule.class,
|
||||
SyncModule.class, SettingsModule.class, ClientsModule.class,
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
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.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 StreamReaderFactory streamReaderFactory;
|
||||
private final StreamWriterFactory streamWriterFactory;
|
||||
|
||||
private ContactExchangeListener listener;
|
||||
private LocalAuthor localAuthor;
|
||||
private DuplexTransportConnection conn;
|
||||
private TransportId transportId;
|
||||
private SecretKey masterSecret;
|
||||
private boolean alice;
|
||||
|
||||
public ContactExchangeTaskImpl(AuthorFactory authorFactory,
|
||||
BdfReaderFactory bdfReaderFactory,
|
||||
BdfWriterFactory bdfWriterFactory, Clock clock,
|
||||
ConnectionManager connectionManager, ContactManager contactManager,
|
||||
CryptoComponent crypto, 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.streamReaderFactory = streamReaderFactory;
|
||||
this.streamWriterFactory = streamWriterFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startExchange(ContactExchangeListener listener,
|
||||
LocalAuthor localAuthor, SecretKey masterSecret,
|
||||
DuplexTransportConnection conn, TransportId transportId,
|
||||
boolean alice) {
|
||||
this.listener = listener;
|
||||
this.localAuthor = localAuthor;
|
||||
this.conn = conn;
|
||||
this.transportId = transportId;
|
||||
this.masterSecret = masterSecret;
|
||||
this.alice = alice;
|
||||
start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Derive the header keys for the transport streams
|
||||
SecretKey aliceHeaderKey = crypto.deriveHeaderKey(masterSecret, true);
|
||||
SecretKey bobHeaderKey = crypto.deriveHeaderKey(masterSecret, false);
|
||||
BdfReader r;
|
||||
BdfWriter w;
|
||||
try {
|
||||
// Create the readers
|
||||
InputStream streamReader =
|
||||
streamReaderFactory.createInvitationStreamReader(
|
||||
conn.getReader().getInputStream(),
|
||||
alice ? bobHeaderKey : aliceHeaderKey);
|
||||
r = bdfReaderFactory.createReader(streamReader);
|
||||
// Create the writers
|
||||
OutputStream streamWriter =
|
||||
streamWriterFactory.createInvitationStreamWriter(
|
||||
conn.getWriter().getOutputStream(),
|
||||
alice ? aliceHeaderKey : bobHeaderKey);
|
||||
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 nonces to be signed
|
||||
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, alice);
|
||||
// Reuse the connection as a transport connection
|
||||
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
|
||||
return contactManager.addContact(remoteAuthor, localAuthor.getId(),
|
||||
master, timestamp, alice, true);
|
||||
}
|
||||
|
||||
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,17 @@
|
||||
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.StreamReaderFactory;
|
||||
import org.briarproject.api.transport.StreamWriterFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -25,4 +34,16 @@ 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, StreamReaderFactory streamReaderFactory,
|
||||
StreamWriterFactory streamWriterFactory) {
|
||||
return new ContactExchangeTaskImpl(authorFactory, bdfReaderFactory,
|
||||
bdfWriterFactory, clock, connectionManager, contactManager,
|
||||
crypto, streamReaderFactory, streamWriterFactory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,24 +63,22 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
return s.getBytes(Charset.forName("US-ASCII"));
|
||||
}
|
||||
|
||||
// KDF label for bluetooth master key derivation
|
||||
private static final byte[] BT_MASTER = ascii("MASTER");
|
||||
// KDF labels for bluetooth confirmation code derivation
|
||||
private static final byte[] BT_A_CONFIRM = ascii("ALICE_CONFIRMATION_CODE");
|
||||
private static final byte[] BT_B_CONFIRM = ascii("BOB_CONFIRMATION_CODE");
|
||||
// KDF labels for bluetooth invitation stream header key derivation
|
||||
private static final byte[] BT_A_INVITE = ascii("ALICE_INVITATION_KEY");
|
||||
private static final byte[] BT_B_INVITE = ascii("BOB_INVITATION_KEY");
|
||||
// KDF labels for bluetooth signature nonce derivation
|
||||
private static final byte[] BT_A_NONCE = ascii("ALICE_SIGNATURE_NONCE");
|
||||
private static final byte[] BT_B_NONCE = ascii("BOB_SIGNATURE_NONCE");
|
||||
// KDF labels for contact exchange stream header key derivation
|
||||
private static final byte[] A_INVITE = ascii("ALICE_INVITATION_KEY");
|
||||
private static final byte[] B_INVITE = ascii("BOB_INVITATION_KEY");
|
||||
// KDF labels for contact exchange signature nonce derivation
|
||||
private static final byte[] A_SIG_NONCE = ascii("ALICE_SIGNATURE_NONCE");
|
||||
private static final byte[] B_SIG_NONCE = ascii("BOB_SIGNATURE_NONCE");
|
||||
// Hash label for BQP public key commitment derivation
|
||||
private static final byte[] COMMIT = ascii("COMMIT");
|
||||
// Hash label for BQP shared secret derivation
|
||||
// Hash label for shared secret derivation
|
||||
private static final byte[] SHARED_SECRET = ascii("SHARED_SECRET");
|
||||
// KDF label for BQP confirmation key derivation
|
||||
private static final byte[] CONFIRMATION_KEY = ascii("CONFIRMATION_KEY");
|
||||
// KDF label for BQP master key derivation
|
||||
// KDF label for master key derivation
|
||||
private static final byte[] MASTER_KEY = ascii("MASTER_KEY");
|
||||
// KDF labels for tag key derivation
|
||||
private static final byte[] A_TAG = ascii("ALICE_TAG_KEY");
|
||||
@@ -210,12 +208,14 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
return ByteUtils.readUint(b, CODE_BITS);
|
||||
}
|
||||
|
||||
public SecretKey deriveBTInvitationKey(SecretKey master, boolean alice) {
|
||||
return new SecretKey(macKdf(master, alice ? BT_A_INVITE : BT_B_INVITE));
|
||||
public SecretKey deriveHeaderKey(SecretKey master,
|
||||
boolean alice) {
|
||||
return new SecretKey(macKdf(master, alice ? A_INVITE : B_INVITE));
|
||||
}
|
||||
|
||||
public byte[] deriveBTSignatureNonce(SecretKey master, boolean alice) {
|
||||
return macKdf(master, alice ? BT_A_NONCE : BT_B_NONCE);
|
||||
public byte[] deriveSignatureNonce(SecretKey master,
|
||||
boolean alice) {
|
||||
return macKdf(master, alice ? A_SIG_NONCE : B_SIG_NONCE);
|
||||
}
|
||||
|
||||
public byte[] deriveKeyCommitment(byte[] publicKey) {
|
||||
@@ -438,29 +438,6 @@ class CryptoComponentImpl implements CryptoComponent {
|
||||
}
|
||||
}
|
||||
|
||||
// Key derivation function based on a hash function - see NIST SP 800-56A,
|
||||
// section 5.8
|
||||
private byte[] hashKdf(byte[]... inputs) {
|
||||
Digest digest = new Blake2sDigest();
|
||||
// The output of the hash function must be long enough to use as a key
|
||||
int hashLength = digest.getDigestSize();
|
||||
if (hashLength < SecretKey.LENGTH) throw new IllegalStateException();
|
||||
// Calculate the hash over the concatenated length-prefixed inputs
|
||||
byte[] length = new byte[INT_32_BYTES];
|
||||
for (byte[] input : inputs) {
|
||||
ByteUtils.writeUint32(input.length, length, 0);
|
||||
digest.update(length, 0, length.length);
|
||||
digest.update(input, 0, input.length);
|
||||
}
|
||||
byte[] hash = new byte[hashLength];
|
||||
digest.doFinal(hash, 0);
|
||||
// The output is the first SecretKey.LENGTH bytes of the hash
|
||||
if (hash.length == SecretKey.LENGTH) return hash;
|
||||
byte[] truncated = new byte[SecretKey.LENGTH];
|
||||
System.arraycopy(hash, 0, truncated, 0, truncated.length);
|
||||
return truncated;
|
||||
}
|
||||
|
||||
// Key derivation function based on a pseudo-random function - see
|
||||
// NIST SP 800-108, section 5.1
|
||||
private byte[] macKdf(SecretKey key, byte[]... inputs) {
|
||||
|
||||
@@ -125,8 +125,8 @@ class AliceConnector extends Connector {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " confirmation succeeded");
|
||||
// Derive the header keys
|
||||
SecretKey aliceHeaderKey = crypto.deriveBTInvitationKey(master, true);
|
||||
SecretKey bobHeaderKey = crypto.deriveBTInvitationKey(master, false);
|
||||
SecretKey aliceHeaderKey = crypto.deriveHeaderKey(master, true);
|
||||
SecretKey bobHeaderKey = crypto.deriveHeaderKey(master, false);
|
||||
// Create the readers
|
||||
InputStream streamReader =
|
||||
streamReaderFactory.createInvitationStreamReader(in,
|
||||
@@ -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;
|
||||
|
||||
@@ -125,8 +125,10 @@ class BobConnector extends Connector {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(pluginName + " confirmation succeeded");
|
||||
// Derive the header keys
|
||||
SecretKey aliceHeaderKey = crypto.deriveBTInvitationKey(master, true);
|
||||
SecretKey bobHeaderKey = crypto.deriveBTInvitationKey(master, false);
|
||||
SecretKey aliceHeaderKey = crypto.deriveHeaderKey(master,
|
||||
true);
|
||||
SecretKey bobHeaderKey = crypto.deriveHeaderKey(master,
|
||||
false);
|
||||
// Create the readers
|
||||
InputStream streamReader =
|
||||
streamReaderFactory.createInvitationStreamReader(in,
|
||||
@@ -138,8 +140,10 @@ 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;
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.briarproject.keyagreement;
|
||||
|
||||
class AbortException extends Exception {
|
||||
public boolean receivedAbort;
|
||||
|
||||
public AbortException() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
public AbortException(boolean receivedAbort) {
|
||||
super();
|
||||
this.receivedAbort = receivedAbort;
|
||||
}
|
||||
|
||||
public AbortException(Exception e) {
|
||||
this(e, false);
|
||||
}
|
||||
|
||||
public AbortException(Exception e, boolean receivedAbort) {
|
||||
super(e);
|
||||
this.receivedAbort = receivedAbort;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
package org.briarproject.keyagreement;
|
||||
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.KeyPair;
|
||||
import org.briarproject.api.keyagreement.KeyAgreementConnection;
|
||||
import org.briarproject.api.keyagreement.KeyAgreementListener;
|
||||
import org.briarproject.api.keyagreement.Payload;
|
||||
import org.briarproject.api.keyagreement.TransportDescriptor;
|
||||
import org.briarproject.api.plugins.PluginManager;
|
||||
import org.briarproject.api.plugins.duplex.DuplexPlugin;
|
||||
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.api.system.Clock;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletionService;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorCompletionService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.api.keyagreement.KeyAgreementConstants.CONNECTION_TIMEOUT;
|
||||
|
||||
class KeyAgreementConnector {
|
||||
|
||||
interface Callbacks {
|
||||
void connectionWaiting();
|
||||
}
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(KeyAgreementConnector.class.getName());
|
||||
|
||||
private final Callbacks callbacks;
|
||||
private final Clock clock;
|
||||
private final CryptoComponent crypto;
|
||||
private final PluginManager pluginManager;
|
||||
private final CompletionService<KeyAgreementConnection> connect;
|
||||
|
||||
private final List<KeyAgreementListener> listeners =
|
||||
new ArrayList<KeyAgreementListener>();
|
||||
private final List<Future<KeyAgreementConnection>> pending =
|
||||
new ArrayList<Future<KeyAgreementConnection>>();
|
||||
|
||||
private volatile boolean connecting = false;
|
||||
private volatile boolean alice = false;
|
||||
|
||||
public KeyAgreementConnector(Callbacks callbacks, Clock clock,
|
||||
CryptoComponent crypto, PluginManager pluginManager,
|
||||
Executor ioExecutor) {
|
||||
this.callbacks = callbacks;
|
||||
this.clock = clock;
|
||||
this.crypto = crypto;
|
||||
this.pluginManager = pluginManager;
|
||||
connect = new ExecutorCompletionService<KeyAgreementConnection>(
|
||||
ioExecutor);
|
||||
}
|
||||
|
||||
public Payload listen(KeyPair localKeyPair) {
|
||||
LOG.info("Starting BQP listeners");
|
||||
// Derive commitment
|
||||
byte[] commitment = crypto.deriveKeyCommitment(
|
||||
localKeyPair.getPublic().getEncoded());
|
||||
// Start all listeners and collect their descriptors
|
||||
List<TransportDescriptor> descriptors =
|
||||
new ArrayList<TransportDescriptor>();
|
||||
for (DuplexPlugin plugin : pluginManager.getKeyAgreementPlugins()) {
|
||||
KeyAgreementListener l = plugin.createKeyAgreementListener(
|
||||
commitment);
|
||||
if (l != null) {
|
||||
TransportDescriptor d = l.getDescriptor();
|
||||
descriptors.add(d);
|
||||
pending.add(connect.submit(new ReadableTask(l.listen())));
|
||||
listeners.add(l);
|
||||
}
|
||||
}
|
||||
return new Payload(commitment, descriptors);
|
||||
}
|
||||
|
||||
public void stopListening() {
|
||||
LOG.info("Stopping BQP listeners");
|
||||
for (KeyAgreementListener l : listeners) {
|
||||
l.close();
|
||||
}
|
||||
listeners.clear();
|
||||
}
|
||||
|
||||
public KeyAgreementTransport connect(Payload remotePayload,
|
||||
boolean alice) {
|
||||
// Let the listeners know if we are Alice
|
||||
this.connecting = true;
|
||||
this.alice = alice;
|
||||
long end = clock.currentTimeMillis() + CONNECTION_TIMEOUT;
|
||||
|
||||
// Start connecting over supported transports
|
||||
LOG.info("Starting outgoing BQP connections");
|
||||
for (TransportDescriptor d : remotePayload.getTransportDescriptors()) {
|
||||
DuplexPlugin plugin = (DuplexPlugin) pluginManager.getPlugin(
|
||||
d.getIdentifier());
|
||||
if (plugin != null)
|
||||
pending.add(connect.submit(new ReadableTask(
|
||||
new ConnectorTask(plugin, remotePayload.getCommitment(),
|
||||
d, end))));
|
||||
}
|
||||
|
||||
// Get chosen connection
|
||||
KeyAgreementConnection chosen = null;
|
||||
try {
|
||||
long now = clock.currentTimeMillis();
|
||||
Future<KeyAgreementConnection> f =
|
||||
connect.poll(end - now, MILLISECONDS);
|
||||
if (f == null)
|
||||
return null; // No task completed within the timeout.
|
||||
chosen = f.get();
|
||||
return new KeyAgreementTransport(chosen);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.info("Interrupted while waiting for connection");
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
} catch (ExecutionException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
return null;
|
||||
} finally {
|
||||
stopListening();
|
||||
// Close all other connections
|
||||
closePending(chosen);
|
||||
}
|
||||
}
|
||||
|
||||
private void closePending(KeyAgreementConnection chosen) {
|
||||
for (Future<KeyAgreementConnection> f : pending) {
|
||||
try {
|
||||
if (f.cancel(true))
|
||||
LOG.info("Cancelled task");
|
||||
else if (!f.isCancelled()) {
|
||||
KeyAgreementConnection c = f.get();
|
||||
if (c != null && c != chosen)
|
||||
tryToClose(c.getConnection(), false);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOG.info("Interrupted while closing sockets");
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
} catch (ExecutionException e) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void tryToClose(DuplexTransportConnection conn, boolean exception) {
|
||||
try {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Closing connection, exception: " + exception);
|
||||
conn.getReader().dispose(exception, true);
|
||||
conn.getWriter().dispose(exception);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private class ConnectorTask implements Callable<KeyAgreementConnection> {
|
||||
|
||||
private final byte[] commitment;
|
||||
private final TransportDescriptor descriptor;
|
||||
private final long end;
|
||||
private final DuplexPlugin plugin;
|
||||
|
||||
private ConnectorTask(DuplexPlugin plugin, byte[] commitment,
|
||||
TransportDescriptor descriptor, long end) {
|
||||
this.plugin = plugin;
|
||||
this.commitment = commitment;
|
||||
this.descriptor = descriptor;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyAgreementConnection call() throws Exception {
|
||||
// Repeat attempts until we connect or get interrupted
|
||||
while (true) {
|
||||
long now = clock.currentTimeMillis();
|
||||
DuplexTransportConnection conn =
|
||||
plugin.createKeyAgreementConnection(commitment,
|
||||
descriptor, end - now);
|
||||
if (conn != null) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(plugin.getId().getString() +
|
||||
": Outgoing connection");
|
||||
return new KeyAgreementConnection(conn, plugin.getId());
|
||||
}
|
||||
// Wait 2s before retry (to circumvent transient failures)
|
||||
Thread.sleep(2000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ReadableTask
|
||||
implements Callable<KeyAgreementConnection> {
|
||||
|
||||
private final Callable<KeyAgreementConnection> connectionTask;
|
||||
|
||||
private ReadableTask(Callable<KeyAgreementConnection> connectionTask) {
|
||||
this.connectionTask = connectionTask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyAgreementConnection call()
|
||||
throws Exception {
|
||||
KeyAgreementConnection c = connectionTask.call();
|
||||
InputStream in = c.getConnection().getReader().getInputStream();
|
||||
boolean waitingSent = false;
|
||||
while (!alice && in.available() == 0) {
|
||||
if (!waitingSent && connecting && !alice) {
|
||||
// Bob waits here until Alice obtains his payload.
|
||||
callbacks.connectionWaiting();
|
||||
waitingSent = true;
|
||||
}
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(c.getTransportId().toString() +
|
||||
": Waiting for connection");
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
if (!alice && LOG.isLoggable(INFO))
|
||||
LOG.info(c.getTransportId().toString() + ": Data available");
|
||||
return c;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.briarproject.keyagreement;
|
||||
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.data.BdfReaderFactory;
|
||||
import org.briarproject.api.data.BdfWriterFactory;
|
||||
import org.briarproject.api.event.EventBus;
|
||||
import org.briarproject.api.keyagreement.KeyAgreementTaskFactory;
|
||||
import org.briarproject.api.keyagreement.PayloadEncoder;
|
||||
import org.briarproject.api.keyagreement.PayloadParser;
|
||||
import org.briarproject.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.api.plugins.PluginManager;
|
||||
import org.briarproject.api.system.Clock;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@Module
|
||||
public class KeyAgreementModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
KeyAgreementTaskFactory provideKeyAgreementTaskFactory(Clock clock,
|
||||
CryptoComponent crypto, EventBus eventBus,
|
||||
@IoExecutor Executor ioExecutor, PayloadEncoder payloadEncoder,
|
||||
PluginManager pluginManager) {
|
||||
return new KeyAgreementTaskFactoryImpl(clock, crypto, eventBus,
|
||||
ioExecutor, payloadEncoder, pluginManager);
|
||||
}
|
||||
|
||||
@Provides
|
||||
PayloadEncoder providePayloadEncoder(BdfWriterFactory bdfWriterFactory) {
|
||||
return new PayloadEncoderImpl(bdfWriterFactory);
|
||||
}
|
||||
|
||||
@Provides
|
||||
PayloadParser providePayloadParser(BdfReaderFactory bdfReaderFactory) {
|
||||
return new PayloadParserImpl(bdfReaderFactory);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package org.briarproject.keyagreement;
|
||||
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.KeyPair;
|
||||
import org.briarproject.api.crypto.SecretKey;
|
||||
import org.briarproject.api.keyagreement.Payload;
|
||||
import org.briarproject.api.keyagreement.PayloadEncoder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Implementation of the BQP protocol.
|
||||
* <p/>
|
||||
* Alice:
|
||||
* <ul>
|
||||
* <li>Send A_KEY</li>
|
||||
* <li>Receive B_KEY
|
||||
* <ul>
|
||||
* <li>Check B_KEY matches B_COMMIT</li>
|
||||
* </ul></li>
|
||||
* <li>Calculate s</li>
|
||||
* <li>Send A_CONFIRM</li>
|
||||
* <li>Receive B_CONFIRM
|
||||
* <ul>
|
||||
* <li>Check B_CONFIRM matches expected</li>
|
||||
* </ul></li>
|
||||
* <li>Derive master</li>
|
||||
* </ul><p/>
|
||||
* Bob:
|
||||
* <ul>
|
||||
* <li>Receive A_KEY
|
||||
* <ul>
|
||||
* <li>Check A_KEY matches A_COMMIT</li>
|
||||
* </ul></li>
|
||||
* <li>Send B_KEY</li>
|
||||
* <li>Calculate s</li>
|
||||
* <li>Receive A_CONFIRM
|
||||
* <ul>
|
||||
* <li>Check A_CONFIRM matches expected</li>
|
||||
* </ul></li>
|
||||
* <li>Send B_CONFIRM</li>
|
||||
* <li>Derive master</li>
|
||||
* </ul>
|
||||
*/
|
||||
class KeyAgreementProtocol {
|
||||
|
||||
interface Callbacks {
|
||||
void connectionWaiting();
|
||||
void initialPacketReceived();
|
||||
}
|
||||
|
||||
private Callbacks callbacks;
|
||||
private CryptoComponent crypto;
|
||||
private PayloadEncoder payloadEncoder;
|
||||
private KeyAgreementTransport transport;
|
||||
private Payload theirPayload, ourPayload;
|
||||
private KeyPair ourKeyPair;
|
||||
private boolean alice;
|
||||
|
||||
public KeyAgreementProtocol(Callbacks callbacks, CryptoComponent crypto,
|
||||
PayloadEncoder payloadEncoder, KeyAgreementTransport transport,
|
||||
Payload theirPayload, Payload ourPayload, KeyPair ourKeyPair,
|
||||
boolean alice) {
|
||||
this.callbacks = callbacks;
|
||||
this.crypto = crypto;
|
||||
this.payloadEncoder = payloadEncoder;
|
||||
this.transport = transport;
|
||||
this.theirPayload = theirPayload;
|
||||
this.ourPayload = ourPayload;
|
||||
this.ourKeyPair = ourKeyPair;
|
||||
this.alice = alice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the BQP protocol.
|
||||
*
|
||||
* @return the negotiated master secret.
|
||||
* @throws AbortException when the protocol may have been tampered with.
|
||||
* @throws IOException for all other other connection errors.
|
||||
*/
|
||||
public SecretKey perform() throws AbortException, IOException {
|
||||
try {
|
||||
byte[] theirPublicKey;
|
||||
if (alice) {
|
||||
sendKey();
|
||||
// Alice waits here until Bob obtains her payload.
|
||||
callbacks.connectionWaiting();
|
||||
theirPublicKey = receiveKey();
|
||||
} else {
|
||||
theirPublicKey = receiveKey();
|
||||
sendKey();
|
||||
}
|
||||
SecretKey s = deriveSharedSecret(theirPublicKey);
|
||||
if (alice) {
|
||||
sendConfirm(s, theirPublicKey);
|
||||
receiveConfirm(s, theirPublicKey);
|
||||
} else {
|
||||
receiveConfirm(s, theirPublicKey);
|
||||
sendConfirm(s, theirPublicKey);
|
||||
}
|
||||
return crypto.deriveMasterSecret(s);
|
||||
} catch (AbortException e) {
|
||||
sendAbort(e.getCause() != null);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void sendKey() throws IOException {
|
||||
transport.sendKey(ourKeyPair.getPublic().getEncoded());
|
||||
}
|
||||
|
||||
private byte[] receiveKey() throws AbortException {
|
||||
byte[] publicKey = transport.receiveKey();
|
||||
callbacks.initialPacketReceived();
|
||||
byte[] expected = crypto.deriveKeyCommitment(publicKey);
|
||||
if (!Arrays.equals(expected, theirPayload.getCommitment()))
|
||||
throw new AbortException();
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
private SecretKey deriveSharedSecret(byte[] theirPublicKey)
|
||||
throws AbortException {
|
||||
try {
|
||||
return crypto.deriveSharedSecret(theirPublicKey, ourKeyPair, alice);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new AbortException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendConfirm(SecretKey s, byte[] theirPublicKey)
|
||||
throws IOException {
|
||||
byte[] confirm = crypto.deriveConfirmationRecord(s,
|
||||
payloadEncoder.encode(theirPayload),
|
||||
payloadEncoder.encode(ourPayload),
|
||||
theirPublicKey, ourKeyPair,
|
||||
alice, alice);
|
||||
transport.sendConfirm(confirm);
|
||||
}
|
||||
|
||||
private void receiveConfirm(SecretKey s, byte[] theirPublicKey)
|
||||
throws AbortException {
|
||||
byte[] confirm = transport.receiveConfirm();
|
||||
byte[] expected = crypto.deriveConfirmationRecord(s,
|
||||
payloadEncoder.encode(theirPayload),
|
||||
payloadEncoder.encode(ourPayload),
|
||||
theirPublicKey, ourKeyPair,
|
||||
alice, !alice);
|
||||
if (!Arrays.equals(expected, confirm))
|
||||
throw new AbortException();
|
||||
}
|
||||
|
||||
private void sendAbort(boolean exception) {
|
||||
transport.sendAbort(exception);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.briarproject.keyagreement;
|
||||
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.event.Event;
|
||||
import org.briarproject.api.event.EventBus;
|
||||
import org.briarproject.api.event.EventListener;
|
||||
import org.briarproject.api.event.KeyAgreementAbortedEvent;
|
||||
import org.briarproject.api.event.KeyAgreementFailedEvent;
|
||||
import org.briarproject.api.event.KeyAgreementFinishedEvent;
|
||||
import org.briarproject.api.keyagreement.KeyAgreementTask;
|
||||
import org.briarproject.api.keyagreement.KeyAgreementTaskFactory;
|
||||
import org.briarproject.api.keyagreement.PayloadEncoder;
|
||||
import org.briarproject.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.api.plugins.PluginManager;
|
||||
import org.briarproject.api.system.Clock;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
class KeyAgreementTaskFactoryImpl implements KeyAgreementTaskFactory {
|
||||
|
||||
private final Clock clock;
|
||||
private final CryptoComponent crypto;
|
||||
private final EventBus eventBus;
|
||||
private final Executor ioExecutor;
|
||||
private final PayloadEncoder payloadEncoder;
|
||||
private final PluginManager pluginManager;
|
||||
|
||||
@Inject
|
||||
KeyAgreementTaskFactoryImpl(Clock clock, CryptoComponent crypto,
|
||||
EventBus eventBus, @IoExecutor Executor ioExecutor,
|
||||
PayloadEncoder payloadEncoder, PluginManager pluginManager) {
|
||||
this.clock = clock;
|
||||
this.crypto = crypto;
|
||||
this.eventBus = eventBus;
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.payloadEncoder = payloadEncoder;
|
||||
this.pluginManager = pluginManager;
|
||||
}
|
||||
|
||||
public KeyAgreementTask getTask() {
|
||||
return new KeyAgreementTaskImpl(clock, crypto, eventBus, payloadEncoder,
|
||||
pluginManager, ioExecutor);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package org.briarproject.keyagreement;
|
||||
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.KeyPair;
|
||||
import org.briarproject.api.crypto.SecretKey;
|
||||
import org.briarproject.api.event.EventBus;
|
||||
import org.briarproject.api.event.KeyAgreementAbortedEvent;
|
||||
import org.briarproject.api.event.KeyAgreementFailedEvent;
|
||||
import org.briarproject.api.event.KeyAgreementFinishedEvent;
|
||||
import org.briarproject.api.event.KeyAgreementListeningEvent;
|
||||
import org.briarproject.api.event.KeyAgreementStartedEvent;
|
||||
import org.briarproject.api.event.KeyAgreementWaitingEvent;
|
||||
import org.briarproject.api.keyagreement.KeyAgreementResult;
|
||||
import org.briarproject.api.keyagreement.KeyAgreementTask;
|
||||
import org.briarproject.api.keyagreement.Payload;
|
||||
import org.briarproject.api.keyagreement.PayloadEncoder;
|
||||
import org.briarproject.api.plugins.PluginManager;
|
||||
import org.briarproject.api.system.Clock;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
class KeyAgreementTaskImpl extends Thread implements
|
||||
KeyAgreementTask, KeyAgreementConnector.Callbacks,
|
||||
KeyAgreementProtocol.Callbacks {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(KeyAgreementTaskImpl.class.getName());
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
private final EventBus eventBus;
|
||||
private final PayloadEncoder payloadEncoder;
|
||||
private final KeyPair localKeyPair;
|
||||
private final KeyAgreementConnector connector;
|
||||
|
||||
private Payload localPayload;
|
||||
private Payload remotePayload;
|
||||
|
||||
public KeyAgreementTaskImpl(Clock clock, CryptoComponent crypto,
|
||||
EventBus eventBus, PayloadEncoder payloadEncoder,
|
||||
PluginManager pluginManager, Executor ioExecutor) {
|
||||
this.crypto = crypto;
|
||||
this.eventBus = eventBus;
|
||||
this.payloadEncoder = payloadEncoder;
|
||||
localKeyPair = crypto.generateAgreementKeyPair();
|
||||
connector = new KeyAgreementConnector(this, clock, crypto,
|
||||
pluginManager, ioExecutor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void listen() {
|
||||
if (localPayload == null) {
|
||||
localPayload = connector.listen(localKeyPair);
|
||||
eventBus.broadcast(new KeyAgreementListeningEvent(localPayload));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void stopListening() {
|
||||
if (localPayload != null) {
|
||||
if (remotePayload == null)
|
||||
connector.stopListening();
|
||||
else
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void connectAndRunProtocol(Payload remotePayload) {
|
||||
if (this.localPayload == null)
|
||||
throw new IllegalStateException(
|
||||
"Must listen before connecting");
|
||||
if (this.remotePayload != null)
|
||||
throw new IllegalStateException(
|
||||
"Already provided remote payload for this task");
|
||||
this.remotePayload = remotePayload;
|
||||
start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
boolean alice = localPayload.compareTo(remotePayload) < 0;
|
||||
|
||||
// Open connection to remote device
|
||||
KeyAgreementTransport transport =
|
||||
connector.connect(remotePayload, alice);
|
||||
if (transport == null) {
|
||||
// Notify caller that the connection failed
|
||||
eventBus.broadcast(new KeyAgreementFailedEvent());
|
||||
return;
|
||||
}
|
||||
|
||||
// Run BQP protocol over the connection
|
||||
LOG.info("Starting BQP protocol");
|
||||
KeyAgreementProtocol protocol = new KeyAgreementProtocol(this, crypto,
|
||||
payloadEncoder, transport, remotePayload, localPayload,
|
||||
localKeyPair, alice);
|
||||
try {
|
||||
SecretKey master = protocol.perform();
|
||||
KeyAgreementResult result =
|
||||
new KeyAgreementResult(master, transport.getConnection(),
|
||||
transport.getTransportId(), alice);
|
||||
LOG.info("Finished BQP protocol");
|
||||
// Broadcast result to caller
|
||||
eventBus.broadcast(new KeyAgreementFinishedEvent(result));
|
||||
} catch (AbortException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
// Notify caller that the protocol was aborted
|
||||
eventBus.broadcast(new KeyAgreementAbortedEvent(e.receivedAbort));
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
// Notify caller that the connection failed
|
||||
eventBus.broadcast(new KeyAgreementFailedEvent());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionWaiting() {
|
||||
eventBus.broadcast(new KeyAgreementWaitingEvent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialPacketReceived() {
|
||||
// We send this here instead of when we create the protocol, so that
|
||||
// if device A makes a connection after getting device B's payload and
|
||||
// starts its protocol, device A's UI doesn't change to prevent device B
|
||||
// from getting device A's payload.
|
||||
eventBus.broadcast(new KeyAgreementStartedEvent());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package org.briarproject.keyagreement;
|
||||
|
||||
import org.briarproject.api.TransportId;
|
||||
import org.briarproject.api.keyagreement.KeyAgreementConnection;
|
||||
import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.util.ByteUtils;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.api.keyagreement.KeyAgreementConstants.RECORD_HEADER_LENGTH;
|
||||
import static org.briarproject.api.keyagreement.KeyAgreementConstants.RECORD_HEADER_PAYLOAD_LENGTH_OFFSET;
|
||||
import static org.briarproject.api.keyagreement.RecordTypes.ABORT;
|
||||
import static org.briarproject.api.keyagreement.RecordTypes.CONFIRM;
|
||||
import static org.briarproject.api.keyagreement.RecordTypes.KEY;
|
||||
|
||||
/**
|
||||
* Handles the sending and receiving of BQP records.
|
||||
*/
|
||||
class KeyAgreementTransport {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(KeyAgreementTransport.class.getName());
|
||||
|
||||
private final KeyAgreementConnection kac;
|
||||
private final InputStream in;
|
||||
private final OutputStream out;
|
||||
|
||||
public KeyAgreementTransport(KeyAgreementConnection kac)
|
||||
throws IOException {
|
||||
this.kac = kac;
|
||||
in = kac.getConnection().getReader().getInputStream();
|
||||
out = kac.getConnection().getWriter().getOutputStream();
|
||||
}
|
||||
|
||||
public DuplexTransportConnection getConnection() {
|
||||
return kac.getConnection();
|
||||
}
|
||||
|
||||
public TransportId getTransportId() {
|
||||
return kac.getTransportId();
|
||||
}
|
||||
|
||||
public void sendKey(byte[] key) throws IOException {
|
||||
writeRecord(KEY, key);
|
||||
}
|
||||
|
||||
public byte[] receiveKey() throws AbortException {
|
||||
return readRecord(KEY);
|
||||
}
|
||||
|
||||
public void sendConfirm(byte[] confirm) throws IOException {
|
||||
writeRecord(CONFIRM, confirm);
|
||||
}
|
||||
|
||||
public byte[] receiveConfirm() throws AbortException {
|
||||
return readRecord(CONFIRM);
|
||||
}
|
||||
|
||||
public void sendAbort(boolean exception) {
|
||||
try {
|
||||
writeRecord(ABORT, new byte[0]);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
exception = true;
|
||||
}
|
||||
tryToClose(exception);
|
||||
}
|
||||
|
||||
public void tryToClose(boolean exception) {
|
||||
try {
|
||||
LOG.info("Closing connection");
|
||||
kac.getConnection().getReader().dispose(exception, true);
|
||||
kac.getConnection().getWriter().dispose(exception);
|
||||
} catch (IOException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeRecord(byte type, byte[] payload) throws IOException {
|
||||
byte[] recordHeader = new byte[RECORD_HEADER_LENGTH];
|
||||
recordHeader[0] = PROTOCOL_VERSION;
|
||||
recordHeader[1] = type;
|
||||
ByteUtils.writeUint16(payload.length, recordHeader,
|
||||
RECORD_HEADER_PAYLOAD_LENGTH_OFFSET);
|
||||
out.write(recordHeader);
|
||||
out.write(payload);
|
||||
out.flush();
|
||||
}
|
||||
|
||||
private byte[] readRecord(byte type) throws AbortException {
|
||||
byte[] header = readHeader();
|
||||
if (header[0] != PROTOCOL_VERSION)
|
||||
throw new AbortException(); // TODO handle?
|
||||
if (header[1] != type) {
|
||||
// Unexpected packet
|
||||
throw new AbortException(header[1] == ABORT);
|
||||
}
|
||||
int len = ByteUtils.readUint16(header,
|
||||
RECORD_HEADER_PAYLOAD_LENGTH_OFFSET);
|
||||
try {
|
||||
return readData(len);
|
||||
} catch (IOException e) {
|
||||
throw new AbortException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] readHeader() throws AbortException {
|
||||
try {
|
||||
return readData(RECORD_HEADER_LENGTH);
|
||||
} catch (IOException e) {
|
||||
throw new AbortException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] readData(int len) throws IOException {
|
||||
byte[] data = new byte[len];
|
||||
int offset = 0;
|
||||
while (offset < data.length) {
|
||||
int read = in.read(data, offset, data.length - offset);
|
||||
if (read == -1) throw new EOFException();
|
||||
offset += read;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.briarproject.keyagreement;
|
||||
|
||||
import org.briarproject.api.data.BdfWriter;
|
||||
import org.briarproject.api.data.BdfWriterFactory;
|
||||
import org.briarproject.api.keyagreement.Payload;
|
||||
import org.briarproject.api.keyagreement.PayloadEncoder;
|
||||
import org.briarproject.api.keyagreement.TransportDescriptor;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
|
||||
|
||||
class PayloadEncoderImpl implements PayloadEncoder {
|
||||
|
||||
private final BdfWriterFactory bdfWriterFactory;
|
||||
|
||||
@Inject
|
||||
public PayloadEncoderImpl(BdfWriterFactory bdfWriterFactory) {
|
||||
this.bdfWriterFactory = bdfWriterFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encode(Payload p) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
BdfWriter w = bdfWriterFactory.createWriter(out);
|
||||
try {
|
||||
w.writeListStart(); // Payload start
|
||||
w.writeLong(PROTOCOL_VERSION);
|
||||
w.writeRaw(p.getCommitment());
|
||||
w.writeListStart(); // Descriptors start
|
||||
for (TransportDescriptor d : p.getTransportDescriptors()) {
|
||||
w.writeListStart();
|
||||
w.writeString(d.getIdentifier().getString());
|
||||
w.writeDictionary(d.getProperties());
|
||||
w.writeListEnd();
|
||||
}
|
||||
w.writeListEnd(); // Descriptors end
|
||||
w.writeListEnd(); // Payload end
|
||||
} catch (IOException e) {
|
||||
// Shouldn't happen with ByteArrayOutputStream
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package org.briarproject.keyagreement;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.TransportId;
|
||||
import org.briarproject.api.data.BdfReader;
|
||||
import org.briarproject.api.data.BdfReaderFactory;
|
||||
import org.briarproject.api.keyagreement.Payload;
|
||||
import org.briarproject.api.keyagreement.PayloadParser;
|
||||
import org.briarproject.api.keyagreement.TransportDescriptor;
|
||||
import org.briarproject.api.properties.TransportProperties;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
|
||||
import static org.briarproject.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
|
||||
|
||||
class PayloadParserImpl implements PayloadParser {
|
||||
|
||||
private final BdfReaderFactory bdfReaderFactory;
|
||||
|
||||
@Inject
|
||||
public PayloadParserImpl(BdfReaderFactory bdfReaderFactory) {
|
||||
this.bdfReaderFactory = bdfReaderFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Payload parse(byte[] raw) throws IOException {
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(raw);
|
||||
BdfReader r = bdfReaderFactory.createReader(in);
|
||||
r.readListStart(); // Payload start
|
||||
int proto = (int) r.readLong();
|
||||
if (proto != PROTOCOL_VERSION)
|
||||
throw new FormatException();
|
||||
byte[] commitment = r.readRaw(COMMIT_LENGTH);
|
||||
if (commitment.length != COMMIT_LENGTH)
|
||||
throw new FormatException();
|
||||
List<TransportDescriptor> descriptors = new ArrayList<TransportDescriptor>();
|
||||
r.readListStart(); // Descriptors start
|
||||
while (r.hasList()) {
|
||||
r.readListStart();
|
||||
while (!r.hasListEnd()) {
|
||||
TransportId id =
|
||||
new TransportId(r.readString(MAX_PROPERTY_LENGTH));
|
||||
TransportProperties p = new TransportProperties();
|
||||
r.readDictionaryStart();
|
||||
while (!r.hasDictionaryEnd()) {
|
||||
String key = r.readString(MAX_PROPERTY_LENGTH);
|
||||
String value = r.readString(MAX_PROPERTY_LENGTH);
|
||||
p.put(key, value);
|
||||
}
|
||||
r.readDictionaryEnd();
|
||||
descriptors.add(new TransportDescriptor(id, p));
|
||||
}
|
||||
r.readListEnd();
|
||||
}
|
||||
r.readListEnd(); // Descriptors end
|
||||
r.readListEnd(); // Payload end
|
||||
if (!r.eof())
|
||||
throw new FormatException();
|
||||
return new Payload(commitment, descriptors);
|
||||
}
|
||||
}
|
||||
@@ -149,6 +149,13 @@ class PluginManagerImpl implements PluginManager, Service {
|
||||
return Collections.unmodifiableList(supported);
|
||||
}
|
||||
|
||||
public Collection<DuplexPlugin> getKeyAgreementPlugins() {
|
||||
List<DuplexPlugin> supported = new ArrayList<DuplexPlugin>();
|
||||
for (DuplexPlugin d : duplexPlugins)
|
||||
if (d.supportsKeyAgreement()) supported.add(d);
|
||||
return Collections.unmodifiableList(supported);
|
||||
}
|
||||
|
||||
private class SimplexPluginStarter implements Runnable {
|
||||
|
||||
private final SimplexPluginFactory factory;
|
||||
|
||||
@@ -2,6 +2,8 @@ package org.briarproject.plugins.tcp;
|
||||
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.crypto.PseudoRandom;
|
||||
import org.briarproject.api.keyagreement.KeyAgreementListener;
|
||||
import org.briarproject.api.keyagreement.TransportDescriptor;
|
||||
import org.briarproject.api.plugins.Backoff;
|
||||
import org.briarproject.api.plugins.duplex.DuplexPlugin;
|
||||
import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
|
||||
@@ -250,6 +252,20 @@ abstract class TcpPlugin implements DuplexPlugin {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public boolean supportsKeyAgreement() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public KeyAgreementListener createKeyAgreementListener(
|
||||
byte[] commitment) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public DuplexTransportConnection createKeyAgreementConnection(
|
||||
byte[] commitment, TransportDescriptor d, long timeout) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
protected Collection<InetAddress> getLocalIpAddresses() {
|
||||
List<NetworkInterface> ifaces;
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user