Install with Maven
<dependency>
<groupId>io.github.hakky54</groupId>
<artifactId>sslcontext-kickstart</artifactId>
<version>5.0.0</version>
</dependency>
implementation 'io.github.hakky54:sslcontext-kickstart:5.0.0'
libraryDependencies += "io.github.hakky54" % "sslcontext-kickstart" % "5.0.0"
<dependency org="io.github.hakky54" name="sslcontext-kickstart" rev="5.0.0" />
SSLContext Kickstart is a library which provides a High-Level Factory class for configuring a http client to communicate over SSL/TLS for one way authentication or two way authentication.
As a Java developer I worked for different kinds of clients. Most of the time the application required to call other microservices within the organization or some other http servers. It was required to be HTTPS configured and so I began writing the code which was needed to configure the Http Client to communicate over ssl/tls. And every time I needed to write almost the same code over and over again which is in my opinion very verbose and hard to unit test.
As a developer you need to know how to properly load your file into your application and consume it as a KeyStore instance. Therefor you also need to learn how to properly create a KeyManagerFactory, TrustManagerFactory and SSLContext. Traditional creation of SSLContext can be rewritten if you use a Http Client which relies on libraries of Jetty or Netty and therefor it makes it even more complex. The sslcontext-kickstart library is taking the responsibility of creating an instance of SSLContext from the provided arguments. I wanted to be as easy as possible to use to give every developer a kickstart when configuring their Http Client. So feel free to provide feedback or feature requests. The library also provide other utilities such as KeyStoreUtils, KeyManagerUtils and TrustManagerUtils. See the javadoc for all the options.
I would like to thank Cody A. Ray for his contribution to the community regarding loading multiple Keystores into the SSLContext. The limitation of the JDK is to only support one keystore for the KeyManagerFactory and only one keystore for the TrustManagerFactory. The code snippets which Cody has shared are now available within this library and can be found here: CompositeX509KeyManager and CompositeX509TrustManager
The original article can be found here: Codyaray - Java SSL with Multiple KeyStores.
- No need for low-level SSLContext configuration anymore
- No knowledge needed about SSLContext, TrustManager, TrustManagerFactory, KeyManager, KeyManagerFactory and how to create it.
- Above classes will all be created with just providing an identity and a trustStore
- Load multiple identities/trustStores/keyManagers/trustManagers
- Identity: A KeyStore which holds the key pair also known as private and public key
- TrustStore: A KeyStore containing one or more certificates also known as public key. This KeyStore contains a list of trusted certificates
- One way authentication (also known as one way tls, one way ssl): Https connection where the client validates the certificate of the counter party
- Two way authentication (also known as two way tls, two way ssl, mutual authentication): Https connection where the client as well as the counter party validates the certificate, also known as mutual authentication
Example configuration with apache http client, or see here for other clients: ClientConfig class
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.json.JSONException;
import org.json.JSONObject;
import nl.altindag.sslcontext.SSLFactory;
public class App {
public static void main(String[] args) throws IOException, JSONException {
SSLFactory sslFactory = SSLFactory.builder()
.withDefaultTrustMaterial()
.build();
HttpClient httpClient = HttpClients.custom()
.setSSLContext(sslFactory.getSslContext())
.setSSLHostnameVerifier(sslFactory.getHostnameVerifier())
.build();
HttpGet request = new HttpGet("https://api.chucknorris.io/jokes/random");
HttpResponse response = httpClient.execute(request);
String chuckNorrisJoke = new JSONObject(EntityUtils.toString(response.getEntity())).getString("value");
System.out.println(String.format("Received the following status code: %d", response.getStatusLine().getStatusCode()));
System.out.println(String.format("Received the following joke: %s", chuckNorrisJoke));
}
}
Response:
Received the following status code: 200
Received the following joke: If a black cat crosses your path, you have bad luck. If Chuck Norris crosses your path, it was nice knowing you.
The SSLFactory provides other useful options, see below for all the returnable values:
import nl.altindag.sslcontext.SSLFactory;
import nl.altindag.sslcontext.model.KeyStoreHolder;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Optional;
public class App {
public static void main(String[] args) {
SSLFactory sslFactory = SSLFactory.builder()
.withIdentityMaterial("keystore.p12", "secret".toCharArray(), "PKCS12")
.withTrustMaterial("truststore.p12", "secret".toCharArray(), "PKCS12")
.build();
SSLContext sslContext = sslFactory.getSslContext();
HostnameVerifier hostnameVerifier = sslFactory.getHostnameVerifier();
Optional<X509ExtendedKeyManager> keyManager = sslFactory.getKeyManager();
Optional<X509ExtendedTrustManager> trustManager = sslFactory.getTrustManager();
List<X509Certificate> trustedCertificates = sslFactory.getTrustedCertificates();
List<KeyStoreHolder> identities = sslFactory.getIdentities();
List<KeyStoreHolder> trustStores = sslFactory.getTrustStores();
}
}
One way authentication with custom trustStore
SSLFactory.builder()
.withTrustMaterial(trustStore, trustStorePassword)
.build();
One way authentication while trusting all certificates without validation, not recommended to use at production!
SSLFactory.builder()
.withTrustingAllCertificatesWithoutValidation()
.build();
One way authentication with specific encryption protocol version, custom secure random and option to validate the hostname within the request against the SAN field of a certificate.
If you are using java 11 or newer, than you are also able to use TLSv1.3 as encryption protocol. Just provide TLSv1.3
as protocol argument and it will work out-of-the-box.
SSLFactory.builder()
.withTrustMaterial(trustStore, trustStorePassword)
.withHostnameVerifier(hostnameVerifier)
.withSecureRandom(secureRandom)
.withProtocol("TLSv1.2")
.build();
Two way authentication with custom trustStore, hostname verifier and encryption protocol version
SSLFactory.builder()
.withIdentityMaterial(identity, identityPassword)
.withTrustMaterial(trustStore, trustStorePassword)
.withHostnameVerifier(hostnameVerifier)
.withProtocol("TLSv1.2")
.build();
Support for using multiple identity materials and trust materials
SSLFactory.builder()
.withIdentityMaterial(identityA, identityPasswordA)
.withIdentityMaterial(identityB, identityPasswordB)
.withIdentityMaterial(identityC, identityPasswordC)
.withTrustMaterial(trustStoreA, trustStorePasswordA)
.withTrustMaterial(trustStoreB, trustStorePasswordB)
.withTrustMaterial(trustStoreC, trustStorePasswordC)
.withTrustMaterial(trustStoreD, trustStorePasswordD)
.withProtocol("TLSv1.2")
.build();
Support for using X509ExtendedKeyManager and X509ExtendedTrustManager
X509ExtendedKeyManager keyManager = ...
X509ExtendedTrustManager trustManager = ...
SSLFactory.builder()
.withIdentityMaterial(keyManager)
.withTrustMaterial(trustManager)
.build();
Some http clients relay on different ssl classes from third parties and require mapping from SSLFactory to those libraries. Below you will find the maven dependency which will provide the mapping and also the SSLFactory library. When using one of the below libraries, it is not required to also explicitly include sslcontext-kickstart.
Some know http clients which relay on netty libraries are: Spring WebFlux WebClient Netty, Async Http Client and Dispatch Reboot Http Client.
<dependency>
<groupId>io.github.hakky54</groupId>
<artifactId>sslcontext-kickstart-for-netty</artifactId>
<version>5.0.0</version>
</dependency>
Example setup for Spring WebClient with Netty:
import io.netty.handler.ssl.SslContext;
import nl.altindag.sslcontext.SSLFactory;
import nl.altindag.sslcontext.util.NettySslContextUtils;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.netty.http.client.HttpClient;
import javax.net.ssl.SSLException;
public class App {
public static void main(String[] args) throws SSLException {
SSLFactory sslFactory = SSLFactory.builder()
.withDefaultTrustMaterial()
.build();
SslContext sslContext = NettySslContextUtils.forClient(sslFactory).build();
HttpClient httpClient = HttpClient.create()
.secure(sslSpec -> sslSpec.sslContext(sslContext));
WebClient webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
}
}
<dependency>
<groupId>io.github.hakky54</groupId>
<artifactId>sslcontext-kickstart-for-jetty</artifactId>
<version>5.0.0</version>
</dependency>
Example setup for Spring WebFlux WebClient Jetty:
import nl.altindag.sslcontext.SSLFactory;
import nl.altindag.sslcontext.util.JettySslContextUtils;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.springframework.http.client.reactive.JettyClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
public class App {
public static void main(String[] args) {
SSLFactory sslFactory = SSLFactory.builder()
.withDefaultTrustMaterial()
.build();
SslContextFactory.Client sslContextFactory = JettySslContextUtils.forClient(sslFactory);
HttpClient httpClient = new HttpClient(sslContextFactory);
WebClient webClient = WebClient.builder()
.clientConnector(new JettyClientHttpConnector(httpClient))
.build();
}
}
Apache Http Client works with javax.net.ssl.SSLContext, so an additional mapping to their library is not required, see here. However it is still possible to configure the http client with their custom configuration class. you can find below an example configuration for that use case:
<dependency>
<groupId>io.github.hakky54</groupId>
<artifactId>sslcontext-kickstart-for-apache</artifactId>
<version>5.0.0</version>
</dependency>
import nl.altindag.sslcontext.SSLFactory;
import nl.altindag.sslcontext.util.ApacheSslContextUtils;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.impl.client.HttpClients;
public class App {
public static void main(String[] args) {
SSLFactory sslFactory = SSLFactory.builder()
.withDefaultTrustMaterial()
.build();
LayeredConnectionSocketFactory socketFactory = ApacheSslContextUtils.toLayeredConnectionSocketFactory(sslFactory);
HttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(socketFactory)
.build();
}
}
Below is a list of clients which have already been tested with examples, see in the ClientConfig class and the service directory for detailed configuration
Java
- Apache HttpClient -> Client configuration | Example request
- JDK HttpClient -> Client Configuration | Example request
- Old JDK HttpClient -> Client Configuration & Example request
- Netty Reactor -> Client Configuration | Example request
- Jetty Reactive HttpClient -> Client Configuration | Example request
- Spring RestTemplate -> Client Configuration | Example request
- Spring WebFlux WebClient Netty -> Client Configuration | Example request
- Spring WebFlux WebClient Jetty -> Client Configuration | Example request
- OkHttp -> Client Configuration | Example request
- Jersey Client -> Client Configuration | Example request
- Old Jersey Client -> Client Configuration | Example request
- Google HttpClient -> Client Configuration | Example request
- Unirest -> Client Configuration | Example request
- Retrofit -> Client Configuration | Example request
- Async Http Client -> Client Configuration | Example request
- Feign -> Client Configuration | Example request
- Methanol -> Client Configuration | Example request
Kotlin
Scala
- Twitter Finagle -> Client Configuration | Example request
- Twitter Finagle Featherbed -> Client Configuration & Example request
- Akka Http Client -> Client Configuration | Example request
- Dispatch Reboot -> Client Configuration & Example request
- ScalaJ / Simplified Http Client -> Client Configuration & Example request
- Sttp -> Client Configuration & Example request
- Requests-Scala -> Client Configuration & Example request
- Http4s Blaze Client -> Client Configuration | Example request
- Http4s Java Net Client -> Client Configuration | Example request
There is a github project available named Mutual-tls-ssl which provides a tutorial containing steps for setting up these four scenarios:
- No security
- One way authentication
- Two way authentication
- Two way authentication with trusting the Certificate Authority
It will also explain how to create KeyStores, Certificates, Certificate Signing Requests and how to implement it.