2014-04-17 54 views
8

我的應用程序有一個個人密鑰庫,其中包含用於本地網絡的可信自簽名證書 - 比如mykeystore.jks。我希望能夠使用本地供應的自簽名證書連接到公共網站(例如google.com)以及本地網絡中的公共網站。如何使用多個信任來源初始化TrustManagerFactory?

這裏的問題是,當我連接到https://google.com,路徑構建失敗,因爲設置自己的密鑰庫將覆蓋包含與JRE捆綁根CA的默認密鑰庫,報告異常

sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target 

但是,如果我將CA證書導入我自己的密鑰庫(mykeystore.jks),它工作正常。有沒有辦法支持兩者?

我有自己TrustManger用於此目的,

public class CustomX509TrustManager implements X509TrustManager { 

     X509TrustManager defaultTrustManager; 

     public MyX509TrustManager(KeyStore keystore) { 
       TrustManagerFactory trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 
       trustMgrFactory.init(keystore); 
       TrustManager trustManagers[] = trustMgrFactory.getTrustManagers(); 
       for (int i = 0; i < trustManagers.length; i++) { 
        if (trustManagers[i] instanceof X509TrustManager) { 
         defaultTrustManager = (X509TrustManager) trustManagers[i]; 
         return; 
        } 
       } 

     public void checkServerTrusted(X509Certificate[] chain, String authType) 
       throws CertificateException { 
      try { 
       defaultTrustManager.checkServerTrusted(chain, authType); 
      } catch (CertificateException ce) { 
      /* Handle untrusted certificates */ 
      } 
     } 
    } 

然後我初始化的SSLContext, 的TrustManager [] trustManagers = 新的TrustManager [] {新CustomX509TrustManager(密鑰庫)}; SSLContext customSSLContext = SSLContext.getInstance(「TLS」); customSSLContext.init(null,trustManagers,null);

,並設置套接字工廠,

HttpsURLConnection.setDefaultSSLSocketFactory(customSSLContext.getSocketFactory()); 

主程序,

URL targetServer = new URL(url); 
HttpsURLConnection conn = (HttpsURLConnection) targetServer.openConnection(); 

如果我不把我自己的信任管理器,它連接到https://google.com就好了。我如何獲得指向默認密鑰庫的「默認信任管理器」?

+0

[JVM中註冊多個密鑰庫]的可能的複製(http://stackoverflow.com/questions/1793979/registering-multiple-keystores-in-jvm) – OrangeDog

回答

-1

當您初始化SSLContext時,您提供TrustManager的數組。您提供兩種:使用JRE信任庫的默認產品,以及使用您的另一種產品。代表團模式在這裏是錯誤的答案。

+0

不幸的是,沒有辦法獲得由JSSE使用的默認信任管理器(使用JRE CA),只有套接字工廠。我可以實現自己的信任管理器來搜索JRE證書,但我認爲這不是一個乾淨的解決方案,因爲它取決於JRE的位置。 – varrunr

+1

只使用數組中特定鍵和/或信任管理器實現類型的第一個實例。 (例如,將只使用數組中的第一個javax.net.ssl.X509KeyManager。) – Dale

14

trustMgrFactory.init(keystore);您正在配置defaultTrustManager與您自己的個人密鑰庫,而不是系統默認密鑰庫。

基於閱讀源代碼sun.security.ssl.TrustManagerFactoryImpl,它看起來像trustMgrFactory.init((KeyStore) null);會做你需要的東西(加載系統默認密鑰庫),並基於快速測試,它似乎爲我工作。

+1

我已經用Java 1.8(build 1.8.0_60-b27)嘗試了它。但它不適合我。我有同樣的錯誤:PKIX路徑構建失敗。 –

+0

但是,這不會讓您在自定義密鑰庫中使用您的證書,而只能在系統上安裝CA證書。 –

+0

通過閱讀資料來源瞭解這一點的榮譽!這簡化了很多cacerts路徑和名稱處理雜耍!我也驚訝於它也沒有密碼要求。 – FUD

1

我遇到了與Commons HttpClient相同的問題。工作了我的情況的解決方案是在以下方法來創建PKIX TrustManagers委託鏈:

public class TrustManagerDelegate implements X509TrustManager { 
    private final X509TrustManager mainTrustManager; 
    private final X509TrustManager trustManager; 
    private final TrustStrategy trustStrategy; 

    public TrustManagerDelegate(X509TrustManager mainTrustManager, X509TrustManager trustManager, TrustStrategy trustStrategy) { 
     this.mainTrustManager = mainTrustManager; 
     this.trustManager = trustManager; 
     this.trustStrategy = trustStrategy; 
    } 

    @Override 
    public void checkClientTrusted(
      final X509Certificate[] chain, final String authType) throws CertificateException { 
     this.trustManager.checkClientTrusted(chain, authType); 
    } 

    @Override 
    public void checkServerTrusted(
      final X509Certificate[] chain, final String authType) throws CertificateException { 
     if (!this.trustStrategy.isTrusted(chain, authType)) { 
      try { 
       mainTrustManager.checkServerTrusted(chain, authType); 
      } catch (CertificateException ex) { 
       this.trustManager.checkServerTrusted(chain, authType); 
      } 
     } 
    } 

    @Override 
    public X509Certificate[] getAcceptedIssuers() { 
     return this.trustManager.getAcceptedIssuers(); 
    } 

} 

而在下面的方式初始化HttpClient的(是的,它的醜陋):

final SSLContext sslContext; 
try { 
    sslContext = SSLContext.getInstance("TLS"); 
    final TrustManagerFactory javaDefaultTrustManager = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 
    javaDefaultTrustManager.init((KeyStore)null); 
    final TrustManagerFactory customCaTrustManager = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 
    customCaTrustManager.init(getKeyStore()); 

    sslContext.init(
     null, 
     new TrustManager[]{ 
      new TrustManagerDelegate(
        (X509TrustManager)customCaTrustManager.getTrustManagers()[0], 
        (X509TrustManager)javaDefaultTrustManager.getTrustManagers()[0], 
        new TrustSelfSignedStrategy() 
      ) 
     }, 
     secureRandom 
    ); 

} catch (final NoSuchAlgorithmException ex) { 
    throw new SSLInitializationException(ex.getMessage(), ex); 
} catch (final KeyManagementException ex) { 
    throw new SSLInitializationException(ex.getMessage(), ex); 
} 

SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext); 

PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(
     RegistryBuilder.<ConnectionSocketFactory>create() 
       .register("http", PlainConnectionSocketFactory.getSocketFactory()) 
       .register("https", sslSocketFactory) 
       .build() 
); 
//maximum parallel requests is 500 
cm.setMaxTotal(500); 
cm.setDefaultMaxPerRoute(500); 

CredentialsProvider cp = new BasicCredentialsProvider(); 
cp.setCredentials(
     new AuthScope(apiSettings.getIdcApiUrl(), 443), 
     new UsernamePasswordCredentials(apiSettings.getAgencyId(), apiSettings.getAgencyPassword()) 
); 

client = HttpClients.custom() 
        .setConnectionManager(cm) 
        .build(); 

在你的情況簡單HttpsURLConnection的你可與委託類的簡化版本獲得通過:

public class TrustManagerDelegate implements X509TrustManager { 
    private final X509TrustManager mainTrustManager; 
    private final X509TrustManager trustManager; 

