diff --git a/src/ssl.c b/src/ssl.c index aa3c50ca6..3bca521f1 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -49613,7 +49613,6 @@ int wolfSSL_sk_X509_NAME_set_cmp_func(WOLF_STACK_OF(WOLFSSL_X509_NAME)* sk, #ifndef NO_BIO -#if defined(WOLFSSL_APACHE_HTTPD) || defined(OPENSSL_ALL) || defined(WOLFSSL_NGINX) /* Helper function for X509_NAME_print_ex. Sets *buf to string for domain name attribute based on NID. Returns size of buf */ static int get_dn_attr_by_nid(int n, const char** buf) @@ -49660,87 +49659,160 @@ static int get_dn_attr_by_nid(int n, const char** buf) *buf = str; return len; } -#endif + +/** + * Escape input string for RFC2253 requirements. The following characters + * are escaped with a backslash (\): + * + * 1. A space or '#' at the beginning of the string + * 2. A space at the end of the string + * 3. One of: ",", "+", """, "\", "<", ">", ";" + * + * in - input string to escape + * inSz - length of in, not including the null terminator + * out - buffer for output string to be written, will be null terminated + * outSz - size of out + * + * Returns size of output string (not counting NULL terminator) on success, + * negative on error. + */ +static int wolfSSL_EscapeString_RFC2253(char* in, word32 inSz, + char* out, word32 outSz) +{ + word32 inIdx = 0; + word32 outIdx = 0; + char c = 0; + + if (in == NULL || out == NULL || inSz == 0 || outSz == 0) { + return BAD_FUNC_ARG; + } + + for (inIdx = 0; inIdx < inSz; inIdx++) { + + c = in[inIdx]; + + if (((inIdx == 0) && (c == ' ' || c == '#')) || + ((inIdx == (inSz-1)) && (c == ' ')) || + c == ',' || c == '+' || c == '"' || c == '\\' || + c == '<' || c == '>' || c == ';') { + + if (outIdx > (outSz - 1)) { + return BUFFER_E; + } + out[outIdx] = '\\'; + outIdx++; + } + if (outIdx > (outSz - 1)) { + return BUFFER_E; + } + out[outIdx] = c; + outIdx++; + } + + /* null terminate out */ + if (outIdx > (outSz -1)) { + return BUFFER_E; + } + out[outIdx] = '\0'; + + return outIdx; +} /* - * The BIO output of wolfSSL_X509_NAME_print_ex does NOT include the null terminator + * Print human readable version of X509_NAME to provided BIO. + * + * bio - output BIO to place name string. Does not include null terminator. + * name - input name to convert to string + * indent - number of indent spaces to prepend to name string + * flags - flags to control function behavior. Not all flags are currently + * supported/implemented. Currently supported are: + * XN_FLAG_RFC2253 - only the backslash escape requirements from + * RFC22523 currently implemented. + * XN_FLAG_DN_REV - print name reversed. Automatically done by + * XN_FLAG_RFC2253. + * + * Returns WOLFSSL_SUCCESS (1) on success, WOLFSSL_FAILURE (0) on failure. */ int wolfSSL_X509_NAME_print_ex(WOLFSSL_BIO* bio, WOLFSSL_X509_NAME* name, int indent, unsigned long flags) { -#if defined(WOLFSSL_APACHE_HTTPD) || defined(OPENSSL_ALL) || defined(WOLFSSL_NGINX) - int count = 0, len = 0, totalSz = 0, tmpSz = 0; - char tmp[ASN_NAME_MAX+1]; - char fullName[ASN_NAME_MAX+2]; + int i, count = 0, len = 0, tmpSz = 0, nameStrSz = 0, escapeSz = 0; + char* tmp = NULL; + char* nameStr = NULL; const char *buf = NULL; WOLFSSL_X509_NAME_ENTRY* ne; WOLFSSL_ASN1_STRING* str; -#endif - int i; - (void)flags; + char escaped[ASN_NAME_MAX]; + WOLFSSL_ENTER("wolfSSL_X509_NAME_print_ex"); + if ((name == NULL) || (name->sz == 0) || (bio == NULL)) + return WOLFSSL_FAILURE; + for (i = 0; i < indent; i++) { if (wolfSSL_BIO_write(bio, " ", 1) != 1) return WOLFSSL_FAILURE; } - if ((name == NULL) || (name->sz == 0)) - return WOLFSSL_FAILURE; + count = wolfSSL_X509_NAME_entry_count(name); -#if defined(WOLFSSL_APACHE_HTTPD) || defined(OPENSSL_ALL) || defined(WOLFSSL_NGINX) - /* If XN_FLAG_DN_REV is present, print X509_NAME in reverse order */ - if (flags == (XN_FLAG_RFC2253 & ~XN_FLAG_DN_REV)) { - fullName[0] = '\0'; - count = wolfSSL_X509_NAME_entry_count(name); - for (i = 0; i < count; i++) { + for (i = 0; i < count; i++) { + /* reverse name order for RFC2253 and DN_REV */ + if ((flags & XN_FLAG_RFC2253) || (flags & XN_FLAG_DN_REV)) { ne = wolfSSL_X509_NAME_get_entry(name, count - i - 1); - if (ne == NULL) - return WOLFSSL_FAILURE; - - str = wolfSSL_X509_NAME_ENTRY_get_data(ne); - if (str == NULL) - return WOLFSSL_FAILURE; - - len = get_dn_attr_by_nid(ne->nid, &buf); - if (len == 0 || buf == NULL) - return WOLFSSL_FAILURE; - - tmpSz = str->length + len + 2; /* + 2 for '=' and comma */ - if (tmpSz > ASN_NAME_MAX) { - WOLFSSL_MSG("Size greater than ASN_NAME_MAX"); - return WOLFSSL_FAILURE; - } - - if (i < count - 1) { - /* tmpSz+1 for last null char */ - XSNPRINTF(tmp, tmpSz+1, "%s=%s,", buf, str->data); - XSTRNCAT(fullName, tmp, tmpSz+1); - } - else { - XSNPRINTF(tmp, tmpSz, "%s=%s", buf, str->data); - XSTRNCAT(fullName, tmp, tmpSz-1); - tmpSz--; /* Don't include null char in tmpSz */ - } - totalSz += tmpSz; + } else { + ne = wolfSSL_X509_NAME_get_entry(name, i); } - if (wolfSSL_BIO_write(bio, fullName, totalSz) != totalSz) + if (ne == NULL) return WOLFSSL_FAILURE; - return WOLFSSL_SUCCESS; - } -#else - if (flags == XN_FLAG_RFC2253) { - if ((name->sz < 3) || - (wolfSSL_BIO_write(bio, name->name + 1, name->sz - 2) - != name->sz - 2)) + + str = wolfSSL_X509_NAME_ENTRY_get_data(ne); + if (str == NULL) return WOLFSSL_FAILURE; + + if (flags & XN_FLAG_RFC2253) { + /* escape string for RFC 2253, ret sz not counting null term */ + escapeSz = wolfSSL_EscapeString_RFC2253(str->data, + str->length, escaped, sizeof(escaped)); + if (escapeSz < 0) + return WOLFSSL_FAILURE; + + nameStr = escaped; + nameStrSz = escapeSz; + } + else { + nameStr = str->data; + nameStrSz = str->length; + } + + /* len is without null terminator */ + len = get_dn_attr_by_nid(ne->nid, &buf); + if (len == 0 || buf == NULL) + return WOLFSSL_FAILURE; + + tmpSz = nameStrSz + len + 3; /* + 3 for '=', comma, and '\0' */ + tmp = (char*)XMALLOC(tmpSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (tmp == NULL) { + return WOLFSSL_FAILURE; + } + + if (i < count - 1) { + XSNPRINTF(tmp, tmpSz, "%s=%s,", buf, nameStr); + tmpSz = len + nameStrSz + 2; /* 2 for '=', comma */ + } + else { + XSNPRINTF(tmp, tmpSz, "%s=%s", buf, nameStr); + tmpSz = len + nameStrSz + 2; /* 2 for '=', '\0' */ + } + + if (wolfSSL_BIO_write(bio, tmp, tmpSz) != tmpSz) { + XFREE(tmp, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return WOLFSSL_FAILURE; + } + + XFREE(tmp, NULL, DYNAMIC_TYPE_TMP_BUFFER); } -#endif /* WOLFSSL_APACHE_HTTPD || OPENSSL_ALL || WOLFSSL_NGINX */ - else { - if ((name->sz < 2) || - (wolfSSL_BIO_write(bio, name->name, name->sz - 1) != name->sz - 1)) - return WOLFSSL_FAILURE; - } + return WOLFSSL_SUCCESS; } diff --git a/tests/api.c b/tests/api.c index 5307cb02a..469ade94c 100644 --- a/tests/api.c +++ b/tests/api.c @@ -29623,6 +29623,142 @@ static void test_wolfSSL_X509_NAME_hash(void) #endif } +static void test_wolfSSL_X509_NAME_print_ex(void) +{ +#if (defined(OPENSSL_ALL) || (defined(OPENSSL_EXTRA) && \ + (defined(HAVE_STUNNEL) || defined(WOLFSSL_NGINX) || \ + defined(HAVE_LIGHTY) || defined(WOLFSSL_HAPROXY) || \ + defined(WOLFSSL_OPENSSH) || defined(HAVE_SBLIM_SFCB)))) && \ + !defined(NO_BIO) + int memSz; + byte* mem = NULL; + BIO* bio = NULL; + BIO* membio = NULL; + X509* x509 = NULL; + X509_NAME* name = NULL; + + const char* expNormal = "C=US,CN=wolfssl.com"; + const char* expReverse = "CN=wolfssl.com,C=US"; + + const char* expNotEscaped = "C= US,+\"\\ ,CN=#wolfssl.com<>;"; + const char* expNotEscapedRev = "CN=#wolfssl.com<>;,C= US,+\"\\ "; + const char* expRFC5523 = + "CN=\\#wolfssl.com\\<\\>\\;,C=\\ US\\,\\+\\\"\\\\\\ "; + + printf(testingFmt, "wolfSSL_X509_NAME_print_ex"); + + /* Test with real cert (svrCertFile) first */ + AssertNotNull(bio = BIO_new(BIO_s_file())); + AssertIntGT(BIO_read_filename(bio, svrCertFile), 0); + AssertNotNull(PEM_read_bio_X509(bio, &x509, NULL, NULL)); + AssertNotNull(name = X509_get_subject_name(x509)); + + /* Test without flags */ + AssertNotNull(membio = BIO_new(BIO_s_mem())); + AssertIntEQ(X509_NAME_print_ex(membio, name, 0, 0), WOLFSSL_SUCCESS); + BIO_free(membio); + + /* Test flag: XN_FLAG_RFC2253 */ + AssertNotNull(membio = BIO_new(BIO_s_mem())); + AssertIntEQ(X509_NAME_print_ex(membio, name, 0, + XN_FLAG_RFC2253), WOLFSSL_SUCCESS); + BIO_free(membio); + + /* Test flag: XN_FLAG_RFC2253 | XN_FLAG_DN_REV */ + AssertNotNull(membio = BIO_new(BIO_s_mem())); + AssertIntEQ(X509_NAME_print_ex(membio, name, 0, + XN_FLAG_RFC2253 | XN_FLAG_DN_REV), WOLFSSL_SUCCESS); + BIO_free(membio); + + X509_free(x509); + BIO_free(bio); + + /* Test normal case without escaped characters */ + { + /* Create name: "/C=US/CN=wolfssl.com" */ + AssertNotNull(name = X509_NAME_new()); + AssertIntEQ(X509_NAME_add_entry_by_txt(name, "countryName", + MBSTRING_UTF8, (byte*)"US", 2, -1, 0), + WOLFSSL_SUCCESS); + AssertIntEQ(X509_NAME_add_entry_by_txt(name, "commonName", + MBSTRING_UTF8, (byte*)"wolfssl.com", 11, -1, 0), + WOLFSSL_SUCCESS); + + /* Test without flags */ + AssertNotNull(membio = BIO_new(BIO_s_mem())); + AssertIntEQ(X509_NAME_print_ex(membio, name, 0, 0), WOLFSSL_SUCCESS); + AssertIntGE((memSz = BIO_get_mem_data(membio, &mem)), 0); + AssertIntEQ(memSz, XSTRLEN(expNormal)+1); + AssertIntEQ(XSTRNCMP((char*)mem, expNormal, XSTRLEN(expNormal)), 0); + BIO_free(membio); + + /* Test flags: XN_FLAG_RFC2253 - should be reversed */ + AssertNotNull(membio = BIO_new(BIO_s_mem())); + AssertIntEQ(X509_NAME_print_ex(membio, name, 0, + XN_FLAG_RFC2253), WOLFSSL_SUCCESS); + AssertIntGE((memSz = BIO_get_mem_data(membio, &mem)), 0); + AssertIntEQ(memSz, XSTRLEN(expReverse)+1); + BIO_free(membio); + + /* Test flags: XN_FLAG_DN_REV - reversed */ + AssertNotNull(membio = BIO_new(BIO_s_mem())); + AssertIntEQ(X509_NAME_print_ex(membio, name, 0, + XN_FLAG_DN_REV), WOLFSSL_SUCCESS); + AssertIntGE((memSz = BIO_get_mem_data(membio, &mem)), 0); + AssertIntEQ(memSz, XSTRLEN(expReverse)+1); + AssertIntEQ(XSTRNCMP((char*)mem, expReverse, XSTRLEN(expReverse)), 0); + BIO_free(membio); + + X509_NAME_free(name); + } + + /* Test RFC2253 characters are escaped with backslashes */ + { + AssertNotNull(name = X509_NAME_new()); + AssertIntEQ(X509_NAME_add_entry_by_txt(name, "countryName", + /* space at beginning and end, and: ,+"\ */ + MBSTRING_UTF8, (byte*)" US,+\"\\ ", 8, -1, 0), + WOLFSSL_SUCCESS); + AssertIntEQ(X509_NAME_add_entry_by_txt(name, "commonName", + /* # at beginning, and: <>;*/ + MBSTRING_UTF8, (byte*)"#wolfssl.com<>;", 15, -1, 0), + WOLFSSL_SUCCESS); + + /* Test without flags */ + AssertNotNull(membio = BIO_new(BIO_s_mem())); + AssertIntEQ(X509_NAME_print_ex(membio, name, 0, 0), WOLFSSL_SUCCESS); + AssertIntGE((memSz = BIO_get_mem_data(membio, &mem)), 0); + AssertIntEQ(memSz, XSTRLEN(expNotEscaped)+1); + AssertIntEQ(XSTRNCMP((char*)mem, expNotEscaped, + XSTRLEN(expNotEscaped)), 0); + BIO_free(membio); + + /* Test flags: XN_FLAG_RFC5523 - should be reversed and escaped */ + AssertNotNull(membio = BIO_new(BIO_s_mem())); + AssertIntEQ(X509_NAME_print_ex(membio, name, 0, + XN_FLAG_RFC2253), WOLFSSL_SUCCESS); + AssertIntGE((memSz = BIO_get_mem_data(membio, &mem)), 0); + AssertIntEQ(memSz, XSTRLEN(expRFC5523)+1); + AssertIntEQ(XSTRNCMP((char*)mem, expRFC5523, XSTRLEN(expRFC5523)), 0); + BIO_free(membio); + + /* Test flags: XN_FLAG_DN_REV - reversed but not escaped */ + AssertNotNull(membio = BIO_new(BIO_s_mem())); + AssertIntEQ(X509_NAME_print_ex(membio, name, 0, + XN_FLAG_DN_REV), WOLFSSL_SUCCESS); + AssertIntGE((memSz = BIO_get_mem_data(membio, &mem)), 0); + AssertIntEQ(memSz, XSTRLEN(expNotEscapedRev)+1); + AssertIntEQ(XSTRNCMP((char*)mem, expNotEscapedRev, + XSTRLEN(expNotEscapedRev)), 0); + BIO_free(membio); + + X509_NAME_free(name); + } + + printf(resultFmt, passed); +#endif +} + #ifndef NO_BIO static void test_wolfSSL_X509_INFO_multiple_info(void) { @@ -52579,6 +52715,7 @@ void ApiTest(void) test_wolfSSL_lhash(); test_wolfSSL_X509_NAME(); test_wolfSSL_X509_NAME_hash(); + test_wolfSSL_X509_NAME_print_ex(); #ifndef NO_BIO test_wolfSSL_X509_INFO_multiple_info(); test_wolfSSL_X509_INFO();