Skip to content

Commit e1a2ba3

Browse files
committed
Restore error code from DecodeGeneralName
1 parent 0cecccd commit e1a2ba3

6 files changed

Lines changed: 112 additions & 53 deletions

File tree

doc/dox_comments/header_files/ssl.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13859,6 +13859,14 @@ int wolfSSL_set_msg_callback_arg(WOLFSSL *ssl, void* arg);
1385913859

1386013860
\param cert a pointer to the wolfSSL_X509 structure.
1386113861

13862+
\warning The returned value is a bare C string with no length. A dNSName,
13863+
rfc822Name, or uniformResourceIdentifier SAN may legally contain an embedded
13864+
NUL byte (RFC 6125 Sec. 6.3 treats such a name as an invalid presented
13865+
identifier, not a malformed certificate, so the certificate still parses),
13866+
which silently truncates the returned string. Do not use strlen or strcmp on
13867+
the result for security comparisons; use wolfSSL_X509_get_ext_d2i and the
13868+
GENERAL_NAME ASN1_STRING length instead.
13869+
1386213870
_Example_
1386313871
\code
1386413872
WOLFSSL_X509 x509 = (WOLFSSL_X509*)XMALLOC(sizeof(WOLFSSL_X509), NULL,
@@ -13872,6 +13880,7 @@ int wolfSSL_set_msg_callback_arg(WOLFSSL *ssl, void* arg);
1387213880

1387313881
\sa wolfSSL_X509_get_issuer_name
1387413882
\sa wolfSSL_X509_get_subject_name
13883+
\sa wolfSSL_X509_get_ext_d2i
1387513884
*/
1387613885
char* wolfSSL_X509_get_next_altname(WOLFSSL_X509*);
1387713886

tests/api/test_asn.c

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1378,7 +1378,9 @@ int test_wc_DecodeRsaPssParams(void)
13781378
}
13791379

13801380
/* Test that DecodeAltNames rejects a SAN entry whose length exceeds the
1381-
* remaining SEQUENCE length (integer underflow on the length tracker). */
1381+
* remaining SEQUENCE length (integer underflow on the length tracker), and
1382+
* that a dNSName SAN carrying an embedded NUL is stored rather than rejected
1383+
* so verification reports a name mismatch instead of a parse error. */
13821384
int test_DecodeAltNames_length_underflow(void)
13831385
{
13841386
EXPECT_DECLS;
@@ -1481,14 +1483,29 @@ int test_DecodeAltNames_length_underflow(void)
14811483
WC_NO_ERR_TRACE(ASN_PARSE_E));
14821484
wc_FreeDecodedCert(&cert);
14831485

1484-
/* NUL in dNSName SAN must be rejected per RFC 5280 4.2.1.6. */
1486+
/* An embedded NUL in a dNSName SAN is an invalid presented identifier
1487+
* (RFC 6125 Sec. 6.3 / RFC 9525 Sec. 6.3), not a malformed certificate.
1488+
* Set the third byte of the 4-byte dNSName ("a*b*") to NUL, giving
1489+
* "a*\0*". The certificate must still parse: the entry is stored with
1490+
* its embedded NUL intact (length 4, not truncated) so that hostname
1491+
* verification reports DOMAIN_NAME_MISMATCH rather than the parser
1492+
* aborting with ASN_PARSE_E (regression from curl test 311). */
14851493
XMEMCPY(bad_san_cert, good_san_cert, sizeof(good_san_cert));
14861494
bad_san_cert[SAN_SEQ_LEN_OFFSET + 5] = 0x00;
14871495

14881496
wc_InitDecodedCert(&cert, bad_san_cert, (word32)sizeof(bad_san_cert),
14891497
NULL);
1490-
ExpectIntEQ(wc_ParseCert(&cert, CERT_TYPE, NO_VERIFY, NULL),
1491-
WC_NO_ERR_TRACE(ASN_PARSE_E));
1498+
ExpectIntEQ(wc_ParseCert(&cert, CERT_TYPE, NO_VERIFY, NULL), 0);
1499+
ExpectNotNull(cert.altNames);
1500+
if (cert.altNames != NULL) {
1501+
ExpectIntEQ(cert.altNames->type, ASN_DNS_TYPE);
1502+
ExpectIntEQ(cert.altNames->len, 4);
1503+
/* Embedded NUL preserved at offset 2: stored, not truncated. */
1504+
ExpectNotNull(cert.altNames->name);
1505+
if (cert.altNames->name != NULL) {
1506+
ExpectIntEQ(cert.altNames->name[2], 0x00);
1507+
}
1508+
}
14921509
wc_FreeDecodedCert(&cert);
14931510

