improve tor identity management

This commit is contained in:
Craig Raw 2021-10-07 12:23:28 +02:00
parent 6f95dbe309
commit c18a2f4388
6 changed files with 97 additions and 8 deletions

View file

@ -2,11 +2,16 @@ package com.sparrowwallet.sparrow;
import com.sparrowwallet.drongo.LogHandler; import com.sparrowwallet.drongo.LogHandler;
import com.sparrowwallet.sparrow.event.TorStatusEvent; import com.sparrowwallet.sparrow.event.TorStatusEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.event.Level; import org.slf4j.event.Level;
public class TorLogHandler implements LogHandler { public class TorLogHandler implements LogHandler {
private static final Logger log = LoggerFactory.getLogger(TorLogHandler.class);
@Override @Override
public void handleLog(String threadName, Level level, String message, String loggerName, long timestamp, StackTraceElement[] callerData) { public void handleLog(String threadName, Level level, String message, String loggerName, long timestamp, StackTraceElement[] callerData) {
log.debug(message);
EventManager.get().post(new TorStatusEvent(message)); EventManager.get().post(new TorStatusEvent(message));
} }
} }

View file

@ -13,6 +13,7 @@ import com.sparrowwallet.sparrow.whirlpool.WhirlpoolException;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.control.*; import javafx.scene.control.*;
import org.controlsfx.glyphfont.Glyph; import org.controlsfx.glyphfont.Glyph;
import org.controlsfx.tools.Platform;
public class MixStatusCell extends TreeTableCell<Entry, UtxoEntry.MixStatus> { public class MixStatusCell extends TreeTableCell<Entry, UtxoEntry.MixStatus> {
public MixStatusCell() { public MixStatusCell() {
@ -68,7 +69,8 @@ public class MixStatusCell extends TreeTableCell<Entry, UtxoEntry.MixStatus> {
Tooltip tt = new Tooltip(); Tooltip tt = new Tooltip();
tt.setText(mixFailReason.getMessage() + (mixError == null ? "" : ": " + mixError) + tt.setText(mixFailReason.getMessage() + (mixError == null ? "" : ": " + mixError) +
"\nMix failures are generally caused by peers disconnecting during a mix." + "\nMix failures are generally caused by peers disconnecting during a mix." +
"\nMake sure your internet connection is stable and the computer is configured to prevent sleeping."); "\nMake sure your internet connection is stable and the computer is configured to prevent sleeping." +
"\nTo prevent sleeping, use the " + getPlatformSleepConfig() + " or enable the function in the Tools menu.");
setTooltip(tt); setTooltip(tt);
} else { } else {
setGraphic(null); setGraphic(null);
@ -76,6 +78,17 @@ public class MixStatusCell extends TreeTableCell<Entry, UtxoEntry.MixStatus> {
} }
} }
private String getPlatformSleepConfig() {
Platform platform = Platform.getCurrent();
if(platform == Platform.OSX) {
return "OSX System Preferences";
} else if(platform == Platform.WINDOWS) {
return "Windows Control Panel";
}
return "system power settings";
}
private void setMixProgress(MixProgress mixProgress) { private void setMixProgress(MixProgress mixProgress) {
if(mixProgress.getMixStep() != MixStep.FAIL) { if(mixProgress.getMixStep() != MixStep.FAIL) {
ProgressIndicator progressIndicator = getProgressIndicator(); ProgressIndicator progressIndicator = getProgressIndicator();

View file

@ -3,15 +3,14 @@ package com.sparrowwallet.sparrow.net;
import javafx.concurrent.ScheduledService; import javafx.concurrent.ScheduledService;
import javafx.concurrent.Task; import javafx.concurrent.Task;
import net.freehaven.tor.control.TorControlError; import net.freehaven.tor.control.TorControlError;
import org.berndpruenster.netlayer.tor.NativeTor; import org.berndpruenster.netlayer.tor.*;
import org.berndpruenster.netlayer.tor.Tor;
import org.berndpruenster.netlayer.tor.TorCtlException;
import org.berndpruenster.netlayer.tor.Torrc;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Field;
import java.net.Socket;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collections; import java.util.Collections;
@ -40,6 +39,7 @@ public class TorService extends ScheduledService<NativeTor> {
try { try {
LinkedHashMap<String, String> torrcOptionsMap = new LinkedHashMap<>(); LinkedHashMap<String, String> torrcOptionsMap = new LinkedHashMap<>();
torrcOptionsMap.put("SocksPort", Integer.toString(PROXY_PORT)); torrcOptionsMap.put("SocksPort", Integer.toString(PROXY_PORT));
torrcOptionsMap.put("HashedControlPassword", "16:D780432418F09B06609940000924317D3B9DF522A3191F8F4E597E9329");
torrcOptionsMap.put("DisableNetwork", "0"); torrcOptionsMap.put("DisableNetwork", "0");
Torrc override = new Torrc(torrcOptionsMap); Torrc override = new Torrc(torrcOptionsMap);
@ -62,4 +62,25 @@ public class TorService extends ScheduledService<NativeTor> {
} }
}; };
} }
public static Socket getControlSocket() {
Tor tor = Tor.getDefault();
if(tor != null) {
try {
Class<?> torClass = Class.forName("org.berndpruenster.netlayer.tor.Tor");
Field torControllerField = torClass.getDeclaredField("torController");
torControllerField.setAccessible(true);
TorController torController = (TorController)torControllerField.get(tor);
Class<?> torControllerClass = Class.forName("org.berndpruenster.netlayer.tor.TorController");
Field socketField = torControllerClass.getDeclaredField("socket");
socketField.setAccessible(true);
return (Socket)socketField.get(torController);
} catch(Exception e) {
log.error("Error retrieving Tor control socket", e);
}
}
return null;
}
} }

View file

@ -42,6 +42,7 @@ import com.sparrowwallet.sparrow.whirlpool.dataPersister.SparrowDataPersister;
import com.sparrowwallet.sparrow.whirlpool.dataSource.SparrowDataSource; import com.sparrowwallet.sparrow.whirlpool.dataSource.SparrowDataSource;
import com.sparrowwallet.sparrow.whirlpool.dataSource.SparrowMinerFeeSupplier; import com.sparrowwallet.sparrow.whirlpool.dataSource.SparrowMinerFeeSupplier;
import com.sparrowwallet.sparrow.whirlpool.dataSource.SparrowPostmixHandler; import com.sparrowwallet.sparrow.whirlpool.dataSource.SparrowPostmixHandler;
import com.sparrowwallet.sparrow.whirlpool.tor.SparrowTorClientService;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
@ -87,7 +88,7 @@ public class Whirlpool {
this.whirlpoolServer = WhirlpoolServer.valueOf(network.getName().toUpperCase()); this.whirlpoolServer = WhirlpoolServer.valueOf(network.getName().toUpperCase());
this.httpClientService = new JavaHttpClientService(torProxy); this.httpClientService = new JavaHttpClientService(torProxy);
this.stompClientService = new JavaStompClientService(httpClientService); this.stompClientService = new JavaStompClientService(httpClientService);
this.torClientService = new WhirlpoolTorClientService(); this.torClientService = new SparrowTorClientService(this);
this.whirlpoolWalletService = new WhirlpoolWalletService(); this.whirlpoolWalletService = new WhirlpoolWalletService();
this.config = computeWhirlpoolWalletConfig(torProxy); this.config = computeWhirlpoolWalletConfig(torProxy);

View file

@ -0,0 +1,49 @@
package com.sparrowwallet.sparrow.whirlpool.tor;
import com.google.common.net.HostAndPort;
import com.samourai.tor.client.TorClientService;
import com.sparrowwallet.sparrow.net.TorService;
import com.sparrowwallet.sparrow.whirlpool.Whirlpool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.Socket;
public class SparrowTorClientService extends TorClientService {
private static final Logger log = LoggerFactory.getLogger(SparrowTorClientService.class);
private final Whirlpool whirlpool;
public SparrowTorClientService(Whirlpool whirlpool) {
this.whirlpool = whirlpool;
}
@Override
public void changeIdentity() {
HostAndPort proxy = whirlpool.getTorProxy();
if(proxy != null) {
Socket controlSocket = TorService.getControlSocket();
if(controlSocket != null) {
try {
writeNewNym(controlSocket);
} catch(Exception e) {
log.warn("Error sending NEWNYM to " + controlSocket, e);
}
} else {
HostAndPort control = HostAndPort.fromParts(proxy.getHost(), proxy.getPort() + 1);
try(Socket socket = new Socket(control.getHost(), control.getPort())) {
writeNewNym(socket);
} catch(Exception e) {
log.warn("Error connecting to " + control + ", no Tor ControlPort configured?");
}
}
}
}
private void writeNewNym(Socket socket) throws IOException {
log.debug("Sending NEWNYM to " + socket);
socket.getOutputStream().write("AUTHENTICATE \"\"\r\n".getBytes());
socket.getOutputStream().write("SIGNAL NEWNYM\r\n".getBytes());
}
}

View file

@ -145,7 +145,7 @@
<PasswordField fx:id="corePass"/> <PasswordField fx:id="corePass"/>
</Field> </Field>
<Field text="Use Proxy:"> <Field text="Use Proxy:">
<UnlabeledToggleSwitch fx:id="coreUseProxy"/> <UnlabeledToggleSwitch fx:id="coreUseProxy"/><HelpLabel helpText="Bitcoin Core RPC onion URLs, and all other non-RPC external addresses will be connected via this proxy if configured." />
</Field> </Field>
<Field text="Proxy URL:"> <Field text="Proxy URL:">
<TextField fx:id="coreProxyHost" /> <TextField fx:id="coreProxyHost" />
@ -172,7 +172,7 @@
</Button> </Button>
</Field> </Field>
<Field text="Use Proxy:"> <Field text="Use Proxy:">
<UnlabeledToggleSwitch fx:id="useProxy"/> <UnlabeledToggleSwitch fx:id="useProxy"/><HelpLabel helpText="All external addresses will be connected via this proxy if configured." />
</Field> </Field>
<Field text="Proxy URL:"> <Field text="Proxy URL:">
<TextField fx:id="proxyHost" /> <TextField fx:id="proxyHost" />