"PKIX路径构建失败" 和 "无法找到请求目标的有效认证路径"

892

我正在尝试在我的Java项目中使用twitter4j库获取推文,在底层使用的是java.net.HttpURLConnection(如堆栈跟踪所示)。在第一次运行时,我遇到了关于证书sun.security.validator.ValidatorExceptionsun.security.provider.certpath.SunCertPathBuilderException的错误。然后我通过以下方式添加了Twitter证书:

C:\Program Files\Java\jdk1.7.0_45\jre\lib\security>keytool -importcert -trustcacerts -file PathToCert -alias ca_alias -keystore "C:\Program Files\Java\jdk1.7.0_45\jre\lib\security\cacerts"

但是没有成功。这里是获取推文的过程:

public static void main(String[] args) throws TwitterException {
    ConfigurationBuilder cb = new ConfigurationBuilder();
    cb.setDebugEnabled(true)
        .setOAuthConsumerKey("myConsumerKey")
        .setOAuthConsumerSecret("myConsumerSecret")
        .setOAuthAccessToken("myAccessToken")
        .setOAuthAccessTokenSecret("myAccessTokenSecret");
    
    TwitterFactory tf = new TwitterFactory(cb.build());
    Twitter twitter = tf.getInstance();
    
    try {
        Query query = new Query("iphone");
        QueryResult result;
        result = twitter.search(query);
        System.out.println("Total amount of tweets: " + result.getTweets().size());
        List<Status> tweets = result.getTweets();
        
        for (Status tweet : tweets) {
            System.out.println("@" + tweet.getUser().getScreenName() + " : " + tweet.getText());
        }
    } catch (TwitterException te) {
        te.printStackTrace();
        System.out.println("Failed to search tweets: " + te.getMessage());
    }

这里是错误:

sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Relevant discussions can be found on the Internet at:
    http://www.google.co.jp/search?q=d35baff5 or
    http://www.google.co.jp/search?q=1446302e
TwitterException{exceptionCode=[d35baff5-1446302e 43208640-747fd158 43208640-747fd158 43208640-747fd158], statusCode=-1, message=null, code=-1, retryAfter=-1, rateLimitStatus=null, version=3.0.5}
    at twitter4j.internal.http.HttpClientImpl.request(HttpClientImpl.java:177)
    at twitter4j.internal.http.HttpClientWrapper.request(HttpClientWrapper.java:61)
    at twitter4j.internal.http.HttpClientWrapper.get(HttpClientWrapper.java:81)
    at twitter4j.TwitterImpl.get(TwitterImpl.java:1929)
    at twitter4j.TwitterImpl.search(TwitterImpl.java:306)
    at jku.cc.servlets.TweetsAnalyzer.main(TweetsAnalyzer.java:38)
Caused by: 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(Unknown Source)
    at sun.security.ssl.SSLSocketImpl.fatal(Unknown Source)
    at sun.security.ssl.Handshaker.fatalSE(Unknown Source)
    at sun.security.ssl.Handshaker.fatalSE(Unknown Source)
    at sun.security.ssl.ClientHandshaker.serverCertificate(Unknown Source)
    at sun.security.ssl.ClientHandshaker.processMessage(Unknown Source)
    at sun.security.ssl.Handshaker.processLoop(Unknown Source)
    at sun.security.ssl.Handshaker.process_record(Unknown Source)
    at sun.security.ssl.SSLSocketImpl.readRecord(Unknown Source)
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake(Unknown Source)
    at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source)
    at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source)
    at sun.net.www.protocol.https.HttpsClient.afterConnect(Unknown Source)
    at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(Unknown Source)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream(Unknown Source)
    at java.net.HttpURLConnection.getResponseCode(Unknown Source)
    at sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(Unknown Source)
    at twitter4j.internal.http.HttpResponseImpl.<init>(HttpResponseImpl.java:34)
    at twitter4j.internal.http.HttpClientImpl.request(HttpClientImpl.java:141)
    ... 5 more
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(Unknown Source)
    at sun.security.validator.PKIXValidator.engineValidate(Unknown Source)
    at sun.security.validator.Validator.validate(Unknown Source)
    at sun.security.ssl.X509TrustManagerImpl.validate(Unknown Source)
    at sun.security.ssl.X509TrustManagerImpl.checkTrusted(Unknown Source)
    at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(Unknown Source)
    ... 20 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(Unknown Source)
    at java.security.cert.CertPathBuilder.build(Unknown Source)
    ... 26 more
