请求的目标路径无法找到有效的证书路径-即使导入了证书仍然出现错误

296

我有一个Java客户端尝试访问一个具有自签名证书的服务器。

当我尝试向服务器发送Post请求时,我遇到了以下错误:

无法找到到所请求目标的有效认证路径

经过一些研究后,我执行了以下步骤:

  1. 将我的服务器域名保存为一个root.cer文件。

  2. 在我的Glassfish服务器的JRE中,我运行了以下命令:

    keytool -import -alias example -keystore cacerts -file root.cer
    
  3. 为了检查我的证书是否成功添加到cacert中,我执行了以下操作:

  4. keytool -list -v -keystore cacerts
    

    我可以看到证书已经存在。

  5. 然后我重新启动了Glassfish并重试了“post”。

我仍然得到相同的错误。

我有一种感觉,这是因为我的Glassfish实际上没有读取我修改的cacert文件,而可能是其他一些文件。

你们有没有遇到过这个问题,能指导我正确的方向吗?


2
只是澄清一下,“我有一个Java客户端试图访问一个带有自签名证书的服务器。”:你是在谈论使用自签名的客户端证书,对吗?你的Glassfish连接器设置中是否有任何特定的配置(特别是信任存储设置)? - Bruno
1
我在Glassfish JVM中找到了2个设置:-Djavax.net.ssl.keyStore=${com.sun.aas.instanceRoot}/config/keystore.jks和-Djavax.net.ssl.trustStore=${com.sun.aas.instanceRoot}/config/cacerts.jks。现在我需要将我的证书添加到其中一个。您能确认我应该将其添加到密钥库中吗? - TheCoder
8
在服务器上,密钥库用于存储服务器证书和其私钥(密钥库用于存储属于本地方的内容)。信任库用于存储用于验证远程方信任的证书。您应该将客户端证书添加到您的服务器信任库中。(另请参见this,尽管Glassfish似乎没有使用JRE的默认位置。) - Bruno
对于Glassfish V4,这个过程对我来说有效,但像TheCoder一样,我将其添加到了我的Glassfish cacerts.jks而不是Java的那个。 - ben_979
2
您可以像这个完整的可运行示例代码中所示地盲目接受所有SLL证书 - 当然,这是不安全的!:http://www.nakov.com/blog/2009/07/16/disable-certificate-validation-in-java-ssl-connections/ - Dreamspace President
显示剩余6条评论
17个回答

219

不幸的是,这可能有很多原因。许多应用服务器和其他Java“包装器”容易操作属性、密钥链等等。因此,它可能在查看完全不同的东西。

除了使用truss命令,我建议尝试:

java -Djavax.net.debug=all -Djavax.net.ssl.trustStore=trustStore ...

你可以尝试将其设置为“ssl”,密钥管理器和信任管理器之一,以查看是否有所帮助。在大多数平台上,将其设置为“help”将列出类似以下的内容。

不管怎样,确保您充分了解密钥库(其中包含您用于证明自己身份的私钥和证书)与信任库(确定您信任谁)之间的区别,以及您自己的身份也有到根的“链” - 这与您需要弄清楚“您”信任的任何根链是分开的。

all            turn on all debugging
ssl            turn on ssl debugging

The   following can be used with ssl:
    record       enable per-record tracing
    handshake    print each handshake message
    keygen       print key generation data
    session      print session activity
    defaultctx   print default SSL initialization
    sslctx       print SSLContext tracing
    sessioncache print session cache tracing
    keymanager   print key manager tracing
    trustmanager print trust manager tracing
    pluggability print pluggability tracing

    handshake debugging can be widened with:
    data         hex dump of each handshake message
    verbose      verbose handshake message printing

    record debugging can be widened with:
    plaintext    hex dump of record plaintext
    packet       print raw SSL/TLS packets

来源:# 请参阅http://download.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html#Debug


