package play.api.libs.ws.ssl
import play.api.PlayConfig
import java.security.{ KeyStore, SecureRandom }
import java.net.URL
import javax.net.ssl.{ TrustManagerFactory, KeyManagerFactory, HostnameVerifier }
case class KeyStoreConfig(storeType: String = KeyStore.getDefaultType,
filePath: Option[String] = None,
data: Option[String] = None,
password: Option[String] = None) {
assert(filePath.isDefined ^ data.isDefined, "Either key store path or data must be defined, but not both.")
}
case class TrustStoreConfig(storeType: String = KeyStore.getDefaultType,
filePath: Option[String],
data: Option[String]) {
assert(filePath.isDefined ^ data.isDefined, "Either trust store path or data must be defined, but not both.")
}
case class KeyManagerConfig(
algorithm: String = KeyManagerFactory.getDefaultAlgorithm,
keyStoreConfigs: Seq[KeyStoreConfig] = Nil)
case class TrustManagerConfig(
algorithm: String = TrustManagerFactory.getDefaultAlgorithm,
trustStoreConfigs: Seq[TrustStoreConfig] = Nil)
case class SSLDebugConfig(
all: Boolean = false,
ssl: Boolean = false,
certpath: Boolean = false,
ocsp: Boolean = false,
record: Option[SSLDebugRecordOptions] = None,
handshake: Option[SSLDebugHandshakeOptions] = None,
keygen: Boolean = false,
session: Boolean = false,
defaultctx: Boolean = false,
sslctx: Boolean = false,
sessioncache: Boolean = false,
keymanager: Boolean = false,
trustmanager: Boolean = false,
pluggability: Boolean = false) {
def enabled = all || ssl || certpath || ocsp || record.isDefined || handshake.isDefined ||
keygen || session || defaultctx || sslctx || sessioncache || keymanager || trustmanager ||
pluggability
def withAll = this.copy(all = true)
def withCertPath = this.copy(certpath = true)
def withOcsp = this.withCertPath.copy(ocsp = true)
def withRecord(plaintext: Boolean = false, packet: Boolean = false) = {
this.copy(record = Some(SSLDebugRecordOptions(plaintext, packet)))
}
def withHandshake(data: Boolean = false, verbose: Boolean = false) = {
this.copy(handshake = Some(SSLDebugHandshakeOptions(data, verbose)))
}
def withSSL = this.copy(ssl = true)
def withKeygen = this.copy(keygen = true)
def withSession = this.copy(session = true)
def withDefaultContext = this.copy(defaultctx = true)
def withSSLContext = this.copy(sslctx = true)
def withSessionCache = this.copy(sessioncache = true)
def withKeyManager = this.copy(keymanager = true)
def withTrustManager = this.copy(trustmanager = true)
def withPluggability = this.copy(pluggability = true)
}
case class SSLDebugHandshakeOptions(data: Boolean = false, verbose: Boolean = false)
case class SSLDebugRecordOptions(plaintext: Boolean = false, packet: Boolean = false)
case class SSLLooseConfig(
allowWeakCiphers: Boolean = false,
allowWeakProtocols: Boolean = false,
allowLegacyHelloMessages: Option[Boolean] = None,
allowUnsafeRenegotiation: Option[Boolean] = None,
disableHostnameVerification: Boolean = false,
acceptAnyCertificate: Boolean = false)
case class SSLConfig(
default: Boolean = false,
protocol: String = "TLSv1.2",
checkRevocation: Option[Boolean] = None,
revocationLists: Option[Seq[URL]] = None,
enabledCipherSuites: Option[Seq[String]] = None,
enabledProtocols: Option[Seq[String]] = Some(Seq("TLSv1.2", "TLSv1.1", "TLSv1")),
disabledSignatureAlgorithms: Seq[String] = Seq("MD2", "MD4", "MD5"),
disabledKeyAlgorithms: Seq[String] = Seq("RSA keySize < 2048", "DSA keySize < 2048", "EC keySize < 224"),
keyManagerConfig: KeyManagerConfig = KeyManagerConfig(),
trustManagerConfig: TrustManagerConfig = TrustManagerConfig(),
hostnameVerifierClass: Class[_ <: HostnameVerifier] = classOf[DefaultHostnameVerifier],
secureRandom: Option[SecureRandom] = None,
debug: SSLDebugConfig = SSLDebugConfig(),
loose: SSLLooseConfig = SSLLooseConfig())
object SSLConfigFactory {
def defaultConfig = SSLConfig()
}
class SSLConfigParser(c: PlayConfig, classLoader: ClassLoader) {
def parse(): SSLConfig = {
val default = c.get[Boolean]("default")
val protocol = c.get[String]("protocol")
val checkRevocation = c.getOptional[Boolean]("checkRevocation")
val revocationLists: Option[Seq[URL]] = Some(
c.get[Seq[String]]("revocationLists").map(new URL(_))
).filter(_.nonEmpty)
val debug = parseDebug(c.get[PlayConfig]("debug"))
val looseOptions = parseLooseOptions(c.get[PlayConfig]("loose"))
val ciphers = Some(c.get[Seq[String]]("enabledCipherSuites")).filter(_.nonEmpty)
val protocols = Some(c.get[Seq[String]]("enabledProtocols")).filter(_.nonEmpty)
val hostnameVerifierClass = c.getOptional[String]("hostnameVerifierClass") match {
case None => classOf[DefaultHostnameVerifier]
case Some(fqcn) => classLoader.loadClass(fqcn).asSubclass(classOf[HostnameVerifier])
}
val disabledSignatureAlgorithms = c.get[Seq[String]]("disabledSignatureAlgorithms")
val disabledKeyAlgorithms = c.get[Seq[String]]("disabledKeyAlgorithms")
val keyManagers = parseKeyManager(c.get[PlayConfig]("keyManager"))
val trustManagers = parseTrustManager(c.get[PlayConfig]("trustManager"))
SSLConfig(
default = default,
protocol = protocol,
checkRevocation = checkRevocation,
revocationLists = revocationLists,
enabledCipherSuites = ciphers,
enabledProtocols = protocols,
keyManagerConfig = keyManagers,
hostnameVerifierClass = hostnameVerifierClass,
disabledSignatureAlgorithms = disabledSignatureAlgorithms,
disabledKeyAlgorithms = disabledKeyAlgorithms,
trustManagerConfig = trustManagers,
secureRandom = None,
debug = debug,
loose = looseOptions)
}
def parseLooseOptions(config: PlayConfig): SSLLooseConfig = {
val allowWeakProtocols = config.get[Boolean]("allowWeakProtocols")
val allowWeakCiphers = config.get[Boolean]("allowWeakCiphers")
val allowMessages = config.getOptional[Boolean]("allowLegacyHelloMessages")
val allowUnsafeRenegotiation = config.getOptional[Boolean]("allowUnsafeRenegotiation")
val disableHostnameVerification = config.get[Boolean]("disableHostnameVerification")
val acceptAnyCertificate = config.get[Boolean]("acceptAnyCertificate")
SSLLooseConfig(
allowWeakCiphers = allowWeakCiphers,
allowWeakProtocols = allowWeakProtocols,
allowLegacyHelloMessages = allowMessages,
allowUnsafeRenegotiation = allowUnsafeRenegotiation,
disableHostnameVerification = disableHostnameVerification,
acceptAnyCertificate = acceptAnyCertificate
)
}
def parseDebug(config: PlayConfig): SSLDebugConfig = {
val certpath = config.get[Boolean]("certpath")
if (config.get[Boolean]("all")) {
SSLDebugConfig(all = true, certpath = certpath)
} else {
val record: Option[SSLDebugRecordOptions] = if (config.get[Boolean]("record")) {
val plaintext = config.get[Boolean]("plaintext")
val packet = config.get[Boolean]("packet")
Some(SSLDebugRecordOptions(plaintext = plaintext, packet = packet))
} else None
val handshake = if (config.get[Boolean]("handshake")) {
val data = config.get[Boolean]("data")
val verbose = config.get[Boolean]("verbose")
Some(SSLDebugHandshakeOptions(data = data, verbose = verbose))
} else {
None
}
val keygen = config.get[Boolean]("keygen")
val session = config.get[Boolean]("session")
val defaultctx = config.get[Boolean]("defaultctx")
val sslctx = config.get[Boolean]("sslctx")
val sessioncache = config.get[Boolean]("sessioncache")
val keymanager = config.get[Boolean]("keymanager")
val trustmanager = config.get[Boolean]("trustmanager")
val pluggability = config.get[Boolean]("pluggability")
val ssl = config.get[Boolean]("ssl")
SSLDebugConfig(
ssl = ssl,
record = record,
handshake = handshake,
keygen = keygen,
session = session,
defaultctx = defaultctx,
sslctx = sslctx,
sessioncache = sessioncache,
keymanager = keymanager,
trustmanager = trustmanager,
pluggability = pluggability,
certpath = certpath)
}
}
def parseKeyStoreInfo(config: PlayConfig): KeyStoreConfig = {
val storeType = config.getOptional[String]("type").getOrElse(KeyStore.getDefaultType)
val path = config.getOptional[String]("path")
val data = config.getOptional[String]("data")
val password = config.getOptional[String]("password")
KeyStoreConfig(filePath = path, storeType = storeType, data = data, password = password)
}
def parseTrustStoreInfo(config: PlayConfig): TrustStoreConfig = {
val storeType = config.getOptional[String]("type").getOrElse(KeyStore.getDefaultType)
val path = config.getOptional[String]("path")
val data = config.getOptional[String]("data")
TrustStoreConfig(filePath = path, storeType = storeType, data = data)
}
def parseKeyManager(config: PlayConfig): KeyManagerConfig = {
val algorithm = config.getOptional[String]("algorithm") match {
case None => KeyManagerFactory.getDefaultAlgorithm
case Some(other) => other
}
val keyStoreInfos = config.getPrototypedSeq("stores").map { store =>
parseKeyStoreInfo(store)
}
KeyManagerConfig(algorithm, keyStoreInfos)
}
def parseTrustManager(config: PlayConfig): TrustManagerConfig = {
val algorithm = config.getOptional[String]("algorithm") match {
case None => TrustManagerFactory.getDefaultAlgorithm
case Some(other) => other
}
val trustStoreInfos = config.getPrototypedSeq("stores").map { store =>
parseTrustStoreInfo(store)
}
TrustManagerConfig(algorithm, trustStoreInfos)
}
}