Skip to content

Linux CheckX509IpAddress processes IP literals in CN on Linux when SAN is also present #131734

Description

@wfurt

Traditionally, one can put IP address to CN and have that validated agains actual IP address.
However, when SubjectAlternativeName is present it should take precedence:

https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6

The behavior is Linux specific and it works as expected when same validation runs on Windows.

Activity

  1. dotnet-policy-service commented on Aug 3, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @bartonjs, @vcsjones, @dotnet/area-system-security
    See info in area-owners.md if you want to be subscribed.

  2. changed the title [-]X509Chain process IP literals in CN on Linux when SAN is also present[/-] [+]Linux CheckX509IpAddress processes IP literals in CN on Linux when SAN is also present[/+] on Aug 14, 2026
  3. removed
    untriagedNew issue has not been triaged by the area owner
    on Aug 14, 2026
  4. added this to the 12.0.0 milestone on Aug 14, 2026
  5. makazeu commented on Oct 10, 2026

    @makazeu
    Contributor

    Reproduction: IP address matching with CN and SAN on Windows and Linux

    I reproduced the difference described in dotnet/runtime#131734 using actual SslStream TLS connections on both platforms.

    The difference occurs in the tested SANs containing a DNS name: Linux accepts the target IP from CN, while Windows reports a name mismatch. However, an IP-only SAN containing a different IP did not prevent CN fallback on either platform. The latter also reproduced for IPv6.

    Environment

    Platform Environment
    Windows OS version 10.0.26300; .NET 10.0.12; SDK 10.0.401
    Linux Ubuntu 24.04.5 LTS in Docker; .NET 10.0.12; OpenSSL 3.0.13
    Linux image mcr.microsoft.com/dotnet/sdk:10.0
    Image digest sha256:e70cdb7f80b0348f5cb85f19a8f670fca061f033d57eed12fa003d58b0e06317

    These tests used installed .NET binaries, not a locally built runtime.

    Reproduction method

    The same TLS harness ran 11 certificate/target combinations on each platform:

    1. Generate a self-signed RSA-2048 certificate using CertificateRequest, SHA-256, and PKCS#1 signature padding.
    2. Set CN to the IP literal listed below. Add SAN IP/DNS entries using SubjectAlternativeNameBuilder, or omit SAN for the "None" cases.
    3. Export and re-import the certificate as in-memory PKCS#12 using X509CertificateLoader.LoadPkcs12 with DefaultKeySet. This accommodates Windows Schannel, which rejected the originally generated ephemeral private keys.
    4. Establish a client/server TCP connection on loopback and wrap both ends in SslStream.
    5. Authenticate using TLS 1.2, with the client's TargetHost set to the target IP literal and revocation checking disabled.
    6. Record the actual SslPolicyErrors in RemoteCertificateValidationCallback, then return true solely to let the diagnostic connection continue.
    7. Exchange an encrypted client-to-server payload, verify its bytes, and check both streams' IsAuthenticated and IsEncrypted properties.

    The target addresses are documentation-range IPs used as identity-check inputs; the TCP connections go to loopback. No certificates were installed in a trust store.

    Important: the callback deliberately allows the connections despite certificate errors. Successful handshakes therefore do not demonstrate default certificate acceptance. The results below describe the name-validation errors reported by .NET before that override.

    TLS name-validation results

    "Match" means RemoteCertificateNameMismatch was absent; "Mismatch" means it was present.

    CN SAN contents Target IP Windows TLS Linux TLS
    192.0.2.1 None 192.0.2.1 Match Match
    192.0.2.1 IP: 192.0.2.2 192.0.2.1 Match Match
    192.0.2.1 IP: 192.0.2.2 192.0.2.2 Match Match
    192.0.2.1 IP: 192.0.2.2 192.0.2.3 Mismatch Mismatch
    192.0.2.1 DNS: example.test 192.0.2.1 Mismatch Match
    192.0.2.1 DNS: example.test; IP: 192.0.2.2 192.0.2.1 Mismatch Match
    192.0.2.1 DNS: example.test; IP: 192.0.2.2 192.0.2.2 Match Match
    192.0.2.1 None 192.0.2.2 Mismatch Mismatch
    2001:db8::1 None 2001:db8::1 Match Match
    2001:db8::1 IP: 2001:db8::2 2001:db8::1 Match Match
    2001:db8::1 IP: 2001:db8::2 2001:db8::2 Match Match

    All 22 TLS handshakes and encrypted payload exchanges completed under the diagnostic callback.

    Every case reported RemoteCertificateChainErrors, as expected for these untrusted self-signed certificates. Windows additionally reported RemoteCertificateNameMismatch in four cases; Linux reported it in two cases.

    Representative recorded output for CN 192.0.2.1, SAN DNS example.test, target 192.0.2.1:

    Windows:
    TLS-name-match=False
    TLS-errors=RemoteCertificateNameMismatch, RemoteCertificateChainErrors
    protocol=Tls12; encrypted-data=True
    
    Linux:
    TLS-name-match=True
    TLS-errors=RemoteCertificateChainErrors
    protocol=Tls12; encrypted-data=True
    

    Independent native checks

    The TLS results agreed with native name-validation probes for all 11 combinations on each platform:

    • Windows: CertVerifyCertificateChainPolicy with CERT_CHAIN_POLICY_SSL, AUTHTYPE_SERVER, the target IP as pwszServerName, and policy flags 0xFBF (ignore other policy errors, retain name checking). Matches returned dwError = 0; mismatches returned 0x800B010F (CERT_E_CN_NO_MATCH).
    • Linux: the installed .NET native library's CryptoNative_CheckX509IpAddress, with the target IP bytes and literal string. Matches returned 1; mismatches returned 0.

    The inspected Linux source checks SAN IP entries first, then checks CN whenever success remains false, without restricting fallback based on SAN presence:

    CryptoNative_CheckX509IpAddress, inspected revision.

    The Windows native probe follows the parameters in:

    CertificateValidation.Windows.cs, inspected revision.

    These source links describe the inspected checkout; the executed binaries were .NET 10.0.12.

    Standards interpretation

    Windows compatibility and strict standards-based IP identity matching are different targets here.

    RFC 5280 section 4.2.1.6 defines SAN identity representation and IP encoding, but does not specify a client's blanket SAN-over-CN precedence algorithm. Its requirement that SAN identities be verified by the CA concerns issuance, not client target-name matching.

    For HTTPS, RFC 2818 section 3.1 explicitly states:

    In some cases, the URI is specified as an IP address rather than a hostname. In this case, the iPAddress subjectAltName must be present in the certificate and must exactly match the IP in the URI.

    The current TLS service-identity specification, RFC 9525, which obsoletes RFC 6125, states in section 2:

    The Common Name RDN MUST NOT be used to identify a service because it is not strongly typed (it is essentially free-form text) and therefore suffers from ambiguities in interpretation.

    And section 6.4 specifies:

    Matching of an IP-ID is based on an octet-for-octet comparison of the bytes of the reference identity with the bytes contained in the iPAddress subjectAltName.

    For the direct IP targets tested here, this means a matching IP SAN is required: CN-only certificates and certificates with a matching CN but only a conflicting SAN IP should fail the identity check.

    Consequently, neither tested platform fully follows that strict IP-identity rule. Windows rejects the two DNS-containing SAN cases that Linux accepts, but both still accept CN-only IP identities and CN fallback with conflicting IP-only SANs.

    These observations are limited to the versions and combinations above. Default rejection behavior was not tested because the diagnostic callback deliberately accepted the connections.

    Additional Windows reproduction: numeric DNS SANs and wildcards

    The original 11-case reproduction above did not include wildcard tests. A later Windows run expanded the TLS harness to 22 cases. The following results come from that additional reproduction, not from the original suite.

    This run used Windows OS 10.0.26300 and installed .NET 10.0.12. No additional installed-runtime Linux results are included in this section.

    CN SAN contents Target IP Windows TLS
    192.0.2.1 DNS: 192.0.2.1 192.0.2.1 Match
    other.test DNS: 192.0.2.1 192.0.2.1 Match
    192.0.2.1 DNS: *.0.2.1 192.0.2.1 Mismatch
    *.0.2.1 None 192.0.2.1 Mismatch
    2001:db8::1 DNS: 2001:db8::1 2001:db8::1 Match
    other.test DNS: 2001:db8::1 2001:db8::1 Match

    Recorded Windows wildcard results:

    CN=192.0.2.1; SAN-DNS=*.0.2.1; target=192.0.2.1
    TLS-name-match=False
    TLS-errors=RemoteCertificateNameMismatch, RemoteCertificateChainErrors
    protocol=Tls12; encrypted-data=True; verified=True
    
    CN=*.0.2.1; SAN-DNS=; target=192.0.2.1
    TLS-name-match=False
    TLS-errors=RemoteCertificateNameMismatch, RemoteCertificateChainErrors
    protocol=Tls12; encrypted-data=True; verified=True
    

    The expanded Windows run completed 22 TLS checks and 22 encrypted payload exchanges with zero harness assertion failures. It did not execute independent native probes.

    These two IPv4 wildcard cases demonstrate rejection of the particular wildcard identities tested; they do not establish the behavior of every possible wildcard pattern or Windows' internal implementation. As in the original harness, the callback deliberately accepted certificate errors so the encrypted exchange could complete.

    Note

    This report and its test harnesses were generated with GitHub Copilot. The reported results were obtained by executing native probes and actual TLS tests on Windows and in a Linux Docker container.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions