Cheat sheet for secure connection in Java

Author
Damian
Terlecki
23 minutes read

The standard Java package that provides an abstraction over secure network communication (certificate management, handshaking and verification) is javax.net.ssl. The most popular protocol which developers have to deal with is the HTTPS. HTTPS is the secure version of the request-response HTTP (RFC 2616) protocol. It can be either implemented over SSL or a more secure and upgraded version – TLS.

ProtocolPublishedWebsite supportSecurity
SSL 1.0 Unpublished
SSL 2.0 1995 1.6% Insecure
SSL 3.0 1996 6.7% Insecure
TLS 1.0 1999 65.0% Depends on cipher and client mitigations
TLS 1.1 2006 75.1% Depends on cipher and client mitigations
TLS 1.2 2008 96.0% Depends on cipher and client mitigations
TLS 1.3 2018 18.4% Secure

Sources: https://en.wikipedia.org/wiki/Transport_Layer_Security, https://www.ssllabs.com/ssl-pulse/

The support for various protocol versions and ciphers in Java is implemented in the form of a pluggable security architecture through the means of security providers. By default, at least one security provider is distributed with JRE/JDK and if needed a third-party provider can be added.

For example, at the moment of writing this, Oracle JRE8/JDK8 does not provide support for TLS 1.3, though it is planned for 2020-07-14. Meanwhile, you can enjoy TLS 1.3 on Java 11 and on Azul's Zing/Zulu Java 8 JVMs/JDKs.

Customizing the secure connection

A security provider is injected into the SSLContext which is used for initiating the connection. The default supported protocols can be seen by querying SSLContext parameters SSLContext.getDefault().getSupportedSSLParameters().getProtocols(). To restrict the list only to the chosen protocols, we can use setEnabledProtocols(String[] protocols) method of the SSLContext.

Let's check first what elements are there to initialize the context:

ClassDescriptionExample use
SSLContext An abstraction over SSL/TSL connection, facilitates connection using certificates contained within managed trust and key stores.
            SSLContext context = SSLContext.getInstance("TLSv1.2");
context.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
         
TrustStore A keystore containing trusted certificates from the client's point of view.
KeyStore A store containing our identity certificate.
            KeyStore ks = KeyStore.getInstance("JKS");
char[] password = "changeit".toCharArray();
try (FileInputStream fis = FileInputStream("path/to/keystore")) {
    ks.load(fis, password);
}
         
TrustManagerFactory
/
KeyManagerFactory
Factories for initialization of trust/key managers from key stores or managers provided by the runtime. The trust/key managers can also be instantiated using your own implementation.
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(ksAlgorithm);
kmf.init(ks, password);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init((KeyStore) null); // Default keystore will be used
         
KeyManager Presents a certificate chain with the public key to the client and provides a private key for decryption of the data encrypted by the public key. We've seen that passwords are associated with key stores but private keys can also have a password. Since there is no way to provide a password for the private key to the KeyManager, when the default "SunX509" KeyManagerFactory algorithm is used, it's assumed to be the same as the keystore password.
However, if we use "NewSunX509" algorithm we can overcome this issue – a more detailed explanation by Will Argent.
TrustManager Decides whether the credentials provided by the peer should be accepted.
HostnameVerifier During the SSL/TLS connection to further prevent MITM attacks, it's recommended to verify whether the target hostname is the same as the one provided with the certificate. Three popular implementations can be found in Apache HttpComponents library:
  • org.apache.http.conn.ssl.DefaultHostnameVerifier – verifies hostname (IPv4/IPv6/DNS name) based on RFC 2818 in a strict manner (only singular wildcard in the domain is legal) by comparing the target hostname and the certificate DNS Name values in the Subject Alternative Name (subjectAltName) field;
  • org.apache.http.conn.ssl.BrowserCompatHostnameVerifier – similar to DefaultHostnameVerifier but without the strict requirement, deprecated;
  • org.apache.http.conn.ssl.NoopHostnameVerifier – always returns true i.e. no verification is done – this should not be used, unless we narrow the scope of acceptable certificates to the one that the peer will present (RFC 6125).
If these three solutions do not suit your case, you can provide your own implementation based on some external information. You can read a detailed article on the hostname verification, by Will Argent.

Most of the HTTP clients support customizing the connection through the SSLContext class. In general, the default configuration provided by the JDK/JRE would often suffice when making a secure connection as a client. Unless of course the server also requires a valid certificate from us. In such a case, we will have to prove our identity through the KeyManager.

Some examples of the final link between the secure connection configuration and client/connection classes:

// javax.net.ssl
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setSSLSocketFactory(sslContext.getSocketFactory());
connection.setHostnameVerifier(hostnameVerifier);

// org.apache.httpcomponents:httpclient:4.5
CloseableHttpClient httpClient = HttpClientBuilder.create()
    .setSSLContext(sslContext)
    .setHostnameVerifier(hostnameVerifier)
    .build();

// com.squareup.okhttp3:okhttp:4.x
OkHttpClient okHttpClient = OkHttpClient.Builder()
    .sslSocketFactory(sslContext.getSocketFactory(), trustManager)
    .hostnameVerifier(hostnameVerifier)
    .build()

// org.glassfish.jersey.core:jersey-client:2.x
Client jerseyClient = ClientBuilder.newBuilder()
    .sslContext(sslContext)
    .hostnameVerifier(hostnameVerifier)
    .build();

To manage key stores, create CSR (Certificate Signing Request – to be signed by a Certification Authority) we use the keytool command-line program included in JRE/JDK bin directory. For some popular commands, refer to the SSL Shopper's article.

When in doubt why the standard configuration does not work, it's always a good idea to check the validity of the site certificate, domain name, and trust chain unless the certificate is self-signed and imported into the trust store (if so, verify this too).

Getting the site certificate using browser (lock symbol) Verification of certificate DNS name Checking the certification path

Often though, on the servers, checking the certificate through the browser isn't a feasible scenario as they're usually run in a headless mode. You can still use some command-line tools like curl or openssl to extract the certificate in such a situation.

Cheers, and stay safe!