I am using Spray 1.3, Akka 2.3, and Scala 2.11 on Mac 10.9.4 to set up an HTTP server. I am following the Ch. 2 example in Manning's Akka in Action (sample code available here: https://github.com/RayRoestenburg/akka-in-action.git), which compiles, runs, and behaves as expected when I use http, but I am having trouble configuring it for use with https.
To run with https, I have generated a self-signed certificate as follows:
keytool -genkey -keyalg RSA -alias selfsigned -keystore myjks.jks -storepass abcdef -validity 360 -keysize 2048
Following this example, https://github.com/spray/spray/tree/v1.2-M8/examples/spray-can/simple-http-server/src/main/scala/spray/examples
I've added an SSL config class:
package com.goticks
import java.security.{SecureRandom, KeyStore}
import javax.net.ssl.{KeyManagerFactory, SSLContext, TrustManagerFactory}
import spray.io._
// for SSL support (if enabled in application.conf)
trait MySSLConfig {
// if there is no SSLContext in scope implicitly the HttpServer uses the default SSLContext,
// since we want non-default settings in this example we make a custom SSLContext available here
implicit def sslContext: SSLContext = {
val keyStoreResource = "myjks.jks"
val password = "abcdef"
val keyStore = KeyStore.getInstance("jks")
keyStore.load(getClass.getResourceAsStream(keyStoreResource), password.toCharArray)
val keyManagerFactory = KeyManagerFactory.getInstance("SunX509")
keyManagerFactory.init(keyStore, password.toCharArray)
val trustManagerFactory = TrustManagerFactory.getInstance("SunX509")
trustManagerFactory.init(keyStore)
val context = SSLContext.getInstance("TLS")
context.init(keyManagerFactory.getKeyManagers, trustManagerFactory.getTrustManagers, new SecureRandom)
context
}
// if there is no ServerSSLEngineProvider in scope implicitly the HttpServer uses the default one,
// since we want to explicitly enable cipher suites and protocols we make a custom ServerSSLEngineProvider
// available here
implicit def sslEngineProvider: ServerSSLEngineProvider = {
ServerSSLEngineProvider { engine =>
engine.setEnabledCipherSuites(Array("TLS_RSA_WITH_AES_256_CBC_SHA"))
engine.setEnabledProtocols(Array("SSLv3", "TLSv1"))
engine
}
}
}
I've updated the Main class to use the SSL config:
package com.goticks
import akka.actor._
import akka.io.IO
import spray.can.Http
import spray.can.server._
import com.typesafe.config.ConfigFactory
object Main extends App with MySSLConfig {
val config = ConfigFactory.load()
val host = config.getString("http.host")
val port = config.getInt("http.port")
implicit val system = ActorSystem("goticks")
val api = system.actorOf(Props(new RestInterface()), "httpInterface")
IO(Http) ! Http.Bind(listener = api, interface = host, port = port)
}
and I've updated the application.conf:
spray {
can {
server {
server-header = "GoTicks.com REST API"
ssl-encryption = on
}
}
}
After compiling and running the server, I get the following error when I try to do an https GET:
[ERROR] [09/15/2014 10:40:48.056] [goticks-akka.actor.default-dispatcher-4] [akka://goticks/user/IO-HTTP/listener-0/7] Aborting encrypted connection to localhost/0:0:0:0:0:0:0:1%0:59617 due to [SSLHandshakeException:no cipher suites in common] -> [SSLHandshakeException:no cipher suites in common]
I'm not sure if my problem is with the generated key, or with my configuration. Incidentally, my final goal is to use this configuration with a TCP socket (see my other question: TCP socket with SSL on Scala with Akka), but I was unable to find documentation for running secure TCP, so I thought I would start with HTTPS.
Any help is appreciated.
I was finally able to make it work using Apache Camel following the advice found here. Seems like overkill to bring in Camel just to set up the SSLContext, but this is what finally worked.
My SSLConfig ended up looking like this:
BTW, the errors logged by Camel were much more helpful. Doing something silly like providing a bad path to the keystone or an incorrect password gives meaningful, human-readable errors rather than the silent failure I was seeing previously.
If you want to read the keystore file outside the project, you could use
otherwise you need to put the file in project's resource folder, ex. /your_project/src/main/resource, and read it