Search code examples
scalanettyakkakeystore

AKKA remote (with SSL) can't find keystore/truststore files on classpath


I'm trying to configure AKKA SSL connection to use my keystore and trustore files, and I want it to be able to find them on a classpath.

I tried to set application.conf to:

...
remote.netty.ssl = {
        enable = on
        key-store = "keystore"
        key-store-password = "passwd"
        trust-store = "truststore"
        trust-store-password = "passwd"
        protocol = "TLSv1"
        random-number-generator = "AES128CounterSecureRNG"
        enabled-algorithms = ["TLS_RSA_WITH_AES_128_CBC_SHA"]
    }
...

This works fine if keystore and trustore files are in the current directory. In my application these files get packaged into WAR and JAR archives, and because of that I'd like to read them from the classpath.

I tried to use getResource("keystore") in application.conf as described here without any luck. Config reads it literally as a string.

I also tried to parse String conf and force it to read the value:

val conf: Config = ConfigFactory parseString (s"""
...
"${getClass.getClassLoader.getResource("keystore").getPath}" 
...""")

In this case it finds proper path on the classpath as file://some_dir/target/scala-2.10/server_2.10-1.1-one-jar.jar!/main/server_2.10-1.1.jar!/keystore which is exactly where it's located (in the jar). However, underlying Netty SSL transport can't find the file given this path, and I get:

Oct 03, 2013 1:02:48 PM org.jboss.netty.channel.socket.nio.NioServerSocketPipelineSink
WARNING: Failed to initialize an accepted socket.
45a13eb9-6cb1-46a7-a789-e48da9997f0fakka.remote.RemoteTransportException: Server SSL connection could not be established because key store could not be loaded
    at akka.remote.netty.NettySSLSupport$.constructServerContext$1(NettySSLSupport.scala:113)
    at akka.remote.netty.NettySSLSupport$.initializeServerSSL(NettySSLSupport.scala:130)
    at akka.remote.netty.NettySSLSupport$.apply(NettySSLSupport.scala:27)
    at akka.remote.netty.NettyRemoteTransport$PipelineFactory$.defaultStack(NettyRemoteSupport.scala:74)
    at akka.remote.netty.NettyRemoteTransport$PipelineFactory$$anon$1.getPipeline(NettyRemoteSupport.scala:67)
    at akka.remote.netty.NettyRemoteTransport$PipelineFactory$$anon$1.getPipeline(NettyRemoteSupport.scala:67)
    at org.jboss.netty.channel.socket.nio.NioServerSocketPipelineSink$Boss.registerAcceptedChannel(NioServerSocketPipelineSink.java:277)
    at org.jboss.netty.channel.socket.nio.NioServerSocketPipelineSink$Boss.run(NioServerSocketPipelineSink.java:242)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:724)
Caused by: java.io.FileNotFoundException: file:/some_dir/server/target/scala-2.10/server_2.10-1.1-one-jar.jar!/main/server_2.10-1.1.jar!/keystore (No such file or directory)
    at java.io.FileInputStream.open(Native Method)
    at java.io.FileInputStream.<init>(FileInputStream.java:138)
    at java.io.FileInputStream.<init>(FileInputStream.java:97)
    at akka.remote.netty.NettySSLSupport$.constructServerContext$1(NettySSLSupport.scala:118)
    ... 10 more

I wonder if there is any way to configure this in AKKA without implementing custom SSL transport. Maybe I should configure Netty in the code?

Obviously I can hardcode the path or read it from an environment variable, but I would prefer a more flexible classpath solution.

I decided to look at the akka.remote.netty.NettySSLSupport at the code where exception is thrown from, and here is the code:

  def initializeServerSSL(settings: NettySettings, log: LoggingAdapter): SslHandler = {
log.debug("Server SSL is enabled, initialising ...")

def constructServerContext(settings: NettySettings, log: LoggingAdapter, keyStorePath: String, keyStorePassword: String, protocol: String): Option[SSLContext] =
  try {
    val rng = initializeCustomSecureRandom(settings.SSLRandomNumberGenerator, settings.SSLRandomSource, log)
    val factory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm)
    factory.init({
      val keyStore = KeyStore.getInstance(KeyStore.getDefaultType)
      val fin = new FileInputStream(keyStorePath)
      try keyStore.load(fin, keyStorePassword.toCharArray) finally fin.close()
      keyStore
    }, keyStorePassword.toCharArray)
    Option(SSLContext.getInstance(protocol)) map { ctx ⇒ ctx.init(factory.getKeyManagers, null, rng); ctx }
  } catch {
    case e: FileNotFoundException    ⇒ throw new RemoteTransportException("Server SSL connection could not be established because key store could not be loaded", e)
    case e: IOException              ⇒ throw new RemoteTransportException("Server SSL connection could not be established because: " + e.getMessage, e)
    case e: GeneralSecurityException ⇒ throw new RemoteTransportException("Server SSL connection could not be established because SSL context could not be constructed", e)
  }

It looks like it must be a plain filename (String) because that's what FileInputStream takes.

Any suggestions would be welcome!


Solution

  • At the time of writing this question there was no way to do it AFAIK. I'm closing this question but I welcome updates if new versions provide such functionality or if there are other ways to do that.