解决 javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path buildin

详细分析Java中访问https请求exception(SSLHandshakeException, SSLPeerUnverifiedException)的原因及解决方法。
1、现象
用JAVA测试程序访问下面两个链接。
https链接一:web服务器为jetty,后台语言为java。
https链接二:web服务器为nginx,后台语言为php。
链接一能正常访问,访问链接二报异常,且用HttpURLConnection和apache的HttpClient两种不同的api访问异常信息不同,具体如下:
(1) 用HttpURLConnection访问,测试代码如下:

HttpURLConnection访问https

异常信息为:

1

javax.net.ssl.SSLPeerUnverifiedException: No peer certificate

(2) 用apache的HttpClient访问,测试代码如下:

HttpClient访问https

异常信息为:

1

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

2、原因分析
需要快速寻求答案的可直接看第3部分 解决方式,这部分详细分析原因。
google发现stackoverflow上不少人反应,twitter和新浪微博的api也会报这个异常,不少人反映客户端需要导入证书,其实大可不必,如果要导证书的话,用户不得哭了。。

从上面的情况可以看出,用jetty做为容器是能正常访问的,只是当容器为nginx时才会异常。

配合后台开发调试了很久,开始以为是cipher suite的问题,为此特地把
ssl_ciphers EDH-RSA-DES-CBC3-SHA;
加入了nginx的配置中,后来发现依然无效。stackoverflow发现,如下代码是能正常访问上面异常的https url

HttpURLConnection访问https并相信所有证书

public static String httpGet(String httpUrl) {
			BufferedReader input = null;
			StringBuilder sb = null;
			URL url = null;
			HttpURLConnection con = null;
			try {
			url = new URL(httpUrl);
			try {
			// trust all hosts
			trustAllHosts();
			HttpsURLConnection https = (HttpsURLConnection)url.openConnection();
			if (url.getProtocol().toLowerCase().equals("https")) {
			https.setHostnameVerifier(DO_NOT_VERIFY);
			con = https;
			} else {
			con = (HttpURLConnection)url.openConnection();
			}
			input = new BufferedReader(new InputStreamReader(con.getInputStream()));
			sb = new StringBuilder();
			String s;
			while ((s = input.readLine()) != null) {
			sb.append(s).append("\n");
			}
			} catch (IOException e) {
			e.printStackTrace();
			}
			} catch (MalformedURLException e1) {
			e1.printStackTrace();
			} finally {
			// close buffered
			if (input != null) {
			try {
			input.close();
			} catch (IOException e) {
			e.printStackTrace();
			}}

			// disconnecting releases the resources held by a connection so they may be closed or reused
			if (con != null) {
			con.disconnect();
			}
			}
			return sb == null ? null : sb.toString();
			}

			final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
			public boolean verify(String hostname, SSLSession session) {
			return true;
			}
			};
			/**
			* Trust every server - dont check for any certificate
			*/
			private static void trustAllHosts() {
			final String TAG = "trustAllHosts";
			// Create a trust manager that does not validate certificate chains
			TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
			public java.security.cert.X509Certificate[] getAcceptedIssuers() {
			return new java.security.cert.X509Certificate[] {};
			}
			public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
			Log.i(TAG, "checkClientTrusted");
			}
			public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
			Log.i(TAG, "checkServerTrusted");
			}
			} };
			// Install the all-trusting trust manager
			try {
			SSLContext sc = SSLContext.getInstance("TLS");
			sc.init(null, trustAllCerts, new java.security.SecureRandom());
			HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
			} catch (Exception e) {
			e.printStackTrace();
			}
			}

可以看出其中与之前的HttpsURLConnection测试代码主要的不同就是加入了

1

trustAllHosts();

1

https.setHostnameVerifier(DO_NOT_VERIFY);

表示相信所有证书,并且所有host name验证返回true,这样就能定位到之前的异常是证书验证不通过的问题了。

在上面checkServerTrusted函数中添加断点,查看X509Certificate[] chain的值,即证书信息,发现访问两个不同链接X509Certificate[] chain值有所区别,nginx传过来证书信息缺少了startssl 的ca证书,证书如下:

至此原因大白:
JAVA的证书库里已经带了startssl ca证书,而nginx默认不带startssl ca证书,这样JAVA端访问nginx为容器的https url校验就会失败,jetty默认带startssl ca证书,所以正常
PS:后来对windows和mac下java访问https也做了测试,发现mac上的jdk缺省不带startssl ca证书所以能访问通过,而加上startssl ca证书后同android一样访问不通过。而windows上的jdk缺省带startssl ca证书同android一样访问失败。

3、解决方式
上面的分析中已经介绍了一种解决方法即客户端相信所有证书,不过这种方式只是规避了问题,同时也给客户端带来了风险,比较合适的解决方式是为nginx添加startssl ca证书

