如何使用Apache HttpClient处理无效的SSL证书?

我知道,这个问题有很多不同的问题和答案,但我不明白…

我有:ubuntu-9.10-desktop-amd64 + NetBeans6.7.1从“closures”安装。 代表。 我需要通过HTTPS连接到某个站点。 为此,我使用Apache的HttpClient。

从教程我读:

“一旦你正确安装了JSSE,通过SSL的安全HTTP通信应该是
简单的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 

我要做什么来创build最简单的SSL连接? (可能没有KeyManager和信任pipe理器等)

https://mms.nw.ru使用自签名证书,显然不包含在默认的信任pipe理器集合中。

您需要以下其中一项:

  • 使用接受任何证书的TrustManagerconfigurationSSLContext(请参见下文)

  • 使用包含您的证书的适当的信任库来configurationSSLContext

  • 将该站点的证书添加到默认的java信任存储区。

下面是一个示例程序,它创build一个接受任何证书的(主要是不值钱的)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密钥存储中,如无法find请求的目标的有效证书path所述 :

在使用以https协议运行的启用了SSL的服务器的客户端上工作时,如果服务器证书不是由证书颁发机构颁发的,而是由自签名的或发布的,则可能会出现错误“找不到有效的证书path到请求的目标”一个私人CMS。

不要惊慌。 如果您的客户端是用Java编写的,那么您只需将服务器证书添加到可信的Java密钥库中。 您可能想知道如何不能访问安装服务器的机器。 有一个简单的程序可以帮助你。 请下载Java程序并运行

 % java InstallCert _web_site_hostname_ 

这个程序打开了一个到指定主机的连接,并开始了SSL握手。 它打印出现错误的exception堆栈跟踪,并显示服务器使用的证书。 现在它会提示您将证书添加到您的可信KeyStore。

如果您改变了主意,请input“q”。 如果您确实想要添加证书,请input“1”或其他数字以添加其他证书,即使是CA证书,但您通常不希望这样做。 一旦做出select,程序将显示完整的证书,然后将其添加到当前目录中名为“jssecacerts”的Java KeyStore中。

要在程序中使用它,可以configurationJSSE将其用作其信任存储或将其复制到$ JAVA_HOME / jre / lib / security目录中。 如果您希望所有Java应用程序都将该证书识别为可信,而不仅仅是JSSE,则还可以覆盖该目录中的cacerts文件。

毕竟,JSSE将能够与主机完成握手,您可以通过再次运行程序来validation。

要了解更多的细节,你可以查看Leeland的博客没有更多的“无法find有效的authenticationpath到要求的目标”

除了Pascal Thivent的正确答案之外,另一种方法是从Firefox(查看证书 – >详细信息 – >导出)保存证书或openssl s_client并将其导入到信任库中。

如果您有办法validation该证书,则只能这样做。 如果没有这样做,首次连接时,如果证书在后续连接上意外更改,至less会出现错误。

要将其导入到信任库中,请使用:

 keytool -importcert -keystore truststore.jks -file servercert.pem 

默认情况下,缺省信任库应该是lib/security/cacerts ,其密码应该是changeit ,请参阅JSSE参考指南 。

如果您不想全局允许该证书,但仅限于这些连接,则可以为其创build一个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来使用此SSLContext 。 (这里有例子)。

Apache HTTP客户端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(); } 

在这里可以findMySSLSocketFactory的例子。 它引用一个TrustManager ,你可以修改它以信任一切(尽pipe你必须考虑这一点!)

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缺less后者的包名)。

一旦你有一个Java Cert Store(通过使用上面创build的伟大的 InstallCert类),你可以通过在java启动时传递“javax.net.ssl.trustStore”参数来使用它。

例如:

 java -Djavax.net.ssl.trustStore=/path/to/jssecacerts MyClassName 

您可能遇到的另一个问题是使用自签名testing证书:

java.io.IOException:HTTPS主机名错误:应该是…

当您尝试访问HTTPSurl时,会发生此错误。 您可能已经将服务器证书安装到您的JRE的密钥库。 但是,此错误意味着服务器证书的名称与URL中提到的服务器的实际域名不匹配。 这通常发生在您使用非CA颁发的证书时。

此示例显示如何编写忽略证书服务器名称的HttpsURLConnection DefaultHostnameVerifier:

http://www.java-samples.com/showtutorial.php?tutorialid=211

对于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。 为了解决这个问题,应该在connectionManager中注册定制的SSLConnectionSocketFactory。 正确的代码应该是这样的:

 PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(RegistryBuilder. <ConnectionSocketFactory>create() .register("http",PlainConnectionSocketFactory.getSocketFactory()) .register("https", sslConnectionSocketFactory).build()); CloseableHttpClient client = HttpClients.custom() .setConnectionManager(connectionManager) .build(); 

为了轻松添加您在运行时信任的主机,而不必抛出所有检查,请尝试以下代码: 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文件,并执行-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<String> 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; } 

}