Simpler tls over tcp #24153

This commit is contained in:
Johan Andrén 2018-01-16 18:05:08 +01:00 committed by GitHub
parent 14c427bd4f
commit 32987c8704
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 382 additions and 24 deletions

View file

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

View file

@ -4,7 +4,9 @@
package akka.stream.io
import java.net._
import java.security.SecureRandom
import java.util.concurrent.atomic.AtomicInteger
import javax.net.ssl.{ KeyManagerFactory, SSLContext, TrustManagerFactory }
import akka.actor.{ ActorIdentity, ActorSystem, ExtendedActorSystem, Identify, Kill }
import akka.io.Tcp._
@ -18,6 +20,7 @@ import akka.testkit.SocketUtil.temporaryServerAddress
import akka.util.ByteString
import akka.{ Done, NotUsed }
import com.typesafe.config.ConfigFactory
import org.scalatest.concurrent.PatienceConfiguration
import org.scalatest.concurrent.PatienceConfiguration.Timeout
import scala.collection.immutable
@ -25,7 +28,10 @@ import scala.concurrent.duration._
import scala.concurrent.{ Await, Future, Promise }
import scala.util.control.NonFatal
class TcpSpec extends StreamSpec("akka.stream.materializer.subscription-timeout.timeout = 2s") with TcpHelper {
class TcpSpec extends StreamSpec("""
akka.loglevel = info
akka.stream.materializer.subscription-timeout.timeout = 2s
""") with TcpHelper {
"Outgoing TCP stream" must {
@ -692,6 +698,94 @@ class TcpSpec extends StreamSpec("akka.stream.materializer.subscription-timeout.
}
}
"TLS client and server convenience methods" should {
"allow for 'simple' TLS" in {
// cert is valid until 2025, so if this tests starts failing after that you need to create a new one
val (sslContext, firstSession) = initSslMess()
val address = temporaryServerAddress()
Tcp().bindAndHandleTls(
// just echo charactes until we reach '\n', then complete stream
// also - byte is our framing
Flow[ByteString].mapConcat(_.utf8String.toList)
.takeWhile(_ != '\n')
.map(c ByteString(c)),
address.getHostName,
address.getPort,
sslContext,
firstSession
).futureValue
system.log.info(s"Server bound to ${address.getHostString}:${address.getPort}")
val connectionFlow = Tcp().outgoingTlsConnection(address.getHostName, address.getPort, sslContext, firstSession)
val chars = "hello\n".toList.map(_.toString)
val (connectionF, result) =
Source(chars).map(c ByteString(c))
.concat(Source.maybe) // do not complete it from our side
.viaMat(connectionFlow)(Keep.right)
.map(_.utf8String)
.toMat(Sink.fold("")(_ + _))(Keep.both)
.run()
connectionF.futureValue
system.log.info(s"Client connected to ${address.getHostString}:${address.getPort}")
result.futureValue(PatienceConfiguration.Timeout(10.seconds)) should ===("hello")
}
def initSslMess() = {
// #setting-up-ssl-context
import akka.stream.TLSClientAuth
import akka.stream.TLSProtocol
import com.typesafe.sslconfig.akka.AkkaSSLConfig
import java.security.KeyStore
import javax.net.ssl._
val sslConfig = AkkaSSLConfig()
// Don't hardcode your password in actual code
val password = "abcdef".toCharArray
// trust store and keys in one keystore
val keyStore = KeyStore.getInstance("PKCS12")
keyStore.load(classOf[TcpSpec].getResourceAsStream("/tcp-spec-keystore.p12"), password)
val tmf = TrustManagerFactory.getInstance("SunX509")
tmf.init(keyStore)
val keyManagerFactory = KeyManagerFactory.getInstance("SunX509")
keyManagerFactory.init(keyStore, password)
// initial ssl context
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(keyManagerFactory.getKeyManagers, tmf.getTrustManagers, new SecureRandom)
// protocols
val defaultParams = sslContext.getDefaultSSLParameters
val defaultProtocols = defaultParams.getProtocols
val protocols = sslConfig.configureProtocols(defaultProtocols, sslConfig.config)
defaultParams.setProtocols(protocols)
// ciphers
val defaultCiphers = defaultParams.getCipherSuites
val cipherSuites = sslConfig.configureCipherSuites(defaultCiphers, sslConfig.config)
defaultParams.setCipherSuites(cipherSuites)
val negotiateNewSession = TLSProtocol.NegotiateNewSession
.withCipherSuites(cipherSuites: _*)
.withProtocols(protocols: _*)
.withParameters(defaultParams)
.withClientAuth(TLSClientAuth.None)
// #setting-up-ssl-context
(sslContext, negotiateNewSession)
}
}
def validateServerClientCommunication(
testData: ByteString,
serverConnection: ServerConnection,