How to open TCP connection with TLS in scala using akka

前端 未结 2 1096
梦毁少年i
梦毁少年i 2021-01-13 05:19

I want to write a Scala client that talks a proprietary protocol over a tcp connection with TLS.

Basically, I want to rewrite the following code from Node.js in Sca

2条回答
  •  猫巷女王i
    2021-01-13 06:12

    Got it working with the following code and want to share.

    Basically, I started looking at the TcpTlsEcho.java that I got from the akka community.

    I followed the documentation of akka-streams. Another very good example that shows and illustrate the usage of akka-streams can be found in the following blog post

    The connection setup and flow looks like:

        /**
        +---------------------------+               +---------------------------+
        | Flow                      |               | tlsConnectionFlow         |
        |                           |               |                           |
        | +------+        +------+  |               |  +------+        +------+ |
        | | SRC  | ~Out~> |      | ~~> O2   --  I1 ~~> |      |  ~O1~> |      | |
        | |      |        | LOGG |  |               |  | TLS  |        | CONN | |
        | | SINK | <~In~  |      | <~~ I2   --  O2 <~~ |      | <~I2~  |      | |
        | +------+        +------+  |               |  +------+        +------+ |
        +---------------------------+               +---------------------------+
    **/
    // the tcp connection to the server
    val connection = Tcp().outgoingConnection(address, port)
    
    // ignore the received data for now. There are different actions to implement the Sink.
    val sink = Sink.ignore
    
    // create a source as an actor reference
    val source = Source.actorRef(1000, OverflowStrategy.fail)
    
    // join the TLS BidiFlow (see below) with the connection
    val tlsConnectionFlow = tlsStage(TLSRole.client).join(connection)
    
    // run the source with the TLS conection flow that is joined with a logging step that prints the bytes that are sent and or received from the connection.
    val sourceActor = tlsConnectionFlow.join(logging).to(sink).runWith(source) 
    
    // send a message to the sourceActor that will be send to the Source of the stream
    sourceActor ! ByteString("")
    

    The TLS connection flow is a BidiFlow. My first simple example ignores all certificates and avoids managing trust and key stores. Examples how that is done can be found in the .java example above.

      def tlsStage(role: TLSRole)(implicit system: ActorSystem) = {
        val sslConfig = AkkaSSLConfig.get(system)
        val config = sslConfig.config
    
        // create a ssl-context that ignores self-signed certificates
        implicit val sslContext: SSLContext = {
            object WideOpenX509TrustManager extends X509TrustManager {
                override def checkClientTrusted(chain: Array[X509Certificate], authType: String) = ()
                override def checkServerTrusted(chain: Array[X509Certificate], authType: String) = ()
                override def getAcceptedIssuers = Array[X509Certificate]()
            }
    
            val context = SSLContext.getInstance("TLS")
            context.init(Array[KeyManager](), Array(WideOpenX509TrustManager), null)
            context
        }
        // protocols
        val defaultParams = sslContext.getDefaultSSLParameters()
        val defaultProtocols = defaultParams.getProtocols()
        val protocols = sslConfig.configureProtocols(defaultProtocols, config)
        defaultParams.setProtocols(protocols)
    
        // ciphers
        val defaultCiphers = defaultParams.getCipherSuites()
        val cipherSuites = sslConfig.configureCipherSuites(defaultCiphers, config)
        defaultParams.setCipherSuites(cipherSuites)
    
        val firstSession = new TLSProtocol.NegotiateNewSession(None, None, None, None)
           .withCipherSuites(cipherSuites: _*)
           .withProtocols(protocols: _*)
           .withParameters(defaultParams)
    
        val clientAuth = getClientAuth(config.sslParametersConfig.clientAuth)
        clientAuth map { firstSession.withClientAuth(_) }
    
        val tls = TLS.apply(sslContext, firstSession, role)
    
        val pf: PartialFunction[TLSProtocol.SslTlsInbound, ByteString] = {
          case TLSProtocol.SessionBytes(_, sb) => ByteString.fromByteBuffer(sb.asByteBuffer)
        }
    
        val tlsSupport = BidiFlow.fromFlows(
            Flow[ByteString].map(TLSProtocol.SendBytes),
            Flow[TLSProtocol.SslTlsInbound].collect(pf));
    
        tlsSupport.atop(tls);
      }
    
      def getClientAuth(auth: ClientAuth) = {
         if (auth.equals(ClientAuth.want)) {
             Some(TLSClientAuth.want)
         } else if (auth.equals(ClientAuth.need)) {
             Some(TLSClientAuth.need)
         } else if (auth.equals(ClientAuth.none)) {
             Some(TLSClientAuth.none)
         } else {
             None
         }
      }
    

    And for completion there is the logging stage that has been implemented as a BidiFlow as well.

      def logging: BidiFlow[ByteString, ByteString, ByteString, ByteString, NotUsed] = {
        // function that takes a string, prints it with some fixed prefix in front and returns the string again
        def logger(prefix: String) = (chunk: ByteString) => {
          println(prefix + chunk.utf8String)
          chunk
        }
    
        val inputLogger = logger("> ")
        val outputLogger = logger("< ")
    
        // create BidiFlow with a separate logger function for each of both streams
        BidiFlow.fromFunctions(outputLogger, inputLogger)
     }
    

    I will further try to improve and update the answer. Hope that helps.

提交回复
热议问题