Configuration issue for Spray https server with se

2019-03-20 15:49发布

问题:

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.

回答1:

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.



回答2:

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")