Fix a bug detecting BER deeply nested inside DER

When BER was nested deep inside otherwise valid DER, cbs_find_ber's
internally book-keeping would clobber *out_ber_found and lose track that
there was some BER to convert.

This is unlikely to come up in real world inputs, as the only reason to
use any of these awful BER features is to stream the output. I.e., there
is no reason to have indefinite-length inside definite-length, and there
is no reason to have definite-length constructed string. That means
cbs_find_ber, realistically, can just check for indefinite-length
encoding at the front and do nothing else.

Nonetheless, these inputs are nominally allowed, so fix the book-keeping
bug.

Change-Id: Idaf3c178fa511ce24af83d6ae27fa9f197f1789e
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/65507
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Bob Beck <bbe@google.com>
Commit-Queue: Bob Beck <bbe@google.com>
Auto-Submit: David Benjamin <davidben@google.com>
diff --git a/crypto/bytestring/ber.c b/crypto/bytestring/ber.c
index 5d43d0b..522174b 100644
--- a/crypto/bytestring/ber.c
+++ b/crypto/bytestring/ber.c
@@ -56,13 +56,11 @@
 // found. The value of |orig_in| is not changed. It returns one on success (i.e.
 // |*ber_found| was set) and zero on error.
 static int cbs_find_ber(const CBS *orig_in, int *ber_found, uint32_t depth) {
-  CBS in;
-
   if (depth > kMaxDepth) {
     return 0;
   }
 
-  CBS_init(&in, CBS_data(orig_in), CBS_len(orig_in));
+  CBS in = *orig_in;
   *ber_found = 0;
 
   while (CBS_len(&in) > 0) {
@@ -87,6 +85,10 @@
           !cbs_find_ber(&contents, ber_found, depth + 1)) {
         return 0;
       }
+      if (*ber_found) {
+        // We already found BER. No need to continue parsing.
+        return 1;
+      }
     }
   }
 
diff --git a/crypto/bytestring/bytestring_test.cc b/crypto/bytestring/bytestring_test.cc
index 08cfb87..deae6bb 100644
--- a/crypto/bytestring/bytestring_test.cc
+++ b/crypto/bytestring/bytestring_test.cc
@@ -679,6 +679,38 @@
       0xa0, 0x08, 0x04, 0x02, 0x00, 0x01, 0x04, 0x02, 0x02, 0x03,
   };
 
+  // kWrappedIndefBER contains indefinite-length SEQUENCE, wrapped
+  // and followed by valid DER. This tests that we correctly identify BER nested
+  // inside DER.
+  //
+  //  SEQUENCE {
+  //    SEQUENCE {
+  //      SEQUENCE indefinite {}
+  //    }
+  //    SEQUENCE {}
+  //  }
+  static const uint8_t kWrappedIndefBER[] = {0x30, 0x08, 0x30, 0x04, 0x30,
+                                             0x80, 0x00, 0x00, 0x30, 0x00};
+  static const uint8_t kWrappedIndefDER[] = {0x30, 0x06, 0x30, 0x02,
+                                             0x30, 0x00, 0x30, 0x00};
+
+  // kWrappedConstructedStringBER contains a constructed OCTET STRING, wrapped
+  // and followed by valid DER. This tests that we correctly identify BER nested
+  // inside DER.
+  //
+  //  SEQUENCE {
+  //    SEQUENCE {
+  //      [OCTET_STRING CONSTRUCTED] {
+  //        OCTET_STRING {}
+  //      }
+  //    }
+  //    SEQUENCE {}
+  //  }
+  static const uint8_t kWrappedConstructedStringBER[] = {
+      0x30, 0x08, 0x30, 0x04, 0x24, 0x02, 0x04, 0x00, 0x30, 0x00};
+  static const uint8_t kWrappedConstructedStringDER[] = {
+      0x30, 0x06, 0x30, 0x02, 0x04, 0x00, 0x30, 0x00};
+
   // kConstructedBitString contains a BER constructed BIT STRING. These are not
   // supported and thus are left unchanged.
   static const uint8_t kConstructedBitStringBER[] = {
@@ -695,6 +727,9 @@
                    kConstructedStringBER);
   ExpectBerConvert("kConstructedBitStringBER", kConstructedBitStringBER,
                    kConstructedBitStringBER);
+  ExpectBerConvert("kWrappedIndefBER", kWrappedIndefDER, kWrappedIndefBER);
+  ExpectBerConvert("kWrappedConstructedStringBER", kWrappedConstructedStringDER,
+                   kWrappedConstructedStringBER);
 }
 
 struct BERTest {
diff --git a/crypto/pkcs8/pkcs12_test.cc b/crypto/pkcs8/pkcs12_test.cc
index 79ebae3..50cedff 100644
--- a/crypto/pkcs8/pkcs12_test.cc
+++ b/crypto/pkcs8/pkcs12_test.cc
@@ -151,6 +151,13 @@
   TestImpl("EmptyPassword (empty password)", StringToBytes(data), "", nullptr);
   TestImpl("EmptyPassword (null password)", StringToBytes(data), nullptr,
            nullptr);
+
+  // The above input, modified to have a constructed string.
+  data = GetTestData("crypto/pkcs8/test/empty_password_ber.p12");
+  TestImpl("EmptyPassword (BER, empty password)", StringToBytes(data), "",
+           nullptr);
+  TestImpl("EmptyPassword (BER, null password)", StringToBytes(data), nullptr,
+           nullptr);
 }
 
 TEST(PKCS12Test, TestNullPassword) {
diff --git a/crypto/pkcs8/test/empty_password_ber.p12 b/crypto/pkcs8/test/empty_password_ber.p12
new file mode 100644
index 0000000..3a87ebb
--- /dev/null
+++ b/crypto/pkcs8/test/empty_password_ber.p12
Binary files differ
diff --git a/sources.cmake b/sources.cmake
index 5cf324b..b6206bf 100644
--- a/sources.cmake
+++ b/sources.cmake
@@ -147,6 +147,7 @@
   crypto/keccak/keccak_tests.txt
   crypto/kyber/kyber_tests.txt
   crypto/pkcs8/test/empty_password.p12
+  crypto/pkcs8/test/empty_password_ber.p12
   crypto/pkcs8/test/no_encryption.p12
   crypto/pkcs8/test/nss.p12
   crypto/pkcs8/test/null_password.p12