Merge branch 'socks-socket' into 'master'

Fetch RSS feeds via Tor

This patch replaces jsocks with our own minimal SOCKS 5 implementation, which is compatible with Android's OpenSSL hacks (see discussion on #599 for the horrifying details). This allows us to use OkHttp over Tor to fetch RSS feeds.

It turns out that SOCKS 5 without authentication is a really simple protocol: https://tools.ietf.org/html/rfc1928

Closes #599.

See merge request !308
This commit is contained in:
Torsten Grote
2016-09-06 18:37:38 +00:00
15 changed files with 289 additions and 62 deletions

View File

@@ -11,9 +11,6 @@ dependencies {
compile project(':briar-api')
compile project(':briar-core')
compile fileTree(dir: 'libs', include: '*.jar')
// This shouldn't be necessary; per section 23.4.4 of the Gradle docs:
// "file dependencies are included in transitive project dependencies within the same build".
compile files('../briar-core/libs/jsocks.jar')
compile "com.android.support:support-v4:$supportVersion"
compile("com.android.support:appcompat-v7:$supportVersion") {

View File

@@ -22,6 +22,8 @@ import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.Executor;
import javax.net.SocketFactory;
import dagger.Module;
import dagger.Provides;
@@ -30,15 +32,16 @@ public class AndroidPluginsModule {
@Provides
public PluginConfig providePluginConfig(@IoExecutor Executor ioExecutor,
AndroidExecutor androidExecutor,
SecureRandom random, BackoffFactory backoffFactory, Application app,
LocationUtils locationUtils, DevReporter reporter,
AndroidExecutor androidExecutor, SecureRandom random,
SocketFactory torSocketFactory, BackoffFactory backoffFactory,
Application app, LocationUtils locationUtils, DevReporter reporter,
EventBus eventBus) {
Context appContext = app.getApplicationContext();
DuplexPluginFactory bluetooth = new DroidtoothPluginFactory(ioExecutor,
androidExecutor, appContext, random, backoffFactory);
DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, appContext,
locationUtils, reporter, eventBus, backoffFactory);
locationUtils, reporter, eventBus, torSocketFactory,
backoffFactory);
DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
backoffFactory, appContext);
final Collection<DuplexPluginFactory> duplex =

View File

@@ -14,8 +14,6 @@ import android.os.PowerManager;
import net.freehaven.tor.control.EventHandler;
import net.freehaven.tor.control.TorControlConnection;
import net.sourceforge.jsocks.socks.Socks5Proxy;
import net.sourceforge.jsocks.socks.SocksSocket;
import org.briarproject.android.util.AndroidUtils;
import org.briarproject.api.TransportId;
@@ -62,6 +60,8 @@ import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.zip.ZipInputStream;
import javax.net.SocketFactory;
import static android.content.Context.CONNECTIVITY_SERVICE;
import static android.content.Context.MODE_PRIVATE;
import static android.content.Context.POWER_SERVICE;
@@ -74,7 +74,6 @@ 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;
import static org.briarproject.api.plugins.TorConstants.CONTROL_PORT;
import static org.briarproject.api.plugins.TorConstants.SOCKS_PORT;
import static org.briarproject.util.PrivacyUtils.scrubOnion;
class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@@ -93,6 +92,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private final Context appContext;
private final LocationUtils locationUtils;
private final DevReporter reporter;
private final SocketFactory torSocketFactory;
private final Backoff backoff;
private final DuplexPluginCallback callback;
private final String architecture;
@@ -110,13 +110,15 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private volatile BroadcastReceiver networkStateReceiver = null;
TorPlugin(Executor ioExecutor, Context appContext,
LocationUtils locationUtils, DevReporter reporter, Backoff backoff,
LocationUtils locationUtils, DevReporter reporter,
SocketFactory torSocketFactory, Backoff backoff,
DuplexPluginCallback callback, String architecture, int maxLatency,
int maxIdleTime) {
this.ioExecutor = ioExecutor;
this.appContext = appContext;
this.locationUtils = locationUtils;
this.reporter = reporter;
this.torSocketFactory = torSocketFactory;
this.backoff = backoff;
this.callback = callback;
this.architecture = architecture;
@@ -295,6 +297,14 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
}
private void tryToClose(Socket s) {
try {
if (s != null) s.close();
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
private void listFiles(File f) {
if (f.isDirectory()) for (File child : f.listFiles()) listFiles(child);
else LOG.info(f.getAbsolutePath());
@@ -320,8 +330,9 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
ioExecutor.execute(new Runnable() {
@Override
public void run() {
// TODO: Trigger this with a TransportEnabledEvent
File reportDir = AndroidUtils.getReportDir(appContext);
reporter.sendReports(reportDir, SOCKS_PORT);
reporter.sendReports(reportDir);
}
});
}
@@ -516,21 +527,22 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (LOG.isLoggable(INFO)) LOG.info("Invalid hostname: " + onion);
return null;
}
Socket s = null;
try {
if (LOG.isLoggable(INFO))
LOG.info("Connecting to " + scrubOnion(onion));
controlConnection.forgetHiddenService(onion);
Socks5Proxy proxy = new Socks5Proxy("127.0.0.1", SOCKS_PORT);
proxy.resolveAddrLocally(false);
Socket s = new SocksSocket(proxy, onion + ".onion", 80);
s = torSocketFactory.createSocket(onion + ".onion", 80);
s.setSoTimeout(socketTimeout);
if (LOG.isLoggable(INFO))
LOG.info("Connected to " + scrubOnion(onion));
return new TorTransportConnection(this, s);
} catch (IOException e) {
if (LOG.isLoggable(INFO))
if (LOG.isLoggable(INFO)) {
LOG.info("Could not connect to " + scrubOnion(onion) + ": " +
e.toString());
}
tryToClose(s);
return null;
}
}

View File

@@ -18,6 +18,8 @@ import org.briarproject.api.system.LocationUtils;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.net.SocketFactory;
public class TorPluginFactory implements DuplexPluginFactory {
private static final Logger LOG =
@@ -34,16 +36,19 @@ public class TorPluginFactory implements DuplexPluginFactory {
private final LocationUtils locationUtils;
private final DevReporter reporter;
private final EventBus eventBus;
private final SocketFactory torSocketFactory;
private final BackoffFactory backoffFactory;
public TorPluginFactory(Executor ioExecutor, Context appContext,
LocationUtils locationUtils, DevReporter reporter,
EventBus eventBus, BackoffFactory backoffFactory) {
EventBus eventBus, SocketFactory torSocketFactory,
BackoffFactory backoffFactory) {
this.ioExecutor = ioExecutor;
this.appContext = appContext;
this.locationUtils = locationUtils;
this.reporter = reporter;
this.eventBus = eventBus;
this.torSocketFactory = torSocketFactory;
this.backoffFactory = backoffFactory;
}
@@ -81,8 +86,8 @@ public class TorPluginFactory implements DuplexPluginFactory {
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
TorPlugin plugin = new TorPlugin(ioExecutor, appContext, locationUtils,
reporter, backoff, callback, architecture, MAX_LATENCY,
MAX_IDLE_TIME);
reporter, torSocketFactory, backoff, callback, architecture,
MAX_LATENCY, MAX_IDLE_TIME);
eventBus.addListener(plugin);
return plugin;
}

View File

@@ -9,4 +9,5 @@ public interface TorConstants {
int SOCKS_PORT = 59050;
int CONTROL_PORT = 59051;
int CONNECT_TO_PROXY_TIMEOUT = 5000; // Milliseconds
}

View File

@@ -22,7 +22,6 @@ public interface DevReporter {
* Send reports previously stored on-disk.
*
* @param reportDir the directory where reports are stored.
* @param socksPort the SOCKS port of a Tor client.
*/
void sendReports(File reportDir, int socksPort);
void sendReports(File reportDir);
}

Binary file not shown.

View File

@@ -22,6 +22,7 @@ import org.briarproject.reliability.ReliabilityModule;
import org.briarproject.reporting.ReportingModule;
import org.briarproject.settings.SettingsModule;
import org.briarproject.sharing.SharingModule;
import org.briarproject.socks.SocksModule;
import org.briarproject.sync.SyncModule;
import org.briarproject.system.SystemModule;
import org.briarproject.transport.TransportModule;
@@ -50,6 +51,7 @@ import dagger.Module;
ReportingModule.class,
SettingsModule.class,
SharingModule.class,
SocksModule.class,
SyncModule.class,
SystemModule.class,
TransportModule.class,

View File

@@ -40,9 +40,6 @@ import org.briarproject.util.StringUtils;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.SocketAddress;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collections;
@@ -55,11 +52,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javax.inject.Inject;
import javax.net.SocketFactory;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
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.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH;
@@ -67,7 +66,6 @@ import static org.briarproject.api.feed.FeedConstants.FETCH_DELAY_INITIAL;
import static org.briarproject.api.feed.FeedConstants.FETCH_INTERVAL;
import static org.briarproject.api.feed.FeedConstants.FETCH_UNIT;
import static org.briarproject.api.feed.FeedConstants.KEY_FEEDS;
import static org.briarproject.api.plugins.TorConstants.SOCKS_PORT;
class FeedManagerImpl implements FeedManager, Client, EventListener {
@@ -79,6 +77,8 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
"466565644d616e6167657202fb797097"
+ "255af837abbf8c16e250b3c2ccc286eb"));
private static final int CONNECT_TIMEOUT = 60 * 1000; // Milliseconds
private final ScheduledExecutorService feedExecutor;
private final Executor ioExecutor;
private final DatabaseComponent db;
@@ -86,6 +86,7 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
private final ClientHelper clientHelper;
private final IdentityManager identityManager;
private final BlogManager blogManager;
private final SocketFactory torSocketFactory;
private final AtomicBoolean fetcherStarted = new AtomicBoolean(false);
@Inject
@@ -99,7 +100,8 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
FeedManagerImpl(ScheduledExecutorService feedExecutor,
@IoExecutor Executor ioExecutor, DatabaseComponent db,
PrivateGroupFactory privateGroupFactory, ClientHelper clientHelper,
IdentityManager identityManager, BlogManager blogManager) {
IdentityManager identityManager, BlogManager blogManager,
SocketFactory torSocketFactory) {
this.feedExecutor = feedExecutor;
this.ioExecutor = ioExecutor;
@@ -108,6 +110,7 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
this.clientHelper = clientHelper;
this.identityManager = identityManager;
this.blogManager = blogManager;
this.torSocketFactory = torSocketFactory;
}
@Override
@@ -354,14 +357,10 @@ class FeedManagerImpl implements FeedManager, Client, EventListener {
}
private InputStream getFeedInputStream(String url) throws IOException {
// Set proxy
SocketAddress socketAddress =
new InetSocketAddress("127.0.0.1", SOCKS_PORT);
Proxy proxy = new Proxy(Proxy.Type.SOCKS, socketAddress);
// Build HTTP Client
OkHttpClient client = new OkHttpClient.Builder()
// .proxy(proxy)
.socketFactory(torSocketFactory)
.connectTimeout(CONNECT_TIMEOUT, MILLISECONDS)
.build();
// Build Request

View File

@@ -1,9 +1,5 @@
package org.briarproject.reporting;
import net.sourceforge.jsocks.socks.Socks5Proxy;
import net.sourceforge.jsocks.socks.SocksException;
import net.sourceforge.jsocks.socks.SocksSocket;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.reporting.DevConfig;
import org.briarproject.api.reporting.DevReporter;
@@ -21,10 +17,10 @@ import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.logging.Logger;
import javax.net.SocketFactory;
import static java.util.logging.Level.WARNING;
class DevReporterImpl implements DevReporter {
@@ -35,21 +31,28 @@ class DevReporterImpl implements DevReporter {
private static final int SOCKET_TIMEOUT = 30 * 1000; // 30 seconds
private static final int LINE_LENGTH = 70;
private CryptoComponent crypto;
private DevConfig devConfig;
private final CryptoComponent crypto;
private final DevConfig devConfig;
private final SocketFactory torSocketFactory;
public DevReporterImpl(CryptoComponent crypto, DevConfig devConfig) {
public DevReporterImpl(CryptoComponent crypto, DevConfig devConfig,
SocketFactory torSocketFactory) {
this.crypto = crypto;
this.devConfig = devConfig;
this.torSocketFactory = torSocketFactory;
}
private Socket connectToDevelopers(int socksPort)
throws UnknownHostException, SocksException, SocketException {
Socks5Proxy proxy = new Socks5Proxy("127.0.0.1", socksPort);
proxy.resolveAddrLocally(false);
Socket s = new SocksSocket(proxy, devConfig.getDevOnionAddress(), 80);
s.setSoTimeout(SOCKET_TIMEOUT);
return s;
private Socket connectToDevelopers() throws IOException {
String onion = devConfig.getDevOnionAddress();
Socket s = null;
try {
s = torSocketFactory.createSocket(onion, 80);
s.setSoTimeout(SOCKET_TIMEOUT);
return s;
} catch (IOException e) {
tryToClose(s);
throw e;
}
}
@Override
@@ -74,7 +77,7 @@ class DevReporterImpl implements DevReporter {
}
@Override
public void sendReports(File reportDir, int socksPort) {
public void sendReports(File reportDir) {
File[] reports = reportDir.listFiles();
if (reports == null || reports.length == 0)
return; // No reports to send
@@ -84,7 +87,7 @@ class DevReporterImpl implements DevReporter {
OutputStream out = null;
InputStream in = null;
try {
Socket s = connectToDevelopers(socksPort);
Socket s = connectToDevelopers();
out = s.getOutputStream();
in = new FileInputStream(f);
IoUtils.copy(in, out);
@@ -106,4 +109,12 @@ class DevReporterImpl implements DevReporter {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
private void tryToClose(Socket s) {
try {
if (s != null) s.close();
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
}

View File

@@ -4,6 +4,8 @@ import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.reporting.DevConfig;
import org.briarproject.api.reporting.DevReporter;
import javax.net.SocketFactory;
import dagger.Module;
import dagger.Provides;
@@ -12,7 +14,7 @@ public class ReportingModule {
@Provides
DevReporter provideDevReportTask(CryptoComponent crypto,
DevConfig devConfig) {
return new DevReporterImpl(crypto, devConfig);
DevConfig devConfig, SocketFactory torSocketFactory) {
return new DevReporterImpl(crypto, devConfig, torSocketFactory);
}
}

View File

@@ -0,0 +1,22 @@
package org.briarproject.socks;
import java.net.InetSocketAddress;
import javax.net.SocketFactory;
import dagger.Module;
import dagger.Provides;
import static org.briarproject.api.plugins.TorConstants.CONNECT_TO_PROXY_TIMEOUT;
import static org.briarproject.api.plugins.TorConstants.SOCKS_PORT;
@Module
public class SocksModule {
@Provides
SocketFactory provideTorSocketFactory() {
InetSocketAddress proxy = new InetSocketAddress("127.0.0.1",
SOCKS_PORT);
return new SocksSocketFactory(proxy, CONNECT_TO_PROXY_TIMEOUT);
}
}

View File

@@ -0,0 +1,108 @@
package org.briarproject.socks;
import org.briarproject.util.ByteUtils;
import org.briarproject.util.IoUtils;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
class SocksSocket extends Socket {
private final SocketAddress proxy;
private final int connectToProxyTimeout;
SocksSocket(SocketAddress proxy, int connectToProxyTimeout) {
this.proxy = proxy;
this.connectToProxyTimeout = connectToProxyTimeout;
}
@Override
public void connect(SocketAddress endpoint, int timeout)
throws IOException {
// Validate the endpoint
if (!(endpoint instanceof InetSocketAddress))
throw new IllegalArgumentException();
InetSocketAddress inet = (InetSocketAddress) endpoint;
String host = inet.getHostName();
if (host.length() > 255) throw new IllegalArgumentException();
int port = inet.getPort();
// Connect to the proxy
super.connect(proxy, connectToProxyTimeout);
OutputStream out = getOutputStream();
InputStream in = getInputStream();
// Request SOCKS 5 with no authentication
sendMethodRequest(out);
receiveMethodResponse(in);
// Use the supplied timeout temporarily
int oldTimeout = getSoTimeout();
setSoTimeout(timeout);
// Connect to the endpoint via the proxy
sendConnectRequest(out, host, port);
receiveConnectResponse(in);
// Restore the old timeout
setSoTimeout(oldTimeout);
}
private void sendMethodRequest(OutputStream out) throws IOException {
byte[] methodRequest = new byte[] {
5, // SOCKS version is 5
1, // Number of methods is 1
0 // Method is 0, no authentication
};
out.write(methodRequest);
out.flush();
}
private void receiveMethodResponse(InputStream in) throws IOException {
byte[] methodResponse = new byte[2];
IoUtils.read(in, methodResponse);
byte version = methodResponse[0];
byte method = methodResponse[1];
if (version != 5)
throw new IOException("Unsupported SOCKS version: " + version);
if (method == (byte) 255)
throw new IOException("Proxy requires authentication");
if (method != 0)
throw new IOException("Unsupported auth method: " + method);
}
private void sendConnectRequest(OutputStream out, String host, int port)
throws IOException {
byte[] connectRequest = new byte[7 + host.length()];
connectRequest[0] = 5; // SOCKS version is 5
connectRequest[1] = 1; // Command is 1, connect
connectRequest[3] = 3; // Address type is 3, domain name
connectRequest[4] = (byte) host.length(); // Length of domain name
for (int i = 0; i < host.length(); i++)
connectRequest[5 + i] = (byte) host.charAt(i);
ByteUtils.writeUint16(port, connectRequest, connectRequest.length - 2);
out.write(connectRequest);
out.flush();
}
private void receiveConnectResponse(InputStream in) throws IOException {
byte[] connectResponse = new byte[4];
IoUtils.read(in, connectResponse);
byte version = connectResponse[0];
byte reply = connectResponse[1];
byte addressType = connectResponse[3];
if (version != 5)
throw new IOException("Unsupported SOCKS version: " + version);
if (reply != 0)
throw new IOException("Connection failed: " + reply);
if (addressType == 1) IoUtils.read(in, new byte[4]); // IPv4
else if (addressType == 4) IoUtils.read(in, new byte[16]); // IPv6
else throw new IOException("Unsupported address type: " + addressType);
IoUtils.read(in, new byte[2]); // Port number
}
}

View File

@@ -0,0 +1,49 @@
package org.briarproject.socks;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import javax.net.SocketFactory;
class SocksSocketFactory extends SocketFactory {
private final SocketAddress proxy;
private final int connectToProxyTimeout;
SocksSocketFactory(SocketAddress proxy, int connectToProxyTimeout) {
this.proxy = proxy;
this.connectToProxyTimeout = connectToProxyTimeout;
}
@Override
public Socket createSocket() {
return new SocksSocket(proxy, connectToProxyTimeout);
}
@Override
public Socket createSocket(String host, int port) throws IOException {
Socket socket = createSocket();
socket.connect(InetSocketAddress.createUnresolved(host, port));
return socket;
}
@Override
public Socket createSocket(InetAddress host, int port) {
throw new UnsupportedOperationException();
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost,
int localPort) {
throw new UnsupportedOperationException();
}
@Override
public Socket createSocket(InetAddress address, int port,
InetAddress localAddress, int localPort) throws IOException {
throw new UnsupportedOperationException();
}
}

View File

@@ -1,5 +1,7 @@
package org.briarproject.util;
import java.io.Closeable;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -22,19 +24,34 @@ public class IoUtils {
throws IOException {
byte[] buf = new byte[4096];
try {
try {
while (true) {
int read = in.read(buf);
if (read == -1) break;
out.write(buf, 0, read);
}
out.flush();
} finally {
in.close();
while (true) {
int read = in.read(buf);
if (read == -1) break;
out.write(buf, 0, read);
}
} finally {
in.close();
out.flush();
out.close();
} catch (IOException e) {
tryToClose(in);
tryToClose(out);
}
}
private static void tryToClose(Closeable c) {
try {
if (c != null) c.close();
} catch (IOException e) {
// We did our best
}
}
public static void read(InputStream in, byte[] b) throws IOException {
int offset = 0;
while (offset < b.length) {
int read = in.read(b, offset, b.length - offset);
if (read == -1) throw new EOFException();
offset += read;
}
}
}