1

I've made a crawler application that for some website fail to connect due to the error "handshake alert: unrecognized_name".

Most of the solutions I found is by disabling the SNI extension(jsse.enableSNIExtension=false). But this creates problems with the domains that require SNI enabled.

How can I disable it only for some domains?

To do the crawling I'm using Jsoup, and because I'm also using proxies I've added this code at startup.

  private static void disableSslVerification() {
    TrustManager[] trustAllCertificates = new TrustManager[] {
            new X509TrustManager() {
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return null; // Not relevant.
                }
                @Override
                public void checkClientTrusted(X509Certificate[] certs, String authType) {
                    // Do nothing. Just allow them all.
                }
                @Override
                public void checkServerTrusted(X509Certificate[] certs, String authType) {
                    // Do nothing. Just allow them all.
                }
            }
    };

    HostnameVerifier trustAllHostnames = new HostnameVerifier() {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true; // Just allow them all.
        }
    };

    try {
        System.setProperty("https.protocols", "TLSv1.2,TLSv1.1,SSLv3");
       // System.setProperty("jsse.enableSNIExtension", "false");
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCertificates, new SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        HttpsURLConnection.setDefaultHostnameVerifier(trustAllHostnames);
    }
    catch (GeneralSecurityException e) {
        throw new ExceptionInInitializerError(e);
    }
}

As you can see the SNIextension is commented. I would appreciate an example.

The url I'm trying to access is the next one.

https://www.ocinerioshopping.es/
Sinjuice
  • 532
  • 1
  • 4
  • 21
  • See the fallback strategy described in https://stackoverflow.com/a/14884941/139985 – Stephen C Jun 27 '19 at 08:41
  • What is not clear is if I have to do it for each requests or only once. Also, what does it mean that the second time I have to do it without the hostname? Do I have to resolve the domain and get the ip? – Sinjuice Jun 27 '19 at 08:51
  • 1
    My reading is 1) for each request that fails, and 2) yes. Also, follow the link to the commit that he provided as an example. – Stephen C Jun 27 '19 at 08:52
  • Thats a lot of work just to make a request, also I'll have to find out how to integrate it with Jsoup. I'll give it a try, thank you. – Sinjuice Jun 27 '19 at 08:53
  • Well. Since SIN has been available for ~15 years, you could argue that this is the fault of the site you are trying to access. They should be updating .... – Stephen C Jun 27 '19 at 08:55
  • Sure, yet curl and web browsers handle it well. But thats a conversation for another time. – Sinjuice Jun 27 '19 at 08:57
  • 1
    Read the first paragraph of the linked answer. The Oracle engineers think what they are doing is correct. Period. – Stephen C Jun 27 '19 at 09:14
  • @StephenC Could you unmark the question as duplicate? I managed to solve it in the context with a different approach that the one you linked. I would like to add the solution, if not I will add it at the end of the question. – Sinjuice Jun 27 '19 at 12:48
  • You could add your solution as a solution to the older question! – Stephen C Jun 27 '19 at 12:51
  • Hmm, I don't know if it applies, but maybe. What I did was to "extend" the SSLSocketFactory and rewrite the function createSocket in order to ignore the hostname, then I can pass a SSLSocketFactory to Jsoup to use. That way I can control which instance of JSoup has the SNI disabled and which not. Inspiration came from https://github.com/square/okhttp/issues/3573. It only works if you know which instance need it beforehand. – Sinjuice Jun 27 '19 at 12:55

1 Answers1

1

I managed to solve the issue by extending the SSLSocketConnection and by sending null instead of the hostname when the createSocket is called. That way java disables the SNI. Then I just pass a instance of the new class to Jsoup where I know the SNI will fail.

import javax.net.ssl.*;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;

public class CustomSSLSocketFactory extends SSLSocketFactory {
    private SSLSocketFactory defaultFactory;
    public CustomSSLSocketFactory() throws IOException {
        TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
            public void checkClientTrusted(X509Certificate[] chain, String authType) {
            }

            public void checkServerTrusted(X509Certificate[] chain, String authType) {
            }

            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        }};

        try {
            SSLContext sslContext = SSLContext.getInstance("SSL");
            sslContext.init((KeyManager[])null, trustAllCerts, new SecureRandom());
            defaultFactory = sslContext.getSocketFactory();
        } catch (KeyManagementException | NoSuchAlgorithmException var3) {
            throw new IOException("Can't create unsecure trust manager");
        }
    }
    @Override
    public String[] getDefaultCipherSuites() {
       return defaultFactory.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return defaultFactory.getSupportedCipherSuites();
    }

    @Override
    public Socket createSocket(Socket socket, String s, int i, boolean b) throws IOException {
        //magic happens here, we send null as hostname
        return defaultFactory.createSocket(socket, null, i, b);
    }

    @Override
    public Socket createSocket(String s, int i) throws IOException, UnknownHostException {
        return defaultFactory.createSocket(s,i);
    }

    @Override
    public Socket createSocket(String s, int i, InetAddress inetAddress, int i1) throws IOException, UnknownHostException {
        return defaultFactory.createSocket(s,i,inetAddress,i1);
    }

    @Override
    public Socket createSocket(InetAddress inetAddress, int i) throws IOException {
        return defaultFactory.createSocket(inetAddress, i);
    }

    @Override
    public Socket createSocket(InetAddress inetAddress, int i, InetAddress inetAddress1, int i1) throws IOException {
        return defaultFactory.createSocket(inetAddress,i, inetAddress1, i1);
    }
}

Jsoup initialization.

Connection conn = Jsoup.connect(url);
conn.sslSocketFactory(new CustomSSLSocketFactory());
Sinjuice
  • 532
  • 1
  • 4
  • 21