Configuration issue for Spray https server with self-signed certificate?
Asked Answered
I

2

6

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.

Idiotism answered 15/9, 2014 at 17:55 Comment(8)
The cipher suite you enabled exclusively may not be available in your client: engine.setEnabledCipherSuites(Array("TLS_RSA_WITH_AES_256_CBC_SHA")). Try just deleting this line.Anthracosilicosis
Thanks. I just tried that, and I'm seeing the same issue. BTW, to test the connection initially, I'm simply using my browser (Safari, Firefox, and Chrome all trigger the same 'no ciphers' log).Idiotism
Also, how can I be sure that my keystore is ok? I have placed logs in the ssl config class above, and the error is the same even if I set the keystoneResource or password to something that I know to be bogus (e.g., val keyStoreResource = "/bad/path/myjks.jks").Idiotism
See this ML topic: groups.google.com/d/msg/spray-user/srnTol6bvuc/aRc4LDBQY5UJ, and this SO answer: #3663337 It may be that you don't have the right kind of key material to support the cipher in question.Anthracosilicosis
"Also, how can I be sure that my keystore is ok?" - Check if keyManagerFactory.getKeyManagers returns a proper value. In the link from above I could reproduce the same behavior by just setting the key manager to null so that would support your theory that your keystore is bogus.Anthracosilicosis
I've logged the keyManagerFactory.getKeyManagers and context.getSupportedSSLParameters.getCipherSuites, and context.getSupportedSSLParameters.getCipherSuites.size. This is what I get: Key managers: [Ljavax.net.ssl.KeyManager;@3df3bec Supported ciphers: [Ljava.lang.String;@3c993730 Num supported ciphers: 38Idiotism
I'm not sure what to make of the keyManagers and ciphers output.Idiotism
You can also try switching on -Djavax.net.debug=ssl when running the app and see if you can figure something out of the vast output it will produce.Anthracosilicosis
I
5

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:

import javax.net.ssl.SSLContext
import spray.io._
import org.apache.camel.util.jsse._

trait MySSLConfig {
    implicit def sslContext: SSLContext = {
        //val keyStoreFile = "/Users/eschow/repo/services/jks/keystore.jks"
        val keyStoreFile = "/Users/eschow/code/scala/akka-in-action/chapter2/myjks.jks"

        val ksp = new KeyStoreParameters()
        ksp.setResource(keyStoreFile);
        ksp.setPassword("abcdef")

        val kmp = new KeyManagersParameters()
        kmp.setKeyStore(ksp)
        kmp.setKeyPassword("abcdef")

        val scp = new SSLContextParameters()
        scp.setKeyManagers(kmp)

        val context= scp.createSSLContext()

        context
      }

    implicit def sslEngineProvider: ServerSSLEngineProvider = {
        ServerSSLEngineProvider { engine =>
            engine.setEnabledCipherSuites(Array("TLS_RSA_WITH_AES_256_CBC_SHA"))
            engine.setEnabledProtocols(Array("SSLv3", "TLSv1"))
            engine
        }
    }
}

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.

Idiotism answered 18/9, 2014 at 13:34 Comment(0)
E
1

If you want to read the keystore file outside the project, you could use

new FileInputStream("/Users/eschow/code/scala/akka-in-action/chapter2/myjks.jks")

otherwise you need to put the file in project's resource folder, ex. /your_project/src/main/resource, and read it

getClass.getResourceAsStream("/myjks.jks")
Ectomorph answered 9/3, 2015 at 6:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.