Merge branch 'windows-tor-plugin' into 'master'

Add Tor plugin for Windows

See merge request briar/briar!1666
This commit is contained in:
Torsten Grote
2022-06-22 17:08:23 +00:00
9 changed files with 241 additions and 38 deletions

View File

@@ -107,7 +107,7 @@ import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@ParametersNotNullByDefault @ParametersNotNullByDefault
abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener { 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 = { private static final String[] EVENTS = {
"CIRC", "CIRC",
@@ -124,7 +124,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private static final int COOKIE_POLLING_INTERVAL_MS = 200; private static final int COOKIE_POLLING_INTERVAL_MS = 200;
private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}"); 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 Executor connectionStatusExecutor;
private final NetworkManager networkManager; private final NetworkManager networkManager;
private final LocationUtils locationUtils; private final LocationUtils locationUtils;
@@ -254,34 +255,15 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
Map<String, String> env = pb.environment(); Map<String, String> env = pb.environment();
env.put("HOME", torDirectory.getAbsolutePath()); env.put("HOME", torDirectory.getAbsolutePath());
pb.directory(torDirectory); pb.directory(torDirectory);
pb.redirectErrorStream(true);
try { try {
torProcess = pb.start(); torProcess = pb.start();
} catch (SecurityException | IOException e) { } catch (SecurityException | IOException e) {
throw new PluginException(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 { try {
// Wait for the process to detach or exit // Wait for the Tor process to start
int exit = torProcess.waitFor(); waitForTorToStart(torProcess);
if (exit != 0) {
if (LOG.isLoggable(WARNING))
LOG.warning("Tor exited with value " + exit);
throw new PluginException();
}
// Wait for the auth cookie file to be created/updated // Wait for the auth cookie file to be created/updated
long start = clock.currentTimeMillis(); long start = clock.currentTimeMillis();
while (cookieFile.length() < 32) { while (cookieFile.length() < 32) {
@@ -397,7 +379,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return zin; 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(name);
strb.append(" "); strb.append(" ");
strb.append(value); strb.append(value);
@@ -405,13 +387,17 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
} }
private InputStream getConfigInputStream() { private InputStream getConfigInputStream() {
File dataDirectory = new File(torDirectory, ".tor");
StringBuilder strb = new StringBuilder(); StringBuilder strb = new StringBuilder();
append(strb, "ControlPort", torControlPort); append(strb, "ControlPort", torControlPort);
append(strb, "CookieAuthentication", 1); append(strb, "CookieAuthentication", 1);
append(strb, "DataDirectory", dataDirectory.getAbsolutePath());
append(strb, "DisableNetwork", 1); append(strb, "DisableNetwork", 1);
append(strb, "RunAsDaemon", 1); append(strb, "RunAsDaemon", 1);
append(strb, "SafeSocks", 1); append(strb, "SafeSocks", 1);
append(strb, "SocksPort", torSocksPort); append(strb, "SocksPort", torSocksPort);
strb.append("GeoIPFile\n");
strb.append("GeoIPv6File\n");
//noinspection CharsetObjectCanBeUsed //noinspection CharsetObjectCanBeUsed
return new ByteArrayInputStream( return new ByteArrayInputStream(
strb.toString().getBytes(Charset.forName("UTF-8"))); 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() { private void bind() {
ioExecutor.execute(() -> { ioExecutor.execute(() -> {
// If there's already a port number stored in config, reuse it // If there's already a port number stored in config, reuse it

View File

@@ -18,7 +18,9 @@ dependencies {
implementation "net.java.dev.jna:jna:$jna_version" implementation "net.java.dev.jna:jna:$jna_version"
implementation "net.java.dev.jna:jna-platform:$jna_version" implementation "net.java.dev.jna:jna-platform:$jna_version"
tor "org.briarproject:tor-linux:$tor_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-linux:$obfs4proxy_version"
tor "org.briarproject:obfs4proxy-windows:$obfs4proxy_version"
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version" annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -16,9 +16,7 @@ public class DesktopSecureRandomModule {
@Provides @Provides
@Singleton @Singleton
SecureRandomProvider provideSecureRandomProvider() { SecureRandomProvider provideSecureRandomProvider() {
if (isLinux() || isMac()) if (isLinux() || isMac()) return new UnixSecureRandomProvider();
return new UnixSecureRandomProvider(); return () -> null; // Use system default
// TODO: Create a secure random provider for Windows
throw new UnsupportedOperationException();
} }
} }

View File

@@ -25,7 +25,9 @@ dependencyVerification {
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd', '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.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-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-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-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.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', 'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',

View File

@@ -60,7 +60,12 @@ void jarFactory(Jar jarTask, jarArchitecture) {
} }
{ {
it.duplicatesStrategy(DuplicatesStrategy.EXCLUDE) 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) { for (String arch : architectures) {
if (arch != jarArchitecture) { if (arch != jarArchitecture) {
exclude "obfs4proxy_" + arch + ".zip" exclude "obfs4proxy_" + arch + ".zip"
@@ -113,6 +118,10 @@ task x86LinuxJar(type: Jar) {
jarFactory(it, 'linux-x86_64') jarFactory(it, 'linux-x86_64')
} }
task windowsJar(type: Jar) {
jarFactory(it, 'windows-x86_64')
}
task linuxJars { task linuxJars {
dependsOn(aarch64LinuxJar, armhfLinuxJar, x86LinuxJar) dependsOn(aarch64LinuxJar, armhfLinuxJar, x86LinuxJar)
} }

View File

@@ -21,6 +21,7 @@ import org.briarproject.bramble.event.DefaultEventExecutorModule
import org.briarproject.bramble.network.JavaNetworkModule import org.briarproject.bramble.network.JavaNetworkModule
import org.briarproject.bramble.plugin.tor.CircumventionModule import org.briarproject.bramble.plugin.tor.CircumventionModule
import org.briarproject.bramble.plugin.tor.UnixTorPluginFactory import org.briarproject.bramble.plugin.tor.UnixTorPluginFactory
import org.briarproject.bramble.plugin.tor.WindowsTorPluginFactory
import org.briarproject.bramble.socks.SocksModule import org.briarproject.bramble.socks.SocksModule
import org.briarproject.bramble.system.ClockModule import org.briarproject.bramble.system.ClockModule
import org.briarproject.bramble.system.DefaultTaskSchedulerModule 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.system.JavaSystemModule
import org.briarproject.bramble.util.OsUtils.isLinux import org.briarproject.bramble.util.OsUtils.isLinux
import org.briarproject.bramble.util.OsUtils.isMac 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.blogs.HeadlessBlogModule
import org.briarproject.briar.headless.contact.HeadlessContactModule import org.briarproject.briar.headless.contact.HeadlessContactModule
import org.briarproject.briar.headless.event.HeadlessEventModule import org.briarproject.briar.headless.event.HeadlessEventModule
@@ -94,9 +96,15 @@ internal class HeadlessModule(private val appDir: File) {
@Provides @Provides
@Singleton @Singleton
internal fun providePluginConfig(tor: UnixTorPluginFactory): PluginConfig { internal fun providePluginConfig(
val duplex: List<DuplexPluginFactory> = unixTor: UnixTorPluginFactory,
if (isLinux() || isMac()) listOf(tor) else emptyList() winTor: WindowsTorPluginFactory
): PluginConfig {
val duplex: List<DuplexPluginFactory> = when {
isLinux() || isMac() -> listOf(unixTor)
isWindows() -> listOf(winTor)
else -> emptyList()
}
return object : PluginConfig { return object : PluginConfig {
override fun getDuplexFactories(): Collection<DuplexPluginFactory> = duplex override fun getDuplexFactories(): Collection<DuplexPluginFactory> = duplex
override fun getSimplexFactories(): Collection<SimplexPluginFactory> = emptyList() override fun getSimplexFactories(): Collection<SimplexPluginFactory> = emptyList()

View File

@@ -8,6 +8,8 @@ import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.types.int import com.github.ajalt.clikt.parameters.types.int
import org.bouncycastle.util.encoders.Base64.toBase64String import org.bouncycastle.util.encoders.Base64.toBase64String
import org.briarproject.bramble.BrambleCoreEagerSingletons 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.briarproject.briar.BriarCoreEagerSingletons
import org.slf4j.impl.SimpleLogger.DEFAULT_LOG_LEVEL_KEY import org.slf4j.impl.SimpleLogger.DEFAULT_LOG_LEVEL_KEY
import java.io.File import java.io.File
@@ -90,11 +92,13 @@ private class Main : CliktCommand(
} else if (!file.isDirectory) { } else if (!file.isDirectory) {
throw IOException("Data dir is not a directory: ${file.absolutePath}") throw IOException("Data dir is not a directory: ${file.absolutePath}")
} }
val perms = HashSet<PosixFilePermission>() if (isLinux() || isMac()) {
perms.add(OWNER_READ) val perms = HashSet<PosixFilePermission>()
perms.add(OWNER_WRITE) perms.add(OWNER_READ)
perms.add(OWNER_EXECUTE) perms.add(OWNER_WRITE)
setPosixFilePermissions(file.toPath(), perms) perms.add(OWNER_EXECUTE)
setPosixFilePermissions(file.toPath(), perms)
}
return file return file
} }