Simpler tls over tcp #24153
This commit is contained in:
parent
14c427bd4f
commit
32987c8704
6 changed files with 382 additions and 24 deletions
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
BIN
akka-stream-tests/src/test/resources/tcp-spec-keystore.p12
Normal file
BIN
akka-stream-tests/src/test/resources/tcp-spec-keystore.p12
Normal file
Binary file not shown.
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue