Java SSLHandshakeException:使用证书密钥调用HTTPS API失败
我之前也踩过这个SSL握手的坑,结合你给出的代码和错误信息来看,问题出在信任证书链的校验环节——你的Java客户端找不到能信任目标API服务器证书的根证书。下面给你几个针对性的解决方案,一步步来排查:
方案一:将目标API的服务器根证书导入JDK信任库
你之前尝试把PFX安装到cacerts里,但PFX包含的是你的客户端证书和私钥,而我们需要的是能验证服务端证书的根证书。正确的步骤应该是:
用OpenSSL导出目标API的服务器证书:
openssl s_client -connect your-api-domain:443 < /dev/null | openssl x509 -outform PEM > server-root-cert.pem把
your-api-domain换成实际的API域名。用keytool将证书导入JDK默认的cacerts信任库:
keytool -importcert -file server-root-cert.pem -keystore /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/security/cacerts -alias my-api-trust-cert输入默认密码
changeit,然后按提示输入yes确认信任该证书。
完成后重启你的Java客户端,再尝试发起请求。
方案二:修改代码,复用PFX作为信任库
如果你的PFX文件本身包含了能验证服务端证书的根证书链(比如服务端和客户端用的是同一套CA签发的证书),可以修改代码,让SSLContext同时把PFX当作密钥库(用于客户端认证)和信任库(用于验证服务端证书),这样就不用依赖JDK默认的cacerts了。修改后的enableSSL方法如下:
private static final String keyStoreFile = "/pathTocert/mycert.pfx"; private static final String keyStorePassword = "changeit"; public static SSLSocketFactory enableSSL() { SSLContext context = null; InputStream keyInput = null; try { // 加载PFX作为密钥库(包含客户端私钥和证书链) KeyStore keyStore = KeyStore.getInstance("PKCS12"); keyInput = new FileInputStream(new File(keyStoreFile)); keyStore.load(keyInput, keyStorePassword.toCharArray()); // 初始化密钥管理器,用于客户端认证 KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); keyManagerFactory.init(keyStore, keyStorePassword.toCharArray()); // 直接用PFX作为信任库,这样会信任其中包含的所有证书 TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(keyStore); // 初始化SSLContext context = SSLContext.getInstance("TLS"); context.init(keyManagerFactory.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom()); return context.getSocketFactory(); } catch (Exception e) { e.printStackTrace(); } finally { // 记得关闭输入流 if (keyInput != null) { try { keyInput.close(); } catch (IOException e) { e.printStackTrace(); } } } return null; }
这种方式更灵活,尤其是在不能修改JDK默认信任库的场景下(比如生产环境)。
方案三:检查PFX的证书链完整性
有时候PFX文件里的证书链不完整,导致客户端无法验证服务端证书。你可以用keytool查看PFX里的证书信息:
keytool -list -v -keystore /pathTocert/mycert.pfx -storetype PKCS12
查看输出里的Certificate chain部分,如果只有客户端证书,没有上级的中间证书或根证书,那你需要把缺失的根证书导入到PFX中,或者单独导入到信任库。
小提示
生产环境中不建议修改JDK默认的cacerts,最好创建一个自定义的信任库,把需要的证书导入进去,然后在代码里加载这个自定义信任库(类似方案二的思路,只是用单独的JKS文件作为信任库)。
内容的提问来源于stack exchange,提问作者Abhinav