Failed to search tweets: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

11
你好,请检查下面的网址。我相信这些将对你有所帮助。http://www.java-samples.com/showtutorial.php?tutorialid=210 https://confluence.atlassian.com/display/JIRAKB/Unable+to+Connect+to+SSL+Services+due+to+PKIX+Path+Building+Failed+sun.security.provider.certpath.SunCertPathBuilderException。你需要将你的SSL证书添加到Java信任库证书中(路径:jre/lib/security/cacerts)。 - sus007
4
我刚刚使用了这段 Java 代码,对于 https,请不要忘记将端口指定为 443。 Java 代码位于 https://github.com/escline/InstallCert/blob/master/InstallCert.java它将使用您的 CACERTS 文件,并将添加所有那些以及您输入的 URL 的当前证书。 在我的情况下,我将值硬编码为 host="mywebservice.uat.xyz.com"; port=443; passphrase="changeit".toCharArray();然后,程序会创建一个名为“jssecacerts”的新文件,其中将包含所有内容。将其重命名为“cacerts”并使用此文件。您就可以开始了。 - Reddymails
我只是将HTTPS替换为HTTP,它就能正常工作了。也许我的评论对某人有用。 - Oleksandr
当运行mvn时,添加此参数对我有帮助,因为它由于某种原因未使用JAVA_HOME中的默认Java信任存储:-"Djavax.net.ssl.trustStore"="C:\path\to\your\cacerts" - ADJenks
显示剩余2条评论
60个回答

4

对我来说,证书错误弹出是因为我在后台运行了Fiddler,这会干扰证书。它充当代理,所以关闭它并重新启动Eclipse。


即使关闭了所有应用程序,Eclipse 中仍然出现相同的问题。 - arvindwill
1
@Atihska,这真的解决了我的问题。谢谢你,非常感激。 - Janesh Kodikara
一样,我忘记了 Charles Proxy 正在运行,结果出现了那个错误。停止 Charles 后,一切正常工作了。 - Nicolas Raoul

3

目标:

  1. 使用https连接
  2. 验证SSL链
  3. 不要处理cacerts
  4. 在运行时添加证书
  5. 不要丢失cacerts中的证书

如何实现:

  1. 定义自己的密钥库
  2. 将证书放入密钥库中
  3. 使用我们自定义的类重新定义SSL默认上下文
  4. ???
  5. 获利

我的密钥库包装文件:

public class CertificateManager {

    private final static Logger logger = Logger.getLogger(CertificateManager.class);

    private String keyStoreLocation;
    private String keyStorePassword;
    private X509TrustManager myTrustManager;
    private static KeyStore myTrustStore;

    public CertificateManager(String keyStoreLocation, String keyStorePassword) throws Exception {
        this.keyStoreLocation = keyStoreLocation;
        this.keyStorePassword = keyStorePassword;
        myTrustStore = createKeyStore(keyStoreLocation, keyStorePassword);
    }

    public void addCustomCertificate(String certFileName, String certificateAlias)
            throws Exception {
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init((KeyStore) null);
        Certificate certificate = myTrustStore.getCertificate(certificateAlias);
        if (certificate == null) {
            logger.info("Certificate not exists");
            addCertificate(certFileName, certificateAlias);
        } else {
            logger.info("Certificate exists");
        }
        tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(myTrustStore);
        for (TrustManager tm : tmf.getTrustManagers()) {
            if (tm instanceof X509TrustManager) {
                setMytrustManager((X509TrustManager) tm);
                logger.info("Trust manager found");
                break;
            }
        }
    }

    private InputStream fullStream(String fname) throws IOException {
        ClassLoader classLoader = getClass().getClassLoader();
        InputStream resource = classLoader.getResourceAsStream(fname);
        try {
            if (resource != null) {
                DataInputStream dis = new DataInputStream(resource);
                byte[] bytes = new byte[dis.available()];
                dis.readFully(bytes);
                return new ByteArrayInputStream(bytes);
            } else {
                logger.info("resource not found");
            }
        } catch (Exception e) {
            logger.error("exception in certificate fetching as resource", e);
        }
        return null;
    }