14941511
#endif /* !NO_CERTS && !NO_RSA && !NO_ASN */

tests/api/test_ossl_x509.c

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1795,6 +1795,73 @@ int test_wolfSSL_MatchDomainName_idn(void)
17951795
return EXPECT_RESULT();
17961796
}
17971797

1798+
/* Regression test mirroring curl test 311 ("HTTPS wrong subjectAltName but
1799+
* right CN"). The leaf Subject CN is the wanted host ("localhost") while its
1800+
* only dNSName SAN carries an embedded NUL ("localhost\0h"). Such a SAN is an
1801+
* invalid presented identifier (RFC 6125 Sec. 6.3 / RFC 9525 Sec. 6.3), not a
1802+
* malformed certificate, so the certificate must parse. Verification must then
1803+
* report DOMAIN_NAME_MISMATCH: the SAN presence suppresses Subject CN fallback
1804+
* and the NUL name can never match the NUL-free reference host, so the
1805+
* otherwise-correct CN must not rescue it. Before the parser stored rather than
1806+
* rejected the entry, verification aborted earlier with ASN_PARSE_E. */
1807+
int test_wolfSSL_X509_check_host_embedded_nul_san(void)
1808+
{
1809+
EXPECT_DECLS;
1810+
#if !defined(NO_FILESYSTEM) && !defined(NO_CERTS) && !defined(NO_RSA) && \
1811+
defined(OPENSSL_EXTRA) && defined(WOLFSSL_CERT_GEN) && \
1812+
defined(WOLFSSL_CERT_EXT) && defined(WOLFSSL_ALT_NAMES) && \
1813+
!defined(NO_SHA256) && !defined(NO_ASN)
1814+
WOLFSSL_EVP_PKEY* priv = NULL;
1815+
WOLFSSL_X509* leaf = NULL;
1816+
const char* server_cert = "./certs/test/server-goodcn.pem";
1817+
const char host[] = "localhost";
1818+
/* dNSName "localhost" + embedded NUL + 'h', length 11. */
1819+
static const byte nulSan[] = {
1820+
'l', 'o', 'c', 'a', 'l', 'h', 'o', 's', 't', '\0', 'h'
1821+
};
1822+
byte* keyPt = NULL;
1823+
const byte* der = NULL;
1824+
int derSz = 0;
1825+
DecodedCert dCert;
1826+
int parseRet = -1;
1827+
1828+
keyPt = (byte*)server_key_der_2048;
1829+
ExpectNotNull(priv = wolfSSL_d2i_PrivateKey(EVP_PKEY_RSA, NULL,
1830+
(const unsigned char**)&keyPt, sizeof_server_key_der_2048));
1831+
1832+
/* server-goodcn.pem has CN=localhost and no SAN; add one dNSName SAN that
1833+
* carries an embedded NUL, then re-sign so the SAN is serialized. */
1834+
ExpectNotNull(leaf = wolfSSL_X509_load_certificate_file(server_cert,
1835+
WOLFSSL_FILETYPE_PEM));
1836+
ExpectIntEQ(wolfSSL_X509_add_altname_ex(leaf, (const char*)nulSan,
1837+
sizeof(nulSan), ASN_DNS_TYPE), WOLFSSL_SUCCESS);
1838+
ExpectIntGT(wolfSSL_X509_sign(leaf, priv, EVP_sha256()), 0);
1839+
1840+
/* Regression pin: the signed certificate must parse despite the embedded
1841+
* NUL (it aborted with ASN_PARSE_E before the parser stored the entry). */
1842+
ExpectNotNull(der = wolfSSL_X509_get_der(leaf, &derSz));
1843+
ExpectIntGT(derSz, 0);
1844+
if ((der != NULL) && (derSz > 0)) {
1845+
wc_InitDecodedCert(&dCert, der, (word32)derSz, NULL);
1846+
parseRet = wc_ParseCert(&dCert, CERT_TYPE, NO_VERIFY, NULL);
1847+
ExpectIntEQ(parseRet, 0);
1848+
wc_FreeDecodedCert(&dCert);
1849+
}
1850+
1851+
/* Security pin: the host must still be rejected. With parsing now
1852+
* succeeding, the only remaining failure path in check_host for a
1853+
* non-IP host is the hostname comparison, so a failure here means
1854+
* DOMAIN_NAME_MISMATCH: the SAN presence suppressed CN fallback and the
1855+
* NUL name did not match. The correct CN must not rescue the wrong SAN. */
1856+
ExpectIntEQ(wolfSSL_X509_check_host(leaf, host, XSTRLEN(host), 0, NULL),
1857+
WC_NO_ERR_TRACE(WOLFSSL_FAILURE));
1858+
1859+
wolfSSL_X509_free(leaf);
1860+
wolfSSL_EVP_PKEY_free(priv);
1861+
#endif
1862+
return EXPECT_RESULT();
1863+
}
1864+
17981865
int test_wolfSSL_X509_max_altnames(void)
17991866
{
18001867
EXPECT_DECLS;

tests/api/test_ossl_x509.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ int test_wolfSSL_X509_name_match2(void);
5050
int test_wolfSSL_X509_name_match3(void);
5151
int test_wolfssl_local_IsValidFQDN(void);
5252
int test_wolfSSL_MatchDomainName_idn(void);
53+
int test_wolfSSL_X509_check_host_embedded_nul_san(void);
5354
int test_wolfSSL_X509_max_altnames(void);
5455
int test_wolfSSL_X509_max_name_constraints(void);
5556
int test_wolfSSL_X509_check_ca(void);
@@ -83,6 +84,7 @@ int test_wolfSSL_X509_cmp(void);
8384
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_name_match3), \
8485
TEST_DECL_GROUP("ossl_x509", test_wolfssl_local_IsValidFQDN), \
8586
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_MatchDomainName_idn), \
87+
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_check_host_embedded_nul_san),\
8688
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_max_altnames), \
8789
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_max_name_constraints), \
8890
TEST_DECL_GROUP("ossl_x509", test_wolfSSL_X509_check_ca), \

