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

能否基于现有Java SSL证书提取代码获取可接受客户端证书CA名称

实现方案

可以基于现有代码完成可接受客户端证书CA名称的获取,核心逻辑是捕捉TLS握手阶段服务器发送CertificateRequest报文时携带的信任CA列表,通过自定义X509KeyManager即可拿到该数据。

修改后的完整代码

import javax.net.ssl.*;
import javax.security.auth.x500.X500Principal;
import java.io.FileOutputStream;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;

public class ExtractCertificate {

    public static void main(String[] args) throws Exception {

        if (args.length != 2) {
            System.out.println("Usage: java -jar sslextractor.jar host port");
            System.exit(1);
        }

        String host = args[0];
        Integer port = Integer.parseInt(args[1]);

        final List<X509Certificate> certs = new ArrayList<>();
        // 新增:存储服务器接受的客户端证书CA列表
        final List<X500Principal> acceptableClientCAs = new ArrayList<>();

        X509TrustManager trust = new X509TrustManager() {
            public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
                System.out.println(s);
            }
            public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
                for (X509Certificate cert : x509Certificates) {
                    System.out.println("Loading certificate " + cert.getSubjectDN() + " issued by: " + cert.getIssuerDN());
                    certs.add(cert);
                }
            }
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        };

        // 新增:自定义KeyManager,获取服务器返回的可接受客户端CA列表
        X509ExtendedKeyManager keyManager = new X509ExtendedKeyManager() {
            @Override
            public String[] getClientAliases(String s, Principal[] principals) {
                return new String[0];
            }
            @Override
            public String chooseClientAlias(String[] strings, Principal[] issuers, Socket socket) {
                // 服务器返回的可接受CA列表就是issuers参数
                if (issuers != null) {
                    for (Principal issuer : issuers) {
                        acceptableClientCAs.add((X500Principal) issuer);
                    }
                }
                return null;
            }
            @Override
            public String[] getServerAliases(String s, Principal[] principals) {
                return new String[0];
            }
            @Override
            public String chooseServerAlias(String s, Principal[] principals, Socket socket) {
                return null;
            }
            @Override
            public X509Certificate[] getCertificateChain(String s) {
                return new X509Certificate[0];
            }
            @Override
            public PrivateKey getPrivateKey(String s) {
                return null;
            }
        };

        SSLContext sslContext = SSLContext.getInstance("TLS");
        // 初始化时传入自定义的KeyManager
        sslContext.init(new KeyManager[]{keyManager}, new TrustManager[]{trust}, null);
        SSLSocket socket = (SSLSocket) sslContext.getSocketFactory().createSocket(host, port);

        socket.startHandshake(); // 主动触发握手,确保回调执行
        socket.getSession().getPeerCertificates();
        socket.close();

        // 打印可接受的客户端CA列表
        System.out.println("\nAcceptable client certificate CA names");
        for (X500Principal ca : acceptableClientCAs) {
            // 替换为ca.getName(X500Principal.RFC1779)可输出openssl常用的斜杠分隔格式
            System.out.println(ca.getName());
        }

        // 原有服务器证书序列化逻辑
        for (X509Certificate cert : certs) {
            String outputFile = cert.getSubjectDN().getName().replaceAll("[^a-zA-Z0-9-=_\\.]", "_") + ".cer";
            System.out.println("\nSerializing certificate to: " + outputFile);
            FileOutputStream certfos = new FileOutputStream(outputFile);
            certfos.write(cert.getEncoded());
            certfos.close();
        }
    }
}

说明

  • 只有当目标服务器开启了双向TLS认证时,才会返回可接受的客户端CA列表,否则列表为空,和openssl s_client -showcerts的输出逻辑一致
  • 输出的CA名称默认是RFC2253标准格式,可通过代码注释里的修改方式调整为openssl常用的斜杠分隔格式

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 10:45:06