Remove support for indefinite lengths in crypto/asn1.

This simplifies the ASN1_get_object calling convention and removes
another significant source of tasn_dec.c complexity. This change does
not affect our PKCS#7 and PKCS#12 parsers.

Update-Note: Invalid certificates (and the few external structures using
asn1t.h) with BER indefinite lengths will now be rejected.

Bug: 354
Change-Id: I723036798fc3254d0a289c77b105fcbdcda309b2
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/50287
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/crypto/asn1/asn1_lib.c b/crypto/asn1/asn1_lib.c
index 1954bea..fbf4d68 100644
--- a/crypto/asn1/asn1_lib.c
+++ b/crypto/asn1/asn1_lib.c
@@ -104,8 +104,7 @@
 OPENSSL_DECLARE_ERROR_REASON(ASN1, UNKNOWN_TAG)
 OPENSSL_DECLARE_ERROR_REASON(ASN1, UNSUPPORTED_TYPE)
 
-static int asn1_get_length(const unsigned char **pp, int *inf, long *rl,
-                           long max);
+static int asn1_get_length(const unsigned char **pp, long *rl, long max);
 static void asn1_put_length(unsigned char **pp, int length);
 
 int ASN1_get_object(const unsigned char **pp, long *plength, int *ptag,
@@ -114,7 +113,7 @@
     int i, ret;
     long l;
     const unsigned char *p = *pp;
-    int tag, xclass, inf;
+    int tag, xclass;
     long max = omax;
 
     if (!max)
@@ -153,54 +152,43 @@
 
     *ptag = tag;
     *pclass = xclass;
-    if (!asn1_get_length(&p, &inf, plength, max))
+    if (!asn1_get_length(&p, plength, max))
         goto err;
 
-    if (inf && !(ret & V_ASN1_CONSTRUCTED))
-        goto err;
-
-#if 0
-    fprintf(stderr, "p=%d + *plength=%ld > omax=%ld + *pp=%d  (%d > %d)\n",
-            (int)p, *plength, omax, (int)*pp, (int)(p + *plength),
-            (int)(omax + *pp));
-
-#endif
     if (*plength > (omax - (p - *pp))) {
         OPENSSL_PUT_ERROR(ASN1, ASN1_R_TOO_LONG);
         return 0x80;
     }
     *pp = p;
-    return (ret | inf);
+    return ret;
  err:
     OPENSSL_PUT_ERROR(ASN1, ASN1_R_HEADER_TOO_LONG);
     return 0x80;
 }
 
