mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-11 18:29:05 +01:00
Merge branch 'windows-tor-plugin' into 'master'
Add Tor plugin for Windows See merge request briar/briar!1666
This commit is contained in:
@@ -107,7 +107,7 @@ 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());
|
||||
protected static final Logger LOG = getLogger(TorPlugin.class.getName());
|
||||
|
||||
private static final String[] EVENTS = {
|
||||
"CIRC",
|
||||
@@ -124,7 +124,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
private 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;
|
||||
protected final Executor ioExecutor;
|
||||
private final Executor wakefulIoExecutor;
|
||||
private final Executor connectionStatusExecutor;
|
||||
private final NetworkManager networkManager;
|
||||
private final LocationUtils locationUtils;
|
||||
@@ -254,34 +255,15 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
Map<String, String> env = pb.environment();
|
||||
env.put("HOME", torDirectory.getAbsolutePath());
|
||||
pb.directory(torDirectory);
|
||||
pb.redirectErrorStream(true);
|
||||
try {
|
||||
torProcess = pb.start();
|
||||
} catch (SecurityException | IOException e) {
|
||||
throw new PluginException(e);
|
||||
}
|
||||
// Log the process's standard output until it detaches
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
Scanner stdout = new Scanner(torProcess.getInputStream());
|
||||
Scanner stderr = new Scanner(torProcess.getErrorStream());
|
||||
while (stdout.hasNextLine() || stderr.hasNextLine()) {
|
||||
if (stdout.hasNextLine()) {
|
||||
LOG.info(stdout.nextLine());
|
||||
}
|
||||
if (stderr.hasNextLine()) {
|
||||
LOG.info(stderr.nextLine());
|
||||
}
|
||||
}
|
||||
stdout.close();
|
||||
stderr.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);
|
||||
throw new PluginException();
|
||||
}
|
||||
// Wait for the Tor process to start
|
||||
waitForTorToStart(torProcess);
|
||||
// Wait for the auth cookie file to be created/updated
|
||||
long start = clock.currentTimeMillis();
|
||||
while (cookieFile.length() < 32) {
|
||||
@@ -397,7 +379,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
return zin;
|
||||
}
|
||||
|
||||
private static void append(StringBuilder strb, String name, int value) {
|
||||
private static void append(StringBuilder strb, String name, Object value) {
|
||||
strb.append(name);
|
||||
strb.append(" ");
|
||||
strb.append(value);
|
||||
@@ -405,13 +387,17 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
|
||||
private InputStream getConfigInputStream() {
|
||||
File dataDirectory = new File(torDirectory, ".tor");
|
||||
StringBuilder strb = new StringBuilder();
|
||||
append(strb, "ControlPort", torControlPort);
|
||||
append(strb, "CookieAuthentication", 1);
|
||||
append(strb, "DataDirectory", dataDirectory.getAbsolutePath());
|
||||
append(strb, "DisableNetwork", 1);
|
||||
append(strb, "RunAsDaemon", 1);
|
||||
append(strb, "SafeSocks", 1);
|
||||
append(strb, "SocksPort", torSocksPort);
|
||||
strb.append("GeoIPFile\n");
|
||||
strb.append("GeoIPv6File\n");
|
||||
//noinspection CharsetObjectCanBeUsed
|
||||
return new ByteArrayInputStream(
|
||||
strb.toString().getBytes(Charset.forName("UTF-8")));
|
||||
@@ -442,6 +428,23 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
|
||||
}
|
||||
}
|
||||
|
||||
protected void waitForTorToStart(Process torProcess)
|
||||
throws InterruptedException, PluginException {
|
||||
Scanner stdout = new Scanner(torProcess.getInputStream());
|
||||
// Log the first line of stdout (contains Tor and library versions)
|
||||
if (stdout.hasNextLine()) LOG.info(stdout.nextLine());
|
||||
// Read the process's stdout (and redirected stderr) until it detaches
|
||||
while (stdout.hasNextLine()) stdout.nextLine();
|
||||
stdout.close();
|
||||
// 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);
|
||||
throw new PluginException();
|
||||
}
|
||||
}
|
||||
|
||||
private void bind() {
|
||||
ioExecutor.execute(() -> {
|
||||
// If there's already a port number stored in config, reuse it
|
||||
|
||||
@@ -18,7 +18,9 @@ dependencies {
|
||||
implementation "net.java.dev.jna:jna:$jna_version"
|
||||
implementation "net.java.dev.jna:jna-platform:$jna_version"
|
||||
tor "org.briarproject:tor-linux:$tor_version"
|
||||
tor "org.briarproject:tor-windows:$tor_version"
|
||||
tor "org.briarproject:obfs4proxy-linux:$obfs4proxy_version"
|
||||
tor "org.briarproject:obfs4proxy-windows:$obfs4proxy_version"
|
||||
|
||||
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package org.briarproject.bramble.plugin.tor;
|
||||
|
||||
import com.sun.jna.platform.win32.Kernel32;
|
||||
|
||||
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.File;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getProcessId() {
|
||||
return Kernel32.INSTANCE.GetCurrentProcessId();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void waitForTorToStart(Process torProcess)
|
||||
throws InterruptedException, PluginException {
|
||||
// On Windows the RunAsDaemon option has no effect, so Tor won't detach.
|
||||
// Wait for the control port to be opened, then continue to read its
|
||||
// stdout and stderr in a background thread until it exits.
|
||||
BlockingQueue<Boolean> success = new ArrayBlockingQueue<>(1);
|
||||
ioExecutor.execute(() -> {
|
||||
boolean started = false;
|
||||
// Read the process's stdout (and redirected stderr)
|
||||
Scanner stdout = new Scanner(torProcess.getInputStream());
|
||||
// Log the first line of stdout (contains Tor and library versions)
|
||||
if (stdout.hasNextLine()) LOG.info(stdout.nextLine());
|
||||
// Startup has succeeded when the control port is open
|
||||
while (stdout.hasNextLine()) {
|
||||
String line = stdout.nextLine();
|
||||
if (!started && line.contains("Opened Control listener")) {
|
||||
success.add(true);
|
||||
started = true;
|
||||
}
|
||||
}
|
||||
stdout.close();
|
||||
// If the control port wasn't opened, startup has failed
|
||||
if (!started) success.add(false);
|
||||
// Wait for the process to exit
|
||||
try {
|
||||
int exit = torProcess.waitFor();
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Tor exited with value " + exit);
|
||||
} catch (InterruptedException e1) {
|
||||
LOG.warning("Interrupted while waiting for Tor to exit");
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
});
|
||||
// Wait for the startup result
|
||||
if (!success.take()) throw new PluginException();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package org.briarproject.bramble.plugin.tor;
|
||||
|
||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
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.BackoffFactory;
|
||||
import org.briarproject.bramble.api.plugin.PluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.TorControlPort;
|
||||
import org.briarproject.bramble.api.plugin.TorDirectory;
|
||||
import org.briarproject.bramble.api.plugin.TorSocksPort;
|
||||
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.api.system.WakefulIoExecutor;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static org.briarproject.bramble.util.OsUtils.isWindows;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
public class WindowsTorPluginFactory extends TorPluginFactory {
|
||||
|
||||
@Inject
|
||||
WindowsTorPluginFactory(@IoExecutor Executor ioExecutor,
|
||||
@WakefulIoExecutor Executor wakefulIoExecutor,
|
||||
NetworkManager networkManager,
|
||||
LocationUtils locationUtils,
|
||||
EventBus eventBus,
|
||||
SocketFactory torSocketFactory,
|
||||
BackoffFactory backoffFactory,
|
||||
ResourceProvider resourceProvider,
|
||||
CircumventionProvider circumventionProvider,
|
||||
BatteryManager batteryManager,
|
||||
Clock clock,
|
||||
CryptoComponent crypto,
|
||||
@TorDirectory File torDirectory,
|
||||
@TorSocksPort int torSocksPort,
|
||||
@TorControlPort int torControlPort) {
|
||||
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
|
||||
eventBus, torSocketFactory, backoffFactory, resourceProvider,
|
||||
circumventionProvider, batteryManager, clock, crypto,
|
||||
torDirectory, torSocksPort, torControlPort);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
String getArchitectureForTorBinary() {
|
||||
if (!isWindows()) return null;
|
||||
String arch = System.getProperty("os.arch");
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("System's os.arch is " + arch);
|
||||
}
|
||||
if (arch.equals("amd64")) return "windows-x86_64";
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
TorPlugin createPluginInstance(Backoff backoff,
|
||||
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
|
||||
String architecture) {
|
||||
return new WindowsTorPlugin(ioExecutor, wakefulIoExecutor,
|
||||
networkManager, locationUtils, torSocketFactory, clock,
|
||||
resourceProvider, circumventionProvider, batteryManager,
|
||||
backoff, torRendezvousCrypto, callback, architecture,
|
||||
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
|
||||
torControlPort);
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,7 @@ public class DesktopSecureRandomModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
SecureRandomProvider provideSecureRandomProvider() {
|
||||
if (isLinux() || isMac())
|
||||
return new UnixSecureRandomProvider();
|
||||
// TODO: Create a secure random provider for Windows
|
||||
throw new UnsupportedOperationException();
|
||||
if (isLinux() || isMac()) return new UnixSecureRandomProvider();
|
||||
return () -> null; // Use system default
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,9 @@ 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-linux:0.0.12:obfs4proxy-linux-0.0.12.jar:3dd83aff25fe1cb3e4eab78a02c76ac921f552be6877b3af83a472438525df2a',
|
||||
'org.briarproject:obfs4proxy-windows:0.0.12:obfs4proxy-windows-0.0.12.jar:392aa4b9d9c6fef0c659c4068d019d6c6471991bbb62ff00553884ec36018c7b',
|
||||
'org.briarproject:tor-linux:0.4.5.12-2:tor-linux-0.4.5.12-2.jar:d275f323faf5e70b33d2c8a1bdab1bb3ab5a0d8f4e23c4a6dda03d86f4e95838',
|
||||
'org.briarproject:tor-windows:0.4.5.12-2:tor-windows-0.4.5.12-2.jar:46599a15d099ed35a360113293f66acc119571c24ec2e37e85e4fb54b4722e07',
|
||||
'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',
|
||||
|
||||
@@ -60,7 +60,12 @@ void jarFactory(Jar jarTask, jarArchitecture) {
|
||||
}
|
||||
{
|
||||
it.duplicatesStrategy(DuplicatesStrategy.EXCLUDE)
|
||||
String[] architectures = ["linux-aarch64", "linux-armhf", "linux-x86_64"]
|
||||
String[] architectures = [
|
||||
"linux-aarch64",
|
||||
"linux-armhf",
|
||||
"linux-x86_64",
|
||||
"windows-x86_64"
|
||||
]
|
||||
for (String arch : architectures) {
|
||||
if (arch != jarArchitecture) {
|
||||
exclude "obfs4proxy_" + arch + ".zip"
|
||||
@@ -113,6 +118,10 @@ task x86LinuxJar(type: Jar) {
|
||||
jarFactory(it, 'linux-x86_64')
|
||||
}
|
||||
|
||||
task windowsJar(type: Jar) {
|
||||
jarFactory(it, 'windows-x86_64')
|
||||
}
|
||||
|
||||
task linuxJars {
|
||||
dependsOn(aarch64LinuxJar, armhfLinuxJar, x86LinuxJar)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.briarproject.bramble.event.DefaultEventExecutorModule
|
||||
import org.briarproject.bramble.network.JavaNetworkModule
|
||||
import org.briarproject.bramble.plugin.tor.CircumventionModule
|
||||
import org.briarproject.bramble.plugin.tor.UnixTorPluginFactory
|
||||
import org.briarproject.bramble.plugin.tor.WindowsTorPluginFactory
|
||||
import org.briarproject.bramble.socks.SocksModule
|
||||
import org.briarproject.bramble.system.ClockModule
|
||||
import org.briarproject.bramble.system.DefaultTaskSchedulerModule
|
||||
@@ -29,6 +30,7 @@ import org.briarproject.bramble.system.DesktopSecureRandomModule
|
||||
import org.briarproject.bramble.system.JavaSystemModule
|
||||
import org.briarproject.bramble.util.OsUtils.isLinux
|
||||
import org.briarproject.bramble.util.OsUtils.isMac
|
||||
import org.briarproject.bramble.util.OsUtils.isWindows
|
||||
import org.briarproject.briar.headless.blogs.HeadlessBlogModule
|
||||
import org.briarproject.briar.headless.contact.HeadlessContactModule
|
||||
import org.briarproject.briar.headless.event.HeadlessEventModule
|
||||
@@ -94,9 +96,15 @@ internal class HeadlessModule(private val appDir: File) {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
internal fun providePluginConfig(tor: UnixTorPluginFactory): PluginConfig {
|
||||
val duplex: List<DuplexPluginFactory> =
|
||||
if (isLinux() || isMac()) listOf(tor) else emptyList()
|
||||
internal fun providePluginConfig(
|
||||
unixTor: UnixTorPluginFactory,
|
||||
winTor: WindowsTorPluginFactory
|
||||
): PluginConfig {
|
||||
val duplex: List<DuplexPluginFactory> = when {
|
||||
isLinux() || isMac() -> listOf(unixTor)
|
||||
isWindows() -> listOf(winTor)
|
||||
else -> emptyList()
|
||||
}
|
||||
return object : PluginConfig {
|
||||
override fun getDuplexFactories(): Collection<DuplexPluginFactory> = duplex
|
||||
override fun getSimplexFactories(): Collection<SimplexPluginFactory> = emptyList()
|
||||
|
||||
@@ -8,6 +8,8 @@ import com.github.ajalt.clikt.parameters.options.option
|
||||
import com.github.ajalt.clikt.parameters.types.int
|
||||
import org.bouncycastle.util.encoders.Base64.toBase64String
|
||||
import org.briarproject.bramble.BrambleCoreEagerSingletons
|
||||
import org.briarproject.bramble.util.OsUtils.isLinux
|
||||
import org.briarproject.bramble.util.OsUtils.isMac
|
||||
import org.briarproject.briar.BriarCoreEagerSingletons
|
||||
import org.slf4j.impl.SimpleLogger.DEFAULT_LOG_LEVEL_KEY
|
||||
import java.io.File
|
||||
@@ -90,11 +92,13 @@ private class Main : CliktCommand(
|
||||
} else if (!file.isDirectory) {
|
||||
throw IOException("Data dir is not a directory: ${file.absolutePath}")
|
||||
}
|
||||
if (isLinux() || isMac()) {
|
||||
val perms = HashSet<PosixFilePermission>()
|
||||
perms.add(OWNER_READ)
|
||||
perms.add(OWNER_WRITE)
|
||||
perms.add(OWNER_EXECUTE)
|
||||
setPosixFilePermissions(file.toPath(), perms)
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user