/*
 * 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);
    }
    }
 
}

把上面这段代码copy出来,什么都不要改,包括package名,没有就不要加了,我是放到桌面上新建了一个跟类名相同的.java文件,然后用

编译:javac InstallCert.java 
运行:java InstallCert Java请求出错的站点URL(如:www.baidu.com)

会生成一个jssecacerts的文件,会看到如下信息:

java InstallCert ecc.fedora.redhat.com
Loading KeyStore /usr/jdk/instances/jdk1.5.0/jre/lib/security/cacerts...
Opening connection to ecc.fedora.redhat.com:443...
Starting SSL handshake...
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 com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:150)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1476)
at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:174)
at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:168)
at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:846)
at com.sun.net.ssl.internal.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:106)
at com.sun.net.ssl.internal.ssl.Handshaker.processLoop(Handshaker.java:495)
at com.sun.net.ssl.internal.ssl.Handshaker.process_record(Handshaker.java:433)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:815)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1025)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1038)
at InstallCert.main(InstallCert.java:63)
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(PKIXValidator.java:221)
at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:145)
at sun.security.validator.Validator.validate(Validator.java:203)
at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:172)
at InstallCert$SavingTrustManager.checkServerTrusted(InstallCert.java:158)
at com.sun.net.ssl.internal.ssl.JsseX509TrustManager.checkServerTrusted(SSLContextImpl.java:320)
at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:839)
... 7 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:236)
at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:194)
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:216)
... 13 more
Server sent 2 certificate(s):
1 Subject CN=ecc.fedora.redhat.com, O=example.com, C=US
   Issuer CN=Certificate Shack, O=example.com, C=US
   sha1    2e 7f 76 9b 52 91 09 2e 5d 8f 6b 61 39 2d 5e 06 e4 d8 e9 c7 
   md5     dd d1 a8 03 d7 6c 4b 11 a7 3d 74 28 89 d0 67 54
2 Subject CN=Certificate Shack, O=example.com, C=US
   Issuer CN=Certificate Shack, O=example.com, C=US
   sha1    fb 58 a7 03 c4 4e 3b 0e e3 2c 40 2f 87 64 13 4d df e1 a1 a6 
   md5     72 a0 95 43 7e 41 88 18 ae 2f 6d 98 01 2c 89 68
Enter certificate to add to trusted keystore or 'q' to quit: [1]

3.输入1,然后直接回车,会在相应的目录下产生一个名为‘jssecacerts’的证书。将证书copy到$JAVA_HOME/jre/lib/security目录下,或者通过以下方式

1.查看javahome的路径:

[root@testomsapp logs]# echo $JAVA_HOME

/usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0.x86_64

 

2.查看当前已加入信任的证书,需要输入密码changeit

keytool -list -keystore /usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0.x86_64/jre/lib/security/cacerts

/usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0.x86_64/是javahome的路径。

3.重浏览器导出证书,浏览器访问你要访问的https URL, 第一次访问会有提示安装证书,安装完成后,ie,火狐点击地址栏中的 锁 查看证书。ie使用工具->internet选项->内容-证书,导出你的证书,格式是DER二进制编码(X.509).cer. 将证书传到Linux服务器。

4.导入证书,需要输入密码changeit

keytool -import -alias LL1 -keystore /usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0.x86_64/jre/lib/security/cacerts -file /usr/local/tomcat6/LL1.cer

6.查看证书是否已经导入成功,还是用第二步查看,会发现证书的数量多了一个新配置的。

7.导入完成后,重启应用,java程序就不报错了。

注: 只有第3步和第4步是真正导入证书的,其他可以不要。

安装证书与查看证书默认密码是changeit

另详见:https://blog.csdn.net/qq_34836433/article/details/78539009 

  • 48
    点赞
  • 124
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 17
    评论
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path buildin是一个SSL握手异常,它通常发生在与服务器建立安全连接时。这个异常的原因是验证服务器证书时出现了问题,可能是由于证书不受信任或证书链不完整导致的。解决这个问题的方法有几种。 首先,你可以检查你的Java运行环境是否缺少根证书。如果是这样,你可以手动将缺少的根证书添加到Java的信任库中。你可以使用keytool命令来执行这个操作。 另外,你也可以尝试更新你的Java运行环境到最新版本,因为新版本的Java可能已经包含了最新的根证书。 此外,你还可以尝试禁用证书验证,但这并不是一个推荐的解决方法,因为它会降低连接的安全性。 最后,如果你是在使用第三方库或框架,你可以查看它们的文档或社区,看看是否有关于解决这个问题的特定指导。 总之,解决javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path buildin异常的方法包括添加缺少的根证书、更新Java运行环境、禁用证书验证等。希望这些方法能帮助你解决这个问题。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* [javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorExcepti](https://blog.csdn.net/figosoar/article/details/115211179)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path buildin](https://blog.csdn.net/chaishen10000/article/details/82992291)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [解决javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path ...](https://blog.csdn.net/qq_54642035/article/details/126408253)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

才 神

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值