diff --git a/README.md b/README.md index 4376bc5..c99d9b0 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ beyond `scala-logging` Can be used as a library, or as a standalone program. + ## Standalone ``` @@ -40,12 +41,13 @@ etag: "5e9efe7d-264" accept-ranges: bytes ``` + ## Library To use it as a library in-process: ```scala -import tlsproxy.TlsProxy +import io.github.erikvanzijst.scalatlsproxy.TlsProxy new TlsProxy(3128).run() ``` @@ -56,13 +58,14 @@ calling thread. It does not return. To move it to the background, pass it to a `Thread` or `Executor`: ```scala -import tlsproxy.TlsProxy +import io.github.erikvanzijst.scalatlsproxy.TlsProxy import java.util.concurrent.Executors val executor = Executors.newSingleThreadExecutor() executor.submit(new TlsProxy(3128)) ``` + ## Caveat emptor This is only implements the `CONNECT` method and can therefor only proxy HTTPS @@ -75,6 +78,7 @@ connection getting closed: 18:08:53.604 [main] ERROR tlsproxy.TlsProxyHandler - /0:0:0:0:0:0:0:1:51043 -> unconnected: error: connection closed: java.io.IOException: Malformed request ``` + ## Robustness (or lack thereof) * This implementation is totally susceptible to all kinds of [slowloris attacks](https://en.wikipedia.org/wiki/Slowloris_(computer_security). @@ -82,3 +86,84 @@ connection getting closed: * Uses only 1 thread and cannot currently scale to multiple cores * Does not restrict non-standard upstream ports * Undoubtedly riddled with bugs + + +## Publishing + +Publishing is done to the Sonatype Central Repository and requires gpg-signed +artifacts. For this, install gpg and (on Mac) `pin-entry-mac`: + +``` +$ brew install gnupg pinentry-mac +``` + +Add the pinentry program to `~/.gnupg/gpg-agent.conf`: + +``` +pinentry-program /usr/local/bin/pinentry-mac +``` + +Restart `gpg-agent`: + +``` +$ gpgconf --kill gpg-agent +``` + +Run `publishLocalSigned` to ensure signing from `sbt` works (this should pop +up a dialog to enter the private key's passphrase): + +``` +$ sbt publishLocalSigned +[info] Loading global plugins from /Users/erik/.sbt/1.0/plugins +[info] Loading settings for project tlsproxy-build from plugins.sbt ... +[info] Loading project definition from /Users/erik/work/tlsproxy/project +[info] Loading settings for project tlsproxy from build.sbt ... +[info] Set current project to tlsproxy (in build file:/Users/erik/work/tlsproxy/) +[info] Wrote /Users/erik/work/tlsproxy/target/scala-2.12/tlsproxy_2.12-0.1.pom +[info] :: delivering :: erikvanzijst#tlsproxy_2.12;0.1 :: 0.1 :: release :: Tue Aug 17 22:44:46 CEST 2021 +[info] delivering ivy file to /Users/erik/work/tlsproxy/target/scala-2.12/ivy-0.1.xml +[info] gpg: using "E96DDAAB16804D86EFA2A08A4539ACC7B26D1005" as default secret key for signing +[info] gpg: using "E96DDAAB16804D86EFA2A08A4539ACC7B26D1005" as default secret key for signing +[info] gpg: using "E96DDAAB16804D86EFA2A08A4539ACC7B26D1005" as default secret key for signing +[info] gpg: using "E96DDAAB16804D86EFA2A08A4539ACC7B26D1005" as default secret key for signing +[info] published tlsproxy_2.12 to /Users/erik/.ivy2/local/erikvanzijst/tlsproxy_2.12/0.1/jars/tlsproxy_2.12.jar +[info] published tlsproxy_2.12 to /Users/erik/.ivy2/local/erikvanzijst/tlsproxy_2.12/0.1/docs/tlsproxy_2.12-javadoc.jar +[info] published tlsproxy_2.12 to /Users/erik/.ivy2/local/erikvanzijst/tlsproxy_2.12/0.1/srcs/tlsproxy_2.12-sources.jar +[info] published tlsproxy_2.12 to /Users/erik/.ivy2/local/erikvanzijst/tlsproxy_2.12/0.1/poms/tlsproxy_2.12.pom.asc +[info] published tlsproxy_2.12 to /Users/erik/.ivy2/local/erikvanzijst/tlsproxy_2.12/0.1/poms/tlsproxy_2.12.pom +[info] published tlsproxy_2.12 to /Users/erik/.ivy2/local/erikvanzijst/tlsproxy_2.12/0.1/jars/tlsproxy_2.12.jar.asc +[info] published tlsproxy_2.12 to /Users/erik/.ivy2/local/erikvanzijst/tlsproxy_2.12/0.1/srcs/tlsproxy_2.12-sources.jar.asc +[info] published tlsproxy_2.12 to /Users/erik/.ivy2/local/erikvanzijst/tlsproxy_2.12/0.1/docs/tlsproxy_2.12-javadoc.jar.asc +[success] Total time: 1 s, completed Aug 17, 2021 10:44:47 PM +``` + +Now publish to Sonatype: + +``` +$ sbt publishSigned +[info] Loading global plugins from /Users/erik/.sbt/1.0/plugins +[info] Loading settings for project tlsproxy-build from plugins.sbt ... +[info] Loading project definition from /Users/erik/work/tlsproxy/project +[info] Loading settings for project tlsproxy from build.sbt ... +[info] Set current project to scala-tlsproxy (in build file:/Users/erik/work/tlsproxy/) +[info] Wrote /Users/erik/work/tlsproxy/target/scala-2.12/scala-tlsproxy_2.12-0.1-SNAPSHOT.pom +[info] gpg: using "E96DDAAB16804D86EFA2A08A4539ACC7B26D1005" as default secret key for signing +[info] gpg: using "E96DDAAB16804D86EFA2A08A4539ACC7B26D1005" as default secret key for signing +[info] gpg: using "E96DDAAB16804D86EFA2A08A4539ACC7B26D1005" as default secret key for signing +[info] gpg: using "E96DDAAB16804D86EFA2A08A4539ACC7B26D1005" as default secret key for signing +[info] published scala-tlsproxy_2.12 to https://s01.oss.sonatype.org/content/repositories/snapshots/io/github/erikvanzijst/scala-tlsproxy_2.12/0.1-SNAPSHOT/scala-tlsproxy_2.12-0.1-SNAPSHOT.jar +[info] published scala-tlsproxy_2.12 to https://s01.oss.sonatype.org/content/repositories/snapshots/io/github/erikvanzijst/scala-tlsproxy_2.12/0.1-SNAPSHOT/scala-tlsproxy_2.12-0.1-SNAPSHOT-sources.jar +[info] published scala-tlsproxy_2.12 to https://s01.oss.sonatype.org/content/repositories/snapshots/io/github/erikvanzijst/scala-tlsproxy_2.12/0.1-SNAPSHOT/scala-tlsproxy_2.12-0.1-SNAPSHOT-javadoc.jar +[info] published scala-tlsproxy_2.12 to https://s01.oss.sonatype.org/content/repositories/snapshots/io/github/erikvanzijst/scala-tlsproxy_2.12/0.1-SNAPSHOT/scala-tlsproxy_2.12-0.1-SNAPSHOT.jar.asc +[info] published scala-tlsproxy_2.12 to https://s01.oss.sonatype.org/content/repositories/snapshots/io/github/erikvanzijst/scala-tlsproxy_2.12/0.1-SNAPSHOT/scala-tlsproxy_2.12-0.1-SNAPSHOT.pom.asc +[info] published scala-tlsproxy_2.12 to https://s01.oss.sonatype.org/content/repositories/snapshots/io/github/erikvanzijst/scala-tlsproxy_2.12/0.1-SNAPSHOT/scala-tlsproxy_2.12-0.1-SNAPSHOT-sources.jar.asc +[info] published scala-tlsproxy_2.12 to https://s01.oss.sonatype.org/content/repositories/snapshots/io/github/erikvanzijst/scala-tlsproxy_2.12/0.1-SNAPSHOT/scala-tlsproxy_2.12-0.1-SNAPSHOT-javadoc.jar.asc +[info] published scala-tlsproxy_2.12 to https://s01.oss.sonatype.org/content/repositories/snapshots/io/github/erikvanzijst/scala-tlsproxy_2.12/0.1-SNAPSHOT/scala-tlsproxy_2.12-0.1-SNAPSHOT.pom +[success] Total time: 9 s, completed Aug 17, 2021 11:29:22 PM +``` + +Troubleshooting: + +* https://github.com/sbt/sbt-pgp#sbt-pgp +* https://gist.github.com/danieleggert/b029d44d4a54b328c0bac65d46ba4c65 +* https://www.scala-sbt.org/release/docs/Using-Sonatype.html diff --git a/build.sbt b/build.sbt index fd600d1..f31e7d4 100644 --- a/build.sbt +++ b/build.sbt @@ -1,9 +1,42 @@ -name := "tlsproxy" +name := "scala-tlsproxy" +organization := "io.github.erikvanzijst" -version := "0.1" +version := "0.1-SNAPSHOT" scalaVersion := "2.12.14" -libraryDependencies += "com.github.scopt" %% "scopt" % "4.0.1" libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3" libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.9.4" + +credentials += Credentials(Path.userHome / ".sbt" / "sonatype_credentials") + +ThisBuild / organization := "io.github.erikvanzijst" +ThisBuild / organizationName := "erikvanzijst" +ThisBuild / organizationHomepage := Some(url("https://github.com/erikvanzijst")) + +ThisBuild / scmInfo := Some( + ScmInfo( + url("https://github.com/erikvanzijst/scala_tlsproxy"), + "scm:git@github.com:erikvanzijst/scala_tlsproxy.git" + ) +) +ThisBuild / developers := List( + Developer( + id = "erikvanzijst", + name = "Erik van Zijst", + email = "erik.van.zijst@gmail.com", + url = url("https://github.com/erikvanzijst") + ) +) + +ThisBuild / description := "Very simple HTTPS proxy server lib written in Scala 2.12 with no external dependencies." +ThisBuild / licenses := List("Apache 2" -> new URL("http://www.apache.org/licenses/LICENSE-2.0.txt")) +ThisBuild / homepage := Some(url("https://github.com/erikvanzijst/scala_tlsproxy")) + +ThisBuild / pomIncludeRepository := { _ => false } +ThisBuild / publishTo := { + val nexus = "https://s01.oss.sonatype.org/" + if (isSnapshot.value) Some("snapshots" at nexus + "content/repositories/snapshots") + else Some("releases" at nexus + "service/local/staging/deploy/maven2") +} +ThisBuild / publishMavenStyle := true \ No newline at end of file diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 0000000..2a64bd2 --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2") diff --git a/src/main/scala/tlsproxy/EchoHandler.scala b/src/main/scala/io/github/erikvanzijst/scalatlsproxy/EchoHandler.scala similarity index 97% rename from src/main/scala/tlsproxy/EchoHandler.scala rename to src/main/scala/io/github/erikvanzijst/scalatlsproxy/EchoHandler.scala index 8453938..df50d6a 100644 --- a/src/main/scala/tlsproxy/EchoHandler.scala +++ b/src/main/scala/io/github/erikvanzijst/scalatlsproxy/EchoHandler.scala @@ -1,4 +1,4 @@ -package tlsproxy +package io.github.erikvanzijst.scalatlsproxy import java.io.IOException import java.nio.ByteBuffer diff --git a/src/main/scala/io/github/erikvanzijst/scalatlsproxy/Main.scala b/src/main/scala/io/github/erikvanzijst/scalatlsproxy/Main.scala new file mode 100644 index 0000000..768c17e --- /dev/null +++ b/src/main/scala/io/github/erikvanzijst/scalatlsproxy/Main.scala @@ -0,0 +1,18 @@ +package io.github.erikvanzijst.scalatlsproxy + +import ch.qos.logback.classic.Level +import com.typesafe.scalalogging.StrictLogging +import org.slf4j.LoggerFactory + +object Main extends StrictLogging { + + // Suppress debug when running from the shell + Seq("io.github.erikvanzijst.scalatlsproxy.TlsProxyHandler", + "io.github.erikvanzijst.scalatlsproxy.ServerHandler", + "io.github.erikvanzijst.scalatlsproxy.Pipe") + .map(LoggerFactory.getLogger) + .map(_.asInstanceOf[ch.qos.logback.classic.Logger]) + .foreach(_.setLevel(Level.INFO)) + + def main(args: Array[String]): Unit = new TlsProxy(3128).run() +} diff --git a/src/main/scala/tlsproxy/Pipe.scala b/src/main/scala/io/github/erikvanzijst/scalatlsproxy/Pipe.scala similarity index 96% rename from src/main/scala/tlsproxy/Pipe.scala rename to src/main/scala/io/github/erikvanzijst/scalatlsproxy/Pipe.scala index de1ddeb..4e4fe36 100644 --- a/src/main/scala/tlsproxy/Pipe.scala +++ b/src/main/scala/io/github/erikvanzijst/scalatlsproxy/Pipe.scala @@ -1,4 +1,4 @@ -package tlsproxy +package io.github.erikvanzijst.scalatlsproxy import java.nio.ByteBuffer import java.nio.channels.{SelectionKey, SocketChannel} diff --git a/src/main/scala/tlsproxy/ServerHandler.scala b/src/main/scala/io/github/erikvanzijst/scalatlsproxy/ServerHandler.scala similarity index 95% rename from src/main/scala/tlsproxy/ServerHandler.scala rename to src/main/scala/io/github/erikvanzijst/scalatlsproxy/ServerHandler.scala index 688a53d..a31addd 100644 --- a/src/main/scala/tlsproxy/ServerHandler.scala +++ b/src/main/scala/io/github/erikvanzijst/scalatlsproxy/ServerHandler.scala @@ -1,4 +1,4 @@ -package tlsproxy +package io.github.erikvanzijst.scalatlsproxy import java.net.InetSocketAddress import java.nio.channels.{SelectionKey, Selector, ServerSocketChannel} diff --git a/src/main/scala/tlsproxy/TlsProxy.scala b/src/main/scala/io/github/erikvanzijst/scalatlsproxy/TlsProxy.scala similarity index 89% rename from src/main/scala/tlsproxy/TlsProxy.scala rename to src/main/scala/io/github/erikvanzijst/scalatlsproxy/TlsProxy.scala index 441239f..b7e395a 100644 --- a/src/main/scala/tlsproxy/TlsProxy.scala +++ b/src/main/scala/io/github/erikvanzijst/scalatlsproxy/TlsProxy.scala @@ -1,10 +1,8 @@ -package tlsproxy +package io.github.erikvanzijst.scalatlsproxy import java.nio.channels.Selector -import ch.qos.logback.classic.Level import com.typesafe.scalalogging.StrictLogging -import org.slf4j.LoggerFactory trait KeyHandler { def process(): Unit diff --git a/src/main/scala/tlsproxy/TlsProxyHandler.scala b/src/main/scala/io/github/erikvanzijst/scalatlsproxy/TlsProxyHandler.scala similarity index 96% rename from src/main/scala/tlsproxy/TlsProxyHandler.scala rename to src/main/scala/io/github/erikvanzijst/scalatlsproxy/TlsProxyHandler.scala index 293dfba..400d924 100644 --- a/src/main/scala/tlsproxy/TlsProxyHandler.scala +++ b/src/main/scala/io/github/erikvanzijst/scalatlsproxy/TlsProxyHandler.scala @@ -1,4 +1,4 @@ -package tlsproxy +package io.github.erikvanzijst.scalatlsproxy import java.io.IOException import java.net.InetSocketAddress @@ -7,10 +7,10 @@ import java.nio.channels.{SelectionKey, Selector, SocketChannel, UnresolvedAddre import java.nio.charset.StandardCharsets import com.typesafe.scalalogging.StrictLogging -import tlsproxy.TlsProxyHandler.userAgent import scala.collection.JavaConverters._ import scala.util.Try +import scala.util.matching.Regex object ProxyPhase extends Enumeration { type ProxyPhase = Value @@ -18,12 +18,13 @@ object ProxyPhase extends Enumeration { } object TlsProxyHandler { - private val destPattern = "CONNECT ([^:]+):([0-9]+) HTTP/1.1".r - private val userAgent = "TlsProxy/1.0 (github.com/erikvanzijst/scala_tlsproxy)" + val destPattern: Regex = "CONNECT ([^:]+):([0-9]+) HTTP/1.1".r + val userAgent: String = "TlsProxy/1.0 (github.com/erikvanzijst/scala_tlsproxy)" } class TlsProxyHandler(selector: Selector, clientChannel: SocketChannel) extends KeyHandler with StrictLogging { import ProxyPhase._ + import TlsProxyHandler._ clientChannel.configureBlocking(false) private val peer = clientChannel.getRemoteAddress @@ -50,7 +51,6 @@ class TlsProxyHandler(selector: Selector, clientChannel: SocketChannel) extends throw new IOException(s"$peer handshake overflow") } - private def readLine(): Option[String] = { readClient() clientBuffer.flip() @@ -169,9 +169,8 @@ class TlsProxyHandler(selector: Selector, clientChannel: SocketChannel) extends clientChannel.write(serverBuffer) serverBuffer.compact() - if (serverBuffer.position() == 0) { + if (serverBuffer.position() == 0) close() - } } } catch { diff --git a/src/main/scala/tlsproxy/Main.scala b/src/main/scala/tlsproxy/Main.scala deleted file mode 100644 index f40917c..0000000 --- a/src/main/scala/tlsproxy/Main.scala +++ /dev/null @@ -1,39 +0,0 @@ -package tlsproxy - -import java.util.concurrent.{ExecutorService, Executors} - -import ch.qos.logback.classic.Level -import com.typesafe.scalalogging.StrictLogging -import org.slf4j.LoggerFactory -import scopt.OptionParser - -object Main extends StrictLogging { - - // Suppress debug when running from the shell - Seq("tlsproxy.TlsProxyHandler", "tlsproxy.ServerHandler", "tlsproxy.Pipe") - .map(LoggerFactory.getLogger) - .map(_.asInstanceOf[ch.qos.logback.classic.Logger]) - .foreach(_.setLevel(Level.INFO)) - - def main(args: Array[String]): Unit = { - - case class Config(port: Int = 3128) - - val parser: OptionParser[Config] = new OptionParser[Config]("tlsproxy") { - head("tlsproxy", "0.1") - opt[Int]("port") - .text("The port to listen on") - .action((value, cfg) => cfg.copy(port = value)) - } - - implicit val executor: ExecutorService = Executors.newFixedThreadPool(10) - parser.parse(args, Config()) match { - case Some(config) => - new TlsProxy(config.port).run() - case None => - logger.error("Invalid arguments") - System.exit(1) - } - } - -}