Support ANY attribute values in X509_NAME

An attribute in an X.509 name is defined as

   AttributeTypeAndValue ::= SEQUENCE {
     type     AttributeType,
     value    AttributeValue }

   AttributeType ::= OBJECT IDENTIFIER

   AttributeValue ::= ANY -- DEFINED BY AttributeType

However, OpenSSL only supported an ad-hoc subset of types. (See
crbug.com/42290275 for details). Fix this by doing two things:

1. Stop trying to use the MSTRING framework for AttributeValue as many
   of these are decidedly not strings.

2. Reuse ASN1_TYPE's V_ASN1_OTHER representation for non-universal tags.

3. For NULL, OBJECT, and BOOLEAN, use V_ASN1_OTHER because the normal
   representation is not an ASN1_STRING.

This is all wrapped up in a new, internal-only ASN1_ANY_AS_STRING type.
With this, MSTRING is no longer exposed to random unknown tags and we
can remove some default cases in our big switch/cases.

Update-Note: X.509 name attributes may now be any ASN.1 type, matching
the spec.

Additionally, a few obscure, unimplemented ASN.1 types changed
representation: ObjectDescriptor, EXTERNAL, REAL, EMBEDDED PDV,
RELATIVE-OID, and TIME (no connection to time types used in X.509) used
to be represented as ASN1_STRINGs with the type fields set to the
corresponding tag number, and their contents uninterpreted. Now they're
wrapped in V_ASN1_OTHER, consistent with ASN1_TYPE. This is not expected
to impact anyone.

Bug: 42290275
Change-Id: I54b0d72fcbfaeebae416aa96cdf74abdba8b4c88
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/78329
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 d2df1fb..f2604ac 100644
--- a/crypto/asn1/asn1_test.cc
+++ b/crypto/asn1/asn1_test.cc
@@ -106,22 +106,6 @@
                                   obj->value.asn1_string->length));
   TestSerialize(obj.get(), i2d_ASN1_TYPE, kTag128);
 
-  // The historical in-memory representation of |kTag128| was for both
-  // |obj->type| and |obj->value.asn1_string->type| to be 128. This is no
-  // longer used but is still accepted by the encoder.
-  //
-  // TODO(crbug.com/42290275): The encoder should reject it. However, it is
-  // still needed to support some edge cases in |ASN1_ANY_AS_STRING|. When that
-  // is fixed, test that we reject it.
-  obj.reset(ASN1_TYPE_new());
-  ASSERT_TRUE(obj);
-  obj->type = 128;
-  obj->value.asn1_string = ASN1_STRING_type_new(128);
-  ASSERT_TRUE(obj->value.asn1_string);
-  const uint8_t zero = 0;
-  ASSERT_TRUE(ASN1_STRING_set(obj->value.asn1_string, &zero, sizeof(zero)));
-  TestSerialize(obj.get(), i2d_ASN1_TYPE, kTag128);
-
   // If a tag is known, but has the wrong constructed bit, it should be
   // rejected, not placed in |V_ASN1_OTHER|.
   static const uint8_t kConstructedOctetString[] = {0x24, 0x00};
@@ -1997,6 +1981,16 @@
   ASSERT_TRUE(obj);
   EXPECT_EQ(-1, obj->type);
   EXPECT_EQ(-1, i2d_ASN1_TYPE(obj.get(), nullptr));
+
+  // The historical in-memory representation of [UNIVERSAL 128] was for both
+  // |obj->type| and |obj->value.asn1_string->type| to be 128. This is no longer
+  // used and should be rejected by the encoder.
+  obj.reset(ASN1_TYPE_new());
+  ASSERT_TRUE(obj);
+  obj->type = 128;
+  obj->value.asn1_string = ASN1_STRING_type_new(128);
+  ASSERT_TRUE(obj->value.asn1_string);
+  EXPECT_EQ(-1, i2d_ASN1_TYPE(obj.get(), nullptr));
 }
 
 // Encoding invalid MSTRING types should fail. An MSTRING is a CHOICE of
diff --git a/crypto/asn1/internal.h b/crypto/asn1/internal.h
index 5732c02..8d863fc 100644
--- a/crypto/asn1/internal.h
+++ b/crypto/asn1/internal.h
@@ -214,17 +214,9 @@
   ASN1_ex_i2d *asn1_ex_i2d;
 } ASN1_EXTERN_FUNCS;
 
-// ASN1_ANY_AS_STRING is an MSTRING that supports an arbitrary subset of types
-// representable in ASN1_STRING, used by X.509 name attribute values.
-//
-// TODO(crbug.com/42290275): This should support all types and also encode
-// non-ASN1_STRING values in a V_ASN1_OTHER structure.
-#define B_ASN1_ANY_AS_STRING                                          \
-  (B_ASN1_NUMERICSTRING | B_ASN1_PRINTABLESTRING | B_ASN1_T61STRING | \
-   B_ASN1_IA5STRING | B_ASN1_BIT_STRING | B_ASN1_UNIVERSALSTRING |    \
-   B_ASN1_BMPSTRING | B_ASN1_UTF8STRING | B_ASN1_SEQUENCE |           \
-   B_ASN1_OCTET_STRING | B_ASN1_UNKNOWN)
-
+// ASN1_ANY_AS_STRING is an |ASN1_ITEM| with ASN.1 type ANY and C type
+// |ASN1_STRING*|. Types which are not represented with |ASN1_STRING|, such as
+// |ASN1_OBJECT|, are represented with type |V_ASN1_OTHER|.
 DECLARE_ASN1_ITEM(ASN1_ANY_AS_STRING)
 
 
diff --git a/crypto/asn1/tasn_dec.cc b/crypto/asn1/tasn_dec.cc
index 6267120..13dc114 100644
--- a/crypto/asn1/tasn_dec.cc
+++ b/crypto/asn1/tasn_dec.cc
@@ -629,6 +629,7 @@
     return 0;  // Should never happen
   }
 
+  assert(it->itype == ASN1_ITYPE_PRIMITIVE || it->itype == ASN1_ITYPE_MSTRING);
   if (it->itype == ASN1_ITYPE_MSTRING) {
     utype = tag;
     tag = -1;
@@ -636,7 +637,7 @@
     utype = it->utype;
   }
 
