简体   繁体   中英

Unable to get Java to trust my self-signed certificate

Even after adding my self signed certificate to cacerts, I still can't get Java to trust it

I have nginx running on my local machine as an SSL reverse proxy with a self signed certificate. I have generated the certificate like this:

openssl req -new -nodes -keyout server.key -out server.csr
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

I filled in my-org.local when prompted for Common Name. In my hosts file, my-org.local is an alias for localhost.

Testing this setup in the browser, I got a warning that the certificate was not signed by a known authority, which is what I would expect. I then told the browser to trust the certificate, and that worked.

Next, I wrote this small little Java program to be able to verify if I can get Java to trust the certificate:

import java.net.*;
import java.io.*;

public class Main {

    public static void main(String[] args) throws Exception {
        String url = "https://my-org.local/";
        URL obj = new URL(url);
        HttpURLConnection con = (HttpURLConnection) obj.openConnection();
        int responseCode = con.getResponseCode();
        System.out.println("\nSending 'GET' request to URL : " + url);
        System.out.println("Response Code : " + responseCode);
    }
}

If I test this against eg https://google.com , everything works as expected. Against my local machine I get the following stack trace:

Exception in thread "main" 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
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
    at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1884)
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:276)
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:270)
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1341)
    at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:153)
    at sun.security.ssl.Handshaker.processLoop(Handshaker.java:868)
    at sun.security.ssl.Handshaker.process_record(Handshaker.java:804)
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1016)
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1312)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1339)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1323)
    at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:563)
    at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1300)
    at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:468)
    at sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:338)
    at Main.main(Main.java:10)
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:385)
    at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:292)
    at sun.security.validator.Validator.validate(Validator.java:260)
    at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:326)
    at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:231)
    at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:126)
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1323)
    ... 13 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:196)
    at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:268)
    at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:380)
    ... 19 more

... which is more or less what I would expect.

My understanding is that to get Java to trust my self signed certificate, I have to add the certificate to Java's cacerts. On my machine (a mac running Mavericks), this file is to be found here:

/System/Library/Frameworks/JavaVM.framework/Home/lib/security/cacerts

This is how I tried to add the certificate:

sudo keytool -import -keystore /System/Library/Frameworks/JavaVM.framework/Home/lib/security/cacerts -storepass changeit -noprompt -alias myorg -file server.crt

This however has no effect; my small Java program still dies with the same stack trace. What am I doing wrong?

You're getting SSLHandshakeException which clearly means the certificate is not successfully imported into your cacerts file.

So, let's first check if your cacerts has the certificate or not:

echo 'changeit' | keytool -list -v -keystore $(find $JAVA_HOME -name cacerts) | grep 'Owner:'

changeit is the default password. This will just list the owners of all the certificates. You can also grep by the keywords of your own certificate to check if it exists.

If it doesn't, this code does a wonderful job in creating the cacerts file. I'm adding the code for reference:

import javax.net.ssl.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

public class CustomTrustManager implements X509TrustManager
{
    private static final String JAVA_CA_CERT_FILE_NAME = "cacerts";
    private static final String CLASSIC_JAVA_CA_CERT_FILE_NAME = "jssecacerts";
    private static final int DEFAULT_HTTPS_PORT = 443;

    private String[] hostsToTrust = {"server1.company.com", "server2.company.com"};
    private char[] defaultCAKeystorePassphrase = "changeit".toCharArray();
    private KeyStore certificateTrustStore;
    private X509TrustManager defaultTrustManager;

    public static void initSsl()
    {
        try
        {
            SSLContext context = SSLContext.getInstance("TLS");
            context.init(null, new TrustManager[] { new CustomTrustManager() }, new SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
        }
        catch (Exception e)
        {
            throw new RuntimeException(e);
        }
    }

    public CustomTrustManager()
    {
        try
        {
            initTrustStore();
            addTrustedHosts();
            initDefaultTrustManager();
        }
        catch (Exception e)
        {
            throw new RuntimeException(e);
        }
    }

    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException
    {
        defaultTrustManager.checkClientTrusted(chain, authType);
    }

    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException
    {
        defaultTrustManager.checkServerTrusted(chain, authType);
    }

    public X509Certificate[] getAcceptedIssuers()
    {
        return defaultTrustManager.getAcceptedIssuers();
    }

    private void initTrustStore() throws Exception
    {
        File javaTrustStoreFile = findJavaTrustStoreFile();
        InputStream inputStream = new FileInputStream(javaTrustStoreFile);
        certificateTrustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        certificateTrustStore.load(inputStream, defaultCAKeystorePassphrase);
        inputStream.close();
    }

    private void addTrustedHosts() throws Exception
    {
        SSLContext tempConnectContext = SSLContext.getInstance("TLS");
        ExtractX509CertTrustManager getX509CertTrustManager = new ExtractX509CertTrustManager();
        tempConnectContext.init(null, new TrustManager[] { getX509CertTrustManager }, null);
        SSLSocketFactory socketFactory = tempConnectContext.getSocketFactory();
        for (String host : hostsToTrust)
        {
            SSLSocket socket = (SSLSocket) socketFactory.createSocket(host, DEFAULT_HTTPS_PORT);
            // connect the socket to set the cert chain in getX509CertTrustManager
            socket.startHandshake();
            for (X509Certificate cert : getX509CertTrustManager.getCurrentChain())
            {
                if (!certificateTrustStore.isCertificateEntry(host))
                {
                    certificateTrustStore.setCertificateEntry(host, cert);
                }
            }
        }
    }

    private void initDefaultTrustManager() throws Exception
    {
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(certificateTrustStore);
        TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
        for (TrustManager trustManager : trustManagers)
        {
            if (trustManager instanceof X509TrustManager)
            {
                defaultTrustManager = (X509TrustManager) trustManager;
                break;
            }
        }
    }

    /**
     * Trust Manager for the sole purpose of retrieving the X509 cert when a connection is made to a host we want
     * to start trusting.
     */
    private static class ExtractX509CertTrustManager implements X509TrustManager
    {
        private X509Certificate[] currentChain;
        public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { }
        public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException
        {
            currentChain = x509Certificates;
        }
        public X509Certificate[] getAcceptedIssuers() { return null; }
        public X509Certificate[] getCurrentChain()
        {
            return currentChain;
        }
    }

    private File findJavaTrustStoreFile()
    {
        File javaHome = new File(System.getProperty("java.home") + File.separatorChar + "lib" + File.separatorChar + "security");
        File caCertsFile = new File(javaHome, JAVA_CA_CERT_FILE_NAME);
        if (!caCertsFile.exists() || !caCertsFile.isFile())
        {
            caCertsFile = new File(javaHome, CLASSIC_JAVA_CA_CERT_FILE_NAME);
        }
        return caCertsFile;
    }
} 

To get started quickly:

  1. Add your hosts to the hostsToTrust String array.

  2. Call CustomTrustManager.initSsl().

  3. Make SSL connections to your hosts.

That being said, I won't recommend updating cacerts because it's at a common JVM location as is thus used by every Java application. You should create a custom trustStore for the specific application you're working on and only allow that application to trust the self-signed certificate. Checkout this answer for how to do that.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM