浏览器中的证书验证(特定情况:https://gmail.com)

问题描述:

我在写一个函数来验证证书中的主机名/ CN是否与url中的主机名匹配。浏览器中的证书验证(特定情况:https://gmail.com)

我的设置: 我使用默认的SSLSockets Java提供。 我已将一个HandshakeCompletedListener添加到SSLSocket。 (这只是我的原型制造的解决方案,我不认为它的最好的方式来验证证书握手结束后。)

我的难题: 当我连接到https://gmail.com主持人:gmail.com端口:443通过ssl,我获得CN = mail.google.com的证书。我的主机名验证功能拒绝此证书并关闭连接。

奇怪的是,广泛使用的浏览器并没有这样做。他们不显示通常的消息“证书不可信,你想继续吗?”不知何故,他们都信任呈现给他们的证书,即使它与url中的主机名不匹配。

那么浏览器在做什么,这样它就不会直接拒绝证书?采取哪些额外步骤来确保证书在此过程中有效?我的意思是,经过一系列重定向https://gmail.com被替换为https://mail.google.com,证书没有任何问题,因为它现在匹配CN = mail.google.com。这个机制背后有没有特定的规则?

我想听听你有什么想法。 :)

编辑:我已经包括一个测试程序,打印出由主机/同行发送的证书。并打印出http消息。该程序向gmail.com发送获取请求。我仍将证书中的CN视为CN = mail.google.com。任何人都在关心测试呢?我发现这种行为很奇怪,因为,正如ian在他的评论中所建议的那样,返回的结果完全不同。

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

} 

您在没有使用服务器名称指示扩展名的情况下得到的证书确实为CN=mail.google.com,没有任何使用gmail.com的主题备用名称。

如果您符合SNI尝试,你会得到一个不同的证书,为gmail.com:浏览器

openssl s_client -connect gmail.com:443 -servername gmail.com | openssl x509 -text -noout 

大多数现代版本的桌面支持SNI。在桌面上,剩下的不支持SNI的主要是任何版本的XP上的IE浏览器。由于这是一个尚未回溯到SSLv3的TLS扩展,这也解释了为什么它不适用于curl -3

仅支持Java中的SNI,自Java 7 (and only on the client side)开始可用。

(顺便说一下,除非你知道自己在做什么,保留默认启用的密码套件,特别是不要让弱国象EXPORT或伪像套房TLS_EMPTY_RENEGOTIATION_INFO_SCSV

+0

选择此作为正确答案,因为它指出问题的根源。我推荐阅读伊恩的评论。 – psykeron

快速测试与curl -v告诉我,到https://gmail.com初始连接呈现CN=gmail.com证书。 SSL信封内的HTTP响应是301重定向到https://mail.google.com/mail/,后者又提供CN=mail.google.com证书。所以在这种情况下,CN 确实实际上在每个阶段都是匹配的。

但是一般来说,您需要了解“主题备选名称”扩展名,这是证书指定除主要CN之外还有效的多个不同主机名称的一种方式。

+0

我做检查主题替代名称,但在我收到的证书中找不到任何名称。我打印出我在我的HandshakeCompletedListeners中收到的凭据,CN = mail.google.com是我发现的。除非Java的SSLSockets正在做一些它不应该做的奇怪事情。 – psykeron

+0

我刚刚下载了Curl并做了'curl -v -k https:// gmail.com'。我确实拿到了CN = gmail.com的证书。现在我完全丧失了原来问题的原因。 – psykeron

+0

我更新了我最初的问题。这种奇怪的行为与java ssl套接字一致。 – psykeron