9
非常感谢!-Djavax.net.ssl.trustStore=/location_of/trustStore 解决了我的问题,而且调试信息也非常有帮助。 - RHE
8
运行以下命令时出现以下错误: 错误:找不到或无法加载主类... java -Djavax.net.debug=all -Djavax.net.ssl.trustStore=trustStore ... (翻译说明:将原文中的错误信息直接翻译成中文,同时把英文命令和参数保留) - rohith
2
当然,您可能还需要使用-Djavax.net.ssl.trustStorePassword=changeit指定信任库的密码。 - user1754036
@rohith,请将路径指定为一个简单的helloWorld.java,而不是使用... - vesperto

34

以下是解决方案,按照以下链接逐步操作:

http://www.mkyong.com/webservices/jax-ws/suncertpathbuilderexception-unable-to-find-valid-certification-path-to-requested-target/

JAVA文件:博客中缺失

/*
 * Copyright 2006 Sun Microsystems, Inc.  All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Sun Microsystems nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */



import java.io.*;
import java.net.URL;

import java.security.*;
import java.security.cert.*;

import javax.net.ssl.*;

public class InstallCert {

    public static void main(String[] args) throws Exception {
    String host;
    int port;
    char[] passphrase;
    if ((args.length == 1) || (args.length == 2)) {
        String[] c = args[0].split(":");
        host = c[0];
        port = (c.length == 1) ? 443 : Integer.parseInt(c[1]);
        String p = (args.length == 1) ? "changeit" : args[1];
        passphrase = p.toCharArray();
    } else {
        System.out.println("Usage: java InstallCert <host>[:port] [passphrase]");
        return;
    }

    File file = new File("jssecacerts");
    if (file.isFile() == false) {
        char SEP = File.separatorChar;
        File dir = new File(System.getProperty("java.home") + SEP
            + "lib" + SEP + "security");
        file = new File(dir, "jssecacerts");
        if (file.isFile() == false) {
        file = new File(dir, "cacerts");
        }
    }
    System.out.println("Loading KeyStore " + file + "...");
    InputStream in = new FileInputStream(file);
    KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
    ks.load(in, passphrase);
    in.close();

    SSLContext context = SSLContext.getInstance("TLS");
    TrustManagerFactory tmf =
        TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    tmf.init(ks);
    X509TrustManager defaultTrustManager = (X509TrustManager)tmf.getTrustManagers()[0];
    SavingTrustManager tm = new SavingTrustManager(defaultTrustManager);
    context.init(null, new TrustManager[] {tm}, null);
    SSLSocketFactory factory = context.getSocketFactory();

    System.out.println("Opening connection to " + host + ":" + port + "...");
    SSLSocket socket = (SSLSocket)factory.createSocket(host, port);
    socket.setSoTimeout(10000);
    try {
        System.out.println("Starting SSL handshake...");
        socket.startHandshake();
        socket.close();
        System.out.println();
        System.out.println("No errors, certificate is already trusted");
    } catch (SSLException e) {
        System.out.println();
        e.printStackTrace(System.out);
    }

    X509Certificate[] chain = tm.chain;
    if (chain == null) {
        System.out.println("Could not obtain server certificate chain");
        return;
    }

    BufferedReader reader =
        new BufferedReader(new InputStreamReader(System.in));

    System.out.println();
    System.out.println("Server sent " + chain.length + " certificate(s):");
    System.out.println();
    MessageDigest sha1 = MessageDigest.getInstance("SHA1");
    MessageDigest md5 = MessageDigest.getInstance("MD5");
    for (int i = 0; i < chain.length; i++) {
        X509Certificate cert = chain[i];
        System.out.println
            (" " + (i + 1) + " Subject " + cert.getSubjectDN());
        System.out.println("   Issuer  " + cert.getIssuerDN());
        sha1.update(cert.getEncoded());
        System.out.println("   sha1    " + toHexString(sha1.digest()));
        md5.update(cert.getEncoded());
        System.out.println("   md5     " + toHexString(md5.digest()));
        System.out.println();
    }

    System.out.println("Enter certificate to add to trusted keystore or 'q' to quit: [1]");
    String line = reader.readLine().trim();
    int k;
    try {
        k = (line.length() == 0) ? 0 : Integer.parseInt(line) - 1;
    } catch (NumberFormatException e) {
        System.out.println("KeyStore not changed");
        return;
    }

    X509Certificate cert = chain[k];
    String alias = host + "-" + (k + 1);
    ks.setCertificateEntry(alias, cert);

    OutputStream out = new FileOutputStream("jssecacerts");
    ks.store(out, passphrase);
    out.close();

    System.out.println();
    System.out.println(cert);
    System.out.println();
    System.out.println
        ("Added certificate to keystore 'jssecacerts' using alias '"
        + alias + "'");
    }

