crypto/x509: Fix handling of pathLenConstraint on self-issued intermediates.

The test case that fails before this CL is:

```
  EXPECT_EQ(X509_V_ERR_PATH_LENGTH_EXCEEDED,
            VerifyChain({{/*self_issued=*/false, /*pathlen=/0},
                         {/*self_issued=*/false, /*pathlen=/0},
                         {/*self_issued=*/true, /*pathlen=/0},
                         {/*self_issued=*/false, /*pathlen=/1},
                         {/*self_issued=*/true, /*pathlen=/2}}));
```

as the path length of the migrating-to (and self-issued) intermediate
certificate was ignored.

Bug: 525690632
Change-Id: Id5d74cc3f93156d40db61dc83433e4dc6a6a6964
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/95067
Commit-Queue: Rudolf Polzer <rpolzer@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc
index 2401c45..4e04c77 100644
--- a/crypto/x509/x509_test.cc
+++ b/crypto/x509/x509_test.cc
@@ -16,6 +16,7 @@
 
 #include <algorithm>
 #include <functional>
+#include <initializer_list>
 #include <iterator>
 #include <memory>
 #include <string>
@@ -2036,9 +2037,9 @@
   return name;
 }
 
-static bssl::UniquePtr<X509> MakeTestCert(std::string_view issuer,
-                                          std::string_view subject,
-                                          EVP_PKEY *key, bool is_ca) {
+static bssl::UniquePtr<X509> MakeTestCert(
+    std::string_view issuer, std::string_view subject, EVP_PKEY *key,
+    bool is_ca, std::optional<int64_t> pathlen = std::nullopt) {
   UniquePtr<X509_NAME> issuer_name = MakeTestName(issuer);
   UniquePtr<X509_NAME> subject_name = MakeTestName(subject);
   UniquePtr<X509> cert(X509_new());
@@ -2060,6 +2061,13 @@
     return nullptr;
   }
   bc->ca = is_ca ? ASN1_BOOLEAN_TRUE : ASN1_BOOLEAN_FALSE;
+  if (pathlen.has_value()) {
+    bc->pathlen = ASN1_INTEGER_new();
+    if (bc->pathlen == nullptr ||
+        !ASN1_INTEGER_set_int64(bc->pathlen, *pathlen)) {
+      return nullptr;
+    }
+  }
   if (!X509_add1_ext_i2d(cert.get(), NID_basic_constraints, bc.get(),
                          /*crit=*/1, /*flags=*/0)) {
     return nullptr;
@@ -10440,5 +10448,133 @@
   EXPECT_EQ(actual, expected);
 }
 
+struct CertChainItem {
+  bool self_issued;
+  std::optional<int64_t> pathlen;
+};
+
+static std::optional<int> VerifyChain(
+    std::initializer_list<CertChainItem> items) {
+  std::vector<bssl::UniquePtr<X509>> intermediates;
+
+  std::vector<UniquePtr<EVP_PKEY>> keys;
+  for (size_t k = 0; k < items.size(); ++k) {
+    UniquePtr<EVP_PKEY> key(EVP_PKEY_generate_from_alg(EVP_pkey_ec_p256()));
+    if (key == nullptr) {
+      return std::nullopt;
+    }
+    keys.push_back(std::move(key));
+  }
+
+  bssl::UniquePtr<X509> leaf = nullptr;
+
+  size_t name_idx = 0;
+  size_t key_idx = 0;
+  for (const auto &item : items) {
+    std::string subject_name = std::to_string(name_idx);
+    if (!item.self_issued) {
+      ++name_idx;
+    }
+    std::string issuer_name = std::to_string(name_idx);
+
+    size_t subject_key_idx = key_idx;
+    if (key_idx < items.size() - 1) {
+      ++key_idx;
+    }
+    size_t issuer_key_idx = key_idx;
+
+    EVP_PKEY *subject_key = keys[subject_key_idx].get();
+    EVP_PKEY *issuer_key = keys[issuer_key_idx].get();
+
+    bssl::UniquePtr<X509> cert(MakeTestCert(
+        issuer_name, subject_name, subject_key, /*is_ca=*/true, item.pathlen));
+    uint8_t skid = static_cast<uint8_t>(subject_key_idx);
+    uint8_t akid = static_cast<uint8_t>(issuer_key_idx);
+    if (cert == nullptr || !AddSubjectKeyIdentifier(cert.get(), {&skid, 1}) ||
+        !AddAuthorityKeyIdentifier(cert.get(), {&akid, 1}) ||
+        !X509_sign(cert.get(), issuer_key, EVP_sha256())) {
+      return std::nullopt;
+    }
+    if (leaf == nullptr) {
+      // The first cert in the list is the leaf.
+      leaf = std::move(cert);
+    } else {
+      // All else gets into the chain for now.
+      intermediates.emplace_back(std::move(cert));
+    }
+  }
+
+  // The last cert shall be considered the root.
+  bssl::UniquePtr<X509> root = std::move(intermediates.back());
+  intermediates.pop_back();
+
+  std::vector<X509 *> intermediate_ptrs = {};
+  for (const auto &intermediate : intermediates) {
+    intermediate_ptrs.push_back(intermediate.get());
+  }
+  return Verify(leaf.get(), {root.get()}, intermediate_ptrs, {}, /*flags=*/0);
+}
+
+TEST(X509Test, PathLenNormalUnconstrained) {
+  EXPECT_EQ(X509_V_OK,
+            VerifyChain({{/*self_issued=*/false, /*pathlen=*/std::nullopt},
+                         {/*self_issued=*/false, /*pathlen=*/std::nullopt},
+                         {/*self_issued=*/false, /*pathlen=*/std::nullopt},
+                         {/*self_issued=*/false, /*pathlen=*/std::nullopt},
+                         {/*self_issued=*/true, /*pathlen=*/std::nullopt}}));
+}
+
+TEST(X509Test, PathLenNormal) {
+  EXPECT_EQ(X509_V_OK, VerifyChain({{/*self_issued=*/false, /*pathlen=*/0},
+                                    {/*self_issued=*/false, /*pathlen=*/0},
+                                    {/*self_issued=*/false, /*pathlen=*/1},
+                                    {/*self_issued=*/false, /*pathlen=*/2},
+                                    {/*self_issued=*/true, /*pathlen=*/3}}));
+  EXPECT_EQ(X509_V_ERR_PATH_LENGTH_EXCEEDED,
+            VerifyChain({{/*self_issued=*/false, /*pathlen=*/0},
+                         {/*self_issued=*/false, /*pathlen=*/0},
+                         {/*self_issued=*/false, /*pathlen=*/0},
+                         {/*self_issued=*/false, /*pathlen=*/2},
+                         {/*self_issued=*/true, /*pathlen=*/3}}));
+  EXPECT_EQ(X509_V_ERR_PATH_LENGTH_EXCEEDED,
+            VerifyChain({{/*self_issued=*/false, /*pathlen=*/0},
+                         {/*self_issued=*/false, /*pathlen=*/0},
+                         {/*self_issued=*/false, /*pathlen=*/1},
+                         {/*self_issued=*/false, /*pathlen=*/1},
+                         {/*self_issued=*/true, /*pathlen=*/3}}));
+  EXPECT_EQ(X509_V_OK,  // Path length on trust anchor is ignored.
+            VerifyChain({{/*self_issued=*/false, /*pathlen=*/0},
+                         {/*self_issued=*/false, /*pathlen=*/0},
+                         {/*self_issued=*/false, /*pathlen=*/1},
+                         {/*self_issued=*/false, /*pathlen=*/2},
+                         {/*self_issued=*/true, /*pathlen=*/2}}));
+}
+
+TEST(X509Test, PathLenSelfIssuedNotCountedButStillVerified) {
+  EXPECT_EQ(X509_V_OK, VerifyChain({{/*self_issued=*/false, /*pathlen=*/0},
+                                    {/*self_issued=*/false, /*pathlen=*/0},
+                                    {/*self_issued=*/true, /*pathlen=*/1},
+                                    {/*self_issued=*/false, /*pathlen=*/1},
+                                    {/*self_issued=*/true, /*pathlen=*/2}}));
+  EXPECT_EQ(X509_V_ERR_PATH_LENGTH_EXCEEDED,
+            VerifyChain({{/*self_issued=*/false, /*pathlen=*/0},
+                         {/*self_issued=*/false, /*pathlen=*/0},
+                         {/*self_issued=*/true, /*pathlen=*/0},
+                         {/*self_issued=*/false, /*pathlen=*/1},
+                         {/*self_issued=*/true, /*pathlen=*/2}}));
+  EXPECT_EQ(X509_V_ERR_PATH_LENGTH_EXCEEDED,
+            VerifyChain({{/*self_issued=*/false, /*pathlen=*/0},
+                         {/*self_issued=*/false, /*pathlen=*/0},
+                         {/*self_issued=*/true, /*pathlen=*/1},
+                         {/*self_issued=*/false, /*pathlen=*/0},
+                         {/*self_issued=*/true, /*pathlen=*/2}}));
+  EXPECT_EQ(X509_V_OK,  // Path length on trust anchor is ignored.
+            VerifyChain({{/*self_issued=*/false, /*pathlen=*/0},
+                         {/*self_issued=*/false, /*pathlen=*/0},
+                         {/*self_issued=*/true, /*pathlen=*/1},
+                         {/*self_issued=*/false, /*pathlen=*/1},
+                         {/*self_issued=*/true, /*pathlen=*/1}}));
+}
+
 }  // namespace
 BSSL_NAMESPACE_END
diff --git a/crypto/x509/x509_vfy.cc b/crypto/x509/x509_vfy.cc
index 5405038..16625ba 100644
--- a/crypto/x509/x509_vfy.cc
+++ b/crypto/x509/x509_vfy.cc
@@ -499,9 +499,16 @@
         return 0;
       }
     }
-    // Check pathlen if not self issued
-    if (i > 1 && !(x->ex_flags & EXFLAG_SI) && x->ex_pathlen != -1 &&
-        plen > x->ex_pathlen + 1) {
+    // Check path length constraints. See steps (l) and (m) of RFC 5280,
+    // section 6.1.4. Note the spec is structured differently from this
+    // logic. Section 6.1.4 runs from root to leaf and does not run on
+    // the leaf. `plen` counts the number of times step (l) would have
+    // run. The constraint is violated if some `x->ex_pathlen`, read in
+    // step (m), is too low to be decremented `plen` times.
+    //
+    // Note that path lengths of self-issued certificates still have to be
+    // considered - they are just not counted as part of the path length!
+    if (i > 1 && x->ex_pathlen != -1 && plen > x->ex_pathlen + 1) {
       ctx->error = X509_V_ERR_PATH_LENGTH_EXCEEDED;
       ctx->error_depth = i;
       ctx->current_cert = x;
@@ -509,8 +516,11 @@
         return 0;
       }
     }
-    // Increment path length if not self issued
-    if (!(x->ex_flags & EXFLAG_SI)) {
+    // Increment path length if not self issued. As only self-issued
+    // _intermediates_ are skipped in (l) of RFC 5280 (simply because it
+    // operates on certificate chain _edges_), always increment for the first
+    // (the leaf) in the chain.
+    if (i == 0 || !(x->ex_flags & EXFLAG_SI)) {
       plen++;
     }
   }