You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java证书程序化导入及SSL握手失败问题排查求助

问题:程序化导入SSL证书后仍出现PKIX路径构建失败错误

目标与手动操作流程

编写程序的目标是复现以下手动获取SSL证书并导入JRE cacerts证书库的流程:

keytool -printcert -sslserver {host}:{port} -rfc >> {host}.crt
keytool -importcert -alias {host} -keystore /usr/local/openjdk-8/jre/lib/security/cacerts -file {host} -storepass changeit -noprompt

实现代码

程序的核心代码如下:

InputStream in = new FileInputStream(keystore);
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(in, passphrase);
in.close();
 
SSLContext context = SSLContext.getInstance("TLS");

TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(ks);
X509TrustManager defaultTrustManager = null; //(X509TrustManager) tmf.getTrustManagers()[0];
TrustManager[] tms = tmf.getTrustManagers();
for (TrustManager tm : tms) {
    if (tm instanceof X509TrustManager) {
        defaultTrustManager = (X509TrustManager) tm;
        break;
    }
}
if (defaultTrustManager == null) {
    if (isLog) log.error("No Trust Manager found!");
    return false;
}

SaveTrustManager tm = new SaveTrustManager(defaultTrustManager);

context.init(null, new TrustManager[]{tm}, null);

SSLSocketFactory factory = context.getSocketFactory();

if (isLog) log.info("Opening connection -> {}:{} ...", host, port);
 
// Initiate socket
SSLSocket socket = null;
Socket proxiedSocket = null;
if (proxyHost != null) {
    if (isLog) log.info("Using Proxy -> {}:{} ...", proxyHost, proxyPort);

    try {
        if (proxyUser == null || proxyPass == null) {
            Proxy prx = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort));
            proxiedSocket = new Socket(prx);
            proxiedSocket.connect(new InetSocketAddress(host, port));
            socket = (SSLSocket) factory.createSocket(proxiedSocket, host, port, true);
        } else {
            String proxyUserPass = String.format("%s:%s", proxyUser, proxyPass);                        
            String proxyConnect = "CONNECT " + host + ":" + port + " HTTP/1.0\r\n"
                     + "Proxy-Authorization: Basic "
                        + Base64.getEncoder().encodeToString(proxyUserPass.getBytes(ASCII7)).replace("\r\n", "") + "\r\n"
                     + "Connection: close\r\n"
                     + "\r\n";
            if (isLog) log.info("Socket: {}", proxyConnect);
            
            proxiedSocket = new Socket(proxyHost, proxyPort);
            proxiedSocket.getOutputStream().write(proxyConnect.getBytes(ASCII7));
            proxiedSocket.getOutputStream().flush();
            
            this.readSocketConnection(proxiedSocket);
            
            proxiedSocket.getOutputStream().close();
            
            socket = (SSLSocket) factory.createSocket(proxiedSocket, host, port, true);
        }
    } catch (Throwable t) {
        t.printStackTrace();
        if (isLog) log.info("Proxy Error: {}", t.getMessage());
    }
} else {
    socket = (SSLSocket) factory.createSocket(host, port);
}

socket.setSoTimeout(10000);
try {
    if (isLog) log.info("Initiating SSL handshake...");
    socket.startHandshake();
    socket.close();
    if (proxiedSocket != null) proxiedSocket.close();
    if (isLog) log.info("Certificate is already trusted...");
} catch (SSLException e) {
    //e.printStackTrace(System.out);
    success = false;
    if (isLog) log.warn("Socket Warning: {}", e.getMessage());
}
 
X509Certificate[] chain = tm.chain;
if (chain == null) {
    log.error("Could not obtain server certificate chain");
    return false;
}

if (isLog) log.info("Server sent " + chain.length + " certificate(s):");
 
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
MessageDigest md5 = MessageDigest.getInstance("MD5");
for (int i = 0; i < chain.length; i++) {
    X509Certificate cert = chain[i];
    
    try {
        cert.checkValidity();
        if (isLog) log.info("\tValid certificate...");
    } catch (Throwable t) {
        log.info("\tCertificate invalid!");
        continue;
    }
    
    sha1.update(cert.getEncoded());
    md5.update(cert.getEncoded());
    if (isLog) {
        log.info("\tIndex: {} Subject: {}", (i + 1), cert.getSubjectDN());
        log.info("\tIssuer: {}", cert.getIssuerDN());
        log.info("\tSHA-1: {}", toHexString(sha1.digest()));
        log.info("\tMD5: {}", toHexString(md5.digest()));
    }
    
    String alias = host + "-" + (i + 1);
    ks.setCertificateEntry(alias, cert);

    if (isLog) {
        log.info("{}", cert);
        log.info("Added certificate to keystore '{}' using alias '{}'", keystore, alias);
    }
    
}

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

异常现象

程序在本地及其他虚拟机上均可正常运行,但在一台特定的Ubuntu 20(Debian)虚拟机上运行时,出现以下错误:

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

额外异常细节:

  • 程序化下载的证书转换为PEM格式后,与keytool导出的证书完全一致
  • 即使通过程序化方式导入keytool手动下载的PEM文件,使用restTemplate发起请求时仍会出现上述相同错误

已执行的排查操作

  • 启用-Djavax.net.debug=ssl,handshake参数获取调试信息,未找到明确原因
  • 通过wget/curl/openssl可正常访问目标HTTPS站点
  • 确认该虚拟机未使用代理
  • 已为cacerts文件设置全权限
  • 验证目标站点返回的3条证书链中的1条,与keytool导出的一致
  • 程序运行环境为docker化的tomcat:9.0.35-jdk8-openjdk

内容的提问来源于stack exchange,提问作者Francesco Durante

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 11:40:36