Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

I got javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated on my JUnit test

Tags:

java

junit

ssl

I am creating some unit tests for an app that uses a REST API. When I try to send a HttpPost request to the server URL (https://some.server.com), I got this:

javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
    at com.sun.net.ssl.internal.ssl.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:352)
    at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:128)
    at org.apache.http.conn.ssl.SSLSocketFactory.createSocket(SSLSocketFactory.java:399)
    at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:143)
    at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:149)
    at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:108)
    at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:415)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:641)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:576)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:554)

The server has a verified HTTPS certificate. Also, when I run this on production, it works fine. So I think it has to do with JUnit and/or my local computer.

I am also using a HttpClient created with this:

public static class WebClientDevWrapper {
    public static HttpClient wrapClient(HttpClient base) {
        try {
            SSLContext ctx = SSLContext.getInstance("TLS");
            X509TrustManager tm = new X509TrustManager() {

                public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException {
                }

                public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException {
                }

                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
            };
            ctx.init(null, new TrustManager[]{tm}, null);
            SSLSocketFactory ssf = new SSLSocketFactory(ctx);
            ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            ClientConnectionManager ccm = base.getConnectionManager();
            SchemeRegistry sr = ccm.getSchemeRegistry();
            sr.register(new Scheme("https", ssf, 443));
            return new DefaultHttpClient(ccm, base.getParams());
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }
}

AFAIK, if I create a HTTPClient with the code above, it should avoid SSLPeerUnverifiedException, but it seems it is not working.

So how can I solve this problem? I have tried more ways to create HttpClients which don't complain about SSL certs, but nothing has worked so far.

like image 321
Cristian Avatar asked Oct 11 '22 07:10

Cristian


2 Answers

Do you happen to be using Linux? I've had crazy problems using Java and SSL on an Ubuntu system. It's related to the /etc/hosts file mapping localhost to 127.0.1.1 instead of 127.0.0.1. Both are equally valid according to IANA mappings, but Java seems to wig out if it's not 127.0.0.1. When the problem manifests, it rarely even makes it past the handshake, so even if you've completely disabled all cert checking, it will still fail in unexpected ways.

like image 191
Ryan Stewart Avatar answered Oct 18 '22 00:10

Ryan Stewart


Most responses to this question (both on StackOverflow and elsewhere) spend too much time focusing on the Java code itself, and the answer often given is to just bypass SSL verification for all hosts. Please only do this as an absolute last resort, even if it is only a development environment, and only do it per domain as necessary instead of across the board. It is a poor practice, and I wonder how often that "development environment only" code makes it into production.

The reality is that the error is entirely accurate, and in some situations there is actually a better alternative. You will only get this error when:

  • There is no SSL certificate installed on the host to which you want to connect.
  • The certificate is installed, but not valid. This could be because it is expired, or the CN does not match the host domain where the certificate is installed.
  • The certificate is valid, but the certificate authority (CA) is not recognized by your Java JDK implementation. If one authority in the "chain of trust" for the certificate is not recognized by your Java JDK implementation then you might instead receive the javax.net.ssl.SSLHandshakeException.
  • The certificate is self-signed, which is really the same as above, in that the self signer is not a recognized CA.

The first bullet point has its own set of issues. It's questionable why you would want to connect to a host in any production scenario when no certificate is installed, but the application is still serving traffic over HTTPs. If you're sending sensitive user data over that connection then you've created a serious security flaw. The only solution for this bullet point is to bypass verification, which is really no solution at all unless you're simply "crawling" or "scraping" content.

Unfortunately, the same goes for the second bullet point as well. The only solution if you have to connect to the host over SSL is to bypass SSL verification. Even adding the certificate to your truststore will not prevent this exception when the certificate is expired or was created with an invalid CN. If you have an ongoing relationship with the host then ask them if they can correct the problem.

The solution for the third and fourth bullet point depends on the level of trust you have established with the hosts to which you want to connect, but is much better than simply bypassing all SSL host verification. Simply add the certificate as a trusted certificate (authority).

First, download the certificate using your favorite browser. Both Firefox and Chrome provide utilities to download a certificate for a domain. This question and answers from SuperUser provide some details. In the Java JDK the CA truststore is located at [JAVA_HOME]/jre/lib/security/cacerts. Use the following keytool command to add to it the certificate downloaded:

keytool -import -trustcacerts -alias domainalias -file host.crt -keystore cacerts

For more information, and other keytool commands I like to use the following page:

The Most Common Java Keytool Keystore Commands

like image 24
2 revsuser4903 Avatar answered Oct 17 '22 22:10

2 revsuser4903