From c287fff034b598c06ef87bda78dc9851a51da94f Mon Sep 17 00:00:00 2001 From: Ignasi Marimon-Clos Date: Fri, 5 Jun 2020 17:34:44 +0200 Subject: [PATCH] k8s-friendly SSLEngine provider (simplified) (#29152) Co-authored-by: Arnout Engelen Co-authored-by: James Roper --- akka-remote/src/main/resources/reference.conf | 58 +++- .../artery/tcp/ConfigSSLEngineProvider.scala | 158 +++++++++ .../remote/artery/tcp/SSLEngineProvider.scala | 182 +--------- .../artery/tcp/SecureRandomFactory.scala | 58 ++++ .../artery/tcp/ssl/PemManagersProvider.scala | 91 +++++ .../ssl/RotatingKeysSSLEngineProvider.scala | 174 ++++++++++ .../artery/tcp/ssl/SSLEngineConfig.scala | 41 +++ .../artery/tcp/ssl/SessionVerifier.scala | 68 ++++ .../remote/artery/tcp/ssl/X509Readers.scala | 44 +++ akka-remote/src/test/resources/Makefile | 17 +- akka-remote/src/test/resources/domain.crt | 20 ++ akka-remote/src/test/resources/keystore | Bin 1573 -> 2421 bytes akka-remote/src/test/resources/ssl/README.md | 7 + .../artery-node001.example.com.crt | 21 ++ .../artery-node001.example.com.p12 | Bin 0 -> 4143 bytes .../artery-node001.example.com.pem | 27 ++ .../artery-node002.example.com.crt | 21 ++ .../artery-node002.example.com.p12 | Bin 0 -> 4143 bytes .../artery-node002.example.com.pem | 27 ++ .../artery-node003.example.com.crt | 21 ++ .../artery-node003.example.com.p12 | Bin 0 -> 4143 bytes .../artery-node003.example.com.pem | 27 ++ .../test/resources/ssl/client.example.com.crt | 16 + .../test/resources/ssl/client.example.com.p12 | Bin 0 -> 2708 bytes .../src/test/resources/ssl/exampleca.crt | 14 + .../src/test/resources/ssl/exampleca.p12 | Bin 0 -> 1114 bytes .../ssl/gen-artery-nodes.example.com.sh | 21 ++ .../src/test/resources/ssl/gen-functions.sh | 102 ++++++ akka-remote/src/test/resources/ssl/genca.sh | 27 ++ .../src/test/resources/ssl/gencerts.sh | 27 ++ .../test/resources/ssl/island.example.com.crt | 16 + .../test/resources/ssl/island.example.com.p12 | Bin 0 -> 2700 bytes .../test/resources/ssl/node.example.com.crt | 21 ++ .../test/resources/ssl/node.example.com.p12 | Bin 0 -> 4075 bytes .../test/resources/ssl/node.example.com.pem | 27 ++ .../test/resources/ssl/one.example.com.crt | 16 + .../test/resources/ssl/one.example.com.p12 | Bin 0 -> 2694 bytes akka-remote/src/test/resources/ssl/password | 1 + .../src/test/resources/ssl/pem/README.md | 14 + .../src/test/resources/ssl/pem/pkcs1.pem | 27 ++ .../ssl/pem/selfsigned-certificate.pem | 19 + .../resources/ssl/rsa-client.example.com.crt | 21 ++ .../resources/ssl/rsa-client.example.com.p12 | Bin 0 -> 4103 bytes .../resources/ssl/rsa-client.example.com.pem | 27 ++ .../test/resources/ssl/two.example.com.crt | 16 + .../test/resources/ssl/two.example.com.p12 | Bin 0 -> 2694 bytes akka-remote/src/test/resources/truststore | Bin 621 -> 1114 bytes .../remote/artery/ArterySpecSupport.scala | 3 + .../artery/tcp/SecureRandomFactorySpec.scala | 79 +++++ .../akka/remote/artery/tcp/TlsTcpSpec.scala | 124 +++---- .../tcp/ssl/CipherSuiteSupportCheck.scala | 68 ++++ .../tcp/ssl/PeerSubjectVerifierSpec.scala | 67 ++++ .../tcp/ssl/PemManagersProviderSpec.scala | 55 +++ .../RotatingKeysSSLEngineProviderSpec.scala | 326 ++++++++++++++++++ .../artery/tcp/ssl/TlsResourcesSpec.scala | 94 +++++ .../artery/tcp/ssl/X509ReadersSpec.scala | 35 ++ build.sbt | 1 + project/Dependencies.scala | 4 +- 58 files changed, 2064 insertions(+), 266 deletions(-) create mode 100644 akka-remote/src/main/scala/akka/remote/artery/tcp/ConfigSSLEngineProvider.scala create mode 100644 akka-remote/src/main/scala/akka/remote/artery/tcp/SecureRandomFactory.scala create mode 100644 akka-remote/src/main/scala/akka/remote/artery/tcp/ssl/PemManagersProvider.scala create mode 100644 akka-remote/src/main/scala/akka/remote/artery/tcp/ssl/RotatingKeysSSLEngineProvider.scala create mode 100644 akka-remote/src/main/scala/akka/remote/artery/tcp/ssl/SSLEngineConfig.scala create mode 100644 akka-remote/src/main/scala/akka/remote/artery/tcp/ssl/SessionVerifier.scala create mode 100644 akka-remote/src/main/scala/akka/remote/artery/tcp/ssl/X509Readers.scala create mode 100644 akka-remote/src/test/resources/domain.crt create mode 100644 akka-remote/src/test/resources/ssl/README.md create mode 100644 akka-remote/src/test/resources/ssl/artery-nodes/artery-node001.example.com.crt create mode 100644 akka-remote/src/test/resources/ssl/artery-nodes/artery-node001.example.com.p12 create mode 100644 akka-remote/src/test/resources/ssl/artery-nodes/artery-node001.example.com.pem create mode 100644 akka-remote/src/test/resources/ssl/artery-nodes/artery-node002.example.com.crt create mode 100644 akka-remote/src/test/resources/ssl/artery-nodes/artery-node002.example.com.p12 create mode 100644 akka-remote/src/test/resources/ssl/artery-nodes/artery-node002.example.com.pem create mode 100644 akka-remote/src/test/resources/ssl/artery-nodes/artery-node003.example.com.crt create mode 100644 akka-remote/src/test/resources/ssl/artery-nodes/artery-node003.example.com.p12 create mode 100644 akka-remote/src/test/resources/ssl/artery-nodes/artery-node003.example.com.pem create mode 100644 akka-remote/src/test/resources/ssl/client.example.com.crt create mode 100644 akka-remote/src/test/resources/ssl/client.example.com.p12 create mode 100644 akka-remote/src/test/resources/ssl/exampleca.crt create mode 100644 akka-remote/src/test/resources/ssl/exampleca.p12 create mode 100755 akka-remote/src/test/resources/ssl/gen-artery-nodes.example.com.sh create mode 100644 akka-remote/src/test/resources/ssl/gen-functions.sh create mode 100755 akka-remote/src/test/resources/ssl/genca.sh create mode 100755 akka-remote/src/test/resources/ssl/gencerts.sh create mode 100644 akka-remote/src/test/resources/ssl/island.example.com.crt create mode 100644 akka-remote/src/test/resources/ssl/island.example.com.p12 create mode 100644 akka-remote/src/test/resources/ssl/node.example.com.crt create mode 100644 akka-remote/src/test/resources/ssl/node.example.com.p12 create mode 100644 akka-remote/src/test/resources/ssl/node.example.com.pem create mode 100644 akka-remote/src/test/resources/ssl/one.example.com.crt create mode 100644 akka-remote/src/test/resources/ssl/one.example.com.p12 create mode 100644 akka-remote/src/test/resources/ssl/password create mode 100644 akka-remote/src/test/resources/ssl/pem/README.md create mode 100644 akka-remote/src/test/resources/ssl/pem/pkcs1.pem create mode 100644 akka-remote/src/test/resources/ssl/pem/selfsigned-certificate.pem create mode 100644 akka-remote/src/test/resources/ssl/rsa-client.example.com.crt create mode 100644 akka-remote/src/test/resources/ssl/rsa-client.example.com.p12 create mode 100644 akka-remote/src/test/resources/ssl/rsa-client.example.com.pem create mode 100644 akka-remote/src/test/resources/ssl/two.example.com.crt create mode 100644 akka-remote/src/test/resources/ssl/two.example.com.p12 create mode 100644 akka-remote/src/test/scala/akka/remote/artery/tcp/SecureRandomFactorySpec.scala create mode 100644 akka-remote/src/test/scala/akka/remote/artery/tcp/ssl/CipherSuiteSupportCheck.scala create mode 100644 akka-remote/src/test/scala/akka/remote/artery/tcp/ssl/PeerSubjectVerifierSpec.scala create mode 100644 akka-remote/src/test/scala/akka/remote/artery/tcp/ssl/PemManagersProviderSpec.scala create mode 100644 akka-remote/src/test/scala/akka/remote/artery/tcp/ssl/RotatingKeysSSLEngineProviderSpec.scala create mode 100644 akka-remote/src/test/scala/akka/remote/artery/tcp/ssl/TlsResourcesSpec.scala create mode 100644 akka-remote/src/test/scala/akka/remote/artery/tcp/ssl/X509ReadersSpec.scala diff --git a/akka-remote/src/main/resources/reference.conf b/akka-remote/src/main/resources/reference.conf index 8e74e151c8..06a0248730 100644 --- a/akka-remote/src/main/resources/reference.conf +++ b/akka-remote/src/main/resources/reference.conf @@ -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 + } } } } diff --git a/akka-remote/src/main/scala/akka/remote/artery/tcp/ConfigSSLEngineProvider.scala b/akka-remote/src/main/scala/akka/remote/artery/tcp/ConfigSSLEngineProvider.scala new file mode 100644 index 0000000000..cf79ba5b3b --- /dev/null +++ b/akka-remote/src/main/scala/akka/remote/artery/tcp/ConfigSSLEngineProvider.scala @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2020 Lightbend Inc. + */ + +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 + +} diff --git a/akka-remote/src/main/scala/akka/remote/artery/tcp/SSLEngineProvider.scala b/akka-remote/src/main/scala/akka/remote/artery/tcp/SSLEngineProvider.scala index 29767e1f1d..8e624eb904 100644 --- a/akka-remote/src/main/scala/akka/remote/artery/tcp/SSLEngineProvider.scala +++ b/akka-remote/src/main/scala/akka/remote/artery/tcp/SSLEngineProvider.scala @@ -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 - } -} diff --git a/akka-remote/src/main/scala/akka/remote/artery/tcp/SecureRandomFactory.scala b/akka-remote/src/main/scala/akka/remote/artery/tcp/SecureRandomFactory.scala new file mode 100644 index 0000000000..a9879372dc --- /dev/null +++ b/akka-remote/src/main/scala/akka/remote/artery/tcp/SecureRandomFactory.scala @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020 Lightbend Inc. + */ + +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 + } +} diff --git a/akka-remote/src/main/scala/akka/remote/artery/tcp/ssl/PemManagersProvider.scala b/akka-remote/src/main/scala/akka/remote/artery/tcp/ssl/PemManagersProvider.scala new file mode 100644 index 0000000000..5b803721c8 --- /dev/null +++ b/akka-remote/src/main/scala/akka/remote/artery/tcp/ssl/PemManagersProvider.scala @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2020 Lightbend Inc. + */ + +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)) + } + +} diff --git a/akka-remote/src/main/scala/akka/remote/artery/tcp/ssl/RotatingKeysSSLEngineProvider.scala b/akka-remote/src/main/scala/akka/remote/artery/tcp/ssl/RotatingKeysSSLEngineProvider.scala new file mode 100644 index 0000000000..631d8f63a5 --- /dev/null +++ b/akka-remote/src/main/scala/akka/remote/artery/tcp/ssl/RotatingKeysSSLEngineProvider.scala @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2020 Lightbend Inc. + */ + +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) + +} diff --git a/akka-remote/src/main/scala/akka/remote/artery/tcp/ssl/SSLEngineConfig.scala b/akka-remote/src/main/scala/akka/remote/artery/tcp/ssl/SSLEngineConfig.scala new file mode 100644 index 0000000000..82a475bd5f --- /dev/null +++ b/akka-remote/src/main/scala/akka/remote/artery/tcp/ssl/SSLEngineConfig.scala @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2020 Lightbend Inc. + */ + +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 +} diff --git a/akka-remote/src/main/scala/akka/remote/artery/tcp/ssl/SessionVerifier.scala b/akka-remote/src/main/scala/akka/remote/artery/tcp/ssl/SessionVerifier.scala new file mode 100644 index 0000000000..2a2771d08e --- /dev/null +++ b/akka-remote/src/main/scala/akka/remote/artery/tcp/ssl/SessionVerifier.scala @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2020 Lightbend Inc. + */ + +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}")) + } + } + +} diff --git a/akka-remote/src/main/scala/akka/remote/artery/tcp/ssl/X509Readers.scala b/akka-remote/src/main/scala/akka/remote/artery/tcp/ssl/X509Readers.scala new file mode 100644 index 0000000000..51f44374e2 --- /dev/null +++ b/akka-remote/src/main/scala/akka/remote/artery/tcp/ssl/X509Readers.scala @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 Lightbend Inc. + */ + +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 + } + +} diff --git a/akka-remote/src/test/resources/Makefile b/akka-remote/src/test/resources/Makefile index 5343762ec5..86edcc4eaf 100644 --- a/akka-remote/src/test/resources/Makefile +++ b/akka-remote/src/test/resources/Makefile @@ -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 diff --git a/akka-remote/src/test/resources/domain.crt b/akka-remote/src/test/resources/domain.crt new file mode 100644 index 0000000000..facd3f74a6 --- /dev/null +++ b/akka-remote/src/test/resources/domain.crt @@ -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----- diff --git a/akka-remote/src/test/resources/keystore b/akka-remote/src/test/resources/keystore index a76d66d7ab4dcc60e5d8baf937df526e002e09ce..14a0e51f7f159fcf237d284e2886745a7e0be84a 100644 GIT binary patch literal 2421 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 nX?l9tZShpkch--7Pky=6_>6AQ!F?UF(SFtI1m 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 literal 0 HcmV?d00001 diff --git a/akka-remote/src/test/resources/ssl/artery-nodes/artery-node001.example.com.pem b/akka-remote/src/test/resources/ssl/artery-nodes/artery-node001.example.com.pem new file mode 100644 index 0000000000..501ac638a7 --- /dev/null +++ b/akka-remote/src/test/resources/ssl/artery-nodes/artery-node001.example.com.pem @@ -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----- diff --git a/akka-remote/src/test/resources/ssl/artery-nodes/artery-node002.example.com.crt b/akka-remote/src/test/resources/ssl/artery-nodes/artery-node002.example.com.crt new file mode 100644 index 0000000000..db18c33158 --- /dev/null +++ b/akka-remote/src/test/resources/ssl/artery-nodes/artery-node002.example.com.crt @@ -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----- diff --git a/akka-remote/src/test/resources/ssl/artery-nodes/artery-node002.example.com.p12 b/akka-remote/src/test/resources/ssl/artery-nodes/artery-node002.example.com.p12 new file mode 100644 index 0000000000000000000000000000000000000000..bbc0d4172a4920956708db8572730ee50dd72606 GIT binary patch literal 4143 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 literal 0 HcmV?d00001 diff --git a/akka-remote/src/test/resources/ssl/artery-nodes/artery-node002.example.com.pem b/akka-remote/src/test/resources/ssl/artery-nodes/artery-node002.example.com.pem new file mode 100644 index 0000000000..a126038d82 --- /dev/null +++ b/akka-remote/src/test/resources/ssl/artery-nodes/artery-node002.example.com.pem @@ -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----- diff --git a/akka-remote/src/test/resources/ssl/artery-nodes/artery-node003.example.com.crt b/akka-remote/src/test/resources/ssl/artery-nodes/artery-node003.example.com.crt new file mode 100644 index 0000000000..83d4472930 --- /dev/null +++ b/akka-remote/src/test/resources/ssl/artery-nodes/artery-node003.example.com.crt @@ -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----- diff --git a/akka-remote/src/test/resources/ssl/artery-nodes/artery-node003.example.com.p12 b/akka-remote/src/test/resources/ssl/artery-nodes/artery-node003.example.com.p12 new file mode 100644 index 0000000000000000000000000000000000000000..63b32d5ba7ae9844e03926e485930566a3195687 GIT binary patch literal 4143 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 beJGRZ`V*zcU6oOG7bX70YA`V~wk-evsU7*m literal 0 HcmV?d00001 diff --git a/akka-remote/src/test/resources/ssl/exampleca.crt b/akka-remote/src/test/resources/ssl/exampleca.crt new file mode 100644 index 0000000000..6dcee07369 --- /dev/null +++ b/akka-remote/src/test/resources/ssl/exampleca.crt @@ -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----- diff --git a/akka-remote/src/test/resources/ssl/exampleca.p12 b/akka-remote/src/test/resources/ssl/exampleca.p12 new file mode 100644 index 0000000000000000000000000000000000000000..b936601bb34096e54d746a89035e4d1e99d674e6 GIT binary patch literal 1114 zcmXqLVhLknWHxAG;b-I2YV&CO&dbQoxS)xJfu)K0k3kdj4}-?1$TE%BSsJexG+s1l zJjccj)xg8W$h4r`pwvK@-DzImDat8O25<=e)@%y2k> z#Y2X1hj)`t)wW}cJ(aS9s${+xyjSeIBY%7L8BXbDy|nP0wweAS+s*_q6+ipTv`+MU zkC^_6nX=axFTP@{YFNT@!~XMSt#^ByWDVU66yXl!6fqQ$6JkhZs9;EB$Ym&C$YDrj zNM=YxRw8OB!XgxsnVYI$XliL_U}R=yXl!U`Y0$*X12>1AZ9x;$UxOy5pG=Gl22D(# z5HgG?zGMUW^2n2{__Te-zb(IPsaf50LH>YS$X>878=9DQ6g{6&$iUt%*0OwesP;^Q zdX4_T177o%P5<@LzDaH|(`{+K1!regS~acPHD&eR^r`EOViGI5g`NdV+Of8^y`6gJ zV_(JruB+vB55Icwor4L6#ksyf!bP^1@#|~PD zh1`WxS#DnA%aDzGta9#xdR=b9slD$uZn;V5ZhiCFq>D~j zJ`0#anU>Cvy3lah=a;pjWcVI68UGDh?PaX*!lny6zha=B8|=b$m&rKQ=aAsM@EuNq z(N~}5u5^k~7Y+WNo%CzMxivzw_dd$pXZ0dVZ@t-!nG3>Zu&*$=5@K=Xm~gwgc4S)R zDlefe>C7ip@7h}=Sw3{EGqxbN_S8 z^}XDDRqscA^P}`9OJ1sTJ?>fa<>kj6qNnDSs_)xj-7B^J%<-j1+r)3rnEknP*Y8aM z-+%Y?%BbxKkN+Q=r&S=sCQ$NoqxNY@n<@#+w-ohKK^y9=Xdk1j-l)S zGM7wuH@MV!+Wi!#N!h_qf&4-rB~1>=hks12mbvzJxdGQ!!J_bz+06!a28srpY^>UR z%uG_O3@jqj?grw)*Z%$AUF=q=x#na|*w=FmEFxxY=kI*_k)&~@#`D5Wi=Mxddrv7a JF*CL;0080}G8;&X{)lNB~A+b6~X|ru$(w?YXzPjfAit5#V?OJyhxYZel6czuk zDV=inte9%N@w`_Hn^i1&H-68sv3zsJFv7qX?m$ivLp3=ShD?THh8%`OhCGH820eyU zh6;v6hFpdMh8%`e20eykhJ1!x14V=;Q9}_Hp^(hnR0Ts*OG5)AGc!X=Gh<_eCQco= zB6hX~O`Ng@O`MWUj0^@%oI(g0MihUuf&9HLoh|O?H?NDYU8bKtV>Bb-nO|rQ*xwCJ z95)V5yDOW$il4(UB<_gSzJouXYs)1H1b1J{e=*!#Zl@q;xht7U}H}Ox?c1h2= zT~i?V=w!?S6DHNT;CmMylr+ZG2sU1N5|*^dv?7J!{{^qB&YugMPs=w=`_IVw@!kK3 z3zc=rj4pm=MLa$ePS)SwXQQ@!x8TCOBi=Xkw zT~jXP)ay<31!(D0s0`Ls|LY{^;_eS<0MK%;Ma(D!V(?AI~_$I>&g=es7&47P|F2 zSFJyOZRzDb$2bxnzS^}?cH$e6lln`lX1si275(SvuR`IC4$l+>PakN>yH($md47`b zac#w^lbR;Y*3#Mg1YemeG7i7_$wkfFhqIdLh9Sf11)q5W2MEi4z z=(p7GW z|7CWKcK7T({{0`0KK&TP+Wp~CR2_5KDZbaik`~wZG{;Lld%NfH8l%^HSBg(NDVw_1 zM(O3b9%1Jn|NTCun3{c-Yi2K9-Oyc;B{=_od7g_*)WyI*8B;{(KM!+1RJ0|*(4A}T z@@cZu`?B1{maEj?(6l&Gd-^+Y;+9SAj(j_ z=tRC#m}I%rz9pqw3+`I{Ry2)xI?uw?@0-Jlln+HzN^^em!8EkeT`PB;G=9SpFcanet7bx<%@i+7&<40yPey@ z7*LXY{D84*k#F1IGq39F)6doM%Utah628zrS$*9^4dI7Ne`TmmIrLaybIFIu-Fqcg z-@AQq{=ym46P%v!xxdL?+vBwBmfFtxzw=uSFMo@d{l7|8H*l%&<5o@Qc{+P8->$Cc zH9C!U@DhTAj`@P7O%yoX<1C(d8EZgwCW$1 z5Nq0>kB3)Ou4LEM+a9%L){~3a+&4siPufrxe8D9T>s6R|KpSU?TlI3xkfks zNj_29_Kss7=cHc_6Sel4$9>qPul?jNbKWAA6geHAXYmfp<71-R!~gG@k)tR5?7%E8 z-spvY1-x^Z?(VL6v86}iOzCQKlCi9HvPEUuGi$Mp#?V+i|%uVr|KDt z^;W%Tk&VpKvD#@^mTyyK_$~PJKm%OAqQQM+{x4TsfK4_N=^HlXF_ew#B?TJ?CcYUEx!D;kY3@V*6#mo9l~KO?vnIx%e@KhSbxt znrU%zc{_6Tm&`xL@ZJApzizmdx|6r%spw<7+Y6cxbH12#-A7JZ*;!*%bzVm{ZI0JfT@-BS~9 z>3aRGxc2y#_G+i8b)GCY{@m5$e)>vh$ATSGF8n^0R`B%`BdFLz0sAzrb;=xJx%X(Jn{$Fy#s72`3FW{~a@yj3 zzf08buR9f?(SD~!aNf%09i5WWt9{NL+xgvg!eWD8k#F{*>3VS-wJ$#o?MKXJN5p%y(!b7IaL~o{zoL5bo3^k{l z*PS(AAhO!tN%WhCz`0HPLS6;$Vk=$#$1ZW^o%aV;7q>lL!1M0ih5Zv44{9mjte*Jf z#?rsXU)HFk|9`ZYDcW6W@7(SFk}18!jqRBIcgyNFyZ*zge3(p|o zAKhbrrN;Qm+yCJ&m~ZmyGEbX*YsT@motz(!7V3uH$qPR*yK{QQ72}o_+nCNCcTdEAsRu1ouG#AqzOQ2mGfVD?+Ls*AN< z%hKMa#IQz}Ejq_1dUl^`u;l4=i+%k&y=N@CdiB<1Nx4$p#WVh|_^b0_N5!Wn3ij5{ zleLdWM|C&*s9$k7aQob$iCw(jQQd8;-LkJd{&O|!%%RLI)91goZfbD7bosA^$E-yL zb_R+DoNTPxe9TNztPCt7sZ!M|nKj?ZTTVNv8yjIJr!w7!gGD5Ad+Wz783K*x=k|F0 R*e$bl3+uZVOw5dJ3jlW1>KXt5 literal 0 HcmV?d00001 diff --git a/akka-remote/src/test/resources/ssl/node.example.com.crt b/akka-remote/src/test/resources/ssl/node.example.com.crt new file mode 100644 index 0000000000..0bf0ff16fa --- /dev/null +++ b/akka-remote/src/test/resources/ssl/node.example.com.crt @@ -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----- diff --git a/akka-remote/src/test/resources/ssl/node.example.com.p12 b/akka-remote/src/test/resources/ssl/node.example.com.p12 new file mode 100644 index 0000000000000000000000000000000000000000..ca90e540a15addd033e8c92f7c863e73e13c2a1d GIT binary patch literal 4075 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 literal 0 HcmV?d00001 diff --git a/akka-remote/src/test/resources/ssl/rsa-client.example.com.pem b/akka-remote/src/test/resources/ssl/rsa-client.example.com.pem new file mode 100644 index 0000000000..48f4475d14 --- /dev/null +++ b/akka-remote/src/test/resources/ssl/rsa-client.example.com.pem @@ -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----- diff --git a/akka-remote/src/test/resources/ssl/two.example.com.crt b/akka-remote/src/test/resources/ssl/two.example.com.crt new file mode 100644 index 0000000000..fc766d02d3 --- /dev/null +++ b/akka-remote/src/test/resources/ssl/two.example.com.crt @@ -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----- diff --git a/akka-remote/src/test/resources/ssl/two.example.com.p12 b/akka-remote/src/test/resources/ssl/two.example.com.p12 new file mode 100644 index 0000000000000000000000000000000000000000..38b932093875281d061ce77edc2e218e9826b242 GIT binary patch literal 2694 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 KE)z3j+X4V;qW8c6 literal 0 HcmV?d00001 diff --git a/akka-remote/src/test/resources/truststore b/akka-remote/src/test/resources/truststore index f70de5dde24b3f1c2fab5ce43c7396fabf965389..7f6041469c5a3ca7b83c29216907c392cd49b716 100644 GIT binary patch literal 1114 zcmXqLVhLknWHxAG;b-I2YV&CO&dbQoxS)xJfu)K0k3kdj4}&J=F9<2N1x?IP4VsuA zGBGk3G%?>o$S@jcvT;F8;9+DlkY*7P5B2aaXR*^fI6d;w0hNruf?uzlV`64F9MI6j zyx=Ocp7RGc_46B#-!b+U;CgjTOzPx|9k;(KiER&zmfu~rbDMq2sTuc<=S$gXu9Dk$ zW157`l*i&HF6*d$`Mt*C@8_?()h1;8eRFR914G^XO(uB?IU2mH-B}9f%y0BEE^(XI z#<4(JrF45xaq#g2Td&`$={oMkVJO!0v%2ob#$DTU5|X#A&+nf9_;cUOu=Ud}>S-iK zteW>dxS6fZWsTp%Ab9R>&C^{={{@BI;9PZPqfMlKk>V0CM=Vc!WXRaw&vD#XNgS@ zpNsC<5USM{YoV%aqoDaEH1W>XISo(Ggig3O!(48^^xW3ZPiG16`D#A9+@~Y@S}Q50 z<&GYY@1M+tYLchhT@Cqu z%g^zy?G(9f`fT1bx6EyyD6mB^;sKuo*c{+jnx8Tx4 zi!!ErH@(-WJ?X$N*mbBYQLi8*+D&NB+UR-A*&Eb5nSP1vopSBd(>ozT7i{^z_~so? zD&z}a#JyzpGByR5tDBpCMRGX4ub)=7>e+(nCl)=jGH@-}{@}uvB9`9Yvlw)%lz0Ox z4+~xF>{)juuBz7WM(=V(?)Jh879X8m>zETR{QN3@bmxjj{t9n5UFQA#(`+WYUc7bm zZsBp~MVmKqP3En0iJKWWxmxkpRc&4Gdf9i=Yh%uN-q_yl;u~aKacM=zp>?fkGN(;D z?cYV(%xpBd7#qDwbH@5RAzm(}duE^87b5a?it!iWWA?wUzUf|{_?|=Asrawqd&^aC z(qhk^w%vU_Vovb_nF!wL=h+u$WTj1>AO6%Nc=4iQo7+!L2!A&SSi|X(x?^eUJVB4d z)GN1-)|42lSTQrHCq6!1yoASG`Nq6gFT7noF|54JYCI{e?SSEpWp_kxTztp3WP@eN zg|I83DvsA)|JQtRTJZ2m?amo4UVOZ4EsZ&Bou|BcHy(-Jt51rN>TGMz2Pu#jE$(}N>hSVW#3;asr1@(dfh#!bg76L`5q N1-0y$m>Js^005921uFmm literal 621 zcmezO_TO6u1_mYu1_nkj&6-=8om$Djz?l6tZr>&b)(AaQ14{-5CVhh@CT)Wz#wiP! znHZUvL`)(Q!#Xu~_j0pbzxnG5-*<)G>Cy(gY@Awc9&O)w85y}*84O$vxeYkkm_u3E zgqflo4S5W>KpYMp=JM1eLm>kJ5RZ$8(y#l$x7g zl4>9)&TD9CU~Xt?XlP_)Y!n6NS{Rx@xzw_zaXzy98Ce;a8+#cH8atU98yUKmdFCov z9^p~`IPrMSfd_V6-`v~o+}=8G(uy4$AI#b;`)q<_@4qu02d_;G*}?T@eirj32eC79 zGvD@A?cFKhp}>B-D@(p=@g+}feYJv!zel&L z)M>R{RVke>{_*$1fQ@16XBnMdxASs;PK}q{x&WDV^6u4ir RK^371Vhhx6|21ln1^|4#*f0P9 diff --git a/akka-remote/src/test/scala/akka/remote/artery/ArterySpecSupport.scala b/akka-remote/src/test/scala/akka/remote/artery/ArterySpecSupport.scala index 099ef92999..2be5d7e3c1 100644 --- a/akka-remote/src/test/scala/akka/remote/artery/ArterySpecSupport.scala +++ b/akka-remote/src/test/scala/akka/remote/artery/ArterySpecSupport.scala @@ -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 diff --git a/akka-remote/src/test/scala/akka/remote/artery/tcp/SecureRandomFactorySpec.scala b/akka-remote/src/test/scala/akka/remote/artery/tcp/SecureRandomFactorySpec.scala new file mode 100644 index 0000000000..33e3de6e31 --- /dev/null +++ b/akka-remote/src/test/scala/akka/remote/artery/tcp/SecureRandomFactorySpec.scala @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2020 Lightbend Inc. + */ + +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 + } + } + } +} diff --git a/akka-remote/src/test/scala/akka/remote/artery/tcp/TlsTcpSpec.scala b/akka-remote/src/test/scala/akka/remote/artery/tcp/TlsTcpSpec.scala index 4925446996..b530ffa0f9 100644 --- a/akka-remote/src/test/scala/akka/remote/artery/tcp/TlsTcpSpec.scala +++ b/akka-remote/src/test/scala/akka/remote/artery/tcp/TlsTcpSpec.scala @@ -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))) diff --git a/akka-remote/src/test/scala/akka/remote/artery/tcp/ssl/CipherSuiteSupportCheck.scala b/akka-remote/src/test/scala/akka/remote/artery/tcp/ssl/CipherSuiteSupportCheck.scala new file mode 100644 index 0000000000..82941e2d43 --- /dev/null +++ b/akka-remote/src/test/scala/akka/remote/artery/tcp/ssl/CipherSuiteSupportCheck.scala @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2020 Lightbend Inc. + */ + +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 + } + +} diff --git a/akka-remote/src/test/scala/akka/remote/artery/tcp/ssl/PeerSubjectVerifierSpec.scala b/akka-remote/src/test/scala/akka/remote/artery/tcp/ssl/PeerSubjectVerifierSpec.scala new file mode 100644 index 0000000000..150b8280d8 --- /dev/null +++ b/akka-remote/src/test/scala/akka/remote/artery/tcp/ssl/PeerSubjectVerifierSpec.scala @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2020 Lightbend Inc. + */ + +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() + } + } +} diff --git a/akka-remote/src/test/scala/akka/remote/artery/tcp/ssl/PemManagersProviderSpec.scala b/akka-remote/src/test/scala/akka/remote/artery/tcp/ssl/PemManagersProviderSpec.scala new file mode 100644 index 0000000000..5973262135 --- /dev/null +++ b/akka-remote/src/test/scala/akka/remote/artery/tcp/ssl/PemManagersProviderSpec.scala @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2020 Lightbend Inc. + */ + +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 +} diff --git a/akka-remote/src/test/scala/akka/remote/artery/tcp/ssl/RotatingKeysSSLEngineProviderSpec.scala b/akka-remote/src/test/scala/akka/remote/artery/tcp/ssl/RotatingKeysSSLEngineProviderSpec.scala new file mode 100644 index 0000000000..89eb0e6aa0 --- /dev/null +++ b/akka-remote/src/test/scala/akka/remote/artery/tcp/ssl/RotatingKeysSSLEngineProviderSpec.scala @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2016-2020 Lightbend Inc. + */ + +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) +} diff --git a/akka-remote/src/test/scala/akka/remote/artery/tcp/ssl/TlsResourcesSpec.scala b/akka-remote/src/test/scala/akka/remote/artery/tcp/ssl/TlsResourcesSpec.scala new file mode 100644 index 0000000000..df7516b455 --- /dev/null +++ b/akka-remote/src/test/scala/akka/remote/artery/tcp/ssl/TlsResourcesSpec.scala @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2020 Lightbend Inc. + */ + +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()) + } +} diff --git a/akka-remote/src/test/scala/akka/remote/artery/tcp/ssl/X509ReadersSpec.scala b/akka-remote/src/test/scala/akka/remote/artery/tcp/ssl/X509ReadersSpec.scala new file mode 100644 index 0000000000..20e1cc4a25 --- /dev/null +++ b/akka-remote/src/test/scala/akka/remote/artery/tcp/ssl/X509ReadersSpec.scala @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020 Lightbend Inc. + */ + +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")) + } + + } + +} diff --git a/build.sbt b/build.sbt index df345dbc1f..638cb65e30 100644 --- a/build.sbt +++ b/build.sbt @@ -336,6 +336,7 @@ lazy val remote = .dependsOn( actor, stream, + pki, protobuf % "test", actorTests % "test->test", testkit % "test->test", diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 6c7aa30173..d1f684c0d8 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -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)