How to use Mutual Authentication TLS (mTLS) in Java

The Aerospike Knowledge Base has moved to https://support.aerospike.com. Content on https://discuss.aerospike.com is being migrated to either https://support.aerospike.com or https://docs.aerospike.com. Maintenance on articles stored in this repository ceased on December 31st 2022 and this article may be stale. If you have any questions, please do not hesitate to raise a case via https://support.aerospike.com.

How to use Mutual Authentication TLS (mTLS) in Java

Aerospike Server Enterprise supports standard TLS and mutual authentication TLS (mTLS). This article describes how to setup a Java application to connect to an Aerospike cluster configured to use mutual authentication TLS.

A fully functional example project can be found in the aerospike-tls-examples Github repository.

Keys and Certificates

For mutual authentication TLS both the client and server need to have their own private key and certificate. For this example, assume they are both signed by the same Certificate Authority (CA).

Installed on the Aerospike Server nodes:

  • CA Certificate: example.ca.crt
  • Server Certificate: example.server.crt
  • Server Private Key: example.server.key

Installed on the Client (Java) nodes:

  • CA Certificate: example.ca.crt
  • Client Certificate: example.client.crt
  • Client Private Key: example.client.key

Note that the private keys are sensitive and must be kept secure. Do not put the key for the server on client nodes nor the key for the client on the server nodes.

Aerospike Configuration

For simplicity, the example aerospike.conf configuration below shows only the stanzas and directives that are relevant for this TLS configuration:

network {
    tls example.server {
        ca-file /opt/aerospike/etc/certs/example.ca.crt
        cert-file /opt/aerospike/etc/certs/example.server.crt
        key-file /opt/aerospike/etc/private/example.server.key
    }

    service {
        tls-address any
        tls-port 4000
        tls-name example.server
        tls-authenticate-client example.client
    }
}

The tls block in the network stanza defines the TLS configuration for the Aerospike Server certificate. This is used in both standard TLS as well as in mutual authentication TLS.

The name example.server is known as the TLS name. This must match the value of the Common Name (CN) or Subject Alternative Name (SAN) of the server certificate example.server.crt. It will also be referenced in the application code to connect to the cluster. The following command verifies that the certificate has the expected CN value in the subject:

$ openssl x509 -in example.server.crt -text -noout | grep -E -- "Subject:"
        Subject: CN = example.server, O = "Aerospike, Inc.", C = US

The tls-authenticate-client directive specifies example.client. This must match the value of the Common Name (CN) or Subject Alternative Name (SAN) of the client certificate example.client.crt. The following command verifies that the certificate has the expected CN value in the subject:

$ openssl x509 -in example.client.crt -text -noout | grep -E -- "Subject:"
        Subject: CN = example.client, O = "Aerospike, Inc.", C = US

Note: The tls-authenticate-client directive also allows for a value of any which will bypass the step in which the Common Name (CN)/Subject Alternative Names (SAN) are verified. See the tls-authenticate-client for more information.

Java Configuration

Add CA Certificate to Java TrustStore

The CA certificate needs to be imported into a Java TrustStore. It is a public certificate used to verify that the certificate presented by the Aerospike Server during the TLS handshake is signed by a trusted authority.

There are two ways to import the CA certificate into a Java TrustStore on the client nodes.

The first method is to import the CA certificate into the system-wide TrustStore of “trusted CA certificates” used by the Java runtime by default. As the procedure for installing a system-wide trusted CA certificate can vary by OS and JDK/JRE version, this article will not use the system-wide TrustStore. Moreover, default system-wide trusted CA certificates is not allowed by many Enterprise security requirements.

The second method is to install the CA certificate into a new Java TrustStore which will to be used exclusively by the intended Java application. To do this, use the Java keytool command-line utility to import example.ca.crt into a new Java TrustStore:

keytool -importcert -storetype jks -alias example.ca \
-keystore example.ca.jks -file example.ca.crt \
-storepass changeit

This command will create a file named example.ca.jks which is the CA TrustStore the Java application will use.

Note: The above example followed Java convention of using “changeit” as the password. You SHOULD NOT use this password and instead choose a strong password governed by your organization’s password policy.

Verify the certificate is in the TrustStore using the keystore -list command:

$ keytool -list -keystore example.ca.jks -storepass changeit
Keystore type: jks
Keystore provider: SUN

