Rework support for ASN.1 BER.

Previously, the ASN.1 functions in bytestring were capable of processing
indefinite length elements when the _ber functions were used. That works
well enough for PKCS#3, but NSS goes a bit crazy with BER encoding and
PKCS#12. Rather than complicate the core bytestring functions further,
the BER support is removed from them and moved to a separate function
that converts from BER to DER (if needed).

Change-Id: I2212b28e99bab9fab8c61f80d2012d3e5a3cc2f0
Reviewed-on: https://boringssl-review.googlesource.com/1591
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/crypto/bytestring/CMakeLists.txt b/crypto/bytestring/CMakeLists.txt
index 409a0ce..dc48583 100644
--- a/crypto/bytestring/CMakeLists.txt
+++ b/crypto/bytestring/CMakeLists.txt
@@ -5,6 +5,7 @@
 
 	OBJECT
 
+	ber.c
 	cbs.c
 	cbb.c
 )
diff --git a/crypto/bytestring/ber.c b/crypto/bytestring/ber.c
new file mode 100644
index 0000000..8be77e0
--- /dev/null
+++ b/crypto/bytestring/ber.c
@@ -0,0 +1,226 @@
+/* Copyright (c) 2014, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#include <openssl/bytestring.h>
+
+#include "internal.h"
+
+
+/* kMaxDepth is a just a sanity limit. The code should be such that the length
+ * of the input being processes always decreases. None the less, a very large
+ * input could otherwise cause the stack to overflow. */
+static const unsigned kMaxDepth = 2048;
+
+/* cbs_find_ber walks an ASN.1 structure in |orig_in| and sets |*ber_found|
+ * depending on whether an indefinite length element was found. The value of
+ * |in| is not changed. It returns one on success (i.e. |*ber_found| was set)
+ * and zero on error. */
+static int cbs_find_ber(CBS *orig_in, char *ber_found, unsigned depth) {
+  CBS in;
+
+  if (depth > kMaxDepth) {
+    return 0;
+  }
+
+  CBS_init(&in, CBS_data(orig_in), CBS_len(orig_in));
+  *ber_found = 0;
+
+  while (CBS_len(&in) > 0) {
+    CBS contents;
+    unsigned tag;
+    size_t header_len;
+
+    if (!CBS_get_any_asn1_element(&in, &contents, &tag, &header_len)) {
+      return 0;
+    }
+    if (CBS_len(&contents) == header_len &&
+        header_len > 0 &&
+        CBS_data(&contents)[header_len-1] == 0x80) {
+      *ber_found = 1;
+      return 1;
+    }
+    if (tag & CBS_ASN1_CONSTRUCTED) {
+      if (!CBS_skip(&contents, header_len) ||
+          !cbs_find_ber(&contents, ber_found, depth + 1)) {
+        return 0;
+      }
+    }
+  }
+
+  return 1;
+}
+
+/* is_primitive_type returns true if |tag| likely a primitive type. Normally
+ * one can just test the "constructed" bit in the tag but, in BER, even
+ * primitive tags can have the constructed bit if they have indefinite
+ * length. */
+static char is_primitive_type(unsigned tag) {
+  return (tag & 0xc0) == 0 &&
+         (tag & 0x1f) != (CBS_ASN1_SEQUENCE & 0x1f) &&
+         (tag & 0x1f) != (CBS_ASN1_SET & 0x1f);
+}
+
+/* is_eoc returns true if |header_len| and |contents|, as returned by
+ * |CBS_get_any_asn1_element|, indicate an "end of contents" (EOC) value. */
+static char is_eoc(size_t header_len, CBS *contents) {
+  return header_len == 2 && CBS_len(contents) == 2 &&
+         memcmp(CBS_data(contents), "\x00\x00", 2) == 0;
+}
+
+/* cbs_convert_ber reads BER data from |in| and writes DER data to |out|. If
+ * |squash_header| is set then the top-level of elements from |in| will not
+ * have their headers written. This is used when concatenating the fragments of
+ * an indefinite length, primitive value. If |looking_for_eoc| is set then any
+ * EOC elements found will cause the function to return after consuming it.
+ * It returns one on success and zero on error. */
+static int cbs_convert_ber(CBS *in, CBB *out, char squash_header,
+                           char looking_for_eoc, unsigned depth) {
+  if (depth > kMaxDepth) {
+    return 0;
+  }
+
+  while (CBS_len(in) > 0) {
+    CBS contents;
+    unsigned tag;
+    size_t header_len;
+    CBB *out_contents, out_contents_storage;
+
+    if (!CBS_get_any_asn1_element(in, &contents, &tag, &header_len)) {
+      return 0;
+    }
+    out_contents = out;
+
+    if (CBS_len(&contents) == header_len) {
+      if (is_eoc(header_len, &contents)) {
+        return looking_for_eoc;
+      }
+
+      if (header_len > 0 && CBS_data(&contents)[header_len - 1] == 0x80) {
+        CBB out_context_specific;
+
+        /* This is an indefinite length element. If it's a SEQUENCE or SET then
+         * we just need to write the out the contents as normal, but with a
+         * concrete length prefix.
+         *
+         * If it's a something else then the contents will be a series of BER
+         * elements of the same type which need to be concatenated. */
+        const char context_specific = (tag & 0xc0) == 0x80;
+        const char simple_type = is_primitive_type(tag);
+
+        if (!squash_header) {
+          unsigned out_tag = tag;
+          if (simple_type) {
+            out_tag &= ~CBS_ASN1_CONSTRUCTED;
+          }
+          if (!CBB_add_asn1(out, &out_contents_storage, out_tag)) {
+            return 0;
+          }
+          out_contents = &out_contents_storage;
+        }
+
+        /* If context specific then we peek at the inner tag and replicate it.
+         * This is because NSS produces odd-seeming BER structures where an
+         * indefinite length explicit tag doesn't have the expected actual tag
+         * inside it. */
+        if (context_specific) {
+          CBS in_copy, contents;
+          unsigned tag;
+          size_t header_len;
+
+          CBS_init(&in_copy, CBS_data(in), CBS_len(in));
+          if (!CBS_get_any_asn1_element(&in_copy, &contents, &tag, &header_len)) {
+            return 0;
+          }
+          if (is_eoc(header_len, &contents)) {
+            /* The indefinite-length value is empty. Unread the EOC and
+             * continue. */
+            CBS_init(in, CBS_data(&in_copy), CBS_len(&in_copy));
+            continue;
+          }
+          if (is_primitive_type(tag)) {
+            tag &= 0x1f;
+          }
+          if (!CBB_add_asn1(out_contents, &out_context_specific, tag)) {
+            return 0;
+          }
+          out_contents = &out_context_specific;
+        }
+
+        if (!cbs_convert_ber(in, out_contents,
+                             context_specific || simple_type,
+                             1 /* looking for eoc */, depth + 1)) {
+          return 0;
+        }
+        if (out_contents != out && !CBB_flush(out)) {
+          return 0;
+        }
+        continue;
+      }
+    }
+
+    if (!squash_header) {
+      if (!CBB_add_asn1(out, &out_contents_storage, tag)) {
+        return 0;
+      }
+      out_contents = &out_contents_storage;
+    }
+
+    if (!CBS_skip(&contents, header_len)) {
+      return 0;
+    }
+
+    if (tag & CBS_ASN1_CONSTRUCTED) {
+      if (!cbs_convert_ber(&contents, out_contents, 0 /* don't squash header */,
+                           0 /* not looking for eoc */, depth + 1)) {
+        return 0;
+      }
+    } else {
+      if (!CBB_add_bytes(out_contents, CBS_data(&contents),
+                         CBS_len(&contents))) {
+        return 0;
+      }
+    }
+
+    if (out_contents != out && !CBB_flush(out)) {
+      return 0;
+    }
+  }
+
+  return looking_for_eoc == 0;
+}
+
+int CBS_asn1_ber_to_der(CBS *in, uint8_t **out, size_t *out_len) {
+  CBB cbb;
+
+  /* First, do a quick walk to find any indefinite-length elements. Most of the
+   * time we hope that there aren't any and thus we can quickly return. */
+  char conversion_needed;
+  if (!cbs_find_ber(in, &conversion_needed, 0)) {
+    return 0;
+  }
+
+  if (!conversion_needed) {
+    *out = NULL;
+    *out_len = 0;
+    return 1;
+  }
+
+  CBB_init(&cbb, CBS_len(in));
+  if (!cbs_convert_ber(in, &cbb, 0, 0, 0)) {
+    CBB_cleanup(&cbb);
+    return 0;
+  }
+
+  return CBB_finish(&cbb, out, out_len);
+}
diff --git a/crypto/bytestring/bytestring_test.c b/crypto/bytestring/bytestring_test.c
index 29d26e8..5ea9d48 100644
--- a/crypto/bytestring/bytestring_test.c
+++ b/crypto/bytestring/bytestring_test.c
@@ -17,6 +17,8 @@
 
 #include <openssl/bytestring.h>
 
