Drop support for Tor v2 (#4864)

This commit is contained in:
rorp 2022-10-30 14:50:16 -07:00 committed by GitHub
parent 47d2d5a711
commit 4c0e6d5201
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 65 additions and 124 deletions

View File

@ -63,60 +63,11 @@ class TorProtocolHandlerSpec
println(address)
}*/
test("happy path v2") {
val promiseOnionAddress = Promise[InetSocketAddress]()
val protocolHandler = TestActorRef(
props(version = OnionServiceVersion("v2"),
authentication = Password(PASSWORD),
privateKeyPath = PkFilePath,
virtualPort = 9999,
onionAdded = Some(promiseOnionAddress)))
protocolHandler ! Connected(LocalHost, LocalHost)
expectMsg(ByteString("PROTOCOLINFO 1\r\n"))
protocolHandler ! ByteString(
"250-PROTOCOLINFO 1\r\n" +
"250-AUTH METHODS=HASHEDPASSWORD\r\n" +
"250-VERSION Tor=\"0.3.3.5\"\r\n" +
"250 OK\r\n"
)
expectMsg(ByteString(s"""AUTHENTICATE "$PASSWORD"\r\n"""))
protocolHandler ! ByteString(
"250 OK\r\n"
)
expectMsg(ByteString("ADD_ONION NEW:RSA1024 Port=9999,9999\r\n"))
protocolHandler ! ByteString(
"250-ServiceID=z4zif3fy7fe7bpg3\r\n" +
"250-PrivateKey=RSA1024:private-key\r\n" +
"250 OK\r\n"
)
protocolHandler ! GetOnionAddress
val addr = expectMsgType[Option[InetSocketAddress]]
assert(addr.nonEmpty)
assertAddressesEqual(
addr.get,
InetSocketAddress.createUnresolved("z4zif3fy7fe7bpg3.onion", 9999))
val address = Await.result(promiseOnionAddress.future, 3.seconds)
assertAddressesEqual(
address,
InetSocketAddress.createUnresolved("z4zif3fy7fe7bpg3.onion", 9999))
assert(readString(PkFilePath) === "RSA1024:private-key")
}
test("happy path v3") {
val promiseOnionAddress = Promise[InetSocketAddress]()
val protocolHandler = TestActorRef(
props(version = OnionServiceVersion("v3"),
authentication = Password(PASSWORD),
props(authentication = Password(PASSWORD),
privateKeyPath = PkFilePath,
virtualPort = 9999,
onionAdded = Some(promiseOnionAddress)))
@ -164,22 +115,44 @@ class TorProtocolHandlerSpec
assert(readString(PkFilePath) === "ED25519-V3:private-key")
}
test("v2/v3 compatibility check against tor version") {
assert(OnionServiceVersion.isCompatible(V3, "0.3.3.6"))
assert(!OnionServiceVersion.isCompatible(V3, "0.3.3.5"))
assert(OnionServiceVersion.isCompatible(V3, "0.3.3.6-devel"))
assert(OnionServiceVersion.isCompatible(V3, "0.4"))
assert(!OnionServiceVersion.isCompatible(V3, "0.2"))
assert(OnionServiceVersion.isCompatible(V3, "0.5.1.2.3.4"))
test("compatibility check against tor version") {
assert(OnionServiceVersion.isCompatible("0.3.3.6"))
assert(!OnionServiceVersion.isCompatible("0.3.3.5"))
assert(OnionServiceVersion.isCompatible("0.3.3.6-devel"))
assert(OnionServiceVersion.isCompatible("0.4"))
assert(!OnionServiceVersion.isCompatible("0.2"))
assert(OnionServiceVersion.isCompatible("0.5.1.2.3.4"))
}
test("handle unsupported Tor version") {
val promiseOnionAddress = Promise[InetSocketAddress]()
val protocolHandler = TestActorRef(
props(authentication = Password(PASSWORD),
privateKeyPath = PkFilePath,
virtualPort = 9999,
onionAdded = Some(promiseOnionAddress)))
protocolHandler ! Connected(LocalHost, LocalHost)
expectMsg(ByteString("PROTOCOLINFO 1\r\n"))
protocolHandler ! ByteString(
"250-PROTOCOLINFO 1\r\n" +
"250-AUTH METHODS=HASHEDPASSWORD\r\n" +
"250-VERSION Tor=\"0.3.3.5\"\r\n" +
"250 OK\r\n"
)
assert(intercept[TorException] {
Await.result(promiseOnionAddress.future, 3.seconds)
} === TorException("Tor version 0.3.3.5 is not supported"))
}
test("authentication method errors") {
val promiseOnionAddress = Promise[InetSocketAddress]()
val protocolHandler = TestActorRef(
props(version = OnionServiceVersion("v2"),
authentication = Password(PASSWORD),
props(authentication = Password(PASSWORD),
privateKeyPath = PkFilePath,
virtualPort = 9999,
onionAdded = Some(promiseOnionAddress)))
@ -190,7 +163,7 @@ class TorProtocolHandlerSpec
protocolHandler ! ByteString(
"250-PROTOCOLINFO 1\r\n" +
"250-AUTH METHODS=COOKIE,SAFECOOKIE COOKIEFILE=\"" + CookieFilePath + "\"\r\n" +
"250-VERSION Tor=\"0.3.3.5\"\r\n" +
"250-VERSION Tor=\"0.4.7.10\"\r\n" +
"250 OK\r\n"
)
@ -206,13 +179,10 @@ class TorProtocolHandlerSpec
Files.write(CookieFilePath, CryptoUtil.randomBytes(32).toArray)
val protocolHandler = TestActorRef(
props(
version = OnionServiceVersion("v2"),
authentication = SafeCookie(ClientNonce),
privateKeyPath = PkFilePath,
virtualPort = 9999,
onionAdded = Some(promiseOnionAddress)
))
props(authentication = SafeCookie(ClientNonce),
privateKeyPath = PkFilePath,
virtualPort = 9999,
onionAdded = Some(promiseOnionAddress)))
protocolHandler ! Connected(LocalHost, LocalHost)
@ -220,7 +190,7 @@ class TorProtocolHandlerSpec
protocolHandler ! ByteString(
"250-PROTOCOLINFO 1\r\n" +
"250-AUTH METHODS=COOKIE,SAFECOOKIE COOKIEFILE=\"" + CookieFilePath + "\"\r\n" +
"250-VERSION Tor=\"0.3.3.5\"\r\n" +
"250-VERSION Tor=\"0.4.7.10\"\r\n" +
"250 OK\r\n"
)
@ -242,7 +212,6 @@ class TorProtocolHandlerSpec
val protocolHandler = TestActorRef(
props(
version = OnionServiceVersion("v2"),
authentication = SafeCookie(ClientNonce),
privateKeyPath = PkFilePath,
virtualPort = 9999,
@ -255,7 +224,7 @@ class TorProtocolHandlerSpec
protocolHandler ! ByteString(
"250-PROTOCOLINFO 1\r\n" +
"250-AUTH METHODS=COOKIE,SAFECOOKIE COOKIEFILE=\"" + CookieFilePath + "\"\r\n" +
"250-VERSION Tor=\"0.3.3.5\"\r\n" +
"250-VERSION Tor=\"0.4.7.10\"\r\n" +
"250 OK\r\n"
)
@ -284,7 +253,6 @@ class TorProtocolHandlerSpec
val protocolHandler = TestActorRef(
props(
version = OnionServiceVersion("v2"),
authentication = SafeCookie(ClientNonce),
privateKeyPath = PkFilePath,
virtualPort = 9999,
@ -297,7 +265,7 @@ class TorProtocolHandlerSpec
protocolHandler ! ByteString(
"250-PROTOCOLINFO 1\r\n" +
"250-AUTH METHODS=COOKIE,SAFECOOKIE COOKIEFILE=\"" + CookieFilePath + "\"\r\n" +
"250-VERSION Tor=\"0.3.3.5\"\r\n" +
"250-VERSION Tor=\"0.4.7.10\"\r\n" +
"250 OK\r\n"
)
@ -313,7 +281,7 @@ class TorProtocolHandlerSpec
"250 OK\r\n"
)
expectMsg(ByteString("ADD_ONION NEW:RSA1024 Port=9999,9999\r\n"))
expectMsg(ByteString("ADD_ONION NEW:ED25519-V3 Port=9999,9999\r\n"))
protocolHandler ! ByteString(
"513 Invalid argument\r\n"
)

View File

@ -118,7 +118,6 @@ object TorController extends Logging {
val promiseConnected = Promise[Done]()
val protocolHandlerProps = TorProtocolHandler.props(
version = TorProtocolHandler.V3,
authentication = authentication,
privateKeyPath = privateKeyPath,
virtualPort = virtualPort,

View File

@ -4,7 +4,7 @@ import akka.actor.{Actor, ActorLogging, ActorRef, Props, Stash}
import akka.io.Tcp.Connected
import akka.util.ByteString
import org.bitcoins.crypto.CryptoUtil
import org.bitcoins.tor.TorProtocolHandler.{Authentication, OnionServiceVersion}
import org.bitcoins.tor.TorProtocolHandler.Authentication
import scodec.bits.ByteVector
import java.net.InetSocketAddress
@ -23,7 +23,6 @@ case class TorException(private val msg: String)
*
* Specification: https://gitweb.torproject.org/torspec.git/tree/control-spec.txt
*
* @param onionServiceVersion v2 or v3
* @param authentication Tor controller auth mechanism (password or safecookie)
* @param privateKeyPath path to a file that contains a Tor private key
* @param virtualPort port for the public hidden service (typically 9735)
@ -31,7 +30,6 @@ case class TorException(private val msg: String)
* @param onionAdded a Promise to track creation of the endpoint
*/
class TorProtocolHandler(
onionServiceVersion: OnionServiceVersion,
authentication: Authentication,
privateKeyPath: Path,
virtualPort: Int,
@ -60,9 +58,8 @@ class TorProtocolHandler(
val torVersion = unquote(
res.getOrElse("Tor", throw TorException("version not found")))
log.info(s"Tor version $torVersion")
if (!OnionServiceVersion.isCompatible(onionServiceVersion, torVersion)) {
throw TorException(
s"version $torVersion does not support onion service $onionServiceVersion")
if (!OnionServiceVersion.isCompatible(torVersion)) {
throw TorException(s"Tor version $torVersion is not supported")
}
if (!Authentication.isCompatible(authentication, methods)) {
throw TorException(
@ -147,10 +144,7 @@ class TorProtocolHandler(
if (privateKeyPath.toFile.exists()) {
readString(privateKeyPath)
} else {
onionServiceVersion match {
case V2 => "NEW:RSA1024"
case V3 => "NEW:ED25519-V3"
}
"NEW:ED25519-V3"
}
}
@ -192,15 +186,13 @@ class TorProtocolHandler(
object TorProtocolHandler {
def props(
version: OnionServiceVersion,
authentication: Authentication,
privateKeyPath: Path,
virtualPort: Int,
targets: Seq[String] = Seq(),
onionAdded: Option[Promise[InetSocketAddress]] = None): Props =
Props(
new TorProtocolHandler(version,
authentication,
new TorProtocolHandler(authentication,
privateKeyPath,
virtualPort,
targets,
@ -213,46 +205,28 @@ object TorProtocolHandler {
private val ClientKey = ByteVector.view(
"Tor safe cookie authentication controller-to-server hash".getBytes())
// @formatter:off
sealed trait OnionServiceVersion
case object V2 extends OnionServiceVersion
case object V3 extends OnionServiceVersion
// @formatter:on
object OnionServiceVersion {
def apply(s: String): OnionServiceVersion = s match {
case "v2" | "V2" => V2
case "v3" | "V3" => V3
case _ => throw TorException(s"unknown protocol version `$s`")
def isCompatible(torVersion: String): Boolean = {
torVersion
.split("\\.")
.map(
_.split('-').head
) // remove non-numeric symbols at the end of the last number (rc, beta, alpha, etc.)
.map(d => Try(d.toInt).getOrElse(0))
.zipAll(List(0, 3, 3, 6), 0, 0) // min version for v3 is 0.3.3.6
.foldLeft(Option.empty[Boolean]) { // compare subversion by subversion starting from the left
case (Some(res), _) =>
Some(
res
) // we stop the comparison as soon as there is a difference
case (None, (v, vref)) =>
if (v > vref) Some(true)
else if (v < vref) Some(false)
else None
}
.getOrElse(true) // if version == 0.3.3.6 then result will be None
}
def isCompatible(
onionServiceVersion: OnionServiceVersion,
torVersion: String): Boolean =
onionServiceVersion match {
case V2 => true
case V3 =>
torVersion
.split("\\.")
.map(
_.split('-').head
) // remove non-numeric symbols at the end of the last number (rc, beta, alpha, etc.)
.map(d => Try(d.toInt).getOrElse(0))
.zipAll(List(0, 3, 3, 6), 0, 0) // min version for v3 is 0.3.3.6
.foldLeft(Option.empty[Boolean]) { // compare subversion by subversion starting from the left
case (Some(res), _) =>
Some(
res
) // we stop the comparison as soon as there is a difference
case (None, (v, vref)) =>
if (v > vref) Some(true)
else if (v < vref) Some(false)
else None
}
.getOrElse(true) // if version == 0.3.3.6 then result will be None
}
}
sealed trait Authentication