diff --git a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java index 2bc7dbe702d2ddbeba33e540373dd5345a46d398..40a8a041063d78cdd1403c99e99a1603b8dea026 100644 --- a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java +++ b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java @@ -14,6 +14,9 @@ import org.briarproject.api.TransportId; import org.briarproject.android.api.AndroidExecutor; import org.briarproject.api.contact.ContactId; import org.briarproject.api.crypto.PseudoRandom; +import org.briarproject.api.keyagreement.KeyAgreementConnection; +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; @@ -67,6 +70,9 @@ class DroidtoothPlugin implements DuplexPlugin { private static final String DISCOVERY_FINISHED = "android.bluetooth.adapter.action.DISCOVERY_FINISHED"; + private static final String PROP_ADDRESS = "address"; + private static final String PROP_UUID = "uuid"; + private final Executor ioExecutor; private final AndroidExecutor androidExecutor; private final Context appContext; @@ -161,7 +167,7 @@ class DroidtoothPlugin implements DuplexPlugin { if (!StringUtils.isNullOrEmpty(address)) { // Advertise the Bluetooth address to contacts TransportProperties p = new TransportProperties(); - p.put("address", address); + p.put(PROP_ADDRESS, address); callback.mergeLocalProperties(p); } // Bind a server socket to accept connections from contacts @@ -187,13 +193,13 @@ class DroidtoothPlugin implements DuplexPlugin { } private UUID getUuid() { - String uuid = callback.getLocalProperties().get("uuid"); + String uuid = callback.getLocalProperties().get(PROP_UUID); if (uuid == null) { byte[] random = new byte[UUID_BYTES]; secureRandom.nextBytes(random); uuid = UUID.nameUUIDFromBytes(random).toString(); TransportProperties p = new TransportProperties(); - p.put("uuid", uuid); + p.put(PROP_UUID, uuid); callback.mergeLocalProperties(p); } return UUID.fromString(uuid); @@ -264,9 +270,9 @@ class DroidtoothPlugin implements DuplexPlugin { for (Entry<ContactId, TransportProperties> e : remote.entrySet()) { final ContactId c = e.getKey(); if (connected.contains(c)) continue; - final String address = e.getValue().get("address"); + final String address = e.getValue().get(PROP_ADDRESS); if (StringUtils.isNullOrEmpty(address)) continue; - final String uuid = e.getValue().get("uuid"); + final String uuid = e.getValue().get(PROP_UUID); if (StringUtils.isNullOrEmpty(uuid)) continue; ioExecutor.execute(new Runnable() { public void run() { @@ -325,9 +331,9 @@ class DroidtoothPlugin implements DuplexPlugin { if (!isRunning()) return null; TransportProperties p = callback.getRemoteProperties().get(c); if (p == null) return null; - String address = p.get("address"); + String address = p.get(PROP_ADDRESS); if (StringUtils.isNullOrEmpty(address)) return null; - String uuid = p.get("uuid"); + String uuid = p.get(PROP_UUID); if (StringUtils.isNullOrEmpty(uuid)) return null; BluetoothSocket s = connect(address, uuid); if (s == null) return null; @@ -417,6 +423,48 @@ class DroidtoothPlugin implements DuplexPlugin { }); } + public boolean supportsKeyAgreement() { + return true; + } + + public KeyAgreementListener createKeyAgreementListener( + byte[] localCommitment) { + // No truncation necessary because COMMIT_LENGTH = 16 + UUID uuid = UUID.nameUUIDFromBytes(localCommitment); + if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid); + // Bind a server socket for receiving invitation connections + BluetoothServerSocket ss; + try { + ss = InsecureBluetooth.listen(adapter, "RFCOMM", uuid); + } catch (IOException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + return null; + } + TransportProperties p = new TransportProperties(); + String address = AndroidUtils.getBluetoothAddress(appContext, adapter); + if (!StringUtils.isNullOrEmpty(address)) + p.put(PROP_ADDRESS, address); + TransportDescriptor d = new TransportDescriptor(ID, p); + return new BluetoothKeyAgreementListener(d, ss); + } + + public DuplexTransportConnection createKeyAgreementConnection( + byte[] remoteCommitment, TransportDescriptor d, long timeout) { + if (!isRunning()) return null; + if (!ID.equals(d.getIdentifier())) return null; + TransportProperties p = d.getProperties(); + if (p == null) return null; + String address = p.get(PROP_ADDRESS); + if (StringUtils.isNullOrEmpty(address)) return null; + // No truncation necessary because COMMIT_LENGTH = 16 + UUID uuid = UUID.nameUUIDFromBytes(remoteCommitment); + if (LOG.isLoggable(INFO)) + LOG.info("Connecting to key agreement UUID " + uuid); + BluetoothSocket s = connect(address, uuid.toString()); + if (s == null) return null; + return new DroidtoothTransportConnection(this, s); + } + private class BluetoothStateReceiver extends BroadcastReceiver { @Override @@ -545,4 +593,39 @@ class DroidtoothPlugin implements DuplexPlugin { return s; } } + + private class BluetoothKeyAgreementListener extends KeyAgreementListener { + + private final BluetoothServerSocket ss; + + public BluetoothKeyAgreementListener(TransportDescriptor descriptor, + BluetoothServerSocket ss) { + super(descriptor); + this.ss = ss; + } + + @Override + public Callable<KeyAgreementConnection> listen() { + return new Callable<KeyAgreementConnection>() { + @Override + public KeyAgreementConnection call() throws IOException { + BluetoothSocket s = ss.accept(); + if (LOG.isLoggable(INFO)) + LOG.info(ID.getString() + ": Incoming connection"); + return new KeyAgreementConnection( + new DroidtoothTransportConnection( + DroidtoothPlugin.this, s), ID); + } + }; + } + + @Override + public void close() { + try { + ss.close(); + } catch (IOException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } + } + } } diff --git a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java index e5f7180c0e9f91ff459fb941bbc0c2d877bc461b..d3dbff7932243505f43d9426debf3bcec0f597b4 100644 --- a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java +++ b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java @@ -19,6 +19,8 @@ import org.briarproject.api.crypto.PseudoRandom; import org.briarproject.api.event.Event; import org.briarproject.api.event.EventListener; import org.briarproject.api.event.SettingsUpdatedEvent; +import org.briarproject.api.keyagreement.KeyAgreementListener; +import org.briarproject.api.keyagreement.TransportDescriptor; import org.briarproject.api.plugins.duplex.DuplexPlugin; import org.briarproject.api.plugins.duplex.DuplexPluginCallback; import org.briarproject.api.plugins.duplex.DuplexTransportConnection; @@ -570,6 +572,20 @@ class TorPlugin implements DuplexPlugin, EventHandler, 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(); + } + public void circuitStatus(String status, String id, String path) { if (status.equals("BUILT") && !circuitBuilt.getAndSet(true)) { LOG.info("First circuit built"); diff --git a/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementConnection.java b/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementConnection.java new file mode 100644 index 0000000000000000000000000000000000000000..7dfa3bc689d4a288ce01b90de6e10f3e3bd50e7f --- /dev/null +++ b/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementConnection.java @@ -0,0 +1,23 @@ +package org.briarproject.api.keyagreement; + +import org.briarproject.api.TransportId; +import org.briarproject.api.plugins.duplex.DuplexTransportConnection; + +public class KeyAgreementConnection { + private final DuplexTransportConnection conn; + private final TransportId id; + + public KeyAgreementConnection(DuplexTransportConnection conn, + TransportId id) { + this.conn = conn; + this.id = id; + } + + public DuplexTransportConnection getConnection() { + return conn; + } + + public TransportId getTransportId() { + return id; + } +} diff --git a/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementListener.java b/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementListener.java new file mode 100644 index 0000000000000000000000000000000000000000..05163614cc13d2ab383c1041813fc48b63977ae0 --- /dev/null +++ b/briar-api/src/org/briarproject/api/keyagreement/KeyAgreementListener.java @@ -0,0 +1,35 @@ +package org.briarproject.api.keyagreement; + +import java.util.concurrent.Callable; + +/** + * An class for managing a particular key agreement listener. + */ +public abstract class KeyAgreementListener { + + private final TransportDescriptor descriptor; + + public KeyAgreementListener(TransportDescriptor descriptor) { + this.descriptor = descriptor; + } + + /** + * Returns the descriptor that a remote peer can use to connect to this + * listener. + */ + public TransportDescriptor getDescriptor() { + return descriptor; + } + + /** + * Starts listening for incoming connections, and returns a Callable that + * will return a KeyAgreementConnection when an incoming connection is + * received. + */ + public abstract Callable<KeyAgreementConnection> listen(); + + /** + * Closes the underlying server socket. + */ + public abstract void close(); +} diff --git a/briar-api/src/org/briarproject/api/keyagreement/TransportDescriptor.java b/briar-api/src/org/briarproject/api/keyagreement/TransportDescriptor.java new file mode 100644 index 0000000000000000000000000000000000000000..cdaa5a579464e7287f06ada392cb16d9b2ffcb5d --- /dev/null +++ b/briar-api/src/org/briarproject/api/keyagreement/TransportDescriptor.java @@ -0,0 +1,28 @@ +package org.briarproject.api.keyagreement; + +import org.briarproject.api.TransportId; +import org.briarproject.api.properties.TransportProperties; + +/** + * Describes how to connect to a device over a short-range transport. + */ +public class TransportDescriptor { + + private final TransportId id; + private final TransportProperties properties; + + public TransportDescriptor(TransportId id, TransportProperties properties) { + this.id = id; + this.properties = properties; + } + + /** Returns the transport identifier. */ + public TransportId getIdentifier() { + return id; + } + + /** Returns the transport properties. */ + public TransportProperties getProperties() { + return properties; + } +} diff --git a/briar-api/src/org/briarproject/api/plugins/PluginManager.java b/briar-api/src/org/briarproject/api/plugins/PluginManager.java index 35248962d65da3a6e6d2de481a21e4c08f874f7b..838c66ff86e1ac74a41c507f9d48b6774038c247 100644 --- a/briar-api/src/org/briarproject/api/plugins/PluginManager.java +++ b/briar-api/src/org/briarproject/api/plugins/PluginManager.java @@ -19,4 +19,7 @@ public interface PluginManager { /** Returns any running duplex plugins that support invitations. */ Collection<DuplexPlugin> getInvitationPlugins(); + + /** Returns any running duplex plugins that support key agreement. */ + Collection<DuplexPlugin> getKeyAgreementPlugins(); } diff --git a/briar-api/src/org/briarproject/api/plugins/duplex/DuplexPlugin.java b/briar-api/src/org/briarproject/api/plugins/duplex/DuplexPlugin.java index 519400b5639fb2cc598dad8f88b16b7433853feb..ff7db3b51d78526d10b2a54bcc342c3a691583c2 100644 --- a/briar-api/src/org/briarproject/api/plugins/duplex/DuplexPlugin.java +++ b/briar-api/src/org/briarproject/api/plugins/duplex/DuplexPlugin.java @@ -2,6 +2,8 @@ package org.briarproject.api.plugins.duplex; 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.Plugin; /** An interface for transport plugins that support duplex communication. */ @@ -24,4 +26,19 @@ public interface DuplexPlugin extends Plugin { */ DuplexTransportConnection createInvitationConnection(PseudoRandom r, long timeout, boolean alice); + + /** Returns true if the plugin supports short-range key agreement. */ + boolean supportsKeyAgreement(); + + /** + * Returns a listener that can be used to perform key agreement. + */ + KeyAgreementListener createKeyAgreementListener(byte[] localCommitment); + + /** + * Attempts to connect to the remote peer specified in the given descriptor. + * Returns null if no connection can be established within the given time. + */ + DuplexTransportConnection createKeyAgreementConnection( + byte[] remoteCommitment, TransportDescriptor d, long timeout); } diff --git a/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java b/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java index bd110d0cf652715543b0b4670adfa7e7fc65f1ba..43a80bb54685e473511c16e3a97d8cf7a7aaa201 100644 --- a/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java +++ b/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java @@ -161,6 +161,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; diff --git a/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java b/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java index 54376310a0c33bd0b4e4f665f503d1c1ca97b76e..d6effe2a722930d94a5a6dac0022e02005ecb30c 100644 --- a/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java +++ b/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java @@ -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 { diff --git a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java index 79e289c39ca3dd1b538f65f6e037a39a97e21f0f..875112c5e88abe0baed8035d2a9d0da319000ec8 100644 --- a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java +++ b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java @@ -3,6 +3,9 @@ package org.briarproject.plugins.bluetooth; import org.briarproject.api.TransportId; import org.briarproject.api.contact.ContactId; import org.briarproject.api.crypto.PseudoRandom; +import org.briarproject.api.keyagreement.KeyAgreementConnection; +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; @@ -50,6 +53,9 @@ class BluetoothPlugin implements DuplexPlugin { Logger.getLogger(BluetoothPlugin.class.getName()); private static final int UUID_BYTES = 16; + private static final String PROP_ADDRESS = "address"; + private static final String PROP_UUID = "uuid"; + private final Executor ioExecutor; private final SecureRandom secureRandom; private final Backoff backoff; @@ -106,7 +112,7 @@ class BluetoothPlugin implements DuplexPlugin { if (!running) return; // Advertise the Bluetooth address to contacts TransportProperties p = new TransportProperties(); - p.put("address", localDevice.getBluetoothAddress()); + p.put(PROP_ADDRESS, localDevice.getBluetoothAddress()); callback.mergeLocalProperties(p); // Bind a server socket to accept connections from contacts String url = makeUrl("localhost", getUuid()); @@ -135,13 +141,13 @@ class BluetoothPlugin implements DuplexPlugin { } private String getUuid() { - String uuid = callback.getLocalProperties().get("uuid"); + String uuid = callback.getLocalProperties().get(PROP_UUID); if (uuid == null) { byte[] random = new byte[UUID_BYTES]; secureRandom.nextBytes(random); uuid = UUID.nameUUIDFromBytes(random).toString(); TransportProperties p = new TransportProperties(); - p.put("uuid", uuid); + p.put(PROP_UUID, uuid); callback.mergeLocalProperties(p); } return uuid; @@ -203,9 +209,9 @@ class BluetoothPlugin implements DuplexPlugin { for (Entry<ContactId, TransportProperties> e : remote.entrySet()) { final ContactId c = e.getKey(); if (connected.contains(c)) continue; - final String address = e.getValue().get("address"); + final String address = e.getValue().get(PROP_ADDRESS); if (StringUtils.isNullOrEmpty(address)) continue; - final String uuid = e.getValue().get("uuid"); + final String uuid = e.getValue().get(PROP_UUID); if (StringUtils.isNullOrEmpty(uuid)) continue; ioExecutor.execute(new Runnable() { public void run() { @@ -236,9 +242,9 @@ class BluetoothPlugin implements DuplexPlugin { if (!running) return null; TransportProperties p = callback.getRemoteProperties().get(c); if (p == null) return null; - String address = p.get("address"); + String address = p.get(PROP_ADDRESS); if (StringUtils.isNullOrEmpty(address)) return null; - String uuid = p.get("uuid"); + String uuid = p.get(PROP_UUID); if (StringUtils.isNullOrEmpty(uuid)) return null; String url = makeUrl(address, uuid); StreamConnection s = connect(url); @@ -335,6 +341,54 @@ class BluetoothPlugin implements DuplexPlugin { }); } + public boolean supportsKeyAgreement() { + return true; + } + + public KeyAgreementListener createKeyAgreementListener( + byte[] localCommitment) { + // No truncation necessary because COMMIT_LENGTH = 16 + String uuid = UUID.nameUUIDFromBytes(localCommitment).toString(); + if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid); + String url = makeUrl("localhost", uuid); + // Make the device discoverable if possible + makeDeviceDiscoverable(); + // Bind a server socket for receiving invitation connections + final StreamConnectionNotifier ss; + try { + ss = (StreamConnectionNotifier) Connector.open(url); + } catch (IOException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + return null; + } + if (!running) { + tryToClose(ss); + return null; + } + TransportProperties p = new TransportProperties(); + p.put(PROP_ADDRESS, localDevice.getBluetoothAddress()); + TransportDescriptor d = new TransportDescriptor(ID, p); + return new BluetoothKeyAgreementListener(d, ss); + } + + public DuplexTransportConnection createKeyAgreementConnection( + byte[] remoteCommitment, TransportDescriptor d, long timeout) { + if (!isRunning()) return null; + if (!ID.equals(d.getIdentifier())) return null; + TransportProperties p = d.getProperties(); + if (p == null) return null; + String address = p.get(PROP_ADDRESS); + if (StringUtils.isNullOrEmpty(address)) return null; + // No truncation necessary because COMMIT_LENGTH = 16 + String uuid = UUID.nameUUIDFromBytes(remoteCommitment).toString(); + if (LOG.isLoggable(INFO)) + LOG.info("Connecting to key agreement UUID " + uuid); + String url = makeUrl(address, uuid); + StreamConnection s = connect(url); + if (s == null) return null; + return new BluetoothTransportConnection(this, s); + } + private void makeDeviceDiscoverable() { // Try to make the device discoverable (requires root on Linux) try { @@ -414,4 +468,39 @@ class BluetoothPlugin implements DuplexPlugin { return s; } } + + private class BluetoothKeyAgreementListener extends KeyAgreementListener { + + private final StreamConnectionNotifier ss; + + public BluetoothKeyAgreementListener(TransportDescriptor descriptor, + StreamConnectionNotifier ss) { + super(descriptor); + this.ss = ss; + } + + @Override + public Callable<KeyAgreementConnection> listen() { + return new Callable<KeyAgreementConnection>() { + @Override + public KeyAgreementConnection call() throws Exception { + StreamConnection s = ss.acceptAndOpen(); + if (LOG.isLoggable(INFO)) + LOG.info(ID.getString() + ": Incoming connection"); + return new KeyAgreementConnection( + new BluetoothTransportConnection( + BluetoothPlugin.this, s), ID); + } + }; + } + + @Override + public void close() { + try { + ss.close(); + } catch (IOException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } + } + } } diff --git a/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java b/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java index 954529a5e8221fa1341f7b0a7b198409e33efa19..50dcfb7cd0a967f446ab390c068c040e5dc40365 100644 --- a/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java +++ b/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java @@ -3,6 +3,8 @@ package org.briarproject.plugins.modem; import org.briarproject.api.TransportId; 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.TransportConnectionReader; import org.briarproject.api.plugins.TransportConnectionWriter; import org.briarproject.api.plugins.duplex.DuplexPlugin; @@ -158,6 +160,20 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback { 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(); + } + public void incomingCallConnected() { LOG.info("Incoming call connected"); callback.incomingConnectionCreated(new ModemTransportConnection());