Skip to content

Commit

Permalink
Merge pull request #638 from mkurz/ssl-config-0.6.0
Browse files Browse the repository at this point in the history
[2.1.x] ssl-config 0.6.0 (like akka 2.6.19)
  • Loading branch information
mkurz authored Jan 16, 2022
2 parents 6f8016b + 708bb48 commit cb4c8cf
Show file tree
Hide file tree
Showing 3 changed files with 7 additions and 166 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

package play.api.libs.ws.ahc

import java.security.KeyStore
import java.security.cert.CertPathValidatorException
import javax.inject.{ Inject, Provider, Singleton }
import javax.net.ssl._

Expand Down Expand Up @@ -229,14 +227,6 @@ class AhcConfigBuilder(ahcConfig: AhcWSClientConfig = AhcWSClientConfig()) {
Protocols.recommendedProtocols.filter(existingProtocols.contains).toArray
}

if (!sslConfig.loose.allowWeakProtocols) {
val deprecatedProtocols = Protocols.deprecatedProtocols
for (deprecatedProtocol <- deprecatedProtocols) {
if (definedProtocols.contains(deprecatedProtocol)) {
throw new IllegalStateException(s"Weak protocol $deprecatedProtocol found in ws.ssl.protocols!")
}
}
}
definedProtocols
}

Expand All @@ -247,17 +237,9 @@ class AhcConfigBuilder(ahcConfig: AhcWSClientConfig = AhcWSClientConfig()) {
configuredCiphers.filter(existingCiphers.contains(_)).toArray

case None =>
Ciphers.recommendedCiphers.filter(existingCiphers.contains(_)).toArray
existingCiphers
}

if (!sslConfig.loose.allowWeakCiphers) {
val deprecatedCiphers = Ciphers.deprecatedCiphers
for (deprecatedCipher <- deprecatedCiphers) {
if (definedCiphers.contains(deprecatedCipher)) {
throw new IllegalStateException(s"Weak cipher $deprecatedCipher found in ws.ssl.ciphers!")
}
}
}
definedCiphers
}

