在測試我的客戶端 - 服務器分佈式系統時,我很驚訝,首先得知TLS的默認JSSE實現不會執行主機名驗證。我在this的問題上試過接受的答案,但我的用例有點不同。我使用RabbitMQ的連接工廠,它抽象了SSLSocket的構造。我只是給連接工廠提供一個SSLContext。我確實發現了很多關於HTTPS甚至其他一些協議,但並不總是可以使用的東西,即使使用定製協議也是如此。帶主機名驗證的SSLContext
雖然除了使用X509ExtendedTrustManager
之外,還沒有關於創建域驗證SSLContext的真正內容。調試時,我可以看到
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init((KeyStore) null);
tmf.getTrustManagers();
返回一個TrustManager
,一個X509TrustManagerImpl
,而根據this頁擴展X509ExtendedTrustManager
。但是,這不會拒絕有缺陷的證書(並且「錯誤」,我的意思是證書與服務器的主機名不匹配)。
所以我再使出從我TrustManagerFactory
寫我自己X509ExtendedTrustManager
,委託給信託經理:
final TrustManager[] trustManagers = tmf.getTrustManagers();
X509ExtendedTrustManager x509ExtendedTrustManager = new X509ExtendedTrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s, Socket socket) throws CertificateException {
checkClientTrusted(x509Certificates, s);
}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s, Socket socket) throws CertificateException {
checkServerTrusted(x509Certificates, s);
}
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) throws CertificateException {
checkClientTrusted(x509Certificates, s);
}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) throws CertificateException {
checkServerTrusted(x509Certificates, s);
}
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
for (TrustManager trustManager : trustManagers)
((X509TrustManager)trustManager).checkClientTrusted(x509Certificates, s);
}
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
for (TrustManager trustManager : trustManagers)
((X509TrustManager)trustManager).checkServerTrusted(x509Certificates, s);
for (X509Certificate x509Certificate : x509Certificates) {
Collection<List<?>> alternativeNameCollection = x509Certificate.getSubjectAlternativeNames();
if (alternativeNameCollection != null) {
for (List alternativeNames : alternativeNameCollection) {
if (alternativeNames.get(1).equals(host))
return;
}
}
}
throw new CertificateException("Certificate hostname and requested hostname don't match");
}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
但這最後的工作。但我不敢相信沒有更好的方式來做到這一點。基本上我在尋找的東西是標準的東西,因爲我認爲我所做的是非常標準的。我閱讀了關於HostnameVerifier
,但是有沒有辦法與SSLContext一起使用它而不使用HTTPS?某處是否存在主機名驗證實施X509ExtendedTrustManager
?我確信我正在重塑已經寫好的東西。
編輯:this是一個很好的工作示例。儘管仍然是自定義代碼
編輯2:問題仍然存在於RabbitMQ中,因爲RabbitMQ解析DNS並將IP地址傳遞給驗證者,這當然總是失敗。所以X509ExtendedTrustManager
仍然是一條路。
我很想暗示這可能是[這個問題](http://stackoverflow.com/q/17972658/372643)的副本(即你實際上在JSSE中獲得了一些開箱即用的東西,與Java 7)。我不確定你的RabbitMQ庫是如何實現主機名驗證的,但如果它不是的話,那是一個bug。 – Bruno
我同意這些問題非常相似,它們都是關於主機名驗證的。國際海事組織的差異在於,這個問題及其答案都是關於HTTPS的。我正在尋找一個通用的解決方案,基本上是爲任何協議添加一個主機名驗證功能,並且我發現它自己編寫這樣的代碼非常危險且不可維護(現在,我已經解決了這個問題)。我寧願依賴一個由具有更多這方面知識的團隊實施的圖書館。 – delucasvb
@delucasvb有關此更多信息,請參閱https://tersesystems.com/2014/03/23/fixing-hostname-verification/ –