-static int asn1_get_length(const unsigned char **pp, int *inf, long *rl,
-                           long max)
+static int asn1_get_length(const unsigned char **pp, long *rl, long max)
 {
     const unsigned char *p = *pp;
     unsigned long ret = 0;
     unsigned long i;
 
-    if (max-- < 1)
+    if (max-- < 1) {
         return 0;
+    }
     if (*p == 0x80) {
-        *inf = 1;
-        ret = 0;
-        p++;
+        /* We do not support BER indefinite-length encoding. */
+        return 0;
+    }
+    i = *p & 0x7f;
+    if (*(p++) & 0x80) {
+        if (i > sizeof(ret) || max < (long)i)
+            return 0;
+        while (i-- > 0) {
+            ret <<= 8L;
+            ret |= *(p++);
+        }
     } else {
-        *inf = 0;
-        i = *p & 0x7f;
-        if (*(p++) & 0x80) {
-            if (i > sizeof(ret) || max < (long)i)
-                return 0;
-            while (i-- > 0) {
-                ret <<= 8L;
-                ret |= *(p++);
-            }
-        } else
-            ret = i;
+        ret = i;
     }
     /*
      * Bound the length to comfortably fit in an int. Lengths in this module
diff --git a/crypto/asn1/asn1_test.cc b/crypto/asn1/asn1_test.cc
index 96f9f9c..4d8ed1b 100644
--- a/crypto/asn1/asn1_test.cc
+++ b/crypto/asn1/asn1_test.cc
@@ -1678,6 +1678,11 @@
   int tag_class;
   EXPECT_EQ(0x80, ASN1_get_object(&ptr, &length, &tag, &tag_class,
                                   sizeof(kTruncated)));
+
+  static const uint8_t kIndefinite[] = {0x30, 0x80, 0x00, 0x00};
+  ptr = kIndefinite;
+  EXPECT_EQ(0x80, ASN1_get_object(&ptr, &length, &tag, &tag_class,
+                                  sizeof(kIndefinite)));
 }
 
 // The ASN.1 macros do not work on Windows shared library builds, where usage of
diff --git a/crypto/asn1/tasn_dec.c b/crypto/asn1/tasn_dec.c
index c45be7a..beb9a0b 100644
--- a/crypto/asn1/tasn_dec.c
+++ b/crypto/asn1/tasn_dec.c
@@ -75,11 +75,9 @@
 #define ASN1_MAX_CONSTRUCTED_NEST 30
 
 static int asn1_check_eoc(const unsigned char **in, long len);
-static int asn1_find_end(const unsigned char **in, long len, char inf);
 
 static int asn1_check_tlen(long *olen, int *otag, unsigned char *oclass,
-                           char *inf, char *cst,
-                           const unsigned char **in, long len,
+                           char *cst, const unsigned char **in, long len,
                            int exptag, int expclass, char opt, ASN1_TLC *ctx);
 
 static int asn1_template_ex_d2i(ASN1_VALUE **pval,
@@ -166,7 +164,7 @@
     const ASN1_EXTERN_FUNCS *ef;
     const unsigned char *p = NULL, *q;
     unsigned char oclass;
-    char seq_eoc, seq_nolen, cst, isopt;
+    char cst, isopt;
     int i;
     int otag;
     int ret = 0;
@@ -222,7 +220,7 @@
 
         p = *in;
         /* Just read in tag and class */
-        ret = asn1_check_tlen(NULL, &otag, &oclass, NULL, NULL,
+        ret = asn1_check_tlen(NULL, &otag, &oclass, NULL,
                               &p, len, -1, 0, 1, ctx);
         if (!ret) {
             OPENSSL_PUT_ERROR(ASN1, ASN1_R_NESTED_ASN1_ERROR);
@@ -328,15 +326,13 @@
             aclass = V_ASN1_UNIVERSAL;
         }
         /* Get SEQUENCE length and update len, p */
-        ret = asn1_check_tlen(&len, NULL, NULL, &seq_eoc, &cst,
+        ret = asn1_check_tlen(&len, NULL, NULL, &cst,
                               &p, len, tag, aclass, opt, ctx);
         if (!ret) {
             OPENSSL_PUT_ERROR(ASN1, ASN1_R_NESTED_ASN1_ERROR);
             goto err;
         } else if (ret == -1)
             return -1;
-        /* If indefinite we don't do a length check */
-        seq_nolen = seq_eoc;
         if (!cst) {
             OPENSSL_PUT_ERROR(ASN1, ASN1_R_SEQUENCE_NOT_CONSTRUCTED);
             goto err;
@@ -377,15 +373,12 @@
             if (!len)
                 break;
             q = p;
+            /* TODO(https://crbug.com/boringssl/455): Although we've removed
+             * indefinite-length support, this check is not quite a no-op.
+             * Reject [UNIVERSAL 0] in the tag parsers themselves. */
             if (asn1_check_eoc(&p, len)) {
-                if (!seq_eoc) {
-                    OPENSSL_PUT_ERROR(ASN1, ASN1_R_UNEXPECTED_EOC);
-                    goto err;
-                }
-                len -= p - q;
-                seq_eoc = 0;
-                q = p;
-                break;
+                OPENSSL_PUT_ERROR(ASN1, ASN1_R_UNEXPECTED_EOC);
+                goto err;
             }
             /*
              * This determines the OPTIONAL flag value. The field cannot be
@@ -417,13 +410,8 @@
             len -= p - q;
         }
 
-        /* Check for EOC if expecting one */
-        if (seq_eoc && !asn1_check_eoc(&p, len)) {
-            OPENSSL_PUT_ERROR(ASN1, ASN1_R_MISSING_EOC);
-            goto err;
-        }
         /* Check all data read */
-        if (!seq_nolen && len) {
+        if (len) {
             OPENSSL_PUT_ERROR(ASN1, ASN1_R_SEQUENCE_LENGTH_MISMATCH);
             goto err;
         }
@@ -494,7 +482,6 @@
     int ret;
     long len;
     const unsigned char *p, *q;
-    char exp_eoc;
     if (!val)
         return 0;
     flags = tt->flags;
@@ -509,7 +496,7 @@
          * Need to work out amount of data available to the inner content and
          * where it starts: so read in EXPLICIT header to get the info.
          */
-        ret = asn1_check_tlen(&len, NULL, NULL, &exp_eoc, &cst,
+        ret = asn1_check_tlen(&len, NULL, NULL, &cst,
                               &p, inlen, tt->tag, aclass, opt, ctx);
         q = p;
         if (!ret) {
@@ -529,20 +516,10 @@
         }
         /* We read the field in OK so update length */
         len -= p - q;
-        if (exp_eoc) {
-            /* If NDEF we must have an EOC here */
-            if (!asn1_check_eoc(&p, len)) {
-                OPENSSL_PUT_ERROR(ASN1, ASN1_R_MISSING_EOC);
-                goto err;
-            }
-        } else {
-            /*
-             * Otherwise we must hit the EXPLICIT tag end or its an error
-             */
-            if (len) {
-                OPENSSL_PUT_ERROR(ASN1, ASN1_R_EXPLICIT_LENGTH_MISMATCH);
-                goto err;
-            }
+        /* Check for trailing data. */
+        if (len) {
+            OPENSSL_PUT_ERROR(ASN1, ASN1_R_EXPLICIT_LENGTH_MISMATCH);
+            goto err;
         }
     } else
         return asn1_template_noexp_d2i(val, in, inlen, tt, opt, ctx, depth);
@@ -573,7 +550,6 @@
     if (flags & ASN1_TFLG_SK_MASK) {
         /* SET OF, SEQUENCE OF */
         int sktag, skaclass;
-        char sk_eoc;
         /* First work out expected inner tag value */
         if (flags & ASN1_TFLG_IMPTAG) {
             sktag = tt->tag;
@@ -586,7 +562,7 @@
                 sktag = V_ASN1_SEQUENCE;
         }
         /* Get the tag */
-        ret = asn1_check_tlen(&len, NULL, NULL, &sk_eoc, NULL,
+        ret = asn1_check_tlen(&len, NULL, NULL, NULL,
                               &p, len, sktag, skaclass, opt, ctx);
         if (!ret) {
             OPENSSL_PUT_ERROR(ASN1, ASN1_R_NESTED_ASN1_ERROR);
@@ -616,15 +592,12 @@
         while (len > 0) {
             ASN1_VALUE *skfield;
             const unsigned char *q = p;
-            /* See if EOC found */
+            /* TODO(https://crbug.com/boringssl/455): Although we've removed
+             * indefinite-length support, this check is not quite a no-op.
+             * Reject [UNIVERSAL 0] in the tag parsers themselves. */
             if (asn1_check_eoc(&p, len)) {
-                if (!sk_eoc) {
-                    OPENSSL_PUT_ERROR(ASN1, ASN1_R_UNEXPECTED_EOC);
-                    goto err;
-                }
-                len -= p - q;
-                sk_eoc = 0;
-                break;
+                OPENSSL_PUT_ERROR(ASN1, ASN1_R_UNEXPECTED_EOC);
+                goto err;
             }
             skfield = NULL;
              if (!asn1_item_ex_d2i(&skfield, &p, len, ASN1_ITEM_ptr(tt->item),
@@ -639,10 +612,6 @@
                 goto err;
             }
         }
-        if (sk_eoc) {
-            OPENSSL_PUT_ERROR(ASN1, ASN1_R_MISSING_EOC);
-            goto err;
-        }
     } else if (flags & ASN1_TFLG_IMPTAG) {
         /* IMPLICIT tagging */
         ret = asn1_item_ex_d2i(val, &p, len, ASN1_ITEM_ptr(tt->item), tt->tag,
@@ -679,7 +648,7 @@
 {
     int ret = 0, utype;
     long plen;
-    char cst, inf;
+    char cst;
     const unsigned char *p;
     const unsigned char *cont = NULL;
     long len;
@@ -706,7 +675,7 @@
             return 0;
         }
         p = *in;
-        ret = asn1_check_tlen(NULL, &utype, &oclass, NULL, NULL,
+        ret = asn1_check_tlen(NULL, &utype, &oclass, NULL,
                               &p, inlen, -1, 0, 0, ctx);
         if (!ret) {
             OPENSSL_PUT_ERROR(ASN1, ASN1_R_NESTED_ASN1_ERROR);
@@ -721,7 +690,7 @@
     }
     p = *in;
     /* Check header */
-    ret = asn1_check_tlen(&plen, NULL, NULL, &inf, &cst,
+    ret = asn1_check_tlen(&plen, NULL, NULL, &cst,
                           &p, inlen, tag, aclass, opt, ctx);
     if (!ret) {
         OPENSSL_PUT_ERROR(ASN1, ASN1_R_NESTED_ASN1_ERROR);
@@ -746,15 +715,8 @@
         }
 
         cont = *in;
-        /* If indefinite length constructed find the real end */
-        if (inf) {
-            if (!asn1_find_end(&p, plen, inf))
-                goto err;
-            len = p - cont;
-        } else {
-            len = p - cont + plen;
-            p += plen;
-        }
+        len = p - cont + plen;
+        p += plen;
     } else if (cst) {
         /* This parser historically supported BER constructed strings. We no
          * longer do and will gradually tighten this parser into a DER
@@ -906,59 +868,6 @@
     return ret;
 }
 
-/*
- * This function finds the end of an ASN1 structure when passed its maximum
- * length, whether it is indefinite length and a pointer to the content. This
- * is more efficient than calling asn1_collect because it does not recurse on
- * each indefinite length header.
- */
-
-static int asn1_find_end(const unsigned char **in, long len, char inf)
-{
-    int expected_eoc;
-    long plen;
-    const unsigned char *p = *in, *q;
-    /* If not indefinite length constructed just add length */
-    if (inf == 0) {
-        *in += len;
-        return 1;
-    }
-    expected_eoc = 1;
-    /*
-     * Indefinite length constructed form. Find the end when enough EOCs are
-     * found. If more indefinite length constructed headers are encountered
-     * increment the expected eoc count otherwise just skip to the end of the
-     * data.
-     */
-    while (len > 0) {
-        if (asn1_check_eoc(&p, len)) {
-            expected_eoc--;
-            if (expected_eoc == 0)
-                break;
-            len -= 2;
-            continue;
-        }
-        q = p;
-        /* Just read in a header: only care about the length */
-        if (!asn1_check_tlen(&plen, NULL, NULL, &inf, NULL, &p, len,
-                             -1, 0, 0, NULL)) {
-            OPENSSL_PUT_ERROR(ASN1, ASN1_R_NESTED_ASN1_ERROR);
-            return 0;
-        }
-        if (inf)
-            expected_eoc++;
-        else
-            p += plen;
-        len -= p - q;
-    }
-    if (expected_eoc) {
-        OPENSSL_PUT_ERROR(ASN1, ASN1_R_MISSING_EOC);
-        return 0;
-    }
-    *in = p;
-    return 1;
-}
-
 /* Check for ASN1 EOC and swallow it if found */
 
 static int asn1_check_eoc(const unsigned char **in, long len)
@@ -975,15 +884,12 @@
 }
 
 /*
- * Check an ASN1 tag and length: a bit like ASN1_get_object but it sets the
- * length for indefinite length constructed form, we don't know the exact
- * length but we can set an upper bound to the amount of data available minus
- * the header length just read.
+ * Check an ASN1 tag and length: a bit like ASN1_get_object but it handles
+ * the ASN1_TLC cache and checks the expected tag.
  */
 
 static int asn1_check_tlen(long *olen, int *otag, unsigned char *oclass,
-                           char *inf, char *cst,
-                           const unsigned char **in, long len,
+                           char *cst, const unsigned char **in, long len,
                            int exptag, int expclass, char opt, ASN1_TLC *ctx)
 {
     int i;
@@ -1009,10 +915,13 @@
             ctx->hdrlen = p - q;
             ctx->valid = 1;
             /*
-             * If definite length, and no error, length + header can't exceed
-             * total amount of data available.
+             * If no error, length + header can't exceed total amount of data
+             * available.
+             *
+             * TODO(davidben): Is this check necessary? |ASN1_get_object|
+             * should already guarantee this.
              */
-            if (!(i & 0x81) && ((plen + ctx->hdrlen) > len)) {
+            if (!(i & 0x80) && ((plen + ctx->hdrlen) > len)) {
                 OPENSSL_PUT_ERROR(ASN1, ASN1_R_TOO_LONG);
                 asn1_tlc_clear(ctx);
                 return 0;
@@ -1043,12 +952,6 @@
         asn1_tlc_clear(ctx);
     }
 
-    if (i & 1)
-        plen = len - (p - q);
-
-    if (inf)
-        *inf = i & 1;
-
     if (cst)
         *cst = i & V_ASN1_CONSTRUCTED;
 
diff --git a/crypto/x509/asn1_gen.c b/crypto/x509/asn1_gen.c
index f1a20e0..cd8185b 100644
--- a/crypto/x509/asn1_gen.c
+++ b/crypto/x509/asn1_gen.c
@@ -215,13 +215,7 @@
          * For IMPLICIT tagging the length should match the original length
          * and constructed flag should be consistent.
          */
-        if (r & 0x1) {
-            /* Indefinite length constructed */
-            hdr_constructed = 2;
-            hdr_len = 0;
-        } else
-            /* Just retain constructed flag */
-            hdr_constructed = r & V_ASN1_CONSTRUCTED;
+        hdr_constructed = r & V_ASN1_CONSTRUCTED;
         /*
          * Work out new length with IMPLICIT tag: ignore constructed because
          * it will mess up if indefinite length
diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc
index 477e5a1..46b7b3f 100644
--- a/crypto/x509/x509_test.cc
+++ b/crypto/x509/x509_test.cc
@@ -3493,8 +3493,24 @@
 -----END CERTIFICATE-----
 )";
 
+// kIndefiniteLength is an X.509 certificate where the outermost SEQUENCE uses
+// BER indefinite-length encoding.
+static const char kIndefiniteLength[] = R"(
+-----BEGIN CERTIFICATE-----
+MIAwgcagAwIBAgICBNIwCgYIKoZIzj0EAwIwDzENMAsGA1UEAxMEVGVzdDAgFw0w
+MDAxMDEwMDAwMDBaGA8yMTAwMDEwMTAwMDAwMFowDzENMAsGA1UEAxMEVGVzdDBZ
+MBMGByqGSM49AgEGCCqGSM49AwEHA0IABOYraeK/ZZ+Xvi8eDZSKTNWXa7epHg1G
++92pqR6d3LpaAefWl6gKGPnDxKMeVuJ8g0jbFhoc9R1+8ZQtS89yIsGjEDAOMAwG
+A1UdEwQFMAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIhAKnSIhfmzfQpeOKFHiAqcml3
+ex6oaVVGoJWCsPQoZjVAAiEAqTHS9HzZBTQ20cMPXUpf8u5AXZP7adeh4qnksoBs
+xWIAAA==
+-----END CERTIFICATE-----
+)";
+
 TEST(X509Test, BER) {
   // Constructed strings are forbidden in DER.
   EXPECT_FALSE(CertFromPEM(kConstructedBitString));
   EXPECT_FALSE(CertFromPEM(kConstructedOctetString));
+  // Indefinite lengths are forbidden in DER.
+  EXPECT_FALSE(CertFromPEM(kIndefiniteLength));
 }
diff --git a/include/openssl/asn1.h b/include/openssl/asn1.h
index 0cf0604..a186701 100644
--- a/include/openssl/asn1.h
+++ b/include/openssl/asn1.h
@@ -1720,25 +1720,20 @@
 
 // Low-level encoding functions.
 
-// ASN1_get_object parses a BER element from up to |max_len| bytes at |*inp|.
+// ASN1_get_object parses a BER element from up to |max_len| bytes at |*inp|. It
+// returns |V_ASN1_CONSTRUCTED| if it successfully parsed a constructed element,
+// zero if it successfully parsed a primitive element, and 0x80 on error. On
+// success, it additionally advances |*inp| to the element body, sets
+// |*out_length|, |*out_tag|, and |*out_class| to the element's length, tag
+// number, and tag class, respectively,
 //
-// If the return value is 0x80, the function failed to parse an element.
-// The contents of |*inp|, |*out_length|, |*out_tag|, and |*out_class| are
-// undefined.
+// Unlike OpenSSL, this function does not support indefinite-length elements.
 //
-// Otherwise, the function successfully parsed a element. The return value has
-// bit 0x01 set if the length is indefinite, and |V_ASN1_CONSTRUCTED| set if the
-// element was constructed. The function additionally advances |*inp| to the
-// element body and sets |*out_length|, |*out_tag|, and |*out_class| to the
-// element's length, tag number, and tag class, respectively. If the length is
-// indefinite, |*out_length| will be zero and the caller is responsible for
-// finding the end-of-contents.
+// This function is difficult to use correctly. Use |CBS_get_asn1| and related
+// functions from bytestring.h.
 //
-// This function is very difficult to use correctly. Use |CBS_get_asn1| and
-// related functions from bytestring.h.
-//
-// TODO(https://crbug.com/boringssl/354): Remove support for indefinite lengths
-// and non-minimal lengths.
+// TODO(https://crbug.com/boringssl/354): Remove support for non-minimal
+// lengths.
 OPENSSL_EXPORT int ASN1_get_object(const unsigned char **inp, long *out_length,
                                    int *out_tag, int *out_class, long max_len);