2009-05-17 2 views
103

Я использую Java 6 и пытаюсь создать HttpsURLConnection на удаленном сервере с использованием сертификата клиента.
Сервер использует собственный корневой сертификат и требует наличия защищенного паролем сертификата клиента. Я добавил корневой сертификат сервера и сертификат клиента в хранилище ключей java по умолчанию, которое я нашел в /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/lib/security/cacerts (OSX 10.5). Имя файла хранилища ключей, похоже, предполагает, что сертификат клиента не должен туда заходить?Сертификаты клиента Java через HTTPS/SSL

Во всяком случае, добавив корневой сертификат в этот магазин решить пресловутый javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed' problem.

Однако, сейчас я застрял на том, как использовать сертификат клиента. Я пробовал два подхода и нигде не достал.
Во-первых, и предпочли, попробуйте:

SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); 
URL url = new URL("https://somehost.dk:3049"); 
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection(); 
conn.setSSLSocketFactory(sslsocketfactory); 
InputStream inputstream = conn.getInputStream(); 
// The last line fails, and gives: 
// javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure 

Я попытался пропустить класс HttpsURLConnection (не идеал, так как я хочу поговорить HTTP с сервером), и сделать это вместо:

SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); 
SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket("somehost.dk", 3049); 
InputStream inputstream = sslsocket.getInputStream(); 
// do anything with the inputstream results in: 
// java.net.SocketTimeoutException: Read timed out 

Я даже не уверен, что проблема с клиентом - вот проблема.

ответ

82

Наконец решить ее;). Получил сильный намек here (ответ Gandalfs немного коснулся его). Отсутствующие ссылки были (в основном) первым из приведенных ниже параметров, и в некоторой степени я упускал из виду разницу между хранилищами ключей и доверительными магазинами.

Свидетельство самозаверяющего сервера должен быть импортирован в доверенные сертификаты:

Keytool -import -alias gridserver -file gridserver.crt -storepass $ PASS -keystore gridserver.Хранилище ключей

Эти свойства должны быть установлены (либо в командной строке или в коде):

-Djavax.net.ssl.keyStoreType=pkcs12 
-Djavax.net.ssl.trustStoreType=jks 
-Djavax.net.ssl.keyStore=clientcertificate.p12 
-Djavax.net.ssl.trustStore=gridserver.keystore 
-Djavax.net.debug=ssl # very verbose debug 
-Djavax.net.ssl.keyStorePassword=$PASS 
-Djavax.net.ssl.trustStorePassword=$PASS 

Рабочий пример кода:

SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); 
URL url = new URL("https://gridserver:3049/cgi-bin/ls.py"); 
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection(); 
conn.setSSLSocketFactory(sslsocketfactory); 
InputStream inputstream = conn.getInputStream(); 
InputStreamReader inputstreamreader = new InputStreamReader(inputstream); 
BufferedReader bufferedreader = new BufferedReader(inputstreamreader); 

String string = null; 
while ((string = bufferedreader.readLine()) != null) { 
    System.out.println("Received " + string); 
} 
+0

У меня есть URL-адрес, например: https: // localhost: 8443/Application_Name/getAttributes. У меня есть метод с/getAttribute url mapping. Этот метод возвращает список элементов. Я использовал HttpsUrlConnection, код ответа на соединение - 200, но он не дает мне список атрибутов, когда я использую inputStream, он дает мне html-содержимое моей страницы входа. Я выполнил проверку подлинности и установил тип содержимого как JSON. Пожалуйста, предложите – Deepak

4

Я использую пакет клиентских HTTP-пакетов Apache для этого в моем текущем проекте, и он отлично работает с SSL и самозаверяющим сертификатом (после установки его в cacerts, как вы упомянули). Пожалуйста, обратите внимание на это здесь:

http://hc.apache.org/httpclient-3.x/tutorial.html

http://hc.apache.org/httpclient-3.x/sslguide.html

+1

Этот кажется довольно опрятным пакетом, но класс, который должен заставить все работать «AuthSSLProtocolSocketFactory», не является частью официального дистрибутива, ни в 4.0beta (несмотря на примечания к выпуску, заявляющие, что это так), либо в 3.1. Я немного взломал его и теперь, похоже, постоянно застрял в 5-минутной зависании, прежде чем он просто отключит соединение. Это действительно странно - если я загружаю CA и клиентский сертификат в любой браузер, он просто летает. – Jan

+1

Apache HTTP Client 4 может принимать 'SSLContext' напрямую, поэтому вы можете настроить все это таким образом, вместо использования' AuthSSLProtocolSocketFactory'. – Bruno

18

ли ты к хранилищу и/или свойства системы доверенных?

java -Djavax.net.ssl.keyStore=pathToKeystore -Djavax.net.ssl.keyStorePassword=123456 

или с кодом

System.setProperty("javax.net.ssl.keyStore", pathToKeyStore); 

То же с javax.net.ssl.trustStore

3

Я думаю, что у вас есть проблемы с сертификатом сервера, не действительный сертификат (I подумайте, что в этом случае означает «handshake_failure»):

Импортируйте сертификат своего сервера в хранилище ключей trustcacerts на JRE клиента. Это легко сделать с keytool:

keytool 
    -import 
    -alias <provide_an_alias> 
    -file <certificate_file> 
    -keystore <your_path_to_jre>/lib/security/cacerts 
+0

Я пробовал очистить и начать, и ошибка рукопожатия ушла. Теперь я просто получаю 5 минут мертвой тишины до того, как соединение будет прекращено: o – Jan

78

Хотя не рекомендуется, вы можете также отключить SSL Cert проверку ALLtogether:

import javax.net.ssl.*; 
import java.security.SecureRandom; 
import java.security.cert.X509Certificate; 

public class SSLTool { 

    public static void disableCertificateValidation() { 
    // Create a trust manager that does not validate certificate chains 
    TrustManager[] trustAllCerts = new TrustManager[] { 
     new X509TrustManager() { 
     public X509Certificate[] getAcceptedIssuers() { 
      return new X509Certificate[0]; 
     } 
     public void checkClientTrusted(X509Certificate[] certs, String authType) {} 
     public void checkServerTrusted(X509Certificate[] certs, String authType) {} 
    }}; 

    // Ignore differences between given hostname and certificate hostname 
    HostnameVerifier hv = new HostnameVerifier() { 
     public boolean verify(String hostname, SSLSession session) { return true; } 
    }; 

    // Install the all-trusting trust manager 
    try { 
     SSLContext sc = SSLContext.getInstance("SSL"); 
     sc.init(null, trustAllCerts, new SecureRandom()); 
     HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); 
     HttpsURLConnection.setDefaultHostnameVerifier(hv); 
    } catch (Exception e) {} 
    } 
} 
+0

Вы можете сделать то же самое еще более простым способом, если используете структуру Axis. См. Мой ответ ниже. –

+62

Следует отметить, что отключение проверки сертификата подобно этому открывает соединение с возможными атаками MITM: ** не использовать в производстве **. – Bruno

+1

Код не компилируется, к счастью. Это «решение» радикально небезопасно. – EJP

14

Если вы имеете дело с веб-службой вызов с использованием рамки Axis, есть гораздо более простой ответ. Если все хотим, чтобы ваш клиент, чтобы иметь возможность вызова веб-службы SSL и игнорировать ошибки сертификата SSL, просто поставить это заявление, прежде чем ссылаться на какие-либо веб-служб:

System.setProperty("axis.socketSecureFactory", "org.apache.axis.components.net.SunFakeTrustSocketFactory");

Обычные отказы по этому поводу быть очень Плохая вещь, которую нужно делать в производственной среде.

Я нашел это на the Axis wiki.

+0

ОП имеет дело с HttpsURLConnection, а не Axis – neu242

+2

Я понимаю. Я не собирался предполагать, что мой ответ был лучше в общем случае. Просто, если вы * используете * систему Axis, у вас может быть вопрос OP в этом контексте. (Именно так я нашел этот вопрос в первую очередь.) В этом случае способ, который я предоставил, проще. –

