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

Java使用PEM文件发送HTTPS请求遇PKIX路径构建失败求助

解决Apache HttpClient SSLHandshakeException: PKIX路径构建失败问题

你的问题出在信任链没有正确加载:curl命令通过--cacert file.pem指定了包含完整信任链的证书文件,但你的Java代码只加载了客户端证书和私钥,没有把PEM里的CA信任证书添加到SSL上下文的信任管理器中,导致SSL握手时无法验证服务端证书的合法性,抛出PKIX路径错误。

下面提供两种解决方案,推荐第二种(更简洁可靠):


方案一:直接使用你生成的PKCS12文件(推荐)

你已经通过OpenSSL生成了包含客户端证书、私钥和信任链的file.p12文件,直接加载这个文件可以避免手动解析PEM的繁琐,代码更简洁:

import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.DnsResolver;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.impl.conn.SystemDefaultDnsResolver;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyStore;

public class Main {
    public static void main(String[] args) throws Exception {
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();

        // 加载PKCS12文件,里面包含客户端证书、私钥和信任链
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        try (InputStream is = Files.newInputStream(Paths.get("file.p12"))) {
            keyStore.load(is, "password".toCharArray());
        }

        // 初始化KeyManager(用于客户端证书认证)
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(keyStore, "password".toCharArray());
        KeyManager[] km = kmf.getKeyManagers();

        // 初始化TrustManager(用于验证服务端证书,使用PKCS12里的信任链)
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(keyStore);
        TrustManager[] tm = tmf.getTrustManagers();

        // 构建SSL上下文
        SSLContext context = SSLContext.getInstance("TLS");
        context.init(km, tm, null);

        // 自定义DNS解析,对应curl的--resolve参数
        DnsResolver dnsResolver = new SystemDefaultDnsResolver() {
            @Override
            public InetAddress[] resolve(final String host) throws UnknownHostException {
                if (host.equalsIgnoreCase("destinationHost.com")) {
                    return new InetAddress[]{InetAddress.getByName("100.100.100.100")};
                } else {
                    return super.resolve(host);
                }
            }
        };

        // 配置连接管理器和Socket工厂
        SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(context);
        BasicHttpClientConnectionManager connManager = new BasicHttpClientConnectionManager(
                RegistryBuilder.<ConnectionSocketFactory>create()
                        .register("http", PlainConnectionSocketFactory.getSocketFactory())
                        .register("https", socketFactory)
                        .build(),
                null, null, dnsResolver
        );

        httpClientBuilder.setConnectionManager(connManager);
        CloseableHttpClient client = httpClientBuilder.build();

        // 执行请求
        HttpGet httpGet = new HttpGet("https://destinationHost.com/api");
        httpGet.addHeader("Content-Type", "text/plain"); // 对应curl的-H参数
        try (CloseableHttpResponse execute = client.execute(httpGet)) {
            int statusCode = execute.getStatusLine().getStatusCode();
            System.out.println("请求状态码:" + statusCode);
            // 这里可以处理响应内容
        }
    }
}

方案二:手动解析PEM文件加载证书和信任链

如果你坚持要直接使用PEM文件,需要修改代码提取所有CA信任证书并添加到信任管理器中:

关键修改点:

  1. 新增方法提取PEM中的所有证书
  2. 将除客户端证书外的其他证书加载到信任KeyStore
  3. 初始化SSLContext时同时传入KeyManager和TrustManager

完整代码:

import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.DnsResolver;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.impl.conn.SystemDefaultDnsResolver;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.xml.bind.DatatypeConverter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) throws Exception {
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        SSLContext context = SSLContext.getInstance("TLS");
        byte[] certAndKey = Files.readAllBytes(Paths.get("file.pem"));

        // 提取所有证书(客户端证书+信任链CA证书)
        List<byte[]> allCertBytes = parseAllDERFromPEM(certAndKey, "-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----");
        // 提取私钥
        byte[] keyBytes = parseDERFromPEM(certAndKey, "-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----");

        // 第一个证书是客户端证书
        X509Certificate clientCert = generateCertificateFromDER(allCertBytes.get(0));
        RSAPrivateKey key = generatePrivateKeyFromDER(keyBytes);

        // 加载客户端证书和私钥到KeyStore(用于客户端认证)
        KeyStore keyStore = KeyStore.getInstance("JKS");
        keyStore.load(null);
        keyStore.setCertificateEntry("client-cert", clientCert);
        keyStore.setKeyEntry("client-key", key, "password".toCharArray(), new Certificate[]{clientCert});

        // 加载信任链CA证书到TrustStore(用于验证服务端证书)
        KeyStore trustStore = KeyStore.getInstance("JKS");
        trustStore.load(null);
        for (int i = 1; i < allCertBytes.size(); i++) {
            X509Certificate caCert = generateCertificateFromDER(allCertBytes.get(i));
            trustStore.setCertificateEntry("ca-" + i, caCert);
        }

        // 初始化KeyManager和TrustManager
        KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
        kmf.init(keyStore, "password".toCharArray());
        KeyManager[] km = kmf.getKeyManagers();

        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(trustStore);
        TrustManager[] tm = tmf.getTrustManagers();

        // 初始化SSL上下文
        context.init(km, tm, null);

        // 自定义DNS解析(保持不变)
        DnsResolver dnsResolver = new SystemDefaultDnsResolver() {
            @Override
            public InetAddress[] resolve(final String host) throws UnknownHostException {
                if (host.equalsIgnoreCase("destinationHost.com")) {
                    return new InetAddress[]{InetAddress.getByName("100.100.100.100")};
                } else {
                    return super.resolve(host);
                }
            }
        };

        SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(context);
        BasicHttpClientConnectionManager connManager = new BasicHttpClientConnectionManager(
                RegistryBuilder.<ConnectionSocketFactory>create()
                        .register("http", PlainConnectionSocketFactory.getSocketFactory())
                        .register("https", socketFactory)
                        .build(),
                null, null, dnsResolver
        );

        httpClientBuilder.setConnectionManager(connManager);
        CloseableHttpClient client = httpClientBuilder.build();

        HttpGet httpGet = new HttpGet("https://destinationHost.com/api");
        httpGet.addHeader("Content-Type", "text/plain");
        try (CloseableHttpResponse execute = client.execute(httpGet)) {
            int statusCode = execute.getStatusLine().getStatusCode();
            System.out.println("请求状态码:" + statusCode);
            assert statusCode == 200;
        }
    }

    // 提取单个DER块
    private static byte[] parseDERFromPEM(byte[] pem, String beginDelimiter, String endDelimiter) {
        String data = new String(pem);
        String[] tokens = data.split(beginDelimiter);
        tokens = tokens[1].split(endDelimiter);
        return DatatypeConverter.parseBase64Binary(tokens[0].trim());
    }

    // 提取所有DER块(支持多个证书)
    private static List<byte[]> parseAllDERFromPEM(byte[] pem, String beginDelimiter, String endDelimiter) {
        String data = new String(pem);
        String[] tokens = data.split(beginDelimiter);
        List<byte[]> derList = new ArrayList<>();
        // 跳过第一个空元素(split后第一个是begin之前的内容)
        for (int i = 1; i < tokens.length; i++) {
            String[] parts = tokens[i].split(endDelimiter);
            if (parts.length > 0) {
                derList.add(DatatypeConverter.parseBase64Binary(parts[0].trim()));
            }
        }
        return derList;
    }

    private static RSAPrivateKey generatePrivateKeyFromDER(byte[] keyBytes) throws InvalidKeySpecException, NoSuchAlgorithmException {
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
        java.security.KeyFactory factory = java.security.KeyFactory.getInstance("RSA");
        return (RSAPrivateKey) factory.generatePrivate(spec);
    }

    private static X509Certificate generateCertificateFromDER(byte[] certBytes) throws CertificateException {
        CertificateFactory factory = CertificateFactory.getInstance("X.509");
        return (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(certBytes));
    }
}

为什么原来的代码会失败?

你之前的代码中,SSLContext.init(km, null, null)的第二个参数传了null,这会让Java使用系统默认的信任库(比如JRE的cacerts文件),而系统默认信任库不包含你PEM文件里的自定义CA证书,所以SSL握手时无法验证服务端证书的路径,导致PKIX错误。

通过自定义TrustManager并加载你的CA信任证书,就能让Java信任服务端的证书,解决握手问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 03:53:15