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:
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/ebfb5f3ac143cd86542788f972434c96If 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'shttpclient
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;
}