[英]Certificate validation in browsers (specific case: https://gmail.com)
我正在编写一个函数来验证证书中的主机名/ CN是否与url中的主机名匹配。
我的设置:我正在使用Java提供的默认SSLSockets 。 我在SSLSocket中添加了HandshakeCompletedListener 。 (这只是我对解决方案进行原型设计。我不认为这是握手完成后验证证书的最佳方法)
我的难题:当我连接到https://gmail.com即主机:gmail.com端口:443 over ssl时 ,我获得了CN = mail.google.com的证书。 我的主机名验证功能拒绝此证书并关闭连接。
奇怪的是,广泛使用的浏览器不会这样做。 它们不显示通常的消息“证书不可信。你想继续吗?” 不知何故,他们都信任提交给他们的证书,即使它与网址中的主机名不匹配。
那么浏览器在做什么,这样它就不会完全拒绝证书? 需要采取哪些额外措施来确保证书在整个过程中有效? 我的意思是,一系列重定向后https://gmail.com被替换https://mail.google.com和证书验证,没有任何问题,因为现在比赛CN = mail.google.com。 这种机制背后有什么具体规则吗?
我想听听你的任何想法。 :)
编辑:我已经包含了一个测试程序,打印出主机/同行发送的证书。 并打印出http消息。 该程序向gmail.com发送get请求。 我仍然在证书中看到CN为CN = mail.google.com。 有人关心测试吗? 我发现这种行为很奇怪,因为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);
}
}
}
您未使用服务器名称指示扩展而获得的证书确实适用于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之后可用(并且仅在客户端) 。
(顺便说一句,除非你知道自己在做什么,否则请保留默认启用的密码套件,特别是不要启用像EXPORT
这样的弱套件或像TLS_EMPTY_RENEGOTIATION_INFO_SCSV
这样的伪套件。)
使用curl -v
快速测试告诉我,与https://gmail.com
的初始连接提供了CN=gmail.com
的证书。 SSL信封内的HTTP响应是301重定向到https://mail.google.com/mail/
,后者又提供CN=mail.google.com
证书。 因此,在这种情况下,CN 确实在每个阶段都匹配。
但总的来说,您需要了解“主题替代名称”扩展,这是证书指定除主CN之外的有效主机名的一种方式。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.