All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.twitter.finagle.netty4.ssl.client.Netty4ClientSslChannelInitializer.scala Maven / Gradle / Ivy

The newest version!
package com.twitter.finagle.netty4.ssl.client

import com.twitter.finagle.client.Transporter
import com.twitter.finagle.netty4.ssl.{Alpn, Netty4SslHandler}
import com.twitter.finagle.param.Stats
import com.twitter.finagle.ssl.{ApplicationProtocols, Engine}
import com.twitter.finagle.ssl.client.{
  SslClientConfiguration,
  SslClientEngineFactory,
  SslClientSessionVerifier
}
import com.twitter.finagle.transport.Transport
import com.twitter.finagle.{Address, Stack}
import com.twitter.util.Try
import io.netty.channel.{Channel, ChannelInitializer, ChannelPipeline}
import io.netty.handler.ssl.SslHandler
import io.netty.util.concurrent.{GenericFutureListener, Future => NettyFuture}

private[finagle] object Netty4ClientSslChannelInitializer {

  /**
   * A class eligible for configuring a callback that is triggered when the ssl
   * handshake is complete. Note, this is intended for internal use only.
   */
  case class OnSslHandshakeComplete(onComplete: Try[Unit] => Unit)
  object OnSslHandshakeComplete {
    implicit val param = Stack.Param(OnSslHandshakeComplete((_: Try[Unit]) => ()))
  }
}

/**
 * A channel initializer that takes [[Stack.Params]] and upgrades the pipeline with missing
 * SSL/TLS pieces required for client-side transport encryption.
 */
private[finagle] class Netty4ClientSslChannelInitializer(params: Stack.Params)
    extends ChannelInitializer[Channel] {

  // The reason we can't close the channel immediately is because we're in process of decoding an
  // inbound message that is represented by a bunch of TLS records. We need to finish decoding
  // and send that message up to the pipeline before closing the channel. This is why we queue the
  // close event.
  //
  // See CSL-1610 (internal ticket) for more details.
  private[this] val closeChannelOnCloseNotify =
    new GenericFutureListener[NettyFuture[Channel]] {
      def operationComplete(f: NettyFuture[Channel]): Unit = {
        val channel = f.getNow
        if (channel != null && f.isSuccess) {
          channel
            .eventLoop()
            .execute(new Runnable {
              def run(): Unit = channel.close()
            })
        }
      }
    }

  /**
   * Read the configured `SslClientEngineFactory` out of the stack param.
   * The default for clients is `JdkClientEngineFactory`. If it's configured
   * to use the default, for Netty 4, we replace it with the [[Netty4ClientEngineFactory]]
   * instead.
   */
  private[this] def selectEngineFactory(ch: Channel): SslClientEngineFactory = {
    val SslClientEngineFactory.Param(defaultEngineFactory) =
      SslClientEngineFactory.Param.param.default
    val SslClientEngineFactory.Param(engineFactory) =
      params[SslClientEngineFactory.Param]

    if (engineFactory == defaultEngineFactory) Netty4ClientEngineFactory(ch.alloc())
    else engineFactory
  }

  /**
   * This method combines `ApplicationProtocols` that may have been set by the user
   * with ones that are set based on using a protocol like HTTP/2.
   */
  private[this] def combineApplicationProtocols(
    config: SslClientConfiguration
  ): SslClientConfiguration = {
    val Alpn(protocols) = params[Alpn]

    config.copy(
      applicationProtocols = ApplicationProtocols.combine(protocols, config.applicationProtocols)
    )
  }

  private[this] def createSslHandler(engine: Engine): SslHandler = {
    // Rip the `SSLEngine` out of the wrapper `Engine` and use it to
    // create an `SslHandler`.
    val statsReceiver = params[Stats].statsReceiver.scope("tls")
    val ssl = new Netty4SslHandler(engine, statsReceiver)

    // Close channel on close_notify received from a remote peer.
    ssl.sslCloseFuture().addListener(closeChannelOnCloseNotify)

    // Disable Netty's default handshake timeout (10 seconds). We rely on
    // Finagle's own timeouts (i.e., session creation timeout) to interrupt
    // the handshake.
    ssl.setHandshakeTimeoutMillis(0)
    ssl
  }

  private[this] def createSslConnectHandler(
    sslHandler: SslHandler,
    address: Address,
    config: SslClientConfiguration
  ): SslClientVerificationHandler = {
    val SslClientSessionVerifier.Param(sessionVerifier) = params[SslClientSessionVerifier.Param]
    val sslVerifier = new SslClientVerificationHandler(sslHandler, address, config, sessionVerifier)

    val onSslHandshakeComplete =
      params[Netty4ClientSslChannelInitializer.OnSslHandshakeComplete].onComplete
    sslVerifier.handshakeComplete.respond(onSslHandshakeComplete)

    sslVerifier
  }

  private[this] def addHandlersToPipeline(
    pipeline: ChannelPipeline,
    sslHandler: SslHandler,
    sslConnectHandler: SslClientVerificationHandler
  ): Unit = {
    pipeline.addFirst("sslConnect", sslConnectHandler)
    pipeline.addFirst("ssl", sslHandler)
  }

  /**
   * In this method, an `Engine` is created by an `SslClientEngineFactory` via
   * an `SslClientConfiguration` and an `Address`. The `Engine` and the
   * `SslClientConfiguration` are then used to create the appropriate Netty
   * handlers, and they are subsequently added to the channel pipeline.
   */
  def initChannel(ch: Channel): Unit = {
    val Transporter.EndpointAddr(address) = params[Transporter.EndpointAddr]
    val Transport.ClientSsl(configuration) = params[Transport.ClientSsl]

    for (config <- configuration) {
      val factory: SslClientEngineFactory = selectEngineFactory(ch)
      val combined: SslClientConfiguration = combineApplicationProtocols(config)
      val engine: Engine = factory(address, combined)
      val sslHandler: SslHandler = createSslHandler(engine)
      val sslConnectHandler: SslClientVerificationHandler =
        createSslConnectHandler(sslHandler, address, combined)
      addHandlersToPipeline(ch.pipeline, sslHandler, sslConnectHandler)
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy