Document ASN1_STRING_to_UTF8.

We already had a test, but move it to asn1_test.cc since it's part of
the ASN.1 library. Also, since it's easy, test it using public APIs
rather than stack-allocating an ASN1_STRING.

Change-Id: Ic77494e6c8f74584d159a600e334416197761475
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48227
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/crypto/asn1/asn1_test.cc b/crypto/asn1/asn1_test.cc
index a14a5cc..725542c 100644
--- a/crypto/asn1/asn1_test.cc
+++ b/crypto/asn1/asn1_test.cc
@@ -398,6 +398,64 @@
   TestSerialize(val.get(), i2d_ASN1_BIT_STRING, kBitStringEmpty);
 }
 
+TEST(ASN1Test, StringToUTF8) {
+  static const struct {
+    std::vector<uint8_t> in;
+    int type;
+    const char *expected;
+  } kTests[] = {
+      // Non-minimal, two-byte UTF-8.
+      {{0xc0, 0x81}, V_ASN1_UTF8STRING, nullptr},
+      // Non-minimal, three-byte UTF-8.
+      {{0xe0, 0x80, 0x81}, V_ASN1_UTF8STRING, nullptr},
+      // Non-minimal, four-byte UTF-8.
+      {{0xf0, 0x80, 0x80, 0x81}, V_ASN1_UTF8STRING, nullptr},
+      // Truncated, four-byte UTF-8.
+      {{0xf0, 0x80, 0x80}, V_ASN1_UTF8STRING, nullptr},
+      // Low-surrogate value.
+      {{0xed, 0xa0, 0x80}, V_ASN1_UTF8STRING, nullptr},
+      // High-surrogate value.
+      {{0xed, 0xb0, 0x81}, V_ASN1_UTF8STRING, nullptr},
+      // Initial BOMs should be rejected from UCS-2 and UCS-4.
+      {{0xfe, 0xff, 0, 88}, V_ASN1_BMPSTRING, nullptr},
+      {{0, 0, 0xfe, 0xff, 0, 0, 0, 88}, V_ASN1_UNIVERSALSTRING, nullptr},
+      // Otherwise, BOMs should pass through.
+      {{0, 88, 0xfe, 0xff}, V_ASN1_BMPSTRING, "X\xef\xbb\xbf"},
+      {{0, 0, 0, 88, 0, 0, 0xfe, 0xff}, V_ASN1_UNIVERSALSTRING,
+       "X\xef\xbb\xbf"},
+      // The maximum code-point should pass though.
+      {{0, 16, 0xff, 0xfd}, V_ASN1_UNIVERSALSTRING, "\xf4\x8f\xbf\xbd"},
+      // Values outside the Unicode space should not.
+      {{0, 17, 0, 0}, V_ASN1_UNIVERSALSTRING, nullptr},
+      // Non-characters should be rejected.
+      {{0, 1, 0xff, 0xff}, V_ASN1_UNIVERSALSTRING, nullptr},
+      {{0, 1, 0xff, 0xfe}, V_ASN1_UNIVERSALSTRING, nullptr},
+      {{0, 0, 0xfd, 0xd5}, V_ASN1_UNIVERSALSTRING, nullptr},
+      // BMPString is UCS-2, not UTF-16, so surrogate pairs are invalid.
+      {{0xd8, 0, 0xdc, 1}, V_ASN1_BMPSTRING, nullptr},
+  };
+
+  for (const auto &test : kTests) {
+    SCOPED_TRACE(Bytes(test.in));
+    SCOPED_TRACE(test.type);
+    bssl::UniquePtr<ASN1_STRING> s(ASN1_STRING_type_new(test.type));
+    ASSERT_TRUE(s);
+    ASSERT_TRUE(ASN1_STRING_set(s.get(), test.in.data(), test.in.size()));
+
+    uint8_t *utf8;
+    const int utf8_len = ASN1_STRING_to_UTF8(&utf8, s.get());
+    EXPECT_EQ(utf8_len < 0, test.expected == nullptr);
+    if (utf8_len >= 0) {
+      if (test.expected != nullptr) {
+        EXPECT_EQ(Bytes(test.expected), Bytes(utf8, utf8_len));
+      }
+      OPENSSL_free(utf8);
+    } else {
+      ERR_clear_error();
+    }
+  }
+}
+
 // The ASN.1 macros do not work on Windows shared library builds, where usage of
 // |OPENSSL_EXPORT| is a bit stricter.
 #if !defined(OPENSSL_WINDOWS) || !defined(BORINGSSL_SHARED_LIBRARY)
diff --git a/crypto/x509/a_strex.c b/crypto/x509/a_strex.c
index 2c4824e..752d74e 100644
--- a/crypto/x509/a_strex.c
+++ b/crypto/x509/a_strex.c
@@ -627,7 +627,7 @@
  * in output string or a negative error code
  */
 
-int ASN1_STRING_to_UTF8(unsigned char **out, ASN1_STRING *in)
+int ASN1_STRING_to_UTF8(unsigned char **out, const ASN1_STRING *in)
 {
     ASN1_STRING stmp, *str = &stmp;
     int mbflag, type, ret;
diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc
index a3c7b66..fde8bd5 100644
--- a/crypto/x509/x509_test.cc
+++ b/crypto/x509/x509_test.cc
@@ -1986,65 +1986,6 @@
   EXPECT_EQ(X509_NAME_ENTRY_set(X509_NAME_get_entry(name.get(), 2)), 2);
 }
 