    private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();

    private static String toHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder(bytes.length * 3);
        for (int b : bytes) {
            b &= 0xff;
            sb.append(HEXDIGITS[b >> 4]);
            sb.append(HEXDIGITS[b & 15]);
            sb.append(' ');
        }
        return sb.toString();
    }

    private static class SavingTrustManager implements X509TrustManager {

    private final X509TrustManager tm;
    private X509Certificate[] chain;

    SavingTrustManager(X509TrustManager tm) {
        this.tm = tm;
    }

    public X509Certificate[] getAcceptedIssuers() {
        throw new UnsupportedOperationException();
    }

    public void checkClientTrusted(X509Certificate[] chain, String authType)
        throws CertificateException {
        throw new UnsupportedOperationException();
    }

    public void checkServerTrusted(X509Certificate[] chain, String authType)
        throws CertificateException {
        this.chain = chain;
        tm.checkServerTrusted(chain, authType);
    }
    }

}

4
这个解决方案对我有效,但私有类SavingTrustManager需要做一个小变动:public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0];} - Richard
1
@Richard,@Paul,@user6258309 我遇到了异常错误:Exception in thread "main" java.net.SocketTimeoutException: Read timed out at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.socketRead(Unknown Source)请问我该如何解决这个问题? - Renjith Krishnan
14
这个“解决方案”存在极大的不安全性。请勿使用。 - user207421
51
这个答案主要是代码,没有解释为什么它有效或无效。 - user6490459

17
你需要配置JSSE系统属性,具体来说是指向客户端证书存储的位置。
通过命令行实现:
java -Djavax.net.ssl.trustStore=truststores/client.ts com.progress.Client

或通过Java代码:

import java.util.Properties;
    ...
    Properties systemProps = System.getProperties();
    systemProps.put("javax.net.ssl.keyStorePassword","passwordForKeystore");
    systemProps.put("javax.net.ssl.keyStore","pathToKeystore.ks");
    systemProps.put("javax.net.ssl.trustStore", "pathToTruststore.ts");
    systemProps.put("javax.net.ssl.trustStorePassword","passwordForTrustStore");
    System.setProperties(systemProps);
    ...

更多细节请参考RedHat网站


在使用Spring Boot、Spring Cloud微服务以及自签名SSL证书时,我遇到了同样的问题。我能够在application.properties中设置keyStore和keyStorePassword,并使其开箱即用,但是对于trustStore和trustStorePassword,我尝试了很多次都没有成功。这个答案对我关于trust store的问题非常有效。 - Mate Šimović
Java代码对我很有效。谢谢。 - ammy

15

(来自我的其他答案转载)
使用Java软件发行版中的命令行实用工具keytool导入(和信任!)所需证书。

示例:

  1. 从命令行切换到jre\bin目录

  2. 检查密钥库(在jre\bin目录中找到的文件)
    keytool -list -keystore ..\lib\security\cacerts
    密码为changeit

  3. 下载并保存所需服务器链中的所有证书。

  4. 添加证书(在需要之前要删除文件..\lib\security\cacerts上的“只读”属性),运行:

    keytool -alias REPLACE_TO_ANY_UNIQ_NAME -import -keystore .\lib\security\cacerts -file “r:\root.crt”

我偶然发现了这么简单的提示。 其他解决方案需要使用InstallCert.Java和JDK。

来源:http://www.java-samples.com/showtutorial.php?tutorialid=210


非常好,感谢!有了keytool -alias REPLACE_TO_ANY_UNIQ_NAME -import -keystore.\lib\security\cacerts -file "r:\root.crt"的帮助,我不需要更改我的Java代码中的CloseableHttpClient httpClient = HttpClients.createDefault() - frank

10

我在尝试从应用程序访问一个使用自签名证书的https url时,遇到了这个错误。他们提供的是一个.cert 文件,我不确定应该把它放在哪里。我是按照以下方式解决的:

keytool 的位置位于 JDK/bin 文件夹下。

方法1:将证书添加到默认的Java信任库 - cacerts

keytool -import -alias myCert -file C://certificate.cert -keystore C://Program Files//Java//jdk1.8.0_271//jre//lib//security//cacerts

密码: changeit

方法二:

创建信任存储:

keytool -import -alias myCert -file C://certificate.cert -keystore myTrustStore

它会给你以下提示,可以按照以下填写:

Enter keystore password:changeit
Re-enter new password:changeit
Trust this certificate?yes

这将在运行该命令的文件夹中创建一个myTrustStore文件。将此“mytrustStore”复制到方便的位置。

使用信任库:

在运行应用程序/服务器时,请传递以下JVM参数:

-Djavax.net.ssl.trustStore=C://myTrustStore -Djavax.net.ssl.trustStorePassword=changeit

1
我尝试了第二种方法,它对我有效。 - Elf
@Deb 你是在客户端还是服务器上运行这些命令?我猜应该是在客户端上吧? - Ayoub Omari
这是客户端的。 - Deb
方法2对我有效。 - faboulaws
1
我在通过Firebase admin sdk发送推送通知时遇到了问题,这个错误的原因是cacerts.jks文件没有添加SSL证书。经过大量搜索,我找到了@Deb的答案,非常感谢你的努力。 - undefined

7

我曾经遇到和 sbt 相同的问题。
它尝试从 repo1.maven.org 通过 SSL 获取依赖项
但是却说“无法找到到请求目标 URL 的有效认证路径”。
所以我按照这篇文章中的建议去尝试,但仍然无法验证连接。
后来我发现根证书不够,正如那篇文章所建议的一样,所以 -
对我有用的方法是将中间 CA 证书导入密钥库
我实际上添加了链中的所有证书,结果非常成功。


有过同样的经历:只导入根目录不起作用,但导入中间目录却可以。这是什么原因呢? - Vankog
你只需要一个签名证书,而不是所有的证书。 - user207421

5

从JDK 8迁移到JDK 10的解决方案

JDK 10

root@c339504909345:/opt/jdk-minimal/jre/lib/security #  keytool -cacerts -list
Enter keystore password:
Keystore type: JKS
Keystore provider: SUN

Your keystore contains 80 entries

JDK 8

root@c39596768075:/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/security/cacerts #  keytool -cacerts -list
Enter keystore password:
Keystore type: JKS
Keystore provider: SUN

Your keystore contains 151 entries

修复步骤

  • 我删除了JDK 10证书并用JDK 8替换了它
  • 由于我正在构建Docker镜像,因此我可以使用多阶段构建来快速完成
    • 我正在使用jlink构建最小的JRE,命令如下:/opt/jdk/bin/jlink \ --module-path /opt/jdk/jmods...

因此,以下是不同路径和命令序列:

# Java 8
COPY --from=marcellodesales-springboot-builder-jdk8 /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/security/cacerts /etc/ssl/certs/java/cacerts

# Java 10
RUN rm -f /opt/jdk-minimal/jre/lib/security/cacerts
RUN ln -s /etc/ssl/certs/java/cacerts /opt/jdk-minimal/jre/lib/security/cacerts

最后一段可能是Docker脚本的部分。前面的块可能是用于Shell命令的。 - Alexander Stohr

