在 Java 客户端接受服务器的自签名 ssl 证书

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2893819/
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-13 14:10:51  来源:igfitidea点击:

Accept server's self-signed ssl certificate in Java client

javasslhttpskeytool

提问by Nikita Rybak

It looks like a standard question, but I couldn't find clear directions anywhere.

这看起来像是一个标准问题,但我在任何地方都找不到明确的方向。

I have java code trying to connect to a server with probably self-signed (or expired) certificate. The code reports the following error :

我有 java 代码试图连接到可能带有自签名(或过期)证书的服务器。该代码报告以下错误:

[HttpMethodDirector] I/O exception (javax.net.ssl.SSLHandshakeException) caught 
when processing request: sun.security.validator.ValidatorException: PKIX path 
building failed: sun.security.provider.certpath.SunCertPathBuilderException: 
unable to find valid certification path to requested target

As I understand it, I have to use keytooland tell java that it's OK to allow this connection.

据我了解,我必须使用keytool并告诉 java 可以允许此连接。

All instructions to fix this problem assume I'm fully proficient with keytool, such as

解决此问题的所有说明都假定我完全精通 keytool,例如

generate private key for server and import it into keystore

为服务器生成私钥并将其导入密钥库

Is there anybody who could post detailed instructions?

有大佬可以发详细的教程吗?

I'm running unix, so bash script would be best.

我正在运行 unix,所以最好使用 bash 脚本。

Not sure if it's important, but code executed in jboss.

不确定它是否重要,但是在 jboss 中执行的代码。

采纳答案by Pascal Thivent

You have basically two options here: add the self-signed certificate to your JVM truststore or configure your client to

您在这里基本上有两个选择:将自签名证书添加到您的 JVM 信任库或将您的客户端配置为

Option 1

选项1

Export the certificate from your browser and import it in your JVM truststore (to establish a chain of trust):

从浏览器导出证书并将其导入 JVM 信任库(以建立信任链):

<JAVA_HOME>\bin\keytool -import -v -trustcacerts
-alias server-alias -file server.cer
-keystore cacerts.jks -keypass changeit
-storepass changeit 

Option 2

选项 2

Disable Certificate Validation:

禁用证书验证:

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

// Install the all-trusting trust manager
try {
    SSLContext sc = SSLContext.getInstance("SSL"); 
    sc.init(null, trustAllCerts, new java.security.SecureRandom()); 
    HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (GeneralSecurityException e) {
} 
// Now you can access an https URL without having the certificate in the truststore
try { 
    URL url = new URL("https://hostname/index.html"); 
} catch (MalformedURLException e) {
} 

Note that I do not recommend the Option #2 at all. Disabling the trust manager defeats some parts of SSL and makes you vulnerable to man in the middle attacks. Prefer Option #1 or, even better, have the server use a "real" certificate signed by a well known CA.

请注意,我根本不推荐选项 #2。禁用信任管理器会破坏 SSL 的某些部分,并使您容易受到中间人攻击。首选选项 #1,或者更好的是,让服务器使用由知名 CA 签署的“真实”证书。

回答by user207421

If 'they' are using a self-signed certificate it is up to them to take the steps required to make their server usable. Specifically that means providing their certificate to you offline in a trustworthy way. So get them to do that. You then import that into your truststore using the keytool as described in the JSSE Reference Guide. Don't even think about the insecure TrustManager posted here.

If 'they' are using a self-signed certificate it is up to them to take the steps required to make their server usable. Specifically that means providing their certificate to you offline in a trustworthy way. So get them to do that. You then import that into your truststore using the keytool as described in the JSSE Reference Guide. Don't even think about the insecure TrustManager posted here.

EDITFor the benefit of the seventeen(!) downvoters, and numerous commenters below, who clearly have not actually read what I have written here, this is nota jeremiad against self-signed certificates. There is nothing wrong with self-signed certificates when implemented correctly.But,the correct way to implement them is to have the certificate delivered securely via an offline process,rather than via the unauthenticated channel they are going to be used to authenticate. Surely this is obvious? It is certainly obvious to every security-aware organization I have ever worked for, from banks with thousands of branches to my own companies. The client-side code-base 'solution' of trusting allcertificates, including self-signed certificates signed by absolutely anybody, or any arbitary body setting itself up as a CA, is ipso factonot secure. It is just playing at security. It is pointless. You are having a private, tamperproof, reply-proof, injection-proof conversation with ... somebody. Anybody. A man in the middle. An impersonator. Anybody. You may as well just use plaintext.

编辑为了十七(!)downvoters 和下面众多评论者的利益,他们显然没有真正阅读我在这里写的内容,这不是反对自签名证书的 jereemiad。如果正确实施,自签名证书没有任何问题但是,实现它们的正确方法是通过离线过程安全地交付证书而不是通过它们将用于进行身份验证的未经身份验证的渠道。这肯定是显而易见的吗?对于我曾经工作过的每个具有安全意识的组织来说,从拥有数千家分支机构的银行到我自己的公司,这当然是显而易见的。信任所有人的客户端代码库“解决方案”证书,包括由绝对任何人或任何将自己设置为 CA 的任意机构签署的自签名证书,事实上是不安全的。它只是在玩安全。这是毫无意义的。您正在与……某人进行私密、防篡改、防回复、防注入的对话。任何人。中间一个人。一个模仿者。任何人。您也可以只使用纯文本。

回答by K.Nicholas

I chased down this problem to a certificate provider that is not part of the default JVM trusted hosts as of JDK 8u74. The provider is www.identrust.com, but that was not the domain I was trying to connect to. That domain had gotten its certificate from this provider. See Will the cross root cover trust by the default list in the JDK/JRE?-- read down a couple entries. Also see Which browsers and operating systems support Let's Encrypt.

我将这个问题追查到一个证书提供者,该提供者不属于JDK 8u74. 提供商是www.identrust.com,但这不是我试图连接的域。该域已从该提供商处获得其证书。请参阅交叉根是否覆盖 JDK/JRE 中默认列表的信任?- 阅读几个条目。另请参阅哪些浏览器和操作系统支持 Let's Encrypt

So, in order to connect to the domain I was interested in, which had a certificate issued from identrust.comI did the following steps. Basically, I had to get the identrust.com (DST Root CA X3) certificate to be trusted by the JVM. I was able to do that using Apache HttpComponents 4.5 like so:

因此,为了连接到我感兴趣的域,identrust.com我执行了以下步骤颁发的证书。基本上,我必须获得DST Root CA X3JVM 信任的 identrust.com ( ) 证书。我能够使用 Apache HttpComponents 4.5 做到这一点,如下所示:

1: Obtain the certificate from indettrust at Certificate Chain Download Instructions. Click on the DST Root CA X3link.

1:在证书链下载说明中从indetrust获取证书。单击DST Root CA X3链接。

2: Save the string to a file named "DST Root CA X3.pem". Be sure to add the lines "-----BEGIN CERTIFICATE-----" and "-----END CERTIFICATE-----" in the file at the beginning and the end.

2:将字符串保存到名为“DST Root CA X3.pem”的文件中。请务必在文件的开头和结尾添加“-----BEGIN CERTIFICATE-----”和“-----END CERTIFICATE-----”行。

3: Create a java keystore file, cacerts.jks with the following command:

3:使用以下命令创建一个 java 密钥库文件 cacerts.jks:

keytool -import -v -trustcacerts -alias IdenTrust -keypass yourpassword -file dst_root_ca_x3.pem -keystore cacerts.jks -storepass yourpassword

4: Copy the resulting cacerts.jks keystore into the resources directory of your java/(maven) application.

4:将生成的 cacerts.jks 密钥库复制到您的 java/(maven) 应用程序的资源目录中。

5: Use the following code to load this file and attach it to the Apache 4.5 HttpClient. This will solve the problem for all domains that have certificates issued from indetrust.comutil oracle includes the certificate into the JRE default keystore.

5:使用以下代码加载此文件并将其附加到Apache 4.5 HttpClient。这将解决具有从indetrust.comutil oracle颁发的证书的所有域的问题,将证书包含到 JRE 默认密钥库中。

SSLContext sslcontext = SSLContexts.custom()
        .loadTrustMaterial(new File(CalRestClient.class.getResource("/cacerts.jks").getFile()), "yourpasword".toCharArray(),
                new TrustSelfSignedStrategy())
        .build();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
        sslcontext,
        new String[] { "TLSv1" },
        null,
        SSLConnectionSocketFactory.getDefaultHostnameVerifier());
CloseableHttpClient httpclient = HttpClients.custom()
        .setSSLSocketFactory(sslsf)
        .build();

When the project builds then the cacerts.jks will be copied into the classpath and loaded from there. I didn't, at this point in time, test against other ssl sites, but if the above code "chains" in this certificate then they will work too, but again, I don't know.

当项目构建时,cacerts.jks 将被复制到类路径中并从那里加载。目前我没有针对其他 ssl 站点进行测试,但是如果上面的代码“链接”在这个证书中,那么它们也可以工作,但同样,我不知道。

