1
0
mirror of https://github.com/ACINQ/eclair.git synced 2024-11-19 01:43:22 +01:00

Drop support for Tor v2 hidden services (#2296)

Tor v2 addresses have been officially deprecated by the
Tor team and removed from the lightning specification in
https://github.com/lightning/bolts/pull/940
This commit is contained in:
rorp 2022-06-06 01:03:44 -07:00 committed by GitHub
parent 9610fe30e3
commit 47c5b95eaa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 45 additions and 105 deletions

View File

@ -1,5 +1,7 @@
## How to Use Tor with Eclair
Current supported version of Tor is 0.3.3.6 or higher.
### Installing Tor on your node
#### Linux:
@ -100,21 +102,7 @@ eclair-cli getinfo
```
Eclair saves the Tor endpoint's private key in `~/.eclair/tor.dat`, so that it can recreate the endpoint address after
a restart. If you remove the private key Eclair will regenerate the endpoint address.
There are two possible values for `protocol-version`:
```
eclair.tor.protocol-version = "v3"
```
value | description
--------|---------------------------------------------------------
v2 | set up a Tor hidden service version 2 end point
v3 | set up a Tor hidden service version 3 end point (default)
Tor protocol v3 (supported by Tor version 0.3.3.6 and higher) is backwards compatible and supports
both v2 and v3 addresses.
For increased privacy do not advertise your IP address in the `server.public-ips` list, and set your binding IP to `localhost`:
```
eclair.server.binding-ip = "127.0.0.1"

View File

@ -4,7 +4,12 @@
## Major changes
<insert changes>
Dropped support for version 2 of Tor protocol. That means
- Eclair can't open control connection to Tor daemon version 0.3.3.5 and earlier anymore
- Eclair can't create hidden services for Tor protocol v2 with newer versions of Tor daemon
IMPORTANT: You'll need to upgrade your Tor daemon if for some reason you still use Tor v0.3.3.5 or earlier before upgrading to this release.
### API changes

View File

@ -315,7 +315,6 @@ eclair {
tor {
enabled = false
protocol = "v3" // v2, v3
auth = "password" // safecookie, password
password = "foobar" // used when auth=password
host = "127.0.0.1"

View File

@ -43,7 +43,6 @@ import fr.acinq.eclair.payment.receive.PaymentHandler
import fr.acinq.eclair.payment.relay.Relayer
import fr.acinq.eclair.payment.send.{Autoprobe, PaymentInitiator}
import fr.acinq.eclair.router._
import fr.acinq.eclair.tor.TorProtocolHandler.OnionServiceVersion
import fr.acinq.eclair.tor.{Controller, TorProtocolHandler}
import fr.acinq.eclair.wire.protocol.NodeAddress
import grizzled.slf4j.Logging
@ -351,7 +350,6 @@ class Setup(val datadir: File,
case "safecookie" => TorProtocolHandler.SafeCookie()
}
val protocolHandlerProps = TorProtocolHandler.props(
version = OnionServiceVersion(config.getString("tor.protocol")),
authentication = auth,
privateKeyPath = new File(datadir, config.getString("tor.private-key-file")).toPath,
virtualPort = config.getInt("server.port"),

View File

@ -16,20 +16,19 @@
package fr.acinq.eclair.tor
import java.nio.file.attribute.PosixFilePermissions
import java.nio.file.{Files, Path, Paths}
import java.util
import akka.actor.{Actor, ActorLogging, ActorRef, Props, Stash}
import akka.io.Tcp.Connected
import akka.util.ByteString
import fr.acinq.eclair.tor.TorProtocolHandler.{Authentication, OnionServiceVersion}
import fr.acinq.eclair.wire.protocol.{NodeAddress, Tor2, Tor3}
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import fr.acinq.eclair.tor.TorProtocolHandler.Authentication
import fr.acinq.eclair.wire.protocol.{NodeAddress, Tor3}
import scodec.bits.Bases.Alphabets
import scodec.bits.ByteVector
import java.nio.file.attribute.PosixFilePermissions
import java.nio.file.{Files, Path, Paths}
import java.util
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import scala.concurrent.Promise
import scala.util.Try
@ -40,15 +39,13 @@ case class TorException(private val msg: String) extends RuntimeException(s"Tor
*
* 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)
* @param targets address of our protected server (format [host:]port), 127.0.0.1:[[virtualPort]] if empty
* @param onionAdded a Promise to track creation of the endpoint
*/
class TorProtocolHandler(onionServiceVersion: OnionServiceVersion,
authentication: Authentication,
class TorProtocolHandler(authentication: Authentication,
privateKeyPath: Path,
virtualPort: Int,
targets: Seq[String],
@ -74,8 +71,8 @@ class TorProtocolHandler(onionServiceVersion: OnionServiceVersion,
val methods: String = res.getOrElse("METHODS", throw TorException("auth methods not found"))
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 (!isCompatible(torVersion)) {
throw TorException(s"unsupported Tor version: $torVersion")
}
if (!Authentication.isCompatible(authentication, methods)) {
throw TorException(s"cannot use authentication '$authentication', supported methods are '$methods'")
@ -116,10 +113,7 @@ class TorProtocolHandler(onionServiceVersion: OnionServiceVersion,
val res = readResponse(data)
if (ok(res)) {
val serviceId = processOnionResponse(parseResponse(res))
address = Some(onionServiceVersion match {
case V2 => Tor2(serviceId, virtualPort)
case V3 => Tor3(serviceId, virtualPort)
})
address = Some(Tor3(serviceId, virtualPort))
onionAdded.foreach(_.success(address.get))
log.debug("Onion address: {}", address.get)
}
@ -151,10 +145,7 @@ class TorProtocolHandler(onionServiceVersion: OnionServiceVersion,
if (privateKeyPath.toFile.exists()) {
readString(privateKeyPath)
} else {
onionServiceVersion match {
case V2 => "NEW:RSA1024"
case V3 => "NEW:ED25519-V3"
}
"NEW:ED25519-V3"
}
}
@ -190,48 +181,30 @@ class TorProtocolHandler(onionServiceVersion: OnionServiceVersion,
}
object TorProtocolHandler {
def props(version: OnionServiceVersion,
authentication: Authentication,
def props(authentication: Authentication,
privateKeyPath: Path,
virtualPort: Int,
targets: Seq[String] = Seq(),
onionAdded: Option[Promise[NodeAddress]] = None
): Props =
Props(new TorProtocolHandler(version, authentication, privateKeyPath, virtualPort, targets, onionAdded))
Props(new TorProtocolHandler(authentication, privateKeyPath, virtualPort, targets, onionAdded))
// those are defined in the spec
private val ServerKey = ByteVector.view("Tor safe cookie authentication server-to-controller hash".getBytes())
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(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
private[tor] 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
// @formatter:off
sealed trait Authentication

View File

@ -54,7 +54,6 @@ class TorProtocolHandlerSpec extends TestKitBaseClass
val promiseOnionAddress = Promise[NodeAddress]()
val protocolHandlerProps = TorProtocolHandler.props(
version = OnionServiceVersion("v2"),
authentication = Password(PASSWORD),
privateKeyPath = PkFilePath,
virtualPort = 9999,
@ -70,7 +69,6 @@ class TorProtocolHandlerSpec extends TestKitBaseClass
val promiseOnionAddress = Promise[NodeAddress]()
val protocolHandler = TestActorRef(props(
version = OnionServiceVersion("v2"),
authentication = Password(PASSWORD),
privateKeyPath = PkFilePath,
virtualPort = 9999,
@ -86,31 +84,15 @@ class TorProtocolHandlerSpec extends TestKitBaseClass
"250 OK\r\n"
)
expectMsg(ByteString(s"""AUTHENTICATE "$PASSWORD"\r\n"""))
protocolHandler ! ByteString(
"250 OK\r\n"
)
awaitCond(promiseOnionAddress.isCompleted)
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
expectMsg(Some(Tor2("z4zif3fy7fe7bpg3", 9999)))
val address = Await.result(promiseOnionAddress.future, 3 seconds)
assert(address == Tor2("z4zif3fy7fe7bpg3", 9999))
assert(readString(PkFilePath) == "RSA1024:private-key")
assertThrows[TorException](Await.result(promiseOnionAddress.future, Duration.Inf))
}
test("happy path v3") {
val promiseOnionAddress = Promise[NodeAddress]()
val protocolHandler = TestActorRef(props(
version = OnionServiceVersion("v3"),
authentication = Password(PASSWORD),
privateKeyPath = PkFilePath,
virtualPort = 9999,
@ -148,20 +130,18 @@ class TorProtocolHandlerSpec extends TestKitBaseClass
}
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"))
assert(isCompatible("0.3.3.6"))
assert(!isCompatible("0.3.3.5"))
assert(isCompatible("0.3.3.6-devel"))
assert(isCompatible("0.4"))
assert(!isCompatible("0.2"))
assert(isCompatible("0.5.1.2.3.4"))
}
test("authentication method errors") {
val promiseOnionAddress = Promise[NodeAddress]()
val protocolHandler = TestActorRef(props(
version = OnionServiceVersion("v2"),
authentication = Password(PASSWORD),
privateKeyPath = PkFilePath,
virtualPort = 9999,
@ -173,7 +153,7 @@ class TorProtocolHandlerSpec extends TestKitBaseClass
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.3.3.6\"\r\n" +
"250 OK\r\n"
)
@ -188,7 +168,6 @@ class TorProtocolHandlerSpec extends TestKitBaseClass
Files.write(CookieFilePath, fr.acinq.eclair.randomBytes32().toArray)
val protocolHandler = TestActorRef(props(
version = OnionServiceVersion("v2"),
authentication = SafeCookie(ClientNonce),
privateKeyPath = PkFilePath,
virtualPort = 9999,
@ -200,7 +179,7 @@ class TorProtocolHandlerSpec extends TestKitBaseClass
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.3.3.6\"\r\n" +
"250 OK\r\n"
)
@ -221,7 +200,6 @@ class TorProtocolHandlerSpec extends TestKitBaseClass
Files.write(CookieFilePath, AuthCookie.toArray)
val protocolHandler = TestActorRef(props(
version = OnionServiceVersion("v2"),
authentication = SafeCookie(ClientNonce),
privateKeyPath = PkFilePath,
virtualPort = 9999,
@ -233,7 +211,7 @@ class TorProtocolHandlerSpec extends TestKitBaseClass
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.3.3.6\"\r\n" +
"250 OK\r\n"
)
@ -258,7 +236,6 @@ class TorProtocolHandlerSpec extends TestKitBaseClass
Files.write(CookieFilePath, AuthCookie.toArray)
val protocolHandler = TestActorRef(props(
version = OnionServiceVersion("v2"),
authentication = SafeCookie(ClientNonce),
privateKeyPath = PkFilePath,
virtualPort = 9999,
@ -270,7 +247,7 @@ class TorProtocolHandlerSpec extends TestKitBaseClass
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.3.3.6\"\r\n" +
"250 OK\r\n"
)
@ -284,7 +261,7 @@ class TorProtocolHandlerSpec extends TestKitBaseClass
"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"
)