diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPlugin.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPlugin.java index ae68db6e1c5e500601984a861e27f24a2c72ba5b..f367736152b0d494f1283eff97254c29857346b9 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPlugin.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPlugin.java @@ -41,11 +41,12 @@ class AndroidTorPlugin extends TorPlugin { Clock clock, ResourceProvider resourceProvider, CircumventionProvider circumventionProvider, BatteryManager batteryManager, Backoff backoff, + TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback, String architecture, int maxLatency, int maxIdleTime) { super(ioExecutor, networkManager, locationUtils, torSocketFactory, clock, resourceProvider, circumventionProvider, batteryManager, - backoff, callback, architecture, maxLatency, maxIdleTime, + backoff, torRendezvousCrypto, callback, architecture, maxLatency, maxIdleTime, appContext.getDir("tor", MODE_PRIVATE)); this.appContext = appContext; PowerManager pm = (PowerManager) diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPluginFactory.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPluginFactory.java index 560072343a52cb2d4f084248ad9f3805a29cdac8..40535497588885bbb11f2515aa01079d191f21c6 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPluginFactory.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/tor/AndroidTorPluginFactory.java @@ -106,10 +106,12 @@ public class AndroidTorPluginFactory implements DuplexPluginFactory { Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE); + TorRendezvousCrypto torRendezvousCrypto = new TorRendezvousCryptoImpl(); AndroidTorPlugin plugin = new AndroidTorPlugin(ioExecutor, scheduler, appContext, networkManager, locationUtils, torSocketFactory, clock, resourceProvider, circumventionProvider, batteryManager, - backoff, callback, architecture, MAX_LATENCY, MAX_IDLE_TIME); + backoff, torRendezvousCrypto, callback, architecture, + MAX_LATENCY, MAX_IDLE_TIME); eventBus.addListener(plugin); return plugin; } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginManagerImpl.java index cbaac0943070806f70d1926aec677bca4117aded..d4d69ca565f168ab62a49766884eae516e0358b2 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/PluginManagerImpl.java @@ -250,6 +250,7 @@ class PluginManagerImpl implements PluginManager, Service { private class Callback implements PluginCallback { private final TransportId id; + private final AtomicBoolean enabled = new AtomicBoolean(false); private Callback(TransportId id) { this.id = id; @@ -295,12 +296,14 @@ class PluginManagerImpl implements PluginManager, Service { @Override public void transportEnabled() { - eventBus.broadcast(new TransportEnabledEvent(id)); + if (!enabled.getAndSet(true)) + eventBus.broadcast(new TransportEnabledEvent(id)); } @Override public void transportDisabled() { - eventBus.broadcast(new TransportDisabledEvent(id)); + if (enabled.getAndSet(false)) + eventBus.broadcast(new TransportDisabledEvent(id)); } @Override diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java index 76d8f6109a99bf7913cbde2809ef8cafad325862..e2494e8307017224b8550705c88ee29123b6a4a5 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorPlugin.java @@ -32,7 +32,6 @@ import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent; import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.LocationUtils; import org.briarproject.bramble.api.system.ResourceProvider; -import org.briarproject.bramble.util.IoUtils; import java.io.EOFException; import java.io.File; @@ -55,7 +54,6 @@ import java.util.logging.Logger; import java.util.regex.Pattern; import java.util.zip.ZipInputStream; -import javax.annotation.Nullable; import javax.net.SocketFactory; import static java.util.Arrays.asList; @@ -78,7 +76,9 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHE import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_PORT; import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V2; import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V3; +import static org.briarproject.bramble.plugin.tor.TorRendezvousCrypto.SEED_BYTES; import static org.briarproject.bramble.util.IoUtils.copyAndClose; +import static org.briarproject.bramble.util.IoUtils.tryToClose; import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.PrivacyUtils.scrubOnion; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; @@ -105,6 +105,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { private final Clock clock; private final BatteryManager batteryManager; private final Backoff backoff; + private final TorRendezvousCrypto torRendezvousCrypto; private final PluginCallback callback; private final String architecture; private final CircumventionProvider circumventionProvider; @@ -131,6 +132,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { Clock clock, ResourceProvider resourceProvider, CircumventionProvider circumventionProvider, BatteryManager batteryManager, Backoff backoff, + TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback, String architecture, int maxLatency, int maxIdleTime, File torDirectory) { this.ioExecutor = ioExecutor; @@ -142,6 +144,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { this.circumventionProvider = circumventionProvider; this.batteryManager = batteryManager; this.backoff = backoff; + this.torRendezvousCrypto = torRendezvousCrypto; this.callback = callback; this.architecture = architecture; this.maxLatency = maxLatency; @@ -311,8 +314,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { if (!doneFile.createNewFile()) LOG.warning("Failed to create done file"); } catch (IOException e) { - IoUtils.tryToClose(in, LOG, WARNING); - IoUtils.tryToClose(out, LOG, WARNING); + tryToClose(in, LOG, WARNING); + tryToClose(out, LOG, WARNING); throw new PluginException(e); } } @@ -371,7 +374,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { } return b; } finally { - IoUtils.tryToClose(in, LOG, WARNING); + tryToClose(in, LOG, WARNING); } } @@ -389,11 +392,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { ss.bind(new InetSocketAddress("127.0.0.1", port)); } catch (IOException e) { logException(LOG, WARNING, e); - tryToClose(ss); + tryToClose(ss, LOG, WARNING); return; } if (!running) { - tryToClose(ss); + tryToClose(ss, LOG, WARNING); return; } socket = ss; @@ -410,11 +413,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { }); } - private void tryToClose(@Nullable ServerSocket ss) { - IoUtils.tryToClose(ss, LOG, WARNING); - callback.transportDisabled(); - } - private void publishHiddenService(String port) { if (!running) return; LOG.info("Creating hidden service"); @@ -499,7 +497,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { @Override public void stop() { running = false; - tryToClose(socket); + tryToClose(socket, LOG, WARNING); + callback.transportDisabled(); if (controlSocket != null && controlConnection != null) { try { LOG.info("Stopping Tor"); @@ -586,7 +585,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { LOG.info("Could not connect to " + scrubOnion(bestOnion) + ": " + e.toString()); } - IoUtils.tryToClose(s, LOG, WARNING); + tryToClose(s, LOG, WARNING); return null; } } @@ -609,13 +608,58 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { @Override public boolean supportsRendezvous() { - return false; + return true; } @Override public RendezvousEndpoint createRendezvousEndpoint(KeyMaterialSource k, boolean alice, ConnectionHandler incoming) { - throw new UnsupportedOperationException(); + byte[] aliceSeed = k.getKeyMaterial(SEED_BYTES); + byte[] bobSeed = k.getKeyMaterial(SEED_BYTES); + byte[] localSeed = alice ? aliceSeed : bobSeed; + byte[] remoteSeed = alice ? bobSeed : aliceSeed; + String blob = torRendezvousCrypto.getPrivateKeyBlob(localSeed); + String localOnion = torRendezvousCrypto.getOnionAddress(localSeed); + String remoteOnion = torRendezvousCrypto.getOnionAddress(remoteSeed); + TransportProperties remoteProperties = new TransportProperties(); + remoteProperties.put(PROP_ONION_V3, remoteOnion); + try { + ServerSocket ss = new ServerSocket(); + ss.bind(new InetSocketAddress("127.0.0.1", 0)); + int port = ss.getLocalPort(); + ioExecutor.execute(() -> { + try { + //noinspection InfiniteLoopStatement + while (true) { + Socket s = ss.accept(); + incoming.handleConnection( + new TorTransportConnection(this, s)); + } + } catch (IOException e) { + // This is expected when the socket is closed + if (LOG.isLoggable(INFO)) LOG.info(e.toString()); + } + }); + Map<Integer, String> portLines = + singletonMap(80, "127.0.0.1:" + port); + controlConnection.addOnion(blob, portLines); + return new RendezvousEndpoint() { + + @Override + public TransportProperties getRemoteTransportProperties() { + return remoteProperties; + } + + @Override + public void close() throws IOException { + controlConnection.delOnion(localOnion); + tryToClose(ss, LOG, WARNING); + } + }; + } catch (IOException e) { + logException(LOG, WARNING, e); + return null; + } } @Override diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorRendezvousCrypto.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorRendezvousCrypto.java new file mode 100644 index 0000000000000000000000000000000000000000..a5b32630e804701fcca2ca7c58f9a00ebee734cf --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorRendezvousCrypto.java @@ -0,0 +1,10 @@ +package org.briarproject.bramble.plugin.tor; + +interface TorRendezvousCrypto { + + static final int SEED_BYTES = 32; + + String getOnionAddress(byte[] seed); + + String getPrivateKeyBlob(byte[] seed); +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorRendezvousCryptoImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorRendezvousCryptoImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..1545bcfba5c6f67c20eb365b85cf37065081cac4 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/tor/TorRendezvousCryptoImpl.java @@ -0,0 +1,49 @@ +package org.briarproject.bramble.plugin.tor; + +import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec; +import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; +import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; + +import org.briarproject.bramble.util.Base32; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.digests.SHA3Digest; +import org.spongycastle.util.encoders.Base64; + +import java.nio.charset.Charset; + +import static java.lang.System.arraycopy; + +public class TorRendezvousCryptoImpl implements TorRendezvousCrypto { + + private static final EdDSANamedCurveSpec CURVE_SPEC = + EdDSANamedCurveTable.getByName("Ed25519"); + + private static final byte HS_PROTOCOL_VERSION = 3; + private static final int CHECKSUM_BYTES = 2; + + @Override + public String getOnionAddress(byte[] seed) { + EdDSAPrivateKeySpec spec = new EdDSAPrivateKeySpec(seed, CURVE_SPEC); + byte[] publicKey = spec.getA().toByteArray(); + Digest digest = new SHA3Digest(256); + byte[] label = ".onion checksum".getBytes(Charset.forName("US-ASCII")); + digest.update(label, 0, label.length); + digest.update(publicKey, 0, publicKey.length); + digest.update(HS_PROTOCOL_VERSION); + byte[] checksum = new byte[digest.getDigestSize()]; + digest.doFinal(checksum, 0); + byte[] address = new byte[publicKey.length + CHECKSUM_BYTES + 1]; + arraycopy(publicKey, 0, address, 0, publicKey.length); + arraycopy(checksum, 0, address, publicKey.length, CHECKSUM_BYTES); + address[address.length - 1] = HS_PROTOCOL_VERSION; + return Base32.encode(address).toLowerCase(); + } + + @Override + public String getPrivateKeyBlob(byte[] seed) { + EdDSAPrivateKeySpec spec = new EdDSAPrivateKeySpec(seed, CURVE_SPEC); + byte[] hash = spec.getH(); + byte[] base64 = Base64.encode(hash); + return "ED25519-V3:" + new String(base64, Charset.forName("US-ASCII")); + } +} diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/JavaTorPlugin.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/JavaTorPlugin.java index d08300b2af61e796ad668f21482b299512bb4c1d..9b213cba7e332eff85d3a0ed887bf3a7bb386dbc 100644 --- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/JavaTorPlugin.java +++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/JavaTorPlugin.java @@ -25,12 +25,13 @@ abstract class JavaTorPlugin extends TorPlugin { Clock clock, ResourceProvider resourceProvider, CircumventionProvider circumventionProvider, BatteryManager batteryManager, Backoff backoff, + TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback, String architecture, int maxLatency, int maxIdleTime, File torDirectory) { super(ioExecutor, networkManager, locationUtils, torSocketFactory, clock, resourceProvider, circumventionProvider, batteryManager, - backoff, callback, architecture, maxLatency, maxIdleTime, - torDirectory); + backoff, torRendezvousCrypto, callback, architecture, + maxLatency, maxIdleTime, torDirectory); } @Override diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/UnixTorPlugin.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/UnixTorPlugin.java index 753840a76b53acc27ae83da5c20e967b10473bbf..e57ea83f3e806cbb80acf77c87730628d6db13ed 100644 --- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/UnixTorPlugin.java +++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/UnixTorPlugin.java @@ -25,12 +25,13 @@ class UnixTorPlugin extends JavaTorPlugin { Clock clock, ResourceProvider resourceProvider, CircumventionProvider circumventionProvider, BatteryManager batteryManager, Backoff backoff, + TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback, String architecture, int maxLatency, int maxIdleTime, File torDirectory) { super(ioExecutor, networkManager, locationUtils, torSocketFactory, clock, resourceProvider, circumventionProvider, batteryManager, - backoff, callback, architecture, maxLatency, maxIdleTime, - torDirectory); + backoff, torRendezvousCrypto, callback, architecture, + maxLatency, maxIdleTime, torDirectory); } @Override diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/UnixTorPluginFactory.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/UnixTorPluginFactory.java index 0e84257129a805cc4a87bf7e2f306102c9b28240..07cbead567577ad9da962c9584f08e101ee0dab8 100644 --- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/UnixTorPluginFactory.java +++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/tor/UnixTorPluginFactory.java @@ -96,10 +96,12 @@ public class UnixTorPluginFactory implements DuplexPluginFactory { Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE); + TorRendezvousCrypto torRendezvousCrypto = new TorRendezvousCryptoImpl(); UnixTorPlugin plugin = new UnixTorPlugin(ioExecutor, networkManager, locationUtils, torSocketFactory, clock, resourceProvider, - circumventionProvider, batteryManager, backoff, callback, - architecture, MAX_LATENCY, MAX_IDLE_TIME, torDirectory); + circumventionProvider, batteryManager, backoff, + torRendezvousCrypto, callback, architecture, MAX_LATENCY, + MAX_IDLE_TIME, torDirectory); eventBus.addListener(plugin); return plugin; }