diff --git a/briar-android/libs/jtorctl-briar.jar b/briar-android/libs/jtorctl-briar.jar index 134f85dbcb386b866b1cb9023a9f1fa29e308f84..a299bcdb39e4ec965dd35fc56263dabecce069ad 100644 Binary files a/briar-android/libs/jtorctl-briar.jar and b/briar-android/libs/jtorctl-briar.jar differ diff --git a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java index b0c651fb6f3b64b5ea66761385ca5f5bbb12d944..e7852bff66fe9340f3c16b4d6f3744f0913ae88e 100644 --- a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java +++ b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java @@ -47,6 +47,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Scanner; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; @@ -63,20 +64,21 @@ import static android.os.PowerManager.PARTIAL_WAKE_LOCK; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; +import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS; +import static net.freehaven.tor.control.TorControlCommands.HS_PRIVKEY; class TorPlugin implements DuplexPlugin, EventHandler, EventListener { static final TransportId ID = new TransportId("tor"); + private static final String PROP_ONION = "onion"; private static final String[] EVENTS = { "CIRC", "ORCONN", "HS_DESC", "NOTICE", "WARN", "ERR" }; private static final String OWNER = "__OwningControllerProcess"; private static final int SOCKS_PORT = 59050, CONTROL_PORT = 59051; private static final int COOKIE_TIMEOUT = 3000; // Milliseconds - private static final int HOSTNAME_TIMEOUT = 30 * 1000; // Milliseconds - private static final Pattern ONION = - Pattern.compile("[a-z2-7]{16}\\.onion"); + private static final Pattern ONION = Pattern.compile("[a-z2-7]{16}"); private static final int MIN_DESCRIPTORS_PUBLISHED = 3; private static final Logger LOG = Logger.getLogger(TorPlugin.class.getName()); @@ -90,8 +92,8 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener { private final String architecture; private final int maxLatency, maxIdleTime, pollingInterval, socketTimeout; private final ConnectionStatus connectionStatus; - private final File torDirectory, torFile, geoIpFile, configFile, doneFile; - private final File cookieFile, hostnameFile; + private final File torDirectory, torFile, geoIpFile, configFile; + private final File doneFile, cookieFile; private final PowerManager.WakeLock wakeLock; private volatile boolean running = false; @@ -124,7 +126,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener { configFile = new File(torDirectory, "torrc"); doneFile = new File(torDirectory, "done"); cookieFile = new File(torDirectory, ".tor/control_auth_cookie"); - hostnameFile = new File(torDirectory, "hs/hostname"); Object o = appContext.getSystemService(POWER_SERVICE); PowerManager pm = (PowerManager) o; wakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, "TorPlugin"); @@ -421,47 +422,39 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener { private void publishHiddenService(String port) { if (!running) return; - if (!hostnameFile.exists()) { - LOG.info("Creating hidden service"); - try { - // Watch for the hostname file being created/updated - File serviceDirectory = hostnameFile.getParentFile(); - serviceDirectory.mkdirs(); - hostnameFile.createNewFile(); - CountDownLatch latch = new CountDownLatch(1); - FileObserver obs = new WriteObserver(hostnameFile, latch); - obs.startWatching(); - // Use the control connection to update the Tor config - List<String> config = Arrays.asList( - "HiddenServiceDir " + - serviceDirectory.getAbsolutePath(), - "HiddenServicePort 80 127.0.0.1:" + port); - controlConnection.setConf(config); - controlConnection.saveConf(); - // Wait for the hostname file to be created/updated - if (!latch.await(HOSTNAME_TIMEOUT, MILLISECONDS)) { - LOG.warning("Hidden service not created"); - if (LOG.isLoggable(INFO)) listFiles(torDirectory); - return; - } - if (!running) return; - } catch (IOException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - } catch (InterruptedException e) { - LOG.warning("Interrupted while creating hidden service"); - Thread.currentThread().interrupt(); - return; - } - } - // Publish the hidden service's onion hostname in transport properties + LOG.info("Creating hidden service"); + String privKey = callback.getSettings().get(HS_PRIVKEY); + Map<Integer, String> portLines = + Collections.singletonMap(80, "127.0.0.1:" + port); + Map<String, String> response; try { - String hostname = new String(read(hostnameFile), "UTF-8").trim(); - if (LOG.isLoggable(INFO)) LOG.info("Hidden service " + hostname); - TransportProperties p = new TransportProperties(); - p.put("onion", hostname); - callback.mergeLocalProperties(p); + // Use the control connection to set up the hidden service + if (privKey == null) + response = controlConnection.addOnion(portLines); + else response = controlConnection.addOnion(privKey, portLines); } catch (IOException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + return; + } + if (!response.containsKey(HS_ADDRESS)) { + LOG.warning("Tor did not return a hidden service address"); + return; + } + if (privKey == null && !response.containsKey(HS_PRIVKEY)) { + LOG.warning("Tor did not return a private key"); + return; + } + // Publish the hidden service's onion hostname in transport properties + String hostname = response.get(HS_ADDRESS); + if (LOG.isLoggable(INFO)) LOG.info("Hidden service " + hostname); + TransportProperties p = new TransportProperties(); + p.put(PROP_ONION, hostname); + callback.mergeLocalProperties(p); + if (privKey == null) { + // Save the hidden service's private key for next time + Settings s = new Settings(); + s.put(HS_PRIVKEY, response.get(HS_PRIVKEY)); + callback.mergeSettings(s); } } @@ -551,7 +544,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener { if (!isRunning()) return null; TransportProperties p = callback.getRemoteProperties().get(c); if (p == null) return null; - String onion = p.get("onion"); + String onion = p.get(PROP_ONION); if (StringUtils.isNullOrEmpty(onion)) return null; if (!ONION.matcher(onion).matches()) { if (LOG.isLoggable(INFO)) LOG.info("Invalid hostname: " + onion); @@ -559,10 +552,10 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener { } try { if (LOG.isLoggable(INFO)) LOG.info("Connecting to " + onion); - controlConnection.forgetHiddenService(onion.substring(0, 16)); + controlConnection.forgetHiddenService(onion); Socks5Proxy proxy = new Socks5Proxy("127.0.0.1", SOCKS_PORT); proxy.resolveAddrLocally(false); - Socket s = new SocksSocket(proxy, onion, 80); + Socket s = new SocksSocket(proxy, onion + ".onion", 80); s.setSoTimeout(socketTimeout); if (LOG.isLoggable(INFO)) LOG.info("Connected to " + onion); return new TorTransportConnection(this, s); diff --git a/patches/jtorctl.patch b/patches/jtorctl.patch index 167af82d22592834bed17fe41098c3096168988e..7d346eff0c87e66b18defd0d28b53e4d4de93af8 100644 --- a/patches/jtorctl.patch +++ b/patches/jtorctl.patch @@ -1,8 +1,125 @@ +diff --git a/net/freehaven/tor/control/TorControlCommands.java b/net/freehaven/tor/control/TorControlCommands.java +index 36482d5..14486e3 100644 +--- a/net/freehaven/tor/control/TorControlCommands.java ++++ b/net/freehaven/tor/control/TorControlCommands.java +@@ -144,5 +144,8 @@ public interface TorControlCommands { + "No such OR", + }; + ++ public static final String HS_ADDRESS = "onionAddress"; ++ public static final String HS_PRIVKEY = "onionPrivKey"; ++ + } + diff --git a/net/freehaven/tor/control/TorControlConnection.java b/net/freehaven/tor/control/TorControlConnection.java -index 9524612..38b1879 100644 +index 9524612..c0f2070 100644 --- a/net/freehaven/tor/control/TorControlConnection.java +++ b/net/freehaven/tor/control/TorControlConnection.java -@@ -740,7 +740,7 @@ public class TorControlConnection implements TorControlCommands { +@@ -736,11 +736,111 @@ public class TorControlConnection implements TorControlCommands { + sendAndWaitForResponse("TAKEOWNERSHIP\r\n", null); + } + ++ /** ++ * Tells Tor to generate and set up a new onion service using the best ++ * supported algorithm. ++ * <p/> ++ * ADD_ONION was added in Tor 0.2.7.1-alpha. ++ */ ++ public Map<String,String> addOnion(Map<Integer,String> portLines) ++ throws IOException { ++ return addOnion("NEW:BEST", portLines, null); ++ } ++ ++ /** ++ * Tells Tor to generate and set up a new onion service using the best ++ * supported algorithm. ++ * <p/> ++ * ADD_ONION was added in Tor 0.2.7.1-alpha. ++ */ ++ public Map<String,String> addOnion(Map<Integer,String> portLines, ++ boolean ephemeral, boolean detach) ++ throws IOException { ++ return addOnion("NEW:BEST", portLines, ephemeral, detach); ++ } ++ ++ /** ++ * Tells Tor to set up an onion service using the provided private key. ++ * <p/> ++ * ADD_ONION was added in Tor 0.2.7.1-alpha. ++ */ ++ public Map<String,String> addOnion(String privKey, ++ Map<Integer,String> portLines) ++ throws IOException { ++ return addOnion(privKey, portLines, null); ++ } ++ ++ /** ++ * Tells Tor to set up an onion service using the provided private key. ++ * <p/> ++ * ADD_ONION was added in Tor 0.2.7.1-alpha. ++ */ ++ public Map<String,String> addOnion(String privKey, ++ Map<Integer,String> portLines, ++ boolean ephemeral, boolean detach) ++ throws IOException { ++ List<String> flags = new ArrayList<String>(); ++ if (ephemeral) ++ flags.add("DiscardPK"); ++ if (detach) ++ flags.add("Detach"); ++ return addOnion(privKey, portLines, flags); ++ } ++ ++ /** ++ * Tells Tor to set up an onion service. ++ * <p/> ++ * ADD_ONION was added in Tor 0.2.7.1-alpha. ++ */ ++ public Map<String,String> addOnion(String privKey, ++ Map<Integer,String> portLines, ++ List<String> flags) ++ throws IOException { ++ if (privKey.indexOf(':') < 0) ++ throw new IllegalArgumentException("Invalid privKey"); ++ if (portLines == null || portLines.size() < 1) ++ throw new IllegalArgumentException("Must provide at least one port line"); ++ StringBuilder b = new StringBuilder(); ++ b.append("ADD_ONION ").append(privKey); ++ if (flags != null && flags.size() > 0) { ++ b.append(" Flags="); ++ String separator = ""; ++ for (String flag : flags) { ++ b.append(separator).append(flag); ++ separator = ","; ++ } ++ } ++ for (Map.Entry<Integer,String> portLine : portLines.entrySet()) { ++ int virtPort = portLine.getKey(); ++ String target = portLine.getValue(); ++ b.append(" Port=").append(virtPort); ++ if (target != null && target.length() > 0) ++ b.append(",").append(target); ++ } ++ b.append("\r\n"); ++ List<ReplyLine> lst = sendAndWaitForResponse(b.toString(), null); ++ Map<String,String> ret = new HashMap<String,String>(); ++ ret.put(HS_ADDRESS, (lst.get(0)).msg.split("=", 2)[1]); ++ if (lst.size() > 2) ++ ret.put(HS_PRIVKEY, (lst.get(1)).msg.split("=", 2)[1]); ++ return ret; ++ } ++ ++ /** ++ * Tells Tor to take down an onion service previously set up with ++ * addOnion(). The hostname excludes the .onion extension. ++ * <p/> ++ * DEL_ONION was added in Tor 0.2.7.1-alpha. ++ */ ++ public void delOnion(String hostname) throws IOException { ++ sendAndWaitForResponse("DEL_ONION " + hostname + "\r\n", null); ++ } ++ + /** Tells Tor to forget any cached client state relating to the hidden * service with the given hostname (excluding the .onion extension). */ public void forgetHiddenService(String hostname) throws IOException {