X509_NAME_oneline: escape '/', '+' and '\' inside attribute values - #11446
X509_NAME_oneline: escape '/', '+' and '\' inside attribute values#11446aidankeefe2022 wants to merge 1 commit into
Conversation
A single attribute whose value contains the RDN separator rendered byte-identical to a name made of several attributes: CN="foo/O=bar" and CN=foo, O=bar both came out as "/CN=foo/O=bar". X509_NAME_cmp() compares this string, so the two names compared equal, and so did the issuer checks built on it. Add X509CertEscapeName(), which prefixes every '\', '/' and '+' in a value with '\', and use it wherever the one-line form is built: - GetRDN() and GetCertName(), which build the DecodedCert subject and issuer strings. The WC_ASN_NAME_MAX check now uses the escaped length. - AddAllEntry() and RebuildFullName(), which rebuild the string from the X509_NAME entries. The sizing pass skips entries with no value. OpenSSL 3 escapes '/' and '+'. '\' is escaped here as well: otherwise CN="foo\", O=bar still renders as CN="foo/O=bar" does, and CN="a\+b" as CN="a+b" does. A value containing '\' therefore renders differently than it does in OpenSSL. This changes the strings from wolfSSL_X509_NAME_oneline(), wc_GetDecodedCertSubject() and wc_GetDecodedCertIssuer() for values containing these characters, and so the caSubject that wc_OcspResponder_AddSigner() expects. Document the escaping for each. test_wolfSSL_X509_NAME_oneline_escape covers names built from entries and names parsed from a signed certificate, that X509_NAME_dup() does not escape twice, and a value long enough that escaping doubles it. Issue: wolfSSL#11392
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #11446
Scan targets checked: wolfcrypt-src, wolfcrypt-bugs, wolfssl-src, wolfssl-bugs
Findings: 4
4 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Reported findings require changes before merge.
| /* Check there is space for this in the full name string and | ||
| * terminating NUL character. */ | ||
| if ((typeStrLen + strLen) < (word32)(WC_ASN_NAME_MAX - *idx)) | ||
| if ((typeStrLen + escLen) < (word32)(WC_ASN_NAME_MAX - *idx)) |
There was a problem hiding this comment.
Escaping silently omits RDNs that previously fit · Logic errors
GetRDN() silently skips an RDN when escaping exceeds WC_ASN_NAME_MAX, even when its former representation fit. Distinct names can collapse to the same subject or issuer string used by OCSP matching.
Related known finding #6282 (similar but distinct): Both can cause incorrect OCSP-related matching behavior, but GetRDN omits escape-expanded name components due to decoded-name capacity handling, whereas CompareOcspReqResp selects the first duplicate CertID response. The faulting operations and root causes differ, and returning BUFFER_E would not fix duplicate-response selection.
Suggested fix: Return BUFFER_E instead of succeeding when an escaped RDN does not fit the decoded-name buffer.
Basis: The wc_GetDecodedCertSubject() and wc_GetDecodedCertIssuer() API contracts identify their output as the decoded subject or issuer name.
| int escLen = (int)X509CertEscapeName(NULL, | ||
| (const char*)&input[srcIdx], (word32)strLen); | ||
|
|
||
| if ((copyLen + escLen) >= (int)(WC_ASN_NAME_MAX - idx)) |
There was a problem hiding this comment.
Legacy ASN builds also omit escape-expanded RDNs · Logic errors
GetCertName() introduces the same silent omission under WOLFSSL_ASN_ORIGINAL. Distinct names whose unescaped forms previously fit can now collapse in decoded subject and issuer consumers.
Suggested fix: Return BUFFER_E instead of succeeding when an escaped RDN does not fit the decoded-name buffer.
Basis: The wc_GetDecodedCertSubject() and wc_GetDecodedCertIssuer() API contracts identify their output as the decoded subject or issuer name.
| WOLFSSL_MSG("ASN Name too big, skipping"); | ||
| tooBig = TRUE; | ||
| /* Length of the value once special characters are escaped. */ | ||
| int escLen = (int)X509CertEscapeName(NULL, |
There was a problem hiding this comment.
Escaped length narrowing bypasses the legacy bounds check · Integer overflow / underflow
escLen narrows a word32 result to int. On two's-complement targets, large escaped lengths become negative, the guard passes, and X509CertEscapeName() writes attacker-sized data into full.
Related known finding #2651 (similar but distinct): Both are legacy ASN integer-safety defects, but this narrows an escaped word32 length to signed int and bypasses a buffer bound before X509CertEscapeName writes, while #2651 shifts a promoted byte in DecodeKeyUsage on 16-bit-int systems. The operations, root causes, and fixes are separate.
Suggested fix: Keep escLen as word32 and use subtraction-based bounds checks before adding copyLen or writing.
Basis: ISO C17 6.3.1.3 makes an out-of-range integer conversion implementation-defined, while 6.5p5 makes unrepresentable signed arithmetic undefined.
| } | ||
| /* Certain characters need to be escaped, so ask for the | ||
| * length of the value once escaped. */ | ||
| totalLen += (int)X509CertEscapeName(NULL, data, |
There was a problem hiding this comment.
Escaped-name length accumulation can overflow · Integer overflow / underflow
RebuildFullName() accumulates escaped lengths in signed int totalLen without checks. Large API-created entries overflow the allocation size before AddAllEntry() writes the full escaped bytes, causing out-of-bounds writes. Adjacent known finding #7584 concerns mismatched length sources, not arithmetic overflow.
Related known finding #11841 (similar but distinct): Both are signed-length arithmetic overflows in x509.c that can bypass a size check before a copy/write, but RebuildFullName overflows aggregate escaped entry lengths before allocation, while #11841 overflows len + 1 for a request serial field. They occur in different functions and require separate checked-addition fixes.
Suggested fix: Accumulate in size_t with checked additions, reject totals above INT_MAX, and verify totalLen + 1 before allocation.
Basis: ISO C17 §6.5 paragraph 5 states that an exceptional result outside the representable range has undefined behavior.
|
A single attribute whose value contains the RDN separator rendered byte-identical to a name made of several attributes: CN="foo/O=bar" and CN=foo, O=bar both came out as "/CN=foo/O=bar". X509_NAME_cmp() compares this string, so the two names compared equal, and so did the issuer checks built on it.
Description
Add X509CertEscapeName(), which prefixes every '', '/' and '+' in a value with '', and use it wherever the one-line form is built:
OpenSSL 3 escapes '/' and '+'. '' is escaped here as well: otherwise CN="foo", O=bar still renders as CN="foo/O=bar" does, and CN="a+b" as CN="a+b" does. A value containing '' therefore renders differently than it does in OpenSSL.
This changes the strings from wolfSSL_X509_NAME_oneline(), wc_GetDecodedCertSubject() and wc_GetDecodedCertIssuer() for values containing these characters, and so the caSubject that wc_OcspResponder_AddSigner() expects. Document the escaping for each.
Fixes zd#
Testing
test_wolfSSL_X509_NAME_oneline_escape covers names built from entries and names parsed from a signed certificate, that X509_NAME_dup() does not escape twice, and a value long enough that escaping doubles it.
Checklist