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

WinHTTP与Python Requests证书验证异常:CN有效SAN无效时未触发连接失败

Got it, let's tackle this issue for both WinHTTP and Python Requests since neither is enforcing the strict SAN validation required by Common Criteria FCS_TLSC_EXT.1.2 Test 2. The core requirement here is clear: when a server certificate includes a SAN extension, the CN must be ignored entirely—only matching SAN entries are valid for hostname verification. Here's how to fix each component:


Fixing WinHTTP Validation

WinHTTP's default behavior may fall back to the CN even when a SAN extension exists (which violates the Common Criteria requirement). To enforce strict SAN checking, you'll need to implement a custom certificate validation callback:

  • Register a status callback using WinHttpSetStatusCallback for your WinHTTP session.
  • Intercept certificate receipt in the callback (look for WINHTTP_CALLBACK_STATUS_CERTIFICATE_RECEIVED events).
  • Parse the certificate's SAN extension: Use Windows CryptoAPI functions (like CryptQueryObject, CryptDecodeObjectEx) to extract the SAN entries from the certificate.
  • Enforce SAN-only matching: If the SAN extension exists, verify that at least one DNS entry in the SAN matches your reference identifier. If no match is found, return an error to terminate the connection. Ignore the CN entirely in this case.

Example Snippet (C++)

void CALLBACK WinHttpStatusCallback(
    HINTERNET hInternet,
    DWORD_PTR dwContext,
    DWORD dwInternetStatus,
    LPVOID lpStatusInformation,
    DWORD dwStatusInformationLength
) {
    if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CERTIFICATE_RECEIVED) {
        PWINHTTP_CERTIFICATE_INFO pCertInfo = (PWINHTTP_CERTIFICATE_INFO)lpStatusInformation;
        HCERTSTORE hCertStore = CertOpenSystemStore(NULL, "MY");
        PCCERT_CONTEXT pCertContext = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, pCertInfo->pbCertEncoded, pCertInfo->dwCertEncodedSize);
        
        // Extract SAN extension
        DWORD dwDataLen = 0;
        CertGetCertificateContextProperty(pCertContext, CERT_SUBJECT_ALT_NAME2_PROP_ID, NULL, &dwDataLen);
        std::vector<BYTE> sanData(dwDataLen);
        CertGetCertificateContextProperty(pCertContext, CERT_SUBJECT_ALT_NAME2_PROP_ID, sanData.data(), &dwDataLen);
        
        // Parse SAN entries (simplified example)
        BOOL sanMatch = FALSE;
        PCERT_ALT_NAME_INFO pSanInfo = (PCERT_ALT_NAME_INFO)sanData.data();
        for (DWORD i = 0; i < pSanInfo->cAltEntry; i++) {
            if (pSanInfo->rgAltEntry[i].dwAltNameChoice == CERT_ALT_NAME_DNS_NAME) {
                if (_wcsicmp(pSanInfo->rgAltEntry[i].pwszDNSName, L"your-reference-identifier.com") == 0) {
                    sanMatch = TRUE;
                    break;
                }
            }
        }
        
        // If SAN exists but no match, abort the connection
        if (pSanInfo->cAltEntry > 0 && !sanMatch) {
            WinHttpCloseHandle(hInternet);
        }
        
        CertFreeCertificateContext(pCertContext);
        CertCloseStore(hCertStore, 0);
    }
}

Fixing Python Requests Validation

Python Requests relies on urllib3 for SSL validation. While newer urllib3 versions follow RFC 6125 (ignoring CN when SAN exists), older versions or custom setups might still allow CN fallback. Here are two approaches:

Option 1: Upgrade Dependencies

First, ensure you're using the latest versions of requests and urllib3:

pip install --upgrade requests urllib3

Newer versions should automatically enforce SAN-only checking when a SAN extension is present.

Option 2: Custom Validation Callback

If upgrading doesn't work (or you need explicit control), implement a custom adapter to enforce strict SAN validation:

from cryptography import x509
from cryptography.x509.oid import NameOID
import requests
from requests.adapters import HTTPAdapter
from urllib3.poolmanager import PoolManager
from urllib3.exceptions import SSLError

class StrictSANAdapter(HTTPAdapter):
    def init_poolmanager(self, *args, **kwargs):
        # Override the default hostname assertion logic
        kwargs["assert_hostname"] = self._strict_san_assertion
        super().init_poolmanager(*args, **kwargs)
    
    def _strict_san_assertion(self, cert_data, hostname):
        # Parse the PEM-encoded certificate
        cert = x509.load_pem_x509_certificate(cert_data.encode("utf-8"))
        
        # Check for SAN extension
        try:
            san_ext = cert.extensions.get_extension_for_oid(x509.OID_SUBJECT_ALTERNATIVE_NAME)
            san_dns_names = san_ext.value.get_values_for_type(x509.DNSName)
            
            # Verify hostname is in SAN entries
            if hostname not in san_dns_names:
                raise SSLError(f"Hostname '{hostname}' not found in certificate SAN entries: {san_dns_names}")
        except x509.ExtensionNotFound:
            # No SAN exists, fall back to CN validation
            cn = cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
            if cn != hostname:
                raise SSLError(f"Certificate CN '{cn}' does not match hostname '{hostname}'")
        
        return True

# Use the custom adapter in your session
session = requests.Session()
session.mount("https://", StrictSANAdapter())

# Test the connection (should fail if SAN exists but doesn't match)
try:
    response = session.get("https://your-test-server.com")
    print("Connection succeeded (unexpected!)")
except SSLError as e:
    print(f"Connection failed as required: {str(e)}")

Both approaches ensure that your client adheres to FCS_TLSC_EXT.1.2 Test 2: if a server certificate has a SAN extension with no matching identifiers (even if the CN is correct), the connection will terminate with an error.

内容的提问来源于stack exchange,提问作者Muhammad Uzair Khan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 10:39:17