Allow for the path builder to limit the number of valid paths.

The existing behaviour of "explore_all_paths" is preserved with
the valid path limits of 0 (unlimited) and 1 (stop after first
valid path). This allows for a later change for portable verify
API to simply specify a desired limit.

SetExploreAllPaths could eventually be deprecated.

Bug: 660

Change-Id: I7a75acbfbee135a1368fe6716eb460af41cdb590
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/64727
Reviewed-by: David Benjamin <davidben@google.com>
Reviewed-by: Matt Mueller <mattm@google.com>
Commit-Queue: Bob Beck <bbe@google.com>
diff --git a/pki/path_builder.cc b/pki/path_builder.cc
index f89576e..6aa42e7 100644
--- a/pki/path_builder.cc
+++ b/pki/path_builder.cc
@@ -787,8 +787,12 @@
   max_path_building_depth_ = limit;
 }
 
+void CertPathBuilder::SetValidPathLimit(size_t limit) {
+  valid_path_limit_ = limit;
+}
+
 void CertPathBuilder::SetExploreAllPaths(bool explore_all_paths) {
-  explore_all_paths_ = explore_all_paths;
+  valid_path_limit_ = explore_all_paths ? 0 : 1;
 }
 
 CertPathBuilder::Result CertPathBuilder::Run() {
@@ -853,10 +857,13 @@
 
     AddResultPath(std::move(result_path));
 
-    if (path_is_good && !explore_all_paths_) {
-      out_result_.iteration_count = iteration_count;
-      // Found a valid path, return immediately.
-      return std::move(out_result_);
+    if (path_is_good) {
+      valid_path_count_++;
+      if (valid_path_limit_ > 0 && valid_path_count_ == valid_path_limit_) {
+        out_result_.iteration_count = iteration_count;
+        // Found enough paths, return immediately.
+        return std::move(out_result_);
+      }
     }
     // Path did not verify. Try more paths.
   }
diff --git a/pki/path_builder.h b/pki/path_builder.h
index 325477b..05999b0 100644
--- a/pki/path_builder.h
+++ b/pki/path_builder.h
@@ -213,10 +213,16 @@
   // to root. Setting |limit| to 0 disables this limit, which is the default.
   void SetDepthLimit(uint32_t limit);
 
-  // If |explore_all_paths| is false (the default), path building will stop as
-  // soon as a valid path is found. If |explore_all_paths| is true, path
-  // building will continue until all possible paths have been exhausted (or
-  // iteration limit / deadline is exceeded).
+  // Set the limit of valid paths returned by the path builder to |limit|.  If
+  // |limit| is non zero, path building will stop once |limit| valid paths have
+  // been found. Setting |limit| to 0 disables the limit, meaning path building
+  // will continue until all possible paths have been exhausted (or iteration
+  // limit / deadline is exceeded).  The default limit is 1.
+  void SetValidPathLimit(size_t limit);
+
+  // If |explore_all_paths| is false, this is equivalent to calling
+  // SetValidPathLimit(1). If |explore_all_paths| is true, this is equivalent to
+  // calling SetValidPathLimit(0).
   void SetExploreAllPaths(bool explore_all_paths);
 
   // Executes verification of the target certificate.
@@ -241,7 +247,8 @@
   const InitialAnyPolicyInhibit initial_any_policy_inhibit_;
   uint32_t max_iteration_count_ = 0;
   uint32_t max_path_building_depth_ = 0;
-  bool explore_all_paths_ = false;
+  size_t valid_path_limit_ = 1;
+  size_t valid_path_count_ = 0;
 };
 
 }  // namespace bssl
diff --git a/pki/path_builder_unittest.cc b/pki/path_builder_unittest.cc
index c76670d..d231358 100644
--- a/pki/path_builder_unittest.cc
+++ b/pki/path_builder_unittest.cc
@@ -1352,6 +1352,95 @@
   }
 }
 
+// Tests that when SetValidPathLimit is used path builder returns the number of
+// valid paths we expect before the valid path limit was reached.
+TEST_F(PathBuilderKeyRolloverTest, ExplorePathsWithPathLimit) {
+  struct Expectation {
+    size_t valid_path_limit;
+    size_t expected_num_paths;
+  } kExpectations[] = {
+      {0, 4},  // No path limit. Three valid, one partial path should be built
+      {1, 1},  // One valid path
+      {2, 3},  // Two valid, one partial
+      {3, 4},
+      {4, 4},
+      {5, 4},
+  };
+
+  // Trust both old and new roots.
+  TrustStoreInMemory trust_store;
+  trust_store.AddTrustAnchor(oldroot_);
+  trust_store.AddTrustAnchor(newroot_);
+
+  // Intermediates and root rollover are all provided synchronously.
+  CertIssuerSourceStatic sync_certs;
+  sync_certs.AddCert(oldintermediate_);
+  sync_certs.AddCert(newintermediate_);
+
+  for (const auto &expectation : kExpectations) {
+    SCOPED_TRACE(expectation.valid_path_limit);
+
+    CertPathBuilder path_builder(
+        target_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU,
+        initial_explicit_policy_, user_initial_policy_set_,
+        initial_policy_mapping_inhibit_, initial_any_policy_inhibit_);
+    path_builder.AddCertIssuerSource(&sync_certs);
+
+    // Stop after finding enough valid paths.
+    path_builder.SetValidPathLimit(expectation.valid_path_limit);
+
+    auto result = path_builder.Run();
+
+    EXPECT_TRUE(result.HasValidPath());
+    ASSERT_EQ(expectation.expected_num_paths, result.paths.size());
+
+    if (result.paths.size() > 0) {
+      // Path builder will first build path: target <- newintermediate <-
+      // newroot
+      const auto &path0 = *result.paths[0];
+      EXPECT_TRUE(path0.IsValid());
+      ASSERT_EQ(3U, path0.certs.size());
+      EXPECT_EQ(target_, path0.certs[0]);
+      EXPECT_EQ(newintermediate_, path0.certs[1]);
+      EXPECT_EQ(newroot_, path0.certs[2]);
+      EXPECT_EQ(3U, result.max_depth_seen);
+    }
+
+    if (result.paths.size() > 1) {
+      // Next path:  target <- newintermediate <- oldroot
+      const auto &path1 = *result.paths[1];
+      EXPECT_FALSE(path1.IsValid());
+      ASSERT_EQ(3U, path1.certs.size());
+      EXPECT_EQ(target_, path1.certs[0]);
+      EXPECT_EQ(newintermediate_, path1.certs[1]);
+      EXPECT_EQ(oldroot_, path1.certs[2]);
+      EXPECT_EQ(3U, result.max_depth_seen);
+    }
+
+    if (result.paths.size() > 2) {
+      // Next path:  target <- oldintermediate <- oldroot
+      const auto &path2 = *result.paths[2];
+      EXPECT_TRUE(path2.IsValid());
+      ASSERT_EQ(3U, path2.certs.size());
+      EXPECT_EQ(target_, path2.certs[0]);
+      EXPECT_EQ(oldintermediate_, path2.certs[1]);
+      EXPECT_EQ(oldroot_, path2.certs[2]);
+      EXPECT_EQ(3U, result.max_depth_seen);
+    }
+
+    if (result.paths.size() > 3) {
+      // Final path:  target <- oldintermediate <- newroot
+      const auto &path3 = *result.paths[3];
+      EXPECT_FALSE(path3.IsValid());
+      ASSERT_EQ(3U, path3.certs.size());
+      EXPECT_EQ(target_, path3.certs[0]);
+      EXPECT_EQ(oldintermediate_, path3.certs[1]);
+      EXPECT_EQ(newroot_, path3.certs[2]);
+      EXPECT_EQ(3U, result.max_depth_seen);
+    }
+  }
+}
+
 // If the target cert is a trust anchor, however is not itself *signed* by a
 // trust anchor, then it is not considered valid (the SPKI and name of the
 // trust anchor matches the SPKI and subject of the targe certificate, but the
@@ -2262,7 +2351,7 @@
         target, &trust_store, &delegate, verify_time, KeyPurpose::ANY_EKU,
         InitialExplicitPolicy::kFalse, {der::Input(kAnyPolicyOid)},
         InitialPolicyMappingInhibit::kFalse, InitialAnyPolicyInhibit::kFalse);
-    path_builder.SetExploreAllPaths(true);
+    path_builder.SetValidPathLimit(0);
 
     CertPathBuilder::Result result = path_builder.Run();
     EXPECT_TRUE(result.HasValidPath());
@@ -2438,7 +2527,7 @@
       target, &trust_store, &delegate, verify_time, KeyPurpose::ANY_EKU,
       InitialExplicitPolicy::kFalse, {der::Input(kAnyPolicyOid)},
       InitialPolicyMappingInhibit::kFalse, InitialAnyPolicyInhibit::kFalse);
-  path_builder.SetExploreAllPaths(true);
+  path_builder.SetValidPathLimit(0);
 
   CertPathBuilder::Result result = path_builder.Run();
   EXPECT_TRUE(result.HasValidPath());