Your keystore contains 1 entry

example.ca, Apr 5, 2020, trustedCertEntry, 
Certificate fingerprint (SHA1): 85:99:36:F8:20:A7:42:AA:ED:E6:9B:7B:F1:B6:84:45:31:13:D0:43

Note that the entry is listed as trustedCertEntry. This file can be stored on the filesystem with other public certificates.

Add Client Certificate Chain to Java KeyStore

During the TLS handshake the client will need to send its certificate to the server as well as a message encrypted using its private key. Since the Java application will need access to both the client certificate and the client private key these need to be imported into a Java KeyStore.

First, the CA certificate, the client certificate, and the client private key need to all be concatenated together in that order. This creates a single chain certificate. The following command will create a single chain certificate file named example.client.chain.crt:

cat example.ca.crt example.client.crt example.client.key > example.client.chain.crt

Next, the chain certificate needs to be converted to PKCS #12 format. This is a standard format for storing cryptographic objects and is reccommended over the proprietary Java KeyStore (jks) format. The following command will create a chain certificate file named example.client.chain.p12 which is the KeyStore file the Java application will use:

openssl pkcs12 -export -in example.client.chain.crt \
-out example.client.chain.p12 -password pass:"changeit" \
-name example.client -noiter -nomaciter

Note: The above example followed Java convention of using “changeit” as the password. You SHOULD NOT use this password and instead choose a strong password governed by your organization’s password policy.

Verify the certificate is in the KeyStore using the keystore -list command:

$ keytool -list -keystore example.client.chain.p12 -storepass changeit
Keystore type: PKCS12
Keystore provider: SUN

Your keystore contains 1 entry

example.client, Apr 5, 2020, PrivateKeyEntry, 
Certificate fingerprint (SHA1): A3:63:D6:B0:3B:E9:7E:78:81:46:5F:D6:53:93:5D:57:27:1B:FE:6D

Note that the entry is listed as PrivateKeyEntry. This file should be stored on the filesystem securely with limited permissions (the user invoking the JVM will need read access).

Java Application

When developing a Java application which connects to an Aerospike cluster using TLS, the application:

  1. Must enable TLS in the client policy
  2. Must specify the host TLS Name
  3. Must use the TrustStore with the CA certificate
  4. Must use the KeyStore with the client certificate chain (for mTLS only)
  5. Should log Aerospike debug messages
  6. Should log debug messages during the TLS handshake when troubleshooting

To enable TLS in the Aerospike Client, the ClientPolicy needs to have a TlsPolicy assigned to the tlsPolicy property:

ClientPolicy policy = new ClientPolicy();
policy.tlsPolicy = new TlsPolicy();

To specify the TLS Name, Instantiate Host objects with the constructor that accepts tlsName as the 2nd parameter:

Host[] hosts = new Host[] {
    new Host("127.0.0.1", "example.server", 4000)
};

Remember that the TLS Name must match the Common Name (CN) or Subject Alternative Name (SAN) in the server certificate as well as the tls-name used in the Aerospike configuration file.

To pass the TrustStore containing the CA certificate to the JVM use the -Djavax.net.ssl.trustStore argument:

java -Djavax.net.ssl.trustStore=example.ca.jks \
-jar aerospike-tls-example.jar

To pass the KeyStore and KeyStore password containing the client certificate chain to the JVM use the Djavax.net.ssl.keyStore and -Djavax.net.ssl.keyStorePassword arguments repsecitvely:

java -Djavax.net.ssl.trustStore=example.ca.jks \
-Djavax.net.ssl.keyStore=example.client.p12 \
-Djavax.net.ssl.keyStorePassword=changeit \
-jar aerospike-tls-example.jar

To log Aerospike debug messages see the Java Client logging usage.

To log debug messages during the TLS handshake pass the -Djavax.net.debug argument to the JVM:

java -Djavax.net.debug=all \
-Djavax.net.ssl.keyStore=example.client.p12 \
-Djavax.net.ssl.keyStorePassword=changeit \
-Djavax.net.ssl.trustStore=example.ca.jks \
-jar aerospike-tls-example.jar

See tls-example-java for a complete example.

Keywords

TLS MUTUAL AUTH MAUTH JAVA

Timestamp

April 2020