-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
=pro merge ssl-config-akka from ssl-config project into akka-stream
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 lightbend/ssl-config@470fae7 See lightbend/ssl-config#47
- Loading branch information
Showing
6 changed files
with
286 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
211 changes: 211 additions & 0 deletions
211
akka-stream/src/main/scala/com/typesafe/sslconfig/akka/AkkaSSLConfig.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
/* | ||
* 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 | ||
|
||
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()) | ||
} | ||
|
||
} |
27 changes: 27 additions & 0 deletions
27
akka-stream/src/main/scala/com/typesafe/sslconfig/akka/SSLEngineConfigurator.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
akka-stream/src/main/scala/com/typesafe/sslconfig/akka/util/AkkaLoggerBridge.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)) | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters