Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

server side TLS support #339

Merged
merged 3 commits into from
Oct 4, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 79 additions & 29 deletions framework/src/play/src/main/scala/play/core/server/NettyServer.scala
Original file line number Original file line Diff line number Diff line change
@@ -1,15 +1,9 @@
package play.core.server package play.core.server


import org.jboss.netty.buffer._
import org.jboss.netty.channel._ import org.jboss.netty.channel._
import org.jboss.netty.bootstrap._ import org.jboss.netty.bootstrap._
import org.jboss.netty.channel.Channels._ import org.jboss.netty.channel.Channels._
import org.jboss.netty.handler.codec.http._ import org.jboss.netty.handler.codec.http._
import org.jboss.netty.channel.socket.nio._
import org.jboss.netty.handler.stream._
import org.jboss.netty.handler.codec.http.HttpHeaders._
import org.jboss.netty.handler.codec.http.HttpHeaders.Names._
import org.jboss.netty.handler.codec.http.HttpHeaders.Values._
import org.jboss.netty.channel.group._ import org.jboss.netty.channel.group._
import org.jboss.netty.handler.ssl._ import org.jboss.netty.handler.ssl._


Expand All @@ -18,15 +12,12 @@ import javax.net.ssl._
import java.util.concurrent._ import java.util.concurrent._


import play.core._ import play.core._
import play.core.server.websocket._
import play.api._ import play.api._
import play.api.mvc._
import play.api.libs.iteratee._
import play.api.libs.iteratee.Input._
import play.api.libs.concurrent._
import play.core.server.netty._ import play.core.server.netty._


import scala.collection.JavaConverters._ import java.security.cert.X509Certificate
import java.io.{File, FileInputStream}
import utils.IO


/** /**
* provides a stopable Server * provides a stopable Server
Expand All @@ -48,26 +39,75 @@ class NettyServer(appProvider: ApplicationProvider, port: Int, sslPort: Option[I
Executors.newCachedThreadPool())) Executors.newCachedThreadPool()))


class PlayPipelineFactory(secure: Boolean = false) extends ChannelPipelineFactory { class PlayPipelineFactory(secure: Boolean = false) extends ChannelPipelineFactory {

def getPipeline = { def getPipeline = {
val newPipeline = pipeline() val newPipeline = pipeline()

if (secure) { if (secure) {
val keyStore = KeyStore.getInstance("JKS") sslContext.map { ctxt =>
keyStore.load(FakeKeyStore.asInputStream, FakeKeyStore.getKeyStorePassword) val sslEngine = ctxt.createSSLEngine
val kmf = KeyManagerFactory.getInstance("SunX509") sslEngine.setUseClientMode(false)
kmf.init(keyStore, FakeKeyStore.getCertificatePassword) newPipeline.addLast("ssl", new SslHandler(sslEngine))
val sslContext = SSLContext.getInstance("TLS") }
sslContext.init(kmf.getKeyManagers, null, null)
val sslEngine = sslContext.createSSLEngine
sslEngine.setUseClientMode(false)
newPipeline.addLast("ssl", new SslHandler(sslEngine))
} }

newPipeline.addLast("decoder", new HttpRequestDecoder(4096, 8192, 8192)) newPipeline.addLast("decoder", new HttpRequestDecoder(4096, 8192, 8192))
newPipeline.addLast("encoder", new HttpResponseEncoder()) newPipeline.addLast("encoder", new HttpResponseEncoder())
newPipeline.addLast("handler", defaultUpStreamHandler) newPipeline.addLast("handler", defaultUpStreamHandler)
newPipeline newPipeline
} }

lazy val sslContext: Option[SSLContext] = //the sslContext should be reused on each connection
for (tlsPort <- sslPort;
app <- appProvider.get.right.toOption )
yield {
val config = app.configuration
val ksAttr = "https.port" + tlsPort + ".keystore"
val keyStore = KeyStore.getInstance(config.getString(ksAttr + ".type").getOrElse("JKS"))
val kmfOpt: Option[Option[KeyManagerFactory]] = for (
path <- config.getString(ksAttr + ".location");
alias <- config.getString(ksAttr + ".alias");
password <- config.getString(ksAttr + ".password").orElse(Some("")).map(_.toCharArray);
algorithm <- config.getString(ksAttr + ".algorithm").orElse(Option(KeyManagerFactory.getDefaultAlgorithm))
) yield {
//Logger("play").info("path="+path+" alias="+alias+" password="+password+" alg="+algorithm)
val file = new File(path)
if (file.isFile) {
IO.use(new FileInputStream(file)) {
in =>
keyStore.load(in, password)
}
Logger("play").info("for port " + tlsPort + " using keystore at " + file)
val kmf = KeyManagerFactory.getInstance(algorithm)
kmf.init(keyStore, password) //there should be a certificate keystore
Some(kmf)
} else None
}
val kmf = kmfOpt.flatMap(a => a).orElse {
Logger("play").warn("using localhost fake keystore for ssl connection on port " + sslPort.get)
keyStore.load(FakeKeyStore.asInputStream, FakeKeyStore.getKeyStorePassword)
val kmf = KeyManagerFactory.getInstance("SunX509")
kmf.init(keyStore, FakeKeyStore.getCertificatePassword)
Some(kmf)
}.get

val sslContext = SSLContext.getInstance("TLS")
val tm = config.getString(ksAttr + ".trust").map {
case "noCA" => {
Logger("play").warn("secure http server (https) on port " + sslPort.get + " with no client " +
"side CA verification. Requires http://webid.info/ for client certifiate verification.")
Array[TrustManager](noCATrustManager)
}
case path => {
Logger("play").info("no trust info for port " + tlsPort)
null
} //for the moment
}.getOrElse {
Logger("play").info("no trust attribute for port " + tlsPort)
null
}
sslContext.init(kmf.getKeyManagers, tm, new SecureRandom())
sslContext
}

} }


// Keep a reference on all opened channels (useful to close everything properly, especially in DEV mode) // Keep a reference on all opened channels (useful to close everything properly, especially in DEV mode)
Expand Down Expand Up @@ -134,13 +174,19 @@ class NettyServer(appProvider: ApplicationProvider, port: Int, sslPort: Option[I


} }


object noCATrustManager extends X509TrustManager {
val nullArray = Array[X509Certificate]()
def checkClientTrusted(x509Certificates: Array[X509Certificate], s: String) {}
def checkServerTrusted(x509Certificates: Array[X509Certificate], s: String) {}
def getAcceptedIssuers() = nullArray
}

/** /**
* bootstraps Play application with a NettyServer backened * bootstraps Play application with a NettyServer backened
*/ */
object NettyServer { object NettyServer {


import java.io._ import java.io._
import java.net._


/** /**
* creates a NettyServer based on the application represented by applicationPath * creates a NettyServer based on the application represented by applicationPath
Expand Down Expand Up @@ -170,11 +216,13 @@ object NettyServer {
} }


try { try {
val app = new StaticApplication(applicationPath)
val config = app.application.configuration
val server = new NettyServer( val server = new NettyServer(
new StaticApplication(applicationPath), app,
Option(System.getProperty("http.port")).map(Integer.parseInt(_)).getOrElse(9000), config.getInt("http.port").getOrElse(9000),
Option(System.getProperty("https.port")).map(Integer.parseInt(_)), config.getInt("https.port"),
Option(System.getProperty("http.address")).getOrElse("0.0.0.0")) config.getString("http.address").getOrElse("0.0.0.0"))


Runtime.getRuntime.addShutdownHook(new Thread { Runtime.getRuntime.addShutdownHook(new Thread {
override def run { override def run {
Expand Down Expand Up @@ -214,7 +262,9 @@ object NettyServer {
play.utils.Threads.withContextClassLoader(this.getClass.getClassLoader) { play.utils.Threads.withContextClassLoader(this.getClass.getClassLoader) {
try { try {
val appProvider = new ReloadableApplication(sbtLink) val appProvider = new ReloadableApplication(sbtLink)
new NettyServer(appProvider, port, mode = Mode.Dev) new NettyServer(appProvider, port,
Option(System.getProperty("https.port")).map(Integer.parseInt(_)),
mode = Mode.Dev)
} catch { } catch {
case e => { case e => {
throw e match { throw e match {
Expand Down
Loading