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));
+    }
+  }
+}
diff --git a/include/openssl/x509v3.h b/include/openssl/x509v3.h
index c0d1677..ca6e362 100644
--- a/include/openssl/x509v3.h
+++ b/include/openssl/x509v3.h
@@ -127,17 +127,6 @@
   void *usr_data;  // Any extension specific data
 };
 
-// Context specific info
-struct v3_ext_ctx {
-#define CTX_TEST 0x1
-  int flags;
-  const X509 *issuer_cert;
-  const X509 *subject_cert;
-  const X509_REQ *subject_req;
-  const X509_CRL *crl;
-  const CONF *db;
-};
-
 DEFINE_STACK_OF(X509V3_EXT_METHOD)
 
 // ext_flags values
@@ -348,9 +337,6 @@
 // onlysomereasons present
 #define IDP_REASONS 0x40
 
-#define X509V3_set_ctx_test(ctx) \
-  X509V3_set_ctx(ctx, NULL, NULL, NULL, NULL, CTX_TEST)
-#define X509V3_set_ctx_nodb(ctx) (ctx)->db = NULL;
 
 
 // X509_PURPOSE stuff
@@ -560,40 +546,142 @@
     const CONF_VALUE *cnf, int is_nc);
 OPENSSL_EXPORT void X509V3_conf_free(CONF_VALUE *val);
 
-// X509V3_EXT_conf_nid contains the only exposed instance of an LHASH in our
-// public headers. The |conf| pointer must be NULL but cryptography.io wraps
-// this function so we cannot, yet, replace the type with a dummy struct.
+
+// Deprecated config-based extension creation.
+//
+// The following functions allow specifying X.509 extensions using OpenSSL's
+// config file syntax, from the OpenSSL command-line tool. They are retained,
+// for now, for compatibility with legacy software but may be removed in the
+// future. Construct the extensions using the typed C APIs instead.
+//
+// Callers should especially avoid these functions if passing in non-constant
+// values. They use ad-hoc, string-based formats which are prone to injection
+// vulnerabilities. For a CA, this means using them risks misissuance.
+//
+// These functions are not safe to use with untrusted inputs. The string formats
+// may implicitly reference context information and, in OpenSSL (though not
+// BoringSSL), one even allows reading arbitrary files. They additionally see
+// much less testing and review than most of the library and may have bugs
+// including memory leaks or crashes.
+
+// v3_ext_ctx, aka |X509V3_CTX|, contains additional context information for
+// constructing extensions. Some string formats reference additional values in
+// these objects. It must be initialized with both |X509V3_set_ctx| and
+// |X509V3_set_nconf| before use.
+struct v3_ext_ctx {
+  int flags;
+  const X509 *issuer_cert;
+  const X509 *subject_cert;
+  const X509_REQ *subject_req;
+  const X509_CRL *crl;
+  const CONF *db;
+};
+
+// TODO(davidben): Rename this to |X509V3_CTX_TEST|.
+#define CTX_TEST 0x1
+
+// X509V3_set_ctx partially initializes |ctx| with the specified objects. Some
+// string formats will reference fields in these objects. Each object may be
+// NULL to omit it, in which case those formats cannot be used. |flags| should
+// be zero, unless called via |X509V3_set_ctx_test|.
+//
+// |issuer|, |subject|, |req|, and |crl|, if non-NULL, must outlive |ctx|.
+//
+// WARNING: This function only partially initializes |ctx|. Callers must also
+// call |X509V3_set_nconf| or |X509V3_set_ctx_nodb|.
+OPENSSL_EXPORT void X509V3_set_ctx(X509V3_CTX *ctx, const X509 *issuer,
+                                   const X509 *subject, const X509_REQ *req,
+                                   const X509_CRL *crl, int flags);
+
+// X509V3_set_ctx_test calls |X509V3_set_ctx| without any reference objects and
+// mocks out some features that use them. The resulting extensions may be
+// incomplete and should be discarded. This can be used to partially validate
+// syntax.
+//
+// WARNING: This function only partially initializes |ctx|. Callers must also
+// call |X509V3_set_nconf| or |X509V3_set_ctx_nodb|.
+//
+// TODO(davidben): Can we remove this?
+#define X509V3_set_ctx_test(ctx) \
+  X509V3_set_ctx(ctx, NULL, NULL, NULL, NULL, CTX_TEST)
+
+// X509V3_set_nconf partially initializes |ctx| with |conf| as the config
+// database. Some string formats will reference sections in |conf|. |conf| may
+// be NULL, in which case these formats cannot be used. If non-NULL, |conf| must
+// outlive |ctx|.
+//
+// WARNING: This function only partially initializes |ctx|. Callers must also
+// call |X509V3_set_ctx| or |X509V3_set_ctx_test|.
+//
+// TODO(davidben): All the public entrypoints take a |CONF| already. OpenSSL
+// does not document the relationship between |db| in this structure and the
+// parameter, but all callers either match them, or use NULL and forget to call
+// |X509V3_set_ctx_nodb|. The latter results in reading an uninitialized pointer
+// if an applicable format is ever accidentally used. Perhaps this should be
+// automatically initialized by |X509V3_EXT_nconf|, etc.
+OPENSSL_EXPORT void X509V3_set_nconf(X509V3_CTX *ctx, const CONF *conf);
+
+// X509V3_set_ctx_nodb calls |X509V3_set_nconf| with no config database.
+#define X509V3_set_ctx_nodb(ctx) X509V3_set_nconf(ctx, NULL)
+
+// X509V3_EXT_nconf constructs an extension of type specified by |name|, and
+// value specified by |value|. It returns a newly-allocated |X509_EXTENSION|
+// object on success, or NULL on error. |conf| and |ctx| specify additional
+// information referenced by some formats. |conf| may be NULL, in which case
+// features which use it will be disabled or crash.
+//
+// TODO(davidben): Fix the crashes. Also allow |ctx| to be NULL. One caller
+// seems to do it, even though it doesn't really work.
+OPENSSL_EXPORT X509_EXTENSION *X509V3_EXT_nconf(const CONF *conf,
+                                                const X509V3_CTX *ctx,
+                                                const char *name,
+                                                const char *value);
+
+// X509V3_EXT_nconf_nid behaves like |X509V3_EXT_nconf|, except the extension
+// type is specified as a NID.
+OPENSSL_EXPORT X509_EXTENSION *X509V3_EXT_nconf_nid(const CONF *conf,
+                                                    const X509V3_CTX *ctx,
+                                                    int ext_nid,
+                                                    const char *value);
+
+// X509V3_EXT_conf_nid calls |X509V3_EXT_nconf_nid|. |conf| must be NULL.
+//
+// TODO(davidben): This is the only exposed instance of an LHASH in our public
+// headers. cryptography.io wraps this function so we cannot, yet, replace the
+// type with a dummy struct.
 OPENSSL_EXPORT X509_EXTENSION *X509V3_EXT_conf_nid(LHASH_OF(CONF_VALUE) *conf,
                                                    const X509V3_CTX *ctx,
                                                    int ext_nid,
                                                    const char *value);
 
-OPENSSL_EXPORT X509_EXTENSION *X509V3_EXT_nconf_nid(const CONF *conf,
-                                                    const X509V3_CTX *ctx,
-                                                    int ext_nid,
-                                                    const char *value);
-OPENSSL_EXPORT X509_EXTENSION *X509V3_EXT_nconf(const CONF *conf,
-                                                const X509V3_CTX *ctx,
-                                                const char *name,
-                                                const char *value);
+// X509V3_EXT_add_nconf_sk looks up the section named |section| in |conf|. For
+// each |CONF_VALUE| in the section, it constructs an extension as in
+// |X509V3_EXT_nconf|, taking |name| and |value| from the |CONF_VALUE|. Each new
+// extension is appended to |*sk|. If |*sk| is non-NULL, and at least one
+// extension is added, it sets |*sk| to a newly-allocated
+// |STACK_OF(X509_EXTENSION)|. It returns one on success and zero on error.
 OPENSSL_EXPORT int X509V3_EXT_add_nconf_sk(const CONF *conf,
                                            const X509V3_CTX *ctx,
                                            const char *section,
                                            STACK_OF(X509_EXTENSION) **sk);
+
+// X509V3_EXT_add_nconf adds extensions to |cert| as in
+// |X509V3_EXT_add_nconf_sk|. It returns one on success and zero on error.
 OPENSSL_EXPORT int X509V3_EXT_add_nconf(const CONF *conf, const X509V3_CTX *ctx,
                                         const char *section, X509 *cert);
+
+// X509V3_EXT_REQ_add_nconf adds extensions to |req| as in
+// |X509V3_EXT_add_nconf_sk|. It returns one on success and zero on error.
 OPENSSL_EXPORT int X509V3_EXT_REQ_add_nconf(const CONF *conf,
                                             const X509V3_CTX *ctx,
                                             const char *section, X509_REQ *req);
+
+// X509V3_EXT_CRL_add_nconf adds extensions to |crl| as in
+// |X509V3_EXT_add_nconf_sk|. It returns one on success and zero on error.
 OPENSSL_EXPORT int X509V3_EXT_CRL_add_nconf(const CONF *conf,
                                             const X509V3_CTX *ctx,
                                             const char *section, X509_CRL *crl);
 
-OPENSSL_EXPORT void X509V3_set_nconf(X509V3_CTX *ctx, const CONF *conf);
-
-OPENSSL_EXPORT void X509V3_set_ctx(X509V3_CTX *ctx, const X509 *issuer,
-                                   const X509 *subject, const X509_REQ *req,
-                                   const X509_CRL *crl, int flags);
 
 OPENSSL_EXPORT char *i2s_ASN1_INTEGER(const X509V3_EXT_METHOD *meth,
                                       const ASN1_INTEGER *aint);