Reject present-but-empty name constraint subtrees - #15560
Conversation
GeneralSubtrees is SEQUENCE SIZE (1..MAX), so a subtree field that is present must not be empty. An empty permittedSubtrees was accepted and then had no effect, because the loop over the subtrees never runs, so every name passed the permitted side instead of none.
|
It should be possible to write a test case for this against the Python APIs - that's preferrable to writing directly against the Rust APIs. Ideally this could just be a testcase in x509-limbo. |
|
Done — the testcase is now C2SP/x509-limbo#658, as Should the Rust tests in this PR come out once the limbo case lands, or stay as a unit test? |
|
Yes, the rust tests should be removed once tha tlands. |
|
Understood — I will drop the Rust tests here as soon as C2SP/x509-limbo#658 lands. |
C2SP/x509-limbo#658 landed, so rfc5280::nc::permitted-empty-sequence-excluded-nonempty and rfc5280::nc::permitted-nonempty-excluded-nonempty cover these two cases. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI pins x509-limbo to 972626160c26b45426bbd8c935a605219bd93207 (2026-08-27), which predates C2SP/x509-limbo#658, so the new cases are not in the vectors CI runs against yet. Without this test extension.rs:754-756 is uncovered and the coverage gate fails. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Removed
|
|
#15580 has the fix, but there's a chicken and egg situation, if you cherry pick that it solves this. |
Cherry-picked from pyca#15580 at Alex's suggestion on pyca#15560: the new pin 21cc053 is the merge commit for C2SP/x509-limbo#658, so the two new name-constraint testcases are in the vectors CI runs. (cherry picked from commit 319a8a4) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The pin now points at 21cc053, which carries rfc5280::nc::permitted-empty-sequence-excluded-nonempty, so the branch this test guarded is exercised by the limbo vectors CI runs. test_ca_name_constraints_empty_excluded_subtrees stays: no limbo case covers a non-empty permittedSubtrees with an empty excludedSubtrees. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Cherry-picked 319a8a4 from #15580, which moves the pin to 21cc053 — the merge commit for the limbo PR. With those vectors in CI,
|
|
Yes, we'd rather tests live in limbo
All that is necessary for evil to succeed is for good people to do nothing.
…On Wed, Sep 2, 2026, 8:42 AM Eirik Botten Nicolaysen < ***@***.***> wrote:
*avalyset* left a comment (pyca/cryptography#15560)
<#15560 (comment)>
Cherry-picked 319a8a4
<319a8a4>
from #15580 <#15580>, which
moves the pin to 21cc053 — the merge commit for the limbo PR. With those
vectors in CI, test_ca_name_constraints_empty_permitted_subtrees is
covered by rfc5280::nc::permitted-empty-sequence-excluded-nonempty and is
now removed.
test_ca_name_constraints_empty_excluded_subtrees still stays: no limbo
case covers a non-empty permittedSubtrees with an empty excludedSubtrees.
I can add that one to limbo as well if you would rather have it there.
—
Reply to this email directly, view it on GitHub
<#15560?email_source=notifications&email_token=AAAAGBGRIEYQAURLAGWHSHT5NAIS5A5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKNJQHE3DSMZQHA3KM4TFMFZW63VHMNXW23LFNZ2KKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-5509693086>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAAAGBEBOXYVAYTLGXAAXGL5NAIS5AVCNFSNUABEKJSXA33TNF2G64TZHMYTCOJTHE2DQNB3JFZXG5LFHM2TGMBQGA2DQMZSGCQXMAQ>
.
Triage notifications, keep track of coding agent tasks and review pull
requests on the go with GitHub Mobile for iOS
<https://github.com/notifications/mobile/ios/AAAAGBBBT2VJ5SAUN5S57KL5NAIS5A5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKNJQHE3DSMZQHA3KM4TFMFZW63VHMNXW23LFNZ2KKZLWMVXHJKTGN5XXIZLSL5UW64Y>
and Android
<https://github.com/notifications/mobile/android/AAAAGBGX53TWA5YG2NKLYMT5NAIS5A5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKNJQHE3DSMZQHA3KM4TFMFZW63VHMNXW23LFNZ2KKZLWMVXHJLTGN5XXIZLSL5QW4ZDSN5UWI>.
Download it today!
You are receiving this because you commented.Message ID:
***@***.***>
|
|
I flagged this in C2SP/x509-limbo#660 as well, but noting here too: AFAICT basically zero validators conform to this ATM. That also goes for the case added in C2SP/x509-limbo#658: https://x509-limbo.com/testcases/rfc5280/#rfc5280ncpermitted-empty-sequence-excluded-nonempty |
|
For context, that would be consistent with that the validator currently does, i.e. we don't target requirements that are widely ignored by other validators: cryptography/tests/x509/verification/test_limbo.py Lines 37 to 39 in 5936b46 |
Reported privately as GHSA-fw9w-89pp-fpmg. The advisory is not public, so there is nothing to link. The response there was that this is a regular bug rather than a security issue, with a request to open a public PR instead; this is that PR. We had offered no severity assessment either way.
The rule is already enforced in the Python layer
src/cryptography/x509/extensions.py:1344:That check came from #6982, "Possible bug: empty sequence in NameConstraints" (opened and closed 2022-03-19), where the conclusion was explicit:
The Rust path validator did not enforce it
policy/extension.rsrejected only when both subtree fields were empty, so an emptypermittedSubtreescombined with a non-emptyexcludedSubtreespassed that check. Inlib.rs:258-274the permit flag then keeps its default:With an empty sequence the loop body never runs, so
permitstaystrueand every SAN is accepted by the permitted side.Basis
RFC 5280 6.1.4 (g)(1) sets
permitted_subtreesto the intersection of its previous value and the value in the extension; the intersection with an empty set is empty. RFC 5280 6.1.3 (b) requires the name to lie withinpermitted_subtrees, so with an empty one no name qualifies and every certificate below that CA should be rejected. It was accepting all of them instead.Reaching this requires a signed CA certificate carrying such an extension, and RFC 5280 4.2.1.10 says conforming CAs must not issue one.
The change
GeneralSubtrees ::= SEQUENCE SIZE (1..MAX)constrains both fields, so this rejects a present-but-emptypermittedSubtreesand a present-but-emptyexcludedSubtrees. Only the first has any consequence; an emptyexcludedSubtreesexcludes nothing, which is harmless. But it is the same ASN.1 requirement on the same type, and enforcing it for one field and not the other seemed arbitrary. Happy to drop the second check if you would rather keep the diff to the field that matters.The existing both-empty check is kept. I checked whether it became redundant: it does not. It is the only one that covers a
NameConstraintswhere neither field is present, whichis_some_andby construction never matches.Behaviour
The probe from the advisory, through the public API. The CA's
NameConstraintshas to be built as raw DER, because the Python API refuses to construct this shape at all, which is itself part of the point.Before, against 50.0.1 from PyPI:
After, with this branch:
The second line is the control, and it is unchanged.
Tests
Three tests in
policy::extension::tests, using raw DER because an empty sequence cannot be built through the writing API: emptypermittedSubtreeswith non-emptyexcludedSubtrees, emptyexcludedSubtreeswith non-emptypermittedSubtrees, and a control with both non-empty that must still be accepted.Verified they fail without the fix: reverting the check and keeping the tests turns both rejection tests red while the control stays green.
Checks
cargo test --allgreen across the workspace, including the 43 tests incryptography-x509-verificationcargo fmt --all -- --checkcleancargo clippy -p cryptography-x509-verification --all-targets: 8 warnings, the same 8 as onmain, none on the changed linespytest tests/: 4490 passed, 199 skipped (wycheproof_rootandx509_limbo_rootnot available locally)