    public static KeyStore createKeyStore(String keystore, String pass) throws Exception {
        try {
            InputStream in = CertificateManager.class.getClass().getResourceAsStream(keystore);
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(in, pass.toCharArray());
            logger.info("Keystore was created from resource file");
            return keyStore;
        } catch (Exception e) {
            logger.info("Fail to create keystore from resource file");
        }

        File file = new File(keystore);
        KeyStore keyStore = KeyStore.getInstance("JKS");
        if (file.exists()) {
            keyStore.load(new FileInputStream(file), pass.toCharArray());
            logger.info("Default keystore loaded");
        } else {
            keyStore.load(null, null);
            keyStore.store(new FileOutputStream(file), pass.toCharArray());
            logger.info("New keystore created");
        }
        return keyStore;
    }

    private void addCertificate(String certFileName, String certificateAlias) throws CertificateException,
            IOException, KeyStoreException, NoSuchAlgorithmException {
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        InputStream certStream = fullStream(certFileName);
        Certificate certs = cf.generateCertificate(certStream);
        myTrustStore.setCertificateEntry(certificateAlias, certs);
        FileOutputStream out = new FileOutputStream(getKeyStoreLocation());
        myTrustStore.store(out, getKeyStorePassword().toCharArray());
        out.close();
        logger.info("Certificate pushed");
    }

    public String getKeyStoreLocation() {
        return keyStoreLocation;
    }

    public String getKeyStorePassword() {
        return keyStorePassword;
    }
    public X509TrustManager getMytrustManager() {
        return myTrustManager;
    }
    public void setMytrustManager(X509TrustManager myTrustManager) {
        this.myTrustManager = myTrustManager;
    }
}

这个类将在必要时创建密钥库,并能够管理其中的证书。下面是SSL上下文的类:

public class CustomTrustManager implements X509TrustManager {

    private final static Logger logger = Logger.getLogger(CertificateManager.class);

    private static SSLSocketFactory socketFactory;
    private static CustomTrustManager instance = new CustomTrustManager();
    private static List<CertificateManager> register = new ArrayList<>();

    public static CustomTrustManager getInstance() {
        return instance;
    }

    private X509TrustManager defaultTm;

    public void register(CertificateManager certificateManager) {
        for(CertificateManager manager : register) {
            if(manager == certificateManager) {
                logger.info("Certificate manager already registered");
                return;
            }
        }
        register.add(certificateManager);
        logger.info("New Certificate manager registered");
    }

    private CustomTrustManager() {
        try {
            String algorithm = TrustManagerFactory.getDefaultAlgorithm();
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);

            tmf.init((KeyStore) null);
            boolean found = false;
            for (TrustManager tm : tmf.getTrustManagers()) {
                if (tm instanceof X509TrustManager) {
                    defaultTm = (X509TrustManager) tm;
                    found = true;
                    break;
                }
            }
            if(found) {
                logger.info("Default trust manager found");
            } else {
                logger.warn("Default trust manager was not found");
            }

            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, new TrustManager[]{this}, null);
            SSLContext.setDefault(sslContext);
            socketFactory = sslContext.getSocketFactory();
            HttpsURLConnection.setDefaultSSLSocketFactory(socketFactory);


            logger.info("Custom trust manager was set");
        } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
            logger.warn("Custom trust manager can't be set");
            e.printStackTrace();
        }
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        List<X509Certificate> out = new ArrayList<>();
        if (defaultTm != null) {
            out.addAll(Arrays.asList(defaultTm.getAcceptedIssuers()));
        }
        int defaultCount = out.size();
        logger.info("Default trust manager contain " + defaultCount + " certficates");
        for(CertificateManager manager : register) {
            X509TrustManager customTrustManager = manager.getMytrustManager();
            X509Certificate[] issuers = customTrustManager.getAcceptedIssuers();
            out.addAll(Arrays.asList(issuers));
        }
        logger.info("Custom trust managers contain " + (out.size() - defaultCount) + " certficates");
        X509Certificate[] arrayOut = new X509Certificate[out.size()];
        return out.toArray(arrayOut);
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain,
                                   String authType) throws CertificateException {
        for(CertificateManager certificateManager : register) {
            X509TrustManager customTrustManager = certificateManager.getMytrustManager();
            try {
                customTrustManager.checkServerTrusted(chain, authType);
                logger.info("Certificate chain (server) was aproved by custom trust manager");
                return;
            } catch (Exception e) {
            }
        }
        if (defaultTm != null) {
            defaultTm.checkServerTrusted(chain, authType);
            logger.info("Certificate chain (server) was aproved by default trust manager");
        } else {
            logger.info("Certificate chain (server) was rejected");
            throw new CertificateException("Can't check server trusted certificate.");
        }
    }

    @Override
    public void checkClientTrusted(X509Certificate[] chain,
                                   String authType) throws CertificateException {
        try {
            if (defaultTm != null) {
                defaultTm.checkClientTrusted(chain, authType);
                logger.info("Certificate chain (client) was aproved by default trust manager");
            } else {
                throw new NullPointerException();
            }
        } catch (Exception e) {
            for(CertificateManager certificateManager : register) {
                X509TrustManager customTrustManager = certificateManager.getMytrustManager();
                try {
                    customTrustManager.checkClientTrusted(chain, authType);
                    logger.info("Certificate chain (client) was aproved by custom trust manager");
                    return;
                } catch (Exception e1) {
                }
            }
            logger.info("Certificate chain (client) was rejected");
            throw new CertificateException("Can't check client trusted certificate.");
        }
    }

    public SSLSocketFactory getSocketFactory() {
        return socketFactory;
    }
}

这个类被制作成单例,因为只允许一个默认的SSL上下文。所以,现在的用法是:

            CertificateManager certificateManager = new CertificateManager("C:\\myapplication\\mykeystore.jks", "changeit");
            String certificatePath = "C:\\myapplication\\public_key_for_your_ssl_service.crt";
            try {
                certificateManager.addCustomCertificate(certificatePath, "alias_for_public_key_for_your_ssl_service");
            } catch (Exception e) {
                log.error("Can't add custom certificate");
                e.printStackTrace();
            }
            CustomTrustManager.getInstance().register(certificateManager);

可能,这些设置可能不起作用,因为我将证书文件放在资源文件夹中,所以我的路径不是绝对路径。但总的来说,它可以完美地工作。

1
available() 的双重误用在其自己的 Javadoc 中有明确的警告。 - user207421

3
如果您的仓库URL也可以在HTTP上工作且安全性不是一个问题,您可以转到%USERPROFILE%/.m2(通常位于那里),并将<repository><pluginRepository> URL中的HTTPS替换为HTTP。
例如,这样做:
<repository>
    <snapshots>
        <enabled>false</enabled>
    </snapshots>
    <id>central</id>
    <name>libs-release</name>
    <url>https://<artifactory>/libs-release</url>
</repository>

应该被替换为这个:

<repository>
    <snapshots>
        <enabled>false</enabled>
    </snapshots>
    <id>central</id>
    <name>libs-release</name>
    <url>https://<artifactory>/libs-release</url>
</repository>

有什么区别? - Lance Samaria

3
我使用自己的信任存储而不是 JRE 的信任存储,通过传递 arg -Djavax.net.ssl.trustStore=。无论信任存储中是否有证书,我都会收到此错误。对我来说问题在于传递参数行的顺序。当我将-Djavax.net.ssl.trustStore=& -Djavax.net.ssl.trustStorePassword=放在-Dspring.config.location= & -jar args之前时,我能够成功地通过 https 调用我的 rest 调用。

当我试图弄清楚如何让Maven正常工作时,这对我很有用。只需要-"Djavax.net.ssl.trustStore"=<path>。谢谢。 - ADJenks

3

如果您遇到以下异常问题:

问题:javax.net.ssl.SSLHandshakeException: PKIX路径构建失败......无法找到请求目标的有效认证路径。

原因:SSL握手失败,因为客户端没有所需的服务器证书。对于JAVA应用程序堆栈,它使用自己的证书存储,位于JRE或JDK位置中:

  1. JRE:jre/lib/security/cacerts
  2. JDK::\Program Files\Java\jdk-11.0.15.1\lib/security/cacerts

解决方案:您需要使用以下命令在JAVA设置的cacerts文件夹中注册证书:

keytool -import -alias {certificate-name} -keystore "{absolute-path-of-cacerts-directory}" -file {.cer-file-location}

