Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

android retrofit Hostname not verified

I got certificate issued for IP address (Not a common name) and I'm triyng to connect to the server with that certificate.

OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
OkHttpClient okHttpClient = builder.build();
Gson gson = new GsonBuilder()
                .setLenient()
                .create();
retrofit = new Retrofit.Builder()
                .baseUrl(url)
                .client(okHttpClient)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .build();
ServerRouts service = retrofit.create(ServerRouts.class);
Resp_json> call = service.login(param, user, pw);

and I got an error:

Hostname 11.8.222.333 not verified:

but when I use

builder.hostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            });

then everything works.

How to solve that error without turning off hostname verifier?

P.S. My certificate issued for IP (11.8.222.333)

like image 936
Rainmaker Avatar asked Jan 09 '17 06:01

Rainmaker


2 Answers

I redefined verify method like that ( just copied sources from DefaultHostnameVerifier.java ) and everything works now. I don't know why it didn't work but now it's fine.

builder.hostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {

                Certificate[] certs;
                try {
                    certs = session.getPeerCertificates();
                } catch (SSLException e) {
                    return false;
                }
                X509Certificate x509 = (X509Certificate) certs[0];
                // We can be case-insensitive when comparing the host we used to
                // establish the socket to the hostname in the certificate.
                String hostName = hostname.trim().toLowerCase(Locale.ENGLISH);
                // Verify the first CN provided. Other CNs are ignored. Firefox, wget,
                // curl, and Sun Java work this way.
                String firstCn = getFirstCn(x509);
                if (matches(hostName, firstCn)) {
                    return true;
                }
                for (String cn : getDNSSubjectAlts(x509)) {
                    if (matches(hostName, cn)) {
                        return true;
                    }
                }
                return false;

            }
        });


private String getFirstCn(X509Certificate cert) {
        String subjectPrincipal = cert.getSubjectX500Principal().toString();
        for (String token : subjectPrincipal.split(",")) {
            int x = token.indexOf("CN=");
            if (x >= 0) {
                return token.substring(x + 3);
            }
        }
        return null;
    }
like image 56
Rainmaker Avatar answered Nov 08 '22 17:11

Rainmaker


The default HostNameVerifier for OkHttpClient.Builder is okhttp3.internal.tls.OkHostnameVerifier. It appears that this verifier does not match hostname against common name in peer's certificate, but only against subject alternative names. This behavior is by design and not a bug - look at this issue: https://github.com/square/okhttp/issues/4966

Two options solve it:

  1. If you want to continue using the OkHostnameVerifier, you should make sure your server certificate contains Subject Alternative Name (s). At least repeat the common name in the SAN. I found this gist helpful in doing so: https://gist.github.com/croxton/ebfb5f3ac143cd86542788f972434c96

  2. If you cannot control server certificate, you can use an alternative HostNameVerifier implementation which also matches against certificate's CN. The one in @Rainmaker's answer (https://stackoverflow.com/a/41543384/978164) will do. I also found org.apache.http.conn.ssl.DefaultHostnameVerifier in Apache's httpclient package to work fine and its probably a more through implementation.

// instantiate and configure your OkHttpClient.Builder and then:
builder.hostnameVerifier(new org.apache.http.conn.ssl.DefaultHostnameVerifier());
like image 2
dux2 Avatar answered Nov 08 '22 16:11

dux2