2016-04-11 81 views
1

在測試我的客戶端 - 服務器分佈式系統時,我很驚訝,首先得知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仍然是一條路。

+0

我很想暗示這可能是[這個問題](http://stackoverflow.com/q/17972658/372643)的副本(即你實際上在JSSE中獲得了一些開箱即用的東西,與Java 7)。我不確定你的RabbitMQ庫是如何實現主機名驗證的,但如果它不是的話,那是一個bug。 – Bruno

+0

我同意這些問題非常相似,它們都是關於主機名驗證的。國際海事組織的差異在於,這個問題及其答案都是關於HTTPS的。我正在尋找一個通用的解決方案,基本上是爲任何協議添加一個主機名驗證功能,並且我發現它自己編寫這樣的代碼非常危險且不可維護(現在,我已經解決了這個問題)。我寧願依賴一個由具有更多這方面知識的團隊實施的圖書館。 – delucasvb

+0

@delucasvb有關此更多信息,請參閱https://tersesystems.com/2014/03/23/fixing-hostname-verification/ –

回答

2

主機名驗證不是SSL的一部分。它是HTTPS的一部分。您正在調查錯誤的圖層和錯誤的API。您應該查看HttpURLConnectionHostnameVerifier

+0

我正在使用除HTTPS之外的其他協議,其中一些是自定義協議。 – delucasvb

+0

這不會以任何方式改變我的答案。 – EJP

+0

我編輯了我的問題。我嘗試使用主機名驗證程序,但這並不總是有效,因爲它並不總是接收主機名。一些庫或框架(如RabbitMQ)會發送一個IP地址。 – delucasvb