+#include "internal.h"
+
 
 static int test_skip(void) {
   static const uint8_t kData[] = {1, 2, 3};
@@ -145,61 +147,6 @@
   return 1;
 }
 
-static int test_get_indef(void) {
-  static const uint8_t kData1[] = {0x30, 0x80, 0x00, 0x00};
-  static const uint8_t kDataWithoutEOC[] = {0x30, 0x80, 0x01, 0x00};
-  static const uint8_t kDataWithBadInternalLength[] = {0x30, 0x80, 0x01, 0x01};
-  static const uint8_t kDataNested[] = {0x30, 0x80, 0x30, 0x80, 0x30, 0x80,
-                                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
-  static const uint8_t kDataPrimitive[] = {0x02, 0x80, 0x00, 0x00};
-
-  CBS data, contents;
-  CBS_init(&data, kData1, sizeof(kData1));
-  if (CBS_get_asn1(&data, &contents, 0x30)) {
-    /* Indefinite lengths should not be supported in DER mode. */
-    fprintf(stderr, "Indefinite length parsed by CBS_get_asn1.\n");
-    return 0;
-  }
-
-  if (!CBS_get_asn1_ber(&data, &contents, 0x30) ||
-      CBS_len(&contents) != 0 ||
-      CBS_len(&data) != 0) {
-    fprintf(stderr, "Simple indefinite length failed.\n");
-    return 0;
-  }
-
-  CBS_init(&data, kDataWithoutEOC, sizeof(kDataWithoutEOC));
-  if (CBS_get_asn1_ber(&data, &contents, 0x30)) {
-    fprintf(stderr, "Parsed without EOC.\n");
-    return 0;
-  }
-
-  CBS_init(&data, kDataWithBadInternalLength,
-           sizeof(kDataWithBadInternalLength));
-  if (CBS_get_asn1_ber(&data, &contents, 0x30)) {
-    fprintf(stderr, "Parsed with internal length.\n");
-    return 0;
-  }
-
-  CBS_init(&data, kDataNested, sizeof(kDataNested));
-  if (!CBS_get_asn1_ber(&data, &contents, 0x30) ||
-      CBS_len(&contents) != 8 ||
-      CBS_len(&data) != 0) {
-    fprintf(stderr, "Nested indefinite lengths failed.\n");
-    return 0;
-  }
-
-  CBS_init(&data, kDataPrimitive, sizeof(kDataPrimitive));
-  if (CBS_get_asn1_ber(&data, &contents, 0x02)) {
-    /* Indefinite lengths should not be supported for non-constructed
-     * elements. */
-    fprintf(stderr, "Parsed non-constructed element with indefinite length\n");
-    return 0;
-  }
-
-  return 1;
-}
-
 static int test_cbb_basic(void) {
   static const uint8_t kExpected[] = {1, 2, 3, 4, 5, 6, 7, 8};
   uint8_t *buf;
@@ -405,19 +352,100 @@
   return 1;
 }
 
+static int do_ber_convert(const char *name,
+                          const uint8_t *der_expected, size_t der_len,
+                          const uint8_t *ber, size_t ber_len) {
+  CBS in;
+  uint8_t *out;
+  size_t out_len;
+
+  CBS_init(&in, ber, ber_len);
+  if (!CBS_asn1_ber_to_der(&in, &out, &out_len)) {
+    fprintf(stderr, "%s: CBS_asn1_ber_to_der failed.\n", name);
+    return 0;
+  }
+
+  if (out == NULL) {
+    if (ber_len != der_len ||
+        memcmp(der_expected, ber, ber_len) != 0) {
+      fprintf(stderr, "%s: incorrect unconverted result.\n", name);
+      return 0;
+    }
+
+    return 1;
+  }
+
+  if (out_len != der_len ||
+      memcmp(out, der_expected, der_len) != 0) {
+    fprintf(stderr, "%s: incorrect converted result.\n", name);
+    return 0;
+  }
+
+  free(out);
+  return 1;
+}
+
+static int test_ber_convert(void) {
+  static const uint8_t kSimpleBER[] = {0x01, 0x01, 0x00};
+
+  /* kIndefBER contains a SEQUENCE with an indefinite length. */
+  static const uint8_t kIndefBER[] = {0x30, 0x80, 0x01, 0x01, 0x02, 0x00, 0x00};
+  static const uint8_t kIndefDER[] = {0x30, 0x03, 0x01, 0x01, 0x02};
+
+  /* kOctetStringBER contains an indefinite length OCTETSTRING with two parts.
+   * These parts need to be concatenated in DER form. */
+  static const uint8_t kOctetStringBER[] = {0x24, 0x80, 0x04, 0x02, 0,    1,
+                                            0x04, 0x02, 2,    3,    0x00, 0x00};
+  static const uint8_t kOctetStringDER[] = {0x04, 0x04, 0, 1, 2, 3};
+
+  /* kNSSBER is part of a PKCS#12 message generated by NSS that uses indefinite
+   * length elements extensively. */
+  static const uint8_t kNSSBER[] = {
+      0x30, 0x80, 0x02, 0x01, 0x03, 0x30, 0x80, 0x06, 0x09, 0x2a, 0x86, 0x48,
+      0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01, 0xa0, 0x80, 0x24, 0x80, 0x04, 0x04,
+      0x01, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x39,
+      0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05,
+      0x00, 0x04, 0x14, 0x84, 0x98, 0xfc, 0x66, 0x33, 0xee, 0xba, 0xe7, 0x90,
+      0xc1, 0xb6, 0xe8, 0x8f, 0xfe, 0x1d, 0xc5, 0xa5, 0x97, 0x93, 0x3e, 0x04,
+      0x10, 0x38, 0x62, 0xc6, 0x44, 0x12, 0xd5, 0x30, 0x00, 0xf8, 0xf2, 0x1b,
+      0xf0, 0x6e, 0x10, 0x9b, 0xb8, 0x02, 0x02, 0x07, 0xd0, 0x00, 0x00,
+  };
+
+  static const uint8_t kNSSDER[] = {
+      0x30, 0x53, 0x02, 0x01, 0x03, 0x30, 0x13, 0x06, 0x09, 0x2a, 0x86,
+      0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01, 0xa0, 0x06, 0x04, 0x04,
+      0x01, 0x02, 0x03, 0x04, 0x30, 0x39, 0x30, 0x21, 0x30, 0x09, 0x06,
+      0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14, 0x84,
+      0x98, 0xfc, 0x66, 0x33, 0xee, 0xba, 0xe7, 0x90, 0xc1, 0xb6, 0xe8,
+      0x8f, 0xfe, 0x1d, 0xc5, 0xa5, 0x97, 0x93, 0x3e, 0x04, 0x10, 0x38,
+      0x62, 0xc6, 0x44, 0x12, 0xd5, 0x30, 0x00, 0xf8, 0xf2, 0x1b, 0xf0,
+      0x6e, 0x10, 0x9b, 0xb8, 0x02, 0x02, 0x07, 0xd0,
+  };
+
+  return do_ber_convert("kSimpleBER", kSimpleBER, sizeof(kSimpleBER),
+                        kSimpleBER, sizeof(kSimpleBER)) &&
+         do_ber_convert("kIndefBER", kIndefDER, sizeof(kIndefDER), kIndefBER,
+                        sizeof(kIndefBER)) &&
+         do_ber_convert("kOctetStringBER", kOctetStringDER,
+                        sizeof(kOctetStringDER), kOctetStringBER,
+                        sizeof(kOctetStringBER)) &&
+         do_ber_convert("kNSSBER", kNSSDER, sizeof(kNSSDER), kNSSBER,
+                        sizeof(kNSSBER));
+}
+
 int main(void) {
   if (!test_skip() ||
       !test_get_u() ||
       !test_get_prefixed() ||
       !test_get_prefixed_bad() ||
       !test_get_asn1() ||
-      !test_get_indef() ||
       !test_cbb_basic() ||
       !test_cbb_fixed() ||
       !test_cbb_finish_child() ||
       !test_cbb_misuse() ||
       !test_cbb_prefixed() ||
-      !test_cbb_asn1()) {
+      !test_cbb_asn1() ||
+      !test_ber_convert()) {
     return 1;
   }
 
diff --git a/crypto/bytestring/cbs.c b/crypto/bytestring/cbs.c
index 547b5a4..d6e9442 100644
--- a/crypto/bytestring/cbs.c
+++ b/crypto/bytestring/cbs.c
@@ -19,6 +19,8 @@
 #include <assert.h>
 #include <string.h>
 
+#include "internal.h"
+
 
 void CBS_init(CBS *cbs, const uint8_t *data, size_t len) {
   cbs->data = data;
@@ -156,50 +158,8 @@
   return cbs_get_length_prefixed(cbs, out, 3);
 }
 
-static int cbs_get_asn1_element(CBS *cbs, CBS *out, unsigned *out_tag,
-                                size_t *out_header_len, unsigned depth,
-                                int *was_indefinite_len);
-
-/* cbs_get_asn1_indefinite_len sets |*out| to be a CBS that covers an
- * indefinite length element in |cbs| and advances |*in|. On entry, |cbs| will
- * not have had the tag and length byte removed. On exit, |*out| does not cover
- * the EOC element, but |*in| is skipped over it.
- *
- * The |depth| argument counts the number of times the code has recursed trying
- * to find an indefinite length. */
-static int cbs_get_asn1_indefinite_len(CBS *in, CBS *out, unsigned depth) {
-  static const size_t kEOCLength = 2;
-  size_t header_len;
-  unsigned tag;
-  int was_indefinite_len;
-  CBS orig = *in, child;
-
-  if (!CBS_skip(in, 2 /* tag plus 0x80 byte for indefinite len */)) {
-    return 0;
-  }
-
-  for (;;) {
-    if (!cbs_get_asn1_element(in, &child, &tag, &header_len, depth + 1,
-                              &was_indefinite_len)) {
-      return 0;
-    }
-
-    if (!was_indefinite_len && CBS_len(&child) == kEOCLength &&
-        header_len == kEOCLength && tag == 0) {
-      break;
-    }
-  }
-
-  return CBS_get_bytes(&orig, out, CBS_len(&orig) - CBS_len(in) - kEOCLength);
-}
-
-/* MAX_DEPTH the maximum number of levels of indefinite lengths that we'll
- * support. */
-#define MAX_DEPTH 64
-
-static int cbs_get_asn1_element(CBS *cbs, CBS *out, unsigned *out_tag,
-                                size_t *out_header_len, unsigned depth,
-                                int *was_indefinite_len) {
+int CBS_get_any_asn1_element(CBS *cbs, CBS *out, unsigned *out_tag,
+                             size_t *out_header_len) {
   uint8_t tag, length_byte;
   CBS header = *cbs;
   if (!CBS_get_u8(&header, &tag) ||
@@ -213,9 +173,6 @@
   }
 
   *out_tag = tag;
-  if (was_indefinite_len) {
-    *was_indefinite_len = 0;
-  }
 
   size_t len;
   if ((length_byte & 0x80) == 0) {
@@ -227,14 +184,10 @@
     const size_t num_bytes = length_byte & 0x7f;
     uint32_t len32;
 
-    if ((tag & CBS_ASN1_CONSTRUCTED) != 0 && depth < MAX_DEPTH &&
-        num_bytes == 0) {
+    if ((tag & CBS_ASN1_CONSTRUCTED) != 0 && num_bytes == 0) {
       /* indefinite length */
       *out_header_len = 2;
-      if (was_indefinite_len) {
-        *was_indefinite_len = 1;
-      }
-      return cbs_get_asn1_indefinite_len(cbs, out, depth);
+      return CBS_get_bytes(cbs, out, 2);
     }
 
     if (num_bytes == 0 || num_bytes > 4) {
@@ -263,7 +216,7 @@
   return CBS_get_bytes(cbs, out, len);
 }
 
-static int cbs_get_asn1(CBS *cbs, CBS *out, unsigned tag_value, int ber,
+static int cbs_get_asn1(CBS *cbs, CBS *out, unsigned tag_value,
                         int skip_header) {
   size_t header_len;
   unsigned tag;
@@ -273,9 +226,13 @@
     out = &throwaway;
   }
 
-  if (!cbs_get_asn1_element(cbs, out, &tag, &header_len, ber ? 0 : MAX_DEPTH,
-                            NULL) ||
-      tag != tag_value) {
+  if (!CBS_get_any_asn1_element(cbs, out, &tag, &header_len) ||
+      tag != tag_value ||
+      (header_len > 0 &&
+       /* This ensures that the tag is either zero length or
+        * indefinite-length. */
+       CBS_len(out) == header_len &&
+       CBS_data(out)[header_len - 1] == 0x80)) {
     return 0;
   }
 
@@ -288,16 +245,39 @@
 }
 
 int CBS_get_asn1(CBS *cbs, CBS *out, unsigned tag_value) {
-  return cbs_get_asn1(cbs, out, tag_value, 0 /* DER */,
-                      1 /* skip header */);
-}
-
-int CBS_get_asn1_ber(CBS *cbs, CBS *out, unsigned tag_value) {
-  return cbs_get_asn1(cbs, out, tag_value, 1 /* BER */,
-                      1 /* skip header */);
+  return cbs_get_asn1(cbs, out, tag_value, 1 /* skip header */);
 }
 
 int CBS_get_asn1_element(CBS *cbs, CBS *out, unsigned tag_value) {
-  return cbs_get_asn1(cbs, out, tag_value, 0 /* DER */,
-                      0 /* include header */);
+  return cbs_get_asn1(cbs, out, tag_value, 0 /* include header */);
+}
+
+int CBS_get_asn1_uint64(CBS *cbs, uint64_t *out) {
+  CBS bytes;
+  const uint8_t *data;
+  size_t i, len;
+
+  if (!CBS_get_asn1(cbs, &bytes, CBS_ASN1_INTEGER)) {
+    return 0;
+  }
+
+  *out = 0;
+  data = CBS_data(&bytes);
+  len = CBS_len(&bytes);
+
+  if (len > 0 && (data[0] & 0x80) != 0) {
+    /* negative number */
+    return 0;
+  }
+
+  for (i = 0; i < len; i++) {
+    if ((*out >> 56) != 0) {
+      /* Too large to represent as a uint64_t. */
+      return 0;
+    }
+    *out <<= 8;
+    *out |= data[i];
+  }
+
+  return 1;
 }
diff --git a/crypto/bytestring/internal.h b/crypto/bytestring/internal.h
new file mode 100644
index 0000000..42c1a39
--- /dev/null
+++ b/crypto/bytestring/internal.h
@@ -0,0 +1,55 @@
+/* Copyright (c) 2014, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#ifndef OPENSSL_HEADER_BYTESTRING_INTERNAL_H
+#define OPENSSL_HEADER_BYTESTRING_INTERNAL_H
+
+#include <openssl/base.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+
+/* CBS_get_any_asn1_element sets |*out| to contain the next ASN.1 element from
+ * |*cbs| (including header bytes) and advances |*cbs|. It sets |*out_tag| to
+ * the tag number and |*out_header_len| to the length of the ASN.1 header. If
+ * the element has indefinite length then |*out| will only contain the header.
+ *
+ * Tag numbers greater than 31 are not supported. */
+int CBS_get_any_asn1_element(CBS *cbs, CBS *out, unsigned *out_tag,
+                             size_t *out_header_len);
+
+/* CBS_asn1_ber_to_der reads an ASN.1 structure from |in|. If it finds
+ * indefinite-length elements then it attempts to convert the BER data to DER
+ * and sets |*out| and |*out_length| to describe a malloced buffer containing
+ * the DER data. Additionally, |*in| will be advanced over the ASN.1 data.
+ *
+ * If it doesn't find any indefinite-length elements then it sets |*out| to
+ * NULL and |*in| is unmodified.
+ *
+ * A sufficiently complex ASN.1 structure will break this function because it's
+ * not possible to generically convert BER to DER without knowledge of the
+ * structure itself. However, this sufficies to handle the PKCS#7 and #12 output
+ * from NSS.
+ *
+ * It returns one on success and zero otherwise. */
+int CBS_asn1_ber_to_der(CBS *in, uint8_t **out, size_t *out_len);
+
+
+#if defined(__cplusplus)
+}  /* extern C */
+#endif
+
+#endif  /* OPENSSL_HEADER_BYTESTRING_INTERNAL_H */
diff --git a/crypto/x509/pkcs7.c b/crypto/x509/pkcs7.c
index 7744fcc..75c101b 100644
--- a/crypto/x509/pkcs7.c
+++ b/crypto/x509/pkcs7.c
@@ -19,48 +19,61 @@
 #include <openssl/obj.h>
 #include <openssl/stack.h>
 
+#include "../bytestring/internal.h"
+
 
 int PKCS7_get_certificates(STACK_OF(X509) *out_certs, CBS *cbs) {
-  CBS content_info, content_type, wrapped_signed_data, signed_data,
-      version_bytes, certificates;
-  int nid;
+  uint8_t *der_bytes = NULL;
+  size_t der_len;
+  CBS in, content_info, content_type, wrapped_signed_data, signed_data,
+      certificates;
   const size_t initial_certs_len = sk_X509_num(out_certs);
+  uint64_t version;
+  int ret = 0;
 
-  /* See https://tools.ietf.org/html/rfc2315#section-7 */
-  if (!CBS_get_asn1_ber(cbs, &content_info, CBS_ASN1_SEQUENCE) ||
-      !CBS_get_asn1(&content_info, &content_type, CBS_ASN1_OBJECT)) {
+  /* The input may be in BER format. */
+  if (!CBS_asn1_ber_to_der(cbs, &der_bytes, &der_len)) {
     return 0;
   }
+  if (der_bytes != NULL) {
+    CBS_init(&in, der_bytes, der_len);
+  } else {
+    CBS_init(&in, CBS_data(cbs), CBS_len(cbs));
+  }
 
-  nid = OBJ_cbs2nid(&content_type);
-  if (nid != NID_pkcs7_signed) {
+  /* See https://tools.ietf.org/html/rfc2315#section-7 */
+  if (!CBS_get_asn1(&in, &content_info, CBS_ASN1_SEQUENCE) ||
+      !CBS_get_asn1(&content_info, &content_type, CBS_ASN1_OBJECT)) {
+    goto err;
+  }
+
+  if (OBJ_cbs2nid(&content_type) != NID_pkcs7_signed) {
     OPENSSL_PUT_ERROR(X509, PKCS7_get_certificates,
                       X509_R_NOT_PKCS7_SIGNED_DATA);
-    return 0;
+    goto err;
   }
 
   /* See https://tools.ietf.org/html/rfc2315#section-9.1 */
-  if (!CBS_get_asn1_ber(&content_info, &wrapped_signed_data,
-                        CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) ||
-      !CBS_get_asn1_ber(&wrapped_signed_data, &signed_data,
-                        CBS_ASN1_SEQUENCE) ||
-      !CBS_get_asn1_ber(&signed_data, &version_bytes, CBS_ASN1_INTEGER) ||
-      !CBS_get_asn1_ber(&signed_data, NULL /* digests */, CBS_ASN1_SET) ||
-      !CBS_get_asn1_ber(&signed_data, NULL /* content */, CBS_ASN1_SEQUENCE)) {
-    return 0;
+  if (!CBS_get_asn1(&content_info, &wrapped_signed_data,
+                    CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) ||
+      !CBS_get_asn1(&wrapped_signed_data, &signed_data, CBS_ASN1_SEQUENCE) ||
+      !CBS_get_asn1_uint64(&signed_data, &version) ||
+      !CBS_get_asn1(&signed_data, NULL /* digests */, CBS_ASN1_SET) ||
+      !CBS_get_asn1(&signed_data, NULL /* content */, CBS_ASN1_SEQUENCE)) {
+    goto err;
   }
 
-  if (CBS_len(&version_bytes) < 1 || CBS_data(&version_bytes)[0] == 0) {
+  if (version < 1) {
     OPENSSL_PUT_ERROR(X509, PKCS7_get_certificates,
                       X509_R_BAD_PKCS7_VERSION);
-    return 0;
+    goto err;
   }
 
-  if (!CBS_get_asn1_ber(&signed_data, &certificates,
-                        CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0)) {
+  if (!CBS_get_asn1(&signed_data, &certificates,
+                    CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0)) {
     OPENSSL_PUT_ERROR(X509, PKCS7_get_certificates,
                       X509_R_NO_CERTIFICATES_INCLUDED);
-    return 0;
+    goto err;
   }
 
   while (CBS_len(&certificates) > 0) {
@@ -86,15 +99,21 @@
     sk_X509_push(out_certs, x509);
   }
 
-  return 1;
+  ret = 1;
 
 err:
-  while (sk_X509_num(out_certs) != initial_certs_len) {
-    X509 *x509 = sk_X509_pop(out_certs);
-    X509_free(x509);
+  if (der_bytes) {
+    OPENSSL_free(der_bytes);
   }
 
-  return 0;
+  if (!ret) {
+    while (sk_X509_num(out_certs) != initial_certs_len) {
+      X509 *x509 = sk_X509_pop(out_certs);
+      X509_free(x509);
+    }
+  }
+
+  return ret;
 }
 
 int PKCS7_bundle_certificates(CBB *out, const STACK_OF(X509) *certs) {
diff --git a/include/openssl/bytestring.h b/include/openssl/bytestring.h
index 6c0e799..a7507d8 100644
--- a/include/openssl/bytestring.h
+++ b/include/openssl/bytestring.h
@@ -138,20 +138,16 @@
  * Tag numbers greater than 31 are not supported. */
 OPENSSL_EXPORT int CBS_get_asn1(CBS *cbs, CBS *out, unsigned tag_value);
 
-/* CBS_get_asn1_ber sets |*out| to the contents of BER-encoded, ASN.1 element
- * (not including tag and length bytes) and advances |cbs| over it. The ASN.1
- * element must match |tag_value|. It returns one on success and zero on error.
- *
- * The major difference between this function and |CBS_get_asn1| is that
- * indefinite-length elements may be processed by this function.
- *
- * Tag numbers greater than 31 are not supported. */
-OPENSSL_EXPORT int CBS_get_asn1_ber(CBS *cbs, CBS *out, unsigned tag_value);
-
 /* CBS_get_asn1_element acts like |CBS_get_asn1| but |out| will include the
  * ASN.1 header bytes too. */
 OPENSSL_EXPORT int CBS_get_asn1_element(CBS *cbs, CBS *out, unsigned tag_value);
 
+/* CBS_get_asn1_uint64 gets an ASN.1 INTEGER from |cbs| using |CBS_get_asn1|
+ * and sets |*out| to its value. It returns one on success and zero on error,
+ * where error includes the integer being negative, or too large to represent
+ * in 64 bits. */
+OPENSSL_EXPORT int CBS_get_asn1_uint64(CBS *cbs, uint64_t *out);
+
 
 /* CRYPTO ByteBuilder.
  *