Skip to content

Commit db36c3b

Browse files
Bob Becksamuel40791765
Bob Beck
authored andcommitted
Convert X509_NAME_get_text_by_[NID|OBJ] to return UTF-8
Callers to these functions are usually using them to grab subject name components and universally use the result as a C string, whereas the OpenSSL versions return raw ASN1_STRING bytes and ignore the encoding, which "usually works" in a "hold my beer here are some bytes" sort of way until the object is not encoded as you hoped. Make this safer for the callers by making the returned result be at least "text" and a C string. This converts the ASN1_STRING bytes to UTF-8, and will introduce new failure cases for this function if either memory allocation fails for the UTF-8 conversion, or if the resulting UTF-8 contains a 0 codepoint and would produce an artificially truncated C string. Additionally if the provided buffer is not NULL but is too small to hold the output, we fail rather than returning a truncated output. Fixed: 436 Change-Id: I487c10a5ff5188e94df520ef4c8982e593c680d7 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/58445 Reviewed-by: David Benjamin <[email protected]> Commit-Queue: Bob Beck <[email protected]> (cherry picked from commit 3763efb56b5282cf92d71c259576352555c1a8f8)
1 parent 2ed11af commit db36c3b

File tree

3 files changed

+109
-20
lines changed

3 files changed

+109
-20
lines changed

crypto/x509/x509_test.cc

+65
Original file line numberDiff line numberDiff line change
@@ -6931,3 +6931,68 @@ TEST(X509Test, NameAttributeValues) {
69316931
EXPECT_FALSE(name);
69326932
}
69336933
}
6934+
6935+
TEST(X509Test, GetTextByOBJ) {
6936+
struct OBJTestCase {
6937+
const char *content;
6938+
int content_type;
6939+
int len;
6940+
int expected_result;
6941+
const char *expected_string;
6942+
} kTests[] = {
6943+
{"", V_ASN1_UTF8STRING, 0, 0, ""},
6944+
{"derp", V_ASN1_UTF8STRING, 4, 4, "derp"},
6945+
{"\x30\x00", // Empty sequence can not be converted to UTF-8
6946+
V_ASN1_SEQUENCE, 2, -1, ""},
6947+
{
6948+
"der\0p",
6949+
V_ASN1_TELETEXSTRING,
6950+
5,
6951+
-1,
6952+
"",
6953+
},
6954+
{
6955+
"0123456789ABCDEF",
6956+
V_ASN1_IA5STRING,
6957+
16,
6958+
16,
6959+
"0123456789ABCDEF",
6960+
},
6961+
{
6962+
"\x07\xff",
6963+
V_ASN1_BMPSTRING,
6964+
2,
6965+
2,
6966+
"\xdf\xbf",
6967+
},
6968+
{
6969+
"\x00\xc3\x00\xaf",
6970+
V_ASN1_BMPSTRING,
6971+
4,
6972+
4,
6973+
"\xc3\x83\xc2\xaf",
6974+
},
6975+
};
6976+
for (const auto &test : kTests) {
6977+
bssl::UniquePtr<X509_NAME> name(X509_NAME_new());
6978+
ASSERT_TRUE(name);
6979+
ASSERT_TRUE(X509_NAME_add_entry_by_NID(
6980+
name.get(), NID_commonName, test.content_type,
6981+
reinterpret_cast<const uint8_t *>(test.content), test.len, /*loc=*/-1,
6982+
/*set=*/0));
6983+
char text[256] = {};
6984+
EXPECT_EQ(test.expected_result,
6985+
X509_NAME_get_text_by_NID(name.get(), NID_commonName, text,
6986+
sizeof(text)));
6987+
EXPECT_STREQ(text, test.expected_string);
6988+
if (test.expected_result > 0) {
6989+
// Test truncation. The function writes a trailing NUL byte so the
6990+
// buffer needs to be one bigger than the expected result.
6991+
char small[2] = "a";
6992+
EXPECT_EQ(
6993+
-1, X509_NAME_get_text_by_NID(name.get(), NID_commonName, small, 1));
6994+
// The buffer should be unmodified by truncation failure.
6995+
EXPECT_STREQ(small, "a");
6996+
}
6997+
}
6998+
}

crypto/x509/x509name.c

+28-6
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
#include <string.h>
5858

