能否基于现有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
相关产品推荐
相关产品推荐

