简体   繁体   English

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

[英]Certificate validation in browsers (specific case: https://gmail.com)

I am writing a function to verify if the hostname/CN in a certificate matches the hostname in the url. 我正在编写一个函数来验证证书中的主机名/ CN是否与url中的主机名匹配。

My setup: I am Using default SSLSockets provided by Java. 我的设置:我正在使用Java提供的默认SSLSockets I have added a HandshakeCompletedListener to the SSLSocket. 我在SSLSocket中添加了HandshakeCompletedListener (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 ie Host: gmail.com Port: 443 over ssl , I get a certificate for CN=mail.google.com. 我的难题:当我连接到https://gmail.com主机:gmail.com端口:443 over ssl时 ,我获得了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. 我的意思是,一系列重定向后https://gmail.com被替换https://mail.google.com和证书验证,没有任何问题,因为现在比赛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. 并打印出http消息。 The program sends a get request to gmail.com. 该程序向gmail.com发送get请求。 I still see the CN in the certificate as CN=mail.google.com. 我仍然在证书中看到CN为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. 我发现这种行为很奇怪,因为curl -v -k https://gmail.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);
    }
}

}

The certificate you get without using the Server Name Indication extension is indeed for CN=mail.google.com , without any Subject Alternative Name for gmail.com . 您未使用服务器名称指示扩展而获得的证书确实适用于CN=mail.google.com ,没有任何gmail.com主题备用名称。

If you try with SNI, you'll get a different cert, for gmail.com : 如果您尝试使用SNI,您将获得gmail.com的不同证书:

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

Most modern version of browsers on desktop support SNI. 桌面上大多数现代版本的浏览器都支持SNI。 On the desktop, the main remaining one not supporting SNI is IE on any version of XP. 在桌面上,主要的不支持SNI的是任何版本的XP上的IE。 Since it's a TLS extension that hasn't been backported to SSLv3, this also explains why it doesn't work with curl -3 . 由于它是一个尚未向后移植到SSLv3的TLS扩展,这也解释了为什么它不适用于curl -3

Support for SNI in Java is only available since Java 7 (and only on the client side) . 支持Java中的SNI 仅在Java 7之后可用(并且仅在客户端)

(By the way, unless you know what you're doing, leave the default enabled cipher suites, in particular, don't enable weak ones like the EXPORT or pseudo-suites like TLS_EMPTY_RENEGOTIATION_INFO_SCSV .) (顺便说一句,除非你知道自己在做什么,否则请保留默认启用的密码套件,特别是不要启用像EXPORT这样的弱套件或像TLS_EMPTY_RENEGOTIATION_INFO_SCSV这样的伪套件。)

A quick test with curl -v tells me that the initial connection to https://gmail.com presents a certificate with CN=gmail.com . 使用curl -v快速测试告诉我,与https://gmail.com的初始连接提供了CN=gmail.com的证书。 The HTTP response inside the SSL envelope is a 301 redirect to https://mail.google.com/mail/ , which in turn presents a CN=mail.google.com certificate. SSL信封内的HTTP响应是301重定向到https://mail.google.com/mail/ ,后者又提供CN=mail.google.com证书。 So in this case the CNs do in fact match at every stage. 因此,在这种情况下,CN 确实在每个阶段都匹配。

But in general, you need to learn about the "subject alternative name" extension, which is a way for a certificate to specify a number of different host names that it is valid for in addition to the main CN. 但总的来说,您需要了解“主题替代名称”扩展,这是证书指定除主CN之外的有效主机名的一种方式。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM