Как обрабатывать недопустимые SSL-сертификаты с помощью Apache HttpClient?
Я знаю, есть много разных вопросов и так много ответов об этой проблеме … Но я не могу понять …
У меня есть: ubuntu-9.10-desktop-amd64 + NetBeans6.7.1 установлен «как есть» от off. по донесению Мне нужно подключиться к некоторому сайту через HTTPS. Для этого я использую Apache HttpClient.
Из учебника я прочитал:
- HTTPS с NSURLConnection - NSURLErrorServerCertificateUntrusted
- Как программно установить SSLContext клиента JAX-WS?
- Как решить проблему с подтверждением сертификата в Windows?
- Тип Keystore: какой из них использовать?
- Сколько накладных расходов накладывает SSL?
«После правильной установки JSSE безопасная HTTP-связь через SSL должна быть такой же, как и
простое как обычное HTTP-сообщение ». И еще один пример:
HttpClient httpclient = new HttpClient(); GetMethod httpget = new GetMethod("https://www.verisign.com/"); try { httpclient.executeMethod(httpget); System.out.println(httpget.getStatusLine()); } finally { httpget.releaseConnection(); }
К настоящему моменту я пишу это:
HttpClient client = new HttpClient(); HttpMethod get = new GetMethod("https://mms.nw.ru"); //get.setDoAuthentication(true); try { int status = client.executeMethod(get); System.out.println(status); BufferedInputStream is = new BufferedInputStream(get.getResponseBodyAsStream()); int r=0;byte[] buf = new byte[10]; while((r = is.read(buf)) > 0) { System.out.write(buf,0,r); } } catch(Exception ex) { ex.printStackTrace(); }
В результате у меня есть набор ошибок:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target at sun.security.ssl.Alerts.getSSLException(Alerts.java:192) at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1627) at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:204) at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:198) at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:994) at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:142) at sun.security.ssl.Handshaker.processLoop(Handshaker.java:533) at sun.security.ssl.Handshaker.process_record(Handshaker.java:471) at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:904) at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1132) at sun.security.ssl.SSLSocketImpl.writeRecord(SSLSocketImpl.java:643) at sun.security.ssl.AppOutputStream.write(AppOutputStream.java:78) at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82) at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140) at org.apache.commons.httpclient.HttpConnection.flushRequestOutputStream(HttpConnection.java:828) at org.apache.commons.httpclient.HttpMethodBase.writeRequest(HttpMethodBase.java:2116) at org.apache.commons.httpclient.HttpMethodBase.execute(HttpMethodBase.java:1096) at org.apache.commons.httpclient.HttpMethodDirector.executeWithRetry(HttpMethodDirector.java:398) at org.apache.commons.httpclient.HttpMethodDirector.executeMethod(HttpMethodDirector.java:171) at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:397) at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:323) at simpleapachehttp.Main.main(Main.java:41) Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:302) at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:205) at sun.security.validator.Validator.validate(Validator.java:235) at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:147) at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:230) at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:270) at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:973) ... 17 more Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:191) at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:255) at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:297) ... 23 more
Что мне делать, чтобы создать простейшее SSL-соединение? (Возможно, без KeyManager и Trust Manager и т. Д.).
- Как вы подписываете запрос на подпись сертификата в своем сертификационном центре?
- Какие Cipher Suites включить для SSL Socket?
- Проблемы с использованием Maven и SSL за прокси-сервером
- Запретить Google Chrome «Ваше подключение не является частным» для конкретного адреса?
- Определение данных сертификата TLS с помощью Microsoft Edge
- Force SSL / HTTPS с mod_rewrite
- Доверяйте всем сертификатам okHttp
- curl: (60) SSL-сертификат: не удалось получить сертификат локального эмитента
https://mms.nw.ru использует самозаверяющий сертификат, который, очевидно, не содержится в наборе доверительных менеджеров по умолчанию.
Вам необходимо выполнить одно из следующих действий:
-
Настройте SSLContext с помощью TrustManager, который принимает любой сертификат (см. Ниже)
-
Настройте SSLContext с соответствующим хранилищем доверия, которое включает ваш сертификат
-
Добавьте сертификат для этого сайта в хранилище доверия java по умолчанию.
Вот пример программы, которая создает (в основном бесполезный) SSL-контекст, который принимает любой сертификат:
import java.net.URL; import java.security.SecureRandom; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; public class SSLTest { public static void main(String [] args) throws Exception { // configure the SSLContext with a TrustManager SSLContext ctx = SSLContext.getInstance("TLS"); ctx.init(new KeyManager[0], new TrustManager[] {new DefaultTrustManager()}, new SecureRandom()); SSLContext.setDefault(ctx); URL url = new URL("https://mms.nw.ru"); HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); conn.setHostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String arg0, SSLSession arg1) { return true; } }); System.out.println(conn.getResponseCode()); conn.disconnect(); } private static class DefaultTrustManager implements X509TrustManager { @Override public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {} @Override public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {} @Override public X509Certificate[] getAcceptedIssuers() { return null; } } }
Вероятно, https://mms.nw.ru использует сертификат, не выданный сертификационным центром. Следовательно, вам нужно добавить сертификат в хранилище доверенных Java-ключей, как описано в разделе « Не удалось найти допустимый путь сертификации для запрошенной цели :
При работе с клиентом, который работает с сервером с поддержкой SSL, который работает в протоколе https, вы можете получить сообщение об ошибке «не удалось найти допустимый путь сертификации к запрашиваемой цели», если сертификат сервера не выдается сертификационным центром, а сам подписан или выдан частной CMS.
Не паникуйте. Все, что вам нужно сделать, это добавить сертификат сервера в хранилище доверенных Java-ключей, если ваш клиент написан на Java. Возможно, вам интересно, как будто вы не можете получить доступ к машине, на которой установлен сервер. Простая программа может вам помочь. Загрузите программу Java и запустите
% java InstallCert _web_site_hostname_
Эта программа открыла соединение с указанным хостом и начала квитирование SSL. Он напечатал трассировку стека исключений, которая произошла и показывает вам сертификаты, используемые сервером. Теперь он предлагает вам добавить сертификат в свой доверенный KeyStore.
Если вы передумали, введите «q». Если вы действительно хотите добавить сертификат, введите «1» или другие номера, чтобы добавить другие сертификаты, даже сертификат CA, но обычно вы этого не хотите. После того, как вы сделаете свой выбор, программа отобразит полный сертификат, а затем добавит его в Java KeyStore с именем «jssecacerts» в текущем каталоге.
Чтобы использовать его в своей программе, настройте JSSE, чтобы использовать его в качестве хранилища доверия или скопировать его в каталог $ JAVA_HOME / jre / lib / security. Если вы хотите, чтобы все приложения Java распознавали сертификат как доверенный, а не только JSSE, вы также можете перезаписать файл cacerts в этом каталоге.
После этого JSSE сможет выполнить рукопожатие с хостом, которое вы можете проверить, запустив программу снова.
Чтобы получить более подробную информацию, вы можете проверить блог Leeland. « Больше не удается найти правильный путь сертификации для запрошенной цели».
В дополнение к правильному ответу Паскаля Тивента другим способом является сохранение сертификата из Firefox (Просмотреть сертификат -> Подробности -> экспорт) или openssl s_client
и импортировать его в хранилище доверия.
Вы должны сделать это только в том случае, если у вас есть способ проверить этот сертификат. Если это произойдет, сделайте это при первом подключении, это, по крайней мере, даст вам ошибку, если сертификат неожиданно изменится при последующих подключениях.
Чтобы импортировать его в хранилище доверия, используйте:
keytool -importcert -keystore truststore.jks -file servercert.pem
По умолчанию хранилище доверия по умолчанию должно быть lib/security/cacerts
и его пароль должен быть changeit
, см. Справочное руководство JSSE для получения дополнительной информации.
Если вы не хотите разрешать этот сертификат глобально, но только для этих подключений, для него можно создать SSLContext
:
TrustManagerFactory tmf = TrustManagerFactory .getInstance(TrustManagerFactory.getDefaultAlgorithm()); KeyStore ks = KeyStore.getInstance("JKS"); FileInputStream fis = new FileInputStream("/.../truststore.jks"); ks.load(fis, null); // or ks.load(fis, "thepassword".toCharArray()); fis.close(); tmf.init(ks); SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, tmf.getTrustManagers(), null);
Затем вам нужно настроить его для Apache HTTP Client 3.x, SecureProtocolSocketFactory
его, если его SecureProtocolSocketFactory
использовать этот SSLContext
. (Здесь есть примеры).
Apache HTTP Client 4.x (помимо самой ранней версии) имеет прямую поддержку для передачи SSLContext
.
С http://hc.apache.org/httpclient-3.x/sslguide.html :
Protocol.registerProtocol("https", new Protocol("https", new MySSLSocketFactory(), 443)); HttpClient httpclient = new HttpClient(); GetMethod httpget = new GetMethod("https://www.whatever.com/"); try { httpclient.executeMethod(httpget); System.out.println(httpget.getStatusLine()); } finally { httpget.releaseConnection(); }
Где можно найти пример MySSLSocketFactory. Он ссылается на TrustManager
, который вы можете изменить, чтобы доверять всему (хотя вы должны это учитывать!)
Путь Apache HttpClient 4.5:
org.apache.http.ssl.SSLContextBuilder sslContextBuilder = SSLContextBuilder.create(); sslContextBuilder.loadTrustMaterial(new org.apache.http.conn.ssl.TrustSelfSignedStrategy()); SSLContext sslContext = sslContextBuilder.build(); org.apache.http.conn.ssl.SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, new org.apache.http.conn.ssl.DefaultHostnameVerifier()); HttpClientBuilder httpClientBuilder = HttpClients.custom().setSSLSocketFactory(sslSocketFactory); httpClient = httpClientBuilder.build();
ПРИМЕЧАНИЕ. org.apache.http.conn.ssl.SSLContextBuilder
устарел, и org.apache.http.ssl.SSLContextBuilder
является новым (уведомление об conn
отсутствует в имени пакета).
После того, как у вас есть Java Cert Store (используя отличный class InstallCert, созданный выше), вы можете получить java, чтобы использовать его, передав параметр javax.net.ssl.trustStore при запуске java.
Пример:
java -Djavax.net.ssl.trustStore=/path/to/jssecacerts MyClassName
Для Apache HttpClient 4.5+ и Java8:
SSLContext sslContext = SSLContexts.custom() .loadTrustMaterial((chain, authType) -> true).build(); SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, new String[] {"SSLv2Hello", "SSLv3", "TLSv1","TLSv1.1", "TLSv1.2" }, null, NoopHostnameVerifier.INSTANCE); CloseableHttpClient client = HttpClients.custom() .setSSLSocketFactory(sslConnectionSocketFactory) .build();
Но если ваш HttpClient использует ConnectionManager для поиска соединения, например, вот так:
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); CloseableHttpClient client = HttpClients.custom() .setConnectionManager(connectionManager) .build();
HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory)
имеет никакого эффекта , проблема не устранена.
Поскольку HttpClient использует указанный connectionManager для поиска соединения, и указанный connectionManager не зарегистрировал нашу настроенную SSLConnectionSocketFactory. Чтобы решить эту проблему, необходимо зарегистрировать настроенный SSLConnectionSocketFactory в connectionManager. Правильный код должен выглядеть так:
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(RegistryBuilder. create() .register("http",PlainConnectionSocketFactory.getSocketFactory()) .register("https", sslConnectionSocketFactory).build()); CloseableHttpClient client = HttpClients.custom() .setConnectionManager(connectionManager) .build();
Еще одна проблема, с которой вы можете столкнуться с самоподписанными тестовыми сертификатами, такова:
java.io.IOException: неправильное имя хоста HTTPS: должно быть …
Эта ошибка возникает, когда вы пытаетесь получить доступ к URL-адресу HTTPS. Возможно, вы уже установили сертификат сервера в хранилище ключей JRE. Но эта ошибка означает, что имя сертификата сервера не совпадает с фактическим доменным именем сервера, указанным в URL-адресе. Обычно это происходит, когда вы используете сертификат, выданный без CA.
В этом примере показано, как написать HttpsURLConnection DefaultHostnameVerifier, который игнорирует имя сервера сертификатов:
Чтобы легко добавить хосты, которым вы доверяете во время выполнения, не выбрасывая все проверки, попробуйте код здесь: http://code.google.com/p/self-signed-cert-trust-manager/ .
EasySSLProtocolSocketFactory давал мне проблемы, поэтому я закончил реализацию своего собственного протокола ProtocolSocketFactory.
Сначала вам необходимо зарегистрировать его:
Protocol.registerProtocol("https", new Protocol("https", new TrustAllSSLSocketFactory(), 443)); HttpClient client = new HttpClient(); ...
Затем выполните ProtocolSocketFactory:
class TrustAllSSLSocketFactory implements ProtocolSocketFactory { public static final TrustManager[] TRUST_ALL_CERTS = new TrustManager[]{ new X509TrustManager() { public void checkClientTrusted(final X509Certificate[] certs, final String authType) { } public void checkServerTrusted(final X509Certificate[] certs, final String authType) { } public X509Certificate[] getAcceptedIssuers() { return null; } } }; private TrustManager[] getTrustManager() { return TRUST_ALL_CERTS; } public Socket createSocket(final String host, final int port, final InetAddress clientHost, final int clientPort) throws IOException { return getSocketFactory().createSocket(host, port, clientHost, clientPort); } @Override public Socket createSocket(final String host, final int port, final InetAddress localAddress, final int localPort, final HttpConnectionParams params) throws IOException { return createSocket(host, port); } public Socket createSocket(final String host, final int port) throws IOException { return getSocketFactory().createSocket(host, port); } private SocketFactory getSocketFactory() throws UnknownHostException { TrustManager[] trustAllCerts = getTrustManager(); try { SSLContext context = SSLContext.getInstance("SSL"); context.init(null, trustAllCerts, new SecureRandom()); final SSLSocketFactory socketFactory = context.getSocketFactory(); HttpsURLConnection.setDefaultSSLSocketFactory(socketFactory); return socketFactory; } catch (NoSuchAlgorithmException | KeyManagementException exception) { throw new UnknownHostException(exception.getMessage()); } } }
Примечание. Это с HttpClient 3.1 и Java 8
Эта ссылка объясняет требование, которое вы выполняете шаг за шагом. Если вы действительно не обеспокоены тем, какой сертификат вы можете продолжить, выполните следующую ссылку.
Примечание. Возможно, вам стоит дважды проверить, что вы делаете, это небезопасная операция.
Используя InstallCert
для генерации файла jssecacerts
и do -Djavax.net.ssl.trustStore=/path/to/jssecacerts
поработали.
Я использую httpclient 3.1.X, и это работает для меня
try { SSLContext sslContext = SSLContext.getInstance("TLS"); TrustManager trustManager = new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { } @Override public X509Certificate[] getAcceptedIssuers() { return null; } }; sslContext.init(null, new TrustManager[]{trustManager}, null); SslContextSecureProtocolSocketFactory socketFactory = new SslContextSecureProtocolSocketFactory(sslContext,false); Protocol.registerProtocol("https", new Protocol("https", (ProtocolSocketFactory) socketFactory, 443));//同样会影响到HttpUtils } catch (Throwable e) { e.printStackTrace();
}
public class SslContextSecureProtocolSocketFactory implements SecureProtocolSocketFactory { private SSLContext sslContext; private boolean verifyHostname; public SslContextSecureProtocolSocketFactory(SSLContext sslContext, boolean verifyHostname) { this.verifyHostname = true; this.sslContext = sslContext; this.verifyHostname = verifyHostname; } public SslContextSecureProtocolSocketFactory(SSLContext sslContext) { this(sslContext, true); } public SslContextSecureProtocolSocketFactory(boolean verifyHostname) { this((SSLContext)null, verifyHostname); } public SslContextSecureProtocolSocketFactory() { this((SSLContext)null, true); } public synchronized void setHostnameVerification(boolean verifyHostname) { this.verifyHostname = verifyHostname; } public synchronized boolean getHostnameVerification() { return this.verifyHostname; } public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException, UnknownHostException { SSLSocketFactory sf = this.getSslSocketFactory(); SSLSocket sslSocket = (SSLSocket)sf.createSocket(host, port, clientHost, clientPort); this.verifyHostname(sslSocket); return sslSocket; } public Socket createSocket(String host, int port, InetAddress localAddress, int localPort, HttpConnectionParams params) throws IOException, UnknownHostException, ConnectTimeoutException { if(params == null) { throw new IllegalArgumentException("Parameters may not be null"); } else { int timeout = params.getConnectionTimeout(); Socket socket = null; SSLSocketFactory socketfactory = this.getSslSocketFactory(); if(timeout == 0) { socket = socketfactory.createSocket(host, port, localAddress, localPort); } else { socket = socketfactory.createSocket(); InetSocketAddress localaddr = new InetSocketAddress(localAddress, localPort); InetSocketAddress remoteaddr = new InetSocketAddress(host, port); socket.bind(localaddr); socket.connect(remoteaddr, timeout); } this.verifyHostname((SSLSocket)socket); return socket; } } public Socket createSocket(String host, int port) throws IOException, UnknownHostException { SSLSocketFactory sf = this.getSslSocketFactory(); SSLSocket sslSocket = (SSLSocket)sf.createSocket(host, port); this.verifyHostname(sslSocket); return sslSocket; } public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { SSLSocketFactory sf = this.getSslSocketFactory(); SSLSocket sslSocket = (SSLSocket)sf.createSocket(socket, host, port, autoClose); this.verifyHostname(sslSocket); return sslSocket; } private void verifyHostname(SSLSocket socket) throws SSLPeerUnverifiedException, UnknownHostException { synchronized(this) { if(!this.verifyHostname) { return; } } SSLSession session = socket.getSession(); String hostname = session.getPeerHost(); try { InetAddress.getByName(hostname); } catch (UnknownHostException var10) { throw new UnknownHostException("Could not resolve SSL sessions server hostname: " + hostname); } X509Certificate[] certs = (X509Certificate[])((X509Certificate[])session.getPeerCertificates()); if(certs != null && certs.length != 0) { X500Principal subjectDN = certs[0].getSubjectX500Principal(); List cns = this.getCNs(subjectDN); boolean foundHostName = false; Iterator i$ = cns.iterator(); AntPathMatcher matcher = new AntPathMatcher(); while(i$.hasNext()) { String cn = (String)i$.next(); if(matcher.match(cn.toLowerCase(),hostname.toLowerCase())) { foundHostName = true; break; } } if(!foundHostName) { throw new SSLPeerUnverifiedException("HTTPS hostname invalid: expected \'" + hostname + "\', received \'" + cns + "\'"); } } else { throw new SSLPeerUnverifiedException("No server certificates found!"); } } private List getCNs(X500Principal subjectDN) { ArrayList cns = new ArrayList(); StringTokenizer st = new StringTokenizer(subjectDN.getName(), ","); while(st.hasMoreTokens()) { String cnField = st.nextToken(); if(cnField.startsWith("CN=")) { cns.add(cnField.substring(3)); } } return cns; } protected SSLSocketFactory getSslSocketFactory() { SSLSocketFactory sslSocketFactory = null; synchronized(this) { if(this.sslContext != null) { sslSocketFactory = this.sslContext.getSocketFactory(); } } if(sslSocketFactory == null) { sslSocketFactory = (SSLSocketFactory)SSLSocketFactory.getDefault(); } return sslSocketFactory; } public synchronized void setSSLContext(SSLContext sslContext) { this.sslContext = sslContext; }
}
Для HttpClient мы можем сделать это:
SSLContext ctx = SSLContext.getInstance("TLS"); ctx.init(new KeyManager[0], new TrustManager[] {new DefaultTrustManager()}, new SecureRandom()); SSLContext.setDefault(ctx); String uri = new StringBuilder("url").toString(); HostnameVerifier hostnameVerifier = new HostnameVerifier() { @Override public boolean verify(String arg0, SSLSession arg1) { return true; } }; HttpClient client = HttpClientBuilder.create().setSSLContext(ctx) .setSSLHostnameVerifier(hostnameVerifier).build()