Name-constraint CN check rejects ordinary non-DNS common names since v1.71.0
- Dominant language
- Assembly
- Stars
- 830
- Forks
- 212
- Avg merge
- 2d 22h
- Merged PRs (30d)
- 61
Description
### Problem
The fix for GHSA-3jrg-j22w-mpmc rejects certificate chains whose CN is an ordinary human-readable name. A CN containing one internal dot and at least one space is classified as a DNS name and then hard-fails with `X509_V_ERR_UNSUPPORTED_NAME_SYNTAX` (53), where OpenSSL and aws-lc v1.70.0 verify the chain.
`CN=ACME Corp. Issuing CA G2` is enough. It is a textbook intermediate-CA subject.
### Mechanism
`crypto/x509/v3_ncons.c`, in `cn2dnsid`:
```c
if (c >= 0x80 || c <= 0x20 || c == 0x7F) {
has_non_dns_char = 1;
continue; // note: does not clear isdnsname
}
```
Upstream OpenSSL has no such branch: any byte that is not alphanumeric, `_`, an internal `-` or a qualifying internal `.` reaches `isdnsname = 0; break;` and the CN is quietly skipped as "not a DNS name".
The character class here includes `0x20`, the space. The comment above the resulting error describes the intent as "Multi-label CN with non-ASCII bytes or control characters", and a space is neither, so the `<=` looks like it is one off from what was meant. Because the branch `continue`s without clearing `isdnsname`, a single qualifying internal dot latches the classification and nothing later can undo it.
### Reproduction
Three-certificate chain, every name inside the permitted subtree, built with the OpenSSL CLI:
- root: `CN=Test Constrained CA`, `nameConstraints=critical,permitted;DNS:permitted.example`
- intermediate: `CN=ACME Corp. Issuing CA G2`, `CA:TRUE,pathlen:0`, no SAN
- leaf: `CN=www.permitted.example`, `subjectAltName=DNS:www.permitted.example`
Same source compiled against both libraries:
```
=== AWS-LC ===
int=int.pem -> verify=0 err=53(unsupported or invalid name syntax) depth=1
int=int2.pem -> verify=1 err=0(ok) depth=0
=== OpenSSL 3.6.4 ===
int=int.pem -> verify=1 err=0(ok) depth=0
int=int2.pem -> verify=1 err=0(ok) depth=0
```
`int2.pem` is the same intermediate reissued as `CN=ACME Corp Issuing CA G2`, the full stop removed and nothing else changed. `depth=1` names the intermediate.
Present in v1.71.0, v1.72.1 and v1.73.0; absent in v1.70.0, which matches PR #3108 as the origin.
### Why I am raising it rather than sending a patch
PR #3108's call-out says:
> Only certificates that were already in violation of their CA's constraints will be newly rejected.
A CN of `Acme Corp. Inc` under a CA constrained only on `excluded;IP:10.0.0.0/255.0.0.0` is in violation of nothing, and it is newly rejected. `Acme Corp Inc`, one character different, is not.
The obvious edit is `c < 0x20` instead of `c <= 0x20`, which excludes the space while keeping non-ASCII and control characters sticky. I built that and ran it: it clears the false rejects and keeps both halves of the advisory fix (the wildcard case still errors 47, the Unicode case still errors 53). But it also fails your own `X509CompatTest.CommonNameToDNS`:
```
crypto/x509/x509_compat_test.cc:2583: Failure
[ FAILED ] X509CompatTest.CommonNameToDNS
```
because the table at `x509_compat_test.cc:2471` deliberately expects `"foo .evil.com"` to produce `X509_V_ERR_UNSUPPORTED_NAME_SYNTAX`. So a space was considered when the boundary was drawn, and changing it is a policy call about whether a multi-label CN containing a space should be treated as a malformed DNS name or as not a DNS name at all. That is yours to make, not mine to assume in a patch.
A second option, which I have not built: stop latching `isdnsname` once a disqualifying byte has been seen, so the classification is decided by the whole string rather than by whichever dot came first.
### Scope
This is fail-closed. Valid chains are rejected; nothing invalid is accepted. There is no attacker role, since the offending string is a CA operator's own display name for their own intermediate. Raising it as a correctness and availability regression, not a security issue.
Contributor guide
Research direction
Start in crypto/x509/v3_ncons.c at cn2dnsid and compare the classification path with PR #3108; then run crypto/x509/x509_compat_test.cc, especially CommonNameToDNS and the table around line 2471. Resolve the intended treatment of human-readable CNs containing spaces, preserve the wildcard and Unicode error cases, and verify the reproduced constrained-chain behavior matches the chosen policy.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c
- Domain
- cryptography, security
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100