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:
Ignasi Marimon-Clos 2020-06-05 17:34:44 +02:00 committed by GitHub
parent 9f05948f29
commit c287fff034
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
58 changed files with 2064 additions and 266 deletions

View file

@ -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
}
}
}
}

View file

@ -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
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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))
}
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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}"))
}
}
}

View file

@ -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
}
}

View file

@ -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

View 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-----

View 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.

View file

@ -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-----

View file

@ -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-----

View file

@ -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-----

View file

@ -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-----

View file

@ -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-----

View file

@ -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-----

View 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-----

View 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-----

Binary file not shown.

View 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/.

View 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
}

View 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

View 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

View 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-----

View 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-----

View 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-----

View 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-----

Binary file not shown.

View file

@ -0,0 +1 @@
kLnCu3rboe

View 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.

View 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-----

View file

@ -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-----

View file

@ -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-----

View file

@ -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-----

View 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-----

Binary file not shown.

View file

@ -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

View file

@ -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
}
}
}
}

View file

@ -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)))

View file

@ -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
}
}

View file

@ -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()
}
}
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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())
}
}

View file

@ -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"))
}
}
}

View file

@ -336,6 +336,7 @@ lazy val remote =
.dependsOn(
actor,
stream,
pki,
protobuf % "test",
actorTests % "test->test",
testkit % "test->test",

View file

@ -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)