refactored code into different files: code related to Https setup in server.scala and code related to the initial test server in another.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/netty/SslLoginTest.scala Fri Oct 21 23:01:04 2011 +0200
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2011 Henry Story (bblfish.net)
+ * under the MIT licence defined
+ * http://www.opensource.org/licenses/mit-license.html
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in the
+ * Software without restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so, subject to the
+ * following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package org.w3.readwriteweb.netty
+
+import org.jboss.netty.handler.ssl.SslHandler
+import unfiltered.request.Path
+import unfiltered.response.ResponseString
+import unfiltered.netty._
+import org.w3.readwriteweb.netty.{NormalPlan, KeyAuth_Https}
+
+/**
+ * A very light weight plan to test SSL login using TLS renegotiation in netty.
+ * This shows how easy it is to to this, and can be useful to try out different browsers' implementations
+ * The certificate should only be requested of the client on going to /test/login .
+ *
+ * Note: to get this to work on all browsers, and if security is just less of a concern for you, you should
+ * set the sun.security.ssl.allowUnsafeRenegotiation=true and sun.security.ssl.allowLegacyHelloMessages=true
+ * see:
+ *
+ * http://download.oracle.com/javase/7/docs/technotes/guides/security/jsse/JSSERefGuide.html#workarounds
+ *
+ *
+ * @author hjs
+ * @created: 21/10/2011
+ */
+object SslLoginTest extends NormalPlan {
+
+ def certAvailable(sslh: SslHandler): String = try {
+ sslh.getEngine.getSession.getPeerCertificateChain.head.toString
+ } catch {
+ case e => e.getMessage
+ }
+
+
+ def intent = {
+
+ case req @ Path("/test/login") => {
+
+ req.underlying.context.getPipeline.get(classOf[org.jboss.netty.handler.ssl.SslHandler]) match {
+ case sslh: SslHandler => {
+ sslh.setEnableRenegotiation(true)
+ sslh.getEngine.setWantClientAuth(true)
+ val future = sslh.handshake()
+ future.await(5000)
+ val res = if (future.isDone) {
+ var r ="We are in login & we have an https handler! "
+ if (future.isSuccess)
+ r += "\r\n"+"SSL handchake Successful. Did we get the certificate? \r\n\r\n"+certAvailable(sslh)
+ else {
+ r += "\r\n handshake failed. Cause \r\n" +future.getCause
+ }
+ r
+ } else {
+ "Still waiting for requested certificate"
+ }
+ ResponseString(res)
+ }
+ case _ =>ResponseString("We are in login but no https handler!")
+ }
+
+ }
+ case req => {
+ req.underlying.context.getPipeline.get(classOf[org.jboss.netty.handler.ssl.SslHandler]) match {
+ case sslh: SslHandler => {
+ ResponseString(certAvailable(sslh))
+ }
+ case null => ResponseString("Just a normal hello world")
+
+ }
+ }
+ }
+
+
+ def main(args: Array[String]) {
+ new KeyAuth_Https(8443).plan(SslLoginTest).run()
+ }
+}
\ No newline at end of file
--- a/src/main/scala/netty/server.scala Fri Oct 21 16:27:37 2011 +0200
+++ b/src/main/scala/netty/server.scala Fri Oct 21 23:01:04 2011 +0200
@@ -25,104 +25,16 @@
import unfiltered.netty._
-import unfiltered.response.ResponseString
-import unfiltered.request.Path
-import org.jboss.netty.handler.ssl.SslHandler
import java.lang.String
import org.jboss.netty.channel.{ChannelPipelineFactory, ChannelHandler}
import java.security.cert.X509Certificate
import javax.net.ssl.{SSLEngine, X509ExtendedTrustManager}
import java.net.Socket
-trait nplan extends cycle.Plan with cycle.ThreadPool with ServerErrorResponse
-
-object light extends nplan {
-
- def certAvailable(sslh: SslHandler): String = try {
- sslh.getEngine.getSession.getPeerCertificateChain.head.toString
- } catch {
- case e => e.getMessage
- }
+trait NormalPlan extends cycle.Plan with cycle.ThreadPool with ServerErrorResponse
- def intent = {
-
- case req @ Path("/login") => {
-
- req.underlying.context.getPipeline.get(classOf[org.jboss.netty.handler.ssl.SslHandler]) match {
- case sslh: SslHandler => {
- sslh.setEnableRenegotiation(true)
- sslh.getEngine.setWantClientAuth(true)
- val future = sslh.handshake()
- future.await(5000)
- val res = if (future.isDone) {
- var r ="We are in login & we have an https handler! "
- if (future.isSuccess)
- r += "\r\n"+"SSL handchake Successful. Did we get the certificate? \r\n\r\n"+certAvailable(sslh)
- else {
- r += "\r\n handshake failed. Cause \r\n" +future.getCause
- }
- r
- } else {
- "Still waiting for requested certificate"
- }
- ResponseString(res)
- }
- case _ =>ResponseString("We are in login but no https handler!")
- }
-
- }
- case req => {
- req.underlying.context.getPipeline.get(classOf[org.jboss.netty.handler.ssl.SslHandler]) match {
- case sslh: SslHandler => {
- ResponseString(certAvailable(sslh))
- }
- case null => ResponseString("Just a normal hello world")
-
- }
- }
- }
-
-
- def main(args: Array[String]) {
- new Trusting_Https(8443).plan(light).run()
- }
-}
-
-object Https {
-
- /** bind to a the loopback interface only */
- def local(port: Int): Https =
- new Https(port, "127.0.0.1")
-
- /** bind to any available port on the loopback interface */
- def anylocal = local(unfiltered.util.Port.any)
-}
-
-case class Trusting_Https(override val port: Int) extends Https(port) with TrustAllSsl
-
-
-
-/** Http + Ssl implementation of the Server trait. */
-class Https(val port: Int,
- val host: String,
- val handlers: List[() => ChannelHandler],
- val beforeStopBlock: () => Unit)
-extends HttpServer
-with TrustAllSsl { self =>
-
- def this(port: Int, host: String) = this(port, host, Nil, () => ())
-
- def this(port: Int) = this(port, "0.0.0.0")
-
- def pipelineFactory: ChannelPipelineFactory =
- new SecureServerPipelineFactory(channels, handlers, this)
-
- type ServerBuilder = Https
- def handler(h: => ChannelHandler) = new Https(port, host, { () => h } :: handlers, beforeStopBlock)
- def plan(plan: => ChannelHandler) = handler(plan)
- def beforeStop(block: => Unit) = new Https(port, host, handlers, { () => beforeStopBlock(); block })
-}
+case class KeyAuth_Https(override val port: Int) extends Https(port) with KeyAuth_Ssl
/**
@@ -131,7 +43,7 @@
* is that the client knew the private key of the given public key. It is the job of other layers,
* to follow through on claims made in the certificate.
*/
-trait TrustAllSsl extends Ssl {
+trait KeyAuth_Ssl extends Ssl {
import java.security.SecureRandom
import javax.net.ssl.{SSLContext, TrustManager}
@@ -159,4 +71,41 @@
override def initSslContext(ctx: SSLContext) = ctx.init(keyManagers, trustManagers, new SecureRandom)
-}
\ No newline at end of file
+}
+
+//
+// Below is code mostly taken from unfiltered.netty, and so available under their licence. I am waiting
+// for them to make it easier to extend the netty.Https classes so that this code would not longer be
+// needed
+//
+
+object Https {
+
+ /** bind to a the loopback interface only */
+ def local(port: Int): Https =
+ new Https(port, "127.0.0.1")
+
+ /** bind to any available port on the loopback interface */
+ def anylocal = local(unfiltered.util.Port.any)
+}
+
+
+/** Http + Ssl implementation of the Server trait. */
+class Https(val port: Int,
+ val host: String,
+ val handlers: List[() => ChannelHandler],
+ val beforeStopBlock: () => Unit) extends HttpServer with KeyAuth_Ssl { self =>
+
+ def this(port: Int, host: String) = this(port, host, Nil, () => ())
+
+ def this(port: Int) = this(port, "0.0.0.0")
+
+ def pipelineFactory: ChannelPipelineFactory =
+ new SecureServerPipelineFactory(channels, handlers, this)
+
+ type ServerBuilder = Https
+ def handler(h: => ChannelHandler) = new Https(port, host, { () => h } :: handlers, beforeStopBlock)
+ def plan(plan: => ChannelHandler) = handler(plan)
+ def beforeStop(block: => Unit) = new Https(port, host, handlers, { () => beforeStopBlock(); block })
+
+}