Compare commits

...

2 Commits

Author SHA1 Message Date
Nico Alt
fcd52261fe Temporarily add self-built Windows tor binary
Until it's officially published by the Briar Project.
2022-02-09 00:00:01 +00:00
Nico Alt
fa6f7e62ed Make use of Tor on Windows 2022-02-09 00:00:00 +00:00
9 changed files with 304 additions and 43 deletions

View File

@@ -105,43 +105,46 @@ import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@ParametersNotNullByDefault
abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private static final Logger LOG = getLogger(TorPlugin.class.getName());
static final Logger LOG = getLogger(TorPlugin.class.getName());
private static final String[] EVENTS = {
static final String[] EVENTS = {
"CIRC", "ORCONN", "HS_DESC", "NOTICE", "WARN", "ERR"
};
private static final String OWNER = "__OwningControllerProcess";
private static final int COOKIE_TIMEOUT_MS = 3000;
private static final int COOKIE_POLLING_INTERVAL_MS = 200;
static final String OWNER = "__OwningControllerProcess";
static final int COOKIE_TIMEOUT_MS = 3000;
static final int COOKIE_POLLING_INTERVAL_MS = 200;
private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}");
private final Executor ioExecutor, wakefulIoExecutor;
private final Executor connectionStatusExecutor;
private final NetworkManager networkManager;
final NetworkManager networkManager;
private final LocationUtils locationUtils;
private final SocketFactory torSocketFactory;
private final Clock clock;
private final BatteryManager batteryManager;
final Clock clock;
final BatteryManager batteryManager;
private final Backoff backoff;
private final TorRendezvousCrypto torRendezvousCrypto;
private final PluginCallback callback;
final PluginCallback callback;
private final String architecture;
private final CircumventionProvider circumventionProvider;
private final ResourceProvider resourceProvider;
private final long maxLatency;
private final int maxIdleTime;
private final int socketTimeout;
private final File torDirectory, geoIpFile, configFile;
private final int torSocksPort;
private final int torControlPort;
private final File doneFile, cookieFile;
private final AtomicBoolean used = new AtomicBoolean(false);
final File torDirectory;
final File geoIpFile;
final File configFile;
final int torSocksPort;
final int torControlPort;
private final File doneFile;
final File cookieFile;
final AtomicBoolean used = new AtomicBoolean(false);
protected final PluginState state = new PluginState();
private volatile Socket controlSocket = null;
private volatile TorControlConnection controlConnection = null;
private volatile Settings settings = null;
volatile Socket controlSocket = null;
volatile TorControlConnection controlConnection = null;
volatile Settings settings = null;
protected abstract int getProcessId();
@@ -242,6 +245,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
Process torProcess;
ProcessBuilder pb =
new ProcessBuilder(torPath, "-f", configPath, OWNER, pid);
// TODO: pb.redirectErrorStream on Linux, too?
Map<String, String> env = pb.environment();
env.put("HOME", torDirectory.getAbsolutePath());
pb.directory(torDirectory);
@@ -318,8 +322,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
bind();
}
// TODO: Remove after a reasonable migration period (added 2020-06-25)
private Settings migrateSettings(Settings settings) {
Settings migrateSettings(Settings settings) {
int network = settings.getInt(PREF_TOR_NETWORK,
DEFAULT_PREF_TOR_NETWORK);
if (network == PREF_TOR_NETWORK_NEVER) {
@@ -330,11 +335,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return settings;
}
private boolean assetsAreUpToDate() {
boolean assetsAreUpToDate() {
return doneFile.lastModified() > getLastUpdateTime();
}
private void installAssets() throws PluginException {
void installAssets() throws PluginException {
try {
// The done file may already exist from a previous installation
//noinspection ResultOfMethodCallIgnored
@@ -389,20 +394,20 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private InputStream getObfs4InputStream() throws IOException {
InputStream in = resourceProvider
.getResourceInputStream("obfs4proxy_" + architecture, ".zip");
.getResourceInputStream("obfs4proxy_" + "linux-x86_64", ".zip");
ZipInputStream zin = new ZipInputStream(in);
if (zin.getNextEntry() == null) throw new IOException();
return zin;
}
private static void append(StringBuilder strb, String name, int value) {
protected static void append(StringBuilder strb, String name, int value) {
strb.append(name);
strb.append(" ");
strb.append(value);
strb.append("\n");
}
private InputStream getConfigInputStream() {
protected InputStream getConfigInputStream() {
StringBuilder strb = new StringBuilder();
append(strb, "ControlPort", torControlPort);
append(strb, "CookieAuthentication", 1);
@@ -415,7 +420,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
strb.toString().getBytes(Charset.forName("UTF-8")));
}
private void listFiles(File f) {
void listFiles(File f) {
if (f.isDirectory()) {
File[] children = f.listFiles();
if (children != null) for (File child : children) listFiles(child);
@@ -424,7 +429,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
}
private byte[] read(File f) throws IOException {
byte[] read(File f) throws IOException {
byte[] b = new byte[(int) f.length()];
FileInputStream in = new FileInputStream(f);
try {
@@ -440,7 +445,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
}
private void bind() {
void bind() {
ioExecutor.execute(() -> {
// If there's already a port number stored in config, reuse it
String portString = settings.get(PREF_TOR_PORT);
@@ -814,8 +819,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
});
}
private void updateConnectionStatus(NetworkStatus status,
boolean charging) {
void updateConnectionStatus(NetworkStatus status,
boolean charging) {
connectionStatusExecutor.execute(() -> {
if (!state.isTorRunning()) return;
boolean online = status.isConnected();

View File

@@ -0,0 +1,12 @@
package org.briarproject.bramble.system;
import javax.annotation.Nullable;
import java.security.Provider;
public class WindowsSecureRandomProvider extends AbstractSecureRandomProvider {
@Nullable
@Override
public Provider getProvider() {
return null;
}
}

View File

@@ -17,7 +17,7 @@ dependencies {
def jna_version = '4.5.2'
implementation "net.java.dev.jna:jna:$jna_version"
implementation "net.java.dev.jna:jna-platform:$jna_version"
tor "org.briarproject:tor:$tor_version"
tor fileTree(dir: 'libs', include: '*.zip')
tor "org.briarproject:obfs4proxy:$obfs4proxy_version@zip"
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"

Binary file not shown.

View File

@@ -32,13 +32,14 @@ import javax.net.SocketFactory;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.OsUtils.isLinux;
import static org.briarproject.bramble.util.OsUtils.isWindows;
@Immutable
@NotNullByDefault
public class UnixTorPluginFactory implements DuplexPluginFactory {
public class DesktopTorPluginFactory implements DuplexPluginFactory {
private static final Logger LOG =
getLogger(UnixTorPluginFactory.class.getName());
getLogger(DesktopTorPluginFactory.class.getName());
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
@@ -62,7 +63,7 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
private final CryptoComponent crypto;
@Inject
UnixTorPluginFactory(@IoExecutor Executor ioExecutor,
DesktopTorPluginFactory(@IoExecutor Executor ioExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor,
NetworkManager networkManager,
LocationUtils locationUtils,
@@ -107,25 +108,33 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
@Override
public DuplexPlugin createPlugin(PluginCallback callback) {
// Check that we have a Tor binary for this architecture
String architecture = null;
if (isLinux()) {
String arch = System.getProperty("os.arch");
if (LOG.isLoggable(INFO)) {
LOG.info("System's os.arch is " + arch);
}
if (arch.equals("amd64")) {
architecture = "linux-x86_64";
return createUnixPlugin(callback, "linux-x86_64");
} else if (arch.equals("aarch64")) {
architecture = "linux-aarch64";
return createUnixPlugin(callback, "linux-aarch64");
} else if (arch.equals("arm")) {
architecture = "linux-armhf";
return createUnixPlugin(callback, "linux-armhf");
}
}
if (architecture == null) {
LOG.info("Tor is not supported on this architecture");
return null;
if (isWindows()) {
String arch = System.getProperty("os.arch");
if (LOG.isLoggable(INFO)) {
LOG.info("System's os.arch is " + arch);
}
if (arch.equals("amd64")) {
return createWindowsPlugin(callback, "windows-x86_64");
}
}
LOG.info("Tor is not supported on this architecture");
return null;
}
private DuplexPlugin createUnixPlugin(PluginCallback callback, String architecture) {
if (LOG.isLoggable(INFO)) {
LOG.info("The selected architecture for Tor is " + architecture);
}
@@ -143,4 +152,21 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
eventBus.addListener(plugin);
return plugin;
}
private DuplexPlugin createWindowsPlugin(PluginCallback callback, String architecture) {
if (LOG.isLoggable(INFO)) {
LOG.info("The selected architecture for Tor is " + architecture);
}
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
TorRendezvousCrypto torRendezvousCrypto = new TorRendezvousCryptoImpl();
WindowsTorPlugin plugin = new WindowsTorPlugin(ioExecutor, wakefulIoExecutor,
networkManager, locationUtils, torSocketFactory, clock,
resourceProvider, circumventionProvider, batteryManager,
backoff, torRendezvousCrypto, callback, architecture,
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort, torControlPort);
eventBus.addListener(plugin);
return plugin;
}
}

View File

@@ -0,0 +1,217 @@
package org.briarproject.bramble.plugin.tor;
import com.sun.jna.Library;
import com.sun.jna.Native;
import net.freehaven.tor.control.TorControlConnection;
import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider;
import java.io.*;
import java.net.Socket;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.net.SocketFactory;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
@NotNullByDefault
class WindowsTorPlugin extends JavaTorPlugin {
WindowsTorPlugin(Executor ioExecutor,
Executor wakefulIoExecutor,
NetworkManager networkManager,
LocationUtils locationUtils,
SocketFactory torSocketFactory,
Clock clock,
ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto,
PluginCallback callback,
String architecture,
long maxLatency,
int maxIdleTime,
File torDirectory,
int torSocksPort,
int torControlPort) {
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
torSocketFactory, clock, resourceProvider,
circumventionProvider, batteryManager, backoff,
torRendezvousCrypto, callback, architecture,
maxLatency, maxIdleTime, torDirectory, torSocksPort, torControlPort);
}
protected File getTorExecutableFile() {
return new File(torDirectory, "tor.exe");
}
protected InputStream getConfigInputStream() {
StringBuilder strb = new StringBuilder();
append(strb, "ControlPort", torControlPort);
append(strb, "CookieAuthentication", 1);
append(strb, "DisableNetwork", 1);
append(strb, "RunAsDaemon", 1);
append(strb, "SafeSocks", 1);
append(strb, "SocksPort", torSocksPort);
InputStream inputStream = new ByteArrayInputStream(
strb.toString().getBytes(Charset.forName("UTF-8")));
InputStream windowsPaths = new ByteArrayInputStream(getTorrcPaths());
inputStream = new SequenceInputStream(inputStream, windowsPaths);
return inputStream;
}
private byte[] getTorrcPaths() {
StringBuilder sb = new StringBuilder();
sb.append("\n");
sb.append("GeoIPFile ");
sb.append(geoIpFile.getAbsolutePath());
sb.append("\n");
sb.append("GeoIPv6File ");
sb.append(geoIpFile.getAbsolutePath());
sb.append("6");
sb.append("\n");
sb.append("DataDirectory ");
sb.append(torDirectory);
sb.append("\\.tor");
return sb.toString().getBytes(StandardCharsets.UTF_8);
}
@Override
public void start() throws PluginException {
/*
TODO:
- properly handle and throw PluginExceptions etc.
- absolute paths in Windows torrc (Linux too?)
- don't do 10 seconds sleep in main thread
*/
if (used.getAndSet(true)) throw new IllegalStateException();
if (!torDirectory.exists()) {
if (!torDirectory.mkdirs()) {
LOG.warning("Could not create Tor directory.");
throw new PluginException();
}
}
// Load the settings
settings = migrateSettings(callback.getSettings());
// Install or update the assets if necessary
if (!assetsAreUpToDate()) installAssets();
if (cookieFile.exists() && !cookieFile.delete())
LOG.warning("Old auth cookie not deleted");
// Start a new Tor process
LOG.info("Starting Tor");
File torFile = getTorExecutableFile();
String torPath = torFile.getAbsolutePath();
String configPath = configFile.getAbsolutePath();
String pid = String.valueOf(getProcessId());
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
Process torProcess;
ProcessBuilder pb =
new ProcessBuilder(torPath, "-f", configPath, OWNER, pid);
pb.redirectErrorStream(true); // logged only first line on Windows otherwise
Map<String, String> env = pb.environment();
env.put("HOME", torDirectory.getAbsolutePath());
pb.directory(torDirectory);
try {
torProcess = pb.start();
// Log the process's standard output until it detaches
if (LOG.isLoggable(INFO)) {
Scanner stdout = new Scanner(torProcess.getInputStream());
while (stdout.hasNextLine()) {
if (stdout.hasNextLine()) {
LOG.info(stdout.nextLine());
}
}
stdout.close();
}
try {
// Wait for the process to detach or exit
int exit = torProcess.waitFor();
if (exit != 0) {
if (LOG.isLoggable(WARNING))
LOG.warning("Tor exited with value " + exit);
}
// Wait for the auth cookie file to be created/updated
long start = clock.currentTimeMillis();
while (cookieFile.length() < 32) {
if (clock.currentTimeMillis() - start > COOKIE_TIMEOUT_MS) {
LOG.warning("Auth cookie not created");
if (LOG.isLoggable(INFO)) listFiles(torDirectory);
}
Thread.sleep(COOKIE_POLLING_INTERVAL_MS);
}
LOG.info("Auth cookie created");
} catch (InterruptedException e) {
LOG.warning("Interrupted while starting Tor");
Thread.currentThread().interrupt();
e.printStackTrace();
}
} catch (SecurityException | IOException e) {
e.printStackTrace();
}
}
});
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
// Open a control connection and authenticate using the cookie file
controlSocket = new Socket("127.0.0.1", torControlPort);
controlConnection = new TorControlConnection(controlSocket);
controlConnection.authenticate(read(cookieFile));
// Tell Tor to exit when the control connection is closed
controlConnection.takeOwnership();
controlConnection.resetConf(singletonList(OWNER));
// Register to receive events from the Tor process
controlConnection.setEventHandler(this);
controlConnection.setEvents(asList(EVENTS));
// Check whether Tor has already bootstrapped
String phase = controlConnection.getInfo("status/bootstrap-phase");
if (phase != null && phase.contains("PROGRESS=100")) {
LOG.info("Tor has already bootstrapped");
state.setBootstrapped();
}
} catch (IOException e) {
throw new PluginException(e);
}
state.setStarted();
// Check whether we're online
updateConnectionStatus(networkManager.getNetworkStatus(),
batteryManager.isCharging());
// Bind a server socket to receive incoming hidden service connections
bind();
}
@Override
protected int getProcessId() {
return CLibrary.INSTANCE._getpid();
}
private interface CLibrary extends Library {
CLibrary INSTANCE = Native.loadLibrary("msvcrt", CLibrary.class);
int _getpid();
}
}

View File

@@ -9,6 +9,7 @@ import dagger.Provides;
import static org.briarproject.bramble.util.OsUtils.isLinux;
import static org.briarproject.bramble.util.OsUtils.isMac;
import static org.briarproject.bramble.util.OsUtils.isWindows;
@Module
public class DesktopSecureRandomModule {
@@ -18,7 +19,8 @@ public class DesktopSecureRandomModule {
SecureRandomProvider provideSecureRandomProvider() {
if (isLinux() || isMac())
return new UnixSecureRandomProvider();
// TODO: Create a secure random provider for Windows
if (isWindows())
return new WindowsSecureRandomProvider();
throw new UnsupportedOperationException();
}
}

View File

@@ -107,7 +107,7 @@ public class BridgeTest extends BrambleTestCase {
private final File torDir = getTestDirectory();
private final Params params;
private UnixTorPluginFactory factory;
private DesktopTorPluginFactory factory;
public BridgeTest(Params params) {
this.params = params;
@@ -152,7 +152,7 @@ public class BridgeTest extends BrambleTestCase {
return singletonList(params.bridge);
}
};
factory = new UnixTorPluginFactory(ioExecutor, wakefulIoExecutor,
factory = new DesktopTorPluginFactory(ioExecutor, wakefulIoExecutor,
networkManager, locationUtils, eventBus, torSocketFactory,
backoffFactory, resourceProvider, bridgeProvider,
batteryManager, clock, torDir, DEFAULT_SOCKS_PORT,

View File

@@ -25,7 +25,6 @@ dependencyVerification {
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.briarproject:obfs4proxy:0.0.12-dev-40245c4a:obfs4proxy-0.0.12-dev-40245c4a.zip:172029e7058b3a83ac93ac4991a44bf76e16ce8d46f558f5836d57da3cb3a766',
'org.briarproject:tor:0.3.5.17:tor-0.3.5.17.jar:ce0e1f4d8f14878e61b23a35a452bc0f2a8e3117ced5a74773cd78475fa7af39',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',