Check for invalid CHOICE selectors in i2d functions.

This handles normal CHOICE types. A follow-up CL will handle MSTRING and
ANY types.

Update-Note: An invalid CHOICE object (e.g. GENERAL_NAME) will now fail
when encoded, rather than be silently omitted. In particular, CHOICE
objects are default-initialized by tasn_new.c in an empty -1 state.
Structures containing a required CHOICE field can no longer be encoded
without filling in the CHOICE.

Bug: 429
Change-Id: I7011deadf518ddc344a56b07a0e268ceaae17fe0
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/49347
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 28a5998..d93e704 100644
--- a/crypto/asn1/asn1_test.cc
+++ b/crypto/asn1/asn1_test.cc
@@ -1067,6 +1067,22 @@
   }
 }
 
+// Encoding a CHOICE type with an invalid selector should fail.
+TEST(ASN1Test, InvalidChoice) {
+  bssl::UniquePtr<GENERAL_NAME> name(GENERAL_NAME_new());
+  ASSERT_TRUE(name);
+  // CHOICE types are initialized with an invalid selector.
+  EXPECT_EQ(-1, name->type);
+  // |name| should fail to encode.
+  EXPECT_EQ(-1, i2d_GENERAL_NAME(name.get(), nullptr));
+
+  // The error should be propagated through types containing |name|.
+  bssl::UniquePtr<GENERAL_NAMES> names(GENERAL_NAMES_new());
+  ASSERT_TRUE(names);
+  EXPECT_TRUE(bssl::PushToStack(names.get(), std::move(name)));
+  EXPECT_EQ(-1, i2d_GENERAL_NAMES(names.get(), nullptr));
+}
+
 // 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/asn1/tasn_enc.c b/crypto/asn1/tasn_enc.c
index 51a860d..6f35820 100644
--- a/crypto/asn1/tasn_enc.c
+++ b/crypto/asn1/tasn_enc.c
@@ -145,7 +145,7 @@
         }
         return asn1_i2d_ex_primitive(pval, out, it, -1, aclass);
 
-    case ASN1_ITYPE_CHOICE:
+    case ASN1_ITYPE_CHOICE: {
         /*
          * It never makes sense for CHOICE types to have implicit tagging, so if
          * tag != -1, then this looks like an error in the template.
@@ -157,19 +157,19 @@
         if (asn1_cb && !asn1_cb(ASN1_OP_I2D_PRE, pval, it, NULL))
             return -1;
         i = asn1_get_choice_selector(pval, it);
-        if ((i >= 0) && (i < it->tcount)) {
-            ASN1_VALUE **pchval;
-            const ASN1_TEMPLATE *chtt;
-            chtt = it->templates + i;
-            pchval = asn1_get_field_ptr(pval, chtt);
-            return asn1_template_ex_i2d(pchval, out, chtt, -1, aclass);
-        }
-        /* Fixme: error condition if selector out of range. Additionally,
-         * |asn1_cb| is currently called only on error when it should be called
-         * on success. */
-        if (asn1_cb && !asn1_cb(ASN1_OP_I2D_POST, pval, it, NULL))
+        if (i < 0 || i >= it->tcount) {
+            OPENSSL_PUT_ERROR(ASN1, ASN1_R_NO_MATCHING_CHOICE_TYPE);
             return -1;
-        return 0;
+        }
+        const ASN1_TEMPLATE *chtt = it->templates + i;
+        ASN1_VALUE **pchval = asn1_get_field_ptr(pval, chtt);
+        int ret = asn1_template_ex_i2d(pchval, out, chtt, -1, aclass);
+        if (ret < 0 ||
+            (asn1_cb && !asn1_cb(ASN1_OP_I2D_POST, pval, it, NULL))) {
+            return -1;
+        }
+        return ret;
+    }
 
     case ASN1_ITYPE_EXTERN:
         /* If new style i2d it does all the work */