Skip to content

X509_NAME_oneline: escape '/', '+' and '\' inside attribute values - #11446

Open
aidankeefe2022 wants to merge 1 commit into
wolfSSL:masterfrom
aidankeefe2022:wolfssl_issue_x509
Open

X509_NAME_oneline: escape '/', '+' and '\' inside attribute values#11446
aidankeefe2022 wants to merge 1 commit into
wolfSSL:masterfrom
aidankeefe2022:wolfssl_issue_x509

Conversation

@aidankeefe2022

Copy link
Copy Markdown
Member

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:

  • 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.

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

  • added tests
  • updated/added doxygen

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 wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread wolfcrypt/src/asn.c
/* 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))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread wolfcrypt/src/asn_orig.c
int escLen = (int)X509CertEscapeName(NULL,
(const char*)&input[srcIdx], (word32)strLen);

if ((copyLen + escLen) >= (int)(WC_ASN_NAME_MAX - idx))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread wolfcrypt/src/asn_orig.c
WOLFSSL_MSG("ASN Name too big, skipping");
tooBig = TRUE;
/* Length of the value once special characters are escaped. */
int escLen = (int)X509CertEscapeName(NULL,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/x509.c
}
/* Certain characters need to be escaped, so ask for the
* length of the value once escaped. */
totalLen += (int)X509CertEscapeName(NULL, data,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@github-actions

Copy link
Copy Markdown

MemBrowse Memory Report

gcc-arm-cortex-m0plus

  • FLASH: .text +88 B (+0.1%, 67,047 B / 262,144 B, total: 26% used)

gcc-arm-cortex-m3

  • FLASH: .text +64 B (+0.1%, 125,839 B / 262,144 B, total: 48% used)

gcc-arm-cortex-m4

  • FLASH: .text +64 B (+0.0%, 205,021 B / 262,144 B, total: 78% used)

gcc-arm-cortex-m4-baremetal

  • FLASH: .text +64 B (+0.1%, 69,539 B / 262,144 B, total: 27% used)

gcc-arm-cortex-m4-crypto-only

  • FLASH: .text +64 B (+0.0%, 178,968 B / 262,144 B, total: 68% used)

gcc-arm-cortex-m4-dtls13

  • FLASH: .text +64 B (+0.0%, 187,324 B / 1,048,576 B, total: 18% used)

gcc-arm-cortex-m4-min-ecc

  • FLASH: .text +64 B (+0.1%, 64,389 B / 262,144 B, total: 25% used)

gcc-arm-cortex-m4-openssl-compat

  • FLASH: .text +64 B (+0.0%, 783,908 B / 1,048,576 B, total: 75% used)

gcc-arm-cortex-m4-pkcs7

  • FLASH: .text +64 B (+0.0%, 217,692 B / 262,144 B, total: 83% used)

gcc-arm-cortex-m4-pq

  • FLASH: .text +64 B (+0.0%, 302,152 B / 1,048,576 B, total: 29% used)

gcc-arm-cortex-m4-sp-math

  • FLASH: .text +64 B (+0.1%, 64,389 B / 262,144 B, total: 25% used)

gcc-arm-cortex-m4-tls12

  • FLASH: .text +64 B (+0.1%, 126,643 B / 262,144 B, total: 48% used)

gcc-arm-cortex-m4-tls13

  • FLASH: .text +64 B (+0.0%, 242,703 B / 262,144 B, total: 93% used)

gcc-arm-cortex-m7

  • FLASH: .text +64 B (+0.0%, 205,021 B / 262,144 B, total: 78% used)

gcc-arm-cortex-m7-pq

  • FLASH: .text +64 B (+0.0%, 303,112 B / 1,048,576 B, total: 29% used)

gcc-arm-cortex-m7-tls13

  • FLASH: .text +64 B (+0.0%, 242,703 B / 262,144 B, total: 93% used)

linuxkm-pie

  • Data: __patchable_function_entries +24 B (+0.1%, 27,096 B)

linuxkm-standard

  • Data: __patchable_function_entries +16 B (+0.0%, 49,888 B)

stm32-sim-stm32h753

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants