java 使用 trustStore 时 HTTPS 证书验证失败

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

HTTPS certificate validation fails when using a trustStore

javasslhttps

提问by Ignorante

I'm getting the following error

我收到以下错误

sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

sun.security.validator.ValidatorException:PKIX 路径构建失败:sun.security.provider.certpath.SunCertPathBuilderException:无法找到请求目标的有效认证路径

when connecting to google maps geocoding API. I am able to reproduce the error in a simple Main program. Here's how to reproduce it with this test program:

连接到谷歌地图地理编码 API 时。我能够在一个简单的 Main 程序中重现该错误。以下是使用此测试程序重现它的方法:

import javax.net.ssl.*;
import java.net.*;
import java.io.*;

public class Main {

    public static void main(String[] args) {
        try {
            String httpsURL = "https://maps.googleapis.com/maps/api/geocode/json?address=49+874%2Cla+plata%2Cbuenos+aires%2Cargentina&sensor=false&key=AIzaSyAJ1QS0C6KjiWajwxx4jUb_Jz0b8lBZyyE";
            URL myurl = new URL(httpsURL);
            HttpsURLConnection con = (HttpsURLConnection) myurl.openConnection();
            InputStream ins = con.getInputStream();
            InputStreamReader isr = new InputStreamReader(ins);
            BufferedReader in = new BufferedReader(isr);
            String inputLine;
            while ((inputLine = in.readLine()) != null) {
                System.out.println(inputLine);
            }
            in.close();
        } catch (IOException ex) {
            System.err.println(ex.getMessage());
        }
    }
}

Saved as Main.java Compile it

保存为 Main.java 编译

javac Main.java

javac Main.java

Run it

运行

java Main

java Main

I get the normal result (json response is printed).

我得到了正常的结果(打印了 json 响应)。

But if I create a TrustStore with a certificate from here: https://www.clic.gob.ar/I downloaded the SSL certificate and saved it as an X.509 PEM file named clic.gob.ar

但是,如果我使用以下证书创建 TrustStore:https://www.clic.gob.ar/ 我下载了 SSL 证书并将其保存为名为 clic.gob.ar 的 X.509 PEM 文件

Create a new TrustStore named keystorefede.jks

创建一个名为 keystorefede.jks 的新 TrustStore

keytool -import -file clic.gob.ar -alias clicCert -keystore keystorefede.jks

keytool -import -file clic.gob.ar -alias clicCert -keystore keystorefede.jks

I gave it password tompass. I can list it

我给了它密码 tompass。我可以列出来

keytool -list -keystore keystorefede.jks -storepass tompass

keytool -list -keystore keystorefede.jks -storepass tompass

Tipo de Almacén de Claves: JKS Proveedor de Almacén de Claves: SUN

克拉夫斯阿尔马森的分类:JKS 阿尔马森·德克拉夫斯的证明者:SUN

Su almacén de claves contiene 1 entrada

Su almacén de claves contiene 1 entrada

cliccert, 01/08/2014, trustedCertEntry, Huella Digital de Certificado (SHA1): 15:3B:67:EE:51:C9:F2:CF:68:7C:24:51:A4:B6:6E:AE:EA:61:D5:B5

cliccert,01/08/2014,trustedCertEntry,Huella Digital de Certificado (SHA1):15:3B:67:EE:51:C9:F2:CF:68:7C:24:51:A4:B6:6E:AE: EA:61:D5:B5

Now run the same program using the trustStore

现在使用 trustStore 运行相同的程序

java -Djavax.net.ssl.trustStore=/home/fede/keystorefede.jks -Djavax.net.ssl.trustStorePassword=tompass Mainsun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

java -Djavax.net.ssl.trustStore=/home/fede/keystorefede.jks -Djavax.net.ssl.trustStorePassword=tompass Mainsun.security.validator.ValidatorException:PKIX 路径构建失败:sun.security.provider.certpath.SunCertPathBuilderException:无法找到请求目标的有效认证路径

This happens with Java 8 and also Java 7.

Java 8 和 Java 7 都会发生这种情况。

java version "1.8.0_11"

java版本“1.8.0_11”

Java(TM) SE Runtime Environment (build 1.8.0_11-b12)

Java(TM) SE 运行时环境(构建 1.8.0_11-b12)

Java HotSpot(TM) 64-Bit Server VM (build 25.11-b03, mixed mode)

Java HotSpot(TM) 64 位服务器 VM(构建 25.11-b03,混合模式)

The problem was first discovered inside a web application running inside Tomcat. The certificate has to be in a TrustStore in Tomcat's command line for another request; the trust store has nothing google related, just one certificate. If I add Google's certificate to the trust store then the problem is solved, but this is not a proper solution. Google does not accept geocoding requests over http using an API key. It the -Djavax.net.ssl.trustStore overriding all the root CA's Java knows?

该问题最初是在 Tomcat 内运行的 Web 应用程序中发现的。对于另一个请求,证书必须位于 Tomcat 命令行中的 TrustStore 中;信任商店与谷歌无关,只有一个证书。如果我将 Google 的证书添加到信任库,那么问题就解决了,但这不是一个正确的解决方案。Google 不接受使用 API 密钥通过 http 的地理编码请求。是 -Djavax.net.ssl.trustStore 覆盖了所有根 CA 的 Java 知道的?

回答by GPI

The javax.net.ssl.trustStore property, indeed, overrides all known certificates of the JVM with the one you provide.

实际上,javax.net.ssl.trustStore 属性会使用您提供的证书覆盖 JVM 的所有已知证书。

By default, the JVM comes with a trustStore prepopulated with a fairly decent number of well known authorities (the Oracle JVM stores it in JAVA_HOME/jre/lib/security/cacerts).

默认情况下,JVM 带有一个 trustStore,其中预填充了相当数量的知名权限(Oracle JVM 将其存储在 JAVA_HOME/jre/lib/security/cacerts 中)。

This keyStore will be used as the default by the JSSE (Java Secure Socket Extension) by default to validate SSL handshakes.

默认情况下,此密钥库将被 JSSE(Java 安全套接字扩展)用作默认值以验证 SSL 握手。

The javax.net.ssl.trustStore environnement variable overrides this default location, meaning none of its content's are relevant any more.

javax.net.ssl.trustStore 环境变量覆盖此默认位置,这意味着其内容不再相关。

Going forward, you have a few solutions:
One is : you build your own JKS containing everything you need.
Second is : you add certificates to your JVM's default file.
Third is : you code.

展望未来,您有几个解决方案:
一个是:您构建自己的 JKS,其中包含您需要的一切。
其次是:您将证书添加到 JVM 的默认文件中。
第三是:你的代码。

Getting your own SSL Context "by hand" ?

“手动”获取您自己的 SSL 上下文?

Sockets that underly HTTPURLConnection are made out of SocketFactoryinstances. When HTTPS is involved, what happens is that you need to initialize your own SSLSocketFactory with wathever certificate/private keys are needed for your call, and associate the SocketFactory with the HTTPURLConnection before connecting it : see http://docs.oracle.com/javase/7/docs/api/javax/net/ssl/HttpsURLConnection.html#setSSLSocketFactory%28javax.net.ssl.SSLSocketFactory%29

HTTPURLConnection 基础的套接字由SocketFactory实例构成。当涉及 HTTPS 时,发生的情况是您需要使用调用需要的证书/私钥来初始化您自己的 SSLSocketFactory,并在连接之前将 SocketFactory 与 HTTPURLConnection 相关联:请参阅http://docs.oracle.com/ javase/7/docs/api/javax/net/ssl/HttpsURLConnection.html#setSSLSocketFactory%28javax.net.ssl.SSLSocketFactory%29

This works like this. First, you need to load your KeyStore (JKS file containing your certificate, exception handling cut for shortening) :

这就像这样。首先,您需要加载您的 KeyStore(包含您的证书的 JKS 文件,用于缩短的异常处理):

InputStream keyStoreStream = ... // Wherever it is
KeyStore ks = KeyStore.getInstance("JKS"); // or "PKCS12" for pfx/p12
ks.load(is, password);

Once you have a KeyStore instance, you can build a "TrustManager" that will use any certificates declared as trusted in the Keystore as valid trust anchors.

一旦您拥有 KeyStore 实例,您就可以构建一个“TrustManager”,它将使用在 Keystore 中声明为受信任的任何证书作为有效的信任锚。

TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); // PKIX
tmf.init(yourKeyStore); // if you pass null, you get the JVM defaults
                        // which is CACerts file or javax.net.ssl.trustStore

You can do the same for your SSL KeyManagerFactory (if you use 2 way SSL), the pattern is exactly the same. Once you have TrustManagerFactory and KeyManagerFactory instances, you are ready to build a SSLSocketFactory.

您可以对 SSL KeyManagerFactory 执行相同操作(如果您使用 2 路 SSL),模式完全相同。拥有 TrustManagerFactory 和 KeyManagerFactory 实例后,您就可以构建 SSLSocketFactory 了。

  SSLContext sslCtx = SSLContext.getInstance("TLS");
  sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
  SSLSocketFactory sslSF = sslCtx.getSocketFactory();

At this point, you can do

此时,你可以做

  URL url = new URL("https://test.com/test");
  URLConnection conn = url.openConnection();
  if(conn instanceof HttpsURLConnection) {
    ((HttpsURLConnection) conn).setSSLSocketFactory(sslSF);
  }
  conn.connect();

回答by Adrien

The application doesn't find the right truststore. You can define it that way: System.setProperty("javax.net.ssl.trustStore", ".\\src\\truststore.jks"); System.setProperty("javax.net.ssl.trustStorePassword", "psw123");

应用程序找不到正确的信任库。你可以这样定义: System.setProperty("javax.net.ssl.trustStore", ".\\src\\truststore.jks"); System.setProperty("javax.net.ssl.trustStorePassword", "psw123");