support cookie authentication for tor control port

This commit is contained in:
Craig Raw 2024-03-26 13:26:32 +02:00
parent 2e8112cba0
commit 08ec158d19

View file

@ -1,16 +1,22 @@
package com.sparrowwallet.sparrow.net; package com.sparrowwallet.sparrow.net;
import com.google.common.net.HostAndPort; import com.google.common.net.HostAndPort;
import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.AppServices;
import io.matthewnelson.kmp.tor.controller.common.control.usecase.TorControlSignal; import io.matthewnelson.kmp.tor.controller.common.control.usecase.TorControlSignal;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.*;
import java.net.Socket; import java.net.Socket;
import java.nio.file.Files;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TorUtils { public class TorUtils {
private static final Logger log = LoggerFactory.getLogger(TorUtils.class); private static final Logger log = LoggerFactory.getLogger(TorUtils.class);
private static final Pattern TOR_OK = Pattern.compile("^2\\d{2}[ -]OK$");
private static final Pattern TOR_AUTH_METHODS = Pattern.compile("^2\\d{2}[ -]AUTH METHODS=(\\S+)\\s?(COOKIEFILE=\"?(.+?)\"?)?$");
public static void changeIdentity(HostAndPort proxy) { public static void changeIdentity(HostAndPort proxy) {
if(AppServices.isTorRunning()) { if(AppServices.isTorRunning()) {
@ -22,16 +28,59 @@ public class TorUtils {
} else { } else {
HostAndPort control = HostAndPort.fromParts(proxy.getHost(), proxy.getPort() + 1); HostAndPort control = HostAndPort.fromParts(proxy.getHost(), proxy.getPort() + 1);
try(Socket socket = new Socket(control.getHost(), control.getPort())) { try(Socket socket = new Socket(control.getHost(), control.getPort())) {
writeNewNym(socket); if(authenticate(socket)) {
writeNewNym(socket);
}
} catch(TorAuthenticationException e) {
log.warn("Error authenticating to Tor at " + control + ", server returned " + e.getMessage());
} catch(Exception e) { } catch(Exception e) {
log.warn("Error connecting to " + control + ", no Tor ControlPort configured?"); log.warn("Error connecting to " + control + ", no Tor ControlPort configured?");
} }
} }
} }
private static boolean authenticate(Socket socket) throws IOException, TorAuthenticationException {
socket.getOutputStream().write("PROTOCOLINFO\r\n".getBytes());
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
File cookieFile = null;
while((line = reader.readLine()) != null) {
Matcher authMatcher = TOR_AUTH_METHODS.matcher(line);
if(authMatcher.matches()) {
String methods = authMatcher.group(1);
if(methods.contains("COOKIE") && !authMatcher.group(3).isEmpty()) {
cookieFile = new File(authMatcher.group(3));
}
}
if(TOR_OK.matcher(line).matches()) {
break;
}
}
if(cookieFile != null && cookieFile.exists()) {
byte[] cookieBytes = Files.readAllBytes(cookieFile.toPath());
String authentication = "AUTHENTICATE " + Utils.bytesToHex(cookieBytes) + "\r\n";
socket.getOutputStream().write(authentication.getBytes());
} else {
socket.getOutputStream().write("AUTHENTICATE \"\"\r\n".getBytes());
}
line = reader.readLine();
if(TOR_OK.matcher(line).matches()) {
return true;
} else {
throw new TorAuthenticationException(line);
}
}
private static void writeNewNym(Socket socket) throws IOException { private static void writeNewNym(Socket socket) throws IOException {
log.debug("Sending NEWNYM to " + socket); log.debug("Sending NEWNYM to " + socket);
socket.getOutputStream().write("AUTHENTICATE \"\"\r\n".getBytes());
socket.getOutputStream().write("SIGNAL NEWNYM\r\n".getBytes()); socket.getOutputStream().write("SIGNAL NEWNYM\r\n".getBytes());
} }
private static class TorAuthenticationException extends Exception {
public TorAuthenticationException(String message) {
super(message);
}
}
} }