Reference: Custom SSL contextand How do I accept a self-signed certificate with a Java HttpsURLConnection?

参考:自定义 SSL 上下文如何接受带有 Java HttpsURLConnection 的自签名证书?

回答by Jon Daniel

Rather than setting the default socket factory (which IMO is a bad thing) - yhis will just affect the current connection rather than every SSL connection you try to open:

而不是设置默认套接字工厂(IMO 是一件坏事) - yhis 只会影响当前连接而不是您尝试打开的每个 SSL 连接:

URLConnection connection = url.openConnection();
    // JMD - this is a better way to do it that doesn't override the default SSL factory.
    if (connection instanceof HttpsURLConnection)
    {
        HttpsURLConnection conHttps = (HttpsURLConnection) connection;
        // Set up a Trust all manager
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager()
        {

            public java.security.cert.X509Certificate[] getAcceptedIssuers()
            {
                return null;
            }

            public void checkClientTrusted(
                java.security.cert.X509Certificate[] certs, String authType)
            {
            }

            public void checkServerTrusted(
                java.security.cert.X509Certificate[] certs, String authType)
            {
            }
        } };

        // Get a new SSL context
        SSLContext sc = SSLContext.getInstance("TLSv1.2");
        sc.init(null, trustAllCerts, new java.security.SecureRandom());
        // Set our connection to use this SSL context, with the "Trust all" manager in place.
        conHttps.setSSLSocketFactory(sc.getSocketFactory());
        // Also force it to trust all hosts
        HostnameVerifier allHostsValid = new HostnameVerifier() {
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };
        // and set the hostname verifier.
        conHttps.setHostnameVerifier(allHostsValid);
    }
InputStream stream = connection.getInputStream();

回答by Ashish Saini

Trust all SSL certificates:- You can bypass SSL if you want to test on the testing server. But do not use this code for production.

信任所有 SSL 证书:- 如果您想在测试服务器上进行测试,您可以绕过 SSL。但不要将此代码用于生产。

public static class NukeSSLCerts {
protected static final String TAG = "NukeSSLCerts";

public static void nuke() {
    try {
        TrustManager[] trustAllCerts = new TrustManager[] { 
            new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    X509Certificate[] myTrustedAnchors = new X509Certificate[0];  
                    return myTrustedAnchors;
                }

                @Override
                public void checkClientTrusted(X509Certificate[] certs, String authType) {}

                @Override
                public void checkServerTrusted(X509Certificate[] certs, String authType) {}
            }
        };

        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, new SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String arg0, SSLSession arg1) {
                return true;
            }
        });
    } catch (Exception e) { 
    }
}

}

}

Please call this function in onCreate() function in Activity or in your Application Class.

请在 Activity 或您的应用程序类中的 onCreate() 函数中调用此函数。

NukeSSLCerts.nuke();

This can be used for Volley in Android.

这可用于 Android 中的 Volley。

回答by spiffy

Apache HttpClient 4.5 supports accepting self-signed certificates:

Apache HttpClient 4.5 支持接受自签名证书:

SSLContext sslContext = SSLContexts.custom()
    .loadTrustMaterial(new TrustSelfSignedStrategy())
    .build();
SSLConnectionSocketFactory socketFactory =
    new SSLConnectionSocketFactory(sslContext);
Registry<ConnectionSocketFactory> reg =
    RegistryBuilder.<ConnectionSocketFactory>create()
    .register("https", socketFactory)
    .build();
HttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(reg);        
CloseableHttpClient httpClient = HttpClients.custom()
    .setConnectionManager(cm)
    .build();
HttpGet httpGet = new HttpGet(url);
CloseableHttpResponse sslResponse = httpClient.execute(httpGet);

This builds an SSL socket factory which will use the TrustSelfSignedStrategy, registers it with a custom connection manager then does an HTTP GET using that connection manager.

这将构建一个 SSL 套接字工厂,它将使用 ,将其TrustSelfSignedStrategy注册到自定义连接管理器,然后使用该连接管理器执行 HTTP GET。

I agree with those who chant "don't do this in production", however there are use-cases for accepting self-signed certificates outside production; we use them in automated integration tests, so that we're using SSL (like in production) even when not running on the production hardware.

我同意那些高呼“不要在生产中这样做”的人,但是也有在生产之外接受自签名证书的用例;我们在自动化集成测试中使用它们,因此即使不在生产硬件上运行,我们也可以使用 SSL(就像在生产中一样)。

回答by Christopher Schneider

