From 32987c8704254db02ba65852a11f1cc5a36c586e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Andr=C3=A9n?= Date: Tue, 16 Jan 2018 18:05:08 +0100 Subject: [PATCH] Simpler tls over tcp #24153 --- .../src/main/paradox/stream/stream-io.md | 15 +++ .../java/akka/stream/javadsl/TcpTest.java | 97 ++++++++++++--- .../src/test/resources/tcp-spec-keystore.p12 | Bin 0 -> 3384 bytes .../test/scala/akka/stream/io/TcpSpec.scala | 96 ++++++++++++++- .../main/scala/akka/stream/javadsl/Tcp.scala | 82 ++++++++++++- .../main/scala/akka/stream/scaladsl/Tcp.scala | 116 +++++++++++++++++- 6 files changed, 382 insertions(+), 24 deletions(-) create mode 100644 akka-stream-tests/src/test/resources/tcp-spec-keystore.p12 diff --git a/akka-docs/src/main/paradox/stream/stream-io.md b/akka-docs/src/main/paradox/stream/stream-io.md index 6d92d4bb4c..c7f13ad864 100644 --- a/akka-docs/src/main/paradox/stream/stream-io.md +++ b/akka-docs/src/main/paradox/stream/stream-io.md @@ -123,6 +123,21 @@ see @java[[Javadoc](http://doc.akka.io/japi/akka/current/akka/stream/javadsl/Framing.html#simpleFramingProtocol-int-)] for more information. +### TLS + +Similar factories as shown above for raw TCP but where the data is encrypted using TLS are available from `Tcp` through `outgoingTlsConnection`, `bindTls` and `bindAndHandleTls`, see the @scala[@scaladoc[`Tcp Scaladoc`](akka.stream.scaladsl.Tcp)]@java[@javadoc[`Tcp Javadoc`](akka.stream.javadsl.Tcp)] for details. + +Using TLS requires a keystore and a truststore and then a somewhat involved dance of configuring the SSLContext and the details for how the session should be negotiated: + +Scala +: @@snip [TcpSpec.scala]($akka$akka-stream-tests/src/test/scala/akka/stream/io/TcpSpec.scala) { #setting-up-ssl-context } + +Java +: @@snip [TcpSpec.scala]($akka$akka-stream-tests/src/test/java/akka/stream/javadsl/TcpTest.java) { #setting-up-ssl-context } + + +The `SslContext` and `NegotiateFirstSession` instances can then be used with the binding or outgoing connection factory methods. + ## Streaming File IO Akka Streams provide simple Sources and Sinks that can work with `ByteString` instances to perform IO operations diff --git a/akka-stream-tests/src/test/java/akka/stream/javadsl/TcpTest.java b/akka-stream-tests/src/test/java/akka/stream/javadsl/TcpTest.java index aefbb49ff1..0763d1dbb7 100644 --- a/akka-stream-tests/src/test/java/akka/stream/javadsl/TcpTest.java +++ b/akka-stream-tests/src/test/java/akka/stream/javadsl/TcpTest.java @@ -3,34 +3,44 @@ */ package akka.stream.javadsl; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import akka.Done; +import akka.actor.ActorSystem; +import akka.japi.function.Function2; +import akka.japi.function.Procedure; +import akka.stream.BindFailedException; +import akka.stream.StreamTcpException; +import akka.stream.StreamTest; +import akka.stream.javadsl.Tcp.IncomingConnection; +import akka.stream.javadsl.Tcp.ServerBinding; +import akka.testkit.AkkaJUnitActorSystemResource; +import akka.testkit.AkkaSpec; +import akka.testkit.SocketUtil; +import akka.testkit.javadsl.EventFilter; +import akka.testkit.javadsl.TestKit; +import akka.util.ByteString; +import org.junit.ClassRule; +import org.junit.Test; + +import java.net.BindException; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; -import java.net.BindException; -import akka.Done; -import akka.NotUsed; -import akka.testkit.javadsl.EventFilter; -import akka.testkit.javadsl.TestKit; -import org.junit.ClassRule; -import org.junit.Test; -import scala.concurrent.Await; -import scala.concurrent.Future; -import scala.concurrent.duration.FiniteDuration; -import scala.runtime.BoxedUnit; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; -import akka.stream.*; -import akka.stream.javadsl.Tcp.*; -import akka.japi.function.*; -import akka.testkit.AkkaSpec; -import akka.testkit.SocketUtil; -import akka.util.ByteString; -import akka.testkit.AkkaJUnitActorSystemResource; +// #setting-up-ssl-context +// imports +import akka.stream.TLSClientAuth; +import akka.stream.TLSProtocol; +import com.typesafe.sslconfig.akka.AkkaSSLConfig; +import java.security.KeyStore; +import javax.net.ssl.*; +import java.security.SecureRandom; +// #setting-up-ssl-context public class TcpTest extends StreamTest { public TcpTest() { @@ -126,4 +136,51 @@ public class TcpTest extends StreamTest { } } + // compile only sample + public void constructSslContext() throws Exception { + ActorSystem system = null; + + // #setting-up-ssl-context + + // -- setup logic --- + + AkkaSSLConfig sslConfig = AkkaSSLConfig.get(system); + + // Don't hardcode your password in actual code + char[] password = "abcdef".toCharArray(); + + // trust store and keys in one keystore + KeyStore keyStore = KeyStore.getInstance("PKCS12"); + keyStore.load(getClass().getResourceAsStream("/tcp-spec-keystore.p12"), password); + + TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); + tmf.init(keyStore); + + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); + keyManagerFactory.init(keyStore, password); + + // initial ssl context + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(keyManagerFactory.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom()); + + // protocols + SSLParameters defaultParams = sslContext.getDefaultSSLParameters(); + String[] defaultProtocols = defaultParams.getProtocols(); + String[] protocols = sslConfig.configureProtocols(defaultProtocols, sslConfig.config()); + defaultParams.setProtocols(protocols); + + // ciphers + String[] defaultCiphers = defaultParams.getCipherSuites(); + String[] cipherSuites = sslConfig.configureCipherSuites(defaultCiphers, sslConfig.config()); + defaultParams.setCipherSuites(cipherSuites); + + TLSProtocol.NegotiateNewSession negotiateNewSession = TLSProtocol.negotiateNewSession() + .withCipherSuites(cipherSuites) + .withProtocols(protocols) + .withParameters(defaultParams) + .withClientAuth(TLSClientAuth.none()); + + // #setting-up-ssl-context + } + } diff --git a/akka-stream-tests/src/test/resources/tcp-spec-keystore.p12 b/akka-stream-tests/src/test/resources/tcp-spec-keystore.p12 new file mode 100644 index 0000000000000000000000000000000000000000..d72cc9f3d4ecb9ee14918145b1a506a06b642054 GIT binary patch literal 3384 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