Сертификаты клиента Java через HTTPS / SSL
Я использую Java 6 и пытаюсь создать HttpsURLConnection
против удаленного сервера, используя сертификат клиента.
Сервер использует самоподписанный корневой сертификат и требует, чтобы был представлен защищенный паролем сертификат клиента. Я добавил корневой сертификат сервера и сертификат клиента в хранилище ключей java по умолчанию, которое я нашел в /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/lib/security/cacerts
(OSX 10.5).
Имя файла хранилища ключей, по-видимому, предполагает, что сертификат клиента не должен туда входить?
Так или иначе, добавление корневого сертификата в это хранилище решило печально известную проблему 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
Я даже не уверен, что сертификат клиента является здесь проблемой.
8 ответов:
Наконец-то решил ее ;). Получил сильный намек здесь (Ответ Гандальфа коснулся немного и его). Недостающие ссылки были (в основном) первым из приведенных ниже параметров, и в какой-то степени я упустил из виду разницу между хранилищами ключей и хранилищами доверия.
Самозаверяющий сертификат сервера должен быть импортирован в хранилище доверия:
Keytool-import-alias gridserver-файл gridserver.crt-storepass $PASS-keystore gridserver.keystore
Эти свойства должны быть установлены (либо в командной строке, либо в коде):
-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); }
Хотя это не рекомендуется, вы также можете отключить проверку 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) {} } }
Установили ли вы системные свойства KeyStore и/или TrustStore?
java -Djavax.net.ssl.keyStore=pathToKeystore -Djavax.net.ssl.keyStorePassword=123456
Или от с кодом
System.setProperty("javax.net.ssl.keyStore", pathToKeyStore);
Же с javax.чистая.протокол SSL.хранилище
Если вы имеете дело с вызовом веб-службы с использованием платформы Axis, существует гораздо более простой ответ. Если все, что нужно, чтобы ваш клиент мог вызывать веб-службу SSL и игнорировать ошибки SSL-сертификата, просто поместите это заявление перед вызовом любых веб-служб:
System.setProperty("axis.socketSecureFactory", "org.apache.axis.components.net.SunFakeTrustSocketFactory");
Применяются обычные оговорки о том, что это очень плохо в производственной среде.
Я нашел это в Axis wiki.
Для меня это то, что работало с использованием Apache Httpcomments ~ 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(); }
Я использую пакет HTTP-клиента Apache commons Для этого в моем текущем проекте, и он прекрасно работает с SSL и самозаверяющим сертификатом (после установки его в cacerts, как вы упомянули). Пожалуйста, взгляните на это здесь:
Я думаю, что у вас есть проблема с вашим сертификатом сервера, не является действительным сертификатом (я думаю, что это то, что "handshake_failure" означает в этом случае):
Импортируйте сертификат сервера в хранилище ключей trustcacerts на JRE клиента. Это легко сделать с помощью keytool:
keytool -import -alias <provide_an_alias> -file <certificate_file> -keystore <your_path_to_jre>/lib/security/cacerts
Используя код ниже
-Djavax.net.ssl.keyStoreType=pkcs12
Или
System.setProperty("javax.net.ssl.keyStore", pathToKeyStore);
Совсем не требуется. Также нет необходимости создавать собственную фабрику SSL.
Я также столкнулся с той же проблемой, в моем случае была проблема, что полная цепочка сертификатов не была импортирована в truststores. Импорт сертификатов с помощью утилиты keytool right FOM root certificate, также вы можете открыть файл cacerts в блокноте и посмотреть, импортирована ли полная цепочка сертификатов или нет. Сверьте с псевдонимом имя, которое вы указали при импорте сертификатов, откройте сертификаты и посмотрите, сколько их содержит, такое же количество сертификатов должно быть в файле cacerts.
Также файл cacerts должен быть настроен на сервере, на котором вы запускаете свое приложение, два сервера будут аутентифицировать друг друга с помощью открытых/закрытых ключей.