diff --git a/akka-docs/src/main/paradox/remoting-artery.md b/akka-docs/src/main/paradox/remoting-artery.md index f4b3b10407..7283557087 100644 --- a/akka-docs/src/main/paradox/remoting-artery.md +++ b/akka-docs/src/main/paradox/remoting-artery.md @@ -379,8 +379,6 @@ akka.remote.artery { protocol = "TLSv1.2" enabled-algorithms = [TLS_DHE_RSA_WITH_AES_128_GCM_SHA256] - - random-number-generator = "AES128CounterSecureRNG" } } ``` diff --git a/akka-docs/src/main/paradox/remoting.md b/akka-docs/src/main/paradox/remoting.md index aa458b1bd0..5af916a424 100644 --- a/akka-docs/src/main/paradox/remoting.md +++ b/akka-docs/src/main/paradox/remoting.md @@ -469,8 +469,6 @@ akka { protocol = "TLSv1.2" enabled-algorithms = [TLS_DHE_RSA_WITH_AES_128_GCM_SHA256] - - random-number-generator = "AES128CounterSecureRNG" } } } diff --git a/akka-docs/src/main/paradox/security/2018-08-29-aes-rng.md b/akka-docs/src/main/paradox/security/2018-08-29-aes-rng.md new file mode 100644 index 0000000000..bfd6c5a4a3 --- /dev/null +++ b/akka-docs/src/main/paradox/security/2018-08-29-aes-rng.md @@ -0,0 +1,93 @@ +# Broken random number generators AES128CounterSecureRNG / AES256CounterSecureRNG, Fixed in Akka 2.5.16 + +### Date + +29 August 2018 + +### Description of Vulnerability + +A random number generator is used in Akka Remoting for TLS (both classic and Artery +Remoting). Akka allows to configure custom random number generators. For historical reasons, +Akka included the `AES128CounterSecureRNG` and `AES256CounterSecureRNG` random number +generators. The implementations had a bug that caused the generated numbers to be repeated +after only a few bytes. + +The custom RNG implementations were not configured by default but examples in the +documentation showed (and therefore implicitly recommended) using the custom ones. + +This can be used by an attacker to compromise the communication if these random number generators +are enabled in configuration. It would be possible to eavesdrop, replay or modify the messages sent with +Akka Remoting/Cluster. + +To protect against such attacks the system should be updated to Akka *2.5.16* or later, or the default +configuration of the TLS random number generator should be used: + +``` +# Set `SecureRandom` RNG explicitly (but it is also the default) +akka.remote.netty.ssl.random-number-generator = "SecureRandom" +akka.remote.artery.ssl.config-ssl-engine.random-number-generator = "SecureRandom" +``` + +Please subscribe to the [akka-security](https://groups.google.com/forum/#!forum/akka-security) mailing list to be notified promptly about future security issues. + +### Severity + +The [CVSS](https://en.wikipedia.org/wiki/CVSS) score of this vulnerability is 5.9 (Medium), based on vector [AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N/E:U/RL:O/RC:C](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N/E:U/RL:O/RC:C). + +Rationale for the score: + + * AV:A - Best practice is that Akka remoting nodes should only be accessible from the adjacent network, so in + good setups, this will be adjacent. + * AC:H - Any one in the adjacent network can launch the attack with non-special access privileges, + but man-in-the-middle attacks are not trivial. + * C:H, I:H - Confidentiality and Integrity are only partially affected because only the networking component + is affected and not the whole Akka cluster. Assessed to be High anyway because access to actor system data would + probably be possible by injecting messages into the remoting communication. + +### Affected Versions + + * Akka *2.5.0 - 2.5.15* with any of the following configuration properties defined: + +``` +akka.remote.netty.ssl.random-number-generator = "AES128CounterSecureRNG" +akka.remote.netty.ssl.random-number-generator = "AES256CounterSecureRNG" +akka.remote.artery.ssl.config-ssl-engine.random-number-generator = "AES128CounterSecureRNG" +akka.remote.artery.ssl.config-ssl-engine.random-number-generator = "AES256CounterSecureRNG" +``` + +Akka *2.4.x* versions are not affected by this particular bug. It has reached +end-of-life since start of 2018. If you still run on Akka 2.4, we still +recommend to use the default `SecureRandom` implementation for the reasons +given below. Please check your configuration files not to configure the +custom RNGs. + +### Fixed Versions + +We have prepared patches for the affected versions, and have released the following version which resolve the issue: + + * Akka *2.5.16* (Scala 2.11, 2.12) + +Binary and source compatibility has been maintained for the patched releases so the upgrade procedure is as simple +as changing the library dependency. + +The exact historical reasons to include custom RNG implementations could not be reconstructed +but it was likely because RNGs provided by previous versions of the JDK were deemed too slow. + +Including custom cryptographic components in your library (or application) should not be done +lightly. We acknowledge that we cannot prove that the custom RNGs that Akka provides or has +been providing are generally correct or just correct enough for the purposes in Akka. + +The reporter of this vulnerability, Rafał Sumisławski, kindly provided us with fixes for the +custom RNGs in Akka. However, as we cannot thoroughly verify the correctness of the algorithm +we decided to remove custom RNGs from Akka. + +If the "AES128CounterSecureRNG" and "AES256CounterSecureRNG" configuration values are still used with Akka 2.5.16 +they will be ignored and the default `SecureRandom` is used and a warning is logged. This is to avoid accidental +use of these unverified and possibly insecure implementations. The deprecated implementations are not recommended, +but they can be enabled by using configuration values "DeprecatedAES128CounterSecureRNG" or "DeprecatedAES256CounterSecureRNG" +during the transition period until they have been removed. + +### Acknowledgements + +We would like to thank Rafał Sumisławski at NetworkedAssets for bringing this issue to our attention and providing +a patch. diff --git a/akka-docs/src/main/paradox/security/index.md b/akka-docs/src/main/paradox/security/index.md index 6e536ede39..a293cec584 100644 --- a/akka-docs/src/main/paradox/security/index.md +++ b/akka-docs/src/main/paradox/security/index.md @@ -30,5 +30,6 @@ to ensure that a fix can be provided without delay. * [2017-02-10-java-serialization](2017-02-10-java-serialization.md) * [2017-08-09-camel](2017-08-09-camel.md) +* [2018-08-29-aes-rng](2018-08-29-aes-rng.md) @@@ diff --git a/akka-remote/src/main/mima-filters/2.5.15.backwards.excludes b/akka-remote/src/main/mima-filters/2.5.15.backwards.excludes new file mode 100644 index 0000000000..3b6086e367 --- /dev/null +++ b/akka-remote/src/main/mima-filters/2.5.15.backwards.excludes @@ -0,0 +1,8 @@ +# AES random number generator issue, security was selected over compatibility +ProblemFilters.exclude[MissingClassProblem]("akka.remote.security.provider.AESCounterBuiltinRNG$AESKey") +ProblemFilters.exclude[MissingClassProblem]("akka.remote.security.provider.AESCounterBuiltinRNG") +ProblemFilters.exclude[MissingClassProblem]("akka.remote.security.provider.AESCounterBuiltinRNG$") +ProblemFilters.exclude[MissingClassProblem]("akka.remote.security.provider.AkkaProvider$") +ProblemFilters.exclude[MissingClassProblem]("akka.remote.security.provider.AES256CounterSecureRNG") +ProblemFilters.exclude[MissingClassProblem]("akka.remote.security.provider.AES128CounterSecureRNG") +ProblemFilters.exclude[MissingClassProblem]("akka.remote.security.provider.AkkaProvider") diff --git a/akka-remote/src/main/resources/reference.conf b/akka-remote/src/main/resources/reference.conf index 64160a746d..c111527ce6 100644 --- a/akka-remote/src/main/resources/reference.conf +++ b/akka-remote/src/main/resources/reference.conf @@ -666,13 +666,15 @@ akka { # http://docs.oracle.com/javase/7/docs/technotes/guides/security/SunProviders.html#SunJCEProvider enabled-algorithms = ["TLS_RSA_WITH_AES_128_CBC_SHA"] - # There are three options, in increasing order of security: - # "" or SecureRandom => (default) + # There are two options, and the default SecureRandom is recommended: + # "" or "SecureRandom" => (default) # "SHA1PRNG" => Can be slow because of blocking issues on Linux - # "AES128CounterSecureRNG" => fastest startup and based on AES encryption - # algorithm - # "AES256CounterSecureRNG" (Install JCE Unlimited Strength Jurisdiction - # Policy Files first) + # + # "AES128CounterSecureRNG" and "AES256CounterSecureRNG" were deprecated in Akka + # 2.5.16 and if these configuration values are used it will use the default + # SecureRandom anyway and log a warning. The deprecated implementations are + # not recommended, but they can be enabled by using configuration values + # "DeprecatedAES128CounterSecureRNG" or "DeprecatedAES256CounterSecureRNG" # # Setting a value here may require you to supply the appropriate cipher # suite (see enabled-algorithms section above) @@ -1158,13 +1160,15 @@ akka { # http://docs.oracle.com/javase/7/docs/technotes/guides/security/SunProviders.html#SunJCEProvider enabled-algorithms = ["TLS_RSA_WITH_AES_128_CBC_SHA"] - # There are three options, in increasing order of security: - # "" or SecureRandom => (default) + # There are two options, and the default SecureRandom is recommended: + # "" or "SecureRandom" => (default) # "SHA1PRNG" => Can be slow because of blocking issues on Linux - # "AES128CounterSecureRNG" => fastest startup and based on AES encryption - # algorithm - # "AES256CounterSecureRNG" (Install JCE Unlimited Strength Jurisdiction - # Policy Files first) + # + # "AES128CounterSecureRNG" and "AES256CounterSecureRNG" were deprecated in Akka + # 2.5.16 and if these configuration values are used it will use the default + # SecureRandom anyway and log a warning. The deprecated implementations are + # not recommended, but they can be enabled by using configuration values + # "DeprecatedAES128CounterSecureRNG" or "DeprecatedAES256CounterSecureRNG" # # Setting a value here may require you to supply the appropriate cipher # suite (see enabled-algorithms section above) 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 7bb5299d67..8955de4372 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 @@ -24,7 +24,7 @@ import akka.event.LogMarker import akka.event.Logging import akka.event.MarkerLoggingAdapter import akka.japi.Util.immutableSeq -import akka.remote.security.provider.AkkaProvider +import akka.remote.security.provider.DeprecatedAkkaProvider import akka.stream.IgnoreComplete import akka.stream.TLSClosing import akka.stream.TLSRole @@ -223,21 +223,28 @@ object SSLEngineProviderSetup { @InternalApi private[akka] object SecureRandomFactory { def createSecureRandom(randomNumberGenerator: String, log: MarkerLoggingAdapter): SecureRandom = { val rng = randomNumberGenerator match { - case r @ ("AES128CounterSecureRNG" | "AES256CounterSecureRNG") ⇒ - log.debug("SSL random number generator set to: {}", r) - SecureRandom.getInstance(r, AkkaProvider) 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 "" ⇒ - log.debug("SSLRandomNumberGenerator not specified, falling back to SecureRandom") + case "" | "SecureRandom" ⇒ + log.debug("SSL random number generator set to [SecureRandom]") new SecureRandom + case r @ ("AES128CounterSecureRNG" | "AES256CounterSecureRNG") ⇒ + log.warning("SSL random number generator set to deprecated [{}], using [SecureRandom] instead. " + + "The [{}] implementation can be enabled with configuration value [Deprecated{r}], " + + "but that is not recommended.", r, r, r) + new SecureRandom + + case r @ ("DeprecatedAES128CounterSecureRNG" | "DeprecatedAES256CounterSecureRNG") ⇒ + log.warning("SSL random number generator set to deprecated [{}]. Use [SecureRandom] instead.", r) + SecureRandom.getInstance(r, DeprecatedAkkaProvider) + case unknown ⇒ - log.warning(LogMarker.Security, "Unknown SSLRandomNumberGenerator [{}] falling back to SecureRandom", unknown) + log.warning(LogMarker.Security, "Unknown SSL random number generator [{}] falling back to SecureRandom", unknown) new SecureRandom } rng.nextInt() // prevent stall on first access diff --git a/akka-remote/src/main/scala/akka/remote/security/provider/AkkaProvider.scala b/akka-remote/src/main/scala/akka/remote/security/provider/AkkaProvider.scala deleted file mode 100644 index 87e4b603af..0000000000 --- a/akka-remote/src/main/scala/akka/remote/security/provider/AkkaProvider.scala +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (C) 2009-2018 Lightbend Inc. - */ - -package akka.remote.security.provider - -import java.security.{ PrivilegedAction, AccessController, Provider } - -/** - * A provider that for AES128CounterRNGFast, a cryptographically secure random number generator through SecureRandom - */ -object AkkaProvider extends Provider("Akka", 1.0, "Akka provider 1.0 that implements a secure AES random number generator") { - AccessController.doPrivileged(new PrivilegedAction[this.type] { - def run = { - //SecureRandom - put("SecureRandom.AES128CounterSecureRNG", classOf[AES128CounterSecureRNG].getName) - put("SecureRandom.AES256CounterSecureRNG", classOf[AES256CounterSecureRNG].getName) - - //Implementation type: software or hardware - put("SecureRandom.AES128CounterSecureRNG ImplementedIn", "Software") - put("SecureRandom.AES256CounterSecureRNG ImplementedIn", "Software") - null //Magic null is magic - } - }) -} - diff --git a/akka-remote/src/main/scala/akka/remote/security/provider/AES128CounterSecureRNG.scala b/akka-remote/src/main/scala/akka/remote/security/provider/DeprecatedAES128CounterSecureRNG.scala similarity index 80% rename from akka-remote/src/main/scala/akka/remote/security/provider/AES128CounterSecureRNG.scala rename to akka-remote/src/main/scala/akka/remote/security/provider/DeprecatedAES128CounterSecureRNG.scala index 1d35f1f5fb..98a6af586c 100644 --- a/akka-remote/src/main/scala/akka/remote/security/provider/AES128CounterSecureRNG.scala +++ b/akka-remote/src/main/scala/akka/remote/security/provider/DeprecatedAES128CounterSecureRNG.scala @@ -14,13 +14,15 @@ import scala.concurrent.ExecutionContext /** * This class is a wrapper around the 128-bit AESCounterBuiltinRNG AES/CTR PRNG algorithm * The only method used by netty ssl is engineNextBytes(bytes) + * */ -class AES128CounterSecureRNG extends java.security.SecureRandomSpi { +@deprecated("Use SecureRandom instead. We cannot prove that this code is correct, see https://doc.akka.io/docs/akka/current/security/2018-08-29-aes-rng.html", "2.5.16") +class DeprecatedAES128CounterSecureRNG extends java.security.SecureRandomSpi { private val singleThreadPool = ExecutionContext.fromExecutorService(Executors.newSingleThreadExecutor(new AESCounterBuiltinRNGReSeeder)) private val entropySource = new SecureRandom private val seed = entropySource.generateSeed(Seed128) - private val rng = new AESCounterBuiltinRNG(seed, singleThreadPool) + private val rng = new DeprecatedAESCounterBuiltinRNG(seed, singleThreadPool) /** * This is managed internally by AESCounterBuiltinRNG diff --git a/akka-remote/src/main/scala/akka/remote/security/provider/AES256CounterSecureRNG.scala b/akka-remote/src/main/scala/akka/remote/security/provider/DeprecatedAES256CounterSecureRNG.scala similarity index 80% rename from akka-remote/src/main/scala/akka/remote/security/provider/AES256CounterSecureRNG.scala rename to akka-remote/src/main/scala/akka/remote/security/provider/DeprecatedAES256CounterSecureRNG.scala index 791466bc63..457022c99f 100644 --- a/akka-remote/src/main/scala/akka/remote/security/provider/AES256CounterSecureRNG.scala +++ b/akka-remote/src/main/scala/akka/remote/security/provider/DeprecatedAES256CounterSecureRNG.scala @@ -15,12 +15,13 @@ import scala.concurrent.ExecutionContext * This class is a wrapper around the 256-bit AESCounterBuiltinRNG AES/CTR PRNG algorithm * The only method used by netty ssl is engineNextBytes(bytes) */ -class AES256CounterSecureRNG extends java.security.SecureRandomSpi { +@deprecated("Use SecureRandom instead. We cannot prove that this code is correct, see https://doc.akka.io/docs/akka/current/security/2018-08-29-aes-rng.html", "2.5.16") +class DeprecatedAES256CounterSecureRNG extends java.security.SecureRandomSpi { private val singleThreadPool = ExecutionContext.fromExecutorService(Executors.newSingleThreadExecutor(new AESCounterBuiltinRNGReSeeder)) private val entropySource = new SecureRandom private val seed = entropySource.generateSeed(Seed256) - private val rng = new AESCounterBuiltinRNG(seed, singleThreadPool) + private val rng = new DeprecatedAESCounterBuiltinRNG(seed, singleThreadPool) /** * This is managed internally by AESCounterBuiltinRNG diff --git a/akka-remote/src/main/scala/akka/remote/security/provider/AESCounterBuiltinRNG.scala b/akka-remote/src/main/scala/akka/remote/security/provider/DeprecatedAESCounterBuiltinRNG.scala similarity index 88% rename from akka-remote/src/main/scala/akka/remote/security/provider/AESCounterBuiltinRNG.scala rename to akka-remote/src/main/scala/akka/remote/security/provider/DeprecatedAESCounterBuiltinRNG.scala index 8a802edc41..2d8330164a 100644 --- a/akka-remote/src/main/scala/akka/remote/security/provider/AESCounterBuiltinRNG.scala +++ b/akka-remote/src/main/scala/akka/remote/security/provider/DeprecatedAESCounterBuiltinRNG.scala @@ -17,6 +17,11 @@ import scala.concurrent.{ Await, ExecutionContext, Future, duration } /** * INTERNAL API + * + * We cannot prove that this code is correct and it will therefore be removed + * with AES128CounterSecureRNG and AES256CounterSecureRNG. See security + * vulnerability https://doc.akka.io/docs/akka/current/security/2018-08-29-aes-rng.html + * * This class is a Scala implementation of AESCounterRNG algorithm * patterned after org.uncommons.maths.random by Daniel Dyer (Apache License 2.0) * @@ -32,10 +37,10 @@ import scala.concurrent.{ Await, ExecutionContext, Future, duration } * NOTE: this class is not serializable */ @InternalApi -private[akka] class AESCounterBuiltinRNG(val seed: Array[Byte], implicit val executionContext: ExecutionContext, - val reseedingThreshold: Long = CounterRNGConstants.ReseedingThreshold, - val reseedingDeadline: Long = CounterRNGConstants.ReseedingDeadline, - val reseedingTimeout: Duration = CounterRNGConstants.ReseedingTimeout) extends Random { +private[akka] class DeprecatedAESCounterBuiltinRNG(val seed: Array[Byte], implicit val executionContext: ExecutionContext, + val reseedingThreshold: Long = CounterRNGConstants.ReseedingThreshold, + val reseedingDeadline: Long = CounterRNGConstants.ReseedingDeadline, + val reseedingTimeout: Duration = CounterRNGConstants.ReseedingTimeout) extends Random { import CounterRNGConstants._ private val entropySource = new SecureRandom diff --git a/akka-remote/src/main/scala/akka/remote/security/provider/DeprecatedAkkaProvider.scala b/akka-remote/src/main/scala/akka/remote/security/provider/DeprecatedAkkaProvider.scala new file mode 100644 index 0000000000..9f9eb5d089 --- /dev/null +++ b/akka-remote/src/main/scala/akka/remote/security/provider/DeprecatedAkkaProvider.scala @@ -0,0 +1,28 @@ +/** + * Copyright (C) 2009-2018 Lightbend Inc. + */ + +package akka.remote.security.provider + +import java.security.{ PrivilegedAction, AccessController, Provider } + +/** + * A provider that for AES128CounterRNGFast, a cryptographically secure random number generator through SecureRandom + * + */ +@deprecated("Use SecureRandom instead. We cannot prove that this code is correct, see https://doc.akka.io/docs/akka/current/security/2018-08-29-aes-rng.html", "2.5.16") +object DeprecatedAkkaProvider extends Provider("Akka", 1.0, "Akka provider 1.0 that implements a secure AES random number generator") { + AccessController.doPrivileged(new PrivilegedAction[this.type] { + def run = { + //SecureRandom + put("SecureRandom.DeprecatedAES128CounterSecureRNG", classOf[DeprecatedAES128CounterSecureRNG].getName) + put("SecureRandom.DeprecatedAES256CounterSecureRNG", classOf[DeprecatedAES256CounterSecureRNG].getName) + + //Implementation type: software or hardware + put("SecureRandom.DeprecatedAES128CounterSecureRNG ImplementedIn", "Software") + put("SecureRandom.DeprecatedAES256CounterSecureRNG ImplementedIn", "Software") + null //Magic null is magic + } + }) +} + diff --git a/akka-remote/src/main/scala/akka/remote/transport/netty/NettySSLSupport.scala b/akka-remote/src/main/scala/akka/remote/transport/netty/NettySSLSupport.scala index 3bd75d7fe8..21f715c300 100644 --- a/akka-remote/src/main/scala/akka/remote/transport/netty/NettySSLSupport.scala +++ b/akka-remote/src/main/scala/akka/remote/transport/netty/NettySSLSupport.scala @@ -7,7 +7,7 @@ package akka.remote.transport.netty import java.security._ import akka.japi.Util._ -import akka.remote.security.provider.AkkaProvider +import akka.remote.security.provider.DeprecatedAkkaProvider import com.typesafe.config.Config import org.jboss.netty.handler.ssl.SslHandler @@ -46,7 +46,7 @@ private[akka] class SSLSettings(config: Config) { private[akka] object NettySSLSupport { // TODO is this needed in Artery TLS? - Security addProvider AkkaProvider + Security addProvider DeprecatedAkkaProvider /** * Construct a SSLHandler which can be inserted into a Netty server/client pipeline diff --git a/akka-remote/src/test/scala/akka/remote/Ticket1978CommunicationSpec.scala b/akka-remote/src/test/scala/akka/remote/Ticket1978CommunicationSpec.scala index a79adb4fc0..6b33430f6c 100644 --- a/akka-remote/src/test/scala/akka/remote/Ticket1978CommunicationSpec.scala +++ b/akka-remote/src/test/scala/akka/remote/Ticket1978CommunicationSpec.scala @@ -4,7 +4,9 @@ package akka.remote +import java.io.ByteArrayOutputStream import java.security.NoSuchAlgorithmException +import java.util.zip.GZIPOutputStream import akka.actor._ import akka.event.NoMarkerLogging @@ -54,7 +56,8 @@ object Configuration { } """ - final case class CipherConfig(runTest: Boolean, config: Config, cipher: String, localPort: Int, remotePort: Int) + final case class CipherConfig(runTest: Boolean, config: Config, cipher: String, localPort: Int, remotePort: Int, + provider: Option[ConfigSSLEngineProvider]) def getCipherConfig(cipher: String, enabled: String*): CipherConfig = { val localPort, remotePort = { val s = new java.net.ServerSocket(0); try s.getLocalPort finally s.close() } @@ -69,7 +72,10 @@ object Configuration { val rng = sslEngineProvider.createSecureRandom() rng.nextInt() // Has to work - val sRng = settings.SSLRandomNumberGenerator + val sRng = settings.SSLRandomNumberGenerator match { + case "AES128CounterSecureRNG" | "AES256CounterSecureRNG" ⇒ "" + case other ⇒ other + } if (rng.getAlgorithm != sRng && sRng != "") throw new NoSuchAlgorithmException(sRng) @@ -81,9 +87,10 @@ object Configuration { engine.getSupportedProtocols.contains(settings.SSLProtocol) || (throw new IllegalArgumentException("Protocol not supported: " + settings.SSLProtocol)) - CipherConfig(true, config, cipher, localPort, remotePort) + CipherConfig(true, config, cipher, localPort, remotePort, Some(sslEngineProvider)) } catch { - case (_: IllegalArgumentException) | (_: NoSuchAlgorithmException) ⇒ CipherConfig(false, AkkaSpec.testConf, cipher, localPort, remotePort) // Cannot match against the message since the message might be localized :S + case _: IllegalArgumentException | _: NoSuchAlgorithmException ⇒ + CipherConfig(false, AkkaSpec.testConf, cipher, localPort, remotePort, None) // Cannot match against the message since the message might be localized :S } } } @@ -92,13 +99,17 @@ class Ticket1978SHA1PRNGSpec extends Ticket1978CommunicationSpec(getCipherConfig class Ticket1978AES128CounterSecureRNGSpec extends Ticket1978CommunicationSpec(getCipherConfig("AES128CounterSecureRNG", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA")) +class Ticket1978DeprecatedAES128CounterSecureRNGSpec extends Ticket1978CommunicationSpec(getCipherConfig("DeprecatedAES128CounterSecureRNG", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA")) + class Ticket1978AES256CounterSecureRNGSpec extends Ticket1978CommunicationSpec(getCipherConfig("AES256CounterSecureRNG", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA")) +class Ticket1978DeprecatedAES256CounterSecureRNGSpec extends Ticket1978CommunicationSpec(getCipherConfig("DeprecatedAES256CounterSecureRNG", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA")) + class Ticket1978DefaultRNGSecureSpec extends Ticket1978CommunicationSpec(getCipherConfig("", "TLS_RSA_WITH_AES_128_CBC_SHA")) class Ticket1978CrappyRSAWithMD5OnlyHereToMakeSureThingsWorkSpec extends Ticket1978CommunicationSpec(getCipherConfig("", "SSL_RSA_WITH_NULL_MD5")) -class Ticket1978NonExistingRNGSecureSpec extends Ticket1978CommunicationSpec(CipherConfig(false, AkkaSpec.testConf, "NonExistingRNG", 12345, 12346)) +class Ticket1978NonExistingRNGSecureSpec extends Ticket1978CommunicationSpec(CipherConfig(false, AkkaSpec.testConf, "NonExistingRNG", 12345, 12346, None)) abstract class Ticket1978CommunicationSpec(val cipherConfig: CipherConfig) extends AkkaSpec(cipherConfig.config) with ImplicitSender { @@ -121,6 +132,38 @@ abstract class Ticket1978CommunicationSpec(val cipherConfig: CipherConfig) exten val ignoreMe = other.actorOf(Props(new Actor { def receive = { case ("ping", x) ⇒ sender() ! ((("pong", x), sender())) } }), "echo") val otherAddress = other.asInstanceOf[ExtendedActorSystem].provider.asInstanceOf[RemoteActorRefProvider].transport.defaultAddress + "generate random" in { + val rng = cipherConfig.provider.get.createSecureRandom() + val bytes = Array.ofDim[Byte](16) + // awaitAssert just in case we are very unlucky to get same sequence more than once + awaitAssert { + val randomBytes = (1 to 10).map { n ⇒ + 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 + } + "support tell" in within(timeout.duration) { val here = { system.actorSelection(otherAddress.toString + "/user/echo") ! Identify(None) 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 b8542b6a40..d07a681e29 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,7 +5,9 @@ package akka.remote.artery package tcp +import java.io.ByteArrayOutputStream import java.security.NoSuchAlgorithmException +import java.util.zip.GZIPOutputStream import scala.concurrent.duration._ @@ -39,6 +41,13 @@ class TlsTcpWithAES128CounterSecureRNGSpec extends TlsTcpSpec(ConfigFactory.pars } """)) +class TlsTcpWithDeprecatedAES128CounterSecureRNGSpec extends TlsTcpSpec(ConfigFactory.parseString(""" + akka.remote.artery.ssl.config-ssl-engine { + random-number-generator = "DeprecatedAES128CounterSecureRNG" + enabled-algorithms = ["TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA"] + } + """)) + class TlsTcpWithAES256CounterSecureRNGSpec extends TlsTcpSpec(ConfigFactory.parseString(""" akka.remote.artery.ssl.config-ssl-engine { random-number-generator = "AES256CounterSecureRNG" @@ -46,6 +55,13 @@ class TlsTcpWithAES256CounterSecureRNGSpec extends TlsTcpSpec(ConfigFactory.pars } """)) +class TlsTcpWithDeprecatedAES256CounterSecureRNGSpec extends TlsTcpSpec(ConfigFactory.parseString(""" + akka.remote.artery.ssl.config-ssl-engine { + random-number-generator = "DeprecatedAES256CounterSecureRNG" + enabled-algorithms = ["TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA"] + } + """)) + class TlsTcpWithDefaultRNGSecureSpec extends TlsTcpSpec(ConfigFactory.parseString(""" akka.remote.artery.ssl.config-ssl-engine { random-number-generator = "" @@ -86,7 +102,10 @@ abstract class TlsTcpSpec(config: Config) val rng = provider.createSecureRandom() rng.nextInt() // Has to work - val sRng = provider.SSLRandomNumberGenerator + val sRng = provider.SSLRandomNumberGenerator match { + case "AES128CounterSecureRNG" | "AES256CounterSecureRNG" ⇒ "" + case other ⇒ other + } if (rng.getAlgorithm != sRng && sRng != "") throw new NoSuchAlgorithmException(sRng) @@ -102,7 +121,7 @@ abstract class TlsTcpSpec(config: Config) engine.getSupportedProtocols.contains(provider.SSLProtocol) || (throw new IllegalArgumentException("Protocol not supported: " + provider.SSLProtocol)) } catch { - case e @ ((_: IllegalArgumentException) | (_: NoSuchAlgorithmException)) ⇒ + case e @ (_: IllegalArgumentException | _: NoSuchAlgorithmException) ⇒ info(e.toString) false } @@ -128,6 +147,41 @@ 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 = (1 to 10).map { n ⇒ + 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")