我正在编写一个函数来验证证书中的主机名/ CN是否与网址中的主机名匹配。
我的设置: 我正在使用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);
}
}
}
答案 0 :(得分:3)
您未使用服务器名称指示扩展程序获得的证书确实适用于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 7 (and only on the client side)之后才支持Java中的SNI。
(顺便说一句,除非你知道自己在做什么,否则请保留默认启用的密码套件,特别是不要启用弱EXPORT
或伪套件,例如TLS_EMPTY_RENEGOTIATION_INFO_SCSV
。)
答案 1 :(得分:2)
使用curl -v
进行的快速测试告诉我,https://gmail.com
的初始连接会显示CN=gmail.com
的证书。 SSL信封内的HTTP响应是301重定向到https://mail.google.com/mail/
,后者又提供CN=mail.google.com
证书。所以在这种情况下,CN 做实际上在每个阶段都匹配。
但总的来说,您需要了解“主题替代名称”扩展,这是证书指定除主CN之外有效的许多不同主机名的一种方式。