    public TrustManagerDelegate(X509TrustManager mainTrustManager, X509TrustManager trustManager) { 
     this.mainTrustManager = mainTrustManager; 
     this.trustManager = trustManager; 
    } 

    @Override 
    public void checkClientTrusted(
      final X509Certificate[] chain, final String authType) throws CertificateException { 
     this.trustManager.checkClientTrusted(chain, authType); 
    } 

    @Override 
    public void checkServerTrusted(
      final X509Certificate[] chain, final String authType) throws CertificateException { 
     try { 
      mainTrustManager.checkServerTrusted(chain, authType); 
     } catch (CertificateException ex) { 
      this.trustManager.checkServerTrusted(chain, authType); 
     } 
    } 

    @Override 
    public X509Certificate[] getAcceptedIssuers() { 
     return this.trustManager.getAcceptedIssuers(); 
    } 

} 
+0

解決方案在這裏有更詳細的描述:http://blog.novoj.net/2016/02/29/how-to-make-apache-httpclient-trust-讓加密證書權威/ – Novoj

6

答案here是我才明白如何做到這一點。如果您只想接受系統CA證書以及定製的證書密鑰庫,則可以使用一些便捷方法將其簡化爲單個類。完整的代碼可在這裏:

https://gist.github.com/HughJeffner/6eac419b18c6001aeadb

KeyStore keystore; // Get your own keystore here 
    SSLContext sslContext = SSLContext.getInstance("TLS"); 
    TrustManager[] tm = CompositeX509TrustManager.getTrustManagers(keystore); 
    sslContext.init(null, tm, null); 
+0

複合信任管理器是一個非常有用的解決方案。 – Kane