How to Configure Apache/Tomcat to Trust Internal CA Certificates for HTTPS Server-to-Server Communication


7 views



When making HTTPS requests from Apache/Tomcat to internal servers using certificates issued by private CAs, you'll encounter SSL handshake failures. The root cause is simple - your Java truststore doesn't contain the internal CA's root certificate.



Here's the complete workflow to resolve this:

1. Obtain the internal CA's root certificate (usually .crt or .pem format)
2. Import it into Java's cacerts truststore or create a custom truststore
3. Configure Tomcat/Apache to use this truststore



Step 1: Get the CA root certificate
You'll need the root certificate in PEM format. If you only have it in Windows, export it from the Certificate Manager:


openssl x509 -inform der -in CA_Root.cer -out CA_Root.pem


Step 2: Import into Java truststore
The standard location is $JAVA_HOME/lib/security/cacerts. Backup first:


cp $JAVA_HOME/lib/security/cacerts $JAVA_HOME/lib/security/cacerts.backup


Then import (default password is 'changeit'):


keytool -import -trustcacerts -alias internal_ca -file CA_Root.pem -keystore $JAVA_HOME/lib/security/cacerts




For Tomcat, you can specify a custom truststore in setenv.sh:


export JAVA_OPTS="$JAVA_OPTS -Djavax.net.ssl.trustStore=/path/to/custom_truststore.jks"
export JAVA_OPTS="$JAVA_OPTS -Djavax.net.ssl.trustStorePassword=yourpassword"




Create a simple test class:


import javax.net.ssl.HttpsURLConnection;
import java.net.URL;

public class SSLTest {
    public static void main(String[] args) throws Exception {
        URL url = new URL("https://internal.server.com/api");
        HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        System.out.println("Response Code: " + conn.getResponseCode());
    }
}




For more control, implement a custom TrustManager:


SSLContext sslContext = SSLContext.getInstance("TLS");
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream("/path/to/truststore.jks"), "password".toCharArray());
tmf.init(ks);
sslContext.init(null, tmf.getTrustManagers(), null);
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());




Enable SSL debugging if issues persist:

-Djavax.net.debug=ssl:handshake:verbose


When making HTTPS requests from Apache/Tomcat to servers using certificates issued by internal Certificate Authorities (CAs), Java's default truststore doesn't contain these private CA certificates. This results in SSL handshake failures with errors like:

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: 
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: 
unable to find valid certification path to requested target

First, identify which JRE your Tomcat instance is using:

ps -ef | grep tomcat

Then find the cacerts file (Java's default truststore):

find /usr/lib/jvm -name "cacerts"

Typically located at: /usr/lib/jvm/java-11-openjdk-amd64/lib/security/cacerts

Export your internal CA certificate (usually .cer or .pem format) and import it into Java's truststore:

keytool -import -alias internal-ca -file /path/to/your/internal-ca.crt \
  -keystore /usr/lib/jvm/java-11-openjdk-amd64/lib/security/cacerts \
  -storepass changeit

The default password for the cacerts file is changeit.

For better security isolation, create a custom truststore instead of modifying the default one:

# Create new truststore
keytool -import -alias internal-ca -file internal-ca.crt \
  -keystore custom-truststore.jks -storepass yourpassword

# Configure Tomcat to use it
export JAVA_OPTS="$JAVA_OPTS -Djavax.net.ssl.trustStore=/path/to/custom-truststore.jks"
export JAVA_OPTS="$JAVA_OPTS -Djavax.net.ssl.trustStorePassword=yourpassword"

Create a simple test class to verify SSL connectivity:

import javax.net.ssl.HttpsURLConnection;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;

public class SSLTester {
    public static void main(String[] args) throws Exception {
        URL url = new URL("https://internal.server.com/api");
        HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
        
        try (BufferedReader br = new BufferedReader(
            new InputStreamReader(con.getInputStream()))) {
            
            String response;
            while ((response = br.readLine()) != null) {
                System.out.println(response);
            }
        }
    }
}

For applications using Apache HTTP Client, configure SSL context:

import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;

SSLContext sslContext = SSLContextBuilder
    .create()
    .loadTrustMaterial(new File("/path/to/truststore.jks"), "password".toCharArray())
    .build();

CloseableHttpClient httpClient = HttpClients.custom()
    .setSSLContext(sslContext)
    .build();
  • Verify certificate chain with: openssl s_client -connect internal.server.com:443 -showcerts
  • Check Java version compatibility with your certificates
  • For Tomcat connectors, add truststoreFile and truststorePass attributes
  • Monitor Tomcat logs for SSL-related errors during startup