From 08ec158d19beb4b973baa6284075be380f39990b Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Tue, 26 Mar 2024 13:26:32 +0200 Subject: [PATCH] support cookie authentication for tor control port --- .../sparrowwallet/sparrow/net/TorUtils.java | 55 ++++++++++++++++++- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/sparrowwallet/sparrow/net/TorUtils.java b/src/main/java/com/sparrowwallet/sparrow/net/TorUtils.java index ab1548c4..1df3ec09 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/TorUtils.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/TorUtils.java @@ -1,16 +1,22 @@ package com.sparrowwallet.sparrow.net; import com.google.common.net.HostAndPort; +import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.sparrow.AppServices; import io.matthewnelson.kmp.tor.controller.common.control.usecase.TorControlSignal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; +import java.io.*; import java.net.Socket; +import java.nio.file.Files; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class TorUtils { 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) { if(AppServices.isTorRunning()) { @@ -22,16 +28,59 @@ public class TorUtils { } else { HostAndPort control = HostAndPort.fromParts(proxy.getHost(), proxy.getPort() + 1); 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) { 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 { log.debug("Sending NEWNYM to " + socket); - socket.getOutputStream().write("AUTHENTICATE \"\"\r\n".getBytes()); socket.getOutputStream().write("SIGNAL NEWNYM\r\n".getBytes()); } + + private static class TorAuthenticationException extends Exception { + public TorAuthenticationException(String message) { + super(message); + } + } }