Client-Side HTTPS Support

Apache Pekko HTTP supports TLS encryption on the client-side as well as on the server-side.

The central vehicle for configuring encryption is the HttpsConnectionContextHttpsConnectionContext, which can be created using the static methods on ConnectionContextConnectionContext:

Scala
source/**
 *  Creates an HttpsConnectionContext for client-side use from the given SSLContext.
 */
def httpsClient(context: SSLContext): HttpsConnectionContext = // ...
Java
source/**
 * Creates an HttpsConnectionContext for client-side use from the given SSLContext.
 */
def httpsClient(sslContext: SSLContext): HttpsConnectionContext = // ...

In addition to the outgoingConnection, newHostConnectionPool and cachedHostConnectionPool methods the org.apache.pekko.http.scaladsl.Httporg.apache.pekko.http.javadsl.Http extension also defines outgoingConnectionHttps, newHostConnectionPoolHttps and cachedHostConnectionPoolHttps. These methods work identically to their counterparts without the -Https suffix, with the exception that all connections will always be encrypted.

The singleRequest and superPool methods determine the encryption state via the scheme of the incoming request, i.e. requests to an “https” URI will be encrypted, while requests to an “http” URI won’t.

The encryption configuration for all HTTPS connections, i.e. the HttpsContext is determined according to the following logic:

  1. If the optional httpsContext method parameter is defined it contains the configuration to be used (and thus takes precedence over any potentially set default client-side HttpsContext).
  2. If the optional httpsContext method parameter is undefined (which is the default) the default client-side HttpsContext is used, which can be set via the setDefaultClientHttpsContext on the HttpHttp extension.
  3. If no default client-side HttpsContext has been set via the setDefaultClientHttpsContext on the HttpHttp extension the default system configuration is used.

Usually the process is, if the default system TLS configuration is not good enough for your application’s needs, that you configure a custom HttpsContext instance and set it via Http().setDefaultClientHttpsContextHttp.get(system).setDefaultClientHttpsContext. Afterwards you simply use outgoingConnectionHttps, newHostConnectionPoolHttps, cachedHostConnectionPoolHttps, superPool or singleRequest without a specific httpsContext argument, which causes encrypted connections to rely on the configured default client-side HttpsConnectionContextHttpsConnectionContext.

If no custom HttpsContext is defined the default context uses Java’s default TLS settings. Customizing the HttpsContext can make the Https client less secure. Understand what you are doing!

Detailed configuration and workarounds

Warning

While it is possible to disable certain checks, we strongly recommend to instead attempt to solve these issues by properly configuring TLS–for example by adding trusted keys to the keystore.

If however certain checks really need to be disabled because of misconfigured (or legacy) servers that your application has to speak to, instead of disabling the checks globally (by using setDefaultClientHttpsContext) we suggest configuring the loose settings for specific connections that are known to need them disabled (and trusted for some other reason). The pattern of doing so is documented in the following sub-sections.

Disabling hostname verification

Hostname verification proves that the Apache Pekko HTTP client is actually communicating with the server it intended to communicate with. Without this check a man-in-the-middle attack is possible. In the attack scenario, an alternative certificate would be presented which was issued for another host name. Checking the host name in the certificate against the host name the connection was opened against is therefore vital.

When you create your HttpsConnectionContextHttpsConnectionContext with ConnectionContext.httpsClientConnectionContext.httpsClient enables hostname verification. The following shows an example of disabling hostname verification for a given connection:

Scala
sourceimplicit val system = ActorSystem()

def createInsecureSslEngine(host: String, port: Int): SSLEngine = {
  val engine = SSLContext.getDefault.createSSLEngine(host, port)
  engine.setUseClientMode(true)

  // WARNING: this creates an SSL Engine without enabling endpoint identification/verification procedures
  // Disabling host name verification is a very bad idea, please don't unless you have a very good reason to.
  // When in doubt, use the `ConnectionContext.httpsClient` that takes an `SSLContext` instead, or enable with:
  // engine.setSSLParameters({
  //  val params = engine.getSSLParameters
  //  params.setEndpointIdentificationAlgorithm("https")
  //  params
  // )

  engine
}
val badCtx = ConnectionContext.httpsClient(createInsecureSslEngine _)
Http().outgoingConnectionHttps(unsafeHost, connectionContext = badCtx)
Java
sourcefinal ActorSystem system = ActorSystem.create();
final Http http = Http.get(system);

final HttpsConnectionContext badCtx =
    ConnectionContext.httpsClient(
        (host, port) -> {
          SSLEngine engine = SSLContext.getDefault().createSSLEngine(host, port);
          engine.setUseClientMode(true);

          // WARNING: this creates an SSL Engine without enabling endpoint
          // identification/verification procedures
          // Disabling host name verification is a very bad idea, please don't unless you have a
          // very good reason to.
          // When in doubt, use the `ConnectionContext.httpsClient` that takes an `SSLContext`
          // instead, or enable
          // with:
          // SSLParameters params = engine.getSSLParameters();
          // params.setEndpointIdentificationAlgorithm("https");
          // engine.setSSLParameters(params);

          return engine;
        });

http.connectionTo(unsafeHost).withCustomHttpsConnectionContext(badCtx).https();