Использование Apache httpclient для https

Я включил https в tomcat и имею самозаверяющий сертификат для аутентификации сервера. Я создал http-клиент, используя Apache httpClient. Я установил диспетчер доверия, загружающий сертификат сервера. HTTP-клиент может без проблем подключиться к серверу. Чтобы увидеть, что происходит, я включил отладку:

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 по умолчанию! Мой вопрос: почему это происходит?

В своем коде я явно указываю конкретное хранилище доверенных сертификатов для использования (через менеджеров доверенных хранилищ). Я ожидал, что только это будет использоваться. Кажется, что по умолчанию используется как мой доверенный магазин, так и java. Это так должно работать?

ОБНОВЛЕНИЕ:
я попробовал следующее:

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  

ОБНОВЛЕНИЕ 2: Как вы видите в приведенном ниже коде, я указываю свое хранилище ключей. Я ожидаю, что следует использовать только это (а не этот и 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);

Не имеет смысла для меня.


person Cratylus    schedule 05.03.2011    source источник


Ответы (4)


Я собрал это тестовое приложение, чтобы воспроизвести проблему, используя среду тестирования 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)
...
person ok2c    schedule 08.03.2011
comment
Привет, Олег, у меня похожая проблема, но только когда я отправляю запрос POST, а не через GET. Любое понимание? - person Abhishek; 09.05.2011
comment
Итак, есть ли где-нибудь зарегистрированная ошибка, которую мы можем отследить для решения? - person pulkitsinghal; 08.01.2013
comment
Я обнаружил, что у моей JVM нет jar-файлов JCE, необходимых для расшифровки 4096-битного ключа, возможно, это та же проблема с 2048-битным ключом, которую вы тестируете? - person pulkitsinghal; 13.02.2013
comment
Как мы можем разрешить подключения (небезопасные) к сайтам SSL без сертификатов с использованием HTTP-клиента? - person ; 07.01.2016

Когда я использовал Apache HTTP Client 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 в качестве аргумента виртуальной машины. Я остановил свой код в отладчике, и когда любой из вышеперечисленных *ClientConnectionManager был создан, файл cacerts загружался.

person Pedro B. Morales    schedule 06.06.2014
comment
Спасибо вам большое за это. Я не мог на всю жизнь понять, в чем проблема, а затем, прочитав это, заметил, что строитель в основном игнорирует предоставленный вами sslContext, если вы передали диспетчер соединений. - person Ben Green; 15.04.2015
comment
Этот ответ заслуживает большей любви. У меня есть задача для SSL и conn pooling. Я могу поклясться, что успешно провел свои ручные тесты и интеграционные тесты для SSL прямо на моих глазах. Затем я сделал некоторую очистку кода и небрежно вставил диспетчер соединений пула. Вы можете себе представить мое горе, когда все начало рушиться! Мое исправление заключается в том, что вместо того, чтобы устанавливать значения пула в качестве диспетчера соединений, я могу фактически установить его непосредственно на httpClient без создания экземпляра и настройки через setConnectionManager. - person villager; 22.04.2020

Вот что сработало для меня:

    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();
    }
}

Этот код является модифицированной версией http://hc.apache.org/httpcomponents-client-4.3.x/httpclient/examples/org/apache/http/examples/client/ClientCustomSSL.java

person EpicPandaForce    schedule 06.11.2014
comment
Как мы можем разрешить подключение к сайтам SSL без сертификатов небезопасным способом в приведенном выше коде? - person ; 07.01.2016
comment
Какую версию HTTPClient вы использовали? - person ; 07.01.2016
comment
@user4567570 user4567570 Думаю, это было 4.3.5. Чтобы подключиться небезопасно, вам нужно разрешить Hostname Verifier разрешать все, но вам также нужно указать использовать стратегию, которая доверяет всем: sslContextBuilder.loadTrustMaterial(new TrustAllStrategy()); и TrustAllStrategy реализуют TrustStrategy, и все, что он делает, это return true; - person EpicPandaForce; 07.01.2016
comment
Эй, не могли бы вы рассказать об этом stackoverflow.com/questions/34655031/? - person ; 07.01.2016
comment
как получить хранилище ключей и пароль? - person Narendra Singh; 17.11.2016
comment
KeyStore keyStore = KeyStore.getInstance("PKCS12", BouncyCastleProvider.PROVIDER_NAME); byteArrayInputStream = new ByteArrayInputStream(keyStoreBytes); keyStore.load(byteArrayInputStream, keyStorePassword); я сгенерировал эту штуку с помощью bouncycastle - person EpicPandaForce; 17.11.2016

Согласно 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);
person extraneon    schedule 05.03.2011
comment
@ user384706 Я немного запутался. Проблема в том, что client использует 2 TrustManager (что, по словам SSLContext.init, не должно) или это сервер tomcat, который ведет себя странно? - person extraneon; 06.03.2011
comment
В Tomcat нет проблем. Tomcat отправляет сертификат, который я настроил. Проблема в клиентской части. Я указал доверенное хранилище доверия и ожидал, что будет использоваться только это. Информация об отладке показывает, что используется мое хранилище, а также хранилище доверия Java по умолчанию. Я обновил свой пост, чтобы показать код в клиентской части. - person Cratylus; 06.03.2011
comment
@Cratylus, я столкнулся с той же проблемой при тестировании httpclient 4.0 с самоподписанными сертификатами. Можете ли вы опубликовать код, как вы его заработали? - person Deepesh M; 01.03.2013
comment
@DeepeshM: у меня не было никаких рабочих проблем. Мой пост был связан с тем, почему были напечатаны сертификаты, часть cacerts. Меня беспокоило то, что и cacerts и мое хранилище доверенных сертификатов было использовано - person Cratylus; 02.03.2013