diff --git a/src/x509.c b/src/x509.c index 73f4c3c92eb..ade3553f788 100644 --- a/src/x509.c +++ b/src/x509.c @@ -945,10 +945,20 @@ WOLFSSL_X509_EXTENSION* wolfSSL_X509_set_ext(WOLFSSL_X509* x509, int loc) WC_FREE_VAR_EX(cert, NULL, DYNAMIC_TYPE_DCERT); return NULL; } - a->length = (int)x509->pathLength; - - /* Save ASN1_INTEGER in x509 extension */ - ext->obj->pathlen = a; + /* RFC 5280 4.2.1.9: only populate pathLen when the + * pathLenConstraint field was actually present. When it is + * absent no limit is imposed, and pathlen must remain NULL so + * it is distinguishable from a pathLenConstraint of 0. */ + if (x509->basicConstPlSet) { + a->length = (int)x509->pathLength; + + /* Save ASN1_INTEGER in x509 extension */ + ext->obj->pathlen = a; + } + else { + wolfSSL_ASN1_INTEGER_free(a); + ext->obj->pathlen = NULL; + } ext->obj->ca = x509->isCa; break; @@ -1234,6 +1244,135 @@ static int asn1_string_copy_to_buffer(WOLFSSL_ASN1_STRING* str, byte** buf, return WOLFSSL_SUCCESS; } +/* Handle WC_NID_subject_alt_name for wolfSSL_X509_add_ext: iterate the + * GENERAL_NAME stack on the extension and add each entry to x509->altNames. + * Only types whose union member is WOLFSSL_ASN1_STRING* (DNS/RFC822/URI/IP/ + * IA5) are read via gn->d.ia5; ASN_OTHER_TYPE is handled via SetOthername. + * Other types (DIRNAME/RID/X400/EDIPARTY) are rejected to avoid the + * type-confused union access that would XMEMCPY from a wild pointer in + * wolfSSL_X509_add_altname_ex. */ +static int wolfssl_x509_add_subj_alt_name_ext(WOLFSSL_X509 *x509, + WOLFSSL_X509_EXTENSION *ext) +{ + WOLFSSL_GENERAL_NAMES* gns = ext->ext_sk; + while (gns) { + WOLFSSL_GENERAL_NAME* gn = gns->data.gn; + if ((gn != NULL) && (gn->type == ASN_OTHER_TYPE)) { + char *buf = NULL; + int ret = 0; + word32 len = 0; + + len = SetOthername(gn->d.otherName, NULL); + if (len == WC_NO_ERR_TRACE(WOLFSSL_FAILURE)) { + return WOLFSSL_FAILURE; + } + + buf = (char*)XMALLOC(len, x509->heap, DYNAMIC_TYPE_X509_EXT); + if (buf == NULL) { + WOLFSSL_MSG("Couldn't allocate memory for othername"); + return WOLFSSL_FAILURE; + } + + /* SetOthername() cannot fail; already passed above. */ + SetOthername(gn->d.otherName, (byte*)buf); + + ret = wolfSSL_X509_add_altname_ex(x509, buf, len, + ASN_OTHER_TYPE); + XFREE(buf, x509->heap, DYNAMIC_TYPE_X509_EXT); + if (ret == WC_NO_ERR_TRACE(WOLFSSL_FAILURE)) { + WOLFSSL_MSG("wolfSSL_X509_add_altname_ex() failed"); + return WOLFSSL_FAILURE; + } + } + /* Only types whose union member are WOLFSSL_ASN1_STRING. */ + else if (gn == NULL) { + WOLFSSL_MSG("Subject alternative name missing"); + return WOLFSSL_FAILURE; + } + else { + if ((gn->type != ASN_RFC822_TYPE && gn->type != ASN_DNS_TYPE && + gn->type != ASN_URI_TYPE && gn->type != ASN_IP_TYPE && + gn->type != WOLFSSL_GEN_IA5) || (gn->d.ia5 == NULL)) { + WOLFSSL_MSG("Subject alternative name unsupported " + "GeneralName type or missing name"); + return WOLFSSL_FAILURE; + } + if (wolfSSL_X509_add_altname_ex(x509, gn->d.ia5->data, + gn->d.ia5->length, gn->type) != WOLFSSL_SUCCESS) { + WOLFSSL_MSG("Failed to add subject alternative name"); + return WOLFSSL_FAILURE; + } + } + gns = gns->next; + } + x509->subjAltNameSet = 1; + x509->subjAltNameCrit = (byte)ext->crit; + return WOLFSSL_SUCCESS; +} + +#ifdef WOLFSSL_CUSTOM_OID +/* Handle the default (unrecognized NID) case of wolfSSL_X509_add_ext when + * custom-OID extensions are enabled: copy the extension OID text and value + * into the next free slot in x509->custom_exts, taking ownership of the + * allocations on success. */ +static int wolfssl_x509_add_custom_ext(WOLFSSL_X509 *x509, + WOLFSSL_X509_EXTENSION *ext) +{ + char *oid = NULL; + byte *val = NULL; + int err = 0; + + if ((ext->obj == NULL) || (ext->value.length == 0)) { + WOLFSSL_MSG("Extension has insufficient information."); + return WOLFSSL_FAILURE; + } + + if ((x509->customExtCount < 0) || + (x509->customExtCount >= NUM_CUSTOM_EXT)) { + WOLFSSL_MSG("Bad value for customExtCount."); + return WOLFSSL_FAILURE; + } + + /* This is a viable custom extension. */ + oid = (char*)XMALLOC(MAX_OID_STRING_SZ, x509->heap, + DYNAMIC_TYPE_X509_EXT); + val = (byte*)XMALLOC(ext->value.length, x509->heap, + DYNAMIC_TYPE_X509_EXT); + if ((oid == NULL) || (val == NULL)) { + WOLFSSL_MSG("Memory allocation failure.\n"); + err = 1; + } + + if (err == 0) { + XMEMCPY(val, ext->value.data, ext->value.length); + if (wolfSSL_OBJ_obj2txt(oid, MAX_OID_STRING_SZ, ext->obj, 1) < 0) { + err = 1; + } + } + + if (err == 1) { + XFREE(val, x509->heap, DYNAMIC_TYPE_X509_EXT); + XFREE(oid, x509->heap, DYNAMIC_TYPE_X509_EXT); + return WOLFSSL_FAILURE; + } + + /* ext->crit is WOLFSSL_ASN1_BOOLEAN */ + if (ext->crit != 0 && ext->crit != -1) { + XFREE(val, x509->heap, DYNAMIC_TYPE_X509_EXT); + XFREE(oid, x509->heap, DYNAMIC_TYPE_X509_EXT); + return WOLFSSL_FAILURE; + } + + /* x509->custom_exts now owns the buffers and they must be managed. */ + x509->custom_exts[x509->customExtCount].oid = oid; + x509->custom_exts[x509->customExtCount].crit = (byte)ext->crit; + x509->custom_exts[x509->customExtCount].val = val; + x509->custom_exts[x509->customExtCount].valSz = ext->value.length; + x509->customExtCount++; + return WOLFSSL_SUCCESS; +} +#endif /* WOLFSSL_CUSTOM_OID */ + int wolfSSL_X509_add_ext(WOLFSSL_X509 *x509, WOLFSSL_X509_EXTENSION *ext, int loc) { @@ -1272,49 +1411,10 @@ int wolfSSL_X509_add_ext(WOLFSSL_X509 *x509, WOLFSSL_X509_EXTENSION *ext, x509->subjKeyIdCrit = (byte)ext->crit; break; case WC_NID_subject_alt_name: - { - WOLFSSL_GENERAL_NAMES* gns = ext->ext_sk; - while (gns) { - WOLFSSL_GENERAL_NAME* gn = gns->data.gn; - if ((gn != NULL) && (gn->type == ASN_OTHER_TYPE)) { - char *buf = NULL; - int ret = 0; - word32 len = 0; - - len = SetOthername(gn->d.otherName, NULL); - if (len == WC_NO_ERR_TRACE(WOLFSSL_FAILURE)) { - return WOLFSSL_FAILURE; - } - - buf = (char*)XMALLOC(len, x509->heap, DYNAMIC_TYPE_X509_EXT); - if (buf == NULL) { - WOLFSSL_MSG("Couldn't allocate memory for othername"); - return WOLFSSL_FAILURE; - } - - /* SetOthername() cannot fail; already passed above. */ - SetOthername(gn->d.otherName, (byte*)buf); - - ret = wolfSSL_X509_add_altname_ex(x509, buf, len, - ASN_OTHER_TYPE); - XFREE(buf, x509->heap, DYNAMIC_TYPE_X509_EXT); - if (ret == WC_NO_ERR_TRACE(WOLFSSL_FAILURE)) { - WOLFSSL_MSG("wolfSSL_X509_add_altname_ex() failed"); - return WOLFSSL_FAILURE; - } - } - else if (!gn || !gn->d.ia5 || - wolfSSL_X509_add_altname_ex(x509, gn->d.ia5->data, - gn->d.ia5->length, gn->type) != WOLFSSL_SUCCESS) { - WOLFSSL_MSG("Subject alternative name missing extension"); - return WOLFSSL_FAILURE; - } - gns = gns->next; + if (wolfssl_x509_add_subj_alt_name_ext(x509, ext) != WOLFSSL_SUCCESS) { + return WOLFSSL_FAILURE; } - x509->subjAltNameSet = 1; - x509->subjAltNameCrit = (byte)ext->crit; break; - } case WC_NID_key_usage: if (ext && ext->value.data) { if (ext->value.length == sizeof(word16)) { @@ -1376,60 +1476,10 @@ int wolfSSL_X509_add_ext(WOLFSSL_X509 *x509, WOLFSSL_X509_EXTENSION *ext, break; default: #ifdef WOLFSSL_CUSTOM_OID - { - char *oid = NULL; - byte *val = NULL; - int err = 0; - - if ((ext->obj == NULL) || (ext->value.length == 0)) { - WOLFSSL_MSG("Extension has insufficient information."); + if (wolfssl_x509_add_custom_ext(x509, ext) != WOLFSSL_SUCCESS) { return WOLFSSL_FAILURE; } - - if ((x509->customExtCount < 0) || - (x509->customExtCount >= NUM_CUSTOM_EXT)) { - WOLFSSL_MSG("Bad value for customExtCount."); - return WOLFSSL_FAILURE; - } - - /* This is a viable custom extension. */ - oid = (char*)XMALLOC(MAX_OID_STRING_SZ, x509->heap, - DYNAMIC_TYPE_X509_EXT); - val = (byte*)XMALLOC(ext->value.length, x509->heap, - DYNAMIC_TYPE_X509_EXT); - if ((oid == NULL) || (val == NULL)) { - WOLFSSL_MSG("Memory allocation failure.\n"); - err = 1; - } - - if (err == 0) { - XMEMCPY(val, ext->value.data, ext->value.length); - if (wolfSSL_OBJ_obj2txt(oid, MAX_OID_STRING_SZ, ext->obj, 1) < 0) { - err = 1; - } - } - - if (err == 1) { - XFREE(val, x509->heap, DYNAMIC_TYPE_X509_EXT); - XFREE(oid, x509->heap, DYNAMIC_TYPE_X509_EXT); - return WOLFSSL_FAILURE; - } - - /* ext->crit is WOLFSSL_ASN1_BOOLEAN */ - if (ext->crit != 0 && ext->crit != -1) { - XFREE(val, x509->heap, DYNAMIC_TYPE_X509_EXT); - XFREE(oid, x509->heap, DYNAMIC_TYPE_X509_EXT); - return WOLFSSL_FAILURE; - } - - /* x509->custom_exts now owns the buffers and they must be managed. */ - x509->custom_exts[x509->customExtCount].oid = oid; - x509->custom_exts[x509->customExtCount].crit = (byte)ext->crit; - x509->custom_exts[x509->customExtCount].val = val; - x509->custom_exts[x509->customExtCount].valSz = ext->value.length; - x509->customExtCount++; break; - } #else WOLFSSL_MSG("Unsupported extension to add"); return WOLFSSL_FAILURE; @@ -1529,9 +1579,9 @@ int wolfSSL_X509V3_EXT_print(WOLFSSL_BIO *out, WOLFSSL_X509_EXTENSION *ext, WOLFSSL_MSG("Memory error"); return rc; } - valLen = XSNPRINTF(val, (size_t)len, "%*s%s", indent, "", - str->strData); - if ((valLen < 0) || (valLen >= len) + valLen = XSNPRINTF(val, (size_t)len + indent, + "%*s%s", indent, "", str->strData); + if ((valLen < 0) || (valLen >= len + indent) || ((tmpLen + valLen) >= tmpSz)) { XFREE(val, NULL, DYNAMIC_TYPE_TMP_BUFFER); return rc; @@ -1549,6 +1599,10 @@ int wolfSSL_X509V3_EXT_print(WOLFSSL_BIO *out, WOLFSSL_X509_EXTENSION *ext, { char* asn1str; asn1str = wolfSSL_i2s_ASN1_STRING(NULL, str); + if (asn1str == NULL) { + WOLFSSL_MSG("wolfSSL_i2s_ASN1_STRING returned NULL"); + return rc; + } tmpLen = XSNPRINTF(tmp, tmpSz, "%*s%s", indent, "", asn1str); XFREE(asn1str, NULL, DYNAMIC_TYPE_TMP_BUFFER); if (tmpLen >= tmpSz) @@ -1961,7 +2015,13 @@ void* wolfSSL_X509V3_EXT_d2i(WOLFSSL_X509_EXTENSION* ext) } /* Copy pathlen and CA into BASIC_CONSTRAINTS from object */ bc->ca = object->ca; - if (object->pathlen != NULL && object->pathlen->length > 0) { + /* RFC 5280 4.2.1.9: + * "A pathLenConstraint of zero indicates that no non-self-issued + * intermediate CA certificates may follow in a valid + * certification path." + * Check for length of 0 or greater. + */ + if ((object->pathlen != NULL) && (object->pathlen->length >= 0)) { bc->pathlen = wolfSSL_ASN1_INTEGER_dup(object->pathlen); if (bc->pathlen == NULL) { WOLFSSL_MSG("Failed to duplicate ASN1_INTEGER"); @@ -2433,27 +2493,31 @@ void* wolfSSL_X509_get_ext_d2i(const WOLFSSL_X509* x509, int nid, int* c, switch (nid) { case BASIC_CA_OID: if (x509->basicConstSet) { - WOLFSSL_ASN1_INTEGER* a; - bc = wolfSSL_BASIC_CONSTRAINTS_new(); if (!bc) { WOLFSSL_MSG("wolfSSL_BASIC_CONSTRAINTS_new error"); return NULL; } - a = wolfSSL_ASN1_INTEGER_new(); - if (!a) { - WOLFSSL_MSG("wolfSSL_ASN1_INTEGER_new error"); - wolfSSL_BASIC_CONSTRAINTS_free(bc); - return NULL; - } - a->length = (int)x509->pathLength; - #if defined(OPENSSL_ALL) || defined(WOLFSSL_QT) || \ defined(WOLFSSL_APACHE_HTTPD) bc->ca = x509->isCa; #endif - bc->pathlen = a; + /* RFC 5280 4.2.1.9: only populate pathLen when the + * pathLenConstraint field was present. When absent, no limit + * is imposed and pathlen must remain NULL so it is + * distinguishable from a pathLenConstraint of 0. */ + if (x509->basicConstPlSet) { + WOLFSSL_ASN1_INTEGER* a = wolfSSL_ASN1_INTEGER_new(); + if (a == NULL) { + WOLFSSL_MSG("wolfSSL_ASN1_INTEGER_new error"); + wolfSSL_BASIC_CONSTRAINTS_free(bc); + return NULL; + } + a->length = (int)x509->pathLength; + bc->pathlen = a; + } + if (c != NULL) { *c = x509->basicConstCrit; } @@ -2648,14 +2712,15 @@ void* wolfSSL_X509_get_ext_d2i(const WOLFSSL_X509* x509, int nid, int* c, WOLFSSL_MSG("wolfSSL_sk_GENERAL_NAME_push error"); goto err; } + /* gn now owned by stack. */ + gn = NULL; /* push DIST_POINT onto stack */ if (wolfSSL_sk_DIST_POINT_push(sk, dp) <= 0) { WOLFSSL_MSG("Error pushing DIST_POINT onto stack"); goto err; } - - gn = NULL; + /* dp now owned by stack. */ dp = NULL; } @@ -5871,8 +5936,7 @@ int wolfSSL_GENERAL_NAME_print(WOLFSSL_BIO* out, WOLFSSL_GENERAL_NAME* gen) case GEN_EMAIL: ret = wolfSSL_BIO_printf(out, "email:"); ret = (ret > 0) ? WOLFSSL_SUCCESS : WOLFSSL_FAILURE; - if (ret == WOLFSSL_SUCCESS) - { + if (ret == WOLFSSL_SUCCESS) { ret = wolfSSL_ASN1_STRING_print(out, gen->d.rfc822Name); } break; @@ -5881,8 +5945,7 @@ int wolfSSL_GENERAL_NAME_print(WOLFSSL_BIO* out, WOLFSSL_GENERAL_NAME* gen) ret = wolfSSL_BIO_printf(out, "DNS:"); ret = (ret > 0) ? WOLFSSL_SUCCESS : WOLFSSL_FAILURE; if (ret == WOLFSSL_SUCCESS) { - ret = wolfSSL_BIO_printf(out, "%s", gen->d.dNSName->strData); - ret = (ret > 0) ? WOLFSSL_SUCCESS : WOLFSSL_FAILURE; + ret = wolfSSL_ASN1_STRING_print(out, gen->d.dNSName); } break; @@ -5893,6 +5956,7 @@ int wolfSSL_GENERAL_NAME_print(WOLFSSL_BIO* out, WOLFSSL_GENERAL_NAME* gen) case GEN_DIRNAME: ret = wolfSSL_BIO_printf(out, "DirName:"); + ret = (ret > 0) ? WOLFSSL_SUCCESS : WOLFSSL_FAILURE; if (ret == WOLFSSL_SUCCESS) { ret = wolfSSL_X509_NAME_print_ex(out, gen->d.directoryName, 0, XN_FLAG_ONELINE); @@ -7094,7 +7158,8 @@ static int X509PrintExtendedKeyUsage(WOLFSSL_BIO* bio, WOLFSSL_X509* x509, EXTKEYUSE_EMAILPROT, EXTKEYUSE_CODESIGN, EXTKEYUSE_CLIENT_AUTH, - EXTKEYUSE_SERVER_AUTH + EXTKEYUSE_SERVER_AUTH, + EXTKEYUSE_ANY }; const char* usageStrs[] = { "OCSP Signing", @@ -7102,7 +7167,8 @@ static int X509PrintExtendedKeyUsage(WOLFSSL_BIO* bio, WOLFSSL_X509* x509, "E-mail Protection", "Code Signing", "TLS Web Client Authentication", - "TLS Web Server Authentication" + "TLS Web Server Authentication", + "Any Extended Key Usage" }; if (bio == NULL || x509 == NULL) { @@ -7483,12 +7549,22 @@ static int X509PrintExtensions(WOLFSSL_BIO* bio, WOLFSSL_X509* x509, int indent) ret = WOLFSSL_FAILURE; break; } - if ((scratchLen = XSNPRINTF( - scratch, scratchSz, - "%*sCA:%s\n", - indent + 8, "", (x509->isCa)? "TRUE": "FALSE")) - >= scratchSz) - { + /* Match OpenSSL output: print "CA:TRUE"/"CA:FALSE" and append + * ", pathlen:N" only when a pathLenConstraint is present + * (RFC 5280 4.2.1.9). An absent constraint imposes no limit + * and must not be shown as pathlen:0. */ + if (x509->basicConstPlSet) { + scratchLen = XSNPRINTF(scratch, scratchSz, + "%*sCA:%s, pathlen:%u\n", + indent + 8, "", (x509->isCa) ? "TRUE" : "FALSE", + (unsigned int)x509->pathLength); + } + else { + scratchLen = XSNPRINTF(scratch, scratchSz, + "%*sCA:%s\n", + indent + 8, "", (x509->isCa) ? "TRUE" : "FALSE"); + } + if (scratchLen >= scratchSz) { ret = WOLFSSL_FAILURE; break; } diff --git a/tests/api.c b/tests/api.c index 8ba962b79b8..4fe7da2c949 100644 --- a/tests/api.c +++ b/tests/api.c @@ -20566,6 +20566,7 @@ static int test_wolfSSL_GENERAL_NAME_print(void) ACCESS_DESCRIPTION* ad = NULL; ASN1_IA5STRING *dnsname = NULL; ASN1_OBJECT* ridObj = NULL; + X509_NAME* dirName = NULL; const unsigned char v4Addr[] = {192,168,53,1}; const unsigned char v6Addr[] = @@ -20820,22 +20821,35 @@ static int test_wolfSSL_GENERAL_NAME_print(void) /* test for GEN_DIRNAME */ ExpectNotNull(gn = wolfSSL_GENERAL_NAME_new()); + /* Build a real directoryName (X509_NAME) so the print path exercises + * wolfSSL_X509_NAME_print_ex on a valid object. Forcing the type without + * setting d.directoryName would leave it aliasing the default IA5 string + * and cause an out-of-bounds read when printed. */ + ExpectNotNull(dirName = X509_NAME_new()); + ExpectIntEQ(X509_NAME_add_entry_by_NID(dirName, NID_commonName, + MBSTRING_UTF8, (unsigned char*)"wolfSSLDirNameTest", -1, -1, 0), 1); if (gn != NULL) { + /* Replace the default IA5 string allocated by GENERAL_NAME_new with + * the directoryName and take ownership of it. */ + wolfSSL_ASN1_STRING_free(gn->d.ia5); gn->type = GEN_DIRNAME; + gn->d.directoryName = dirName; + dirName = NULL; /* gn owns it now; freed by GENERAL_NAME_free */ } ExpectIntEQ(GENERAL_NAME_print(out, gn), 1); XMEMSET(outbuf,0,sizeof(outbuf)); ExpectIntGT(BIO_read(out, outbuf, sizeof(outbuf)), 0); + /* Output must start with the label and contain the directory name. */ ExpectIntEQ(XSTRNCMP((const char*)outbuf, dirNameStr, XSTRLEN(dirNameStr)), 0); + ExpectNotNull(XSTRSTR((const char*)outbuf, "wolfSSLDirNameTest")); /* Duplicating GEN_DIRNAME not supported. */ ExpectNull(dup_gn = GENERAL_NAME_dup(gn)); - /* Restore to GEN_IA5 (default) to avoid memory leak. */ - if (gn != NULL) { - gn->type = GEN_IA5; - } GENERAL_NAME_free(gn); gn = NULL; + /* Only freed here if ownership was not transferred (e.g. gn alloc failed). */ + X509_NAME_free(dirName); + dirName = NULL; /* test for GEN_RID */ p = ridData; @@ -26974,6 +26988,111 @@ static int test_wolfSSL_X509_print(void) return EXPECT_RESULT(); } +static int test_wolfSSL_X509_print_basic_constraints(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && !defined(NO_FILESYSTEM) && \ + !defined(NO_RSA) && defined(XSNPRINTF) && !defined(WC_DISABLE_RADIX_ZERO_PAD) + /* X509_print must match OpenSSL's Basic Constraints output: "CA:TRUE" + * alone when no pathLenConstraint is present, and "CA:TRUE, pathlen:N" + * when one is (including the meaningful value 0). */ + struct { + const char* file; + const char* expect; /* substring that must be present */ + const char* absent; /* substring that must be absent, or NULL */ + } cases[] = { + { "./certs/ca-cert.pem", "CA:TRUE", "pathlen" }, + { "./certs/intermediate/ca-int-cert.pem", "CA:TRUE, pathlen:1", NULL }, + { "./certs/test-pathlen/chainG-ICA1-pathlen0.pem", + "CA:TRUE, pathlen:0", NULL }, + }; + size_t i; + + for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + X509* x509 = NULL; + BIO* bio = NULL; + char* data = NULL; + int len = 0; + char buf[8192]; + + ExpectNotNull(x509 = X509_load_certificate_file(cases[i].file, + WOLFSSL_FILETYPE_PEM)); + ExpectNotNull(bio = BIO_new(BIO_s_mem())); + ExpectIntEQ(X509_print(bio, x509), SSL_SUCCESS); + /* Memory BIO data is not NUL-terminated; copy into a bounded buffer. */ + ExpectIntGT((len = BIO_get_mem_data(bio, &data)), 0); + ExpectIntLT(len, (int)sizeof(buf)); + if ((data != NULL) && (len > 0) && (len < (int)sizeof(buf))) { + XMEMCPY(buf, data, (size_t)len); + buf[len] = '\0'; + ExpectNotNull(XSTRSTR(buf, cases[i].expect)); + if (cases[i].absent != NULL) { + ExpectNull(XSTRSTR(buf, cases[i].absent)); + } + } + BIO_free(bio); + X509_free(x509); + } +#endif + return EXPECT_RESULT(); +} + +static int test_wolfSSL_X509_print_ext_key_usage(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && !defined(NO_FILESYSTEM) && \ + !defined(NO_RSA) && defined(XSNPRINTF) && !defined(WC_DISABLE_RADIX_ZERO_PAD) + /* Self-signed RSA cert (CN="wolfSSL anyEKU test", valid until 2126) whose + * only Extended Key Usage is anyExtendedKeyUsage (OID 2.5.29.37.0). + * X509_print must render it as "Any Extended Key Usage" to match OpenSSL, + * rather than omitting it. */ + static const char anyEkuCertPem[] = + "-----BEGIN CERTIFICATE-----\n" + "MIIDMDCCAhigAwIBAgIUGFipYIiuuQZHNjsi0R8t6nZrFVowDQYJKoZIhvcNAQEL\n" + "BQAwHjEcMBoGA1UEAwwTd29sZlNTTCBhbnlFS1UgdGVzdDAgFw0yNjA1MjgwMTI5\n" + "MjBaGA8yMTI2MDUwNDAxMjkyMFowHjEcMBoGA1UEAwwTd29sZlNTTCBhbnlFS1Ug\n" + "dGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKIXS15QjpWlqpqk\n" + "sUTD/mgm7akIZfp2DVoweJsf8BC/tnwMX1noWUFjBC8SLOHyhx4bmWxVBzFXv/uu\n" + "ICoUCq63pgnxj3rQbPAa1pwAX4UsYS1PS/2mO2o7lRLRdDLIaBXuUClVHGjti9x5\n" + "x5++4FFhOhKNt7CkYVAfXasTFKdZqSnKdlYX2rM8LoCjP3YHIC5jEIgjyNUEqzup\n" + "Ls02dIaAly5O8yzasUtULwE76E/UpyhE+o2dkhxnc6ukqU5CBLeHNOUJgvThW1lS\n" + "SlGXl8Kd5uvBvDflXb6y3TQBEY/hb40JzYeH+hHf4YIqCtvfy6PMA+Rcu2CKlDpP\n" + "FVf85pUCAwEAAaNkMGIwHQYDVR0OBBYEFAkmMlYp9K3sbeQ8/RluOviXZ3JcMB8G\n" + "A1UdIwQYMBaAFAkmMlYp9K3sbeQ8/RluOviXZ3JcMA8GA1UdEwEB/wQFMAMBAf8w\n" + "DwYDVR0lBAgwBgYEVR0lADANBgkqhkiG9w0BAQsFAAOCAQEAHX5wAZDAyFSmmsn4\n" + "Mu7TayCx+VbcBgvL4ZdxxJYXexslzI8OviKSgpC56LaVB5JpNqZtFS4pZZikG7nv\n" + "kYCoiU33XN82/gggq1bv8rKO740V6kGev3gfEeJuTX30fCPZ18znE1fU8+VQba1L\n" + "bPn0h746Ivom47/1VMg6y7CbTJg90+lloPWcTWujYfm5jWychqWurhfZmAYcUlBH\n" + "Ksk0l4kiEJA6lSnPp3MqBS0GzwsCixSqYc1W1TlwNGNg38cLP2Z5jerJZlazuVws\n" + "SeEbYO6TIhsy6QJy0Pd7hu9DUOKRxp+OQubL6WgWpjrGl1LUzH5sI5pyWueEAEBu\n" + "e74xbw==\n" + "-----END CERTIFICATE-----\n"; + X509* x509 = NULL; + BIO* bio = NULL; + char* data = NULL; + int len = 0; + char buf[8192]; + + ExpectNotNull(x509 = wolfSSL_X509_load_certificate_buffer( + (const unsigned char*)anyEkuCertPem, (int)XSTRLEN(anyEkuCertPem), + WOLFSSL_FILETYPE_PEM)); + ExpectNotNull(bio = BIO_new(BIO_s_mem())); + ExpectIntEQ(X509_print(bio, x509), SSL_SUCCESS); + /* Memory BIO data is not NUL-terminated; copy into a bounded buffer. */ + ExpectIntGT((len = BIO_get_mem_data(bio, &data)), 0); + ExpectIntLT(len, (int)sizeof(buf)); + if ((data != NULL) && (len > 0) && (len < (int)sizeof(buf))) { + XMEMCPY(buf, data, (size_t)len); + buf[len] = '\0'; + ExpectNotNull(XSTRSTR(buf, "X509v3 Extended Key Usage")); + ExpectNotNull(XSTRSTR(buf, "Any Extended Key Usage")); + } + BIO_free(bio); + X509_free(x509); +#endif + return EXPECT_RESULT(); +} + static int test_wolfSSL_X509_CRL_print(void) { EXPECT_DECLS; @@ -40367,6 +40486,8 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_X509_CRL), #ifndef NO_BIO TEST_DECL(test_wolfSSL_X509_print), + TEST_DECL(test_wolfSSL_X509_print_basic_constraints), + TEST_DECL(test_wolfSSL_X509_print_ext_key_usage), TEST_DECL(test_wolfSSL_X509_CRL_print), #endif diff --git a/tests/api/test_ossl_x509_ext.c b/tests/api/test_ossl_x509_ext.c index dfc2451529a..fb0967be5e7 100644 --- a/tests/api/test_ossl_x509_ext.c +++ b/tests/api/test_ossl_x509_ext.c @@ -593,6 +593,70 @@ int test_wolfSSL_X509_add_ext(void) return EXPECT_RESULT(); } +int test_wolfSSL_X509_add_ext_dirname_san_rejected(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_ALL) && !defined(NO_RSA) + WOLFSSL_X509* x509 = NULL; + WOLFSSL_X509_EXTENSION* ext = NULL; + WOLFSSL_ASN1_OBJECT* obj = NULL; + WOLFSSL_GENERAL_NAME* gn = NULL; + WOLFSSL_X509_NAME* dirName = NULL; + WOLFSSL_STACK* sk = NULL; + + ExpectNotNull(x509 = wolfSSL_X509_new()); + ExpectNotNull(ext = wolfSSL_X509_EXTENSION_new()); + + /* Build a GEN_DIRNAME GENERAL_NAME with a real directoryName so that + * gn->d.directoryName aliases an X509_NAME object via the union. */ + ExpectNotNull(gn = wolfSSL_GENERAL_NAME_new()); + ExpectNotNull(dirName = wolfSSL_X509_NAME_new()); + ExpectIntEQ(wolfSSL_X509_NAME_add_entry_by_NID(dirName, NID_commonName, + MBSTRING_UTF8, (unsigned char*)"dirname-san-test", -1, -1, 0), 1); + if (gn != NULL) { + /* Drop the default IA5 string and install the X509_NAME. */ + wolfSSL_ASN1_STRING_free(gn->d.ia5); + gn->type = GEN_DIRNAME; + gn->d.directoryName = dirName; + dirName = NULL; /* gn owns the X509_NAME now */ + } + + /* Build the ext: SAN OID + ext_sk containing the DirName GENERAL_NAME. */ + ExpectNotNull(sk = wolfSSL_sk_new_null()); + if (sk != NULL) { + sk->type = STACK_TYPE_GEN_NAME; + } + ExpectIntGT(wolfSSL_sk_GENERAL_NAME_push(sk, gn), 0); + gn = NULL; /* sk owns gn now */ + + ExpectNotNull(obj = wolfSSL_OBJ_nid2obj(NID_subject_alt_name)); + if (obj != NULL) { + obj->type = NID_subject_alt_name; + obj->nid = NID_subject_alt_name; + } + if ((ext != NULL) && (obj != NULL) && (sk != NULL)) { + ext->obj = obj; + obj = NULL; /* ext owns obj now */ + ext->ext_sk = sk; + sk = NULL; /* ext owns sk now */ + } + + /* The unsupported GeneralName type must be rejected safely, NOT crash + * or read OOB via a type-confused d.ia5 dereference. */ + ExpectIntEQ(wolfSSL_X509_add_ext(x509, ext, -1), + WC_NO_ERR_TRACE(WOLFSSL_FAILURE)); + + /* Cleanup. The success-path owners (set to NULL above) are no-ops. */ + wolfSSL_ASN1_OBJECT_free(obj); + wolfSSL_sk_GENERAL_NAME_pop_free(sk, wolfSSL_GENERAL_NAME_free); + wolfSSL_GENERAL_NAME_free(gn); + wolfSSL_X509_NAME_free(dirName); + wolfSSL_X509_EXTENSION_free(ext); + wolfSSL_X509_free(x509); +#endif + return EXPECT_RESULT(); +} + int test_wolfSSL_X509_get_ext_count(void) { EXPECT_DECLS; @@ -1098,10 +1162,6 @@ int test_wolfSSL_X509V3_EXT_bc(void) ExpectNotNull(ext = wolfSSL_X509_EXTENSION_new()); ExpectNotNull(obj = wolfSSL_ASN1_OBJECT_new()); - ExpectNotNull(pathLen = wolfSSL_ASN1_INTEGER_new()); - if (pathLen != NULL) { - pathLen->length = 2; - } if (obj != NULL) { obj->type = NID_basic_constraints; @@ -1109,17 +1169,47 @@ int test_wolfSSL_X509V3_EXT_bc(void) } ExpectIntEQ(wolfSSL_X509_EXTENSION_set_object(ext, obj), WOLFSSL_SUCCESS); ExpectNotNull(wolfSSL_X509V3_EXT_get(ext)); - /* No pathlen set. */ + + /* No pathLenConstraint present. Per RFC 5280 4.2.1.9 no limit is imposed, + * so pathlen must be NULL (and distinguishable from a value of 0). */ ExpectNotNull(bc = (WOLFSSL_BASIC_CONSTRAINTS*)wolfSSL_X509V3_EXT_d2i(ext)); + ExpectNull(bc->pathlen); wolfSSL_BASIC_CONSTRAINTS_free(bc); bc = NULL; + /* pathLenConstraint of 0 is valid and meaningful (the CA may only issue + * end-entity certificates). It must be preserved, not conflated with an + * absent constraint. */ + ExpectNotNull(pathLen = wolfSSL_ASN1_INTEGER_new()); + if (pathLen != NULL) { + pathLen->length = 0; + } if ((ext != NULL) && (ext->obj != NULL)) { ext->obj->pathlen = pathLen; pathLen = NULL; } - /* pathlen set. */ ExpectNotNull(bc = (WOLFSSL_BASIC_CONSTRAINTS*)wolfSSL_X509V3_EXT_d2i(ext)); + ExpectNotNull(bc->pathlen); + ExpectIntEQ(bc->pathlen->length, 0); + wolfSSL_BASIC_CONSTRAINTS_free(bc); + bc = NULL; + + /* A non-zero pathLenConstraint is preserved as-is. */ + if ((ext != NULL) && (ext->obj != NULL)) { + wolfSSL_ASN1_INTEGER_free(ext->obj->pathlen); + ext->obj->pathlen = NULL; + } + ExpectNotNull(pathLen = wolfSSL_ASN1_INTEGER_new()); + if (pathLen != NULL) { + pathLen->length = 2; + } + if ((ext != NULL) && (ext->obj != NULL)) { + ext->obj->pathlen = pathLen; + pathLen = NULL; + } + ExpectNotNull(bc = (WOLFSSL_BASIC_CONSTRAINTS*)wolfSSL_X509V3_EXT_d2i(ext)); + ExpectNotNull(bc->pathlen); + ExpectIntEQ(bc->pathlen->length, 2); wolfSSL_ASN1_INTEGER_free(pathLen); wolfSSL_BASIC_CONSTRAINTS_free(bc); @@ -1129,6 +1219,72 @@ int test_wolfSSL_X509V3_EXT_bc(void) return EXPECT_RESULT(); } +int test_wolfSSL_X509_get_ext_d2i_basic_constraints(void) +{ + EXPECT_DECLS; +#if !defined(NO_FILESYSTEM) && defined(OPENSSL_ALL) && !defined(NO_RSA) + XFILE f = XBADFILE; + WOLFSSL_X509* x509 = NULL; + WOLFSSL_BASIC_CONSTRAINTS* bc = NULL; + int crit = 0; + + /* CA certificate with basicConstraints CA:TRUE and *no* pathLenConstraint. + * Per RFC 5280 4.2.1.9 no path length limit is imposed, so the returned + * pathlen must be NULL - it must not be reported as a value of 0. */ + ExpectTrue((f = XFOPEN("./certs/ca-cert.pem", "rb")) != XBADFILE); + ExpectNotNull(x509 = wolfSSL_PEM_read_X509(f, NULL, NULL, NULL)); + if (f != XBADFILE) { + XFCLOSE(f); + f = XBADFILE; + } + ExpectNotNull(bc = (WOLFSSL_BASIC_CONSTRAINTS*)wolfSSL_X509_get_ext_d2i( + x509, NID_basic_constraints, &crit, NULL)); + ExpectNull(bc->pathlen); + wolfSSL_BASIC_CONSTRAINTS_free(bc); + bc = NULL; + wolfSSL_X509_free(x509); + x509 = NULL; + + /* Intermediate CA with basicConstraints CA:TRUE, pathlen:1. */ + ExpectTrue((f = XFOPEN("./certs/intermediate/ca-int-cert.pem", "rb")) != + XBADFILE); + ExpectNotNull(x509 = wolfSSL_PEM_read_X509(f, NULL, NULL, NULL)); + if (f != XBADFILE) { + XFCLOSE(f); + f = XBADFILE; + } + ExpectNotNull(bc = (WOLFSSL_BASIC_CONSTRAINTS*)wolfSSL_X509_get_ext_d2i( + x509, NID_basic_constraints, &crit, NULL)); + ExpectNotNull(bc->pathlen); + ExpectIntEQ(bc->pathlen->length, 1); + wolfSSL_BASIC_CONSTRAINTS_free(bc); + bc = NULL; + wolfSSL_X509_free(x509); + x509 = NULL; + + /* CA with basicConstraints CA:TRUE, pathlen:0. A pathLenConstraint of 0 is + * valid and meaningful (the CA may only issue end-entity certificates) and + * must be reported (non-NULL pathlen, value 0) - it must not be conflated + * with an absent constraint. */ + ExpectTrue((f = XFOPEN("./certs/test-pathlen/chainG-ICA1-pathlen0.pem", + "rb")) != XBADFILE); + ExpectNotNull(x509 = wolfSSL_PEM_read_X509(f, NULL, NULL, NULL)); + if (f != XBADFILE) { + XFCLOSE(f); + f = XBADFILE; + } + ExpectNotNull(bc = (WOLFSSL_BASIC_CONSTRAINTS*)wolfSSL_X509_get_ext_d2i( + x509, NID_basic_constraints, &crit, NULL)); + ExpectNotNull(bc->pathlen); + ExpectIntEQ(bc->pathlen->length, 0); + wolfSSL_BASIC_CONSTRAINTS_free(bc); + bc = NULL; + wolfSSL_X509_free(x509); + x509 = NULL; +#endif + return EXPECT_RESULT(); +} + int test_wolfSSL_X509V3_EXT_san(void) { EXPECT_DECLS; diff --git a/tests/api/test_ossl_x509_ext.h b/tests/api/test_ossl_x509_ext.h index ee749facd91..68be4ebf5ee 100644 --- a/tests/api/test_ossl_x509_ext.h +++ b/tests/api/test_ossl_x509_ext.h @@ -30,6 +30,7 @@ int test_wolfSSL_X509_get_ext_by_NID(void); int test_wolfSSL_X509_get_ext_subj_alt_name(void); int test_wolfSSL_X509_set_ext(void); 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_EXTENSION_new(void); @@ -42,6 +43,7 @@ int test_wolfSSL_X509V3_set_ctx(void); int test_wolfSSL_X509V3_EXT_get(void); int test_wolfSSL_X509V3_EXT_nconf(void); int test_wolfSSL_X509V3_EXT_bc(void); +int test_wolfSSL_X509_get_ext_d2i_basic_constraints(void); int test_wolfSSL_X509V3_EXT_san(void); int test_wolfSSL_X509V3_EXT_aia(void); int test_wolfSSL_X509V3_EXT(void); @@ -62,6 +64,8 @@ int test_wolfSSL_NAME_CONSTRAINTS_excluded(void); TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_X509_get_ext_subj_alt_name), \ TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_X509_set_ext), \ TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_X509_add_ext), \ + TEST_DECL_GROUP("ossl_x509_ext", \ + 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_EXTENSION_new), \ @@ -76,6 +80,8 @@ int test_wolfSSL_NAME_CONSTRAINTS_excluded(void); TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_X509V3_EXT_get), \ TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_X509V3_EXT_nconf), \ TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_X509V3_EXT_bc), \ + TEST_DECL_GROUP("ossl_x509_ext", \ + test_wolfSSL_X509_get_ext_d2i_basic_constraints), \ TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_X509V3_EXT_san), \ TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_X509V3_EXT_aia), \ TEST_DECL_GROUP("ossl_x509_ext", test_wolfSSL_X509V3_EXT), \