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

Apache HttpClient 4.5.14证书SAN匹配问题及自定义验证需求

Apache HttpClient 4.5.14 证书SAN匹配问题解决方案

问题背景

使用Apache HttpClient 4.5.14时遇到证书匹配错误:

证书<sub1.sub2.sub3.domain.com>与任一主题备用名称(SAN)均不匹配:[ *.sub3.domain.com, SAN2, SAN3 ]

原因是默认证书验证遵循RFC 6125标准,通配符*仅支持匹配单级子域,无法覆盖多级子域场景。


1. 如何获取错误中显示的正确SAN列表?

你之前从本地信任库证书获取SAN失败,是因为实际握手使用的是服务器返回的证书,而非本地信任库中的副本。正确做法是从SSLSession中提取服务器端证书:

在自定义HostnameVerifier的verify方法中,通过session.getPeerCertificates()获取服务器证书,再解析SAN字段:

@Override
public boolean verify(String hostname, SSLSession session) {
    try {
        // 获取服务器返回的叶证书
        X509Certificate serverCert = (X509Certificate) session.getPeerCertificates()[0];
        Collection<List<?>> sanEntries = serverCert.getSubjectAlternativeNames();
        
        if (sanEntries != null) {
            for (List<?> entry : sanEntries) {
                // 类型标识为2的是DNS类型SAN
                if ((Integer) entry.get(0) == 2) {
                    String san = (String) entry.get(1);
                    // 此处可拿到错误提示中显示的SAN列表项
                    System.out.println("服务器证书SAN: " + san);
                }
            }
        }
    } catch (SSLException e) {
        e.printStackTrace();
    }
    // 后续验证逻辑...
    return false;
}

补充说明

  • 若keytool检查本地信任库无2.5.29.17扩展(即SAN字段)但错误提示存在SAN,说明本地信任库的证书与服务器实际返回证书不一致,需确认信任库是否正确导入服务器证书。

2. 配置允许*匹配多级子域

HttpClient无现成参数直接开启多级通配符匹配,需通过自定义HostnameVerifier实现逻辑,以下是完整可运行示例:

// 已初始化加载trustStore和keystore的SSLContext
builder.setSSLContext(context);

builder.setSSLHostnameVerifier(new HostnameVerifier() {
    @Override
    public boolean verify(String hostname, SSLSession session) {
        try {
            X509Certificate serverCert = (X509Certificate) session.getPeerCertificates()[0];
            Collection<List<?>> sanEntries = serverCert.getSubjectAlternativeNames();
            
            // 优先验证SAN字段
            if (sanEntries != null) {
                for (List<?> entry : sanEntries) {
                    if ((Integer) entry.get(0) == 2) {
                        String san = (String) entry.get(1);
                        if (matchDomain(hostname, san)) {
                            return true;
                        }
                    }
                }
            }
            
            // 无SAN时回退验证证书CN字段
            String cn = extractCommonName(serverCert.getSubjectX500Principal().getName());
            return cn != null && matchDomain(hostname, cn);
            
        } catch (Exception e) {
            return false;
        }
    }

    // 自定义域名匹配逻辑,允许开头的*匹配多级子域
    private boolean matchDomain(String hostname, String san) {
        if (hostname == null || san == null) return false;
        
        // 完全匹配
        if (hostname.equalsIgnoreCase(san)) return true;
        
        // 仅处理开头为*.的通配符规则
        if (san.startsWith("*.")) {
            String sanSuffix = san.substring(2);
            // 匹配规则:hostname以".sanSuffix"结尾,或直接等于sanSuffix(顶级域名场景)
            return hostname.endsWith("." + sanSuffix) || hostname.equalsIgnoreCase(sanSuffix);
        }
        
        return false;
    }

    // 从证书主题DN中提取CN字段
    private String extractCommonName(String subjectDN) {
        String[] dnParts = subjectDN.split(",");
        for (String part : dnParts) {
            String trimmedPart = part.trim();
            if (trimmedPart.startsWith("CN=")) {
                return trimmedPart.substring(3);
            }
        }
        return null;
    }
});

安全提示

  • 仅允许开头位置的*通配符,避免任意位置通配符带来的安全风险;
  • 禁止使用NoopHostnameVerifier.INSTANCE完全关闭验证,会暴露中间人攻击风险。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 14:33:22