Accept server's self-signed ssl certificate in Java client

后端 未结 12 1483
日久生厌
日久生厌 2020-11-22 00:04

It looks like a standard question, but I couldn\'t find clear directions anywhere.

I have java code trying to connect to a server with probably self-signed (or expir

相关标签:
12条回答
  • 2020-11-22 00:34

    Apache HttpClient 4.5 supports accepting self-signed certificates:

    SSLContext sslContext = SSLContexts.custom()
        .loadTrustMaterial(new TrustSelfSignedStrategy())
        .build();
    SSLConnectionSocketFactory socketFactory =
        new SSLConnectionSocketFactory(sslContext);
    Registry<ConnectionSocketFactory> reg =
        RegistryBuilder.<ConnectionSocketFactory>create()
        .register("https", socketFactory)
        .build();
    HttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(reg);        
    CloseableHttpClient httpClient = HttpClients.custom()
        .setConnectionManager(cm)
        .build();
    HttpGet httpGet = new HttpGet(url);
    CloseableHttpResponse sslResponse = httpClient.execute(httpGet);
    

    This builds an SSL socket factory which will use the TrustSelfSignedStrategy, registers it with a custom connection manager then does an HTTP GET using that connection manager.

    I agree with those who chant "don't do this in production", however there are use-cases for accepting self-signed certificates outside production; we use them in automated integration tests, so that we're using SSL (like in production) even when not running on the production hardware.

    0 讨论(0)
  • 2020-11-22 00:35

    There's a better alternative to trusting all certificates: Create a TrustStore that specifically trusts a given certificate and use this to create a SSLContext from which to get the SSLSocketFactory to set on the HttpsURLConnection. Here's the complete code:

    File crtFile = new File("server.crt");
    Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(new FileInputStream(crtFile));
    
    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    keyStore.load(null, null);
    keyStore.setCertificateEntry("server", certificate);
    
    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    trustManagerFactory.init(keyStore);
    
    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
    
    HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
    connection.setSSLSocketFactory(sslContext.getSocketFactory());
    

    You can alternatively load the KeyStore directly from a file or retrieve the X.509 Certificate from any trusted source.

    Note that with this code, the certificates in cacerts will not be used. This particular HttpsURLConnection will only trust this specific certificate.

    0 讨论(0)
  • 2020-11-22 00:40

    Instead of using keytool as suggested by the top comment, on RHEL you can use update-ca-trust starting in newer versions of RHEL 6. You'll need to have the cert in pem format. Then

    trust anchor <cert.pem>
    

    Edit /etc/pki/ca-trust/source/cert.p11-kit and change "certificate category: other-entry" to "certificate category: authority". (Or use sed to do this in a script.) Then do

    update-ca-trust
    

    A couple caveats:

    • I couldn't find "trust" on my RHEL 6 server and yum didn't offer to install it. I ended up using it on an RHEL 7 server and copying the .p11-kit file over.
    • To make this work for you, you may need to do update-ca-trust enable. This will replace /etc/pki/java/cacerts with a symbolic link pointing to /etc/pki/ca-trust/extracted/java/cacerts. (So you might want to back up the former first.)
    • If your java client uses cacerts stored in some other location, you'll want to manually replace it with a symlink to /etc/pki/ca-trust/extracted/java/cacerts, or replace it with that file.
    0 讨论(0)
  • 2020-11-22 00:41

    The accepted answer is fine, but I'd like to add something to this as I was using IntelliJ on Mac and couldn't get it to work using the JAVA_HOME path variable.

    It turns out Java Home was different when running the application from IntelliJ.

    To figure out exactly where it is, you can just do System.getProperty("java.home") as that's where the trusted certificates are read from.

    0 讨论(0)
  • 2020-11-22 00:41

    If 'they' are using a self-signed certificate it is up to them to take the steps required to make their server usable. Specifically that means providing their certificate to you offline in a trustworthy way. So get them to do that. You then import that into your truststore using the keytool as described in the JSSE Reference Guide. Don't even think about the insecure TrustManager posted here.

    EDIT For the benefit of the seventeen (!) downvoters, and numerous commenters below, who clearly have not actually read what I have written here, this is not a jeremiad against self-signed certificates. There is nothing wrong with self-signed certificates when implemented correctly. But, the correct way to implement them is to have the certificate delivered securely via an offline process, rather than via the unauthenticated channel they are going to be used to authenticate. Surely this is obvious? It is certainly obvious to every security-aware organization I have ever worked for, from banks with thousands of branches to my own companies. The client-side code-base 'solution' of trusting all certificates, including self-signed certificates signed by absolutely anybody, or any arbitary body setting itself up as a CA, is ipso facto not secure. It is just playing at security. It is pointless. You are having a private, tamperproof, reply-proof, injection-proof conversation with ... somebody. Anybody. A man in the middle. An impersonator. Anybody. You may as well just use plaintext.

    0 讨论(0)
  • 2020-11-22 00:43

    I had the issue that I was passing a URL into a library which was calling url.openConnection(); I adapted jon-daniel's answer,

    public class TrustHostUrlStreamHandler extends URLStreamHandler {
    
        private static final Logger LOG = LoggerFactory.getLogger(TrustHostUrlStreamHandler.class);
    
        @Override
        protected URLConnection openConnection(final URL url) throws IOException {
    
            final URLConnection urlConnection = new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getFile()).openConnection();
    
            // adapated from
            // https://stackoverflow.com/questions/2893819/accept-servers-self-signed-ssl-certificate-in-java-client
            if (urlConnection instanceof HttpsURLConnection) {
                final HttpsURLConnection conHttps = (HttpsURLConnection) urlConnection;
    
                try {
                    // Set up a Trust all manager
                    final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
    
                        @Override
                        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                            return null;
                        }
    
                        @Override
                        public void checkClientTrusted(final java.security.cert.X509Certificate[] certs, final String authType) {
                        }
    
                        @Override
                        public void checkServerTrusted(final java.security.cert.X509Certificate[] certs, final String authType) {
                        }
                    } };
    
                    // Get a new SSL context
                    final SSLContext sc = SSLContext.getInstance("TLSv1.2");
                    sc.init(null, trustAllCerts, new java.security.SecureRandom());
                    // Set our connection to use this SSL context, with the "Trust all" manager in place.
                    conHttps.setSSLSocketFactory(sc.getSocketFactory());
                    // Also force it to trust all hosts
                    final HostnameVerifier allHostsValid = new HostnameVerifier() {
                        @Override
                        public boolean verify(final String hostname, final SSLSession session) {
                            return true;
                        }
                    };
    
                    // and set the hostname verifier.
                    conHttps.setHostnameVerifier(allHostsValid);
    
                } catch (final NoSuchAlgorithmException e) {
                    LOG.warn("Failed to override URLConnection.", e);
                } catch (final KeyManagementException e) {
                    LOG.warn("Failed to override URLConnection.", e);
                }
    
            } else {
                LOG.warn("Failed to override URLConnection. Incorrect type: {}", urlConnection.getClass().getName());
            }
    
            return urlConnection;
        }
    
    }
    

    Using this class it is possible to create a new URL with:

    trustedUrl = new URL(new URL(originalUrl), "", new TrustHostUrlStreamHandler());
    trustedUrl.openConnection();
    

    This has the advantage that it is localized and not replacing the default URL.openConnection.

    0 讨论(0)
提交回复
热议问题