4
我正在为www.udemy.com(REST Java Web Services)制作有关REST web服务的教程。教程中的示例说明,为了拥有SSL,我们必须在我的eclipse“客户端”项目中拥有一个名为“trust_store”的文件夹,其中应包含一个“key store”文件(我们有一个“客户端”项目来调用服务和一个包含REST web服务的“服务”项目 - 两个项目位于同一eclipse工作区中,一个是客户端,另一个是服务)。为了简单起见,他们建议从我们使用的glassfish应用程序服务器(glassfish \ domains \ domain1 \ config \ keystore.jks)中复制“keystore.jks”并将其放入客户端项目中我创建的这个“trust_store”文件夹中。这似乎很有道理:服务器中的自签名证书将对应于客户端trust_store中的证书。现在,我遇到了原帖提到的错误。我已经谷歌了这个问题,并读到该错误是由于客户端上的“keystore.jks”文件不包含受信任/签名证书造成的,它找到的证书是自签名的。
为了让事情更清晰,让我说一下,据我所知,“keystore.jks”包含自签名证书,“cacerts.jks”文件包含CA证书(由CA签名)。 “keystore.jks”是“keystore”,而“cacerts.jks”是“trust store”。正如评论者“Bruno”所说,“keystore.jks”是本地的,“cacerts.jks”是远程客户端的。
因此,我对自己说,嘿,glassfish也有“cacerts.jks”文件,这是glassfish的trust_store文件。 cacerts.jsk应该包含CA证书。显然,我需要让我的trust_store文件夹包含至少一个CA证书的密钥库文件。因此,我尝试将“cacerts.jks”文件放入我在客户端项目中创建的“trust_store”文件夹中,并更改VM属性以指向“cacerts.jks”而不是“keystore.jks”。那就消除了错误。我想它只需要一个CA证书就可以工作。
这可能不是生产的理想选择,甚至不适用于开发,超出了使某些东西工作的范围。例如,您可以使用“keytool”命令将CA证书添加到客户端的“keystore.jks”文件中。但无论如何,希望这至少缩小了可能导致错误的可能情况范围。
此外:我的方法似乎对客户端有用(将服务器证书添加到客户端trust_store),看起来上面的评论对解决原始帖子很有用(将客户端证书添加到服务器trust_store)。干杯。
Eclipse项目设置:
  • MyClientProject(我的客户端项目)
  • src(源代码)
  • test(测试)
  • JRE系统库
  • ...
  • trust_store
    ---cacerts.jks(证书信任库) ---keystore.jks(密钥库)

来自MyClientProject.java文件的片段:

static {
  // Setup the trustStore location and password
  System.setProperty("javax.net.ssl.trustStore","trust_store/cacerts.jks");
  // comment out below line
  System.setProperty("javax.net.ssl.trustStore","trust_store/keystore.jks");
  System.setProperty("javax.net.ssl.trustStorePassword", "changeit");
  //System.setProperty("javax.net.debug", "all");

  // for localhost testing only
  javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(new javax.net.ssl.HostnameVerifier() {
        public boolean verify(String hostname, javax.net.ssl.SSLSession sslSession) {
          return hostname.equals("localhost");
        }

  });
}

3

浪费了很多时间在这个问题上。如果你已经导入了证书并且可以在这里列表中看到它。

keytool -list -v -keystore $JAVA_HOME/lib/security/cacerts

然后使用以下命令创建新的内容,将SITE_NAME,SITE_PORT,CERTIFICATE_NAME和保存文件的路径替换为相应内容。

echo -n | openssl s_client -connect SITE_NAME:SITE_PORT  \
| openssl x509 > /path/to/save/CERTIFICATE_NAME.cert

在我的情况下,我使用Keycloak和Spring时遇到了问题。在我使用这个命令创建证书并将其导入到密钥库后,问题得到解决,现在它可以正常运行。


2

我的问题是,一个云访问安全代理软件 NetScope 通过软件更新安装在我的工作笔记本电脑上。这会改变证书链,即使我将整个链导入到 cacerts 密钥库中,我仍然无法通过我的 Java 客户端连接到服务器。我禁用了 NetScope,成功地连接到了服务器。


如果您无法禁用Netskope(公司软件),该怎么办? - Germán Sánchez Pastor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接