-TEST(X509Test, StringDecoding) {
-  static const struct {
-    std::vector<uint8_t> in;
-    int type;
-    const char *expected;
-  } kTests[] = {
-      // Non-minimal, two-byte UTF-8.
-      {{0xc0, 0x81}, V_ASN1_UTF8STRING, nullptr},
-      // Non-minimal, three-byte UTF-8.
-      {{0xe0, 0x80, 0x81}, V_ASN1_UTF8STRING, nullptr},
-      // Non-minimal, four-byte UTF-8.
-      {{0xf0, 0x80, 0x80, 0x81}, V_ASN1_UTF8STRING, nullptr},
-      // Truncated, four-byte UTF-8.
-      {{0xf0, 0x80, 0x80}, V_ASN1_UTF8STRING, nullptr},
-      // Low-surrogate value.
-      {{0xed, 0xa0, 0x80}, V_ASN1_UTF8STRING, nullptr},
-      // High-surrogate value.
-      {{0xed, 0xb0, 0x81}, V_ASN1_UTF8STRING, nullptr},
-      // Initial BOMs should be rejected from UCS-2 and UCS-4.
-      {{0xfe, 0xff, 0, 88}, V_ASN1_BMPSTRING, nullptr},
-      {{0, 0, 0xfe, 0xff, 0, 0, 0, 88}, V_ASN1_UNIVERSALSTRING, nullptr},
-      // Otherwise, BOMs should pass through.
-      {{0, 88, 0xfe, 0xff}, V_ASN1_BMPSTRING, "X\xef\xbb\xbf"},
-      {{0, 0, 0, 88, 0, 0, 0xfe, 0xff}, V_ASN1_UNIVERSALSTRING,
-       "X\xef\xbb\xbf"},
-      // The maximum code-point should pass though.
-      {{0, 16, 0xff, 0xfd}, V_ASN1_UNIVERSALSTRING, "\xf4\x8f\xbf\xbd"},
-      // Values outside the Unicode space should not.
-      {{0, 17, 0, 0}, V_ASN1_UNIVERSALSTRING, nullptr},
-      // Non-characters should be rejected.
-      {{0, 1, 0xff, 0xff}, V_ASN1_UNIVERSALSTRING, nullptr},
-      {{0, 1, 0xff, 0xfe}, V_ASN1_UNIVERSALSTRING, nullptr},
-      {{0, 0, 0xfd, 0xd5}, V_ASN1_UNIVERSALSTRING, nullptr},
-      // BMPString is UCS-2, not UTF-16, so surrogate pairs are invalid.
-      {{0xd8, 0, 0xdc, 1}, V_ASN1_BMPSTRING, nullptr},
-  };
-
-  for (size_t i = 0; i < OPENSSL_ARRAY_SIZE(kTests); i++) {
-    SCOPED_TRACE(i);
-    const auto& test = kTests[i];
-    ASN1_STRING s;
-    s.type = test.type;
-    s.data = const_cast<uint8_t*>(test.in.data());
-    s.length = test.in.size();
-
-    uint8_t *utf8;
-    const int utf8_len = ASN1_STRING_to_UTF8(&utf8, &s);
-    EXPECT_EQ(utf8_len < 0, test.expected == nullptr);
-    if (utf8_len >= 0) {
-      if (test.expected != nullptr) {
-        EXPECT_EQ(Bytes(test.expected), Bytes(utf8, utf8_len));
-      }
-      OPENSSL_free(utf8);
-    } else {
-      ERR_clear_error();
-    }
-  }
-}
-
 TEST(X509Test, NoBasicConstraintsCertSign) {
   bssl::UniquePtr<X509> root(CertFromPEM(kSANTypesRoot));
   bssl::UniquePtr<X509> intermediate(
diff --git a/include/openssl/asn1.h b/include/openssl/asn1.h
index fe2f29d..4d2368a 100644
--- a/include/openssl/asn1.h
+++ b/include/openssl/asn1.h
@@ -302,6 +302,13 @@
 // |OPENSSL_malloc|.
 OPENSSL_EXPORT void ASN1_STRING_set0(ASN1_STRING *str, void *data, int len);
 
+// ASN1_STRING_to_UTF8 converts |in| to UTF-8. On success, sets |*out| to a
+// newly-allocated buffer containing the resulting string and returns the length
+// of the string. The caller must call |OPENSSL_free| to release |*out| when
+// done. On error, it returns a negative number.
+OPENSSL_EXPORT int ASN1_STRING_to_UTF8(unsigned char **out,
+                                       const ASN1_STRING *in);
+
 // TODO(davidben): Expand and document function prototypes generated in macros.
 
 
@@ -987,8 +994,6 @@
                                            unsigned long flags);
 #endif
 
-OPENSSL_EXPORT int ASN1_STRING_to_UTF8(unsigned char **out, ASN1_STRING *in);
-
 OPENSSL_EXPORT void *ASN1_item_d2i_bio(const ASN1_ITEM *it, BIO *in, void *x);
 OPENSSL_EXPORT int ASN1_item_i2d_bio(const ASN1_ITEM *it, BIO *out, void *x);
 OPENSSL_EXPORT int ASN1_UTCTIME_print(BIO *fp, const ASN1_UTCTIME *a);