mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Make use of Tor on Windows
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user