I am writing a function to verify if the hostname/CN in a certificate matches the hostname in the url.
My setup: I am Using default SSLSockets provided by Java. I have added a HandshakeCompletedListener to the SSLSocket. (This is just me protoyping a solution. I don't think that its the best way to verify certificates after the handshake is completed)
My Conundrum: when I connect to https://gmail.com i.e. Host: gmail.com Port: 443 over ssl, I get a certificate for CN=mail.google.com. My hostname verification function rejects this certificate and closes the connection.
Strangely, widely used browsers don't do the same. They don't display the usual message "certificate is not trusted. do you want to proceed?" Somehow, they all trust the certificate presented to them even though it doesn't match the hostname in the url.
So what is the browser doing, such that it doesn't reject the certificate outright? What extra steps is it taking to make sure that the certificate becomes valid along the way? By which I mean, after a series of redirects https://gmail.com gets replaced by https://mail.google.com and the certificate validates without any issue since it now matches CN=mail.google.com. Are there any specific rules behind this mechanism?
I would like to hear any ideas that you may have. :)
EDIT: I have included a test program that prints out the certificates sent by the host/peer. And also prints out the http message. The program sends a get request to gmail.com. I still see the CN in the certificate as CN=mail.google.com. Anyone care to test this out? I find this behaviour strange because curl -v -k https://gmail.com
, as suggested by ian in his comment, returns an entirely different result.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.cert.Certificate;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
public class SSLCheck {
public static String[] supportedCiphers = {"SSL_RSA_WITH_RC4_128_MD5",
"SSL_RSA_WITH_RC4_128_SHA",
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
"SSL_RSA_WITH_3DES_EDE_CBC_SHA",
"SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
"SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
"SSL_RSA_WITH_DES_CBC_SHA",
"SSL_DHE_RSA_WITH_DES_CBC_SHA",
"SSL_DHE_DSS_WITH_DES_CBC_SHA",
"SSL_RSA_EXPORT_WITH_RC4_40_MD5",
"SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
"SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
"SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA",
"TLS_EMPTY_RENEGOTIATION_INFO_SCSV"};
public static void main(String[] args) {
int port = 443;
String host = "gmail.com";
try {
Socket sock = SSLSocketFactory.getDefault().createSocket(host, port);
sock.setSoTimeout(2000);
((SSLSocket)sock).setEnabledCipherSuites(supportedCiphers);
PrintWriter out = new PrintWriter(new OutputStreamWriter(sock.getOutputStream()));
out.println("GET " + "/mail" + " HTTP/1.1");
out.println("Host: "+host);
out.println("Accept: */*");
out.println();
out.flush();
BufferedReader in = new BufferedReader(new InputStreamReader(sock.getInputStream()));
SSLSocket ssls = (SSLSocket)sock;
Certificate[] peercerts = ssls.getSession().getPeerCertificates();
System.out.println("***********************PEER CERTS**********************");
for(int i=0;i<peercerts.length;i++){
System.out.println(peercerts[i]);
System.out.println("*********************************************************");
}
String line;
while ((line = in.readLine())!=null) {
System.out.println(line);
}
out.close();
in.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
System.exit(0);
}
}
}