5959
#include <openssl/asn1.h>
60+
#include <openssl/bytestring.h>
6061
#include <openssl/err.h>
6162
#include <openssl/evp.h>
6263
#include <openssl/obj.h>
@@ -86,13 +87,34 @@ int X509_NAME_get_text_by_OBJ(const X509_NAME *name, const ASN1_OBJECT *obj,
8687
}
8788
const ASN1_STRING *data =
8889
X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, i));
89-
i = (data->length > (len - 1)) ? (len - 1) : data->length;
90-
if (buf == NULL) {
91-
return data->length;
90+
unsigned char *text = NULL;
91+
int ret = -1;
92+
int text_len = ASN1_STRING_to_UTF8(&text, data);
93+
// Fail if we could not encode as UTF-8.
94+
if (text_len < 0) {
95+
goto out;
96+
}
97+
CBS cbs;
98+
CBS_init(&cbs, text, text_len);
99+
// Fail if the UTF-8 encoding constains a 0 byte because this is
100+
// returned as a C string and callers very often do not check.
101+
if (CBS_contains_zero_byte(&cbs)) {
102+
goto out;
103+
}
104+
// We still support the "pass NULL to find out how much" API
105+
if (buf != NULL) {
106+
if (text_len >= len || len <= 0 ||
107+
!CBS_copy_bytes(&cbs, (uint8_t *)buf, text_len)) {
108+
goto out;
109+
}
110+
// It must be a C string
111+
buf[text_len] = '\0';
92112
}
93-
OPENSSL_memcpy(buf, data->data, i);
94-
buf[i] = '\0';
95-
return i;
113+
ret = text_len;
114+
115+
out:
116+
OPENSSL_free(text);
117+
return ret;
96118
}
97119

98120
int X509_NAME_entry_count(const X509_NAME *name) {

include/openssl/x509.h

+16-14
Original file line numberDiff line numberDiff line change
@@ -2105,20 +2105,22 @@ OPENSSL_EXPORT ASN1_TIME *X509_CRL_get_nextUpdate(X509_CRL *crl);
21052105
OPENSSL_EXPORT ASN1_INTEGER *X509_get_serialNumber(X509 *x509);
21062106

21072107
// X509_NAME_get_text_by_OBJ finds the first attribute with type |obj| in
2108-
// |name|. If found, it ignores the value's ASN.1 type, writes the raw
2109-
// |ASN1_STRING| representation to |buf|, followed by a NUL byte, and
2110-
// returns the number of bytes in output, excluding the NUL byte.
2111-
//
2112-
// This function writes at most |len| bytes, including the NUL byte. If |len| is
2113-
// not large enough, it silently truncates the output to fit. If |buf| is NULL,
2114-
// it instead writes enough and returns the number of bytes in the output,
2115-
// excluding the NUL byte.
2116-
//
2117-
// WARNING: Do not use this function. It does not return enough information for
2118-
// the caller to correctly interpret its output. The attribute value may be of
2119-
// any type, including one of several ASN.1 string encodings, but this function
2120-
// only outputs the raw |ASN1_STRING| representation. See
2121-
// https://crbug.com/boringssl/436.
2108+
// |name|. If found, it writes the value's UTF-8 representation to |buf|.
2109+
// followed by a NUL byte, and returns the number of bytes in the output,
2110+
// excluding the NUL byte. This is unlike OpenSSL which returns the raw
2111+
// ASN1_STRING data. The UTF-8 encoding of the |ASN1_STRING| may not contain a 0
2112+
// codepoint.
2113+
//
2114+
// This function writes at most |len| bytes, including the NUL byte. If |buf|
2115+
// is NULL, it writes nothing and returns the number of bytes in the
2116+
// output, excluding the NUL byte that would be required for the full UTF-8
2117+
// output.
2118+
//
2119+
// This function may return -1 if an error occurs for any reason, including the
2120+
// value not being a recognized string type, |len| being of insufficient size to
2121+
// hold the full UTF-8 encoding and NUL byte, memory allocation failures, an
2122+
// object with type |obj| not existing in |name|, or if the UTF-8 encoding of
2123+
// the string contains a zero byte.
21222124
OPENSSL_EXPORT int X509_NAME_get_text_by_OBJ(const X509_NAME *name,
21232125
const ASN1_OBJECT *obj, char *buf,
21242126
int len);

0 commit comments

Comments
 (0)