Disable SSLHandshakeException for a single connect

2020-02-05 10:11发布

问题:

I'm looking for a solution similar to this answer, but much safer. I'd like to disable the certificate validation, but for a single request only (which is all I need). So it should do one or more of the following

  • return to the secure state when the one request is done
  • disable validation for the given URL only
  • (maybe) use the insecure settings just for one thread

Addendum

I really wonder what's wrong with this question (score -2) when compared to the original one (score +46), when I'm asking for a more secure solution. Could someone explain?

To explain why I need this: There's a valid server certificate and normally, it gets used. But I need to send one request to localhost and it has to work on a developer machine, too. It must be https as there's no http support.

回答1:

Just use instance methods setX() instead of static setDefaultX():

HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setSSLSocketFactory(...);
connection.setHostnameVerifier(...);


回答2:

The solution

This utility class is based on provided example. Uses a 'relaxed' SSLContext which preconfigures a TrustManager accepting all certificates. Also is needed a hostname verifier in case your localhost ssl certificate was issued to a different CN

Apply it to each connection that requires a 'relaxed' ssl verification using HttpsURLConnection.setSocketFactory and HttpsURLConnection.setHostnameVerifier. The default behaviour wont be changed

import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

public class RelaxedSSLContext {
    // Create a trust manager that does not validate certificate chains like 
    public static TrustManager[] trustAllCerts = new TrustManager[] {
        new X509TrustManager() {
            public java.security.cert.X509Certificate[] getAcceptedIssuers() 
                return null;
            }
            public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
                //No need to implement. 
            }
            public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
                //No need to implement. 
            }
        }
    };

    //hostname verifier. All hosts valid
    public static HostnameVerifier allHostsValid = new HostnameVerifier() {
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    };  

    //hostname verifier. Only localhost and 127.0.0.1 valid
    public static HostnameVerifier localhostValid = new HostnameVerifier() {
        public boolean verify(String hostname, SSLSession session) {
            return "localhost".equals(hostname) || "127.0.0.1".equals(hostname);
        }
    };  


    public static SSLContext getInstance() throws KeyManagementException, NoSuchAlgorithmException{
        return getInstance("SSL");
    }   

    //get a 'Relaxed'  SSLContext with no trust store (all certificates are valids) 
    public static SSLContext getInstance(String protocol) throws KeyManagementException, NoSuchAlgorithmException{
        SSLContext sc = SSLContext.getInstance(protocol);
        sc.init(null, trustAllCerts, new java.security.SecureRandom());
        return sc;
    }   
}

Use it in this way

HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(RelaxedSSLContext.getInstance().getSocketFactory());
conn.setHostnameVerifier(RelaxedSSLContext.localhostValid);

Example1 (default)

url = "https://www.google.cop/finance";
conn = url.openConnection();
conn.connect();
int statusCode = conn.getResponseCode();   // 200

Example2 (bad hostname)

url = "https://216.58.210.164/finance";
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setHostnameVerifier(RelaxedSSLContext.allHostsValid); 
conn.connect();
int statusCode = conn.getResponseCode(); //200
// ERROR 'No subject alternative names matching IP address 216.58.210.164 found' without hostnameVerifier

Example3 (chain not in default truststore)

url = "https://www.aragon.es";
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(RelaxedSSLContext.getInstance().getSocketFactory());
conn.connect();
int statusCode = conn.getResponseCode(); //200
// ERROR 'unable to find valid certification path to requested target' without relaxedSocketFactory

The context

I think your question is on-topic, useful and well expressed. In some scenarios is suitable, or rather, is not essential to apply a context of high security. plain HTTP is still being used...

Let's analyze your context. The required is access to localhost through HTTPS without trusting in server identity. It means that you want to accept any certificate presented by the server. The security issue with this scenario is a MITM attach (man in the middle). From (wikipedia)

Attacker secretly relays and possibly alters the communication between two parties who believe they are directly communicating with each other.

But is it possible a MITM attack with an untrusted https connection to localhost?

See https://security.stackexchange.com/questions/8145/does-https-prevent-man-in-the-middle-attacks-by-proxy-server/8309#8309

First, in https the server has to present the server certificate to the client. The client validates the public key of the certificate and check that matches with local truststore. With the colaboration of the net administrator it would be possible to sniff the net and set a proxy to intercept the connection. But the malicious proxy is not in the possession of the matching private key so the proxy server may try to forge the certificate and provide his own public key instead. The certificate will not be present on client trustore, so the connection will be rejected,. But if you remove the truststore verification, the attack is theoretically possible.

But, is MITM possible restricting connections to localhost?

See https://security.stackexchange.com/questions/61399/are-mitm-attacks-possible-on-http-loopbacks

In the general case it is not possible, but an attacker with root access to the machine could alter DNS and redirect requests to the malicious proxy. Even using 127.0.0.1 it could be possible if the application has a way to configure the connection port.

The paranoic solution could be hardcode the server connection url, port and even trustore. But you are talking about localhost on a development environment, so I think we can relax a little



回答3:

The solution to this is always to either import the server certificate into the client truststore or else, and better, get the server certificate signed by a trusted CA, if it's under your control. You can't do it safely in code, and you shouldn't want to 'just for development'. Otherwise you're not testing the production code in development.