此实用程序使用默认密码"changeit"。在提示时,请输入此密码。

如果未识别keytool,请注意,该.exe是JRE或JDK bin文件夹的一部分,因此将该路径添加到本地计算机环境变量"Path"中应该可以解决此问题。


3

我曾在Windows Server 2016上使用Java 8解决了这个问题,方法是从pkcs12存储导入证书到cacerts密钥库中。

pkcs12存储路径:
C:\Apps\pkcs12.pfx

Java cacerts路径:
C:\Program Files\Java\jre1.8.0_151\lib\security\cacerts

keytool路径:
C:\Program Files\Java\jre1.8.0_151\bin

进入带有keytool的文件夹(作为管理员),将证书从pkcs12导入到cacerts的命令如下:
keytool -v -importkeystore -srckeystore C:\Apps\pkcs12.pfx -srcstoretype PKCS12 -destkeystore "C:\Program Files\Java\jre1.8.0_151\lib\security\cacerts" -deststoretype JKS

您将被提示:
1. 输入目标密钥库密码(cacerts密码,默认为“changeit”)
2. 输入源密钥库密码(pkcs12密码)


要使更改生效,重新启动服务器机器(或仅重启JVM)。


1
我在 Mac 上使用了这种方法,它很顺利地运行了。我的证书当然是自签名的,并且是在 Windows adfs 服务器上生成的 .pfx 文件,我将其导入了。我的应用程序使用 Java 10。谢谢。 - Shashikant Soni
Java cacerts 的路径应该是:C:\Program Files\Java\jdk1.8.0_281\jre\lib\security\cacerts,而不是 Java\jre1.8...。应该使用 Java\jdk1.8...\jre\ - Asad Shakeel

3

我遇到了同样的问题,但是我在我的Linux机器上更新了错误的JRE。很有可能Tomcat正在使用不同的JRE,而你的CLI提示配置为使用不同的JRE。

确保你选择了正确的JRE。

步骤#1:

ps -ef | grep tomcat

你将会看到类似以下的内容:
root     29855     1  3 17:54 pts/3    00:00:42 /usr/java/jdk1.7.0_79/jre/bin/java 

现在使用这个:

keytool -import -alias example -keystore  /usr/java/jdk1.7.0_79/jre/lib/security/cacerts -file cert.cer
PWD: changeit

*按照以下方式可以生成.cer文件(或者您可以使用自己的文件):

openssl x509 -in cert.pem -outform pem -outform der -out cert.cer

2

如果您的主机位于防火墙/代理后,请在cmd中使用以下命令:

Original Answer翻译成"最初的回答"

keytool -J-Dhttps.proxyHost=<proxy_hostname> -J-Dhttps.proxyPort=<proxy_port> -printcert -rfc -sslserver <remote_host_name:remote_ssl_port>

请将配置的HTTP代理服务器中的<proxy_hostname><proxy_port>替换为实际值。将<remote_host_name:remote_ssl_port>替换为具有证书问题的远程主机(基本上是URL)和端口之一。

复制最后打印出来的证书内容(包括开始和结束证书)。将其粘贴到文本文件中,并将其扩展名改为.crt。现在使用Java keytool命令将此证书导入到cacerts中,它应该可以工作。

keytool -importcert -file <filename>.crt -alias randomaliasname -keystore %JAVA_HOME%/jre/lib/security/cacerts -storepass changeit

这篇帖子真的救了我的一天。在添加代理详细信息并调整上述设置之前,我无法解决此问题。结果奏效了。 - Valath

2

1-首先,将您的crt文件导入到{JAVA_HOME}/jre/security/cacerts中,如果您仍然遇到此异常,请更改您的jdk版本。例如从jdk1.8.0_17更改为jdk1.8.0_231


2
这个问题主要是由于我们系统中的应用程序阻止了请求,以保护我们免受网络攻击。
在我的情况下,我尝试访问一个可信的API服务,所以我通过将zscaler证书添加到我的jdk 17的lib/security/cacerts文件中来解决它,该jdk正在IDE中使用,通过在cmd中执行以下keytool命令。
keytool -keystore  "C:\dev\java\jdk-17.0.7\lib\security\cacerts" -import -alias zscalarCert -file "C:\Users\theri\Documents\zscalar-cert.crt"

password:- changeit

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