android retrofit Hostname not verified

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());

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;
    }