k8s-friendly SSLEngine provider (simplified) (#29152)
Co-authored-by: Arnout Engelen <github@bzzt.net> Co-authored-by: James Roper <james@jazzy.id.au>
This commit is contained in:
parent
9f05948f29
commit
c287fff034
58 changed files with 2064 additions and 266 deletions
|
|
@ -1098,7 +1098,6 @@ akka {
|
|||
outbound-client-hostname = ""
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
# SSL configuration that is used when transport=tls-tcp.
|
||||
|
|
@ -1176,6 +1175,63 @@ akka {
|
|||
hostname-verification = off
|
||||
}
|
||||
|
||||
# Config of akka.remote.artery.tcp.ssl.RotatingKeysSSLEngineProvider
|
||||
# This engine provider reads PEM files from a mount point shared with the secret
|
||||
# manager. The constructed SSLContext is cached some time (configurable) so when
|
||||
# the credentials rotate the new credentials are eventually picked up.
|
||||
# By default mTLS is enabled.
|
||||
# This provider also includes a verification fase that runs after the TLS handshake
|
||||
# phase. In this verification, both peers run an authorization and verify they are
|
||||
# part of the same akka cluster. The verification happens via comparing the subject
|
||||
# names in the peer's certificate with the name on the own certificate so if you
|
||||
# use this SSLEngineProvider you should make sure all nodes on the cluster include
|
||||
# at least one common subject name (CN or SAN).
|
||||
# The Key setup this implementation supports has some limitations:
|
||||
# 1. the private key must be provided on a PKCS#1 or a non-encrypted PKCS#8 PEM-formatted file
|
||||
# 2. the private key must be be of an algorythm supported by `akka-pki` tools (e.g. "RSA", not "EC")
|
||||
# 3. the node certificate must be issued by a root CA (not an intermediate CA)
|
||||
# 4. both the node and the CA certificates must be provided in PEM-formatted files
|
||||
rotating-keys-engine {
|
||||
|
||||
# This is a convention that people may follow if they wish to save themselves some configuration
|
||||
secret-mount-point = /var/run/secrets/akka-tls/rotating-keys-engine
|
||||
|
||||
# The absolute path the PEM file with the private key.
|
||||
key-file = ${akka.remote.artery.ssl.rotating-keys-engine.secret-mount-point}/tls.key
|
||||
# The absolute path to the PEM file of the certificate for the private key above.
|
||||
cert-file = ${akka.remote.artery.ssl.rotating-keys-engine.secret-mount-point}/tls.crt
|
||||
# The absolute path to the PEM file of the certificate of the CA that emited
|
||||
# the node certificate above.
|
||||
ca-cert-file = ${akka.remote.artery.ssl.rotating-keys-engine.secret-mount-point}/ca.crt
|
||||
|
||||
# There are two options, and the default SecureRandom is recommended:
|
||||
# "" or "SecureRandom" => (default)
|
||||
# "SHA1PRNG" => Can be slow because of blocking issues on Linux
|
||||
#
|
||||
# Setting a value here may require you to supply the appropriate cipher
|
||||
# suite (see enabled-algorithms section)
|
||||
random-number-generator = ""
|
||||
|
||||
# Example: ["TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||
# "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||
# "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
|
||||
# "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"]
|
||||
# If you use a JDK 8 prior to 8u161 you need to install
|
||||
# the JCE Unlimited Strength Jurisdiction Policy Files to use AES 256.
|
||||
# More info here:
|
||||
# https://www.oracle.com/java/technologies/javase-jce-all-downloads.html
|
||||
enabled-algorithms = ["TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"]
|
||||
|
||||
# Protocol to use for SSL encryption, choose from:
|
||||
# TLS 1.2 is available since JDK7, and default since JDK8:
|
||||
# https://blogs.oracle.com/java-platform-group/entry/java_8_will_use_tls
|
||||
protocol = "TLSv1.2"
|
||||
|
||||
# How long should an SSLContext instance be cached. When rotating keys and certificates,
|
||||
# there must a time overlap between the old certificate/key and the new ones. The
|
||||
# value of this setting should be lower than duration of that overlap.
|
||||
ssl-context-cache-ttl = 5m
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package akka.remote.artery.tcp
|
||||
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import java.security.GeneralSecurityException
|
||||
import java.security.KeyStore
|
||||
import java.security.SecureRandom
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import akka.event.LogMarker
|
||||
import akka.event.Logging
|
||||
import akka.event.MarkerLoggingAdapter
|
||||
import akka.remote.artery.tcp.ssl.SSLEngineConfig
|
||||
import akka.stream.TLSRole
|
||||
import com.typesafe.config.Config
|
||||
import javax.net.ssl.KeyManager
|
||||
import javax.net.ssl.KeyManagerFactory
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.SSLEngine
|
||||
import javax.net.ssl.SSLSession
|
||||
import javax.net.ssl.TrustManager
|
||||
import javax.net.ssl.TrustManagerFactory
|
||||
|
||||
import scala.util.Try
|
||||
|
||||
/**
|
||||
* Config in akka.remote.artery.ssl.config-ssl-engine
|
||||
*
|
||||
* Subclass may override protected methods to replace certain parts, such as key and trust manager.
|
||||
*/
|
||||
class ConfigSSLEngineProvider(protected val config: Config, protected val log: MarkerLoggingAdapter)
|
||||
extends SSLEngineProvider {
|
||||
|
||||
def this(system: ActorSystem) =
|
||||
this(
|
||||
system.settings.config.getConfig("akka.remote.artery.ssl.config-ssl-engine"),
|
||||
Logging.withMarker(system, classOf[ConfigSSLEngineProvider].getName))
|
||||
|
||||
private val sslEngineConfig = new SSLEngineConfig(config)
|
||||
|
||||
val SSLKeyStore: String = config.getString("key-store")
|
||||
val SSLTrustStore: String = config.getString("trust-store")
|
||||
val SSLKeyStorePassword: String = config.getString("key-store-password")
|
||||
val SSLKeyPassword: String = config.getString("key-password")
|
||||
val SSLTrustStorePassword: String = config.getString("trust-store-password")
|
||||
val SSLEnabledAlgorithms: Set[String] = sslEngineConfig.SSLEnabledAlgorithms
|
||||
val SSLProtocol: String = sslEngineConfig.SSLProtocol
|
||||
val SSLRandomNumberGenerator: String = sslEngineConfig.SSLRandomNumberGenerator
|
||||
val SSLRequireMutualAuthentication: Boolean = sslEngineConfig.SSLRequireMutualAuthentication
|
||||
val HostnameVerification: Boolean = sslEngineConfig.HostnameVerification
|
||||
|
||||
private lazy val sslContext: SSLContext = {
|
||||
// log hostname verification warning once
|
||||
if (HostnameVerification)
|
||||
log.debug("TLS/SSL hostname verification is enabled.")
|
||||
else
|
||||
log.info(
|
||||
LogMarker.Security,
|
||||
"TLS/SSL hostname verification is disabled. See Akka reference documentation for more information.")
|
||||
|
||||
constructContext()
|
||||
}
|
||||
|
||||
private def constructContext(): SSLContext = {
|
||||
try {
|
||||
val rng = createSecureRandom()
|
||||
val ctx = SSLContext.getInstance(SSLProtocol)
|
||||
ctx.init(keyManagers, trustManagers, rng)
|
||||
ctx
|
||||
} catch {
|
||||
case e: FileNotFoundException =>
|
||||
throw new SslTransportException(
|
||||
"Server SSL connection could not be established because key store could not be loaded",
|
||||
e)
|
||||
case e: IOException =>
|
||||
throw new SslTransportException("Server SSL connection could not be established because: " + e.getMessage, e)
|
||||
case e: GeneralSecurityException =>
|
||||
throw new SslTransportException(
|
||||
"Server SSL connection could not be established because SSL context could not be constructed",
|
||||
e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass may override to customize loading of `KeyStore`
|
||||
*/
|
||||
protected def loadKeystore(filename: String, password: String): KeyStore = {
|
||||
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType)
|
||||
val fin = Files.newInputStream(Paths.get(filename))
|
||||
try keyStore.load(fin, password.toCharArray)
|
||||
finally Try(fin.close())
|
||||
keyStore
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass may override to customize `KeyManager`
|
||||
*/
|
||||
protected def keyManagers: Array[KeyManager] = {
|
||||
val factory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm)
|
||||
factory.init(loadKeystore(SSLKeyStore, SSLKeyStorePassword), SSLKeyPassword.toCharArray)
|
||||
factory.getKeyManagers
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass may override to customize `TrustManager`
|
||||
*/
|
||||
protected def trustManagers: Array[TrustManager] = {
|
||||
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm)
|
||||
trustManagerFactory.init(loadKeystore(SSLTrustStore, SSLTrustStorePassword))
|
||||
trustManagerFactory.getTrustManagers
|
||||
}
|
||||
|
||||
def createSecureRandom(): SecureRandom =
|
||||
SecureRandomFactory.createSecureRandom(SSLRandomNumberGenerator, log)
|
||||
|
||||
override def createServerSSLEngine(hostname: String, port: Int): SSLEngine =
|
||||
createSSLEngine(akka.stream.Server, hostname, port)
|
||||
|
||||
override def createClientSSLEngine(hostname: String, port: Int): SSLEngine =
|
||||
createSSLEngine(akka.stream.Client, hostname, port)
|
||||
|
||||
private def createSSLEngine(role: TLSRole, hostname: String, port: Int): SSLEngine = {
|
||||
createSSLEngine(sslContext, role, hostname, port)
|
||||
}
|
||||
|
||||
private def createSSLEngine(sslContext: SSLContext, role: TLSRole, hostname: String, port: Int): SSLEngine = {
|
||||
|
||||
val engine = sslContext.createSSLEngine(hostname, port)
|
||||
|
||||
if (HostnameVerification && role == akka.stream.Client) {
|
||||
val sslParams = sslContext.getDefaultSSLParameters
|
||||
sslParams.setEndpointIdentificationAlgorithm("HTTPS")
|
||||
engine.setSSLParameters(sslParams)
|
||||
}
|
||||
|
||||
engine.setUseClientMode(role == akka.stream.Client)
|
||||
engine.setEnabledCipherSuites(SSLEnabledAlgorithms.toArray)
|
||||
engine.setEnabledProtocols(Array(SSLProtocol))
|
||||
|
||||
if ((role != akka.stream.Client) && SSLRequireMutualAuthentication)
|
||||
engine.setNeedClientAuth(true)
|
||||
|
||||
engine
|
||||
}
|
||||
|
||||
override def verifyClientSession(hostname: String, session: SSLSession): Option[Throwable] =
|
||||
None
|
||||
|
||||
override def verifyServerSession(hostname: String, session: SSLSession): Option[Throwable] =
|
||||
None
|
||||
|
||||
}
|
||||
|
|
@ -5,35 +5,11 @@
|
|||
package akka.remote.artery
|
||||
package tcp
|
||||
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import java.security.GeneralSecurityException
|
||||
import java.security.KeyStore
|
||||
import java.security.SecureRandom
|
||||
import javax.net.ssl.KeyManager
|
||||
import javax.net.ssl.KeyManagerFactory
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.SSLEngine
|
||||
import javax.net.ssl.SSLSession
|
||||
import javax.net.ssl.TrustManager
|
||||
import javax.net.ssl.TrustManagerFactory
|
||||
|
||||
import scala.util.Try
|
||||
|
||||
import com.typesafe.config.Config
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import akka.actor.ExtendedActorSystem
|
||||
import akka.actor.setup.Setup
|
||||
import akka.annotation.InternalApi
|
||||
import akka.event.LogMarker
|
||||
import akka.event.Logging
|
||||
import akka.event.MarkerLoggingAdapter
|
||||
import akka.japi.Util.immutableSeq
|
||||
import akka.stream.TLSRole
|
||||
import akka.util.ccompat._
|
||||
import javax.net.ssl.SSLEngine
|
||||
import javax.net.ssl.SSLSession
|
||||
|
||||
@ccompatUsedUntil213
|
||||
trait SSLEngineProvider {
|
||||
|
|
@ -60,132 +36,6 @@ trait SSLEngineProvider {
|
|||
|
||||
class SslTransportException(message: String, cause: Throwable) extends RuntimeException(message, cause)
|
||||
|
||||
/**
|
||||
* Config in akka.remote.artery.ssl.config-ssl-engine
|
||||
*
|
||||
* Subclass may override protected methods to replace certain parts, such as key and trust manager.
|
||||
*/
|
||||
class ConfigSSLEngineProvider(protected val config: Config, protected val log: MarkerLoggingAdapter)
|
||||
extends SSLEngineProvider {
|
||||
|
||||
def this(system: ActorSystem) =
|
||||
this(
|
||||
system.settings.config.getConfig("akka.remote.artery.ssl.config-ssl-engine"),
|
||||
Logging.withMarker(system, classOf[ConfigSSLEngineProvider].getName))
|
||||
|
||||
val SSLKeyStore: String = config.getString("key-store")
|
||||
val SSLTrustStore: String = config.getString("trust-store")
|
||||
val SSLKeyStorePassword: String = config.getString("key-store-password")
|
||||
val SSLKeyPassword: String = config.getString("key-password")
|
||||
val SSLTrustStorePassword: String = config.getString("trust-store-password")
|
||||
val SSLEnabledAlgorithms: Set[String] = immutableSeq(config.getStringList("enabled-algorithms")).to(Set)
|
||||
val SSLProtocol: String = config.getString("protocol")
|
||||
val SSLRandomNumberGenerator: String = config.getString("random-number-generator")
|
||||
val SSLRequireMutualAuthentication: Boolean = config.getBoolean("require-mutual-authentication")
|
||||
val HostnameVerification: Boolean = config.getBoolean("hostname-verification")
|
||||
|
||||
private lazy val sslContext: SSLContext = {
|
||||
// log hostname verification warning once
|
||||
if (HostnameVerification)
|
||||
log.debug("TLS/SSL hostname verification is enabled.")
|
||||
else
|
||||
log.info(
|
||||
LogMarker.Security,
|
||||
"TLS/SSL hostname verification is disabled. See Akka reference documentation for more information.")
|
||||
|
||||
constructContext()
|
||||
}
|
||||
|
||||
private def constructContext(): SSLContext = {
|
||||
try {
|
||||
val rng = createSecureRandom()
|
||||
val ctx = SSLContext.getInstance(SSLProtocol)
|
||||
ctx.init(keyManagers, trustManagers, rng)
|
||||
ctx
|
||||
} catch {
|
||||
case e: FileNotFoundException =>
|
||||
throw new SslTransportException(
|
||||
"Server SSL connection could not be established because key store could not be loaded",
|
||||
e)
|
||||
case e: IOException =>
|
||||
throw new SslTransportException("Server SSL connection could not be established because: " + e.getMessage, e)
|
||||
case e: GeneralSecurityException =>
|
||||
throw new SslTransportException(
|
||||
"Server SSL connection could not be established because SSL context could not be constructed",
|
||||
e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass may override to customize loading of `KeyStore`
|
||||
*/
|
||||
protected def loadKeystore(filename: String, password: String): KeyStore = {
|
||||
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType)
|
||||
val fin = Files.newInputStream(Paths.get(filename))
|
||||
try keyStore.load(fin, password.toCharArray)
|
||||
finally Try(fin.close())
|
||||
keyStore
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass may override to customize `KeyManager`
|
||||
*/
|
||||
protected def keyManagers: Array[KeyManager] = {
|
||||
val factory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm)
|
||||
factory.init(loadKeystore(SSLKeyStore, SSLKeyStorePassword), SSLKeyPassword.toCharArray)
|
||||
factory.getKeyManagers
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass may override to customize `TrustManager`
|
||||
*/
|
||||
protected def trustManagers: Array[TrustManager] = {
|
||||
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm)
|
||||
trustManagerFactory.init(loadKeystore(SSLTrustStore, SSLTrustStorePassword))
|
||||
trustManagerFactory.getTrustManagers
|
||||
}
|
||||
|
||||
def createSecureRandom(): SecureRandom =
|
||||
SecureRandomFactory.createSecureRandom(SSLRandomNumberGenerator, log)
|
||||
|
||||
override def createServerSSLEngine(hostname: String, port: Int): SSLEngine =
|
||||
createSSLEngine(akka.stream.Server, hostname, port)
|
||||
|
||||
override def createClientSSLEngine(hostname: String, port: Int): SSLEngine =
|
||||
createSSLEngine(akka.stream.Client, hostname, port)
|
||||
|
||||
private def createSSLEngine(role: TLSRole, hostname: String, port: Int): SSLEngine = {
|
||||
createSSLEngine(sslContext, role, hostname, port)
|
||||
}
|
||||
|
||||
private def createSSLEngine(sslContext: SSLContext, role: TLSRole, hostname: String, port: Int): SSLEngine = {
|
||||
|
||||
val engine = sslContext.createSSLEngine(hostname, port)
|
||||
|
||||
if (HostnameVerification && role == akka.stream.Client) {
|
||||
val sslParams = sslContext.getDefaultSSLParameters
|
||||
sslParams.setEndpointIdentificationAlgorithm("HTTPS")
|
||||
engine.setSSLParameters(sslParams)
|
||||
}
|
||||
|
||||
engine.setUseClientMode(role == akka.stream.Client)
|
||||
engine.setEnabledCipherSuites(SSLEnabledAlgorithms.toArray)
|
||||
engine.setEnabledProtocols(Array(SSLProtocol))
|
||||
|
||||
if ((role != akka.stream.Client) && SSLRequireMutualAuthentication)
|
||||
engine.setNeedClientAuth(true)
|
||||
|
||||
engine
|
||||
}
|
||||
|
||||
override def verifyClientSession(hostname: String, session: SSLSession): Option[Throwable] =
|
||||
None
|
||||
|
||||
override def verifyServerSession(hostname: String, session: SSLSession): Option[Throwable] =
|
||||
None
|
||||
|
||||
}
|
||||
|
||||
object SSLEngineProviderSetup {
|
||||
|
||||
/**
|
||||
|
|
@ -214,31 +64,3 @@ object SSLEngineProviderSetup {
|
|||
* Constructor is *Internal API*, use factories in [[SSLEngineProviderSetup]]
|
||||
*/
|
||||
class SSLEngineProviderSetup private (val sslEngineProvider: ExtendedActorSystem => SSLEngineProvider) extends Setup
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
@InternalApi private[akka] object SecureRandomFactory {
|
||||
def createSecureRandom(randomNumberGenerator: String, log: MarkerLoggingAdapter): SecureRandom = {
|
||||
val rng = randomNumberGenerator match {
|
||||
case s @ ("SHA1PRNG" | "NativePRNG") =>
|
||||
log.debug("SSL random number generator set to: {}", s)
|
||||
// SHA1PRNG needs /dev/urandom to be the source on Linux to prevent problems with /dev/random blocking
|
||||
// However, this also makes the seed source insecure as the seed is reused to avoid blocking (not a problem on FreeBSD).
|
||||
SecureRandom.getInstance(s)
|
||||
|
||||
case "" | "SecureRandom" =>
|
||||
log.debug("SSL random number generator set to [SecureRandom]")
|
||||
new SecureRandom
|
||||
|
||||
case unknown =>
|
||||
log.warning(
|
||||
LogMarker.Security,
|
||||
"Unknown SSL random number generator [{}] falling back to SecureRandom",
|
||||
unknown)
|
||||
new SecureRandom
|
||||
}
|
||||
rng.nextInt() // prevent stall on first access
|
||||
rng
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package akka.remote.artery.tcp
|
||||
|
||||
import java.security.SecureRandom
|
||||
|
||||
import akka.annotation.InternalApi
|
||||
import akka.event.LogMarker
|
||||
import akka.event.MarkerLoggingAdapter
|
||||
import com.typesafe.config.Config
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
@InternalApi private[akka] object SecureRandomFactory {
|
||||
|
||||
val GeneratorSha1Prng = "SHA1PRNG"
|
||||
val GeneratorNativePrng = "NativePRNG"
|
||||
val GeneratorJdkSecureRandom = "SecureRandom"
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
@InternalApi
|
||||
// extracted as a method for testing
|
||||
private[tcp] def rngConfig(config: Config) = {
|
||||
config.getString("random-number-generator")
|
||||
}
|
||||
|
||||
def createSecureRandom(config: Config, log: MarkerLoggingAdapter): SecureRandom = {
|
||||
createSecureRandom(rngConfig(config), log)
|
||||
}
|
||||
|
||||
def createSecureRandom(randomNumberGenerator: String, log: MarkerLoggingAdapter): SecureRandom = {
|
||||
val rng = randomNumberGenerator match {
|
||||
case s @ (GeneratorSha1Prng | GeneratorNativePrng) =>
|
||||
log.debug("SSL random number generator set to: {}", s)
|
||||
// SHA1PRNG needs /dev/urandom to be the source on Linux to prevent problems with /dev/random blocking
|
||||
// However, this also makes the seed source insecure as the seed is reused to avoid blocking (not a problem on FreeBSD).
|
||||
SecureRandom.getInstance(s)
|
||||
|
||||
case "" | GeneratorJdkSecureRandom =>
|
||||
log.debug("SSL random number generator set to [SecureRandom]")
|
||||
new SecureRandom
|
||||
|
||||
case unknown =>
|
||||
log.warning(
|
||||
LogMarker.Security,
|
||||
"Unknown SSL random number generator [{}] falling back to SecureRandom",
|
||||
unknown)
|
||||
new SecureRandom
|
||||
}
|
||||
rng.nextInt() // prevent stall on first access
|
||||
rng
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package akka.remote.artery.tcp.ssl
|
||||
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.File
|
||||
import java.nio.charset.Charset
|
||||
import java.nio.file.Files
|
||||
import java.security.KeyStore
|
||||
import java.security.PrivateKey
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
import akka.annotation.InternalApi
|
||||
import akka.pki.pem.DERPrivateKeyLoader
|
||||
import akka.pki.pem.PEMDecoder
|
||||
import javax.net.ssl.KeyManager
|
||||
import javax.net.ssl.KeyManagerFactory
|
||||
import javax.net.ssl.TrustManager
|
||||
import javax.net.ssl.TrustManagerFactory
|
||||
|
||||
import scala.concurrent.blocking
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
@InternalApi
|
||||
private[ssl] object PemManagersProvider {
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
@InternalApi
|
||||
private[ssl] def buildKeyManagers(
|
||||
privateKey: PrivateKey,
|
||||
cert: X509Certificate,
|
||||
cacert: Certificate): Array[KeyManager] = {
|
||||
val keyStore = KeyStore.getInstance("JKS")
|
||||
keyStore.load(null)
|
||||
|
||||
keyStore.setCertificateEntry("cert", cert)
|
||||
keyStore.setCertificateEntry("cacert", cacert)
|
||||
keyStore.setKeyEntry("private-key", privateKey, "changeit".toCharArray, Array(cert, cacert))
|
||||
|
||||
val kmf =
|
||||
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm)
|
||||
kmf.init(keyStore, "changeit".toCharArray)
|
||||
val keyManagers = kmf.getKeyManagers
|
||||
keyManagers
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
@InternalApi
|
||||
private[ssl] def buildTrustManagers(cacert: Certificate): Array[TrustManager] = {
|
||||
val trustStore = KeyStore.getInstance("JKS")
|
||||
trustStore.load(null)
|
||||
trustStore.setCertificateEntry("cacert", cacert)
|
||||
|
||||
val tmf =
|
||||
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm)
|
||||
tmf.init(trustStore)
|
||||
tmf.getTrustManagers
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
@InternalApi
|
||||
private[ssl] def loadPrivateKey(filename: String): PrivateKey = blocking {
|
||||
val bytes = Files.readAllBytes(new File(filename).toPath)
|
||||
val pemData = new String(bytes, Charset.forName("UTF-8"))
|
||||
DERPrivateKeyLoader.load(PEMDecoder.decode(pemData))
|
||||
}
|
||||
|
||||
private val certFactory = CertificateFactory.getInstance("X.509")
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
@InternalApi
|
||||
private[ssl] def loadCertificate(filename: String): Certificate = blocking {
|
||||
val bytes = Files.readAllBytes(new File(filename).toPath)
|
||||
certFactory.generateCertificate(new ByteArrayInputStream(bytes))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package akka.remote.artery.tcp.ssl
|
||||
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.security.GeneralSecurityException
|
||||
import java.security.PrivateKey
|
||||
import java.security.SecureRandom
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import akka.annotation.ApiMayChange
|
||||
import akka.annotation.InternalApi
|
||||
import akka.event.Logging
|
||||
import akka.event.MarkerLoggingAdapter
|
||||
import akka.remote.artery.tcp.SSLEngineProvider
|
||||
import akka.remote.artery.tcp.SecureRandomFactory
|
||||
import akka.remote.artery.tcp.SslTransportException
|
||||
import akka.remote.artery.tcp.ssl.RotatingKeysSSLEngineProvider.CachedContext
|
||||
import akka.remote.artery.tcp.ssl.RotatingKeysSSLEngineProvider.ConfiguredContext
|
||||
import akka.stream.TLSRole
|
||||
import com.typesafe.config.Config
|
||||
import javax.net.ssl.KeyManager
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.SSLEngine
|
||||
import javax.net.ssl.SSLSession
|
||||
import javax.net.ssl.TrustManager
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
/**
|
||||
* Variation on ConfigSSLEngineProvider that will periodically reload the keys and certificates
|
||||
* from disk, to facilitate rolling updates of certificates.
|
||||
*
|
||||
* This class is still ApiMayChange because it can likely be further harmonized with
|
||||
* the standard ConfigSSLEngineProvider. Also the location and default values of the
|
||||
* configuration may change in future versions of Akka.
|
||||
*
|
||||
* This provider does not perform hostname verification, but instead allows checking
|
||||
* that the remote certificate has a subject name that matches the subject name of
|
||||
* the configured certificate.
|
||||
*/
|
||||
@ApiMayChange
|
||||
final class RotatingKeysSSLEngineProvider(val config: Config, protected val log: MarkerLoggingAdapter)
|
||||
extends SSLEngineProvider {
|
||||
|
||||
def this(system: ActorSystem) =
|
||||
this(
|
||||
system.settings.config.getConfig("akka.remote.artery.ssl.rotating-keys-engine"),
|
||||
Logging.withMarker(system, classOf[RotatingKeysSSLEngineProvider].getName))
|
||||
|
||||
// read config
|
||||
|
||||
private val SSLKeyFile: String = config.getString("key-file")
|
||||
private val SSLCertFile: String = config.getString("cert-file")
|
||||
private val SSLCACertFile: String = config.getString("ca-cert-file")
|
||||
|
||||
private val sslEngineConfig = new SSLEngineConfig(config)
|
||||
import sslEngineConfig._
|
||||
|
||||
// build a PRNG (created once, reused on every instance of SSLContext
|
||||
private val rng: SecureRandom = SecureRandomFactory.createSecureRandom(SSLRandomNumberGenerator, log)
|
||||
|
||||
// handle caching
|
||||
@volatile private var cachedContext: Option[CachedContext] = None
|
||||
|
||||
/** INTERNAL API */
|
||||
@InternalApi
|
||||
private[ssl] def getSSLContext() = getContext().context
|
||||
private def getContext(): ConfiguredContext = {
|
||||
cachedContext match {
|
||||
case Some(CachedContext(_, expired)) if expired.isOverdue() =>
|
||||
// Multiple connection requests arriving when the cache is overdue will
|
||||
// create different CachedContext instances and only the last one will
|
||||
// be cached. This is fine.
|
||||
val context = constructContext()
|
||||
cachedContext = Some(CachedContext(context, SSLContextCacheTime.fromNow))
|
||||
context
|
||||
case Some(CachedContext(cached, _)) => cached
|
||||
case None =>
|
||||
// Multiple connection requests arriving when the cache is empty will
|
||||
// create different CachedContext instances. This is fine.
|
||||
val context = constructContext()
|
||||
cachedContext = Some(CachedContext(context, SSLContextCacheTime.fromNow))
|
||||
context
|
||||
}
|
||||
}
|
||||
|
||||
// Construct the cached instance
|
||||
private def constructContext(): ConfiguredContext = {
|
||||
val (privateKey, cert, cacert) = readFiles()
|
||||
try {
|
||||
val keyManagers: Array[KeyManager] = PemManagersProvider.buildKeyManagers(privateKey, cert, cacert)
|
||||
val trustManagers: Array[TrustManager] = PemManagersProvider.buildTrustManagers(cacert)
|
||||
|
||||
val sessionVerifier = new PeerSubjectVerifier(cert)
|
||||
|
||||
val ctx = SSLContext.getInstance(SSLProtocol)
|
||||
ctx.init(keyManagers, trustManagers, rng)
|
||||
ConfiguredContext(ctx, sessionVerifier)
|
||||
} catch {
|
||||
case e: GeneralSecurityException =>
|
||||
throw new SslTransportException(
|
||||
"Server SSL connection could not be established because SSL context could not be constructed",
|
||||
e)
|
||||
case e: IllegalArgumentException =>
|
||||
throw new SslTransportException("Server SSL connection could not be established because: " + e.getMessage, e)
|
||||
}
|
||||
}
|
||||
|
||||
private def readFiles(): (PrivateKey, X509Certificate, Certificate) = {
|
||||
try {
|
||||
val cacert: Certificate = PemManagersProvider.loadCertificate(SSLCACertFile)
|
||||
val cert: X509Certificate = PemManagersProvider.loadCertificate(SSLCertFile).asInstanceOf[X509Certificate]
|
||||
val privateKey: PrivateKey = PemManagersProvider.loadPrivateKey(SSLKeyFile)
|
||||
(privateKey, cert, cacert)
|
||||
} catch {
|
||||
case e: FileNotFoundException =>
|
||||
throw new SslTransportException(
|
||||
"Server SSL connection could not be established because a key or cert could not be loaded",
|
||||
e)
|
||||
case e: IOException =>
|
||||
throw new SslTransportException("Server SSL connection could not be established because: " + e.getMessage, e)
|
||||
}
|
||||
}
|
||||
|
||||
// Implement the SSLEngine create methods from the trait
|
||||
override def createServerSSLEngine(hostname: String, port: Int): SSLEngine =
|
||||
createSSLEngine(akka.stream.Server, hostname, port)(getContext().context)
|
||||
|
||||
override def createClientSSLEngine(hostname: String, port: Int): SSLEngine =
|
||||
createSSLEngine(akka.stream.Client, hostname, port)(getContext().context)
|
||||
|
||||
private def createSSLEngine(role: TLSRole, hostname: String, port: Int)(sslContext: SSLContext) = {
|
||||
|
||||
val engine = sslContext.createSSLEngine(hostname, port)
|
||||
|
||||
engine.setUseClientMode(role == akka.stream.Client)
|
||||
engine.setEnabledCipherSuites(SSLEnabledAlgorithms.toArray)
|
||||
engine.setEnabledProtocols(Array(SSLProtocol))
|
||||
|
||||
if (role != akka.stream.Client) engine.setNeedClientAuth(true)
|
||||
|
||||
engine
|
||||
}
|
||||
|
||||
// Implement the post-handshake verification methods from the trait
|
||||
override def verifyClientSession(hostname: String, session: SSLSession): Option[Throwable] =
|
||||
getContext().sessionVerifier.verifyClientSession(hostname, session)
|
||||
|
||||
override def verifyServerSession(hostname: String, session: SSLSession): Option[Throwable] =
|
||||
getContext().sessionVerifier.verifyServerSession(hostname, session)
|
||||
|
||||
}
|
||||
|
||||
object RotatingKeysSSLEngineProvider {
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
@InternalApi
|
||||
private case class CachedContext(cached: ConfiguredContext, expires: Deadline)
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
@InternalApi
|
||||
private case class ConfiguredContext(context: SSLContext, sessionVerifier: SessionVerifier)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package akka.remote.artery.tcp.ssl
|
||||
|
||||
import akka.annotation.InternalApi
|
||||
import akka.japi.Util.immutableSeq
|
||||
import com.typesafe.config.Config
|
||||
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
import scala.concurrent.duration._
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
@InternalApi
|
||||
private[tcp] class SSLEngineConfig(config: Config) {
|
||||
private[tcp] val SSLRandomNumberGenerator: String = config.getString("random-number-generator")
|
||||
|
||||
private[tcp] val SSLProtocol: String = config.getString("protocol")
|
||||
private[tcp] val SSLEnabledAlgorithms: Set[String] =
|
||||
immutableSeq(config.getStringList("enabled-algorithms")).toSet
|
||||
private[tcp] val SSLRequireMutualAuthentication: Boolean = {
|
||||
if (config.hasPath("require-mutual-authentication"))
|
||||
config.getBoolean("require-mutual-authentication")
|
||||
else
|
||||
false
|
||||
}
|
||||
private[tcp] val HostnameVerification: Boolean = {
|
||||
if (config.hasPath("hostname-verification"))
|
||||
config.getBoolean("hostname-verification")
|
||||
else
|
||||
false
|
||||
}
|
||||
private[tcp] val SSLContextCacheTime: FiniteDuration =
|
||||
if (config.hasPath("ssl-context-cache-ttl"))
|
||||
config.getDuration("ssl-context-cache-ttl").toMillis.millis
|
||||
else
|
||||
1024.days // a lot of days (not Inf, because `Inf` is not a FiniteDuration
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package akka.remote.artery.tcp.ssl
|
||||
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
import akka.annotation.InternalApi
|
||||
import javax.net.ssl.SSLSession
|
||||
|
||||
/**
|
||||
* Allows hooking in extra verification before finishing the SSL handshake.
|
||||
*
|
||||
* INTERNAL API
|
||||
*/
|
||||
@InternalApi
|
||||
private[ssl] trait SessionVerifier {
|
||||
def verifyClientSession(hostname: String, session: SSLSession): Option[Throwable]
|
||||
def verifyServerSession(hostname: String, session: SSLSession): Option[Throwable]
|
||||
}
|
||||
|
||||
/**
|
||||
* This verifier approves all sessions.
|
||||
*
|
||||
* INTERNAL API
|
||||
*/
|
||||
@InternalApi
|
||||
private[ssl] final object NoopSessionVerifier extends SessionVerifier {
|
||||
override def verifyClientSession(hostname: String, session: SSLSession): Option[Throwable] = None
|
||||
override def verifyServerSession(hostname: String, session: SSLSession): Option[Throwable] = None
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a TLS session verifier that checks the peer has a subject name that matches
|
||||
* the subject name of the given certificate. This can be useful to prevent accidentally
|
||||
* connecting with other nodes that have certificates that, while being signed by the
|
||||
* same certificate authority, belong to different clusters.
|
||||
*
|
||||
* INTERNAL API
|
||||
*/
|
||||
@InternalApi
|
||||
private[ssl] final class PeerSubjectVerifier(peerCertificate: X509Certificate) extends SessionVerifier {
|
||||
override def verifyClientSession(hostname: String, session: SSLSession): Option[Throwable] =
|
||||
verifyPeerCertificates(session)
|
||||
override def verifyServerSession(hostname: String, session: SSLSession): Option[Throwable] =
|
||||
verifyPeerCertificates(session)
|
||||
|
||||
private def verifyPeerCertificates(session: SSLSession) = {
|
||||
val mySubjectNames = X509Readers.getAllSubjectNames(peerCertificate)
|
||||
if (session.getPeerCertificates.length == 0) {
|
||||
Some(new IllegalArgumentException("No peer certificates"))
|
||||
}
|
||||
session.getPeerCertificates()(0) match {
|
||||
case x509: X509Certificate =>
|
||||
val peerSubjectNames =
|
||||
X509Readers.getAllSubjectNames(x509)
|
||||
if (mySubjectNames.exists(peerSubjectNames)) None
|
||||
else
|
||||
Some(
|
||||
new IllegalArgumentException(
|
||||
s"None of the peer subject names $peerSubjectNames were in local subject names $mySubjectNames"))
|
||||
case other =>
|
||||
Some(new IllegalArgumentException(s"Unknown certificate type: ${other.getClass}"))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package akka.remote.artery.tcp.ssl
|
||||
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util
|
||||
|
||||
import akka.annotation.InternalApi
|
||||
import javax.naming.ldap.LdapName
|
||||
import akka.util.ccompat.JavaConverters._
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
@InternalApi
|
||||
private[akka] object X509Readers {
|
||||
|
||||
def getAllSubjectNames(cert: X509Certificate): Set[String] = {
|
||||
val maybeCommonName =
|
||||
new LdapName(cert.getSubjectX500Principal.getName).getRdns.asScala.collectFirst {
|
||||
case attr if attr.getType.equalsIgnoreCase("CN") =>
|
||||
attr.getValue.toString
|
||||
}
|
||||
|
||||
val iterable: Iterable[util.List[_]] = Option(cert.getSubjectAlternativeNames).map(_.asScala).getOrElse(Nil)
|
||||
val alternates = iterable.collect {
|
||||
// See the javadocs of cert.getSubjectAlternativeNames for what this list contains,
|
||||
// first element should be an integer, if that integer is 2, then the second element
|
||||
// is a String containing the DNS name.
|
||||
case list if list.size() == 2 && list.get(0) == 2 =>
|
||||
list.get(1) match {
|
||||
case dnsName: String => dnsName
|
||||
case other =>
|
||||
throw new IllegalArgumentException(
|
||||
s"Error reading Subject Alternative Name, expected dns name to be a String, but instead got a ${other.getClass}")
|
||||
}
|
||||
}
|
||||
|
||||
maybeCommonName.toSet ++ alternates
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
# Documents how truststore and keystore were created
|
||||
|
||||
all: truststore keystore
|
||||
|
||||
truststore: domain.crt
|
||||
|
|
@ -6,12 +8,17 @@ truststore: domain.crt
|
|||
keystore: domain.crt domain.key
|
||||
openssl pkcs12 -export -inkey domain.key -passin pass:changeme -in domain.crt -out keystore -passout pass:changeme
|
||||
|
||||
domain.crt: domain.csr domain.key
|
||||
openssl x509 -req -in domain.csr -sha256 -extfile <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:localhost")) -out domain.crt -extensions SAN -signkey domain.key
|
||||
domain.crt: domain.csr domain.key domain.cnf
|
||||
openssl x509 -req -in domain.csr -sha256 -extfile domain.cnf -out domain.crt -extensions SAN -signkey domain.key
|
||||
|
||||
domain.csr:
|
||||
openssl req -new -sha256 -key domain.key -subj "/C=ZA/ST=web/O=Lightbend/CN=akka-remote" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:localhost")) -out domain.csr -passout pass:changeme
|
||||
domain.cnf:
|
||||
cat /etc/ssl/openssl.cnf > domain.cnf
|
||||
echo "[SAN]" >> domain.cnf
|
||||
echo "subjectAltName=DNS:localhost" >> domain.cnf
|
||||
|
||||
domain.csr: domain.cnf
|
||||
openssl req -new -newkey rsa:2048 -keyout domain.key -subj "/C=ZA/ST=web/O=Lightbend/CN=akka-remote" -reqexts SAN -config domain.cnf -out domain.csr -passout pass:changeme
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm domain.crt domain.csr keystore truststore
|
||||
rm domain.key domain.crt domain.csr keystore truststore domain.cnf
|
||||
|
|
|
|||
20
akka-remote/src/test/resources/domain.crt
Normal file
20
akka-remote/src/test/resources/domain.crt
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDMDCCAhigAwIBAgIULYjj2NGVQ1r1MzK9j03lmw/s9AAwDQYJKoZIhvcNAQEL
|
||||
BQAwRTELMAkGA1UEBhMCWkExDDAKBgNVBAgMA3dlYjESMBAGA1UECgwJTGlnaHRi
|
||||
ZW5kMRQwEgYDVQQDDAtha2thLXJlbW90ZTAeFw0yMDA2MDIxMTA1MDVaFw0yMDA3
|
||||
MDIxMTA1MDVaMEUxCzAJBgNVBAYTAlpBMQwwCgYDVQQIDAN3ZWIxEjAQBgNVBAoM
|
||||
CUxpZ2h0YmVuZDEUMBIGA1UEAwwLYWtrYS1yZW1vdGUwggEiMA0GCSqGSIb3DQEB
|
||||
AQUAA4IBDwAwggEKAoIBAQC8bahFbJFC31YWoyJGdOasMnZHE+D3jjnTmGS+E6Ev
|
||||
YxQ1zR+ja2CpDv1M5VjOS4qzVXBXm/2OM35Er6sE+cAdtO8qXq39hzhNoS5nvHIo
|
||||
hl881MHIbndohtMZm2NsKM2yHnqnFI4jMnlEK9d8gmn25PsqUwOC9g9h4HOp+qys
|
||||
mqDzMmyZSS3qotyximOPBIQcRan6xh9i3Zhi3VRIxMl9WNR1gU5sbOeO4G7xsKyY
|
||||
FjfEeVjyDOG1pYHpnBVtqTDJoNzs5jZIslzpZU/iCW1fF2r5VwCuwMj/fgSxz7Qv
|
||||
LXqw70QDKeDkebgaTmhqAtYbAT20JXwMCuiE+8Lo4hGbAgMBAAGjGDAWMBQGA1Ud
|
||||
EQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAm97sH2qjazMJV66X
|
||||
wJfxk72qHpZIXyzGIAcORcF8lxDOKaqO8q85cZa9uNhq+CtSOEN41KupBKVk4dfa
|
||||
ZZ7IWFqptXKsztQ6Ff+ruEX3ZeW3ZsZp72+PuauC6ClNmxZG4/bUA0uKKd8s5yPK
|
||||
jqJ3KR6ZuYykBvT2dQrHdI4LQPC4Sh+AZtfizTh21dYz4F1HPe/aBoDx0eAO6oyF
|
||||
S0V6Mm8d/ydCg5wS+s0NmNniia3sww2fud+PyR3AaaubSBKhThQg6pQhFiaxjKSz
|
||||
IMCg9Yicy8vem5w+HqOJqoyiPDSdxtInyeNKskxcB7ayOpcWj/TX/W379FWABxeo
|
||||
rt8eZw==
|
||||
-----END CERTIFICATE-----
|
||||
Binary file not shown.
7
akka-remote/src/test/resources/ssl/README.md
Normal file
7
akka-remote/src/test/resources/ssl/README.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Testing Resources
|
||||
|
||||
This folder contains a diverse set of keys, certificates and keystores generated using scripts inspired by
|
||||
those from https://github.com/playframework/play-samples/tree/2.8.x/play-scala-tls-example/scripts.
|
||||
|
||||
The resources in this folder are build with a few restrictions. See `TlsResourcesSpec.scala` for some
|
||||
tests asserting the certificates and keys in this folder fulfill the required restrictions.
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDgjCCAyagAwIBAgIEba7KPTAMBggqhkjOPQQDAgUAMH4xCzAJBgNVBAYTAlVT
|
||||
MRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRgw
|
||||
FgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxFDASBgNVBAsTC0V4YW1wbGUgT3JnMRIw
|
||||
EAYDVQQDEwlleGFtcGxlQ0EwHhcNMjAwNjAxMTU1MDI3WhcNMzAwNTMwMTU1MDI3
|
||||
WjCBjzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcT
|
||||
DVNhbiBGcmFuY2lzY28xGDAWBgNVBAoTD0V4YW1wbGUgQ29tcGFueTEUMBIGA1UE
|
||||
CxMLRXhhbXBsZSBPcmcxIzAhBgNVBAMTGmFydGVyeS1ub2RlMDAxLmV4YW1wbGUu
|
||||
Y29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjUrn0eQmYrrDg8gP
|
||||
CtDOQblPbjVgfKR0SzBUCCBfIZOWy+YYm51LldRSKwg644IkU0O2eTeEDzo4qVf9
|
||||
9RWPQ+C2SIdc40fv2AEQTKsmah+tfW3HFQsFsR6DBfFJAYbscHis1vKBl+xo2OZp
|
||||
YV2K21+UfI44Je2m7fLrPq0rKY9c5zXCATLNMQOeGV3yrtNqX4cTKSGH3nlDkY3L
|
||||
MpAeYP+agnCMt+wBejxgx44PtQaWOHNwa1A8DwfDB2WIk2dXr90lpODfTREmB5vj
|
||||
v+eBQ2gM2ObgIy8za2Kr8HQwAV+jtPIZLwAHftqBY5VrVWe3th3wMUEJPdhJ9Sqi
|
||||
3MGVwwIDAQABo4GyMIGvMB8GA1UdIwQYMBaAFNkRJB4AQDAmCIvHYS0AKD4/AqUU
|
||||
MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAOBgNVHQ8BAf8EBAMCBaAw
|
||||
PgYDVR0RBDcwNYIaYXJ0ZXJ5LW5vZGUwMDEuZXhhbXBsZS5jb22CF2FydGVyeS1u
|
||||
b2RlLmV4YW1wbGUuY29tMB0GA1UdDgQWBBSjukxB7Goxnv8D7rthlKtLV8/kCzAM
|
||||
BggqhkjOPQQDAgUAA0gAMEUCIQCNaZmqT7X/4iUUTXp01qoWHP6rLQnPV1gM7m4Z
|
||||
C0flhwIgW3xLzxKTEEusX1hOuAxvSXBkKRnKdu1/XeJ9xXj3DrI=
|
||||
-----END CERTIFICATE-----
|
||||
Binary file not shown.
|
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAjUrn0eQmYrrDg8gPCtDOQblPbjVgfKR0SzBUCCBfIZOWy+YY
|
||||
m51LldRSKwg644IkU0O2eTeEDzo4qVf99RWPQ+C2SIdc40fv2AEQTKsmah+tfW3H
|
||||
FQsFsR6DBfFJAYbscHis1vKBl+xo2OZpYV2K21+UfI44Je2m7fLrPq0rKY9c5zXC
|
||||
ATLNMQOeGV3yrtNqX4cTKSGH3nlDkY3LMpAeYP+agnCMt+wBejxgx44PtQaWOHNw
|
||||
a1A8DwfDB2WIk2dXr90lpODfTREmB5vjv+eBQ2gM2ObgIy8za2Kr8HQwAV+jtPIZ
|
||||
LwAHftqBY5VrVWe3th3wMUEJPdhJ9Sqi3MGVwwIDAQABAoIBABSX5lHhPvcE4ZpD
|
||||
fm3FIUrTB5C4lueT0J9k29qTXUo3iLMPRmbn9ixQVemPuYWPYlwAcogYX1cY1UlZ
|
||||
Wdpu2gK6rdbEY/V8dqi0/vstttug4lh1t56JjLrUB6TGFi3yzGNYM1jg36KVAnMa
|
||||
oiFe0O5IcAv3bpeYm8vyq/bmpnPYCUwOAyTGJ6JBUs98YEQu5qGdWVYGk20G346m
|
||||
AW37bAeIE9oTMHD06aXZRBtaFQjvCeCg+aM9FhTo6hOD4mTADeV2qfjUPrhE3jal
|
||||
c6Ul84m0RG5A2M7isBK/JmQ0hZ9uJIEGBSWdAG6g0sDLN02pdNtTWG0nuA+XHolV
|
||||
9KznWYECgYEA3Y11PmyiCpZyHLN3y30Gw62aQ+MW41XVNUcb3Dn42UkKbhgnoFbI
|
||||
rNhZp+jAJapIZTTB3xfE3dwvPlYHHfwwTxcmg20cx0CpNlpqCigVGQqe2FxhWRq8
|
||||
ZR9YHS5mseOmmdJYdfG58TsSmjveEmgbC+Q+1roBVwNNpfIeyYUg43cCgYEAo0LU
|
||||
QsFXZVOTFb6vRaAGrfiSd0bmzFEMWS9H6G6ZJUhNodoECCQaeF0ypJermVg/nfD2
|
||||
wobSTP4hA7dtRDHzNecQRS3zfVdaCmIN+50jpfrT+av6Z07k5bxpFHUAyzI0bj/M
|
||||
gDGjZ9seDo/4vQfdiqxYWjPr8DXYvANazbARuxUCgYAH83uxsdRe7OdLgGVcODB1
|
||||
9VUD+rJnlj0AnHyzeqEjqytkqBlD99lb2qfdDs1WjLXsa+hJSWEXVT+czRmUSeix
|
||||
7fLD5LaTsA5ilPwZQTcAnxD0UtxrhjocpvNSmMe2uqTQAGyMTxCNR8FzJ5LgtjvC
|
||||
QX6/1g0WQlgXDIluUgjMIQKBgQCPQoFH4qhx/Zg/qIfcrMOvvUOo7spv117ik56h
|
||||
0wsHsB6PO+P10Nh5bi6WR5EIimuoiF2/7NZ1QTpvLHHxOXOVhSC908ip4BDes5RZ
|
||||
ilZRu3xuxf6A0LYC8gWzMch0haWEaO9mPiiJZblGRgeauGAq43jUDmOm8VkyAi+X
|
||||
9jxY0QKBgQDTJHTw1KFklidDFQ1v9V+uORLmyVP4Ms/TPMNJsuTqc3lnBSU9Zxfz
|
||||
Vw20aBMZdfno1z5Uvirg++Zt1B7hVUj+MB2VslV6HqLjj3fL96xXrYPsjMYtwTOD
|
||||
//jalt23mVJorMXpKBMfgEUPyw3Ca4R0mL/D4miv88XXZLlUJwwUyA==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDgTCCAyagAwIBAgIEOqBQVjAMBggqhkjOPQQDAgUAMH4xCzAJBgNVBAYTAlVT
|
||||
MRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRgw
|
||||
FgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxFDASBgNVBAsTC0V4YW1wbGUgT3JnMRIw
|
||||
EAYDVQQDEwlleGFtcGxlQ0EwHhcNMjAwNjAxMTU1MDMwWhcNMzAwNTMwMTU1MDMw
|
||||
WjCBjzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcT
|
||||
DVNhbiBGcmFuY2lzY28xGDAWBgNVBAoTD0V4YW1wbGUgQ29tcGFueTEUMBIGA1UE
|
||||
CxMLRXhhbXBsZSBPcmcxIzAhBgNVBAMTGmFydGVyeS1ub2RlMDAyLmV4YW1wbGUu
|
||||
Y29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3pjLzJbVB78E4A1D
|
||||
7AU4mn4XV8aWZHHRfyPf2yL62WtqLLktzdB27c/QdgV8G6oKW50MU4HmyaZqvxK1
|
||||
RD4Q0QSVGy/Dz8MFxJz3k38YWFICqyBYNQcU4NsFMwO/yguQHY21Oyxk4liWtA1p
|
||||
U9ZYn51Tozd7jFtzbYVKC4PucjuV26nb6ufLZZtVZYw+nuz1Uyv4LychKXSZHSqB
|
||||
XSH9rj4piOyO7t7IPdQqYLDFS1OBkBhk3q/YnfzYBHZjkYApLO1t9Jf4S74mtJ2Q
|
||||
TpISxIdpKzizXcr9OfVybeoCOwQXeNNs74zX5qa5nd1ZuqC42qBsNFUeSwqcJv2z
|
||||
HhBScwIDAQABo4GyMIGvMB8GA1UdIwQYMBaAFNkRJB4AQDAmCIvHYS0AKD4/AqUU
|
||||
MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAOBgNVHQ8BAf8EBAMCBaAw
|
||||
PgYDVR0RBDcwNYIaYXJ0ZXJ5LW5vZGUwMDIuZXhhbXBsZS5jb22CF2FydGVyeS1u
|
||||
b2RlLmV4YW1wbGUuY29tMB0GA1UdDgQWBBQgG8h6MjX1T0N/vGI+YYqi27oivDAM
|
||||
BggqhkjOPQQDAgUAA0cAMEQCIBJGrfxFjZFHIr0ZeBlYJylly9CypDJnFOgxEZpY
|
||||
B0ETAiABn94e6yDwRMVOdC3UgnMq8E94LfqJU+tduEPH30qB3Q==
|
||||
-----END CERTIFICATE-----
|
||||
Binary file not shown.
|
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA3pjLzJbVB78E4A1D7AU4mn4XV8aWZHHRfyPf2yL62WtqLLkt
|
||||
zdB27c/QdgV8G6oKW50MU4HmyaZqvxK1RD4Q0QSVGy/Dz8MFxJz3k38YWFICqyBY
|
||||
NQcU4NsFMwO/yguQHY21Oyxk4liWtA1pU9ZYn51Tozd7jFtzbYVKC4PucjuV26nb
|
||||
6ufLZZtVZYw+nuz1Uyv4LychKXSZHSqBXSH9rj4piOyO7t7IPdQqYLDFS1OBkBhk
|
||||
3q/YnfzYBHZjkYApLO1t9Jf4S74mtJ2QTpISxIdpKzizXcr9OfVybeoCOwQXeNNs
|
||||
74zX5qa5nd1ZuqC42qBsNFUeSwqcJv2zHhBScwIDAQABAoIBAHdr0iq44SF+wcbS
|
||||
VxZQ1sVL0Iou6JCK37IuNPMEGUB7+EJ2NrSsmqGLVHN9DdBpsZTk9K/4iTC2L57D
|
||||
EqzB/5OjubsULSFRp86Lx+dB0HXRycy1VZ1dZz4bQvbTlBl5ip/QXuKYNqeYj4GZ
|
||||
kGCCJpm7dhuisI3kolCnqcnzxgFSJhgcQHYZkpJH09CZNxUtF7AIWtMRkj2IIKjV
|
||||
/MzQDJz8EINBQzWIDTviu0Qkd7V2dStJ/GfnFkUeRgxHQUJtXM6p5vRIxObzd+fi
|
||||
HRYF8c2t3HQcnVlqjRWeHPtdh9jKKzETCVABTc8WzLu7DyONnGgE3w3DoMAJKHVL
|
||||
77ziqEECgYEA9kSHfagEviqTd1aYkhD1T9Q1FS04SefdQc2RzvF3FIVDNyv45fxk
|
||||
tbC89X0MoOfdbQbwPoY4p2unjV9tNjwp7WqDnGHSDz56qxbuJEaclq4gPDIciXAM
|
||||
Qi4PvSp1YZ+3na2j5bDomPmjo54s3mh5nitq0Jw52MQGRBebx8ynyXcCgYEA52TK
|
||||
d8G8fJKeRxOTN7iEKds6m77MnFDVvONwlM9pQUCuqPm+FT0vb8cMNDLNWouMmmCB
|
||||
gSP1bc1bNbcN2teH+5lbn5wYQ7AQaF6lbOdgUBV5N49S6AGbvAkT/w2O0Mo7YU5Q
|
||||
mIWvYxhQpPquevtFOAAbSVQrebDiJhg0Rjy2feUCgYEA3YUBN3eWZJSZt4Quk10r
|
||||
vJYO9bCKbHhjnxhR6wtq6QuCTbOBHSduU7zaDBxi6q4GkFbobeWAOqDsw48uBtYR
|
||||
hN0F6/pV4J5760AiEIFvw1534o3U+4/Nhw413BvAIINxwCT8Q9VhNJGBr+DNTXY+
|
||||
x5cYavPMWP7jAAcYep3N47sCgYB7evjk0XkhTSizq0mLmabFo1zyUe5kmGqHAyRH
|
||||
9SspDDhoqeV69gzDbIghrt6RLBkbJNbXMHY/YzACSS5Wk1/Yru0LDsSQEnufBqrm
|
||||
o85szhjCwnQupPUTchC+seB9oP3xHla6HdULX6VhdPj5Xe+BQ+VLy2Pr662zQIVc
|
||||
2fdU1QKBgQCWCPSc8byqtYsIg3b59iyTYbR3rW49ZUHNNjm3fKIXkEpHZQSg9/v2
|
||||
aNAUpVqm9k/Tt0OJ1+OceDRBlBgVPFIJRuqJ37V9pmicsjVN2biqc+2RQtdpgzcl
|
||||
lkz3cnp1Jdg9tGDmnip5XplLcjh3gaxMYNsQRAmHE0j19ck6Rcx2rw==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDgTCCAyagAwIBAgIEPjDg6zAMBggqhkjOPQQDAgUAMH4xCzAJBgNVBAYTAlVT
|
||||
MRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRgw
|
||||
FgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxFDASBgNVBAsTC0V4YW1wbGUgT3JnMRIw
|
||||
EAYDVQQDEwlleGFtcGxlQ0EwHhcNMjAwNjAxMTU1MDMyWhcNMzAwNTMwMTU1MDMy
|
||||
WjCBjzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcT
|
||||
DVNhbiBGcmFuY2lzY28xGDAWBgNVBAoTD0V4YW1wbGUgQ29tcGFueTEUMBIGA1UE
|
||||
CxMLRXhhbXBsZSBPcmcxIzAhBgNVBAMTGmFydGVyeS1ub2RlMDAzLmV4YW1wbGUu
|
||||
Y29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA24C7726icyAGM/Vp
|
||||
baY79OaRUihFm6H4iYDuhdZ66QqBOOTNqLXZnetddJ55mS+LndoSxNYNNhD7XiyI
|
||||
Orcn1ETtzfZmtjJVYb2JB0lHj3awTYN4Lgchny+GGTjb+mEihyGv/6Csu/mMcrxS
|
||||
PZDcwy2pHO4TJ8lQ0yg9SDFneWxzDTOxIn15cSHSiexunL6s8wzw9WzjR6kNF7Ig
|
||||
qHWK/cLabvsZr7cLuNmaWBNpGyaAxqXbOhrhBmDxaY1mCtRSKzPCPGAM83F1mtEa
|
||||
mX64W7WEEw/h71q/u32o9qgmhQqodbZqt5So2RFpU3MVPuYfQQPdTeqkpcwPsDiE
|
||||
NqfdQQIDAQABo4GyMIGvMB8GA1UdIwQYMBaAFNkRJB4AQDAmCIvHYS0AKD4/AqUU
|
||||
MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAOBgNVHQ8BAf8EBAMCBaAw
|
||||
PgYDVR0RBDcwNYIaYXJ0ZXJ5LW5vZGUwMDMuZXhhbXBsZS5jb22CF2FydGVyeS1u
|
||||
b2RlLmV4YW1wbGUuY29tMB0GA1UdDgQWBBSx8rKQxZJ9SjW3ghqxtwZuNMhwhTAM
|
||||
BggqhkjOPQQDAgUAA0cAMEQCIDr0T5OrW7BRXFWosw8clK6jVafAJHJSmysM7iV5
|
||||
4FDIAiBGdRE1iTqcvA1ya3H89gvqAOn2K41RuYf3xSR1tU1zhw==
|
||||
-----END CERTIFICATE-----
|
||||
Binary file not shown.
|
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEA24C7726icyAGM/VpbaY79OaRUihFm6H4iYDuhdZ66QqBOOTN
|
||||
qLXZnetddJ55mS+LndoSxNYNNhD7XiyIOrcn1ETtzfZmtjJVYb2JB0lHj3awTYN4
|
||||
Lgchny+GGTjb+mEihyGv/6Csu/mMcrxSPZDcwy2pHO4TJ8lQ0yg9SDFneWxzDTOx
|
||||
In15cSHSiexunL6s8wzw9WzjR6kNF7IgqHWK/cLabvsZr7cLuNmaWBNpGyaAxqXb
|
||||
OhrhBmDxaY1mCtRSKzPCPGAM83F1mtEamX64W7WEEw/h71q/u32o9qgmhQqodbZq
|
||||
t5So2RFpU3MVPuYfQQPdTeqkpcwPsDiENqfdQQIDAQABAoIBAGKjBNDhPGrTdzYe
|
||||
D9RQIR06Bw+OPUlkjZTstUK7UNwr9kmkt64amcHXJFXlaOsnbGvwtQJy1dj35J07
|
||||
EbSg3WsL1nj5QsqY77lOPKdjjJ6xTSRn8bdtSPSJnI70+BUZVTS4NKiAgV6vEyfz
|
||||
7FjyIeIrQJVZfo4gbwuUR4WLfd4XwratfkHEi7otHbTpnkU3O7TbV1I6EyqUQtZx
|
||||
2xhThAV4utTyKJFyYgaqzTQZ5djIk2a93eyaoz4tnouUIKYzG1XWyz5t82UD+ViH
|
||||
72rHDJGNy39Wok+lOVQ3WpJYJJkdJcf5vfuF4mQ+JdIhRpYsh+q2y68obaYOONPU
|
||||
sMF6SAECgYEA8wjOxj61aviIFSrHhrZI01u00BfAhil7DcZjarKjykxMSXS3uXsb
|
||||
1OoScieHXaQWs1xKXuiS3naiRi0CmdVTs9CLAlddzTK9p2e8Uc9H8Ae5U5yugKdD
|
||||
3ekpbXOih+iHQWYP/9nPN9VEb/q3aMy7iRKYgELC68m6Tp03bZnoeuECgYEA5zaM
|
||||
nh9NVl7ur5FBN2V7fgEORg6Z3va4RLs7DUkXELWp9HWsbC/9rBSboATzyK6NYCMD
|
||||
mL1Q3WbdPldhH3b+zkt7CLGoyQluVIyspXBuhpsj8MmImsFbSQJBMEiQ5zgh2Gd3
|
||||
xdVFXS1P9H5hXhy1GGePGrf7gQfqnKszQer5DmECgYAHnAr9YhFEHCwGnaRJr4Nw
|
||||
OrramSPKD5puv/t058sBFop88k6eXCBu9jVFpb8zS2P6kbUya43NsWE7WUVvk6Jf
|
||||
SvRPSnUBa8lMaI8Y8KiL93HyEEHWfWY+mIJXjvtTzhAOGCgAFs3KLb9K0krT2TU2
|
||||
AYMM4QpBX7uZooqNv/frgQKBgQDi4XdIriSgjVUgOKPLLSzp7zVHb4pz7JvS7fq7
|
||||
Ra55ehnExTeljc4ZbrtrYZCqqwYVgSZFWfgg2ZBeXTXzvzu3yP94/4RFiZiXJNdB
|
||||
HDuIoHG7FLeUTAo8cRbwvzRZf45OoPE50tZW4WDk5KK8y+S0huI48LK94bvJcoFA
|
||||
vMcZ4QKBgFbrlcgi7XcjfKo9+JzXx7Q8p7gC1j1qNevq9qpkGDgAn5Txdi1PFqNM
|
||||
g39aHjiaYA2+GOB61MEGelaZkhtwCl/4cpCV9uVodhhxTUb3bQLM6KuvuB5gW7Qi
|
||||
yGPxI9HnFuakK0Cs250rdQMoTriXRwK+NYahDqmvOqRihZdbS6E2
|
||||
-----END RSA PRIVATE KEY-----
|
||||
16
akka-remote/src/test/resources/ssl/client.example.com.crt
Normal file
16
akka-remote/src/test/resources/ssl/client.example.com.crt
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICkDCCAjWgAwIBAgIEOro4SzAMBggqhkjOPQQDAgUAMH4xCzAJBgNVBAYTAlVT
|
||||
MRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRgw
|
||||
FgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxFDASBgNVBAsTC0V4YW1wbGUgT3JnMRIw
|
||||
EAYDVQQDEwlleGFtcGxlQ0EwHhcNMjAwNjAxMTU1MDIwWhcNMzAwNTMwMTU1MDIw
|
||||
WjCBhzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcT
|
||||
DVNhbiBGcmFuY2lzY28xGDAWBgNVBAoTD0V4YW1wbGUgQ29tcGFueTEUMBIGA1UE
|
||||
CxMLRXhhbXBsZSBPcmcxGzAZBgNVBAMTEmNsaWVudC5leGFtcGxlLmNvbTBZMBMG
|
||||
ByqGSM49AgEGCCqGSM49AwEHA0IABAaveLe/GP5gV9905DmI6wCOUrEcK0TCnbUo
|
||||
4uHCl5lRX6m3v6pY/CyvK/UDLu30qfBvsJhWD5Ggr28wQZYyfOyjgZQwgZEwHwYD
|
||||
VR0jBBgwFoAU2REkHgBAMCYIi8dhLQAoPj8CpRQwEwYDVR0lBAwwCgYIKwYBBQUH
|
||||
AwIwDgYDVR0PAQH/BAQDAgWgMCoGA1UdEQQjMCGCEmNsaWVudC5leGFtcGxlLmNv
|
||||
bYILZXhhbXBsZS5jb20wHQYDVR0OBBYEFOMNZuBCz5J+ar98ruHnFkQAekAQMAwG
|
||||
CCqGSM49BAMCBQADRwAwRAIgfrnQChNQsf2ft/zA4ln5pUzdy3mGmp0eCD4lBN64
|
||||
pVQCIB5ARpTUKetQmj7YkU7etM02TPVpKjAslxmYuMQEpbrt
|
||||
-----END CERTIFICATE-----
|
||||
BIN
akka-remote/src/test/resources/ssl/client.example.com.p12
Normal file
BIN
akka-remote/src/test/resources/ssl/client.example.com.p12
Normal file
Binary file not shown.
14
akka-remote/src/test/resources/ssl/exampleca.crt
Normal file
14
akka-remote/src/test/resources/ssl/exampleca.crt
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICNDCCAdigAwIBAgIEKYhR+DAMBggqhkjOPQQDAgUAMH4xCzAJBgNVBAYTAlVT
|
||||
MRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRgw
|
||||
FgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxFDASBgNVBAsTC0V4YW1wbGUgT3JnMRIw
|
||||
EAYDVQQDEwlleGFtcGxlQ0EwHhcNMjAwNjAxMTU1MDEzWhcNNDcxMDE3MTU1MDEz
|
||||
WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMN
|
||||
U2FuIEZyYW5jaXNjbzEYMBYGA1UEChMPRXhhbXBsZSBDb21wYW55MRQwEgYDVQQL
|
||||
EwtFeGFtcGxlIE9yZzESMBAGA1UEAxMJZXhhbXBsZUNBMFkwEwYHKoZIzj0CAQYI
|
||||
KoZIzj0DAQcDQgAE9K81J2BKaFGTI/4qgUr31mr3Jpl6YK+7pCmy2LNiW0Jufova
|
||||
p41ERTrT9IynJF3NlJpzuKhmuA+80ZM5NSR4QqNCMEAwDwYDVR0TAQH/BAUwAwEB
|
||||
/zAOBgNVHQ8BAf8EBAMCAgQwHQYDVR0OBBYEFNkRJB4AQDAmCIvHYS0AKD4/AqUU
|
||||
MAwGCCqGSM49BAMCBQADSAAwRQIhAP2Uf206YbhJ2pb5MvehuLanJzFydOAq5uC4
|
||||
AXmspW+EAiAuPLFIAPyB+6gpr/mQ8V+yyh3aCJV3bvFq8aorrQyC3Q==
|
||||
-----END CERTIFICATE-----
|
||||
BIN
akka-remote/src/test/resources/ssl/exampleca.p12
Normal file
BIN
akka-remote/src/test/resources/ssl/exampleca.p12
Normal file
Binary file not shown.
21
akka-remote/src/test/resources/ssl/gen-artery-nodes.example.com.sh
Executable file
21
akka-remote/src/test/resources/ssl/gen-artery-nodes.example.com.sh
Executable file
|
|
@ -0,0 +1,21 @@
|
|||
#!/bin/bash
|
||||
|
||||
export PW=`cat password`
|
||||
|
||||
. gen-functions.sh
|
||||
|
||||
|
||||
function createArteryNodeKeySet() {
|
||||
PREFIX=$1
|
||||
createExampleRSAKeySet $PREFIX "serverAuth,clientAuth" "DNS:$PREFIX.example.com,DNS:artery-node.example.com"
|
||||
}
|
||||
|
||||
rm -rf artery-nodes
|
||||
|
||||
## Then create array few artery-nodes (in ./ssl/artery-nodes folder)
|
||||
createArteryNodeKeySet "artery-node001"
|
||||
createArteryNodeKeySet "artery-node002"
|
||||
createArteryNodeKeySet "artery-node003"
|
||||
|
||||
mkdir -p artery-nodes
|
||||
mv artery-node00* artery-nodes/.
|
||||
102
akka-remote/src/test/resources/ssl/gen-functions.sh
Normal file
102
akka-remote/src/test/resources/ssl/gen-functions.sh
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
#!/bin/bash
|
||||
|
||||
function createKeySet() {
|
||||
PREFIX=$1
|
||||
KEYALG=$2
|
||||
KEYLENGTH=$3
|
||||
KeyUsage=$4
|
||||
ExtendedKeyUsage=$5
|
||||
SubjectAlternativeNames=$6
|
||||
|
||||
# Create a certificate used for peer-to-peer, under the example.com CA
|
||||
keytool -genkeypair -v \
|
||||
-alias $PREFIX.example.com \
|
||||
-dname "CN=$PREFIX.example.com, OU=Example Org, O=Example Company, L=San Francisco, ST=California, C=US" \
|
||||
-keystore $PREFIX.example.com.p12 \
|
||||
-storetype PKCS12 \
|
||||
-keypass:env PW \
|
||||
-storepass:env PW \
|
||||
-storetype PKCS12 \
|
||||
-keyalg $KEYALG \
|
||||
-keysize $KEYLENGTH \
|
||||
-validity 3650
|
||||
|
||||
# Create a certificate signing request for $PREFIX.example.com
|
||||
keytool -certreq -v \
|
||||
-alias $PREFIX.example.com \
|
||||
-keypass:env PW \
|
||||
-storepass:env PW \
|
||||
-keystore $PREFIX.example.com.p12 \
|
||||
-file $PREFIX.example.com.csr
|
||||
|
||||
# Tell exampleCA to sign the certificate.
|
||||
keytool -gencert -v \
|
||||
-alias exampleca \
|
||||
-keypass:env PW \
|
||||
-storepass:env PW \
|
||||
-keystore exampleca.p12 \
|
||||
-infile $PREFIX.example.com.csr \
|
||||
-outfile $PREFIX.example.com.crt \
|
||||
-ext KeyUsage:critical=$4 \
|
||||
-ext EKU=$5 \
|
||||
-ext SAN=$6 \
|
||||
-rfc \
|
||||
-validity 3650
|
||||
|
||||
rm $PREFIX.example.com.csr
|
||||
|
||||
# Tell $PREFIX.example.com.p12 it can trust exampleca as a signer.
|
||||
keytool -import -v \
|
||||
-alias exampleca \
|
||||
-file exampleca.crt \
|
||||
-keystore $PREFIX.example.com.p12 \
|
||||
-storetype PKCS12 \
|
||||
-storepass:env PW << EOF
|
||||
yes
|
||||
EOF
|
||||
|
||||
# Import the signed certificate back into $PREFIX.example.com.p12
|
||||
keytool -import -v \
|
||||
-alias $PREFIX.example.com \
|
||||
-file $PREFIX.example.com.crt \
|
||||
-keystore $PREFIX.example.com.p12 \
|
||||
-storetype PKCS12 \
|
||||
-storepass:env PW
|
||||
|
||||
}
|
||||
|
||||
function exportRSAPrivateKeyAsPem {
|
||||
PREFIX=$1
|
||||
# Export the private key as a PEM. This export adds some extra PKCS#12 bag info.
|
||||
openssl pkcs12 -in $PREFIX.example.com.p12 -nodes -nocerts -out tmp.pem -passin pass:kLnCu3rboe
|
||||
# A second pass extracts the PKCS#12 bag information and produces a clean PEM file.
|
||||
openssl rsa -in tmp.pem -out $PREFIX.example.com.pem
|
||||
rm tmp.pem
|
||||
}
|
||||
|
||||
function createExampleRSAKeySet() {
|
||||
PREFIX=$1
|
||||
EKU=$2
|
||||
SAN=$3
|
||||
createKeySet $PREFIX \
|
||||
RSA \
|
||||
2048 \
|
||||
"digitalSignature,keyEncipherment" \
|
||||
$EKU \
|
||||
$SAN
|
||||
exportRSAPrivateKeyAsPem $PREFIX
|
||||
}
|
||||
|
||||
|
||||
function createExampleECKeySet() {
|
||||
PREFIX=$1
|
||||
EKU=$2
|
||||
SAN=$3
|
||||
createKeySet $PREFIX \
|
||||
EC \
|
||||
256 \
|
||||
"digitalSignature,keyEncipherment" \
|
||||
$EKU \
|
||||
$SAN
|
||||
}
|
||||
|
||||
27
akka-remote/src/test/resources/ssl/genca.sh
Executable file
27
akka-remote/src/test/resources/ssl/genca.sh
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
#!/bin/bash
|
||||
|
||||
export PW=`cat password`
|
||||
|
||||
# Create a self signed key pair root CA certificate.
|
||||
keytool -genkeypair -v \
|
||||
-alias exampleca \
|
||||
-dname "CN=exampleCA, OU=Example Org, O=Example Company, L=San Francisco, ST=California, C=US" \
|
||||
-keystore exampleca.p12 \
|
||||
-storetype PKCS12 \
|
||||
-keypass:env PW \
|
||||
-storepass:env PW \
|
||||
-keyalg EC \
|
||||
-keysize 256 \
|
||||
-ext KeyUsage:critical="keyCertSign" \
|
||||
-ext BasicConstraints:critical="ca:true" \
|
||||
-validity 9999
|
||||
|
||||
# Export the exampleCA public certificate
|
||||
keytool -export -v \
|
||||
-alias exampleca \
|
||||
-file exampleca.crt \
|
||||
-keypass:env PW \
|
||||
-storepass:env PW \
|
||||
-keystore exampleca.p12 \
|
||||
-storetype PKCS12 \
|
||||
-rfc
|
||||
27
akka-remote/src/test/resources/ssl/gencerts.sh
Executable file
27
akka-remote/src/test/resources/ssl/gencerts.sh
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
#!/bin/bash
|
||||
|
||||
export PW=`cat password`
|
||||
|
||||
. gen-functions.sh
|
||||
|
||||
rm *.crt
|
||||
rm *.p12
|
||||
rm *.pem
|
||||
|
||||
./genca.sh
|
||||
|
||||
## some server certificates
|
||||
createExampleECKeySet "one" "serverAuth" "DNS:one.example.com,DNS:example.com"
|
||||
createExampleECKeySet "two" "serverAuth" "DNS:two.example.com,DNS:example.com"
|
||||
createExampleECKeySet "island" "serverAuth" "DNS:island.example.com"
|
||||
|
||||
## a client certificate
|
||||
createExampleECKeySet "client" "clientAuth" "DNS:client.example.com,DNS:example.com"
|
||||
|
||||
## node.example.com is part of the example.com dataset (in ./ssl/ folder) but not the artery-nodes
|
||||
createExampleRSAKeySet "node" "serverAuth,clientAuth" "DNS:node.example.com,DNS:example.com"
|
||||
createExampleRSAKeySet "rsa-client" "clientAuth" "DNS:rsa-client.example.com,DNS:example.com"
|
||||
|
||||
## a certificate valid for both server and client (peer-to-peer)
|
||||
## with RSA keys
|
||||
./gen-artery-nodes.example.com.sh
|
||||
16
akka-remote/src/test/resources/ssl/island.example.com.crt
Normal file
16
akka-remote/src/test/resources/ssl/island.example.com.crt
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIChDCCAiigAwIBAgIEJnvhJzAMBggqhkjOPQQDAgUAMH4xCzAJBgNVBAYTAlVT
|
||||
MRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRgw
|
||||
FgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxFDASBgNVBAsTC0V4YW1wbGUgT3JnMRIw
|
||||
EAYDVQQDEwlleGFtcGxlQ0EwHhcNMjAwNjAxMTU1MDE4WhcNMzAwNTMwMTU1MDE4
|
||||
WjCBhzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcT
|
||||
DVNhbiBGcmFuY2lzY28xGDAWBgNVBAoTD0V4YW1wbGUgQ29tcGFueTEUMBIGA1UE
|
||||
CxMLRXhhbXBsZSBPcmcxGzAZBgNVBAMTEmlzbGFuZC5leGFtcGxlLmNvbTBZMBMG
|
||||
ByqGSM49AgEGCCqGSM49AwEHA0IABIkVH3HvRjaESvuOGGT/fusdHKWMLIPL9bW8
|
||||
xY/Xn0R8vfSd11u4DuXAJyXHXoh9nr446gZEnkIJChp8UiR3F6SjgYcwgYQwHwYD
|
||||
VR0jBBgwFoAU2REkHgBAMCYIi8dhLQAoPj8CpRQwEwYDVR0lBAwwCgYIKwYBBQUH
|
||||
AwEwDgYDVR0PAQH/BAQDAgWgMB0GA1UdEQQWMBSCEmlzbGFuZC5leGFtcGxlLmNv
|
||||
bTAdBgNVHQ4EFgQU18dwseHFcZuh+t7OL5S4qArmonEwDAYIKoZIzj0EAwIFAANI
|
||||
ADBFAiAG9sYgiBMx8burgPmM7miEdIxVRWSMcoHFYpt7GP9JyAIhAMu0VjMAsvXj
|
||||
P5ToFhlgEza2dL6spd9rerToqevTBvOm
|
||||
-----END CERTIFICATE-----
|
||||
BIN
akka-remote/src/test/resources/ssl/island.example.com.p12
Normal file
BIN
akka-remote/src/test/resources/ssl/island.example.com.p12
Normal file
Binary file not shown.
21
akka-remote/src/test/resources/ssl/node.example.com.crt
Normal file
21
akka-remote/src/test/resources/ssl/node.example.com.crt
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDYjCCAwagAwIBAgIEfzq/RjAMBggqhkjOPQQDAgUAMH4xCzAJBgNVBAYTAlVT
|
||||
MRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRgw
|
||||
FgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxFDASBgNVBAsTC0V4YW1wbGUgT3JnMRIw
|
||||
EAYDVQQDEwlleGFtcGxlQ0EwHhcNMjAwNjAxMTU1MDIzWhcNMzAwNTMwMTU1MDIz
|
||||
WjCBhTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcT
|
||||
DVNhbiBGcmFuY2lzY28xGDAWBgNVBAoTD0V4YW1wbGUgQ29tcGFueTEUMBIGA1UE
|
||||
CxMLRXhhbXBsZSBPcmcxGTAXBgNVBAMTEG5vZGUuZXhhbXBsZS5jb20wggEiMA0G
|
||||
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCt1BnrLQGu6QaqTYilJufamlpVqW43
|
||||
fjibKUA62FRzoD56rTST5fnn3XK0PHWcr6DyVIEFUuz22DsnXEJazy0Edt2AwKsu
|
||||
aP5An+1zMBBYu9mvYEFXTu1VczFIN5PzYIb8pIWHsuchAhHYVFZmUAWbSCvR8hns
|
||||
VSW1n/bLxnXIfvnrmv5hHMsPgoEdG9gw5V7OA4wDAWwc5nb8fC61E/CSSPnKoWN7
|
||||
Vm8f1laTfh/mKOViO66dKy1zeYXvfgt7BoolfmibG8zkrxqeDQRmedzJYyobbVV6
|
||||
YHFjsAAtqVqmSj6sLfgKUGq6Oq/0+NJqKp8QhEBdPIwyekh+ijpDGirZAgMBAAGj
|
||||
gZwwgZkwHwYDVR0jBBgwFoAU2REkHgBAMCYIi8dhLQAoPj8CpRQwHQYDVR0lBBYw
|
||||
FAYIKwYBBQUHAwEGCCsGAQUFBwMCMA4GA1UdDwEB/wQEAwIFoDAoBgNVHREEITAf
|
||||
ghBub2RlLmV4YW1wbGUuY29tggtleGFtcGxlLmNvbTAdBgNVHQ4EFgQUuZLgycb7
|
||||
6NYNriONSXH0cyN+kXwwDAYIKoZIzj0EAwIFAANIADBFAiEAhHn3vyQz4RzQAz8n
|
||||
6Xn01+GwKYX9crzifo2N90rcWD4CIDSw8zaDacsYroUhS+Z7UqRSYYVLynv5pK98
|
||||
E0Pb8Y9B
|
||||
-----END CERTIFICATE-----
|
||||
BIN
akka-remote/src/test/resources/ssl/node.example.com.p12
Normal file
BIN
akka-remote/src/test/resources/ssl/node.example.com.p12
Normal file
Binary file not shown.
27
akka-remote/src/test/resources/ssl/node.example.com.pem
Normal file
27
akka-remote/src/test/resources/ssl/node.example.com.pem
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEArdQZ6y0BrukGqk2IpSbn2ppaValuN344mylAOthUc6A+eq00
|
||||
k+X5591ytDx1nK+g8lSBBVLs9tg7J1xCWs8tBHbdgMCrLmj+QJ/tczAQWLvZr2BB
|
||||
V07tVXMxSDeT82CG/KSFh7LnIQIR2FRWZlAFm0gr0fIZ7FUltZ/2y8Z1yH7565r+
|
||||
YRzLD4KBHRvYMOVezgOMAwFsHOZ2/HwutRPwkkj5yqFje1ZvH9ZWk34f5ijlYjuu
|
||||
nSstc3mF734LewaKJX5omxvM5K8ang0EZnncyWMqG21VemBxY7AALalapko+rC34
|
||||
ClBqujqv9PjSaiqfEIRAXTyMMnpIfoo6Qxoq2QIDAQABAoIBAGvzmNUQckcpuFXz
|
||||
KLVH4B1GVmt0eVpFLOpPR/BDgM796U9OWaSv4e84+48rLO9NsupLyISB51+1Ead0
|
||||
CFFU8GQhXZYkESsVUTDZISGz67LvllTvtiR+g63Zh/WNbnhqreogUjzfweIh4usy
|
||||
FSYc9B5nR69uZuL6ThzpZt9ONL958U9u6atG6mCDssT3i7ncqbluoQ9qgT1Cqamj
|
||||
33VVziG5x+jM9j5TyWvy28/yO+uaR46lsPYGgAttYJ2SpEvssniWubsZ9X+BxAV8
|
||||
RtGm+0eGgKDzi64qLU/vRq1xBuSBB1zA4XhnIE/jN/ifWpzO5VF3mVtQbHKT554m
|
||||
8BjW18ECgYEA/YT5CXcJuxhzWApupXNr+mQAsIeaNjuduQCFkPYrirZQyjmSWmhW
|
||||
+50IPQtQrvzV5mC98XGzBOfnJ9FL+q73GMJQqx2FGwFrenjATLQJ/OcfYxrr1MEq
|
||||
RdANX7ZTdBRVlR6EF3FCbwVYDyhkPy/W0PGMsgqdZHDwBJc6U+8RutUCgYEAr4eD
|
||||
1WaPnh75gJ/iJ6+ouGJPYKO2NTPCHouXJ0LWXJp6tD4geiV6O25/2sQYVTlkyhBg
|
||||
u3XyC0wpDIGoE/nrk0T6wIX+cyFWeSCxjkWilzT0GHj4LVvOnbz4DYD5PEEkqvns
|
||||
nK8KWxFs9QJ6CVOUBkZv06UCL3YPC1tVJNn9afUCgYB9TKZlVi86CHihr+5F2ckp
|
||||
ZRmuJidC3K40jJx3LCQTF87QVCoQgvdSvqcevKPxCMeTaIcYeTCYoSFvXZNm3+kC
|
||||
lK+YEywBT+9WBa5NesJg+75Yliqu6ZXCEXU6s/uFKLOv0vhIOdMy2fpO65C4ZiWO
|
||||
+YOnT3XA+cy3CCNs7oDdzQKBgQCAKGd/JhTyFBeDbDkJVN3RUiY2nxFoItQ2zSCd
|
||||
j9VHY5r3guzfggGO5wz+w3Iot3D5f5/A/0qsKP1HnlsDytPPgOu8KZkwokSqx84b
|
||||
3Ifr8sPOInTBWWiwDsrlwSc4cS++jh/N/peHCmANO7Oyn41ST5dSZgYEdSRi3Fp8
|
||||
P5UhCQKBgQCj8Hp7UINSAd6SjM+PT2wLiTtzbxAkl37cCwdLAjuEXOkwicxLCfQ/
|
||||
kxcdkHsUnXWsr51gEP24aVcUsODAiEz/0w3ebrgQsFIwZ8S3cZ9/lCVpXmwpyiQv
|
||||
/zOpqsQFbJ1ags5CfQ4cFehoHR0rk/ZOsf65T7mmedBcbaMFeucflA==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
16
akka-remote/src/test/resources/ssl/one.example.com.crt
Normal file
16
akka-remote/src/test/resources/ssl/one.example.com.crt
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICjDCCAi+gAwIBAgIETEztzDAMBggqhkjOPQQDAgUAMH4xCzAJBgNVBAYTAlVT
|
||||
MRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRgw
|
||||
FgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxFDASBgNVBAsTC0V4YW1wbGUgT3JnMRIw
|
||||
EAYDVQQDEwlleGFtcGxlQ0EwHhcNMjAwNjAxMTU1MDE0WhcNMzAwNTMwMTU1MDE0
|
||||
WjCBhDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcT
|
||||
DVNhbiBGcmFuY2lzY28xGDAWBgNVBAoTD0V4YW1wbGUgQ29tcGFueTEUMBIGA1UE
|
||||
CxMLRXhhbXBsZSBPcmcxGDAWBgNVBAMTD29uZS5leGFtcGxlLmNvbTBZMBMGByqG
|
||||
SM49AgEGCCqGSM49AwEHA0IABFWOb1oYjnJHzx1UMQTFbYhFdlEirsWGt0do/ujO
|
||||
/mXk8kMOQz+p9AtrfKR5AUCsZt19EDAtomXPdmepiOf9+7OjgZEwgY4wHwYDVR0j
|
||||
BBgwFoAU2REkHgBAMCYIi8dhLQAoPj8CpRQwEwYDVR0lBAwwCgYIKwYBBQUHAwEw
|
||||
DgYDVR0PAQH/BAQDAgWgMCcGA1UdEQQgMB6CD29uZS5leGFtcGxlLmNvbYILZXhh
|
||||
bXBsZS5jb20wHQYDVR0OBBYEFC72gRAyHCLjripkjmxa9fLT7Ov5MAwGCCqGSM49
|
||||
BAMCBQADSQAwRgIhAP2ltiw9l9mYWpIzoMAP0jBVWBBBKxajHAY0vYA0myaGAiEA
|
||||
8fPcYmmsb0dfSOnoR5LXiegG33Tih7HBiG86Gai+ozA=
|
||||
-----END CERTIFICATE-----
|
||||
BIN
akka-remote/src/test/resources/ssl/one.example.com.p12
Normal file
BIN
akka-remote/src/test/resources/ssl/one.example.com.p12
Normal file
Binary file not shown.
1
akka-remote/src/test/resources/ssl/password
Normal file
1
akka-remote/src/test/resources/ssl/password
Normal file
|
|
@ -0,0 +1 @@
|
|||
kLnCu3rboe
|
||||
14
akka-remote/src/test/resources/ssl/pem/README.md
Normal file
14
akka-remote/src/test/resources/ssl/pem/README.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# PEM resources
|
||||
|
||||
The following resources were copy/pasted from the akka-pki/src/test/resources folder.
|
||||
|
||||
- multi-prime-pkcs1.pem
|
||||
- pkcs1.pem
|
||||
- pkcs8.pem
|
||||
- selfsigned-certificate.pem
|
||||
|
||||
Note that these set of certificates are valid PEMs but are invalid for akka-remote
|
||||
use. Either the key length, certificate usage limitations (via the UsageKeyExtensions),
|
||||
or the fact that the key's certificate is self-signed cause one of the following
|
||||
errors: `certificate_unknown`, `certificate verify message signature error`/`bad_certificate`
|
||||
during the SSLHandshake.
|
||||
27
akka-remote/src/test/resources/ssl/pem/pkcs1.pem
Normal file
27
akka-remote/src/test/resources/ssl/pem/pkcs1.pem
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEA0IQFs9KpS6fpm7Bq5JcAzRzdZnYub7qGBJ9+QZX8F6qUYiXf
|
||||
jbVPAZSIksNg6G5yMkzBLZ8r0UOm5WJs6sAHKGJOQk9DcwZEt3XpGbYXAnM5V1sb
|
||||
xd5oNcbXLcHouU8jrEj+O2KvmgCzDDCUOf3SjGnF4dWqhsTT9tMJZvWVB0OjpKnT
|
||||
zcoxFKO/XCz8Spb1+FgKUgt3afA+JTRpQWtaZJ41fuTg0rm0qtUSeZXPUEYkqqK4
|
||||
msoSe7dbu7uiEOkUPoeiP7wzSrwihQSCxOzwdphV2XtF5Xfs24/Ad4WKXTqRVUK/
|
||||
yldcQy2orFSsX41KBzS8PI1hnOC6uRA0PiGd/QIDAQABAoIBAQCDbxShGuK326m3
|
||||
B2b5m+1XXSB5m3j92FbtxxMwiDgVOuK5UyItEuIwHs5PpHQLTsMQzaze8vwNtlUX
|
||||
Ngltl4lrfTvTNF9Ru9vIwLwkBtFOLA8y7yz8doq9iw7LuvTVCft0d7Y4/KWvr00t
|
||||
G9nzC/mRpIKlLaeFt7/cT34XtikwH+Ez8uWGidYnrqkKZ0bsIZvD+e7gae+veohN
|
||||
BnTcyDIk8P5nXG0vM4hxtjLo3KstemwOt6vCtiKzL2Vq/JAVD3nlec8WPBzft79I
|
||||
k5tb3Qm/OnxIQaWF5AhAVkXgMsLL3ddJoAn/K6NZ6NtRGZozkwdP+m4nacrKFJVJ
|
||||
6ew7OdAJAoGBAO65GvteHLXQ3d1NfU+X6RSBatJdpuf2S4Hc7P+sTkPjLDlQPDKL
|
||||
ZFLVigPlCehMvgGASPOxL8UqPZugwEo83ywp5t/iOUccXZCPSISreSzWJPfOJ+dl
|
||||
aKP2cSHHPNC/mISDpp/NF+xfgEAUQQ6AdOKwHGlsFWBvkkw810d4zmXvAoGBAN+b
|
||||
QYv32wNa2IHC1Ri3VgtfHpQ20fMz+UO6TMefBbgkcgBk4L+O7gSCpThaqy9R7UnX
|
||||
IEH0QyaBEXzQxB7qvCo1pczWiEUcG+vAyyz9U9oO9Hee/UTMOWL4Sj7qoavau2Be
|
||||
5PFOO6qA+19JTnStuNb3swNrMmxDQpyNDvUkYAbTAoGBALuYkSCJ84vZaBBZrajX
|
||||
mt13WieYWuocPXf+0euVTyfAJOehKr0ZlywVDNFEssVvUT1Cv5FpYz3QlPtwlsuA
|
||||
DGzbPMghMZu1Kb3JK1a+nYnjeseVpPwNT+7RYlQGCr+MYOF5x336oNsqrVEt2XX4
|
||||
8mGVva4GtsHCy7fHc/GBeMjXAoGBALhEYkytER//okG0xBUdKFwwo6tyTavEndpx
|
||||
UUqDwpvP9N5cQ1W4vG6dFviMx1s0gX4DOQMA/sFhRX79L1FnEW8bTKmz9RI2qs+p
|
||||
zgUiMhKVlmJpc79ZKMVlZRHaGybbFuTA7pvoY4ULy5rndy7x5kvITg44LZJID0Gh
|
||||
gL0Fn9ifAoGAEaWA7yxTA7phDIt0U91HZEVhNSM4cZDurE7614qWhKveSP8f0J4c
|
||||
3d9y/re4RcCwmss/FtQWSkB+32WbD3qk+QB7hV1oFqJ5ObcfwevGYR8m6vOz1h2L
|
||||
3pQNi4PcH3U8eeGG1laFKUQ295rBLNqIbOo2y+4hxMnC4tka1X118Ec=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDCzCCAfOgAwIBAgIQfEHPfR1p1xuW9TQlfxAugjANBgkqhkiG9w0BAQsFADAv
|
||||
MS0wKwYDVQQDEyQwZDIwN2I2OC05YTIwLTRlZTgtOTJjYi1iZjk2OTk1ODFjZjgw
|
||||
HhcNMTkxMDExMTMyODUzWhcNMjQxMDA5MTQyODUzWjAvMS0wKwYDVQQDEyQwZDIw
|
||||
N2I2OC05YTIwLTRlZTgtOTJjYi1iZjk2OTk1ODFjZjgwggEiMA0GCSqGSIb3DQEB
|
||||
AQUAA4IBDwAwggEKAoIBAQDhD0BxlDzEOzcp45lPHL60lnM6k3puEGb2lKHL5/nR
|
||||
F94FCnZL0FH8EdxWzzAYgys+kUwSdo4QMuWuvjY2Km4Wob6k4uAeYEFTCfBdi4/z
|
||||
r4kpWzu8xLz+uZWimLQrjqVytNNK3DMv6ebWUJ/92VTDS4yzWk4YV0MVr2b2OgMK
|
||||
SgMvaFQ8L/CwyML72PBWIqU67+MMvvcTLxQdyEgQTTjP0bbiXMLDvfZDarLJojsW
|
||||
SNBz7AIkznhGkzIGGdhAa41PnPu9XaBFhaqx9Qe3+MG2/k1l/46eHtmxCqhOUde1
|
||||
i0vy6ZfgcGifua1tg1UBI/oT4S0dsq24dq7K1MYLyHTrAgMBAAGjIzAhMA4GA1Ud
|
||||
DwEB/wQEAwICBDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAa
|
||||
5YOlvob4wqps3HLaOIi7VzDihNHciP+BI0mzxHa7D3bGaecRPSeG3xEoD/Uxs9o4
|
||||
8cByPpYx1Wl8LLRx14wDcK0H+UPpo4gCI6h6q92cJj0YRjcSUUt8EIu3qnFMjtM+
|
||||
sl/uc21fGlq6cvMRZXqtVYENoSNTDHi5a5eEXRa2eZ8XntjvOanOhIKWmxvr8r4a
|
||||
Voz4WdnXx1C8/BzB62UBoMu4QqMGMLk5wXP0D6hECUuespMest+BeoJAVhTq7wZs
|
||||
rSP9q08n1stZFF4+bEBaxcqIqdhOLQdHcYELN+a5v0Mcwdsy7jJMagmNPfsKoOKC
|
||||
hLOsmNYKHdmWg37Jib5o
|
||||
-----END CERTIFICATE-----
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDYzCCAwigAwIBAgIECUHCVTAMBggqhkjOPQQDAgUAMH4xCzAJBgNVBAYTAlVT
|
||||
MRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRgw
|
||||
FgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxFDASBgNVBAsTC0V4YW1wbGUgT3JnMRIw
|
||||
EAYDVQQDEwlleGFtcGxlQ0EwHhcNMjAwNjAxMTU1MDI1WhcNMzAwNTMwMTU1MDI1
|
||||
WjCBizELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcT
|
||||
DVNhbiBGcmFuY2lzY28xGDAWBgNVBAoTD0V4YW1wbGUgQ29tcGFueTEUMBIGA1UE
|
||||
CxMLRXhhbXBsZSBPcmcxHzAdBgNVBAMTFnJzYS1jbGllbnQuZXhhbXBsZS5jb20w
|
||||
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCEViFvY8pp7M/yv4tyUGyq
|
||||
knInRW+FtHH7rkgB7nqL6DCItp81yafB2KMQ7NrdRXKr12sHUhcpd81acchqURZM
|
||||
TqM5GwhK0TieR5qfmT+zQmBXKprIVc2sRvgVC7p4wNseXwky0aLJQRlFWAgB2nZ/
|
||||
+EBDNt0I5QGLSoaN5fMH6F9pVC1IpyCnVFisgDg7obCOIkBUfVyswye0+RcjGYRA
|
||||
PelMzQcy62AnYh/64eBDKdGfLjHczaw14Uuy5OlWjeyVdM6MZBOf+KfxdD8KLlhw
|
||||
2cLxtzkJk3JedqW4+ufRS7ilGRpUw8e2IgR9TQW0VLYByALAVIfJSMfUEx70K5NB
|
||||
AgMBAAGjgZgwgZUwHwYDVR0jBBgwFoAU2REkHgBAMCYIi8dhLQAoPj8CpRQwEwYD
|
||||
VR0lBAwwCgYIKwYBBQUHAwIwDgYDVR0PAQH/BAQDAgWgMC4GA1UdEQQnMCWCFnJz
|
||||
YS1jbGllbnQuZXhhbXBsZS5jb22CC2V4YW1wbGUuY29tMB0GA1UdDgQWBBSPL7X9
|
||||
LBqCjnPi25CIzzkX3OEiITAMBggqhkjOPQQDAgUAA0cAMEQCIDx/fReRIBGXgRkn
|
||||
zYGi09q7VccGTfw0gCTUa68NLTqpAiA27xcuzNMBS21xtXa7nVEOJJ1BUyi7c9pS
|
||||
/9KFyHXAXg==
|
||||
-----END CERTIFICATE-----
|
||||
BIN
akka-remote/src/test/resources/ssl/rsa-client.example.com.p12
Normal file
BIN
akka-remote/src/test/resources/ssl/rsa-client.example.com.p12
Normal file
Binary file not shown.
|
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAhFYhb2PKaezP8r+LclBsqpJyJ0VvhbRx+65IAe56i+gwiLaf
|
||||
NcmnwdijEOza3UVyq9drB1IXKXfNWnHIalEWTE6jORsIStE4nkean5k/s0JgVyqa
|
||||
yFXNrEb4FQu6eMDbHl8JMtGiyUEZRVgIAdp2f/hAQzbdCOUBi0qGjeXzB+hfaVQt
|
||||
SKcgp1RYrIA4O6GwjiJAVH1crMMntPkXIxmEQD3pTM0HMutgJ2If+uHgQynRny4x
|
||||
3M2sNeFLsuTpVo3slXTOjGQTn/in8XQ/Ci5YcNnC8bc5CZNyXnaluPrn0Uu4pRka
|
||||
VMPHtiIEfU0FtFS2AcgCwFSHyUjH1BMe9CuTQQIDAQABAoIBAAEuZ6P/5wmlBIIt
|
||||
NYhysVWgcwZot0MhRjcx6hCiWVOwYeaGgstPHoE7dtEq3BVHjmt8Q8dP80b6e8nH
|
||||
5DsWuDU8KnbTB/LrBS9cgkPUcV4fRI7ioXkSUwCrrYE6lpKHXi1aOdHLT0GupBQb
|
||||
Yg7qLU6dH6256qUuriHZK3ROzimO5R3lsHkA8+Lavw6/LJIuLMkl5wmg91AS4Y0d
|
||||
SYUUf7452hj2Ir/RK7wNrtZ/QQ5g6iIXaH8vKt20DBkUXuiMJI6uP98/w2axwMC6
|
||||
/AkSAsFDh0K3ZiJWjeNYQ5KZrLFUNmADMFo6RL5fvcyp1c2e6vLcIbM6JPOtwql1
|
||||
SI2S2HUCgYEA2ezDtj4lN61uh8nXk0z0UApc5Cx+dzLMcbGrCLIn67x9EbDtcOvz
|
||||
oy+GnnD/JLhd/3HRf1Lwd+30iXpJ5rr1nRLwlSXmWsHvFafwGvRQ4NWYt3QyM9TX
|
||||
98WMchy5m+0pBqBq+0IZPLUB7iY54xgUHMk2/IrbwkkvkYWS4gKUN2sCgYEAm3U1
|
||||
t/DwrDuzF4/ZhGTxOI0Y3JSFRGtwF5npkR/6bN/T9JKU4gD8T3fkvkmEZ66xVzUA
|
||||
iuUIgVs3rSkVi51Sr2xId8KMUPRJtDWAThJYEtY1AAFjY9gYlxYA/kggWQSPWfX+
|
||||
CzLHAAB5uIPe2xS3YN3yXZLABefv7ZF6i1aBBwMCgYBNE0NJEoPBRHLCTe4T5/TE
|
||||
1lVyUhZMfEf4sjjms3QRGTI27pecB6e9AJMhOJ/U0exU62GIIcJw+FUzxm+azmcO
|
||||
LeOvLJ9jXBH+W849CkoMqx7/S3ZyBIZ52IHK3kP7VQ7cjCIqSX95jB9pplV071A1
|
||||
uijbexUsiwvq8Q45J2ZajwKBgCtVNLAdPTkFOxqqQluhN4wn6HI0BCHaQNiTUoPd
|
||||
ghSvH4nhAhctZydPqDdSjtHH5C8G2yvcQ86q+o4OEa9lHxM+/8RCOpKmRZUyBJ2+
|
||||
h0ZY51UlDeta5R/YRlabDElD+CF/bFz6vnXFrCg+ufQfhi4+L7zdlyEOUdbK4nnM
|
||||
lxK5AoGBALMfsbYejpjNP0Dmcv644barz9SLKPT9Sxv5xjDNxRXWATIy2MblM9WO
|
||||
WO0hNw6KLfSl1dkrbZYH4/4abw6g0FYCivBe9X7p/iGkIBKx9PWAW18vTnomW7Oq
|
||||
RfTCzmLHhGVhxl6ByvSzNIV7MCgPeXalC7gUBLdnKuBvQCOvzyPr
|
||||
-----END RSA PRIVATE KEY-----
|
||||
16
akka-remote/src/test/resources/ssl/two.example.com.crt
Normal file
16
akka-remote/src/test/resources/ssl/two.example.com.crt
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICizCCAi+gAwIBAgIECNzvNjAMBggqhkjOPQQDAgUAMH4xCzAJBgNVBAYTAlVT
|
||||
MRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRgw
|
||||
FgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxFDASBgNVBAsTC0V4YW1wbGUgT3JnMRIw
|
||||
EAYDVQQDEwlleGFtcGxlQ0EwHhcNMjAwNjAxMTU1MDE2WhcNMzAwNTMwMTU1MDE2
|
||||
WjCBhDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcT
|
||||
DVNhbiBGcmFuY2lzY28xGDAWBgNVBAoTD0V4YW1wbGUgQ29tcGFueTEUMBIGA1UE
|
||||
CxMLRXhhbXBsZSBPcmcxGDAWBgNVBAMTD3R3by5leGFtcGxlLmNvbTBZMBMGByqG
|
||||
SM49AgEGCCqGSM49AwEHA0IABK2v6kZfa6beqxAXcSjAIUKORvbwivAEwoik38ZN
|
||||
kQ8X60K5/gFE/QNoozTOZ60pbPzI8QFjcTXlpm4aKcRudwajgZEwgY4wHwYDVR0j
|
||||
BBgwFoAU2REkHgBAMCYIi8dhLQAoPj8CpRQwEwYDVR0lBAwwCgYIKwYBBQUHAwEw
|
||||
DgYDVR0PAQH/BAQDAgWgMCcGA1UdEQQgMB6CD3R3by5leGFtcGxlLmNvbYILZXhh
|
||||
bXBsZS5jb20wHQYDVR0OBBYEFDrOW8faP3xxtC/wd1f6EZwlpF7XMAwGCCqGSM49
|
||||
BAMCBQADSAAwRQIhAPr4w+30vBqHWGJOGvBScN3OXQaBnQSlFIc5c0AErLYdAiB+
|
||||
JuYU6hy1EmvRZxz2p/r4pz94K/81pNCAWgHKp1pxgQ==
|
||||
-----END CERTIFICATE-----
|
||||
BIN
akka-remote/src/test/resources/ssl/two.example.com.p12
Normal file
BIN
akka-remote/src/test/resources/ssl/two.example.com.p12
Normal file
Binary file not shown.
Binary file not shown.
|
|
@ -32,6 +32,9 @@ object ArterySpecSupport {
|
|||
// set the test key-store and trust-store properties
|
||||
// TLS only used if transport=tls-tcp, which can be set from specific tests or
|
||||
// System properties (e.g. jenkins job)
|
||||
// TODO: randomly return a Config using ConfigSSLEngineProvider or
|
||||
// RotatingKeysSSLEngineProvider and, eventually, run tests twice
|
||||
// (once for each provider).
|
||||
lazy val tlsConfig: Config = {
|
||||
val trustStore = getClass.getClassLoader.getResource("truststore").getPath
|
||||
val keyStore = getClass.getClassLoader.getResource("keystore").getPath
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package akka.remote.artery.tcp
|
||||
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.SecureRandom
|
||||
import java.util.zip.GZIPOutputStream
|
||||
|
||||
import akka.event.NoMarkerLogging
|
||||
import akka.testkit.AkkaSpec
|
||||
|
||||
class SecureRandomFactorySHA1Spec extends SecureRandomFactorySpec("SHA1PRNG")
|
||||
class SecureRandomFactoryNativePRNGSpec extends SecureRandomFactorySpec("NativePRNG")
|
||||
class SecureRandomFactoryJVMChoiceSpec extends SecureRandomFactorySpec("SecureRandom")
|
||||
class SecureRandomFactoryBlankSpec extends SecureRandomFactorySpec("")
|
||||
class SecureRandomFactoryInvalidPRNGSpec extends SecureRandomFactorySpec("InvalidPRNG")
|
||||
|
||||
abstract class SecureRandomFactorySpec(alg: String) extends AkkaSpec {
|
||||
var prng: SecureRandom = null
|
||||
|
||||
def isSupported: Boolean = {
|
||||
try {
|
||||
prng = SecureRandomFactory.createSecureRandom(alg, NoMarkerLogging)
|
||||
prng.nextInt() // Has to work
|
||||
val sRng = alg
|
||||
if (prng.getAlgorithm != sRng && sRng != "")
|
||||
throw new NoSuchAlgorithmException(sRng)
|
||||
true
|
||||
} catch {
|
||||
case e: NoSuchAlgorithmException =>
|
||||
info(e.toString)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
s"Artery's Secure Random support ($alg)" must {
|
||||
if (isSupported) {
|
||||
"generate random" in {
|
||||
val bytes = Array.ofDim[Byte](16)
|
||||
// Reproducer of the specific issue described at
|
||||
// https://doc.akka.io/docs/akka/current/security/2018-08-29-aes-rng.html
|
||||
// awaitAssert just in case we are very unlucky to get same sequence more than once
|
||||
awaitAssert {
|
||||
val randomBytes = List
|
||||
.fill(10) {
|
||||
prng.nextBytes(bytes)
|
||||
bytes.toVector
|
||||
}
|
||||
.toSet
|
||||
randomBytes.size should ===(10)
|
||||
}
|
||||
}
|
||||
|
||||
"have random numbers that are not compressable, because then they are not random" in {
|
||||
val randomData = new Array[Byte](1024 * 1024)
|
||||
prng.nextBytes(randomData)
|
||||
|
||||
val baos = new ByteArrayOutputStream()
|
||||
val gzipped = new GZIPOutputStream(baos)
|
||||
try gzipped.write(randomData)
|
||||
finally gzipped.close()
|
||||
|
||||
val compressed = baos.toByteArray
|
||||
// random data should not be compressible
|
||||
// Another reproducer of https://doc.akka.io/docs/akka/current/security/2018-08-29-aes-rng.html
|
||||
// with the broken implementation the compressed size was <5k
|
||||
compressed.size should be > randomData.length
|
||||
}
|
||||
|
||||
} else {
|
||||
s"not be run when the $alg PRNG provider is not supported by the platform this test is currently being executed on" in {
|
||||
pending
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,27 +5,23 @@
|
|||
package akka.remote.artery
|
||||
package tcp
|
||||
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.util.zip.GZIPOutputStream
|
||||
import javax.net.ssl.SSLEngine
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigFactory
|
||||
|
||||
import akka.actor.ActorIdentity
|
||||
import akka.actor.ActorPath
|
||||
import akka.actor.ActorRef
|
||||
import akka.actor.ExtendedActorSystem
|
||||
import akka.actor.Identify
|
||||
import akka.actor.RootActorPath
|
||||
import akka.actor.{ ActorIdentity, ActorPath, ActorRef, Identify, RootActorPath }
|
||||
import akka.actor.setup.ActorSystemSetup
|
||||
import akka.remote.artery.tcp.ssl.CipherSuiteSupportCheck
|
||||
import akka.testkit.EventFilter
|
||||
import akka.testkit.ImplicitSender
|
||||
import akka.testkit.TestActors
|
||||
import akka.testkit.TestProbe
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import javax.net.ssl.SSLEngine
|
||||
import javax.net.ssl.SSLSession
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.{ Failure, Success }
|
||||
|
||||
class TlsTcpWithDefaultConfigSpec extends TlsTcpSpec(ConfigFactory.empty())
|
||||
|
||||
|
|
@ -53,6 +49,17 @@ class TlsTcpWithCrappyRSAWithMD5OnlyHereToMakeSureThingsWorkSpec
|
|||
}
|
||||
"""))
|
||||
|
||||
class TlsTcpWithRotatingKeysSSLEngineSpec extends TlsTcpSpec(ConfigFactory.parseString(s"""
|
||||
akka.remote.artery.ssl {
|
||||
ssl-engine-provider = akka.remote.artery.tcp.ssl.RotatingKeysSSLEngineProvider
|
||||
rotating-keys-engine {
|
||||
key-file = ${getClass.getClassLoader.getResource("ssl/node.example.com.pem").getPath}
|
||||
cert-file = ${getClass.getClassLoader.getResource("ssl/node.example.com.crt").getPath}
|
||||
ca-cert-file = ${getClass.getClassLoader.getResource("ssl/exampleca.crt").getPath}
|
||||
}
|
||||
}
|
||||
"""))
|
||||
|
||||
object TlsTcpSpec {
|
||||
|
||||
lazy val config: Config = {
|
||||
|
|
@ -68,37 +75,31 @@ object TlsTcpSpec {
|
|||
|
||||
abstract class TlsTcpSpec(config: Config)
|
||||
extends ArteryMultiNodeSpec(config.withFallback(TlsTcpSpec.config))
|
||||
with ImplicitSender {
|
||||
with ImplicitSender
|
||||
with Matchers {
|
||||
|
||||
val systemB = newRemoteSystem(name = Some("systemB"))
|
||||
val addressB = address(systemB)
|
||||
val rootB = RootActorPath(addressB)
|
||||
|
||||
def isSupported: Boolean = {
|
||||
try {
|
||||
val provider = new ConfigSSLEngineProvider(system)
|
||||
|
||||
val rng = provider.createSecureRandom()
|
||||
rng.nextInt() // Has to work
|
||||
val sRng = provider.SSLRandomNumberGenerator
|
||||
if (rng.getAlgorithm != sRng && sRng != "")
|
||||
throw new NoSuchAlgorithmException(sRng)
|
||||
|
||||
val address = system.asInstanceOf[ExtendedActorSystem].provider.getDefaultAddress
|
||||
val host = address.host.get
|
||||
val port = address.port.get
|
||||
|
||||
val engine = provider.createServerSSLEngine(host, port)
|
||||
val gotAllSupported = provider.SSLEnabledAlgorithms.diff(engine.getSupportedCipherSuites.toSet)
|
||||
val gotAllEnabled = provider.SSLEnabledAlgorithms.diff(engine.getEnabledCipherSuites.toSet)
|
||||
gotAllSupported.isEmpty || (throw new IllegalArgumentException("Cipher Suite not supported: " + gotAllSupported))
|
||||
gotAllEnabled.isEmpty || (throw new IllegalArgumentException("Cipher Suite not enabled: " + gotAllEnabled))
|
||||
engine.getSupportedProtocols.contains(provider.SSLProtocol) ||
|
||||
(throw new IllegalArgumentException("Protocol not supported: " + provider.SSLProtocol))
|
||||
} catch {
|
||||
case e @ (_: IllegalArgumentException | _: NoSuchAlgorithmException) =>
|
||||
val checked = system.settings.config.getString("akka.remote.artery.ssl.ssl-engine-provider") match {
|
||||
case "akka.remote.artery.tcp.ConfigSSLEngineProvider" =>
|
||||
CipherSuiteSupportCheck.isSupported(system, "akka.remote.artery.ssl.config-ssl-engine")
|
||||
case "akka.remote.artery.tcp.ssl.RotatingKeysSSLEngineProvider" =>
|
||||
CipherSuiteSupportCheck.isSupported(system, "akka.remote.artery.ssl.rotating-keys-engine")
|
||||
case other =>
|
||||
fail(
|
||||
s"Don't know how to determine whether the crypto building blocks in [$other] are available on this platform")
|
||||
}
|
||||
checked match {
|
||||
case Success(()) =>
|
||||
true
|
||||
case Failure(e @ (_: IllegalArgumentException | _: NoSuchAlgorithmException)) =>
|
||||
info(e.toString)
|
||||
false
|
||||
case Failure(other) =>
|
||||
fail("Unexpected failure checking whether the crypto building blocks are available on this platform.", other)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -122,43 +123,6 @@ abstract class TlsTcpSpec(config: Config)
|
|||
|
||||
if (isSupported) {
|
||||
|
||||
"generate random" in {
|
||||
val provider = new ConfigSSLEngineProvider(system)
|
||||
val rng = provider.createSecureRandom()
|
||||
val bytes = Array.ofDim[Byte](16)
|
||||
// Reproducer of the specific issue described at
|
||||
// https://doc.akka.io/docs/akka/current/security/2018-08-29-aes-rng.html
|
||||
// awaitAssert just in case we are very unlucky to get same sequence more than once
|
||||
awaitAssert {
|
||||
val randomBytes = List
|
||||
.fill(10) {
|
||||
rng.nextBytes(bytes)
|
||||
bytes.toVector
|
||||
}
|
||||
.toSet
|
||||
randomBytes.size should ===(10)
|
||||
}
|
||||
}
|
||||
|
||||
"have random numbers that are not compressable, because then they are not random" in {
|
||||
val provider = new ConfigSSLEngineProvider(system)
|
||||
val rng = provider.createSecureRandom()
|
||||
|
||||
val randomData = new Array[Byte](1024 * 1024)
|
||||
rng.nextBytes(randomData)
|
||||
|
||||
val baos = new ByteArrayOutputStream()
|
||||
val gzipped = new GZIPOutputStream(baos)
|
||||
try gzipped.write(randomData)
|
||||
finally gzipped.close()
|
||||
|
||||
val compressed = baos.toByteArray
|
||||
// random data should not be compressible
|
||||
// Another reproducer of https://doc.akka.io/docs/akka/current/security/2018-08-29-aes-rng.html
|
||||
// with the broken implementation the compressed size was <5k
|
||||
compressed.size should be > randomData.length
|
||||
}
|
||||
|
||||
"deliver messages" in {
|
||||
systemB.actorOf(TestActors.echoActorProps, "echo")
|
||||
val echoRef = identify(rootB / "user" / "echo")
|
||||
|
|
@ -257,17 +221,21 @@ class TlsTcpWithActorSystemSetupSpec extends ArteryMultiNodeSpec(TlsTcpSpec.conf
|
|||
val sslProviderClientProbe = TestProbe()
|
||||
|
||||
val sslProviderSetup = SSLEngineProviderSetup(sys =>
|
||||
new ConfigSSLEngineProvider(sys) {
|
||||
new SSLEngineProvider {
|
||||
val delegate = new ConfigSSLEngineProvider(sys)
|
||||
override def createServerSSLEngine(hostname: String, port: Int): SSLEngine = {
|
||||
sslProviderServerProbe.ref ! "createServerSSLEngine"
|
||||
super.createServerSSLEngine(hostname, port)
|
||||
delegate.createServerSSLEngine(hostname, port)
|
||||
}
|
||||
|
||||
override def createClientSSLEngine(hostname: String, port: Int): SSLEngine = {
|
||||
sslProviderClientProbe.ref ! "createClientSSLEngine"
|
||||
super.createClientSSLEngine(hostname, port)
|
||||
delegate.createClientSSLEngine(hostname, port)
|
||||
}
|
||||
|
||||
override def verifyClientSession(hostname: String, session: SSLSession): Option[Throwable] =
|
||||
delegate.verifyClientSession(hostname, session)
|
||||
override def verifyServerSession(hostname: String, session: SSLSession): Option[Throwable] =
|
||||
delegate.verifyServerSession(hostname, session)
|
||||
})
|
||||
|
||||
val systemB = newRemoteSystem(name = Some("systemB"), setup = Some(ActorSystemSetup(sslProviderSetup)))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package akka.remote.artery.tcp.ssl
|
||||
|
||||
import java.security.NoSuchAlgorithmException
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import akka.actor.ExtendedActorSystem
|
||||
import akka.event.NoMarkerLogging
|
||||
import akka.remote.artery.tcp.SecureRandomFactory
|
||||
import com.typesafe.config.Config
|
||||
import javax.net.ssl.SSLEngine
|
||||
|
||||
import scala.util.Try
|
||||
|
||||
object CipherSuiteSupportCheck {
|
||||
|
||||
/**
|
||||
* Given a `configPath` runs a set of validations over the default protocols,
|
||||
* cipher suites, PRNG algorithm/provider, etc...
|
||||
*/
|
||||
def isSupported(system: ActorSystem, configPath: String): Try[Unit] = Try {
|
||||
val config = system.settings.config
|
||||
val subConfig = config.getConfig(configPath)
|
||||
|
||||
isPrngSupported(subConfig)
|
||||
val engine: SSLEngine = buildSslEngine(system)
|
||||
val sslFactoryConfig = new SSLEngineConfig(subConfig)
|
||||
|
||||
areAlgorithmsSupported(sslFactoryConfig, engine)
|
||||
isProtocolSupported(sslFactoryConfig, engine)
|
||||
}
|
||||
|
||||
private def isPrngSupported(config: Config): Unit = {
|
||||
val rng = SecureRandomFactory.createSecureRandom(config, NoMarkerLogging)
|
||||
rng.nextInt() // Has to work
|
||||
val setting = SecureRandomFactory.rngConfig(config)
|
||||
if (rng.getAlgorithm != setting && setting != "")
|
||||
throw new NoSuchAlgorithmException(setting)
|
||||
}
|
||||
|
||||
private def areAlgorithmsSupported(sslFactoryConfig: SSLEngineConfig, engine: SSLEngine) = {
|
||||
import sslFactoryConfig._
|
||||
val gotAllSupported = SSLEnabledAlgorithms.diff(engine.getSupportedCipherSuites.toSet)
|
||||
val gotAllEnabled = SSLEnabledAlgorithms.diff(engine.getEnabledCipherSuites.toSet)
|
||||
gotAllSupported.isEmpty || (throw new IllegalArgumentException("Cipher Suite not supported: " + gotAllSupported))
|
||||
gotAllEnabled.isEmpty || (throw new IllegalArgumentException("Cipher Suite not enabled: " + gotAllEnabled))
|
||||
}
|
||||
|
||||
private def isProtocolSupported(sslFactoryConfig: SSLEngineConfig, engine: SSLEngine) = {
|
||||
import sslFactoryConfig._
|
||||
engine.getSupportedProtocols.contains(SSLProtocol) ||
|
||||
(throw new IllegalArgumentException("Protocol not supported: " + SSLProtocol))
|
||||
|
||||
}
|
||||
|
||||
private def buildSslEngine(system: ActorSystem): SSLEngine = {
|
||||
val address = system.asInstanceOf[ExtendedActorSystem].provider.getDefaultAddress
|
||||
val host = address.host.get
|
||||
val port = address.port.get
|
||||
val provider = new akka.remote.artery.tcp.ConfigSSLEngineProvider(system)
|
||||
val engine = provider.createServerSSLEngine(host, port)
|
||||
engine
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package akka.remote.artery.tcp.ssl
|
||||
|
||||
import java.security.Principal
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
import javax.net.ssl.SSLSession
|
||||
import javax.net.ssl.SSLSessionContext
|
||||
import org.scalatest.matchers.must.Matchers
|
||||
import org.scalatest.wordspec.AnyWordSpec
|
||||
|
||||
class PeerSubjectVerifierSpec extends AnyWordSpec with Matchers {
|
||||
import TlsResourcesSpec._
|
||||
"PeerSubjectVerifier" must {
|
||||
|
||||
"accept a peer when both peers have the same subject name" in {
|
||||
// CN=one.example.com
|
||||
// SAN=DNS:number-one.example.com,DNS:example.com
|
||||
// see https://github.com/playframework/play-samples/pull/97
|
||||
val exampleOne = loadCert("/ssl/one.example.com.crt")
|
||||
// CN=two.example.com
|
||||
// SAN=DNS:number-two.example.com,DNS:example.com
|
||||
val exampleTwo = loadCert("/ssl/two.example.com.crt")
|
||||
// verification passes because both certs have `example.com` in the SAN
|
||||
new PeerSubjectVerifier(exampleOne).verifyServerSession(null, inMemSession(exampleTwo)) mustBe None
|
||||
}
|
||||
|
||||
"reject a peer when peers don't have the same subject name" in {
|
||||
// `island.example.com` has no SAN and its only subject is not available on `two.example.com`
|
||||
// the peer verification must fail.
|
||||
val client = loadCert("/ssl/island.example.com.crt")
|
||||
val exampleTwo = loadCert("/ssl/two.example.com.crt")
|
||||
new PeerSubjectVerifier(client).verifyServerSession(null, inMemSession(exampleTwo)) mustNot be(None)
|
||||
}
|
||||
}
|
||||
|
||||
def inMemSession(peerCert: X509Certificate): SSLSession = {
|
||||
new SSLSession {
|
||||
override def getPeerCertificates: Array[Certificate] = Array(peerCert)
|
||||
override def getId: Array[Byte] = throw new UnsupportedOperationException()
|
||||
override def getSessionContext: SSLSessionContext = throw new UnsupportedOperationException()
|
||||
override def getCreationTime: Long = throw new UnsupportedOperationException()
|
||||
override def getLastAccessedTime: Long = throw new UnsupportedOperationException()
|
||||
override def invalidate(): Unit = throw new UnsupportedOperationException()
|
||||
override def isValid: Boolean = throw new UnsupportedOperationException()
|
||||
override def putValue(name: String, value: Any): Unit = throw new UnsupportedOperationException()
|
||||
override def getValue(name: String): AnyRef = throw new UnsupportedOperationException()
|
||||
override def removeValue(name: String): Unit = throw new UnsupportedOperationException()
|
||||
override def getValueNames: Array[String] = throw new UnsupportedOperationException()
|
||||
override def getLocalCertificates: Array[Certificate] = throw new UnsupportedOperationException()
|
||||
override def getPeerCertificateChain: Array[javax.security.cert.X509Certificate] =
|
||||
throw new UnsupportedOperationException()
|
||||
override def getPeerPrincipal: Principal = throw new UnsupportedOperationException()
|
||||
override def getLocalPrincipal: Principal = throw new UnsupportedOperationException()
|
||||
override def getCipherSuite: String = throw new UnsupportedOperationException()
|
||||
override def getProtocol: String = throw new UnsupportedOperationException()
|
||||
override def getPeerHost: String = throw new UnsupportedOperationException()
|
||||
override def getPeerPort: Int = throw new UnsupportedOperationException()
|
||||
override def getPacketBufferSize: Int = throw new UnsupportedOperationException()
|
||||
override def getApplicationBufferSize: Int = throw new UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package akka.remote.artery.tcp.ssl
|
||||
|
||||
import java.security.PrivateKey
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
import org.scalatest.matchers.must.Matchers
|
||||
import org.scalatest.wordspec.AnyWordSpec
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class PemManagersProviderSpec extends AnyWordSpec with Matchers {
|
||||
|
||||
"A PemManagersProvider" must {
|
||||
|
||||
"load stores reading files setup in config (akka-pki samples)" in {
|
||||
// These set of certificates are valid PEMs but are invalid for akka-remote
|
||||
// use. Either the key length, certificate usage limitations (via the UsageKeyExtensions),
|
||||
// or the fact that the key's certificate is self-signed cause one of the following
|
||||
// errors: `certificate_unknown`, `certificate verify message signature error`/`bad_certificate`
|
||||
// during the SSLHandshake.
|
||||
withFiles("ssl/pem/pkcs1.pem", "ssl/pem/selfsigned-certificate.pem", "ssl/pem/selfsigned-certificate.pem") {
|
||||
(pk, cert, cacert) =>
|
||||
PemManagersProvider.buildKeyManagers(pk, cert, cacert).length must be(1)
|
||||
PemManagersProvider.buildTrustManagers(cacert).length must be(1)
|
||||
cert.getSubjectDN.getName must be("CN=0d207b68-9a20-4ee8-92cb-bf9699581cf8")
|
||||
}
|
||||
}
|
||||
|
||||
"load stores reading files setup in config (keytool samples)" in {
|
||||
withFiles("ssl/node.example.com.pem", "ssl/node.example.com.crt", "ssl/exampleca.crt") { (pk, cert, cacert) =>
|
||||
PemManagersProvider.buildKeyManagers(pk, cert, cacert).length must be(1)
|
||||
PemManagersProvider.buildTrustManagers(cacert).length must be(1)
|
||||
cert.getSubjectDN.getName must be(
|
||||
"CN=node.example.com, OU=Example Org, O=Example Company, L=San Francisco, ST=California, C=US")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private def withFiles(keyFile: String, certFile: String, caCertFile: String)(
|
||||
block: (PrivateKey, X509Certificate, Certificate) => Unit) = {
|
||||
block(
|
||||
PemManagersProvider.loadPrivateKey(nameToPath(keyFile)),
|
||||
PemManagersProvider.loadCertificate(nameToPath(certFile)).asInstanceOf[X509Certificate],
|
||||
PemManagersProvider.loadCertificate(nameToPath(caCertFile)))
|
||||
}
|
||||
|
||||
private def nameToPath(name: String): String = getClass.getClassLoader.getResource(name).getPath
|
||||
}
|
||||
|
|
@ -0,0 +1,326 @@
|
|||
/*
|
||||
* Copyright (C) 2016-2020 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package akka.remote.artery.tcp.ssl
|
||||
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.StandardCopyOption
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
import akka.actor.ActorIdentity
|
||||
import akka.actor.ActorPath
|
||||
import akka.actor.ActorRef
|
||||
import akka.actor.ActorSystem
|
||||
import akka.actor.Address
|
||||
import akka.actor.ExtendedActorSystem
|
||||
import akka.actor.Identify
|
||||
import akka.actor.RootActorPath
|
||||
import akka.actor.setup.ActorSystemSetup
|
||||
import akka.remote.artery.ArteryMultiNodeSpec
|
||||
import akka.remote.artery.tcp.SSLEngineProvider
|
||||
import akka.remote.artery.tcp.SSLEngineProviderSetup
|
||||
import akka.remote.artery.tcp.TlsTcpSpec
|
||||
import akka.testkit.ImplicitSender
|
||||
import akka.testkit.TestActors
|
||||
import akka.testkit.TestProbe
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.SSLEngine
|
||||
import javax.net.ssl.SSLSession
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.Await
|
||||
import scala.concurrent.blocking
|
||||
import scala.util.control.NonFatal
|
||||
|
||||
// This is a simplification Spec. It doesn't rely on changing files.
|
||||
class RotatingProviderWithStaticKeysSpec
|
||||
extends RotatingKeysSSLEngineProviderSpec(RotatingKeysSSLEngineProviderSpec.resourcesConfig) {
|
||||
"Artery with TLS/TCP with RotatingKeysSSLEngine" must {
|
||||
|
||||
"rebuild the SSLContext" in {
|
||||
if (!arteryTcpTlsEnabled())
|
||||
pending
|
||||
|
||||
// an initial connection between sysA (from the testkit) and sysB
|
||||
// to get sysB up and running
|
||||
val (remoteSysB, pathEchoB) = buildRemoteWithEchoActor("B-rebuild")
|
||||
contact(system, pathEchoB)
|
||||
assertEnginesCreated(remoteSysB)
|
||||
val before = remoteSysB.sslContextRef.get()
|
||||
|
||||
awaitCacheExpiration()
|
||||
|
||||
// Send message to system C from system B.
|
||||
// Not using system A because we can't get a reference to the SSLContext in system A
|
||||
val (remoteSysC, pathEchoC) = buildRemoteWithEchoActor("C-rebuild")
|
||||
contact(remoteSysB.actorSystem, pathEchoC)
|
||||
assertEnginesCreated(remoteSysC)
|
||||
assertEnginesCreated(remoteSysB)
|
||||
|
||||
// the SSLContext references on sysB should differ
|
||||
val after = remoteSysB.sslContextRef.get()
|
||||
before shouldNot be(after)
|
||||
}
|
||||
|
||||
"keep existing connections alive (no new SSLEngine's created after cache expiration)" in {
|
||||
if (!arteryTcpTlsEnabled())
|
||||
pending
|
||||
|
||||
// an initial connection between sysA (from the testkit) and sysB
|
||||
// to get sysB up and running
|
||||
val (remoteSysB, pathEchoB) = buildRemoteWithEchoActor("B-reuse-alive")
|
||||
contact(system, pathEchoB)
|
||||
assertThreeChannelsAreCreated(remoteSysB)
|
||||
// once the three channels are created, no new engines are required... (cont'd)
|
||||
contact(system, pathEchoB)
|
||||
contact(system, pathEchoB)
|
||||
assertNoEnginesCreated(remoteSysB)
|
||||
|
||||
awaitCacheExpiration()
|
||||
|
||||
// ... (cont) even when the cache has expired.
|
||||
// Send message to system B from system A should not require a new SSLEngine
|
||||
// be created.
|
||||
contact(system, pathEchoB)
|
||||
assertNoEnginesCreated(remoteSysB)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// This is a the real deal Spec. It relies on changing files on a particular folder.
|
||||
class RotatingProviderWithChangingKeysSpec
|
||||
extends RotatingKeysSSLEngineProviderSpec(RotatingKeysSSLEngineProviderSpec.tempFileConfig) {
|
||||
import RotatingKeysSSLEngineProviderSpec._
|
||||
|
||||
protected override def atStartup(): Unit = {
|
||||
super.atStartup()
|
||||
deployCaCert()
|
||||
deployKeySet("ssl/artery-nodes/artery-node001.example.com")
|
||||
}
|
||||
|
||||
"Artery with TLS/TCP with RotatingKeysSSLEngine" must {
|
||||
"rebuild the SSLContext using new keys" in {
|
||||
if (!arteryTcpTlsEnabled())
|
||||
pending
|
||||
|
||||
// an initial connection between sysA (from the testkit) and sysB
|
||||
// to get sysB up and running
|
||||
val (remoteSysB, pathEchoB) = buildRemoteWithEchoActor("B-reread")
|
||||
contact(system, pathEchoB)
|
||||
assertEnginesCreated(remoteSysB)
|
||||
val before = remoteSysB.sslContextRef.get()
|
||||
|
||||
// setup new (invalid) keys
|
||||
// The `ssl/rsa-client.example.com` keyset can't be used in peer-to-peer connections
|
||||
// it's only valid for `clientAuth`
|
||||
|
||||
deployKeySet("ssl/rsa-client.example.com")
|
||||
awaitCacheExpiration()
|
||||
val (_, pathEchoC) = buildRemoteWithEchoActor("C-reread")
|
||||
try {
|
||||
contact(remoteSysB.actorSystem, pathEchoC)
|
||||
fail("The credentials under `ssl/rsa-client` are not valid for Akka remote so contact() must fail.")
|
||||
} catch {
|
||||
case _: java.lang.AssertionError =>
|
||||
// This assertion error is expected because we expect a failure in contact() since
|
||||
// the SSL credentials are invalid
|
||||
}
|
||||
|
||||
// deploy a new key set
|
||||
deployKeySet("ssl/artery-nodes/artery-node003.example.com")
|
||||
|
||||
// Send message to system C from system B.
|
||||
// Using invalid keys, this should fail
|
||||
val (remoteSysD, pathEchoD) = buildRemoteWithEchoActor("D-reread")
|
||||
contact(remoteSysB.actorSystem, pathEchoD)
|
||||
assertEnginesCreated(remoteSysB)
|
||||
assertEnginesCreated(remoteSysD)
|
||||
// the SSLContext references on sysB should differ
|
||||
val after = remoteSysB.sslContextRef.get()
|
||||
before shouldNot be(after)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
object RotatingKeysSSLEngineProviderSpec {
|
||||
val cacheTtlInSeconds = 1
|
||||
|
||||
private val arteryNode001Id = "ssl/artery-nodes/artery-node001.example.com"
|
||||
|
||||
private val baseConfig = """
|
||||
akka.remote.artery {
|
||||
## the large-messages channel in artery is not used for this tests
|
||||
## but we're enabling it to test it also creates its own SSLEngine
|
||||
large-message-destinations = [ "/user/large" ]
|
||||
}
|
||||
akka.remote.artery.ssl {
|
||||
ssl-engine-provider = akka.remote.artery.tcp.ssl.RotatingKeysSSLEngineProvider
|
||||
}
|
||||
"""
|
||||
|
||||
val resourcesConfig: String = baseConfig +
|
||||
s"""
|
||||
akka.remote.artery.ssl.rotating-keys-engine {
|
||||
key-file = ${getClass.getClassLoader.getResource(s"$arteryNode001Id.pem").getPath}
|
||||
cert-file = ${getClass.getClassLoader.getResource(s"$arteryNode001Id.crt").getPath}
|
||||
ca-cert-file = ${getClass.getClassLoader.getResource("ssl/exampleca.crt").getPath}
|
||||
ssl-context-cache-ttl = ${cacheTtlInSeconds}s
|
||||
}
|
||||
"""
|
||||
|
||||
val temporaryDirectory: Path = Files.createTempDirectory("akka-remote-rotating-keys-spec")
|
||||
val keyLocation = new File(temporaryDirectory.toFile, "tls.key")
|
||||
val certLocation = new File(temporaryDirectory.toFile, "tls.crt")
|
||||
val cacertLocation = new File(temporaryDirectory.toFile, "ca.crt")
|
||||
val tempFileConfig: String = baseConfig +
|
||||
s"""
|
||||
akka.remote.artery.ssl.rotating-keys-engine {
|
||||
key-file = ${temporaryDirectory.toFile.getAbsolutePath}/tls.key
|
||||
cert-file = ${temporaryDirectory.toFile.getAbsolutePath}/tls.crt
|
||||
ca-cert-file = ${temporaryDirectory.toFile.getAbsolutePath}/ca.crt
|
||||
ssl-context-cache-ttl = ${cacheTtlInSeconds}s
|
||||
}
|
||||
"""
|
||||
|
||||
private def deployResource(resourceName: String, to: Path): Unit = blocking {
|
||||
// manually ensuring files are deleted and copied to prevent races.
|
||||
try {
|
||||
val from = new File(getClass.getClassLoader.getResource(resourceName).getPath).toPath
|
||||
to.toFile.getParentFile.mkdirs()
|
||||
Files.copy(from, to, StandardCopyOption.REPLACE_EXISTING)
|
||||
} catch {
|
||||
case NonFatal(t) => throw new RuntimeException(s"Can't copy resource [$resourceName] to [$to].", t)
|
||||
}
|
||||
}
|
||||
def deployCaCert(): Unit = {
|
||||
deployResource("ssl/exampleca.crt", cacertLocation.toPath)
|
||||
}
|
||||
def deployKeySet(setName: String): Unit = {
|
||||
deployResource(setName + ".crt", certLocation.toPath)
|
||||
deployResource(setName + ".pem", keyLocation.toPath)
|
||||
}
|
||||
def cleanupTemporaryDirectory(): Unit = {
|
||||
temporaryDirectory.toFile.listFiles().foreach { _.delete() }
|
||||
temporaryDirectory.toFile.delete()
|
||||
}
|
||||
}
|
||||
|
||||
// Superclass to integration tests to test key rotation. Basic happy-path
|
||||
// integration tests of `RotatingKeysSSLEngineProvider` as an SSLEngineProvider for Akka Remote
|
||||
// are in `TlsTcpWithRotatingKeysSSLEngineSpec`
|
||||
abstract class RotatingKeysSSLEngineProviderSpec(extraConfig: String)
|
||||
extends ArteryMultiNodeSpec(ConfigFactory.parseString(extraConfig).withFallback(TlsTcpSpec.config))
|
||||
with ImplicitSender {
|
||||
import RotatingKeysSSLEngineProviderSpec._
|
||||
|
||||
var systemsToTerminate: Seq[ActorSystem] = Nil
|
||||
|
||||
// Assert the RemoteSystem created three pairs of SSLEngines (main channel,
|
||||
// large messages channel and control channel)
|
||||
// NOTE: the large message channel is not enabled but default. In this test suite
|
||||
// it's enabled via adding a value to the `large-message-destinations` setting
|
||||
def assertThreeChannelsAreCreated(remoteSystem: RemoteSystem) = {
|
||||
assertEnginesCreated(remoteSystem)
|
||||
assertEnginesCreated(remoteSystem)
|
||||
assertEnginesCreated(remoteSystem)
|
||||
}
|
||||
def assertEnginesCreated(remoteSystem: RemoteSystem) = {
|
||||
remoteSystem.sslProviderServerProbe.expectMsg("createServerSSLEngine")
|
||||
remoteSystem.sslProviderClientProbe.expectMsg("createClientSSLEngine")
|
||||
}
|
||||
def assertNoEnginesCreated(remoteSystem: RemoteSystem) = {
|
||||
remoteSystem.sslProviderServerProbe.expectNoMessage()
|
||||
remoteSystem.sslProviderClientProbe.expectNoMessage()
|
||||
}
|
||||
|
||||
// sleep to force the cache in sysB's instance to expire
|
||||
def awaitCacheExpiration(): Unit = {
|
||||
Thread.sleep((RotatingKeysSSLEngineProviderSpec.cacheTtlInSeconds + 1) * 1000)
|
||||
}
|
||||
|
||||
def contact(fromSystem: ActorSystem, toPath: ActorPath): Unit = {
|
||||
val senderOnSource = TestProbe()(fromSystem)
|
||||
fromSystem.actorSelection(toPath).tell(Identify(toPath.name), senderOnSource.ref)
|
||||
val targetRef: ActorRef = senderOnSource.expectMsgType[ActorIdentity].ref.get
|
||||
targetRef.tell("ping-1", senderOnSource.ref)
|
||||
senderOnSource.expectMsg("ping-1")
|
||||
}
|
||||
|
||||
def buildRemoteWithEchoActor(id: String): (RemoteSystem, ActorPath) = {
|
||||
val remoteSysB = new RemoteSystem(s"system$id", extraConfig, newRemoteSystem, address)
|
||||
systemsToTerminate :+= remoteSysB.actorSystem
|
||||
val actorName = s"echo$id"
|
||||
remoteSysB.actorSystem.actorOf(TestActors.echoActorProps, actorName)
|
||||
val pathEchoB = remoteSysB.rootActorPath / "user" / actorName
|
||||
(remoteSysB, pathEchoB)
|
||||
}
|
||||
|
||||
override def afterTermination(): Unit = {
|
||||
systemsToTerminate.foreach { systemToTerminate =>
|
||||
system.log.info(s"Terminating $systemToTerminate...")
|
||||
Await.result(systemToTerminate.terminate(), 10.seconds)
|
||||
}
|
||||
// Don't cleanup folder until all systems have terminated
|
||||
cleanupTemporaryDirectory()
|
||||
super.afterTermination()
|
||||
}
|
||||
}
|
||||
|
||||
class RemoteSystem(
|
||||
name: String,
|
||||
configString: String,
|
||||
newRemoteSystem: (Option[String], Option[String], Option[ActorSystemSetup]) => ActorSystem,
|
||||
address: (ActorSystem) => Address)(implicit system: ActorSystem) {
|
||||
|
||||
val sslProviderServerProbe: TestProbe = TestProbe()
|
||||
val sslProviderClientProbe: TestProbe = TestProbe()
|
||||
val sslContextRef = new AtomicReference[SSLContext]()
|
||||
|
||||
val sslProviderSetup =
|
||||
SSLEngineProviderSetup(
|
||||
sys => new ProbedSSLEngineProvider(sys, sslContextRef, sslProviderServerProbe, sslProviderClientProbe))
|
||||
|
||||
val actorSystem =
|
||||
newRemoteSystem(Some(configString), Some(name), Some(ActorSystemSetup(sslProviderSetup)))
|
||||
val remoteAddress = address(actorSystem)
|
||||
val rootActorPath = RootActorPath(remoteAddress)
|
||||
|
||||
}
|
||||
|
||||
class ProbedSSLEngineProvider(
|
||||
sys: ExtendedActorSystem,
|
||||
sslContextRef: AtomicReference[SSLContext],
|
||||
sslProviderServerProbe: TestProbe,
|
||||
sslProviderClientProbe: TestProbe)
|
||||
extends SSLEngineProvider {
|
||||
val delegate = new RotatingKeysSSLEngineProvider(sys)
|
||||
|
||||
override def createServerSSLEngine(hostname: String, port: Int): SSLEngine = {
|
||||
val engine = delegate.createServerSSLEngine(hostname, port)
|
||||
// only report the invocation on the probe when the invocation succeeds
|
||||
sslProviderServerProbe.ref ! "createServerSSLEngine"
|
||||
// invoked last to let `createEngine` be the trigger of the SSL context reconstruction
|
||||
sslContextRef.set(delegate.getSSLContext())
|
||||
engine
|
||||
}
|
||||
|
||||
override def createClientSSLEngine(hostname: String, port: Int): SSLEngine = {
|
||||
val engine = delegate.createClientSSLEngine(hostname, port)
|
||||
// only report the invocation on the probe when the invocation succeeds
|
||||
sslProviderClientProbe.ref ! "createClientSSLEngine"
|
||||
// invoked last to let `createEngine` be the trigger of the SSL context reconstruction
|
||||
sslContextRef.set(delegate.getSSLContext())
|
||||
engine
|
||||
}
|
||||
|
||||
override def verifyClientSession(hostname: String, session: SSLSession): Option[Throwable] =
|
||||
delegate.verifyClientSession(hostname, session)
|
||||
override def verifyServerSession(hostname: String, session: SSLSession): Option[Throwable] =
|
||||
delegate.verifyServerSession(hostname, session)
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package akka.remote.artery.tcp.ssl
|
||||
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
import org.scalatest.matchers.must.Matchers
|
||||
import org.scalatest.wordspec.AnyWordSpec
|
||||
|
||||
import scala.util.Try
|
||||
import scala.util.control.NonFatal
|
||||
|
||||
import akka.util.ccompat.JavaConverters._
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class TlsResourcesSpec extends AnyWordSpec with Matchers {
|
||||
|
||||
import TlsResourcesSpec._
|
||||
|
||||
val baseNode = "node"
|
||||
val baseClient = "client"
|
||||
val baseRsaClient = "rsa-client"
|
||||
val baseIslandServer = "island"
|
||||
val baseServers = Set("one", "two")
|
||||
val arteryNodeSet = Set("artery-nodes/artery-node001", "artery-nodes/artery-node002", "artery-nodes/artery-node003")
|
||||
|
||||
"example.com certificate family" must {
|
||||
"all include `example.com` as SAN" in {
|
||||
val sameSan = baseServers + baseClient + baseNode + baseRsaClient
|
||||
sameSan.foreach { prefix =>
|
||||
val serverCert = loadCert(s"/ssl/$prefix.example.com.crt")
|
||||
X509Readers.getAllSubjectNames(serverCert).contains("example.com") mustBe (true)
|
||||
}
|
||||
}
|
||||
|
||||
"not add `example.com` as SAN on the `island.example.com` certificate" in {
|
||||
val notExampleSan = arteryNodeSet + baseIslandServer
|
||||
notExampleSan.foreach { prefix =>
|
||||
val cert = loadCert(s"/ssl/$prefix.example.com.crt")
|
||||
X509Readers.getAllSubjectNames(cert).contains("example.com") mustBe (false)
|
||||
}
|
||||
}
|
||||
|
||||
val serverAuth = "1.3.6.1.5.5.7.3.1" // TLS Web server authentication
|
||||
val clientAuth = "1.3.6.1.5.5.7.3.2" // TLS Web client authentication
|
||||
"have certificates usable for client Auth" in {
|
||||
val clients = Set(baseClient, baseNode, baseRsaClient) ++ arteryNodeSet
|
||||
clients.foreach { prefix =>
|
||||
val cert = loadCert(s"/ssl/$prefix.example.com.crt")
|
||||
cert.getExtendedKeyUsage.asScala.contains(clientAuth) mustBe (true)
|
||||
}
|
||||
}
|
||||
|
||||
"have certificates usable for server Auth" in {
|
||||
val servers = baseServers + baseIslandServer + baseNode ++ arteryNodeSet
|
||||
servers.foreach { prefix =>
|
||||
val serverCert = loadCert(s"/ssl/$prefix.example.com.crt")
|
||||
serverCert.getExtendedKeyUsage.asScala.contains(serverAuth) mustBe (true)
|
||||
}
|
||||
}
|
||||
|
||||
"have RSA Private Keys in PEM format" in {
|
||||
val nodes = arteryNodeSet + baseNode + baseRsaClient
|
||||
nodes.foreach { prefix =>
|
||||
try {
|
||||
val pk = PemManagersProvider.loadPrivateKey(toAbsolutePath(s"ssl/$prefix.example.com.pem"))
|
||||
pk.getAlgorithm must be("RSA")
|
||||
} catch {
|
||||
case NonFatal(t) => fail(s"Failed test for $prefix", t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object TlsResourcesSpec {
|
||||
|
||||
def toAbsolutePath(resourceName: String): String =
|
||||
getClass.getClassLoader.getResource(resourceName).getPath
|
||||
|
||||
private val certFactory = CertificateFactory.getInstance("X.509")
|
||||
def loadCert(resourceName: String): X509Certificate = {
|
||||
val fin = classOf[X509ReadersSpec].getResourceAsStream(resourceName)
|
||||
try certFactory.generateCertificate(fin).asInstanceOf[X509Certificate]
|
||||
finally Try(fin.close())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Lightbend Inc. <https://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package akka.remote.artery.tcp.ssl
|
||||
|
||||
import org.scalatest.matchers.must.Matchers
|
||||
import org.scalatest.wordspec.AnyWordSpec
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class X509ReadersSpec extends AnyWordSpec with Matchers {
|
||||
import TlsResourcesSpec._
|
||||
|
||||
"X509Readers" must {
|
||||
"read a certificate's name from the CN" in {
|
||||
val island = loadCert("/ssl/island.example.com.crt")
|
||||
X509Readers.getAllSubjectNames(island) mustBe (Set("island.example.com"))
|
||||
}
|
||||
|
||||
"read both the CN and the subject alternative names" in {
|
||||
val serverCert = loadCert("/domain.crt")
|
||||
X509Readers.getAllSubjectNames(serverCert) mustBe (Set("akka-remote", "localhost"))
|
||||
}
|
||||
|
||||
"read a certificate that has no SAN extension" in {
|
||||
// a self-signed CA without SAN
|
||||
val island = loadCert("/ssl/pem/selfsigned-certificate.pem")
|
||||
X509Readers.getAllSubjectNames(island) mustBe (Set("0d207b68-9a20-4ee8-92cb-bf9699581cf8"))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -336,6 +336,7 @@ lazy val remote =
|
|||
.dependsOn(
|
||||
actor,
|
||||
stream,
|
||||
pki,
|
||||
protobuf % "test",
|
||||
actorTests % "test->test",
|
||||
testkit % "test->test",
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ object Dependencies {
|
|||
// Added explicitly for when artery tcp is used
|
||||
val agrona = "org.agrona" % "agrona" % agronaVersion // ApacheV2
|
||||
|
||||
val asnOne = "com.hierynomus" % "asn-one" % "0.4.0" // ApacheV2
|
||||
val asnOne = ("com.hierynomus" % "asn-one" % "0.4.0").exclude("org.slf4j", "slf4j-api") // ApacheV2
|
||||
|
||||
val jacksonCore = "com.fasterxml.jackson.core" % "jackson-core" % jacksonVersion // ApacheV2
|
||||
val jacksonAnnotations = "com.fasterxml.jackson.core" % "jackson-annotations" % jacksonVersion // ApacheV2
|
||||
|
|
@ -202,7 +202,7 @@ object Dependencies {
|
|||
Seq(
|
||||
asnOne,
|
||||
// pull up slf4j version from the one provided transitively in asnOne to fix unidoc
|
||||
Compile.slf4jApi % "provided",
|
||||
Compile.slf4jApi,
|
||||
Test.scalatest)
|
||||
|
||||
val remoteDependencies = Seq(netty, aeronDriver, aeronClient)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue