Add tests for X509_NAME_print_ex.
For now, the tests assert the existing behavior of X509_NAME_print, but
there are several bugs in it.
Change-Id: I9bc211a880ea48f7f756650dbe1f982bc1ec689d
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/52366
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc
index b201afe..6f2170c 100644
--- a/crypto/x509/x509_test.cc
+++ b/crypto/x509/x509_test.cc
@@ -3911,3 +3911,294 @@
EXPECT_STREQ(hex.get(), t.hex);
}
}
+
+TEST(X509Test, NamePrint) {
+ // kTestName is a DER-encoded X.509 that covers many cases.
+ //
+ // SEQUENCE {
+ // SET {
+ // SEQUENCE {
+ // # countryName
+ // OBJECT_IDENTIFIER { 2.5.4.6 }
+ // PrintableString { "US" }
+ // }
+ // }
+ // # Sets may be multi-valued, with different attributes. Try to keep this
+ // # in DER set order, in case we ever enforce this in the parser.
+ // SET {
+ // SEQUENCE {
+ // # stateOrProvinceName
+ // OBJECT_IDENTIFIER { 2.5.4.8 }
+ // PrintableString { "Some State" }
+ // }
+ // SEQUENCE {
+ // # stateOrProvinceName
+ // OBJECT_IDENTIFIER { 2.5.4.8 }
+ // UTF8String { "Some Other State \xe2\x98\x83" }
+ // }
+ // SEQUENCE {
+ // # stateOrProvinceName
+ // OBJECT_IDENTIFIER { 2.5.4.8 }
+ // BMPString { u"Another State \u2603" }
+ // }
+ // SEQUENCE {
+ // # A custom OID
+ // OBJECT_IDENTIFIER { 1.2.840.113554.4.1.72585.2 }
+ // UniversalString { U"\u2603" }
+ // }
+ // }
+ // # Custom OIDs may have non-string values.
+ // SET {
+ // SEQUENCE {
+ // OBJECT_IDENTIFIER { 1.2.840.113554.4.1.72585.3 }
+ // SEQUENCE { INTEGER { 1 } INTEGER { 2 } }
+ // }
+ // }
+ // SET {
+ // SEQUENCE {
+ // # organizationName
+ // OBJECT_IDENTIFIER { 2.5.4.10 }
+ // PrintableString { "Org Name" }
+ // }
+ // }
+ // SET {
+ // SEQUENCE {
+ // # commonName
+ // OBJECT_IDENTIFIER { 2.5.4.3 }
+ // # Embed common delimiter forms to test how well they get escaped.
+ // UTF8String { "Common
+ // Name/CN=A/CN=B,CN=A,CN=B+CN=A+CN=B;CN=A;CN=B\nCN=A\n" }
+ // }
+ // }
+ // SET {
+ // SEQUENCE {
+ // # commonName
+ // OBJECT_IDENTIFIER { 2.5.4.3 }
+ // # Test escaping of leading and trailing spaces.
+ // UTF8String { " spaces " }
+ // }
+ // }
+ static const uint8_t kTestName[] = {
+ 0x30, 0x82, 0x01, 0x00, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04,
+ 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x6d, 0x30, 0x11, 0x06, 0x03, 0x55,
+ 0x04, 0x08, 0x13, 0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x20, 0x53, 0x74, 0x61,
+ 0x74, 0x65, 0x30, 0x1b, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x14, 0x53,
+ 0x6f, 0x6d, 0x65, 0x20, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x20, 0x53, 0x74,
+ 0x61, 0x74, 0x65, 0x20, 0xe2, 0x98, 0x83, 0x30, 0x25, 0x06, 0x03, 0x55,
+ 0x04, 0x08, 0x1e, 0x1e, 0x00, 0x41, 0x00, 0x6e, 0x00, 0x6f, 0x00, 0x74,
+ 0x00, 0x68, 0x00, 0x65, 0x00, 0x72, 0x00, 0x20, 0x00, 0x53, 0x00, 0x74,
+ 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x20, 0x26, 0x03, 0x30, 0x14,
+ 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, 0x84, 0xb7,
+ 0x09, 0x02, 0x1c, 0x04, 0x00, 0x00, 0x26, 0x03, 0x31, 0x18, 0x30, 0x16,
+ 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, 0x84, 0xb7,
+ 0x09, 0x03, 0x30, 0x06, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02, 0x31, 0x11,
+ 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x08, 0x4f, 0x72, 0x67,
+ 0x20, 0x4e, 0x61, 0x6d, 0x65, 0x31, 0x42, 0x30, 0x40, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x0c, 0x39, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x20, 0x4e,
+ 0x61, 0x6d, 0x65, 0x2f, 0x43, 0x4e, 0x3d, 0x41, 0x2f, 0x43, 0x4e, 0x3d,
+ 0x42, 0x2c, 0x43, 0x4e, 0x3d, 0x41, 0x2c, 0x43, 0x4e, 0x3d, 0x42, 0x2b,
+ 0x43, 0x4e, 0x3d, 0x41, 0x2b, 0x43, 0x4e, 0x3d, 0x42, 0x3b, 0x43, 0x4e,
+ 0x3d, 0x41, 0x3b, 0x43, 0x4e, 0x3d, 0x42, 0x0a, 0x43, 0x4e, 0x3d, 0x41,
+ 0x0a, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x08,
+ 0x20, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x20};
+
+ const uint8_t *ptr = kTestName;
+ bssl::UniquePtr<X509_NAME> name(
+ d2i_X509_NAME(nullptr, &ptr, sizeof(kTestName)));
+ ASSERT_TRUE(name);
+ EXPECT_EQ(ptr, kTestName + sizeof(kTestName));
+
+ struct {
+ int indent;
+ unsigned long flags;
+ std::string printed;
+ } kTests[] = {
+ // RFC 2253 uses , and + separators and encodes the RDNs in reverse.
+ // OpenSSL's implementation additionally happens to reverse the values
+ // within each RDN. RFC 2253 says any order is permissible.
+ {/*indent=*/0,
+ /*flags=*/XN_FLAG_RFC2253,
+ "CN=\\ spaces\\ ,"
+ "CN=Common "
+ "Name/CN=A/CN=B\\,CN=A\\,CN=B\\+CN=A\\+CN=B\\;CN=A\\;CN=B\\0ACN=A\\0A,"
+ "O=Org Name,"
+ "1.2.840.113554.4.1.72585.3=#3006020101020102,"
+ "1.2.840.113554.4.1.72585.2=#1C0400002603+"
+ "ST=Another State \\E2\\98\\83+"
+ "ST=Some Other State \\E2\\98\\83+"
+ "ST=Some State,"
+ "C=US"},
+ {/*indent=*/2,
+ /*flags=*/XN_FLAG_RFC2253,
+ " "
+ "CN=\\ spaces\\ ,"
+ "CN=Common "
+ "Name/CN=A/CN=B\\,CN=A\\,CN=B\\+CN=A\\+CN=B\\;CN=A\\;CN=B\\0ACN=A\\0A,"
+ "O=Org Name,"
+ "1.2.840.113554.4.1.72585.3=#3006020101020102,"
+ "1.2.840.113554.4.1.72585.2=#1C0400002603+"
+ "ST=Another State \\E2\\98\\83+"
+ "ST=Some Other State \\E2\\98\\83+"
+ "ST=Some State,"
+ "C=US"},
+ // |XN_FLAG_ONELINE| is an OpenSSL-specific single-line format. It also
+ // omits |XN_FLAG_DUMP_UNKNOWN_FIELDS|, so unknown OIDs that use known
+ // string types will still be decoded. (This may drop important
+ // information if the unknown OID distinguishes between string types.) It
+ // also passes |ASN1_STRFLGS_ESC_QUOTE|.
+ {/*indent=*/0,
+ /*flags=*/XN_FLAG_ONELINE,
+ "C = US, "
+ "ST = Some State + "
+ "ST = Some Other State \\E2\\98\\83 + "
+ "ST = Another State \\E2\\98\\83 + "
+ "1.2.840.113554.4.1.72585.2 = \\E2\\98\\83, "
+ "1.2.840.113554.4.1.72585.3 = #3006020101020102, "
+ "O = Org Name, "
+ "CN = \"Common "
+ "Name/CN=A/CN=B,CN=A,CN=B+CN=A+CN=B;CN=A;CN=B\\0ACN=A\\0A\", "
+ "CN = \" spaces \""},
+ // |XN_FLAG_MULTILINE| is an OpenSSL-specific multi-line format that tries
+ // to vertically align the equal sizes. The vertical alignment doesn't
+ // quite handle multi-valued RDNs right and uses a non-RFC-2253 escaping.
+ {/*indent=*/0,
+ /*flags=*/XN_FLAG_MULTILINE,
+ "countryName = US\n"
+ "stateOrProvinceName = Some State + "
+ "stateOrProvinceName = Some Other State \\U2603 + "
+ "stateOrProvinceName = Another State \\U2603 + "
+ "1.2.840.113554.4.1.72585.2 = \\U2603\n"
+ "1.2.840.113554.4.1.72585.3 = 0\\06\\02\\01\\01\\02\\01\\02\n"
+ "organizationName = Org Name\n"
+ "commonName = Common "
+ "Name/CN=A/CN=B,CN=A,CN=B+CN=A+CN=B;CN=A;CN=B\\0ACN=A\\0A\n"
+ "commonName = spaces "},
+ // The multiline format indents every line.
+ {/*indent=*/2,
+ /*flags=*/XN_FLAG_MULTILINE,
+ " countryName = US\n"
+ " stateOrProvinceName = Some State + "
+ "stateOrProvinceName = Some Other State \\U2603 + "
+ "stateOrProvinceName = Another State \\U2603 + "
+ "1.2.840.113554.4.1.72585.2 = \\U2603\n"
+ " 1.2.840.113554.4.1.72585.3 = 0\\06\\02\\01\\01\\02\\01\\02\n"
+ " organizationName = Org Name\n"
+ " commonName = Common "
+ "Name/CN=A/CN=B,CN=A,CN=B+CN=A+CN=B;CN=A;CN=B\\0ACN=A\\0A\n"
+ " commonName = spaces "},
+ // Callers can also customize the output, wuith both |XN_FLAG_*| and
+ // |ASN1_STRFLGS_*|. |XN_FLAG_SEP_SPLUS_SPC| uses semicolon separators and
+ // |XN_FLAG_FN_OID| forces OIDs.
+ {/*indent=*/0,
+ /*flags=*/XN_FLAG_SEP_SPLUS_SPC | XN_FLAG_FN_OID | ASN1_STRFLGS_RFC2253 |
+ ASN1_STRFLGS_ESC_QUOTE,
+ "2.5.4.6=US; "
+ "2.5.4.8=Some State + "
+ "2.5.4.8=Some Other State \\E2\\98\\83 + "
+ "2.5.4.8=Another State \\E2\\98\\83 + "
+ "1.2.840.113554.4.1.72585.2=\\E2\\98\\83; "
+ "1.2.840.113554.4.1.72585.3=#3006020101020102; "
+ "2.5.4.10=Org Name; "
+ "2.5.4.3=\"Common "
+ "Name/CN=A/CN=B,CN=A,CN=B+CN=A+CN=B;CN=A;CN=B\\0ACN=A\\0A\"; "
+ "2.5.4.3=\" spaces \""},
+ // |XN_FLAG_COMPAT| matches |X509_NAME_print|, rather than
+ // |X509_NAME_print_ex|.
+ //
+ // TODO(davidben): This works by post-processing the output of
+ // |X509_NAME_oneline|, which uses "/"" separators, and replacing with
+ // ", ". The escaping is ambiguous and the post-processing is buggy, so
+ // some of the trailing slashes are still present and some internal
+ // slashes are mis-converted.
+ {/*indent=*/0,
+ /*flags=*/XN_FLAG_COMPAT,
+ "C=US, "
+ "ST=Some State, "
+ "ST=Some Other State \\xE2\\x98\\x83, "
+ "ST=\\x00A\\x00n\\x00o\\x00t\\x00h\\x00e\\x00r\\x00 "
+ "\\x00S\\x00t\\x00a\\x00t\\x00e\\x00 &\\x03/"
+ "1.2.840.113554.4.1.72585.2=\\x00\\x00&\\x03/"
+ "1.2.840.113554.4.1.72585.3=0\\x06\\x02\\x01\\x01\\x02\\x01\\x02, "
+ "O=Org Name, "
+ "CN=Common Name, "
+ "CN=A, CN=B,CN=A,CN=B+CN=A+CN=B;CN=A;CN=B\\x0ACN=A\\x0A, "
+ "CN= spaces "},
+ };
+ for (const auto &t : kTests) {
+ SCOPED_TRACE(t.printed);
+ bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_mem()));
+ ASSERT_TRUE(bio);
+ int len = X509_NAME_print_ex(bio.get(), name.get(), t.indent, t.flags);
+ ASSERT_GT(len, 0);
+
+ const uint8_t *printed;
+ size_t printed_len;
+ ASSERT_TRUE(BIO_mem_contents(bio.get(), &printed, &printed_len));
+ EXPECT_EQ(std::string(printed, printed + printed_len), t.printed);
+ if (t.flags != XN_FLAG_COMPAT) {
+ // TODO(davidben): |XN_FLAG_COMPAT| does not return the length.
+ EXPECT_EQ(static_cast<size_t>(len), printed_len);
+
+ // Passing a null |BIO| measures the output instead.
+ len = X509_NAME_print_ex(nullptr, name.get(), t.indent, t.flags);
+ EXPECT_GT(len, 0);
+ EXPECT_EQ(static_cast<size_t>(len), printed_len);
+ }
+ }
+
+ // TODO(davidben): This escapes the underlying bytes in the string, but that
+ // is ambiguous without capturing the type. Should this escape like
+ // |ASN1_STRFLGS_UTF8_CONVERT| instead?
+ static const char *kOnelineComponents[] = {
+ "/C=US",
+ "/ST=Some State",
+ "/ST=Some Other State \\xE2\\x98\\x83",
+ "/ST=\\x00A\\x00n\\x00o\\x00t\\x00h\\x00e\\x00r\\x00 "
+ "\\x00S\\x00t\\x00a\\x00t\\x00e\\x00 &\\x03",
+ "/1.2.840.113554.4.1.72585.2=\\x00\\x00&\\x03",
+ "/1.2.840.113554.4.1.72585.3=0\\x06\\x02\\x01\\x01\\x02\\x01\\x02",
+ "/O=Org Name",
+ "/CN=Common Name/CN=A/CN=B,CN=A,CN=B+CN=A+CN=B;CN=A;CN=B\\x0ACN=A\\x0A",
+ "/CN= spaces ",
+ };
+ std::string oneline_expected;
+ for (const auto& component : kOnelineComponents) {
+ oneline_expected += component;
+ }
+
+ // Given null buffer, |X509_NAME_oneline| allocates a new output.
+ bssl::UniquePtr<char> oneline(X509_NAME_oneline(name.get(), nullptr, 0));
+ ASSERT_TRUE(oneline);
+ EXPECT_EQ(oneline.get(), oneline_expected);
+
+ // Otherwise it writes to the specified buffer. Note one extra byte is needed
+ // for the trailing NUL.
+ char buf[1024];
+ ASSERT_GE(sizeof(buf), oneline_expected.size() + 2);
+ ASSERT_EQ(buf,
+ X509_NAME_oneline(name.get(), buf, oneline_expected.size() + 1));
+ EXPECT_EQ(buf, oneline_expected);
+
+ memset(buf, 'a', sizeof(buf));
+ ASSERT_EQ(buf,
+ X509_NAME_oneline(name.get(), buf, oneline_expected.size() + 2));
+ EXPECT_EQ(buf, oneline_expected);
+
+ // If the length is too small, |X509_NAME_oneline| truncates at name
+ // entry boundaries.
+ EXPECT_EQ(nullptr, X509_NAME_oneline(name.get(), buf, 0));
+ for (size_t len = 1; len < oneline_expected.size(); len++) {
+ SCOPED_TRACE(len);
+ memset(buf, 'a', sizeof(buf));
+ EXPECT_EQ(buf, X509_NAME_oneline(name.get(), buf, len));
+
+ std::string truncated;
+ for (const auto& component : kOnelineComponents) {
+ if (truncated.size() + strlen(component) + 1 > len) {
+ break;
+ }
+ truncated += component;
+ }
+ EXPECT_EQ(buf, truncated);
+ }
+}
diff --git a/include/openssl/asn1.h b/include/openssl/asn1.h
index d6fa2f7..5ae0064 100644
--- a/include/openssl/asn1.h
+++ b/include/openssl/asn1.h
@@ -1650,6 +1650,8 @@
// replaced with '.'.
OPENSSL_EXPORT int ASN1_STRING_print(BIO *out, const ASN1_STRING *str);
+// The following flags must not collide with |XN_FLAG_*|.
+
// ASN1_STRFLGS_ESC_2253 causes characters to be escaped as in RFC 2253, section
// 2.4.
#define ASN1_STRFLGS_ESC_2253 1
diff --git a/include/openssl/x509.h b/include/openssl/x509.h
index 3633186..7e67080 100644
--- a/include/openssl/x509.h
+++ b/include/openssl/x509.h
@@ -199,7 +199,8 @@
#define X509_FLAG_NO_ATTRIBUTES (1L << 11)
#define X509_FLAG_NO_IDS (1L << 12)
-// Flags specific to X509_NAME_print_ex()
+// Flags specific to X509_NAME_print_ex(). These flags must not collide with
+// |ASN1_STRFLGS_*|.
// The field separator information