The accepted answer is fine, but I'd like to add something to this as I was using IntelliJ on Mac and couldn't get it to work using the JAVA_HOMEpath variable.

接受的答案很好,但我想为此添加一些内容,因为我在 Mac 上使用 IntelliJ 并且无法使用JAVA_HOME路径变量使其工作。

It turns out Java Home was different when running the application from IntelliJ.

事实证明,当从 IntelliJ 运行应用程序时,Java Home 是不同的。

To figure out exactly where it is, you can just do System.getProperty("java.home")as that's where the trusted certificates are read from.

要确定它的确切位置,您可以System.getProperty("java.home")按照读取受信任证书的位置进行操作。

回答by Johannes Brodwall

There's a better alternative to trusting all certificates: Create a TrustStorethat specifically trusts a given certificate and use this to create a SSLContextfrom which to get the SSLSocketFactoryto set on the HttpsURLConnection. Here's the complete code:

有对信任的所有证书一个更好的选择:创建一个TrustStore能特异性信任一个给定的证书,并使用它来创建一个SSLContext从中获得SSLSocketFactory到集上HttpsURLConnection。这是完整的代码:

File crtFile = new File("server.crt");
Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(new FileInputStream(crtFile));

KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry("server", certificate);

TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);

HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
connection.setSSLSocketFactory(sslContext.getSocketFactory());

You can alternatively load the KeyStoredirectly from a file or retrieve the X.509 Certificate from any trusted source.

您也可以KeyStore直接从文件加载或从任何受信任的来源检索 X.509 证书。

Note that with this code, the certificates in cacertswill not be used. This particular HttpsURLConnectionwill only trust this specific certificate.

请注意,使用此代码,cacerts将不会使用中的证书。这个特定的HttpsURLConnection只会信任这个特定的证书。

回答by VSK

This is not a solution to the complete problem but oracle has good detailed documentation on how to use this keytool. This explains how to

这不是一个完整问题的解决方案,但是 oracle 有关于如何使用这个 keytool 的很好的详细文档。这解释了如何

  1. use keytool.
  2. generate certs/self signed certs using keytool.
  3. import generated certs to java clients.
  1. 使用密钥工具。
  2. 使用 keytool 生成证书/自签名证书。
  3. 将生成的证书导入 Java 客户端。

https://docs.oracle.com/cd/E54932_01/doc.705/e54936/cssg_create_ssl_cert.htm#CSVSG178

https://docs.oracle.com/cd/E54932_01/doc.705/e54936/cssg_create_ssl_cert.htm#CSVSG178

回答by user66660

Instead of using keytool as suggested by the top comment, on RHEL you can use update-ca-trust starting in newer versions of RHEL 6. You'll need to have the cert in pem format. Then

在 RHEL 上,您可以从较新版本的 RHEL 6 开始使用 update-ca-trust,而不是按照顶部评论的建议使用 keytool。您需要拥有 pem 格式的证书。然后

trust anchor <cert.pem>

Edit /etc/pki/ca-trust/source/cert.p11-kit and change "certificate category: other-entry" to "certificate category: authority". (Or use sed to do this in a script.) Then do

编辑 /etc/pki/ca-trust/source/cert.p11-kit 并将“证书类别:其他条目”更改为“证书类别:权限”。(或使用 sed 在脚本中执行此操作。)然后执行

update-ca-trust

A couple caveats:

几个警告:

  • I couldn't find "trust" on my RHEL 6 server and yum didn't offer to install it. I ended up using it on an RHEL 7 server and copying the .p11-kit file over.
  • To make this work for you, you may need to do update-ca-trust enable. This will replace /etc/pki/java/cacerts with a symbolic link pointing to /etc/pki/ca-trust/extracted/java/cacerts. (So you might want to back up the former first.)
  • If your java client uses cacerts stored in some other location, you'll want to manually replace it with a symlink to /etc/pki/ca-trust/extracted/java/cacerts, or replace it with that file.
  • 我在我的 RHEL 6 服务器上找不到“信任”,而且 yum 没有提供安装它。我最终在 RHEL 7 服务器上使用它并复制了 .p11-kit 文件。
  • 为了使这项工作适合您,您可能需要执行update-ca-trust enable. 这将用指向 /etc/pki/ca-trust/extracted/java/cacerts 的符号链接替换 ​​/etc/pki/java/cacerts。(所以你可能想先备份前者。)
  • 如果您的 Java 客户端使用存储在其他位置的 cacert,您需要手动将其替换为指向 /etc/pki/ca-trust/extracted/java/cacerts 的符号链接,或将其替换为该文件。