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
andtruststorePass
attributes - Monitor Tomcat logs for SSL-related errors during startup