Deprecate, test, and document X.509 config APIs.

These APIs should not be used, but far too many callers use them. In the
meantime, at least test this behavior (so it can be rewritten) and write
down why it should not be used.

In doing so, I noticed that we actually broke some cases in the
ASN1_generate_v3 logic. I think it broke in
https://boringssl-review.googlesource.com/c/boringssl/+/48825. But since
no one's noticed, I've just kept it broken.

Bug: 430
Change-Id: I80ab1985964fc506c8aead579c769f15291b1384
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/56029
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Bob Beck <bbe@google.com>
diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc
index cc3cd00..11f9eff 100644
--- a/crypto/x509/x509_test.cc
+++ b/crypto/x509/x509_test.cc
@@ -22,6 +22,7 @@
 #include <openssl/asn1.h>
 #include <openssl/bio.h>
 #include <openssl/bytestring.h>
+#include <openssl/conf.h>
 #include <openssl/crypto.h>
 #include <openssl/curve25519.h>
 #include <openssl/digest.h>
@@ -5423,3 +5424,500 @@
                                 }));
   }
 }
+
+TEST(X509Test, ExtensionFromConf) {
+  static const char kTestOID[] = "1.2.840.113554.4.1.72585.2";
+  const struct {
+    const char *name;
+    const char *value;
+    const char *conf;
+    // expected is the resulting extension, encoded in DER, or the empty string
+    // if an error is expected.
+    std::vector<uint8_t> expected;
+  } kTests[] = {
+      // Many extensions have built-in syntax.
+      {"basicConstraints",
+       "critical,CA:true",
+       nullptr,
+       {0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05,
+        0x30, 0x03, 0x01, 0x01, 0xff}},
+
+      // Extension contents may be referenced from a config section.
+      {"basicConstraints",
+       "critical,@section",
+       "[section]\nCA = true\n",
+       {0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05,
+        0x30, 0x03, 0x01, 0x01, 0xff}},
+
+      // TODO(davidben): Enable this test. There is a missing NULL check, so it
+      // crashes right now.
+      // {"basicConstraints", "critical,@section", nullptr, {}},
+
+      // The "DER:" prefix just specifies an arbitrary byte string. Colons
+      // separators are ignored.
+      {kTestOID, "DER:0001020304", nullptr, {0x30, 0x15, 0x06, 0x0c, 0x2a, 0x86,
+                                             0x48, 0x86, 0xf7, 0x12, 0x04, 0x01,
+                                             0x84, 0xb7, 0x09, 0x02, 0x04, 0x05,
+                                             0x00, 0x01, 0x02, 0x03, 0x04}},
+      {kTestOID,
+       "DER:00:01:02:03:04",
+       nullptr,
+       {0x30, 0x15, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01,
+        0x84, 0xb7, 0x09, 0x02, 0x04, 0x05, 0x00, 0x01, 0x02, 0x03, 0x04}},
+      {kTestOID, "DER:invalid hex", nullptr, {}},
+
+      // The "ASN1:" prefix implements a complex language for describing ASN.1
+      // structures. See
+      // https://www.openssl.org/docs/man1.1.1/man3/ASN1_generate_nconf.html
+      {kTestOID, "ASN1:invalid", nullptr, {}},
+      {kTestOID,
+       "ASN1:BOOLEAN:TRUE",
+       nullptr,
+       {0x30, 0x13, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04,
+        0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x03, 0x01, 0x01, 0xff}},
+      {kTestOID, "ASN1:BOOL:yes", nullptr, {0x30, 0x13, 0x06, 0x0c, 0x2a, 0x86,
+                                            0x48, 0x86, 0xf7, 0x12, 0x04, 0x01,
+                                            0x84, 0xb7, 0x09, 0x02, 0x04, 0x03,
+                                            0x01, 0x01, 0xff}},
+      {kTestOID,
+       "ASN1:BOOLEAN:NO",
+       nullptr,
+       {0x30, 0x13, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04,
+        0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x03, 0x01, 0x01, 0x00}},
+      {kTestOID,
+       "ASN1:BOOLEAN",  // Missing value
+       nullptr,
+       {}},
+      {kTestOID, "ASN1:BOOLEAN:invalid", nullptr, {}},
+      {kTestOID, "ASN1:BOOLEAN:TRUE,invalid", nullptr, {}},
+
+      {kTestOID, "ASN1:NULL", nullptr, {0x30, 0x12, 0x06, 0x0c, 0x2a,
+                                        0x86, 0x48, 0x86, 0xf7, 0x12,
+                                        0x04, 0x01, 0x84, 0xb7, 0x09,
+                                        0x02, 0x04, 0x02, 0x05, 0x00}},
+      {kTestOID, "ASN1:NULL,invalid", nullptr, {}},
+      {kTestOID, "ASN1:NULL:invalid", nullptr, {}},
+
+      // Missing value.
+      {kTestOID, "ASN1:INTEGER", nullptr, {}},
+      {kTestOID, "ASN1:INTEGER:", nullptr, {}},
+      {kTestOID, "ASN1:INTEGER,invalid", nullptr, {}},
+
+      // INTEGER may be decimal or hexadecimal.
+      {kTestOID, "ASN1:INT:-0x10", nullptr, {0x30, 0x13, 0x06, 0x0c, 0x2a, 0x86,
+                                             0x48, 0x86, 0xf7, 0x12, 0x04, 0x01,
+                                             0x84, 0xb7, 0x09, 0x02, 0x04, 0x03,
+                                             0x02, 0x01, 0xf0}},
+      {kTestOID, "ASN1:INT:-10", nullptr, {0x30, 0x13, 0x06, 0x0c, 0x2a, 0x86,
+                                           0x48, 0x86, 0xf7, 0x12, 0x04, 0x01,
+                                           0x84, 0xb7, 0x09, 0x02, 0x04, 0x03,
+                                           0x02, 0x01, 0xf6}},
+      {kTestOID, "ASN1:INT:0", nullptr, {0x30, 0x13, 0x06, 0x0c, 0x2a, 0x86,
+                                         0x48, 0x86, 0xf7, 0x12, 0x04, 0x01,
+                                         0x84, 0xb7, 0x09, 0x02, 0x04, 0x03,
+                                         0x02, 0x01, 0x00}},
+      {kTestOID,
+       "ASN1:INTEGER:10",
+       nullptr,
+       {0x30, 0x13, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04,
+        0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x03, 0x02, 0x01, 0x0a}},
+      {kTestOID,
+       "ASN1:INTEGER:0x10",
+       nullptr,
+       {0x30, 0x13, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04,
+        0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x03, 0x02, 0x01, 0x10}},
+
+      {kTestOID, "ASN1:ENUM:0", nullptr, {0x30, 0x13, 0x06, 0x0c, 0x2a, 0x86,
+                                          0x48, 0x86, 0xf7, 0x12, 0x04, 0x01,
+                                          0x84, 0xb7, 0x09, 0x02, 0x04, 0x03,
+                                          0x0a, 0x01, 0x00}},
+      {kTestOID,
+       "ASN1:ENUMERATED:0",
+       nullptr,
+       {0x30, 0x13, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04,
+        0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x03, 0x0a, 0x01, 0x00}},
+
+      // OIDs may be spelled out or specified by name.
+      {kTestOID, "ASN1:OBJECT:invalid", nullptr, {}},
+      {kTestOID,
+       "ASN1:OBJECT:basicConstraints",
+       nullptr,
+       {0x30, 0x15, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01,
+        0x84, 0xb7, 0x09, 0x02, 0x04, 0x05, 0x06, 0x03, 0x55, 0x1d, 0x13}},
+      {kTestOID,
+       "ASN1:OBJECT:2.5.29.19",
+       nullptr,
+       {0x30, 0x15, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01,
+        0x84, 0xb7, 0x09, 0x02, 0x04, 0x05, 0x06, 0x03, 0x55, 0x1d, 0x13}},
+      {kTestOID,
+       "ASN1:OID:2.5.29.19",
+       nullptr,
+       {0x30, 0x15, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01,
+        0x84, 0xb7, 0x09, 0x02, 0x04, 0x05, 0x06, 0x03, 0x55, 0x1d, 0x13}},
+
+      {kTestOID, "ASN1:UTC:invalid", nullptr, {}},
+      {kTestOID, "ASN1:UTC:20001231235959Z", nullptr, {}},
+      {kTestOID, "ASN1:UTCTIME:invalid", nullptr, {}},
+      {kTestOID,
+       "ASN1:UTC:001231235959Z",
+       nullptr,
+       {0x30, 0x1f, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04,
+        0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x0f, 0x17, 0x0d, 0x30, 0x30,
+        0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a}},
+      {kTestOID,
+       "ASN1:UTCTIME:001231235959Z",
+       nullptr,
+       {0x30, 0x1f, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04,
+        0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x0f, 0x17, 0x0d, 0x30, 0x30,
+        0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a}},
+
+      {kTestOID, "ASN1:GENTIME:invalid", nullptr, {}},
+      {kTestOID, "ASN1:GENTIME:001231235959Z", nullptr, {}},
+      {kTestOID, "ASN1:GENERALIZEDTIME:invalid", nullptr, {}},
+      {kTestOID,
+       "ASN1:GENTIME:20001231235959Z",
+       nullptr,
+       {0x30, 0x21, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01,
+        0x84, 0xb7, 0x09, 0x02, 0x04, 0x11, 0x18, 0x0f, 0x32, 0x30, 0x30, 0x30,
+        0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a}},
+      {kTestOID,
+       "ASN1:GENERALIZEDTIME:20001231235959Z",
+       nullptr,
+       {0x30, 0x21, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01,
+        0x84, 0xb7, 0x09, 0x02, 0x04, 0x11, 0x18, 0x0f, 0x32, 0x30, 0x30, 0x30,
+        0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a}},
+
+      // The default input format for string types is ASCII, which is then
+      // converted into the target string type.
+      {kTestOID, "ASN1:UTF8:hello", nullptr, {0x30, 0x17, 0x06, 0x0c, 0x2a,
+                                              0x86, 0x48, 0x86, 0xf7, 0x12,
+                                              0x04, 0x01, 0x84, 0xb7, 0x09,
+                                              0x02, 0x04, 0x07, 0x0c, 0x05,
+                                              0x68, 0x65, 0x6c, 0x6c, 0x6f}},
+      {kTestOID,
+       "ASN1:UTF8String:hello",
+       nullptr,
+       {0x30, 0x17, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+        0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x07,
+        0x0c, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f}},
+      {kTestOID,
+       "ASN1:UNIV:hello",
+       nullptr,
+       {0x30, 0x26, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12,
+        0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x16, 0x1c, 0x14,
+        0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00,
+        0x00, 0x6c, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x6f}},
+      {kTestOID,
+       "ASN1:UNIVERSALSTRING:hello",
+       nullptr,
+       {0x30, 0x26, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12,
+        0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x16, 0x1c, 0x14,
+        0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00,
+        0x00, 0x6c, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x6f}},
+      {kTestOID,
+       "ASN1:BMP:hello",
+       nullptr,
+       {0x30, 0x1c, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12,
+        0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x0c, 0x1e, 0x0a,
+        0x00, 0x68, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f}},
+      {kTestOID,
+       "ASN1:BMPSTRING:hello",
+       nullptr,
+       {0x30, 0x1c, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12,
+        0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x0c, 0x1e, 0x0a,
+        0x00, 0x68, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f}},
+      {kTestOID, "ASN1:IA5:hello", nullptr, {0x30, 0x17, 0x06, 0x0c, 0x2a,
+                                             0x86, 0x48, 0x86, 0xf7, 0x12,
+                                             0x04, 0x01, 0x84, 0xb7, 0x09,
+                                             0x02, 0x04, 0x07, 0x16, 0x05,
+                                             0x68, 0x65, 0x6c, 0x6c, 0x6f}},
+      {kTestOID,
+       "ASN1:IA5STRING:hello",
+       nullptr,
+       {0x30, 0x17, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+        0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x07,
+        0x16, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f}},
+      {kTestOID,
+       "ASN1:PRINTABLE:hello",
+       nullptr,
+       {0x30, 0x17, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+        0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x07,
+        0x13, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f}},
+      {kTestOID,
+       "ASN1:PRINTABLESTRING:hello",
+       nullptr,
+       {0x30, 0x17, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+        0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x07,
+        0x13, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f}},
+      {kTestOID, "ASN1:T61:hello", nullptr, {0x30, 0x17, 0x06, 0x0c, 0x2a,
+                                             0x86, 0x48, 0x86, 0xf7, 0x12,
+                                             0x04, 0x01, 0x84, 0xb7, 0x09,
+                                             0x02, 0x04, 0x07, 0x14, 0x05,
+                                             0x68, 0x65, 0x6c, 0x6c, 0x6f}},
+      {kTestOID,
+       "ASN1:T61STRING:hello",
+       nullptr,
+       {0x30, 0x17, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+        0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x07,
+        0x14, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f}},
+      {kTestOID,
+       "ASN1:TELETEXSTRING:hello",
+       nullptr,
+       {0x30, 0x17, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+        0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x07,
+        0x14, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f}},
+
+      // FORMAT:UTF8 switches the input format to UTF-8. This should be
+      // converted to the destination string, or rejected if invalid.
+      {kTestOID,
+       "ASN1:FORMAT:UTF8,UTF8:\xe2\x98\x83",
+       nullptr,
+       {0x30, 0x15, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01,
+        0x84, 0xb7, 0x09, 0x02, 0x04, 0x05, 0x0c, 0x03, 0xe2, 0x98, 0x83}},
+      {kTestOID,
+       "ASN1:FORMAT:UTF8,UNIV:\xe2\x98\x83",
+       nullptr,
+       {0x30, 0x16, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86,
+        0xf7, 0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02,
+        0x04, 0x06, 0x1c, 0x04, 0x00, 0x00, 0x26, 0x03}},
+      {kTestOID,
+       "ASN1:FORMAT:UTF8,BMP:\xe2\x98\x83",
+       nullptr,
+       {0x30, 0x14, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04,
+        0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x04, 0x1e, 0x02, 0x26, 0x03}},
+      {kTestOID, "ASN1:FORMAT:UTF8,IA5:\xe2\x98\x83", nullptr, {}},
+      {kTestOID,
+       "ASN1:FORMAT:UTF8,IA5:hello",
+       nullptr,
+       {0x30, 0x17, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+        0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x07,
+        0x16, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f}},
+      {kTestOID, "ASN1:FORMAT:UTF8,PRINTABLE:\xe2\x98\x83", nullptr, {}},
+      {kTestOID,
+       "ASN1:FORMAT:UTF8,PRINTABLE:hello",
+       nullptr,
+       {0x30, 0x17, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+        0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x07,
+        0x13, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f}},
+      {kTestOID, "ASN1:FORMAT:UTF8,T61:\xe2\x98\x83", nullptr, {}},
+      {kTestOID,
+       "ASN1:FORMAT:UTF8,T61:\xc3\xb7",
+       nullptr,
+       {0x30, 0x13, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04,
+        0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x03, 0x14, 0x01, 0xf7}},
+
+      // Invalid UTF-8.
+      {kTestOID, "ASN1:FORMAT:UTF8,UTF8:\xff", nullptr, {}},
+
+      // We don't support these string types.
+      // TODO(davidben): Remove them from the implementation, as they don't do
+      // anything.
+      {kTestOID, "ASN1:NUMERIC:0", nullptr, {}},
+      {kTestOID, "ASN1:NUMERICSTRING:0", nullptr, {}},
+      {kTestOID, "ASN1:VISIBLE:hello", nullptr, {}},
+      {kTestOID, "ASN1:VISIBLESTRING:hello", nullptr, {}},
+      {kTestOID, "ASN1:GeneralString:hello", nullptr, {}},
+
+      // OCTET STRING and BIT STRING also default to ASCII, but also accept HEX.
+      // BIT STRING interprets OCTET STRING formats by having zero unused bits.
+      {kTestOID, "ASN1:OCT:hello", nullptr, {0x30, 0x17, 0x06, 0x0c, 0x2a,
+                                             0x86, 0x48, 0x86, 0xf7, 0x12,
+                                             0x04, 0x01, 0x84, 0xb7, 0x09,
+                                             0x02, 0x04, 0x07, 0x04, 0x05,
+                                             0x68, 0x65, 0x6c, 0x6c, 0x6f}},
+      {kTestOID,
+       "ASN1:OCTETSTRING:hello",
+       nullptr,
+       {0x30, 0x17, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+        0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x07,
+        0x04, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f}},
+      {kTestOID,
+       "ASN1:FORMAT:HEX,OCT:0123abcd",
+       nullptr,
+       {0x30, 0x16, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86,
+        0xf7, 0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02,
+        0x04, 0x06, 0x04, 0x04, 0x01, 0x23, 0xab, 0xcd}},
+      {kTestOID,
+       "ASN1:BITSTR:hello",
+       nullptr,
+       {0x30, 0x18, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+        0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x08,
+        0x03, 0x06, 0x00, 0x68, 0x65, 0x6c, 0x6c, 0x6f}},
+      {kTestOID,
+       "ASN1:BITSTRING:hello",
+       nullptr,
+       {0x30, 0x18, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+        0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x08,
+        0x03, 0x06, 0x00, 0x68, 0x65, 0x6c, 0x6c, 0x6f}},
+      {kTestOID,
+       "ASN1:FORMAT:HEX,BITSTR:0123abcd",
+       nullptr,
+       {0x30, 0x17, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+        0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x07,
+        0x03, 0x05, 0x00, 0x01, 0x23, 0xab, 0xcd}},
+
+      {kTestOID, "ASN1:FORMAT:HEX,OCT:invalid hex", nullptr, {}},
+
+      // BIT STRING additionally supports a BITLIST type, which specifies a
+      // list of bits to set.
+      {kTestOID,
+       "ASN1:FORMAT:BITLIST,BITSTR:1,5",
+       nullptr,
+       {0x30, 0x14, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04,
+        0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x04, 0x03, 0x02, 0x02, 0x44}},
+
+      {kTestOID, "ASN1:FORMAT:BITLIST,BITSTR:1,invalid,5", nullptr, {}},
+      // TODO(davidben): Handle overflow and enable this test.
+      // {kTestOID, "ASN1:FORMAT:BITLIST,BITSTR:4294967296", nullptr, {}},
+
+      // Unsupported formats for string types.
+      {kTestOID, "ASN1:FORMAT:BITLIST,IA5:abcd", nullptr, {}},
+      {kTestOID, "ASN1:FORMAT:BITLIST,UTF8:abcd", nullptr, {}},
+      {kTestOID, "ASN1:FORMAT:BITLIST,OCT:abcd", nullptr, {}},
+      {kTestOID, "ASN1:FORMAT:BITLIST,UTC:abcd", nullptr, {}},
+      {kTestOID, "ASN1:FORMAT:HEX,IA5:abcd", nullptr, {}},
+      {kTestOID, "ASN1:FORMAT:HEX,UTF8:abcd", nullptr, {}},
+      {kTestOID, "ASN1:FORMAT:HEX,UTC:abcd", nullptr, {}},
+      {kTestOID, "ASN1:FORMAT:UTF8,OCT:abcd", nullptr, {}},
+      {kTestOID, "ASN1:FORMAT:UTF8,UTC:abcd", nullptr, {}},
+
+      // Invalid format type.
+      {kTestOID, "ASN1:FORMAT:invalid,IA5:abcd", nullptr, {}},
+
+      // SEQUENCE and SET encode empty values when there is no value.
+      {kTestOID, "ASN1:SEQ", nullptr, {0x30, 0x12, 0x06, 0x0c, 0x2a, 0x86, 0x48,
+                                       0x86, 0xf7, 0x12, 0x04, 0x01, 0x84, 0xb7,
+                                       0x09, 0x02, 0x04, 0x02, 0x30, 0x00}},
+      {kTestOID, "ASN1:SET", nullptr, {0x30, 0x12, 0x06, 0x0c, 0x2a, 0x86, 0x48,
+                                       0x86, 0xf7, 0x12, 0x04, 0x01, 0x84, 0xb7,
+                                       0x09, 0x02, 0x04, 0x02, 0x31, 0x00}},
+      {kTestOID, "ASN1:SEQUENCE", nullptr, {0x30, 0x12, 0x06, 0x0c, 0x2a,
+                                            0x86, 0x48, 0x86, 0xf7, 0x12,
+                                            0x04, 0x01, 0x84, 0xb7, 0x09,
+                                            0x02, 0x04, 0x02, 0x30, 0x00}},
+
+      // Otherwise, they require a corresponding section in the config database
+      // to encode values. This can be nested recursively.
+      {kTestOID, "ASN1:SEQ:missing_confdb", nullptr, {}},
+      {kTestOID, "ASN1:SET:missing_confdb", nullptr, {}},
+      {kTestOID,
+       "ASN1:SEQ:seq",
+       R"(
+[seq]
+val1 = NULL
+val2 = IA5:a
+val3 = SET:set
+[set]
+# Config names do not matter, only the order.
+val4 = INT:1
+val3 = INT:2
+val2 = SEQ:empty
+val1 = INT:3
+[empty]
+)",
+       {0x30, 0x24, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12,
+        0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x14, 0x30, 0x12,
+        0x05, 0x00, 0x16, 0x01, 0x61, 0x31, 0x0b, 0x02, 0x01, 0x01,
+        0x02, 0x01, 0x02, 0x02, 0x01, 0x03, 0x30, 0x00}},
+
+      // There is a recursion limit to stop infinite recursion.
+      {kTestOID,
+       "ASN1:SEQ:seq1",
+       R"(
+[seq1]
+val = SEQ:seq2
+[seq2]
+val = SEQ:seq1
+)",
+       {}},
+
+      // Various modifiers wrap with explicit tagging or universal types.
+      {kTestOID,
+       "ASN1:EXP:0,EXP:16U,EXP:100A,EXP:1000C,OCTWRAP,SEQWRAP,SETWRAP,BITWRAP,"
+       "NULL",
+       nullptr,
+       {0x30, 0x26, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12,
+        0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x16, 0xa0, 0x14,
+        0x30, 0x12, 0x7f, 0x64, 0x0f, 0xbf, 0x87, 0x68, 0x0b, 0x04,
+        0x09, 0x30, 0x07, 0x31, 0x05, 0x03, 0x03, 0x00, 0x05, 0x00}},
+
+      // Implicit tagging may also be applied to the underlying type, or the
+      // wrapping modifiers.
+      {kTestOID,
+       "ASN1:IMP:1A,OCTWRAP,IMP:10,SEQWRAP,IMP:100,SETWRAP,IMP:1000,BITWRAP,"
+       "IMP:10000,NULL",
+       nullptr,
+       {0x30, 0x20, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01,
+        0x84, 0xb7, 0x09, 0x02, 0x04, 0x10, 0x41, 0x0e, 0xaa, 0x0c, 0xbf, 0x64,
+        0x09, 0x9f, 0x87, 0x68, 0x05, 0x00, 0x9f, 0xce, 0x10, 0x00}},
+
+      // Implicit tagging may not be applied to explicit tagging or itself.
+      // There's no rule against this in ASN.1, but OpenSSL does not allow it
+      // here.
+      {kTestOID, "ASN1:IMP:1,EXP:1,NULL", nullptr, {}},
+      {kTestOID, "ASN1:IMP:1,IMP:1,NULL", nullptr, {}},
+
+      // Leading and trailing spaces on name:value pairs are removed. However,
+      // while these pairs are delimited by commas, a type will consumes
+      // everything after it, including commas, and spaces. So this is the
+      // string " a, b ".
+      {kTestOID,
+       "ASN1: EXP:0 , IA5: a, b ",
+       nullptr,
+       {0x30, 0x1a, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12,
+        0x04, 0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x0a, 0xa0, 0x08,
+        0x16, 0x06, 0x20, 0x61, 0x2c, 0x20, 0x62, 0x20}},
+
+      // Modifiers without a final type.
+      {kTestOID, "ASN1:EXP:1", nullptr, {}},
+
+      // Put it all together to describe a test Ed25519 key (wrapped inside an
+      // X.509 extension).
+      {kTestOID,
+       "ASN1:SEQUENCE:pkcs8",
+       R"(
+[pkcs8]
+vers = INT:0
+alg = SEQWRAP,OID:1.3.101.112
+key = FORMAT:HEX,OCTWRAP,OCT:9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60
+)",
+       {0x30, 0x40, 0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x12, 0x04,
+        0x01, 0x84, 0xb7, 0x09, 0x02, 0x04, 0x30, 0x30, 0x2e, 0x02, 0x01,
+        0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04,
+        0x20, 0x9d, 0x61, 0xb1, 0x9d, 0xef, 0xfd, 0x5a, 0x60, 0xba, 0x84,
+        0x4a, 0xf4, 0x92, 0xec, 0x2c, 0xc4, 0x44, 0x49, 0xc5, 0x69, 0x7b,
+        0x32, 0x69, 0x19, 0x70, 0x3b, 0xac, 0x03, 0x1c, 0xae, 0x7f, 0x60}},
+  };
+  for (const auto &t : kTests) {
+    SCOPED_TRACE(t.name);
+    SCOPED_TRACE(t.value);
+    SCOPED_TRACE(t.conf);
+
+    bssl::UniquePtr<CONF> conf;
+    if (t.conf != nullptr) {
+      conf.reset(NCONF_new(nullptr));
+      ASSERT_TRUE(conf);
+      bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(t.conf, strlen(t.conf)));
+      ASSERT_TRUE(bio);
+      long error_line;
+      ASSERT_TRUE(NCONF_load_bio(conf.get(), bio.get(), &error_line))
+          << "Failed to load config at line " << error_line;
+    }
+
+    X509V3_CTX ctx;
+    X509V3_set_ctx(&ctx, nullptr, nullptr, nullptr, nullptr, 0);
+    X509V3_set_nconf(&ctx, conf.get());
+    bssl::UniquePtr<X509_EXTENSION> ext(
+        X509V3_EXT_nconf(conf.get(), &ctx, t.name, t.value));
+    if (t.expected.empty()) {
+      EXPECT_FALSE(ext);
+    } else {
+      ASSERT_TRUE(ext);
+      uint8_t *der = nullptr;
+      int len = i2d_X509_EXTENSION(ext.get(), &der);
+      ASSERT_GE(len, 0);
+      bssl::UniquePtr<uint8_t> free_der(der);
+      EXPECT_EQ(Bytes(t.expected), Bytes(der, len));
+    }
+  }
+}