2011-03-05 74 views
31

我在tomcat中啓用了https併爲服務器認證擁有一個自簽名證書。我使用Apache httpClient創建了一個http客戶端。我已經設置了一個加載服務器證書的信任管理器。 http客戶端可以連接服務器沒有問題。要查看是怎麼回事我啓用調試:使用Apache httpclient進行https https

System.setProperty("javax.net.debug", "ssl"); 

我看到了,我無法理解在所有的情況如下:

*** 
adding as trusted cert: 
    Subject: CN=Me, OU=MyHouse, O=Home, L=X, ST=X, C=BB 
    Issuer: CN=Me, OU=MyHouse, O=Home, L=X, ST=X, C=BB 
    Algorithm: RSA; Serial number: 0x4d72356b 
    Valid from Sat Mar 05 15:06:51 EET 2011 until Fri Jun 03 16:06:51 EEST 2011 

我的證書顯示,並添加到信任庫(因爲我看到) 。然後:

trigger seeding of SecureRandom 
done seeding SecureRandom 

這裏是部分來自調試跟蹤我不明白:

trustStore is: C:\Program Files\Java\jre6\lib\security\cacerts 
trustStore type is : jks 
trustStore provider is : 
init truststore 
adding as trusted cert: 
    Subject: CN=SwissSign Platinum CA - G2, O=SwissSign AG, C=CH 
    Issuer: CN=SwissSign Platinum CA - G2, O=SwissSign AG, C=CH 
    Algorithm: RSA; Serial number: 0x4eb200670c035d4f 
    Valid from Wed Oct 25 11:36:00 EEST 2006 until Sat Oct 25 11:36:00 EEST 2036 

adding as trusted cert: 
    Subject: [email protected], CN=http://www.valicert.com/, OU=ValiCert Class 1 Policy Validation Authority, O="ValiCert, Inc.", L=ValiCert Validation Network 
    Issuer: [email protected], CN=http://www.valicert.com/, OU=ValiCert Class 1 Policy Validation Authority, O="ValiCert, Inc.", L=ValiCert Validation Network 
    Algorithm: RSA; Serial number: 0x1 
    Valid from Sat Jun 26 01:23:48 EEST 1999 until Wed Jun 26 01:23:48 EEST 2019 

看來,它也採用了默認的Java信任庫!我的問題是爲什麼會發生這種情況?

在我的代碼中,我明確指定要使用的特定信任存儲(通過truststoremanagers)。我期待只有這個被使用。似乎我的truststore和java的默認都被使用了。這是應該如何工作?

UPDATE:
我試過如下:

System.out.println("TMF No:"+tmf.getTrustManagers().length); 
System.out.println("Class is "+tmf.getTrustManagers()[0].getClass().getName()); 

我想,我應該看到2個信任管理器,因爲2個密鑰庫(我和Java的默認顯示使用)。
但結果只有1個信任管理器!

TMF No:1 
Class is com.sun.net.ssl.internal.ssl.X509TrustManagerImpl 

UPDATE2:正如你在代碼中看到波紋管我指定我keystore.My意料的是,這僅應使用(不是這個 CACERT以及)

HttpClient client = new DefaultHttpClient(); 
    SSLContext sslContext = SSLContext.getInstance("TLS");  

    TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 
    KeyStore ks = KeyStore.getInstance("JKS"); 
    File trustFile = new File("clientTrustStore.jks"); 
    ks.load(new FileInputStream(trustFile), null); 
    tmf.init(ks); 
    sslContext.init(null, tmf.getTrustManagers(),null); 
    SSLSocketFactory sf = new SSLSocketFactory(sslContext); 
    sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); 
    Scheme scheme = new Scheme("https", sf, 443); 
    client.getConnectionManager().getSchemeRegistry().register(scheme); 
    httpGet = new HttpGet("https://localhost:8443/myApp"); 
    HttpResponse httpResponse = client.execute(httpGet); 

不對我來說沒有意義。

回答

23

我放在一起本次測試的應用程序使用HTTP測試框架來自Apache HttpClient的包來重現問題:

ClassLoader cl = HCTest.class.getClassLoader(); 
URL url = cl.getResource("test.keystore"); 
KeyStore keystore = KeyStore.getInstance("jks"); 
char[] pwd = "nopassword".toCharArray(); 
keystore.load(url.openStream(), pwd); 

TrustManagerFactory tmf = TrustManagerFactory.getInstance(
     TrustManagerFactory.getDefaultAlgorithm()); 
tmf.init(keystore); 
TrustManager[] tm = tmf.getTrustManagers(); 

KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(
     KeyManagerFactory.getDefaultAlgorithm()); 
kmfactory.init(keystore, pwd); 
KeyManager[] km = kmfactory.getKeyManagers(); 

SSLContext sslcontext = SSLContext.getInstance("TLS"); 
sslcontext.init(km, tm, null); 

LocalTestServer localServer = new LocalTestServer(sslcontext); 
localServer.registerDefaultHandlers(); 

localServer.start(); 
try { 

    DefaultHttpClient httpclient = new DefaultHttpClient(); 
    TrustStrategy trustStrategy = new TrustStrategy() { 

     public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException { 
      for (X509Certificate cert: chain) { 
       System.err.println(cert); 
      } 
      return false; 
     } 

    }; 

    SSLSocketFactory sslsf = new SSLSocketFactory("TLS", null, null, keystore, null, 
      trustStrategy, new AllowAllHostnameVerifier()); 
    Scheme https = new Scheme("https", 443, sslsf); 
    httpclient.getConnectionManager().getSchemeRegistry().register(https); 

    InetSocketAddress address = localServer.getServiceAddress(); 
    HttpHost target1 = new HttpHost(address.getHostName(), address.getPort(), "https"); 
    HttpGet httpget1 = new HttpGet("/random/100"); 
    HttpResponse response1 = httpclient.execute(target1, httpget1); 
    System.err.println(response1.getStatusLine()); 
    HttpEntity entity1 = response1.getEntity(); 
    EntityUtils.consume(entity1); 
    HttpHost target2 = new HttpHost("www.verisign.com", 443, "https"); 
    HttpGet httpget2 = new HttpGet("/"); 
    HttpResponse response2 = httpclient.execute(target2, httpget2); 
    System.err.println(response2.getStatusLine()); 
    HttpEntity entity2 = response2.getEntity(); 
    EntityUtils.consume(entity2); 
} finally { 
    localServer.stop(); 
} 

即使Sun的JSSE實現出於某種原因總是從默認信任存儲中讀取信任材料,但似乎並未將其添加到SSL上下文中並影響SSL握手過程中的信任驗證過程。

下面是測試應用程序的輸出。正如您所看到的,第一個請求成功,而第二個請求失敗,因爲連接到www.verisign.com被拒絕爲不可信。

[ 
[ 
    Version: V1 
    Subject: CN=Simple Test Http Server, OU=Jakarta HttpClient Project, O=Apache Software Foundation, L=Unknown, ST=Unknown, C=Unknown 
    Signature Algorithm: SHA1withDSA, OID = 1.2.840.10040.4.3 

    Key: Sun DSA Public Key 
    Parameters:DSA 
    p:  fd7f5381 1d751229 52df4a9c 2eece4e7 f611b752 3cef4400 c31e3f80 b6512669 
    455d4022 51fb593d 8d58fabf c5f5ba30 f6cb9b55 6cd7813b 801d346f f26660b7 
    6b9950a5 a49f9fe8 047b1022 c24fbba9 d7feb7c6 1bf83b57 e7c6a8a6 150f04fb 
    83f6d3c5 1ec30235 54135a16 9132f675 f3ae2b61 d72aeff2 2203199d d14801c7 
    q:  9760508f 15230bcc b292b982 a2eb840b f0581cf5 
    g:  f7e1a085 d69b3dde cbbcab5c 36b857b9 7994afbb fa3aea82 f9574c0b 3d078267 
    5159578e bad4594f e6710710 8180b449 167123e8 4c281613 b7cf0932 8cc8a6e1 
    3c167a8b 547c8d28 e0a3ae1e 2bb3a675 916ea37f 0bfa2135 62f1fb62 7a01243b 
    cca4f1be a8519089 a883dfe1 5ae59f06 928b665e 807b5525 64014c3b fecf492a 

    y: 
    f0cc639f 702fd3b1 03fa8fa6 676c3756 ea505448 23cd1147 fdfa2d7f 662f7c59 
    a02ddc1a fd76673e 25210344 cebbc0e7 6250fff1 a814a59f 30ff5c7e c4f186d8 
    f0fd346c 29ea270d b054c040 c74a9fc0 55a7020f eacf9f66 a0d86d04 4f4d23de 
    7f1d681f 45c4c674 5762b71b 808ded17 05b74baf 8de3c4ab 2ef662e3 053af09e 

    Validity: [From: Sat Dec 11 14:48:35 CET 2004, 
       To: Tue Dec 09 14:48:35 CET 2014] 
    Issuer: CN=Simple Test Http Server, OU=Jakarta HttpClient Project, O=Apache Software Foundation, L=Unknown, ST=Unknown, C=Unknown 
    SerialNumber: [ 41bafab3] 

] 
    Algorithm: [SHA1withDSA] 
    Signature: 
0000: 30 2D 02 15 00 85 BE 6B D0 91 EF 34 72 05 FF 1A 0-.....k...4r... 
0010: DB F6 DE BF 92 53 9B 14 27 02 14 37 8D E8 CB AC .....S..'..7.... 
0020: 4E 6C 93 F2 1F 7D 20 A1 2D 6F 80 5F 58 AE 33  Nl.... .-o._X.3 

] 
HTTP/1.1 200 OK 
[ 
[ 
    Version: V3 
    Subject: CN=www.verisign.com, OU=" Production Security Services", O="VeriSign, Inc.", STREET=487 East Middlefield Road, L=Mountain View, ST=California, OID.2.5.4.17=94043, C=US, SERIALNUMBER=2497886, OID.2.5.4.15="V1.0, Clause 5.(b)", OID.1.3.6.1.4.1.311.60.2.1.2=Delaware, OID.1.3.6.1.4.1.311.60.2.1.3=US 
    Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5 

    Key: Sun RSA public key, 2048 bits 
    modulus: 20699622354183393041832954221256409980425015218949582822286196083815087464214375375678538878841956356687753084333860738385445545061253653910861690581771234068858443439641948884498053425403458465980515883570440998475638309355278206558031134532548167239684215445939526428677429035048018486881592078320341210422026566944903775926801017506416629554190534665876551381066249522794321313235316733139718653035476771717662585319643139144923795822646805045585537550376512087897918635167815735560529881178122744633480557211052246428978388768010050150525266771462988042507883304193993556759733514505590387262811565107773578140271 
    public exponent: 65537 
    Validity: [From: Wed May 26 02:00:00 CEST 2010, 
       To: Sat May 26 01:59:59 CEST 2012] 
    Issuer: CN=VeriSign Class 3 Extended Validation SSL SGC CA, OU=Terms of use at https://www.verisign.com/rpa (c)06, OU=VeriSign Trust Network, O="VeriSign, Inc.", C=US 
    SerialNumber: [ 53d2bef9 24a7245e 83ca01e4 6caa2477] 

Certificate Extensions: 10 
[1]: ObjectId: 1.3.6.1.5.5.7.1.1 Criticality=false 
AuthorityInfoAccess [ 
    [accessMethod: 1.3.6.1.5.5.7.48.1 
    accessLocation: URIName: http://EVIntl-ocsp.verisign.com, accessMethod: 1.3.6.1.5.5.7.48.2 
    accessLocation: URIName: http://EVIntl-aia.verisign.com/EVIntl2006.cer] 
] 

... 

] 
Exception in thread "main" javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated 
    at com.sun.net.ssl.internal.ssl.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:345) 
    at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:128) 
    at org.apache.http.conn.ssl.SSLSocketFactory.createLayeredSocket(SSLSocketFactory.java:446) 
... 
+0

這是一個很好的測試。感謝您的幫助 – Cratylus 2011-03-09 20:52:57

+0

嗨Oleg,我面臨類似的問題,但只有當我發送POST請求,而不是通過GET。任何見解? – Abhishek 2011-05-09 06:45:48

+0

那麼,在這個地方是否存在一個可以追蹤解決方案的bug? – pulkitsinghal 2013-01-08 17:20:17

2

根據需要指定密鑰存儲在documentation

Protocol authhttps = new Protocol("https", 
     new AuthSSLProtocolSocketFactory(
      new URL("file:my.keystore"), "mypassword", 
      new URL("file:my.truststore"), "mypassword"), 443); 

HttpClient client = new HttpClient(); 
client.getHostConfiguration().setHost("localhost", 443, authhttps); 
+0

修改後的版本http://hc.apache.org/httpcomponents-客戶GA /。我正在做類似的計劃Scheme = new Scheme(「https」,443,sf); ();和sf是'SSLSocketFactory sf =新的SSLSocketFactory(sslContext,SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);'和在sslContext中我已經設置了我的信任存儲管理器。我可以更新如果它會幫助 – Cratylus 2011-03-05 21:33:40

+0

@ user384706我有些困惑。問題是_client_正在使用2個TrustManagers(SSLContext.init不應該這麼做),還是它的行爲奇怪的tomcat _server_? – extraneon 2011-03-05 21:47:29

+0

Tomcat.Tomcat沒有問題發送我配置的證書。問題出現在客戶端部分。我指定了一個可信的trustore,期望僅使用它。調試信息顯示我的以及默認的java信任庫。我更新了我的帖子,在客戶端部分 – Cratylus 2011-03-05 21:59:05

4

當我使用Apache HTTP客戶端4.3時,我使用池連接管理器或基本連接管理器連接到HTTP客戶端。我注意到,從使用java SSL調試,這些類加載cacerts信任存儲,而不是我已經編程指定的。

PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); 
BasicHttpClientConnectionManager cm = new BasicHttpClientConnectionManager(); 
builder.setConnectionManager(cm); 

我想使用它們,但最終刪除它們並創建一個沒有它們的HTTP客戶端。請注意,構建器是一個HttpClientBuilder。

我使用Java SSL調試標誌運行我的程序時已確認,並在調試器中停止。我使用-Djavax.net.debug = ssl作爲VM參數。我在調試器中停止了我的代碼,當上面的* ClientConnectionManager被構造時,cacerts文件將被加載。

+2

非常感謝你。我不能爲我的生活找出問題所在,然後在閱讀後發現,如果您已經通過連接管理器,構建器基本上忽略了您提供的sslContext。 – 2015-04-15 10:34:48

4

這是對我工作:

KeyStore keyStore = KeyStore.getInstance("PKCS12"); 
    FileInputStream instream = new FileInputStream(new File("client-p12-keystore.p12")); 
    try { 
     keyStore.load(instream, "password".toCharArray()); 
    } finally { 
     instream.close(); 
    } 

    // Trust own CA and all self-signed certs 
    SSLContext sslcontext = SSLContexts.custom() 
     .loadKeyMaterial(keyStore, "password".toCharArray()) 
     //.loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()) 
     .build(); 
    // Allow TLSv1 protocol only 
    SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
     sslcontext, 
     new String[] { "TLSv1" }, 
     null, 
     SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); //TODO 
    CloseableHttpClient httpclient = HttpClients.custom() 
     .setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER) //TODO 
     .setSSLSocketFactory(sslsf) 
     .build(); 
    try { 

     HttpGet httpget = new HttpGet("https://localhost:8443/secure/index"); 

     System.out.println("executing request" + httpget.getRequestLine()); 

     CloseableHttpResponse response = httpclient.execute(httpget); 
     try { 
      HttpEntity entity = response.getEntity(); 

      System.out.println("----------------------------------------"); 
      System.out.println(response.getStatusLine()); 
      if (entity != null) { 
       System.out.println("Response content length: " + entity.getContentLength()); 
      } 
      EntityUtils.consume(entity); 
     } finally { 
      response.close(); 
     } 
    } finally { 
     httpclient.close(); 
    } 
} 

這段代碼是我沒有使用commons.I正在使用的http://hc.apache.org/httpcomponents-client-4.3.x/httpclient/examples/org/apache/http/examples/client/ClientCustomSSL.java

+0

我們如何在上述代碼中允許連接到沒有證書不安全的SSL站點? – 2016-01-07 12:17:19

+0

您使用的是哪個版本的HTTPClient? – 2016-01-07 12:52:03

+0

@ user4567570我認爲是4.3.5。要不安全地連接,你需要允許Hostname Verifier允許所有,但你還需要指定使用一種信任每個人的策略:'sslContextBuilder.loadTrustMaterial(new TrustAllStrategy());''和'TrustAllStrategy'實現'TrustStrategy',它只是'返回true;' – EpicPandaForce 2016-01-07 12:58:51

相關問題