-  if (utype == V_ASN1_ANY) {
+  if (utype == V_ASN1_ANY || utype == V_ASN1_ANY_AS_STRING) {
     // If type is ANY need to figure out type from tag
     unsigned char oclass;
     if (tag >= 0) {
@@ -647,8 +648,9 @@
       OPENSSL_PUT_ERROR(ASN1, ASN1_R_ILLEGAL_OPTIONAL_ANY);
       return 0;
     }
+    const int is_string = utype == V_ASN1_ANY_AS_STRING;
     p = *in;
-    ret = asn1_check_tlen(NULL, &utype, &oclass, NULL, &p, inlen, -1, 0, 0);
+    ret = asn1_check_tlen(&plen, &utype, &oclass, &cst, &p, inlen, -1, 0, 0);
     if (!ret) {
       OPENSSL_PUT_ERROR(ASN1, ASN1_R_NESTED_ASN1_ERROR);
       return 0;
@@ -656,6 +658,37 @@
     if (!is_supported_universal_type(utype, oclass)) {
       utype = V_ASN1_OTHER;
     }
+    // These three types are not represented as |ASN1_STRING|, so they must be
+    // parsed separately and then treated as an opaque |V_ASN1_OTHER|.
+    if (is_string && (utype == V_ASN1_OBJECT || utype == V_ASN1_NULL ||
+                      utype == V_ASN1_BOOLEAN)) {
+      if (cst) {
+        OPENSSL_PUT_ERROR(ASN1, ASN1_R_TYPE_NOT_PRIMITIVE);
+        return 0;
+      }
+      CBS cbs;
+      CBS_init(&cbs, p, plen);
+      if (utype == V_ASN1_OBJECT && !CBS_is_valid_asn1_oid(&cbs)) {
+        OPENSSL_PUT_ERROR(ASN1, ASN1_R_INVALID_OBJECT_ENCODING);
+        return 0;
+      }
+      if (utype == V_ASN1_NULL && CBS_len(&cbs) != 0) {
+        OPENSSL_PUT_ERROR(ASN1, ASN1_R_NULL_IS_WRONG_LENGTH);
+        return 0;
+      }
+      if (utype == V_ASN1_BOOLEAN) {
+        if (CBS_len(&cbs) != 1) {
+          OPENSSL_PUT_ERROR(ASN1, ASN1_R_BOOLEAN_IS_WRONG_LENGTH);
+          return 0;
+        }
+        uint8_t v = CBS_data(&cbs)[0];
+        if (v != 0 && v != 0xff) {
+          OPENSSL_PUT_ERROR(ASN1, ASN1_R_DECODE_ERROR);
+          return 0;
+        }
+      }
+      utype = V_ASN1_OTHER;
+    }
   }
   if (tag == -1) {
     tag = utype;
@@ -738,6 +771,10 @@
     opval = pval;
     pval = &typ->value.asn1_value;
   }
+
+  // If implementing a type that is not represented in |ASN1_STRING|, the
+  // |V_ASN1_ANY_AS_STRING| logic must be modified to redirect it to
+  // |V_ASN1_OTHER|.
   switch (utype) {
     case V_ASN1_OBJECT:
       if (!c2i_ASN1_OBJECT((ASN1_OBJECT **)pval, &cont, len)) {
@@ -796,14 +833,7 @@
     case V_ASN1_UTF8STRING:
     case V_ASN1_OTHER:
     case V_ASN1_SET:
-    case V_ASN1_SEQUENCE:
-    // TODO(crbug.com/42290275): This default case should be removed, now
-    // that we've resolved https://crbug.com/42290430. However, it is still
-    // needed to support some edge cases in |ASN1_ANY_AS_STRING|. It broadly
-    // doesn't tolerate unrecognized universal tags, except for eight values
-    // that map to |B_ASN1_UNKNOWN| instead of zero. See the
-    // X509Test.NameAttributeValues test.
-    default: {
+    case V_ASN1_SEQUENCE: {
       CBS cbs;
       CBS_init(&cbs, cont, (size_t)len);
       if (utype == V_ASN1_BMPSTRING) {
@@ -866,6 +896,10 @@
       }
       break;
     }
+
+    default:
+      OPENSSL_PUT_ERROR(ASN1, ASN1_R_BAD_TEMPLATE);
+      goto err;
   }
   // If ASN1_ANY and NULL type fix up value
   if (typ && (utype == V_ASN1_NULL)) {
diff --git a/crypto/asn1/tasn_enc.cc b/crypto/asn1/tasn_enc.cc
index 8a5bae6..9aa2039 100644
--- a/crypto/asn1/tasn_enc.cc
+++ b/crypto/asn1/tasn_enc.cc
@@ -527,21 +527,23 @@
   unsigned char c;
   int len;
 
+  assert(it->itype == ASN1_ITYPE_PRIMITIVE || it->itype == ASN1_ITYPE_MSTRING);
   // Historically, |it->funcs| for primitive types contained an
   // |ASN1_PRIMITIVE_FUNCS| table of callbacks.
   assert(it->funcs == NULL);
 
   *out_omit = 0;
 
-  // Should type be omitted?
-  if ((it->itype != ASN1_ITYPE_PRIMITIVE) || (it->utype != V_ASN1_BOOLEAN)) {
+  // Handle omitted optional values for all but BOOLEAN, which uses a
+  // non-pointer representation.
+  if (it->itype != ASN1_ITYPE_PRIMITIVE || it->utype != V_ASN1_BOOLEAN) {
     if (!*pval) {
       *out_omit = 1;
       return 0;
     }
   }
 
-  if (it->itype == ASN1_ITYPE_MSTRING) {
+  if (it->itype == ASN1_ITYPE_MSTRING || it->utype == V_ASN1_ANY_AS_STRING) {
     // If MSTRING type set the underlying type
     strtmp = (ASN1_STRING *)*pval;
     utype = strtmp->type;
@@ -550,15 +552,8 @@
       OPENSSL_PUT_ERROR(ASN1, ASN1_R_WRONG_TYPE);
       return -1;
     }
-    // Negative INTEGER and ENUMERATED values use |ASN1_STRING| type values
-    // that do not match their corresponding utype values. INTEGERs cannot
-    // participate in MSTRING types, but ENUMERATEDs can.
-    //
-    // TODO(davidben): Is this a bug? Although arguably one of the MSTRING
-    // types should contain more values, rather than less. See
-    // https://crbug.com/42290275. But it is not possible to fit all possible
-    // ANY values into an |ASN1_STRING|, so matching the spec here is somewhat
-    // hopeless.
+    // Negative INTEGER and ENUMERATED values use |ASN1_STRING| type values that
+    // do not match their corresponding utype values.
     if (utype == V_ASN1_NEG_INTEGER) {
       utype = V_ASN1_INTEGER;
     } else if (utype == V_ASN1_NEG_ENUMERATED) {
@@ -649,18 +644,15 @@
     case V_ASN1_SET:
     // This is not a valid |ASN1_ITEM| type, but it appears in |ASN1_TYPE|.
     case V_ASN1_OTHER:
-    // TODO(crbug.com/42290275): This default case should be removed, now that
-    // we've resolved https://crbug.com/42290430. However, it is still needed to
-    // support some edge cases in |ASN1_ANY_AS_STRING|. It broadly doesn't
-    // tolerate unrecognized universal tags, except for eight values that map to
-    // |B_ASN1_UNKNOWN| instead of zero. See the X509Test.NameAttributeValues
-    // test.
-    default:
       // All based on ASN1_STRING and handled the same
       strtmp = (ASN1_STRING *)*pval;
       cont = strtmp->data;
       len = strtmp->length;
       break;
+
+    default:
+      OPENSSL_PUT_ERROR(ASN1, ASN1_R_BAD_TEMPLATE);
+      return -1;
   }
   if (cout && len) {
     OPENSSL_memcpy(cout, cont, len);
diff --git a/crypto/asn1/tasn_typ.cc b/crypto/asn1/tasn_typ.cc
index 48b0f01..1d11ae8 100644
--- a/crypto/asn1/tasn_typ.cc
+++ b/crypto/asn1/tasn_typ.cc
@@ -46,6 +46,7 @@
 IMPLEMENT_ASN1_TYPE(ASN1_OBJECT)
 
 IMPLEMENT_ASN1_TYPE(ASN1_ANY)
+IMPLEMENT_ASN1_TYPE(ASN1_ANY_AS_STRING)
 
 // Just swallow an ASN1_SEQUENCE in an ASN1_STRING
 IMPLEMENT_ASN1_TYPE(ASN1_SEQUENCE)
@@ -59,8 +60,6 @@
 IMPLEMENT_ASN1_FUNCTIONS_const_fname(ASN1_STRING, DIRECTORYSTRING,
                                      DIRECTORYSTRING)
 
-IMPLEMENT_ASN1_MSTRING(ASN1_ANY_AS_STRING, B_ASN1_ANY_AS_STRING)
-
 // Three separate BOOLEAN type: normal, DEFAULT TRUE and DEFAULT FALSE
 IMPLEMENT_ASN1_TYPE_ex(ASN1_BOOLEAN, ASN1_BOOLEAN, ASN1_BOOLEAN_NONE)
 IMPLEMENT_ASN1_TYPE_ex(ASN1_TBOOLEAN, ASN1_BOOLEAN, ASN1_BOOLEAN_TRUE)
diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc
index 3b7a57d..4bed472 100644
--- a/crypto/x509/x509_test.cc
+++ b/crypto/x509/x509_test.cc
@@ -7166,35 +7166,51 @@
       {CBS_ASN1_BMPSTRING, std::string("\0a", 2), V_ASN1_BMPSTRING,
        std::string("\0a", 2)},
       {CBS_ASN1_OCTETSTRING, "abc", V_ASN1_OCTET_STRING, "abc"},
+      {CBS_ASN1_UTCTIME, "700101000000Z", V_ASN1_UTCTIME, "700101000000Z"},
+      {CBS_ASN1_GENERALIZEDTIME, "19700101000000Z", V_ASN1_GENERALIZEDTIME,
+       "19700101000000Z"},
 
       // ENUMERATED is supported but, currently, INTEGER is not.
+      {CBS_ASN1_INTEGER, "\x01", V_ASN1_INTEGER, "\x01"},
       {CBS_ASN1_ENUMERATED, "\x01", V_ASN1_ENUMERATED, "\x01"},
 
       // Test negative values. These are interesting because, when encoding, the
       // ASN.1 type must be determined from the string type, but the string type
       // has an extra |V_ASN1_NEG| bit.
+      {CBS_ASN1_INTEGER, "\xff", V_ASN1_NEG_INTEGER, "\x01"},
       {CBS_ASN1_ENUMERATED, "\xff", V_ASN1_NEG_ENUMERATED, "\x01"},
 
-      // SEQUENCE is supported but, currently, SET is not. Note the
-      // |ASN1_STRING| representation will include the tag and length.
+      // SEQUENCE and SET use their |ASN1_STRING| representation, which includes
+      // the tag and length.
       {CBS_ASN1_SEQUENCE, "", V_ASN1_SEQUENCE, std::string("\x30\x00", 2)},
+      {CBS_ASN1_SET, "", V_ASN1_SET, std::string("\x31\x00", 2)},
 
-      // These types are not actually supported by the library but,
-      // historically, we would parse them, and not other unsupported types, due
-      // to quirks of |ASN1_tag2bit|.
-      {7, "", V_ASN1_OBJECT_DESCRIPTOR, ""},
-      {8, "", V_ASN1_EXTERNAL, ""},
-      {9, "", V_ASN1_REAL, ""},
-      {11, "", 11 /* EMBEDDED PDV */, ""},
-      {13, "", 13 /* RELATIVE-OID */, ""},
-      {14, "", 14 /* TIME */, ""},
-      {15, "", 15 /* not a type; reserved value */, ""},
-      {29, "", 29 /* CHARACTER STRING */, ""},
+      // NULL, BOOLEAN, and OBJECT IDENTIFIER use non-|ASN1_STRING|
+      // representations, so they are represented with |V_ASN1_OTHER|.
+      {CBS_ASN1_NULL, "", V_ASN1_OTHER, std::string("\x05\x00", 2)},
+      {CBS_ASN1_BOOLEAN, std::string("\x00", 1), V_ASN1_OTHER,
+       std::string("\x01\x01\x00", 3)},
+      {CBS_ASN1_BOOLEAN, "\xff", V_ASN1_OTHER, "\x01\x01\xff"},
+      {CBS_ASN1_OBJECT, "\x01\x02\x03\x04", V_ASN1_OTHER,
+       "\x06\x04\x01\x02\x03\x04"},
 
-      // TODO(crbug.com/42290275): Attribute values are an ANY DEFINED BY type,
-      // so we actually should be accepting all ASN.1 types. We currently
-      // do not and only accept the above types. Extend this test when we fix
-      // this.
+      // These types are not actually supported by the library, but we accept
+      // them as |V_ASN1_OTHER|.
+      {7 /* ObjectDescriptor */, "", V_ASN1_OTHER, std::string("\x07\x00", 2)},
+      {8 /* EXTERNAL */, "", V_ASN1_OTHER, std::string("\x08\x00", 2)},
+      {9 /* REAL */, "", V_ASN1_OTHER, std::string("\x09\x00", 2)},
+      {11 /* EMBEDDED PDV */, "", V_ASN1_OTHER, std::string("\x0b\x00", 2)},
+      {13 /* RELATIVE-OID */, "", V_ASN1_OTHER, std::string("\x0d\x00", 2)},
+      {14 /* TIME */, "", V_ASN1_OTHER, std::string("\x0e\x00", 2)},
+      {15 /* not a type; reserved value */, "", V_ASN1_OTHER,
+       std::string("\x0f\x00", 2)},
+      {29 /* CHARACTER STRING */, "", V_ASN1_OTHER, std::string("\x1d\x00", 2)},
+
+      // Non-universal tags are allowed as |V_ASN1_OTHER| too.
+      {CBS_ASN1_APPLICATION | CBS_ASN1_CONSTRUCTED | 42, "", V_ASN1_OTHER,
+       std::string("\x7f\x2a\x00", 3)},
+      {CBS_ASN1_APPLICATION | 42, "", V_ASN1_OTHER,
+       std::string("\x5f\x2a\x00", 3)},
   };
   for (const auto &t : kTests) {
     SCOPED_TRACE(t.der_tag);
@@ -7257,7 +7273,9 @@
       // Errors in supported universal types should be handled.
       {CBS_ASN1_NULL, "not null"},
       {CBS_ASN1_BOOLEAN, "not bool"},
+      {CBS_ASN1_BOOLEAN, "\1"},  // BOOL in DER must be 0x00 or 0xff.
       {CBS_ASN1_OBJECT, ""},
+      {CBS_ASN1_OBJECT, "\x80\x01"},  // Non-minimal OID encoding
       {CBS_ASN1_INTEGER, std::string("\0\0", 2)},
       {CBS_ASN1_ENUMERATED, std::string("\0\0", 2)},
       {CBS_ASN1_BITSTRING, ""},
@@ -7266,24 +7284,11 @@
       {CBS_ASN1_UNIVERSALSTRING, "not utf-32"},
       {CBS_ASN1_UTCTIME, "not utctime"},
       {CBS_ASN1_GENERALIZEDTIME, "not generalizedtime"},
+      {CBS_ASN1_NULL | CBS_ASN1_CONSTRUCTED, ""},
+      {CBS_ASN1_OBJECT | CBS_ASN1_CONSTRUCTED, "\x01\x02\x03\x04"},
+      {CBS_ASN1_BOOLEAN | CBS_ASN1_CONSTRUCTED, "\xff"},
       {CBS_ASN1_UTF8STRING | CBS_ASN1_CONSTRUCTED, ""},
       {CBS_ASN1_SEQUENCE & ~CBS_ASN1_CONSTRUCTED, ""},
-
-      // TODO(crbug.com/42290275): The following inputs should parse, but are
-      // currently rejected because they cannot be represented in
-      // |ASN1_ANY_AS_STRING|, either because they don't fit in |ASN1_STRING| or
-      // simply in the |B_ASN1_ANY_AS_STRING| bitmask.
-      {CBS_ASN1_NULL, ""},
-      {CBS_ASN1_BOOLEAN, std::string("\x00", 1)},
-      {CBS_ASN1_BOOLEAN, "\xff"},
-      {CBS_ASN1_OBJECT, "\x01\x02\x03\x04"},
-      {CBS_ASN1_INTEGER, "\x01"},
-      {CBS_ASN1_INTEGER, "\xff"},
-      {CBS_ASN1_UTCTIME, "700101000000Z"},
-      {CBS_ASN1_GENERALIZEDTIME, "19700101000000Z"},
-      {CBS_ASN1_SET, ""},
-      {CBS_ASN1_APPLICATION | CBS_ASN1_CONSTRUCTED | 42, ""},
-      {CBS_ASN1_APPLICATION | 42, ""},
   };
   for (const auto &t : kInvalidTests) {
     SCOPED_TRACE(t.der_tag);
diff --git a/include/openssl/asn1.h b/include/openssl/asn1.h
index 6e8e4ae..ad4905c 100644
--- a/include/openssl/asn1.h
+++ b/include/openssl/asn1.h
@@ -77,6 +77,10 @@
 // V_ASN1_ANY is used by the ASN.1 templates to indicate an ANY type.
 #define V_ASN1_ANY (-4)
 
+// V_ASN1_ANY_AS_STRING is used by the ASN.1 templates to indicate an ANY type
+// represented with |ASN1_STRING| instead of |ASN1_TYPE|.
+#define V_ASN1_ANY_AS_STRING (-5)
+
 // The following constants are tag numbers for universal types.
 #define V_ASN1_EOC 0
 #define V_ASN1_BOOLEAN 1