【问题标题】:HTTPS certificate validation fails when using a trustStore使用 trustStore 时 HTTPS 证书验证失败
【发布时间】:2014-09-24 21:15:38
【问题描述】:

我收到以下错误

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

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

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

另存为 Main.java 编译它

javac Main.java

运行它

java Main

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

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

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

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

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

keytool -list -keystore keystorefede.jks -storepass tompass

Tipo de Almacén de Claves:JKS Proveedor de Almacén de Claves:太阳

Su almacén de claves contiene 1 entrada

cliccert,2014 年 1 月 8 日,trustedCertEntry, Huella 数字证书 (SHA1):15:3B:67:EE:51:C9:F2:CF:68:7C:24:51:A4:B6:6E:AE:EA:61:D5:B5

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

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

Java 8 和 Java 7 都会出现这种情况。

java 版本“1.8.0_11”

Java(TM) SE 运行时环境(内部版本 1.8.0_11-b12)

Java HotSpot(TM) 64 位服务器 VM(内部版本 25.11-b03,混合模式)

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

【问题讨论】:

    标签: java ssl https


    【解决方案1】:

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

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

    默认情况下,JSSE(Java 安全套接字扩展)将使用此 keyStore 作为默认值来验证 SSL 握手。

    javax.net.ssl.trustStore 环境变量覆盖了这个默认位置,这意味着它的所有内容都不再相关。

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

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

    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

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

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

    一旦您拥有一个 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
    

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

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

    此时,你可以做

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

    【讨论】:

    • 我编写了一个手动构建 SSLContext 的小型 Java 应用程序(与您在答案中描述的方式非常相似,除了我在 sslCtx.init(...) 信任管理器方法中传递了 null )。我就此询问了question。我遇到的问题是我从 Azure KeyVault 获得了 X509Certificate 对象的句柄,而不是密钥库文件输入流。不知道如何从那里开始。请指教。
    【解决方案2】:

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

    【讨论】:

      猜你喜欢
      • 2016-08-05
      • 2020-11-01
      • 2017-03-25
      • 2019-04-20
      • 1970-01-01
      • 2012-03-15
      • 2011-09-17
      • 1970-01-01
      • 2016-11-21
      相关资源
      最近更新 更多