2011-07-19 54 views
23

我的SSL客戶端連接到我的SSL服務器。 當客戶端無法驗證證書由於根在客戶端的密鑰庫中不存在的,我需要的是證書添加到本地密鑰庫中的代碼,並繼續的選項。 有一些示例總是接受所有證書,但我希望用戶驗證證書並將其添加到本地密鑰存儲,而不必離開應用程序。Java的SSL連接,添加服務器證書密鑰存儲到編程

SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); 
SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket("localhost", 23467); 
try{ 
    sslsocket.startHandshake(); 
} catch (IOException e) { 
    //here I want to get the peer's certificate, conditionally add to local key store, then reauthenticate successfully 
} 

有一大堆的有關自定義的SocketFactory,的TrustManager,SSLContext中,等東西,我真的不知道如何將它們整合在一起,或者這將是我的目標的最短路徑。

+0

這可能有點晚,但http://jcalcote.wordpress.com/2010/06/22/managing-a-dynamic-java-trust-store/此鏈接有幫助。 – goh

回答

19

你可以這樣使用X509TrustManager實現。

獲得一個與SSLContext中

SSLContext ctx = SSLContext.getInstance("TLS"); 

然後用SSLContext#init與您的自定義X509TrustManager初始化。 SecureRandom和KeyManager []可能爲空。後者只有在執行客戶端身份驗證時纔有用,如果在您的方案中只有服務器需要進行身份驗證,則無需設置它。

從此SSLContext,使用SSLContext#getSocketFactory讓您的SSLSocketFactory和按計劃進行。

由於涉及您的X509TrustManager實現,它看起來是這樣的:

TrustManager tm = new X509TrustManager() { 
    public void checkClientTrusted(X509Certificate[] chain, 
        String authType) 
        throws CertificateException { 
     //do nothing, you're the client 
    } 

    public X509Certificate[] getAcceptedIssuers() { 
     //also only relevant for servers 
    } 

    public void checkServerTrusted(X509Certificate[] chain, 
        String authType) 
        throws CertificateException { 
     /* chain[chain.length -1] is the candidate for the 
     * root certificate. 
     * Look it up to see whether it's in your list. 
     * If not, ask the user for permission to add it. 
     * If not granted, reject. 
     * Validate the chain using CertPathValidator and 
     * your list of trusted roots. 
     */ 
    } 
}; 

編輯:

瑞安是正確的,我忘了說明如何將新的根添加到現有的。假設您當前的受信任根的KeyStore源自cacerts(位於jre/lib/security下的JDK附帶的'Java默認信任庫')。我假設你用KeyStore#load(InputStream,char [])加載了這個密鑰存儲(它是JKS格式)。

KeyStore ks = KeyStore.getInstance("JKS"); 
FileInputStream in = new FileInputStream("<path to cacerts""); 
ks.load(in, "changeit".toCharArray); 

如果您還沒有更改它,則默認密碼cacerts是「changeit」。

然後你可以使用KeyStore#setEntry添加addtional信任的根。您可以省略ProtectionParameter(即null),KeyStore.Entry將是一個TrustedCertificateEntry,它將新的根作爲參數傳遞給其構造函數。

KeyStore.Entry newEntry = new KeyStore.TrustedCertificateEntry(newRoot); 
ks.setEntry("someAlias", newEntry, null); 

如果你想堅持的改變信任存儲在某些時候,你可能會KeyStore#store(OutputStream, char[]實現這一目標。

+2

請注意,這是100%不安全。你完全可以不使用SSL。這對於中間人攻擊來說是不錯的。 – EJP

+1

@EJP:請解釋。使用SSL的MITM通常只有在忽略主機名驗證時纔有可能。這仍然由DefaultHostnameVerifier在內部執行。 – emboss

+0

無論這裏的安全性是否正確,它都不回答這個問題。問題是如何以編程方式將密鑰添加到密鑰庫。 –

5

在JSSE API,檢查證書是否可信不一定涉及KeyStore(即負責SSL/TLS的一部分)。絕大多數情況下都會出現這種情況,但假設總是有一個是不正確的。這是通過TrustManager完成的。 此TrustManager很可能使用默認信任存儲KeyStore或通過javax.net.ssl.trustStore系統屬性指定的默認信任存儲,但這不一定是這種情況,並且此文件不一定是$JAVA_HOME/jre/lib/security/cacerts(這一切取決於JRE安全設置)。

在一般情況下,您無法掌握從應用程序中使用的信任管理器所使用的KeyStore,如果在沒有任何系統屬性設置的情況下使用默認信任存儲區,則更是如此。 即使你能找出KeyStore是什麼,你仍然會面臨兩種可能的問題(至少):

  • 您可能無法寫入該文件(這不一定是一個問題如果您沒有永久保存更改)。
  • 這個KeyStore甚至可能不是基於文件的(例如它可能是OSX上的鑰匙串),也可能沒有寫入權限。

我建議什麼是寫周圍的默認TrustManager的包裝(更具體地說,X509TrustManager),這對默認的信任管理器進行檢查,如果這一初步檢查失敗,執行回調到用戶界面檢查是否將其添加到「本地」信任庫。

如果您想使用類似jSSLutils的東西,可以按照this example(帶有brief unit test)所示完成此操作。

準備您SSLContext將這樣進行:

KeyStore keyStore = // ... Create and/or load a keystore from a file 
        // if you want it to persist, null otherwise. 

// In ServerCallbackWrappingTrustManager.CheckServerTrustedCallback 
CheckServerTrustedCallback callback = new CheckServerTrustedCallback { 
    public boolean checkServerTrusted(X509Certificate[] chain, 
      String authType) { 
     return true; // only if the user wants to accept it. 
    } 
} 

// Without arguments, uses the default key managers and trust managers. 
PKIXSSLContextFactory sslContextFactory = new PKIXSSLContextFactory(); 
sslContextFactory 
    .setTrustManagerWrapper(new ServerCallbackWrappingTrustManager.Wrapper(
        callback, keyStore)); 
SSLContext sslContext = sslContextFactory.buildSSLContext(); 
SSLSocketFactory sslSocketFactory = sslContext.getSslSocketFactory(); 
// ... 

// Use keyStore.store(...) if you want to save the resulting keystore for later use. 

(當然,你並不需要使用這個庫及其SSLContextFactory,而是實現自己的X509TrustManager,「包裝」或不是默認如你所願。)

你必須考慮的另一個因素是用戶與此回調的交互。當用戶決定點擊接受或拒絕(例如)時,握手可能已經超時,因此當用戶接受證書時可能需要再次嘗試連接。

在回調設計中要考慮的另一點是,信任管理器不知道正在使用哪個套接字(與其對應的X509KeyManager不同),所以應該與引起什麼用戶操作一樣含糊不清彈出(或者你想要實現回調)。如果建立了多個連接,則不希望驗證錯誤的連接。 似乎有可能通過使用每個SSLSocket的獨特回調,SSLContext和SSLSocketFactory來解決這個問題,該SSLSocket應該創建一個新的連接,以某種方式綁定SSLSocket,然後回調用戶採取的觸發連接嘗試的動作第一名。

+0

所有有趣的點!看到不同語言如何實現這樣的事情,以及解決同一問題的方法有多少,令人驚訝。在我的情況下,我碰巧使用每個客戶端的單個套接字以及由系統屬性指定的自定義密鑰庫。浮雕建議的答案似乎最符合我對這項任務的要求,但我很高興知道一些更大的圖景以及它如何影響這個和未來的項目。 – iratepr

相關問題