[英]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.