+1

@MarkMeuer Спасибо! Работала отлично. –

1

Использование ниже кода

-Djavax.net.ssl.keyStoreType=pkcs12 

или

System.setProperty("javax.net.ssl.keyStore", pathToKeyStore); 

вовсе не требуется. Также нет необходимости создавать собственную собственную фабрику SSL.

Я также столкнулся с той же проблемой, в моем случае возникла проблема, заключавшаяся в том, что полная цепочка сертификатов не была импортирована в доверительные компании. Импортируйте сертификаты с помощью корневого сертификата утилиты keytool, а также вы можете открыть файл cacerts в блокноте и посмотреть, импортирована ли целая цепочка сертификатов. Проверьте на имя псевдонима, которое вы указали при импорте сертификатов, откройте сертификаты и посмотрите, сколько их содержится, то же количество сертификатов должно быть в файле cacerts.

Также файл cacerts должен быть настроен на сервере, на котором выполняется ваше приложение, два сервера будут аутентифицировать друг друга с помощью общедоступных/закрытых ключей.

+0

Создание собственной собственной фабрики SSL намного сложнее и подвержено ошибкам, чем установка двух свойств системы. – EJP

5

Для меня, это то, что работает с использованием Apache HttpComponents ~ HttpClient 4.x:

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

    // Trust own CA and all self-signed certs 
    SSLContext sslcontext = SSLContexts.custom() 
     .loadKeyMaterial(keyStore, "helloworld".toCharArray()) 
     //.loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()) //custom trust store 
     .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(); 
    } 

Файл P12 содержит сертификат клиента и клиента закрытый ключ, созданный с BouncyCastle:

public static byte[] convertPEMToPKCS12(final String keyFile, final String cerFile, 
    final String password) 
    throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException, 
    NoSuchProviderException 
{ 
    // Get the private key 
    FileReader reader = new FileReader(keyFile); 

    PEMParser pem = new PEMParser(reader); 
    PEMKeyPair pemKeyPair = ((PEMKeyPair)pem.readObject()); 
    JcaPEMKeyConverter jcaPEMKeyConverter = new JcaPEMKeyConverter().setProvider("BC"); 
    KeyPair keyPair = jcaPEMKeyConverter.getKeyPair(pemKeyPair); 

    PrivateKey key = keyPair.getPrivate(); 

    pem.close(); 
    reader.close(); 

    // Get the certificate 
    reader = new FileReader(cerFile); 
    pem = new PEMParser(reader); 

    X509CertificateHolder certHolder = (X509CertificateHolder) pem.readObject(); 
    java.security.cert.Certificate x509Certificate = 
     new JcaX509CertificateConverter().setProvider("BC") 
      .getCertificate(certHolder); 

    pem.close(); 
    reader.close(); 

    // Put them into a PKCS12 keystore and write it to a byte[] 
    ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
    KeyStore ks = KeyStore.getInstance("PKCS12", "BC"); 
    ks.load(null); 
    ks.setKeyEntry("key-alias", (Key) key, password.toCharArray(), 
     new java.security.cert.Certificate[]{x509Certificate}); 
    ks.store(bos, password.toCharArray()); 
    bos.close(); 
    return bos.toByteArray(); 
} 
+0

«keyStore» - это то, что содержит закрытый ключ и сертификат. – EpicPandaForce

+1

Вам необходимо включить эти 2 зависимости для кода convertPEMtoP12: org.BouncyCastle bcprov-jdk15on 1,53 org.bouncycastle bcpkix-jdk15on 1,53 BirdOfPrey

+0

@EpicPandaForce Я получаю сообщение об ошибке: Caught: org.codehaus.groovy. runtime.typehandling.GroovyCastException: невозможно создать объект с классом 'org.bouncycastle.jcajce.provider.asymmetric.x509.X509CertificateObject' to class 'in t 'в строке ks.setKeyEntry - любые подсказки, что может быть неправильным –