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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1828775/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 23:02:31  来源:igfitidea点击:

How to handle invalid SSL certificates with Apache HttpClient?

javasslhttpsapache-commons-httpclient

提问by rauch

I know, there are many different questions and so many answers about this problem... But I can't understand...

我知道,关于这个问题有很多不同的问题和很多答案......但我无法理解......

I have: ubuntu-9.10-desktop-amd64 + NetBeans6.7.1 installed "as is" from off. rep. I need connecting to some site over the HTTPS. For this I use Apache's HttpClient.

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

From tutorial I read:

从教程我读到:

"Once you have JSSE correctly installed, secure HTTP communication over SSL should be as
simple as plain HTTP communication." And some example:

“一旦您正确安装了 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();
}

By now, I write this:

到现在为止,我写这个:

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

As a result I have a set of errors:

结果我有一组错误:

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

What have I to do to create simplest SSL connection? (Probably without KeyManager and Trust manager etc. while.)

我该怎么做才能创建最简单的 SSL 连接?(可能没有 KeyManager 和 Trust manager 等。)

采纳答案by Kevin

https://mms.nw.ruuses a self-signed certificate which obviously isn't contained in the default set of trust managers.

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

You'll need to one of the following:

您需要执行以下操作之一:

  • Configure the SSLContext with a TrustManager that accepts any cert (see below)

  • Configure the SSLContext with an appropriate trust store that includes your cert

  • Add the cert for that site to the default java trust store.

  • 使用接受任何证书的 TrustManager 配置 SSLContext(见下文)

  • 使用包含您的证书的适当信任库配置 SSLContext

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

Here is a sample program that creates a (mostly worthless) SSL Context that accepts any cert:

这是一个示例程序,它创建一个(大部分毫无价值的)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;
        }
    }
}

回答by Pascal Thivent

https://mms.nw.rulikely uses a certificate not issued by a certification authority. Consequently, you need to add the certificate to your trusted Java key store as explained in unable to find valid certification path to requested target:

https://mms.nw.ru可能使用不是由证书颁发机构颁发的证书。因此,您需要将证书添加到受信任的 Java 密钥库,如无法找到到请求目标的有效证书路径中所述

When working on a client that works with an SSL enabled server running in https protocol, you could get error 'unable to find valid certification path to requested target' if the server certificate is not issued by certification authority, but a self signed or issued by a private CMS.

Don't panic. All you need to do is to add the server certificate to your trusted Java key store if your client is written in Java. You might be wondering how as if you can not access the machine where the server is installed. There is a simple program can help you. Please download the Java programand run

% java InstallCert _web_site_hostname_

This program opened a connection to the specified host and started an SSL handshake. It printed the exception stack trace of the error that occured and shows you the certificates used by the server. Now it prompts you add the certificate to your trusted KeyStore.

If you've changed your mind, enter 'q'. If you really want to add the certificate, enter '1', or other numbers to add other certificates, even a CA certificate, but you usually don't want to do that. Once you have made your choice, the program will display the complete certificate and then added it to a Java KeyStore named 'jssecacerts' in the current directory.

To use it in your program, either configure JSSE to use it as its trust store or copy it into your $JAVA_HOME/jre/lib/security directory. If you want all Java applications to recognize the certificate as trusted and not just JSSE, you could also overwrite the cacerts file in that directory.

After all that, JSSE will be able to complete a handshake with the host, which you can verify by running the program again.

To get more details, you can check out Leeland's blog No more 'unable to find valid certification path to requested target'

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

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

% java InstallCert _web_site_hostname_

该程序打开了到指定主机的连接并开始了 SSL 握手。它打印了发生错误的异常堆栈跟踪,并向您显示服务器使用的证书。现在它会提示您将证书添加到您信任的 KeyStore。

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

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

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

要获取更多详细信息,您可以查看 Leeland 的博客不再“无法找到所需目标的有效认证路径”

回答by Bozho

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

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

Where MySSLSocketFactory example can be found here. It references a TrustManager, which you can modify to trust everything (although you must consider this!)

这里可以找到 MySSLSocketFactory 示例。它引用 a TrustManager,您可以修改它以信任所有内容(尽管您必须考虑这一点!)

回答by Brian M. Carr

Once you have a Java Cert Store (by using the greatInstallCert class created above), you can get java to use it by passing the "javax.net.ssl.trustStore" param at java startup.

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

Ex:

前任:

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

回答by Kiwicmc

Another issue you may run into with self signed test certs is this:

使用自签名测试证书可能会遇到的另一个问题是:

java.io.IOException: HTTPS hostname wrong: should be ...

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

This error occurs when you are trying to access a HTTPS url. You might have already installed the server certificate to your JRE's keystore. But this error means that the name of the server certificate does not match with the actual domain name of the server that is mentioned in the URL. This normally happens when you are using a non CA issued certificate.

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

This example shows how to write a HttpsURLConnection DefaultHostnameVerifier that ignore the certificates server name:

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

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

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

回答by Dexin Wang

Using the InstallCertto generate the jssecacertsfile and do -Djavax.net.ssl.trustStore=/path/to/jssecacertsworked great.

使用InstallCert生成jssecacerts文件并且 -Djavax.net.ssl.trustStore=/path/to/jssecacerts效果很好。

回答by Matt Olsen

For a way to easily add hosts you trust at runtime without throwing out all checks, try the code here: http://code.google.com/p/self-signed-cert-trust-manager/.

要在运行时轻松添加您信任的主机而无需放弃所有检查,请尝试此处的代码:http: //code.google.com/p/self-signed-cert-trust-manager/

回答by Raghu Kiran

This linkexplains the requirement you have step by step. If You are not really concerned which certificate you can proceed with the process in below link.

链接逐步解释了您的要求。如果您真的不关心哪个证书,您可以继续下面链接中的过程。

Note You might want to double check what you are doing since, this is a unsafe operation.

注意您可能需要仔细检查您在做什么,因为这是一个不安全的操作。

回答by Bruno

In addition to Pascal Thivent's correct answer, another way is to save the certificate from Firefox (View Certificate -> Details -> export) or openssl s_clientand import it into the trust store.

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

You should only do this if you have a way to verify that certificate. Failing that, do it the first time you connect, it will at least give you an error if the certificate changes unexpectedly on subsequent connections.

仅当您有办法验证该证书时才应执行此操作。如果失败,请在第一次连接时执行此操作,如果证书在后续连接中意外更改,它至少会给您一个错误。

To import it in a trust store, use:

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

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

By default, the default trust store should be $JAVA_HOME/jre/lib/security/cacertsand its password should be changeit, see JSSE Reference guidefor details.

默认情况下,默认信任存储应为$JAVA_HOME/jre/lib/security/cacerts,其密码应为changeit,有关详细信息,请参阅JSSE 参考指南

If you don't want to allow that certificate globally, but only for these connections, it's possible to create an SSLContextfor it:

如果您不想全局允许该证书,而仅针对这些连接,则可以为其创建一个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);

Then, you need to set it up for Apache HTTP Client 3.x by implementing one if its SecureProtocolSocketFactoryto use this SSLContext. (There are examples here).

然后,您需要通过实现一个来为 Apache HTTP Client 3.x 设置它,如果它SecureProtocolSocketFactory使用 this SSLContext。(有例子在这里)。

Apache HTTP Client 4.x (apart from the earliest version) has direct support for passing an SSLContext.

Apache HTTP Client 4.x(除了最早的版本)直接支持传递SSLContext.

回答by Anton Krosnev

The Apache HttpClient 4.5 way:

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

NOTE: org.apache.http.conn.ssl.SSLContextBuilderis deprecatedand org.apache.http.ssl.SSLContextBuilderis the new one (notice connmissing from the latter's package name).

注意:org.apache.http.conn.ssl.SSLContextBuilder弃用并且org.apache.http.ssl.SSLContextBuilder是新的(注意conn后者的包名称中缺少通知)。