=pro merge ssl-config-akka from ssl-config project into akka-stream (#21551)
This will fix the cyclic dependency issue between the ssl-config repo
and akka.
It would have been better if akka-stream would not require
these changes at all but com.typesafe.sslconfig.akka.AkkaSSLConfig is
part of the public interface of akka-stream's TLS stage. So, this
can only be fixed in the next major version.
Source code was copied over from the tree at commit
470fae76f3
See https://github.com/typesafehub/ssl-config/issues/47
This commit is contained in:
parent
0127d4f424
commit
c9854e4350
7 changed files with 288 additions and 4 deletions
|
|
@ -96,3 +96,9 @@ akka {
|
|||
protocol = "TLSv1.2"
|
||||
}
|
||||
}
|
||||
|
||||
# ssl configuration
|
||||
# folded in from former ssl-config-akka module
|
||||
ssl-config {
|
||||
logger = "com.typesafe.sslconfig.akka.util.AkkaLoggerBridge"
|
||||
}
|
||||
|
|
@ -64,7 +64,7 @@ object TLS {
|
|||
* configured using [[javax.net.ssl.SSLParameters.setEndpointIdentificationAlgorithm]].
|
||||
*/
|
||||
def apply(
|
||||
sslContext: SSLContext,
|
||||
sslContext: SSLContext, // TODO: in 2.5.x replace sslContext and sslConfig by generic SSLEngine constructor function, see https://github.com/akka/akka/issues/21753
|
||||
sslConfig: Option[AkkaSSLConfig],
|
||||
firstSession: NegotiateNewSession, role: TLSRole,
|
||||
closing: TLSClosing = IgnoreComplete, hostInfo: Option[(String, Int)] = None): scaladsl.BidiFlow[SslTlsOutbound, ByteString, ByteString, SslTlsInbound, NotUsed] =
|
||||
|
|
|
|||
|
|
@ -0,0 +1,212 @@
|
|||
/*
|
||||
* Copyright (C) 2015-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package com.typesafe.sslconfig.akka
|
||||
|
||||
import java.security.KeyStore
|
||||
import java.security.cert.CertPathValidatorException
|
||||
import java.util.Collections
|
||||
import javax.net.ssl._
|
||||
|
||||
import akka.actor._
|
||||
import akka.event.Logging
|
||||
import com.typesafe.sslconfig.akka.util.AkkaLoggerFactory
|
||||
import com.typesafe.sslconfig.ssl._
|
||||
import com.typesafe.sslconfig.util.LoggerFactory
|
||||
|
||||
// TODO: remove again in 2.5.x, see https://github.com/akka/akka/issues/21753
|
||||
object AkkaSSLConfig extends ExtensionId[AkkaSSLConfig] with ExtensionIdProvider {
|
||||
|
||||
//////////////////// EXTENSION SETUP ///////////////////
|
||||
|
||||
override def get(system: ActorSystem): AkkaSSLConfig = super.get(system)
|
||||
def apply()(implicit system: ActorSystem): AkkaSSLConfig = super.apply(system)
|
||||
|
||||
override def lookup() = AkkaSSLConfig
|
||||
|
||||
override def createExtension(system: ExtendedActorSystem): AkkaSSLConfig =
|
||||
new AkkaSSLConfig(system, defaultSSLConfigSettings(system))
|
||||
|
||||
def defaultSSLConfigSettings(system: ActorSystem): SSLConfigSettings = {
|
||||
val akkaOverrides = system.settings.config.getConfig("akka.ssl-config")
|
||||
val defaults = system.settings.config.getConfig("ssl-config")
|
||||
SSLConfigFactory.parse(akkaOverrides withFallback defaults)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
final class AkkaSSLConfig(system: ExtendedActorSystem, val config: SSLConfigSettings) extends Extension {
|
||||
|
||||
private val mkLogger = new AkkaLoggerFactory(system)
|
||||
|
||||
private val log = Logging(system, getClass)
|
||||
log.debug("Initializing AkkaSSLConfig extension...")
|
||||
|
||||
/** Can be used to modify the underlying config, most typically used to change a few values in the default config */
|
||||
def withSettings(c: SSLConfigSettings): AkkaSSLConfig =
|
||||
new AkkaSSLConfig(system, c)
|
||||
|
||||
/**
|
||||
* Returns a new [[AkkaSSLConfig]] instance with the settings changed by the given function.
|
||||
* Please note that the ActorSystem-wide extension always remains configured via typesafe config,
|
||||
* custom ones can be created for special-handling specific connections
|
||||
*/
|
||||
def mapSettings(f: SSLConfigSettings ⇒ SSLConfigSettings): AkkaSSLConfig =
|
||||
new AkkaSSLConfig(system, f(config))
|
||||
|
||||
/**
|
||||
* Returns a new [[AkkaSSLConfig]] instance with the settings changed by the given function.
|
||||
* Please note that the ActorSystem-wide extension always remains configured via typesafe config,
|
||||
* custom ones can be created for special-handling specific connections
|
||||
*
|
||||
* Java API
|
||||
*/
|
||||
// Not same signature as mapSettings to allow latter deprecation of this once we hit Scala 2.12
|
||||
def convertSettings(f: java.util.function.Function[SSLConfigSettings, SSLConfigSettings]): AkkaSSLConfig =
|
||||
new AkkaSSLConfig(system, f.apply(config))
|
||||
|
||||
val hostnameVerifier = buildHostnameVerifier(config)
|
||||
|
||||
val sslEngineConfigurator = {
|
||||
val sslContext = if (config.default) {
|
||||
log.info("ssl-config.default is true, using the JDK's default SSLContext")
|
||||
validateDefaultTrustManager(config)
|
||||
SSLContext.getDefault
|
||||
} else {
|
||||
// break out the static methods as much as we can...
|
||||
val keyManagerFactory = buildKeyManagerFactory(config)
|
||||
val trustManagerFactory = buildTrustManagerFactory(config)
|
||||
new ConfigSSLContextBuilder(mkLogger, config, keyManagerFactory, trustManagerFactory).build()
|
||||
}
|
||||
|
||||
// protocols!
|
||||
val defaultParams = sslContext.getDefaultSSLParameters
|
||||
val defaultProtocols = defaultParams.getProtocols
|
||||
val protocols = configureProtocols(defaultProtocols, config)
|
||||
|
||||
// ciphers!
|
||||
val defaultCiphers = defaultParams.getCipherSuites
|
||||
val cipherSuites = configureCipherSuites(defaultCiphers, config)
|
||||
|
||||
// apply "loose" settings
|
||||
// !! SNI!
|
||||
looseDisableSNI(defaultParams)
|
||||
|
||||
new DefaultSSLEngineConfigurator(config, protocols, cipherSuites)
|
||||
}
|
||||
|
||||
////////////////// CONFIGURING //////////////////////
|
||||
|
||||
def buildKeyManagerFactory(ssl: SSLConfigSettings): KeyManagerFactoryWrapper = {
|
||||
val keyManagerAlgorithm = ssl.keyManagerConfig.algorithm
|
||||
new DefaultKeyManagerFactoryWrapper(keyManagerAlgorithm)
|
||||
}
|
||||
|
||||
def buildTrustManagerFactory(ssl: SSLConfigSettings): TrustManagerFactoryWrapper = {
|
||||
val trustManagerAlgorithm = ssl.trustManagerConfig.algorithm
|
||||
new DefaultTrustManagerFactoryWrapper(trustManagerAlgorithm)
|
||||
}
|
||||
|
||||
def buildHostnameVerifier(conf: SSLConfigSettings): HostnameVerifier = {
|
||||
val clazz: Class[HostnameVerifier] =
|
||||
if (config.loose.disableHostnameVerification) classOf[DisabledComplainingHostnameVerifier].asInstanceOf[Class[HostnameVerifier]]
|
||||
else config.hostnameVerifierClass.asInstanceOf[Class[HostnameVerifier]]
|
||||
|
||||
val v = system.dynamicAccess.createInstanceFor[HostnameVerifier](clazz, Nil)
|
||||
.orElse(system.dynamicAccess.createInstanceFor[HostnameVerifier](clazz, List(classOf[LoggerFactory] → mkLogger)))
|
||||
.getOrElse(throw new Exception("Unable to obtain hostname verifier for class: " + clazz))
|
||||
|
||||
log.debug("buildHostnameVerifier: created hostname verifier: {}", v)
|
||||
v
|
||||
}
|
||||
|
||||
def validateDefaultTrustManager(sslConfig: SSLConfigSettings) {
|
||||
// If we are using a default SSL context, we can't filter out certificates with weak algorithms
|
||||
// We ALSO don't have access to the trust manager from the SSLContext without doing horrible things
|
||||
// with reflection.
|
||||
//
|
||||
// However, given that the default SSLContextImpl will call out to the TrustManagerFactory and any
|
||||
// configuration with system properties will also apply with the factory, we can use the factory
|
||||
// method to recreate the trust manager and validate the trust certificates that way.
|
||||
//
|
||||
// This is really a last ditch attempt to satisfy https://wiki.mozilla.org/CA:MD5and1024 on root certificates.
|
||||
//
|
||||
// http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7-b147/sun/security/ssl/SSLContextImpl.java#79
|
||||
|
||||
val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm)
|
||||
tmf.init(null.asInstanceOf[KeyStore])
|
||||
val trustManager: X509TrustManager = tmf.getTrustManagers()(0).asInstanceOf[X509TrustManager]
|
||||
|
||||
// val disabledKeyAlgorithms = sslConfig.disabledKeyAlgorithms.getOrElse(Algorithms.disabledKeyAlgorithms) // was Option
|
||||
val disabledKeyAlgorithms = sslConfig.disabledKeyAlgorithms.mkString(",") // TODO Sub optimal, we got a Seq...
|
||||
val constraints = AlgorithmConstraintsParser.parseAll(AlgorithmConstraintsParser.line, disabledKeyAlgorithms).get.toSet
|
||||
val algorithmChecker = new AlgorithmChecker(mkLogger, keyConstraints = constraints, signatureConstraints = Set())
|
||||
for (cert ← trustManager.getAcceptedIssuers) {
|
||||
try {
|
||||
algorithmChecker.checkKeyAlgorithms(cert)
|
||||
} catch {
|
||||
case e: CertPathValidatorException ⇒
|
||||
log.warning("You are using ssl-config.default=true and have a weak certificate in your default trust store! (You can modify akka.ssl-config.disabledKeyAlgorithms to remove this message.)", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def configureProtocols(existingProtocols: Array[String], sslConfig: SSLConfigSettings): Array[String] = {
|
||||
val definedProtocols = sslConfig.enabledProtocols match {
|
||||
case Some(configuredProtocols) ⇒
|
||||
// If we are given a specific list of protocols, then return it in exactly that order,
|
||||
// assuming that it's actually possible in the SSL context.
|
||||
configuredProtocols.filter(existingProtocols.contains).toArray
|
||||
|
||||
case None ⇒
|
||||
// Otherwise, we return the default protocols in the given list.
|
||||
Protocols.recommendedProtocols.filter(existingProtocols.contains)
|
||||
}
|
||||
|
||||
val allowWeakProtocols = sslConfig.loose.allowWeakProtocols
|
||||
if (!allowWeakProtocols) {
|
||||
val deprecatedProtocols = Protocols.deprecatedProtocols
|
||||
for (deprecatedProtocol ← deprecatedProtocols) {
|
||||
if (definedProtocols.contains(deprecatedProtocol)) {
|
||||
throw new IllegalStateException(s"Weak protocol $deprecatedProtocol found in ssl-config.protocols!")
|
||||
}
|
||||
}
|
||||
}
|
||||
definedProtocols
|
||||
}
|
||||
|
||||
def configureCipherSuites(existingCiphers: Array[String], sslConfig: SSLConfigSettings): Array[String] = {
|
||||
val definedCiphers = sslConfig.enabledCipherSuites match {
|
||||
case Some(configuredCiphers) ⇒
|
||||
// If we are given a specific list of ciphers, return it in that order.
|
||||
configuredCiphers.filter(existingCiphers.contains(_)).toArray
|
||||
|
||||
case None ⇒
|
||||
Ciphers.recommendedCiphers.filter(existingCiphers.contains(_)).toArray
|
||||
}
|
||||
|
||||
val allowWeakCiphers = sslConfig.loose.allowWeakCiphers
|
||||
if (!allowWeakCiphers) {
|
||||
val deprecatedCiphers = Ciphers.deprecatedCiphers
|
||||
for (deprecatedCipher ← deprecatedCiphers) {
|
||||
if (definedCiphers.contains(deprecatedCipher)) {
|
||||
throw new IllegalStateException(s"Weak cipher $deprecatedCipher found in ssl-config.ciphers!")
|
||||
}
|
||||
}
|
||||
}
|
||||
definedCiphers
|
||||
}
|
||||
|
||||
// LOOSE SETTINGS //
|
||||
|
||||
private def looseDisableSNI(defaultParams: SSLParameters): Unit = if (config.loose.disableSNI) {
|
||||
// this will be logged once for each AkkaSSLConfig
|
||||
log.warning("You are using ssl-config.loose.disableSNI=true! " +
|
||||
"It is strongly discouraged to disable Server Name Indication, as it is crucial to preventing man-in-the-middle attacks.")
|
||||
|
||||
defaultParams.setServerNames(Collections.emptyList())
|
||||
defaultParams.setSNIMatchers(Collections.emptyList())
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright (C) 2015-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package com.typesafe.sslconfig.akka
|
||||
|
||||
import javax.net.ssl.{ SSLContext, SSLEngine }
|
||||
|
||||
import com.typesafe.sslconfig.ssl.SSLConfigSettings
|
||||
|
||||
/**
|
||||
* Gives the chance to configure the SSLContext before it is going to be used.
|
||||
* The passed in context will be already set in client mode and provided with hostInfo during initialization.
|
||||
*/
|
||||
trait SSLEngineConfigurator {
|
||||
def configure(engine: SSLEngine, sslContext: SSLContext): SSLEngine
|
||||
}
|
||||
|
||||
final class DefaultSSLEngineConfigurator(config: SSLConfigSettings, enabledProtocols: Array[String], enabledCipherSuites: Array[String])
|
||||
extends SSLEngineConfigurator {
|
||||
def configure(engine: SSLEngine, sslContext: SSLContext): SSLEngine = {
|
||||
engine.setSSLParameters(sslContext.getDefaultSSLParameters)
|
||||
engine.setEnabledProtocols(enabledProtocols)
|
||||
engine.setEnabledCipherSuites(enabledCipherSuites)
|
||||
engine
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (C) 2015-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package com.typesafe.sslconfig.akka.util
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import akka.event.{ DummyClassForStringSources, EventStream }
|
||||
import akka.event.Logging._
|
||||
import com.typesafe.sslconfig.util.{ LoggerFactory, NoDepsLogger }
|
||||
|
||||
final class AkkaLoggerFactory(system: ActorSystem) extends LoggerFactory {
|
||||
override def apply(clazz: Class[_]): NoDepsLogger = new AkkaLoggerBridge(system.eventStream, clazz)
|
||||
|
||||
override def apply(name: String): NoDepsLogger = new AkkaLoggerBridge(system.eventStream, name, classOf[DummyClassForStringSources])
|
||||
}
|
||||
|
||||
class AkkaLoggerBridge(bus: EventStream, logSource: String, logClass: Class[_]) extends NoDepsLogger {
|
||||
def this(bus: EventStream, clazz: Class[_]) { this(bus, clazz.getCanonicalName, clazz) }
|
||||
|
||||
override def isDebugEnabled: Boolean = true
|
||||
|
||||
override def debug(msg: String): Unit = bus.publish(Debug(logSource, logClass, msg))
|
||||
|
||||
override def info(msg: String): Unit = bus.publish(Info(logSource, logClass, msg))
|
||||
|
||||
override def warn(msg: String): Unit = bus.publish(Warning(logSource, logClass, msg))
|
||||
|
||||
override def error(msg: String): Unit = bus.publish(Error(logSource, logClass, msg))
|
||||
override def error(msg: String, throwable: Throwable): Unit = bus.publish(Error(logSource, logClass, msg))
|
||||
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ object Dependencies {
|
|||
lazy val scalaCheckVersion = settingKey[String]("The version of ScalaCheck to use.")
|
||||
lazy val java8CompatVersion = settingKey[String]("The version of scala-java8-compat to use.")
|
||||
val junitVersion = "4.12"
|
||||
val sslConfigVersion = "0.2.1"
|
||||
|
||||
val Versions = Seq(
|
||||
crossScalaVersions := Seq("2.11.8"), // "2.12.0-RC2"
|
||||
|
|
@ -61,7 +62,7 @@ object Dependencies {
|
|||
val reactiveStreams = "org.reactivestreams" % "reactive-streams" % "1.0.0" // CC0
|
||||
|
||||
// ssl-config
|
||||
val sslConfigAkka = "com.typesafe" %% "ssl-config-akka" % "0.2.1" // ApacheV2
|
||||
val sslConfigCore = "com.typesafe" %% "ssl-config-core" % sslConfigVersion // ApacheV2
|
||||
|
||||
// For akka-http-testkit-java
|
||||
val junit = "junit" % "junit" % junitVersion // Common Public License 1.0
|
||||
|
|
@ -172,8 +173,8 @@ object Dependencies {
|
|||
// akka stream
|
||||
|
||||
lazy val stream = l ++= Seq[sbt.ModuleID](
|
||||
sslConfigAkka,
|
||||
reactiveStreams,
|
||||
sslConfigCore,
|
||||
Test.junitIntf,
|
||||
Test.scalatest.value)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ package akka
|
|||
|
||||
import com.typesafe.sbt.osgi.OsgiKeys
|
||||
import com.typesafe.sbt.osgi.SbtOsgi._
|
||||
import com.typesafe.sbt.osgi.SbtOsgi.autoImport._
|
||||
import sbt._
|
||||
import sbt.Keys._
|
||||
|
||||
|
|
@ -81,7 +82,12 @@ object OSGi {
|
|||
|
||||
val httpJackson = exports(Seq("akka.http.javadsl.marshallers.jackson"))
|
||||
|
||||
val stream = exports(Seq("akka.stream.*"), imports = Seq(scalaJava8CompatImport()))
|
||||
val stream =
|
||||
exports(
|
||||
packages = Seq("akka.stream.*",
|
||||
"com.typesafe.sslconfig.akka.*"),
|
||||
imports = Seq(scalaJava8CompatImport())) ++
|
||||
Seq(OsgiKeys.requireBundle := Seq(s"""com.typesafe.sslconfig;bundle-version="${Dependencies.sslConfigVersion}""""))
|
||||
|
||||
val streamTestkit = exports(Seq("akka.stream.testkit.*"))
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue