From b69ae6ba5c866933cb4303a284e2b3425063d0cf Mon Sep 17 00:00:00 2001 From: Kareem Date: Thu, 1 Oct 2026 16:21:30 -0700 Subject: [PATCH 1/4] Fix read length in ASN1_STRING_print_ex / ASN1_STRING_to_UTF8. Thanks to Anthropic for the report. --- src/ssl_asn1.c | 11 +++++++---- src/x509.c | 12 +++++------- tests/api/test_ossl_asn1.c | 5 +++-- tests/api/test_ossl_x509_ext.c | 6 ++++++ 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/ssl_asn1.c b/src/ssl_asn1.c index 29f35fb6be5..8162cf5e78c 100644 --- a/src/ssl_asn1.c +++ b/src/ssl_asn1.c @@ -2897,8 +2897,9 @@ int wolfSSL_ASN1_STRING_to_UTF8(unsigned char **out, WOLFSSL_ASN1_STRING *asn1) } } if (len != -1) { - /* Copy in string - NUL always put on end of stored string. */ - XMEMCPY(buf, data, (size_t)(len + 1)); + /* Copy in string and NUL terminate - data may not be NUL terminated. */ + XMEMCPY(buf, data, (size_t)len); + buf[len] = '\0'; /* Return buffer. */ *out = buf; } @@ -3640,10 +3641,12 @@ static int wolfssl_asn1_string_print_esc_2253(WOLFSSL_BIO *bio, WOLFSSL_ASN1_STRING *str) { char* p; + int i; int str_len = 0; - /* Write all of string character by character. */ - for (p = str->data; (*p) != '\0'; p++) { + /* Write all of string character by character. + * Use length as data is not necessarily NUL terminated. */ + for (p = str->data, i = 0; i < str->length; p++, i++) { /* Check if character needs escaping. */ if (wolfssl_check_esc_char(*p)){ /* Update count of written characters. */ diff --git a/src/x509.c b/src/x509.c index 59c26ad2de6..bdfe9f974f4 100644 --- a/src/x509.c +++ b/src/x509.c @@ -1196,18 +1196,16 @@ WOLFSSL_X509_EXTENSION* wolfSSL_X509_set_ext(WOLFSSL_X509* x509, int loc) WC_FREE_VAR_EX(cert, NULL, DYNAMIC_TYPE_DCERT); return NULL; } - ext->value.data = (char*)XMALLOC(length, NULL, - DYNAMIC_TYPE_ASN1); - ext->value.isDynamic = 1; - if (ext->value.data == NULL) { - WOLFSSL_MSG("Failed to malloc ASN1_STRING data"); + /* Copy with wolfSSL_ASN1_STRING_set() so the data is NUL terminated + * like every other ASN1_STRING. */ + if (wolfSSL_ASN1_STRING_set(&ext->value, input + tmpIdx, length) + != 1) { + WOLFSSL_MSG("Failed to set ASN1_STRING data"); wolfSSL_X509_EXTENSION_free(ext); FreeDecodedCert(cert); WC_FREE_VAR_EX(cert, NULL, DYNAMIC_TYPE_DCERT); return NULL; } - XMEMCPY(ext->value.data,input+tmpIdx,length); - ext->value.length = length; break; /* Got the Extension. Now exit while loop. */ diff --git a/tests/api/test_ossl_asn1.c b/tests/api/test_ossl_asn1.c index c646141ed7d..e397dbec7d1 100644 --- a/tests/api/test_ossl_asn1.c +++ b/tests/api/test_ossl_asn1.c @@ -1505,8 +1505,9 @@ int test_wolfSSL_ASN1_STRING_print_ex(void) /* RFC2253 Escape */ XMEMSET(rbuf, 0, 255); flags = ASN1_STRFLGS_ESC_2253; - ExpectIntEQ(p_len = wolfSSL_ASN1_STRING_print_ex(bio, esc_str, flags), 9); - ExpectIntEQ(BIO_read(bio, (void*)rbuf, 9), 9); + /* 6 bytes of data (incl. NUL) plus 4 escape characters. */ + ExpectIntEQ(p_len = wolfSSL_ASN1_STRING_print_ex(bio, esc_str, flags), 10); + ExpectIntEQ(BIO_read(bio, (void*)rbuf, 10), 10); ExpectStrEQ((char*)rbuf, "a\\+\\;\\<\\>"); ExpectIntEQ(BIO_set_write_buf_size(fixed, 1), 1); /* Ensure there is 0 bytes available to write into. */ diff --git a/tests/api/test_ossl_x509_ext.c b/tests/api/test_ossl_x509_ext.c index af2bedc46e2..234a407e2a2 100644 --- a/tests/api/test_ossl_x509_ext.c +++ b/tests/api/test_ossl_x509_ext.c @@ -824,6 +824,7 @@ int test_wolfSSL_X509_EXTENSION_get_data(void) WOLFSSL_ASN1_STRING* str = NULL; XFILE file = XBADFILE; #ifndef WOLFSSL_OLD_EXTDATA_FMT + unsigned char* utf8 = NULL; const byte ext_data[] = { 0x04, 0x14, 0xB3, 0x11, 0x32, 0xC9, 0x92, 0x98, 0x84, 0xE2, 0xC9, 0xF8, 0xD0, 0x3B, 0x6E, 0x03, @@ -843,6 +844,11 @@ int test_wolfSSL_X509_EXTENSION_get_data(void) #ifndef WOLFSSL_OLD_EXTDATA_FMT ExpectIntEQ(str->length, sizeof (ext_data)); ExpectBufEQ(str->data, ext_data, sizeof (ext_data)); + /* Data is NUL terminated like any other ASN1_STRING. */ + ExpectIntEQ(str->data[str->length], '\0'); + ExpectIntEQ(wolfSSL_ASN1_STRING_to_UTF8(&utf8, str), sizeof (ext_data)); + ExpectBufEQ(utf8, ext_data, sizeof (ext_data)); + XFREE(utf8, NULL, DYNAMIC_TYPE_OPENSSL); #endif wolfSSL_X509_free(x509); From 60b62f1ce648e9faf5b88a5d3a05c38fa68082ab Mon Sep 17 00:00:00 2001 From: Kareem Date: Thu, 1 Oct 2026 16:24:09 -0700 Subject: [PATCH 2/4] Refactor freeing logic in wolfSSL_X509_get0_extensions. Avoid rebuilding the extension stack unless it is NULL. Allow non critical extensions and extensions with zero-length OCTET STRING to match the core DER parser behavior. Thanks to Anthropic for the report. --- src/x509.c | 32 ++++++++++------ tests/api/test_ossl_x509_ext.c | 68 ++++++++++++++++++++++++++++++++++ tests/api/test_ossl_x509_ext.h | 3 ++ 3 files changed, 92 insertions(+), 11 deletions(-) diff --git a/src/x509.c b/src/x509.c index bdfe9f974f4..905abbe63bf 100644 --- a/src/x509.c +++ b/src/x509.c @@ -406,12 +406,14 @@ static WOLFSSL_STACK* generateExtStack(const WOLFSSL_X509 *x) } /** + * The returned stack is owned by the X509 and stays valid until the X509 is + * freed or its DER encoding is replaced (e.g. by wolfSSL_X509_sign). + * * @param x Certificate to extract extensions from * @return STACK_OF(X509_EXTENSION)* */ const WOLFSSL_STACK *wolfSSL_X509_get0_extensions(const WOLFSSL_X509 *x) { - int numOfExt; WOLFSSL_X509 *x509 = (WOLFSSL_X509*)x; WOLFSSL_ENTER("wolfSSL_X509_get0_extensions"); @@ -420,10 +422,9 @@ const WOLFSSL_STACK *wolfSSL_X509_get0_extensions(const WOLFSSL_X509 *x) return NULL; } - numOfExt = wolfSSL_X509_get_ext_count(x509); - - if (numOfExt != wolfSSL_sk_num(x509->ext_sk_full)) { - wolfSSL_sk_pop_free(x509->ext_sk_full, NULL); + /* Only build the stack once. Freeing and rebuilding it here would leave + * the caller of a previous call holding a dangling pointer. */ + if (x509->ext_sk_full == NULL) { x509->ext_sk_full = generateExtStack(x); } @@ -1155,15 +1156,13 @@ WOLFSSL_X509_EXTENSION* wolfSSL_X509_set_ext(WOLFSSL_X509* x509, int loc) tmpIdx = idx + length; /* Get CRITICAL. If not present, defaults to false. - * It present, must be a valid TRUE */ + * Accept any one byte value like the certificate parser does. */ if ((tmpIdx < (word32)sz) && (input[tmpIdx] == ASN_BOOLEAN)) { if (((tmpIdx + 2) >= (word32)sz) || /* Check bool length */ - (input[tmpIdx+1] != 1) || - /* Assert true if CRITICAL present */ - (input[tmpIdx+2] != 0xff)) + (input[tmpIdx+1] != 1)) { WOLFSSL_MSG("Error decoding unknown extension data"); wolfSSL_X509_EXTENSION_free(ext); @@ -1172,7 +1171,7 @@ WOLFSSL_X509_EXTENSION* wolfSSL_X509_set_ext(WOLFSSL_X509* x509, int loc) return NULL; } - ext->crit = 1; + ext->crit = (input[tmpIdx+2] != 0); tmpIdx += 3; } @@ -1189,7 +1188,8 @@ WOLFSSL_X509_EXTENSION* wolfSSL_X509_set_ext(WOLFSSL_X509* x509, int loc) tmpIdx++; - if (GetLength(input, &tmpIdx, &length, (word32)sz) <= 0) { + /* An empty OCTET STRING is accepted by the certificate parser. */ + if (GetLength(input, &tmpIdx, &length, (word32)sz) < 0) { WOLFSSL_MSG("Error: Invalid Input Length."); wolfSSL_X509_EXTENSION_free(ext); FreeDecodedCert(cert); @@ -13128,6 +13128,12 @@ static int CertFromX509(Cert* cert, WOLFSSL_X509* x509) /* Put in the new certificate encoding into the x509 object. */ FreeDer(&x509->derCert); + #if defined(WOLFSSL_QT) || defined(OPENSSL_ALL) || defined(OPENSSL_EXTRA) + /* Extension stack cached by wolfSSL_X509_get0_extensions() was built + * from the old encoding. */ + wolfSSL_sk_pop_free(x509->ext_sk_full, NULL); + x509->ext_sk_full = NULL; + #endif type = CERT_TYPE; #ifdef WOLFSSL_CERT_REQ if (req) { @@ -17301,6 +17307,10 @@ static int regenX509REQDerBuffer(WOLFSSL_X509* x509) if (wolfssl_x509_make_der(x509, 1, der, &derSz, 0) == WOLFSSL_SUCCESS) { FreeDer(&x509->derCert); + /* Extension stack cached by wolfSSL_X509_get0_extensions() was built + * from the old encoding. */ + wolfSSL_sk_pop_free(x509->ext_sk_full, NULL); + x509->ext_sk_full = NULL; if (AllocDer(&x509->derCert, (word32)derSz, CERT_TYPE, x509->heap) == 0) { XMEMCPY(x509->derCert->buffer, der, derSz); diff --git a/tests/api/test_ossl_x509_ext.c b/tests/api/test_ossl_x509_ext.c index 234a407e2a2..f647bab1fdd 100644 --- a/tests/api/test_ossl_x509_ext.c +++ b/tests/api/test_ossl_x509_ext.c @@ -750,6 +750,74 @@ int test_wolfSSL_X509_stack_extensions(void) return EXPECT_RESULT(); } +/* wolfSSL_X509_get0_extensions() returns a stack the X509 owns, so a later + * call must hand back the same objects: an application holding an + * X509_EXTENSION from an earlier call still reads through it. The encodings + * the compat layer could not convert - here an explicit "critical FALSE", + * which the certificate parser accepts - used to make the entry count differ + * from X509_get_ext_count() and the getter rebuild the stack every call. */ +int test_wolfSSL_X509_get0_extensions_stable(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && !defined(NO_CERTS) && !defined(NO_FILESYSTEM) && \ + defined(HAVE_ECC) + /* keyUsage extension: OID 2.5.29.15 then BOOLEAN TRUE. */ + static const byte keyUsageCrit[] = { + 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff + }; + WOLFSSL_X509* x509 = NULL; + const WOLFSSL_STACK* sk1 = NULL; + const WOLFSSL_STACK* sk2 = NULL; + WOLFSSL_X509_EXTENSION* ext = NULL; + const unsigned char* p = NULL; + byte* der = NULL; + word32 derSz = 0; + word32 i; + int found = 0; + XFILE f = XBADFILE; + + ExpectTrue((f = XFOPEN("./certs/server-ecc.der", "rb")) != XBADFILE); + ExpectNotNull(der = (byte*)XMALLOC(FOURK_BUF, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + if ((f != XBADFILE) && (der != NULL)) { + derSz = (word32)XFREAD(der, 1, FOURK_BUF, f); + } + if (f != XBADFILE) + XFCLOSE(f); + ExpectIntGT(derSz, sizeof(keyUsageCrit)); + + /* Turn the critical flag into an explicit FALSE. */ + for (i = 0; (der != NULL) && (i + sizeof(keyUsageCrit) <= derSz); i++) { + if (XMEMCMP(der + i, keyUsageCrit, sizeof(keyUsageCrit)) == 0) { + der[i + sizeof(keyUsageCrit) - 1] = 0x00; + found = 1; + break; + } + } + ExpectIntEQ(found, 1); + + p = der; + ExpectNotNull(x509 = wolfSSL_d2i_X509(NULL, &p, (int)derSz)); + + ExpectNotNull(sk1 = wolfSSL_X509_get0_extensions(x509)); + ExpectIntEQ(X509v3_get_ext_count(sk1), + wolfSSL_X509_get_ext_count(x509)); + /* The extension that was turned into an explicit FALSE is still there. */ + ExpectIntGE(X509v3_get_ext_by_NID(sk1, NID_key_usage, -1), 0); + ExpectNotNull(ext = X509v3_get_ext(sk1, 0)); + + /* Second call must not release what the first one returned. */ + ExpectNotNull(sk2 = wolfSSL_X509_get0_extensions(x509)); + ExpectPtrEq(sk1, sk2); + ExpectNotNull(wolfSSL_X509_EXTENSION_get_object(ext)); + ExpectPtrEq(X509v3_get_ext(sk2, 0), ext); + + wolfSSL_X509_free(x509); + XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + int test_wolfSSL_X509_EXTENSION_new(void) { EXPECT_DECLS; diff --git a/tests/api/test_ossl_x509_ext.h b/tests/api/test_ossl_x509_ext.h index a95b34a8050..80984dcde69 100644 --- a/tests/api/test_ossl_x509_ext.h +++ b/tests/api/test_ossl_x509_ext.h @@ -33,6 +33,7 @@ int test_wolfSSL_X509_add_ext(void); int test_wolfSSL_X509_add_ext_dirname_san_rejected(void); int test_wolfSSL_X509_get_ext_count(void); int test_wolfSSL_X509_stack_extensions(void); +int test_wolfSSL_X509_get0_extensions_stable(void); int test_wolfSSL_X509_EXTENSION_new(void); int test_wolfSSL_X509_EXTENSION_dup(void); int test_wolfSSL_X509_EXTENSION_get_object(void); @@ -71,6 +72,8 @@ int test_wolfSSL_X509_set_ext_oid_collision(void); test_wolfSSL_X509_add_ext_dirname_san_rejected), \ TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_X509_get_ext_count), \ TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_X509_stack_extensions), \ + TEST_DECL_GROUP("ossl_x509_ext", \ + test_wolfSSL_X509_get0_extensions_stable), \ TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_X509_EXTENSION_new), \ TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_X509_EXTENSION_dup), \ TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_X509_EXTENSION_get_object), \ From 3f6c465395fedcaa4481d570ec2fab04c0a0cdea Mon Sep 17 00:00:00 2001 From: Kareem Date: Thu, 1 Oct 2026 16:28:36 -0700 Subject: [PATCH 3/4] Only read GENERAL_NAME types with strings in wolfSSL_X509V3_EXT_print. Thanks to Anthropic for the report. --- src/x509.c | 23 +++++++++++++++++++++++ tests/api/test_ossl_x509_ext.c | 31 +++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/x509.c b/src/x509.c index 905abbe63bf..84e180bbbc0 100644 --- a/src/x509.c +++ b/src/x509.c @@ -1575,8 +1575,31 @@ int wolfSSL_X509V3_EXT_print(WOLFSSL_BIO *out, WOLFSSL_X509_EXTENSION *ext, sk = ext->ext_sk; while (sk != NULL) { if (sk->type == STACK_TYPE_GEN_NAME && sk->data.gn) { + /* Only the string backed GENERAL_NAME types hold a + * WOLFSSL_ASN1_STRING in the union. otherName, dirName + * and registeredID carry other objects there. */ + switch (sk->data.gn->type) { + case WOLFSSL_GEN_EMAIL: + case WOLFSSL_GEN_DNS: + case WOLFSSL_GEN_URI: + case WOLFSSL_GEN_IPADD: + case WOLFSSL_GEN_IA5: + break; + default: + WOLFSSL_MSG("Unsupported GENERAL_NAME type"); + sk = sk->next; + continue; + } /* str is GENERAL_NAME for subject alternative name ext */ str = sk->data.gn->d.ia5; + if (str == NULL) { + /* Skip it like an unsupported type above: a name + * built through the API can carry the type without + * the string. */ + WOLFSSL_MSG("NULL GENERAL_NAME string"); + sk = sk->next; + continue; + } len = str->length + 2; /* + 2 for NULL char and "," */ if (len > tmpSz) { WOLFSSL_MSG("len greater than buffer size"); diff --git a/tests/api/test_ossl_x509_ext.c b/tests/api/test_ossl_x509_ext.c index f647bab1fdd..b49a5992a42 100644 --- a/tests/api/test_ossl_x509_ext.c +++ b/tests/api/test_ossl_x509_ext.c @@ -1872,6 +1872,37 @@ int test_wolfSSL_X509V3_EXT_print(void) X509_free(x509); } +#ifdef WOLFSSL_FPKI + { + /* rid-cert.der SAN: otherName (UPN), registeredID, DNS, URI and a + * second otherName. With WOLFSSL_FPKI the otherName entries are put + * in the GENERAL_NAME stack and must not be read as an ASN1_STRING. */ + const char* ridCert = "./certs/rid-cert.der"; + X509* x509 = NULL; + BIO* bio = NULL; + X509_EXTENSION* ext = NULL; + char buf[256]; + int loc = 0; + int len = 0; + + ExpectNotNull(x509 = wolfSSL_X509_load_certificate_file(ridCert, + WOLFSSL_FILETYPE_ASN1)); + ExpectNotNull(bio = BIO_new(BIO_s_mem())); + + ExpectIntGT(loc = X509_get_ext_by_NID(x509, NID_subject_alt_name, -1), + -1); + ExpectNotNull(ext = X509_get_ext(x509, loc)); + ExpectIntEQ(X509V3_EXT_print(bio, ext, 0, 0), 1); + + XMEMSET(buf, 0, sizeof(buf)); + ExpectIntGT(len = BIO_read(bio, buf, sizeof(buf) - 1), 0); + ExpectNotNull(XSTRSTR(buf, "www.example.org")); + + BIO_free(bio); + X509_free(x509); + } +#endif /* WOLFSSL_FPKI */ + { BIO* bio = NULL; X509_EXTENSION* ext = NULL; From eb72adf45f2a25e08511c7b8e9f7937e120d34f0 Mon Sep 17 00:00:00 2001 From: Kareem Date: Thu, 1 Oct 2026 16:42:02 -0700 Subject: [PATCH 4/4] Properly handle NULLs inside of DNs in X509_NAME_oneline. Thanks to Anthropic for the report. --- src/x509.c | 66 ++++++++++++++++++++++++++++++--- tests/api/test_ossl_x509_name.c | 53 ++++++++++++++++++++++++++ tests/api/test_ossl_x509_name.h | 5 ++- wolfssl/openssl/x509.h | 6 ++- 4 files changed, 122 insertions(+), 8 deletions(-) diff --git a/src/x509.c b/src/x509.c index 84e180bbbc0..6f9f3454748 100644 --- a/src/x509.c +++ b/src/x509.c @@ -14715,6 +14715,47 @@ WOLFSSL_ASN1_OBJECT* wolfSSL_X509_NAME_ENTRY_get_object( * HAVE_STUNNEL || WOLFSSL_NGINX || HAVE_POCO_LIB || WOLFSSL_HAPROXY */ #if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) + /* Length of the escape a NUL byte is written as. */ + #define NAME_NUL_ESC_SZ 4 + + /* Write a name entry value into a one line name, taking its length from + * the entry rather than from the data, and writing a NUL byte as "\x00" + * as OpenSSL's X509_NAME_oneline() does. Treating the value as a C string + * drops everything from a NUL on and lets the rest of the name follow it, + * so two different names can read the same. + * + * out - buffer for the value, NULL to only get the length + * outSz - space available in out, ignored when out is NULL + * + * Returns the number of bytes written, or needed when out is NULL. + * Returns BUFFER_E when the value does not fit or the length would + * overflow. + */ + static int AddEntryValue(char* out, int outSz, const unsigned char* val, + int valSz) + { + int i; + int sz = 0; + + for (i = 0; (val != NULL) && (i < valSz); i++) { + int need = (val[i] == '\0') ? NAME_NUL_ESC_SZ : 1; + + if (need > INT_MAX - sz) + return BUFFER_E; + if (out != NULL) { + if (need > outSz - sz) + return BUFFER_E; + if (need == 1) + out[sz] = (char)val[i]; + else + XMEMCPY(out + sz, "\\x00", NAME_NUL_ESC_SZ); + } + sz += need; + } + + return sz; + } + /* add all entry of type "nid" to the buffer "fullName" and advance "idx" * since number of entries is small, a brute force search is used here * returns the number of entries added @@ -14747,15 +14788,16 @@ WOLFSSL_ASN1_OBJECT* wolfSSL_X509_NAME_ENTRY_get_object( data = wolfSSL_ASN1_STRING_data(e->value); if (data != NULL) { - sz = (int)XSTRLEN((const char*)data); - XMEMCPY(fullName + *idx, data, sz); + sz = AddEntryValue(fullName + *idx, fullNameSz - *idx, + data, wolfSSL_ASN1_STRING_length(e->value)); + if (sz < 0) + return sz; *idx += sz; } ret++; } } - (void)fullNameSz; return ret; } @@ -14764,7 +14806,7 @@ WOLFSSL_ASN1_OBJECT* wolfSSL_X509_NAME_ENTRY_get_object( * returns 0 on success */ static int RebuildFullName(WOLFSSL_X509_NAME* name) { - int totalLen = 0, i, idx, entryCount = 0; + int totalLen = 0, i, idx, entryCount = 0, valLen; if (name == NULL) return BAD_FUNC_ARG; @@ -14779,8 +14821,20 @@ WOLFSSL_ASN1_OBJECT* wolfSSL_X509_NAME_ENTRY_get_object( if (obj == NULL) return BAD_FUNC_ARG; - totalLen += (int)XSTRLEN(obj->sName) + 2;/*+2 for '/' and '=' */ - totalLen += wolfSSL_ASN1_STRING_length(e->value); + /* Each addition is checked against the room left below + * INT_MAX - 1, so that totalLen + 1 (the NUL added later) + * stays a positive int. */ + valLen = (int)XSTRLEN(obj->sName) + 2;/*+2 for '/' and '=' */ + if (valLen > INT_MAX - 1 - totalLen) + return BUFFER_E; + totalLen += valLen; + + valLen = AddEntryValue(NULL, 0, + wolfSSL_ASN1_STRING_data(e->value), + wolfSSL_ASN1_STRING_length(e->value)); + if ((valLen < 0) || (valLen > INT_MAX - 1 - totalLen)) + return BUFFER_E; + totalLen += valLen; } } diff --git a/tests/api/test_ossl_x509_name.c b/tests/api/test_ossl_x509_name.c index 4539d82aaa0..bd94eb50cd3 100644 --- a/tests/api/test_ossl_x509_name.c +++ b/tests/api/test_ossl_x509_name.c @@ -956,3 +956,56 @@ int test_wolfSSL_X509_NAME_ENTRY_get_object(void) return EXPECT_RESULT(); } + +/* A one line name must not render two different DNs the same way. The value + * of a name entry carries its own length and may contain a NUL byte, so + * X509_NAME_oneline() has to keep that byte visible: taken as a C string the + * value stops there, the attributes after it are still appended, and the + * result is the same string a different DN produces - which also makes + * X509_NAME_cmp() report a match. */ +int test_wolfSSL_X509_NAME_oneline_embedded_nul(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && !defined(NO_CERTS) + static const unsigned char nulCn[] = { + 'a', 'd', 'm', 'i', 'n', 0x00, '.', 'e', 'v', 'i', 'l', '.', 'c', 'o', + 'm' + }; + static const unsigned char plainCn[] = "admin"; + static const unsigned char country[] = "US"; + static const unsigned char org[] = "Corp"; + X509_NAME* legit = NULL; + X509_NAME* spoof = NULL; + char* legitLine = NULL; + char* spoofLine = NULL; + + ExpectNotNull(legit = X509_NAME_new()); + ExpectIntEQ(X509_NAME_add_entry_by_NID(legit, NID_countryName, + V_ASN1_PRINTABLESTRING, country, 2, -1, 0), 1); + ExpectIntEQ(X509_NAME_add_entry_by_NID(legit, NID_commonName, + V_ASN1_UTF8STRING, plainCn, 5, -1, 0), 1); + ExpectIntEQ(X509_NAME_add_entry_by_NID(legit, NID_organizationName, + V_ASN1_UTF8STRING, org, 4, -1, 0), 1); + + ExpectNotNull(spoof = X509_NAME_new()); + ExpectIntEQ(X509_NAME_add_entry_by_NID(spoof, NID_countryName, + V_ASN1_PRINTABLESTRING, country, 2, -1, 0), 1); + ExpectIntEQ(X509_NAME_add_entry_by_NID(spoof, NID_commonName, + V_ASN1_UTF8STRING, nulCn, (int)sizeof(nulCn), -1, 0), 1); + ExpectIntEQ(X509_NAME_add_entry_by_NID(spoof, NID_organizationName, + V_ASN1_UTF8STRING, org, 4, -1, 0), 1); + + ExpectNotNull(legitLine = X509_NAME_oneline(legit, NULL, 0)); + ExpectNotNull(spoofLine = X509_NAME_oneline(spoof, NULL, 0)); + ExpectStrEQ(legitLine, "/C=US/CN=admin/O=Corp"); + ExpectStrNE(spoofLine, legitLine); + ExpectNotNull(XSTRSTR(spoofLine, ".evil.com")); + ExpectIntNE(X509_NAME_cmp(legit, spoof), 0); + + XFREE(legitLine, NULL, DYNAMIC_TYPE_OPENSSL); + XFREE(spoofLine, NULL, DYNAMIC_TYPE_OPENSSL); + X509_NAME_free(legit); + X509_NAME_free(spoof); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_ossl_x509_name.h b/tests/api/test_ossl_x509_name.h index f6763d66479..fde3d622fc9 100644 --- a/tests/api/test_ossl_x509_name.h +++ b/tests/api/test_ossl_x509_name.h @@ -30,6 +30,7 @@ int test_wolfSSL_X509_NAME_hash(void); int test_wolfSSL_X509_NAME_print_ex(void); int test_wolfSSL_X509_NAME_ENTRY(void); int test_wolfSSL_X509_NAME_ENTRY_get_object(void); +int test_wolfSSL_X509_NAME_oneline_embedded_nul(void); #define TEST_OSSL_X509_NAME_DECLS \ TEST_DECL_GROUP("ossl_x509_name", test_wolfSSL_X509_NAME_get_entry), \ @@ -37,6 +38,8 @@ int test_wolfSSL_X509_NAME_ENTRY_get_object(void); TEST_DECL_GROUP("ossl_x509_name", test_wolfSSL_X509_NAME_hash), \ TEST_DECL_GROUP("ossl_x509_name", test_wolfSSL_X509_NAME_print_ex), \ TEST_DECL_GROUP("ossl_x509_name", test_wolfSSL_X509_NAME_ENTRY), \ - TEST_DECL_GROUP("ossl_x509_name", test_wolfSSL_X509_NAME_ENTRY_get_object) + TEST_DECL_GROUP("ossl_x509_name", test_wolfSSL_X509_NAME_ENTRY_get_object), \ + TEST_DECL_GROUP("ossl_x509_name", \ + test_wolfSSL_X509_NAME_oneline_embedded_nul) #endif /* WOLFCRYPT_TEST_OSSL_X509_NAME_H */ diff --git a/wolfssl/openssl/x509.h b/wolfssl/openssl/x509.h index a22a46b4ad6..97fde055bdd 100644 --- a/wolfssl/openssl/x509.h +++ b/wolfssl/openssl/x509.h @@ -77,7 +77,11 @@ WOLFSSL_XN_FLAG_SPC_EQ | \ WOLFSSL_XN_FLAG_FN_LN | \ WOLFSSL_XN_FLAG_FN_ALIGN) -#define WOLFSSL_XN_FLAG_ONELINE (WOLFSSL_XN_FLAG_SEP_CPLUS_SPC | WOLFSSL_XN_FLAG_SPC_EQ | WOLFSSL_XN_FLAG_FN_SN) +#define WOLFSSL_XN_FLAG_ONELINE (WOLFSSL_ASN1_STRFLGS_RFC2253 | \ + WOLFSSL_ASN1_STRFLGS_ESC_QUOTE | \ + WOLFSSL_XN_FLAG_SEP_CPLUS_SPC | \ + WOLFSSL_XN_FLAG_SPC_EQ | \ + WOLFSSL_XN_FLAG_FN_SN) #ifndef OPENSSL_COEXIST