Expand All @@ -269,7 +251,6 @@ class AhcConfigBuilder(ahcConfig: AhcWSClientConfig = AhcWSClientConfig()) {
// context!
val sslContext = if (sslConfig.default) {
logger.info("buildSSLContext: play.ws.ssl.default is true, using default SSLContext")
validateDefaultTrustManager(sslConfig)
SSLContext.getDefault
} else {
// break out the static methods as much as we can...
Expand Down Expand Up @@ -311,31 +292,7 @@ class AhcConfigBuilder(ahcConfig: AhcWSClientConfig = AhcWSClientConfig()) {
}

def validateDefaultTrustManager(sslConfig: SSLConfigSettings): Unit = {
// 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 constraints = sslConfig.disabledKeyAlgorithms.map(a => AlgorithmConstraintsParser.parseAll(AlgorithmConstraintsParser.expression, a).get).toSet
val algorithmChecker = new AlgorithmChecker(loggerFactory, Set(), constraints)
for (cert <- trustManager.getAcceptedIssuers) {
try {
algorithmChecker.checkKeyAlgorithms(cert)
} catch {
case e: CertPathValidatorException =>
logger.warn("You are using play.ws.ssl.default=true and have a weak certificate in your default trust store! (You can modify play.ws.ssl.disabledKeyAlgorithms to remove this message.)", e)
}
}
logger.warn(
"validateDefaultTrustManager is not doing anything since play-ws 2.1.8, it was useful only in Java 7 and below");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@
package play.api.libs.ws.ahc

import com.typesafe.config.{ Config, ConfigFactory }
import com.typesafe.sslconfig.ssl.{ Ciphers, Protocols, SSLConfigFactory, SSLConfigSettings }
import com.typesafe.sslconfig.ssl.{ Protocols, SSLConfigFactory, SSLConfigSettings }
import org.specs2.mock.Mockito
import org.specs2.mutable.Specification
import play.api.libs.ws.WSClientConfig
import play.shaded.ahc.org.asynchttpclient.proxy.ProxyServerSelector
import play.shaded.ahc.org.asynchttpclient.util.ProxyUtils
import uk.org.lidalia.slf4jtest.TestLoggerFactory

import scala.concurrent.duration._

Expand Down Expand Up @@ -53,7 +52,6 @@ class AhcConfigBuilderSpec extends Specification with Mockito {
actual.isFollowRedirect must_== defaultWsConfig.followRedirects
actual.getCookieStore must_== null

actual.getEnabledCipherSuites.toSeq must not contain Ciphers.deprecatedCiphers
actual.getEnabledProtocols.toSeq must not contain Protocols.deprecatedProtocols
}

Expand Down Expand Up @@ -184,47 +182,6 @@ class AhcConfigBuilderSpec extends Specification with Mockito {
asyncClientConfig.getSslEngineFactory must not(beNull)
}

"use the default with a current certificate" in {
// You can't get the value of SSLContext out from the JSSE SSL engine factory, so
// checking SSLContext.getDefault from a default = true is hard.
// Unless we can mock this, it doesn't seem like it's easy to unit test.
pending("AHC 2.0 does not provide a reference to a configured SSLContext")

//val tmc = TrustManagerConfig()
//val wsConfig = defaultWsConfig.copy(ssl = SSLConfig(default = true, trustManagerConfig = tmc))
//val config = defaultConfig.copy(wsClientConfig = wsConfig)
//val builder = new AhcConfigBuilder(config)
//
//val asyncClientConfig = builder.build()
//val sslEngineFactory = asyncClientConfig.getSslEngineFactory
}

"log a warning if sslConfig.default is passed in with an weak certificate" in {
import scala.collection.JavaConverters._
// Pass in a configuration which is guaranteed to fail, by banning RSA, DSA and EC certificates
val underlyingConfig = parseSSLConfig(
"""
|play.ws.ssl.default=true
|play.ws.ssl.disabledKeyAlgorithms=["RSA", "DSA", "EC"]
""".stripMargin)
val sslConfigSettings = SSLConfigFactory.parse(underlyingConfig)

val wsConfig = defaultWsConfig.copy(ssl = sslConfigSettings)
val config = defaultConfig.copy(wsClientConfig = wsConfig)

// clear the logger of any messages...
TestLoggerFactory.clear()
val builder = new AhcConfigBuilder(config)

// Run method that will trigger the logger warning
builder.configureSSL(sslConfig = sslConfigSettings)

val loggerFactory = TestLoggerFactory.getInstance()
val logger = loggerFactory.getLogger(builder.getClass)
val messages = logger.getLoggingEvents.asList().asScala.map(_.getMessage)
messages must contain("You are using play.ws.ssl.default=true and have a weak certificate in your default trust store! (You can modify play.ws.ssl.disabledKeyAlgorithms to remove this message.)")
}

"should validate certificates" in {
val sslConfig = SSLConfigSettings()
val wsConfig = defaultWsConfig.copy(ssl = sslConfig)
Expand Down Expand Up @@ -273,47 +230,6 @@ class AhcConfigBuilderSpec extends Specification with Mockito {

actual.toSeq must containTheSameElementsAs(Seq("derp", "baz", "quux"))
}

"throw exception on deprecated protocols from explicit list" in {
val deprecatedProtocol = Protocols.deprecatedProtocols.head

// the enabled protocol list has a deprecated protocol in it.
val underlyingConfig = parseSSLConfig(s"""play.ws.ssl.enabledProtocols=["$deprecatedProtocol", "goodOne", "goodTwo"]""")
val sslConfig = SSLConfigFactory.parse(underlyingConfig)
val wsConfig = defaultWsConfig.copy(ssl = sslConfig)
val config = defaultConfig.copy(wsClientConfig = wsConfig)

val builder = new AhcConfigBuilder(config)

// The existing protocols is larger than the enabled list, and out of order.
val existingProtocols = Array("goodTwo", "badOne", "badTwo", deprecatedProtocol, "goodOne")

builder.configureProtocols(existingProtocols, sslConfig).must(throwAn[IllegalStateException])
}

"not throw exception on deprecated protocols from list if allowWeakProtocols is enabled" in {
val deprecatedProtocol = Protocols.deprecatedProtocols.head

// the enabled protocol list has a deprecated protocol in it.
val underlyingConfig = parseSSLConfig(
s"""
|play.ws.ssl.enabledProtocols=["$deprecatedProtocol", "goodOne", "goodTwo"]
|play.ws.ssl.loose.allowWeakProtocols=true
""".stripMargin)
val sslConfig = SSLConfigFactory.parse(underlyingConfig)
val wsConfig = defaultWsConfig.copy(ssl = sslConfig)
val config = defaultConfig.copy(wsClientConfig = wsConfig)

val builder = new AhcConfigBuilder(config)

// The existing protocols is larger than the enabled list, and out of order.
val existingProtocols = Array("goodTwo", "badOne", "badTwo", deprecatedProtocol, "goodOne")

val actual = builder.configureProtocols(existingProtocols, sslConfig)

// We should only have the list in order, including the deprecated protocol.
actual.toSeq must containTheSameElementsAs(Seq(deprecatedProtocol, "goodOne", "goodTwo"))
}
}

"with ciphers" should {
Expand All @@ -330,38 +246,6 @@ class AhcConfigBuilderSpec extends Specification with Mockito {

actual.toSeq must containTheSameElementsAs(Seq("goodone", "goodtwo"))
}

"throw exception on deprecated ciphers from the explicit cipher list" in {
val underlyingConfig = parseSSLConfig(s"""play.ws.ssl.enabledCipherSuites=[${Ciphers.deprecatedCiphers.head}, "goodone", "goodtwo"]""")
val enabledCiphers = Seq(Ciphers.deprecatedCiphers.head, "goodone", "goodtwo")
val sslConfig = SSLConfigFactory.parse(underlyingConfig)
val wsConfig = defaultWsConfig.copy(ssl = sslConfig)
val config = defaultConfig.copy(wsClientConfig = wsConfig)
val builder = new AhcConfigBuilder(config)
val existingCiphers = enabledCiphers.toArray

builder.configureCipherSuites(existingCiphers, sslConfig).must(throwAn[IllegalStateException])
}

"not throw exception on deprecated ciphers if allowWeakCiphers is enabled" in {
// User specifies list with deprecated ciphers...
val underlyingConfig = parseSSLConfig(
"""
|play.ws.ssl.enabledCipherSuites=[badone, "goodone", "goodtwo"]
|play.ws.ssl.loose.allowWeakCiphers=true
""".stripMargin)

val sslConfig = SSLConfigFactory.parse(underlyingConfig)
val wsConfig = defaultWsConfig.copy(ssl = sslConfig)
val config = defaultConfig.copy(wsClientConfig = wsConfig)
val builder = new AhcConfigBuilder(config)
val existingCiphers = Array("badone", "goodone", "goodtwo")

val actual = builder.configureCipherSuites(existingCiphers, sslConfig)

actual.toSeq must containTheSameElementsAs(Seq("badone", "goodone", "goodtwo"))
}

}
}
}
Expand Down
6 changes: 3 additions & 3 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ object Dependencies {

val javaxInject = Seq("javax.inject" % "javax.inject" % "1")

val sslConfigVersion = "0.4.1"
val sslConfigVersion = "0.6.0"
val sslConfigCore = Seq("com.typesafe" %% "ssl-config-core" % sslConfigVersion)

val scalaXmlVersion = "1.2.0"
Expand All @@ -48,9 +48,9 @@ object Dependencies {
val asyncHttpClientVersion = "2.10.5"
val asyncHttpClient = Seq("org.asynchttpclient" % "async-http-client" % asyncHttpClientVersion)

val akkaVersion = "2.6.1"
val akkaVersion = "2.6.18+31-9d0684d8-SNAPSHOT"
val akkaStreams = Seq("com.typesafe.akka" %% "akka-stream" % akkaVersion)
val akkaHttp = Seq("com.typesafe.akka" %% "akka-http" % "10.1.11")
val akkaHttp = Seq("com.typesafe.akka" %% "akka-http" % "10.1.15")

val reactiveStreams = Seq("org.reactivestreams" % "reactive-streams" % "1.0.3")

Expand Down

0 comments on commit cb4c8cf

Please sign in to comment.