wolfcrypt/src/asn.c

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18900,31 +18900,20 @@ static int DecodeOtherName(DecodedCert* cert, const byte* input,
1890018900
* @return ASN_UNKNOWN_OID_E when the OID cannot be verified.
1890118901
* @return MEMORY_E when dynamic memory allocation fails.
1890218902
*/
18903-
/* Reject IA5String SAN content that cannot legally appear in
18904-
* dNSName / rfc822Name / URI per RFC 5280 4.2.1.6. Currently just NUL. */
18905-
static int DecodeGeneralNameCheckChars(const byte* input, int len)
18906-
{
18907-
int i;
18908-
for (i = 0; i < len; i++) {
18909-
if (input[i] == 0) {
18910-
return ASN_PARSE_E;
18911-
}
18912-
}
18913-
return 0;
18914-
}
18915-
1891618903
static int DecodeGeneralName(const byte* input, word32* inOutIdx, byte tag,
1891718904
int len, DecodedCert* cert)
1891818905
{
1891918906
int ret = 0;
1892018907
word32 idx = *inOutIdx;
1892118908

18922-
/* GeneralName choice: dnsName */
18909+
/* GeneralName choice: dnsName.
18910+
* An embedded NUL makes a dNSName an invalid presented identifier
18911+
* (RFC 6125 Sec. 6.3 / RFC 9525 Sec. 6.3), not a malformed certificate.
18912+
* Store it so its presence still suppresses Subject CN fallback, but
18913+
* length-based matching in MatchDomainName never matches a NUL-free
18914+
* reference hostname. The result is DOMAIN_NAME_MISMATCH at verification
18915+
* time rather than ASN_PARSE_E at parse time. */
1892318916
if (tag == (ASN_CONTEXT_SPECIFIC | ASN_DNS_TYPE)) {
18924-
ret = DecodeGeneralNameCheckChars(input + idx, len);
18925-
if (ret != 0) {
18926-
return ret;
18927-
}
1892818917
ret = SetDNSEntry(cert->heap, (const char*)(input + idx), len,
1892918918
ASN_DNS_TYPE, &cert->altNames);
1893018919
if (ret == 0) {
@@ -18952,10 +18941,6 @@ static int DecodeGeneralName(const byte* input, word32* inOutIdx, byte tag,
1895218941
}
1895318942
/* GeneralName choice: rfc822Name */
1895418943
else if (tag == (ASN_CONTEXT_SPECIFIC | ASN_RFC822_TYPE)) {
18955-
ret = DecodeGeneralNameCheckChars(input + idx, len);
18956-
if (ret != 0) {
18957-
return ret;
18958-
}
1895918944
ret = SetDNSEntry(cert->heap, (const char*)(input + idx), len,
1896018945
ASN_RFC822_TYPE, &cert->altEmailNames);
1896118946
if (ret == 0) {
@@ -18964,10 +18949,6 @@ static int DecodeGeneralName(const byte* input, word32* inOutIdx, byte tag,
1896418949
}
1896518950
/* GeneralName choice: uniformResourceIdentifier */
1896618951
else if (tag == (ASN_CONTEXT_SPECIFIC | ASN_URI_TYPE)) {
18967-
ret = DecodeGeneralNameCheckChars(input + idx, len);
18968-
if (ret != 0) {
18969-
return ret;
18970-
}
1897118952
WOLFSSL_MSG("\tPutting URI into list but not using");
1897218953

1897318954
#ifndef WOLFSSL_NO_ASN_STRICT

wolfcrypt/src/asn_orig.c

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3125,19 +3125,6 @@ static int DecodeConstructedOtherName(DecodedCert* cert, const byte* input,
31253125
return ret;
31263126
}
31273127

3128-
/* Reject IA5String SAN content that cannot legally appear in
3129-
* dNSName / rfc822Name / URI per RFC 5280 4.2.1.6. Currently just NUL. */
3130-
static int DecodeGeneralNameCheckChars(const byte* input, int len)
3131-
{
3132-
int i;
3133-
for (i = 0; i < len; i++) {
3134-
if (input[i] == 0) {
3135-
return ASN_PARSE_E;
3136-
}
3137-
}
3138-
return 0;
3139-
}
3140-
31413128
static int DecodeAltNames(const byte* input, word32 sz, DecodedCert* cert)
31423129
{
31433130
word32 idx = 0;
@@ -3200,9 +3187,12 @@ static int DecodeAltNames(const byte* input, word32 sz, DecodedCert* cert)
32003187
if ((word32)strLen + idx > sz) {
32013188
return BUFFER_E;
32023189
}
3203-
if (DecodeGeneralNameCheckChars(&input[idx], strLen) != 0) {
3204-
return ASN_PARSE_E;
3205-
}
3190+
/* An embedded NUL makes a dNSName an invalid presented identifier
3191+
* (RFC 6125 Sec. 6.3), not a malformed certificate. Store it so
3192+
* its presence still suppresses Subject CN fallback; length-based
3193+
* matching in MatchDomainName never matches a NUL-free hostname,
3194+
* giving DOMAIN_NAME_MISMATCH at verification rather than
3195+
* ASN_PARSE_E at parse time. */
32063196

32073197
dnsEntry = AltNameNew(cert->heap);
32083198
if (dnsEntry == NULL) {
@@ -3292,9 +3282,6 @@ static int DecodeAltNames(const byte* input, word32 sz, DecodedCert* cert)
32923282
if ((word32)strLen + idx > sz) {
32933283
return BUFFER_E;
32943284
}
3295-
if (DecodeGeneralNameCheckChars(&input[idx], strLen) != 0) {
3296-
return ASN_PARSE_E;
3297-
}
32983285

32993286
emailEntry = AltNameNew(cert->heap);
33003287
if (emailEntry == NULL) {
@@ -3341,10 +3328,6 @@ static int DecodeAltNames(const byte* input, word32 sz, DecodedCert* cert)
33413328
return BUFFER_E;
33423329
}
33433330

3344-
if (DecodeGeneralNameCheckChars(&input[idx], strLen) != 0) {
3345-
return ASN_PARSE_E;
3346-
}
3347-
33483331
#ifndef WOLFSSL_NO_ASN_STRICT
33493332
/* Verify RFC 5280 Sec 4.2.1.6 rule:
33503333
"The name MUST NOT be a relative URI"

0 commit comments

Comments
 (0)