Add X509_get_pathlen and X509_REVOKED_get0_extensions.

Conscrypt will need these functions. Also fix a bug in
X509_get_extension_flags's error-handling. While I'm here, add
X509_CRL_get0_extensions for completeness. Nothing uses this yet, but
this could later be an alternative to avoid Conscrypt's mess with
templates.

Change-Id: I9393b75fcf53346535e6a4712355be081baa630d
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/42744
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/crypto/x509/test/basic_constraints_ca.pem b/crypto/x509/test/basic_constraints_ca.pem
new file mode 100644
index 0000000..50d1318
--- /dev/null
+++ b/crypto/x509/test/basic_constraints_ca.pem
@@ -0,0 +1,9 @@
+-----BEGIN CERTIFICATE-----
+MIIBOzCB4qADAgECAgEBMAoGCCqGSM49BAMCMBwxGjAYBgNVBAMTEUJhc2ljIENv
+bnN0cmFpbnRzMCAXDTAwMDEwMTAwMDAwMFoYDzIxMDAwMTAxMDAwMDAwWjAcMRow
+GAYDVQQDExFCYXNpYyBDb25zdHJhaW50czBZMBMGByqGSM49AgEGCCqGSM49AwEH
+A0IABJEq2LxVbZGSZr4q32NCQw2K2UKzSXnDy7dJLCbsdlES+ZwEIkGNUhERpxGo
+jS6aHNHZXk0vMEE/3I8P8D4KHlejEzARMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZI
+zj0EAwIDSAAwRQIgTNs2aQPDZs+Pal5LA1fAKyC4AKTNN+JE/vEYndKhFxYCIQDf
+b7IjDoXx/3GBnsrht14NUmzUBdqkQafJvC+eHIdtQA==
+-----END CERTIFICATE-----
diff --git a/crypto/x509/test/basic_constraints_ca_pathlen_0.pem b/crypto/x509/test/basic_constraints_ca_pathlen_0.pem
new file mode 100644
index 0000000..2d66801
--- /dev/null
+++ b/crypto/x509/test/basic_constraints_ca_pathlen_0.pem
@@ -0,0 +1,9 @@
+-----BEGIN CERTIFICATE-----
+MIIBPjCB5aADAgECAgEBMAoGCCqGSM49BAMCMBwxGjAYBgNVBAMTEUJhc2ljIENv
+bnN0cmFpbnRzMCAXDTAwMDEwMTAwMDAwMFoYDzIxMDAwMTAxMDAwMDAwWjAcMRow
+GAYDVQQDExFCYXNpYyBDb25zdHJhaW50czBZMBMGByqGSM49AgEGCCqGSM49AwEH
+A0IABJEq2LxVbZGSZr4q32NCQw2K2UKzSXnDy7dJLCbsdlES+ZwEIkGNUhERpxGo
+jS6aHNHZXk0vMEE/3I8P8D4KHlejFjAUMBIGA1UdEwEB/wQIMAYBAf8CAQAwCgYI
+KoZIzj0EAwIDSAAwRQIgHdMalNLi3hzz58PdNQPAqiA5KAa/dfQWuNNjzE6iDIcC
+IQCda6js7OKQvdqCFb/POHPriXX1YXIJ3N95+SE7qFJ9Gg==
+-----END CERTIFICATE-----
diff --git a/crypto/x509/test/basic_constraints_ca_pathlen_1.pem b/crypto/x509/test/basic_constraints_ca_pathlen_1.pem
new file mode 100644
index 0000000..b9d35d0
--- /dev/null
+++ b/crypto/x509/test/basic_constraints_ca_pathlen_1.pem
@@ -0,0 +1,9 @@
+-----BEGIN CERTIFICATE-----
+MIIBPjCB5aADAgECAgEBMAoGCCqGSM49BAMCMBwxGjAYBgNVBAMTEUJhc2ljIENv
+bnN0cmFpbnRzMCAXDTAwMDEwMTAwMDAwMFoYDzIxMDAwMTAxMDAwMDAwWjAcMRow
+GAYDVQQDExFCYXNpYyBDb25zdHJhaW50czBZMBMGByqGSM49AgEGCCqGSM49AwEH
+A0IABJEq2LxVbZGSZr4q32NCQw2K2UKzSXnDy7dJLCbsdlES+ZwEIkGNUhERpxGo
+jS6aHNHZXk0vMEE/3I8P8D4KHlejFjAUMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI
+KoZIzj0EAwIDSAAwRQIgZx7fIDI65CU7Lck0t7ep/GtBkpELR0gKkUJrI09/JJoC
+IQDFPukkJgYA7RpFsAsEq77S+i9gf/S/IreobhvQm/401w==
+-----END CERTIFICATE-----
diff --git a/crypto/x509/test/basic_constraints_ca_pathlen_10.pem b/crypto/x509/test/basic_constraints_ca_pathlen_10.pem
new file mode 100644
index 0000000..c4698a6
--- /dev/null
+++ b/crypto/x509/test/basic_constraints_ca_pathlen_10.pem
@@ -0,0 +1,9 @@
+-----BEGIN CERTIFICATE-----
+MIIBPjCB5aADAgECAgEBMAoGCCqGSM49BAMCMBwxGjAYBgNVBAMTEUJhc2ljIENv
+bnN0cmFpbnRzMCAXDTAwMDEwMTAwMDAwMFoYDzIxMDAwMTAxMDAwMDAwWjAcMRow
+GAYDVQQDExFCYXNpYyBDb25zdHJhaW50czBZMBMGByqGSM49AgEGCCqGSM49AwEH
+A0IABJEq2LxVbZGSZr4q32NCQw2K2UKzSXnDy7dJLCbsdlES+ZwEIkGNUhERpxGo
+jS6aHNHZXk0vMEE/3I8P8D4KHlejFjAUMBIGA1UdEwEB/wQIMAYBAf8CAQowCgYI
+KoZIzj0EAwIDSAAwRQIhALj37ijrYfommrWjrXMXjJyILvGNH7KxViKU1cWjX5dF
+AiA6WjePmZdKilZebpZ++MTPs5cbpdcShWYuJ45sANCKgw==
+-----END CERTIFICATE-----
diff --git a/crypto/x509/test/basic_constraints_leaf.pem b/crypto/x509/test/basic_constraints_leaf.pem
new file mode 100644
index 0000000..4b9b8c2
--- /dev/null
+++ b/crypto/x509/test/basic_constraints_leaf.pem
@@ -0,0 +1,9 @@
+-----BEGIN CERTIFICATE-----
+MIIBOTCB36ADAgECAgEBMAoGCCqGSM49BAMCMBwxGjAYBgNVBAMTEUJhc2ljIENv
+bnN0cmFpbnRzMCAXDTAwMDEwMTAwMDAwMFoYDzIxMDAwMTAxMDAwMDAwWjAcMRow
+GAYDVQQDExFCYXNpYyBDb25zdHJhaW50czBZMBMGByqGSM49AgEGCCqGSM49AwEH
+A0IABJEq2LxVbZGSZr4q32NCQw2K2UKzSXnDy7dJLCbsdlES+ZwEIkGNUhERpxGo
+jS6aHNHZXk0vMEE/3I8P8D4KHlejEDAOMAwGA1UdEwEB/wQCMAAwCgYIKoZIzj0E
+AwIDSQAwRgIhAIc3Cbr1SRZZ8ZusjOQjA/9Ro5ijEZbMaD1ClW62/GqSAiEAy1tU
+No3zRwTUcuyAnav+XbXkS1a5Fm2/rFBoWN8ZAxA=
+-----END CERTIFICATE-----
diff --git a/crypto/x509/test/basic_constraints_none.pem b/crypto/x509/test/basic_constraints_none.pem
new file mode 100644
index 0000000..1228961
--- /dev/null
+++ b/crypto/x509/test/basic_constraints_none.pem
@@ -0,0 +1,9 @@
+-----BEGIN CERTIFICATE-----
+MIIBKjCB0aADAgECAgEBMAoGCCqGSM49BAMCMBwxGjAYBgNVBAMTEUJhc2ljIENv
+bnN0cmFpbnRzMCAXDTAwMDEwMTAwMDAwMFoYDzIxMDAwMTAxMDAwMDAwWjAcMRow
+GAYDVQQDExFCYXNpYyBDb25zdHJhaW50czBZMBMGByqGSM49AgEGCCqGSM49AwEH
+A0IABJEq2LxVbZGSZr4q32NCQw2K2UKzSXnDy7dJLCbsdlES+ZwEIkGNUhERpxGo
+jS6aHNHZXk0vMEE/3I8P8D4KHlejAjAAMAoGCCqGSM49BAMCA0gAMEUCIQCQ1/Ca
+RanCM+PIUqVkCpfumEeLKawHMYIA2ZM3Yy2wngIgZg10Sd25/POZKIXlMAiwlDrM
+UQcfzZiBh8T5JEWKeRc=
+-----END CERTIFICATE-----
diff --git a/crypto/x509/test/make_basic_constraints.go b/crypto/x509/test/make_basic_constraints.go
new file mode 100644
index 0000000..23158b5
--- /dev/null
+++ b/crypto/x509/test/make_basic_constraints.go
@@ -0,0 +1,100 @@
+/* Copyright (c) 2020, 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. */
+
+// make_basic_constraints.go generates self-signed certificates with the basic
+// constraints extension.
+package main
+
+import (
+	"crypto/ecdsa"
+	"crypto/rand"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/pem"
+	"fmt"
+	"io/ioutil"
+	"math/big"
+	"time"
+)
+
+func main() {
+	key := ecdsaKeyFromPEMOrPanic(keyPEM)
+
+	notBefore, err := time.Parse(time.RFC3339, "2000-01-01T00:00:00Z")
+	if err != nil {
+		panic(err)
+	}
+	notAfter, err := time.Parse(time.RFC3339, "2100-01-01T00:00:00Z")
+	if err != nil {
+		panic(err)
+	}
+
+	baseTemplate := x509.Certificate{
+		SerialNumber:       new(big.Int).SetInt64(1),
+		Subject:            pkix.Name{CommonName: "Basic Constraints"},
+		NotBefore:          notBefore,
+		NotAfter:           notAfter,
+		SignatureAlgorithm: x509.ECDSAWithSHA256,
+	}
+
+	certs := []struct {
+		name                  string
+		basicConstraintsValid bool
+		isCA                  bool
+		maxPathLen            int
+		maxPathLenZero        bool
+	}{
+		{name: "none"},
+		{name: "leaf", basicConstraintsValid: true},
+		{name: "ca", basicConstraintsValid: true, isCA: true},
+		{name: "ca_pathlen_0", basicConstraintsValid: true, isCA: true, maxPathLenZero: true},
+		{name: "ca_pathlen_1", basicConstraintsValid: true, isCA: true, maxPathLen: 1},
+		{name: "ca_pathlen_10", basicConstraintsValid: true, isCA: true, maxPathLen: 10},
+	}
+	for _, cert := range certs {
+		template := baseTemplate
+		template.BasicConstraintsValid = cert.basicConstraintsValid
+		template.IsCA = cert.isCA
+		template.MaxPathLen = cert.maxPathLen
+		template.MaxPathLenZero = cert.maxPathLenZero
+
+		certBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
+		if err != nil {
+			panic(err)
+		}
+
+		certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes})
+		if err := ioutil.WriteFile(fmt.Sprintf("basic_constraints_%s.pem", cert.name), certPEM, 0666); err != nil {
+			panic(err)
+		}
+	}
+}
+
+const keyPEM = `-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgoPUXNXuH9mgiS/nk
+024SYxryxMa3CyGJldiHymLxSquhRANCAASRKti8VW2Rkma+Kt9jQkMNitlCs0l5
+w8u3SSwm7HZREvmcBCJBjVIREacRqI0umhzR2V5NLzBBP9yPD/A+Ch5X
+-----END PRIVATE KEY-----`
+
+func ecdsaKeyFromPEMOrPanic(in string) *ecdsa.PrivateKey {
+	keyBlock, _ := pem.Decode([]byte(in))
+	if keyBlock == nil || keyBlock.Type != "PRIVATE KEY" {
+		panic("could not decode private key")
+	}
+	key, err := x509.ParsePKCS8PrivateKey(keyBlock.Bytes)
+	if err != nil {
+		panic(err)
+	}
+	return key.(*ecdsa.PrivateKey)
+}
diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc
index 426e181..599abf5 100644
--- a/crypto/x509/x509_test.cc
+++ b/crypto/x509/x509_test.cc
@@ -2455,3 +2455,33 @@
   ASSERT_TRUE(ctx);
   EXPECT_FALSE(X509_STORE_CTX_init(ctx.get(), nullptr, leaf.get(), nullptr));
 }
+
+TEST(X509Test, BasicConstraints) {
+  const uint32_t kFlagMask = EXFLAG_CA | EXFLAG_BCONS | EXFLAG_INVALID;
+
+  static const struct {
+    const char *file;
+    uint32_t flags;
+    int path_len;
+  } kTests[] = {
+      {"basic_constraints_none.pem", 0, -1},
+      {"basic_constraints_ca.pem", EXFLAG_CA | EXFLAG_BCONS, -1},
+      {"basic_constraints_ca_pathlen_0.pem", EXFLAG_CA | EXFLAG_BCONS, 0},
+      {"basic_constraints_ca_pathlen_1.pem", EXFLAG_CA | EXFLAG_BCONS, 1},
+      {"basic_constraints_ca_pathlen_10.pem", EXFLAG_CA | EXFLAG_BCONS, 10},
+      {"basic_constraints_leaf.pem", EXFLAG_BCONS, -1},
+      {"invalid_extension_leaf_basic_constraints.pem", EXFLAG_INVALID, -1},
+  };
+
+  for (const auto &test : kTests) {
+    SCOPED_TRACE(test.file);
+
+    std::string path = "crypto/x509/test/";
+    path += test.file;
+
+    bssl::UniquePtr<X509> cert = CertFromPEM(GetTestData(path.c_str()).c_str());
+    ASSERT_TRUE(cert);
+    EXPECT_EQ(test.flags, X509_get_extension_flags(cert.get()) & kFlagMask);
+    EXPECT_EQ(test.path_len, X509_get_pathlen(cert.get()));
+  }
+}
diff --git a/crypto/x509/x509cset.c b/crypto/x509/x509cset.c
index ba868b3..b07ff27 100644
--- a/crypto/x509/x509cset.c
+++ b/crypto/x509/x509cset.c
@@ -170,6 +170,11 @@
     return crl->crl->revoked;
 }
 
+const STACK_OF(X509_EXTENSION) *X509_CRL_get0_extensions(const X509_CRL *crl)
+{
+    return crl->crl->extensions;
+}
+
 void X509_CRL_get0_signature(const X509_CRL *crl, const ASN1_BIT_STRING **psig,
                              const X509_ALGOR **palg)
 {
@@ -228,6 +233,12 @@
     return (in != NULL);
 }
 
+const STACK_OF(X509_EXTENSION) *
+    X509_REVOKED_get0_extensions(const X509_REVOKED *r)
+{
+    return r->extensions;
+}
+
 int i2d_re_X509_CRL_tbs(X509_CRL *crl, unsigned char **pp)
 {
     crl->crl->enc.modified = 1;
diff --git a/crypto/x509v3/v3_purp.c b/crypto/x509v3/v3_purp.c
index 51783a4..acb7602 100644
--- a/crypto/x509v3/v3_purp.c
+++ b/crypto/x509v3/v3_purp.c
@@ -451,8 +451,14 @@
                 || !bs->ca) {
                 x->ex_flags |= EXFLAG_INVALID;
                 x->ex_pathlen = 0;
-            } else
+            } else {
+                /* TODO(davidben): |ASN1_INTEGER_get| returns -1 on overflow,
+                 * which currently acts as if the constraint isn't present. This
+                 * works (an overflowing path length constraint may as well be
+                 * infinity), but Chromium's verifier simply treats values above
+                 * 255 as an error. */
                 x->ex_pathlen = ASN1_INTEGER_get(bs->pathlen);
+            }
         } else
             x->ex_pathlen = -1;
         BASIC_CONSTRAINTS_free(bs);
@@ -855,9 +861,9 @@
 
 uint32_t X509_get_extension_flags(X509 *x)
 {
-    if (!x509v3_cache_extensions(x)) {
-        return 0;
-    }
+    /* Ignore the return value. On failure, |x->ex_flags| will include
+     * |EXFLAG_INVALID|. */
+    x509v3_cache_extensions(x);
     return x->ex_flags;
 }
 
@@ -912,3 +918,12 @@
     }
     return x509->akid != NULL ? x509->akid->serial : NULL;
 }
+
+long X509_get_pathlen(X509 *x509)
+{
+    if (!x509v3_cache_extensions(x509) ||
+        (x509->ex_flags & EXFLAG_BCONS) == 0) {
+        return -1;
+    }
+    return x509->ex_pathlen;
+}
diff --git a/include/openssl/x509.h b/include/openssl/x509.h
index 38bdf52..9d307d4 100644
--- a/include/openssl/x509.h
+++ b/include/openssl/x509.h
@@ -541,6 +541,15 @@
 // |X509_get_pubkey| instead.
 #define X509_extract_key(x) X509_get_pubkey(x)
 
+// X509_get_pathlen returns path length constraint from the basic constraints
+// extension in |x509|. (See RFC5280, section 4.2.1.9.) It returns -1 if the
+// constraint is not present, or if some extension in |x509| was invalid.
+//
+// Note that decoding an |X509| object will not check for invalid extensions. To
+// detect the error case, call |X509_get_extensions_flags| and check the
+// |EXFLAG_INVALID| bit.
+OPENSSL_EXPORT long X509_get_pathlen(X509 *x509);
+
 // X509_REQ_get_version returns the numerical value of |req|'s version. That is,
 // it returns zero for a v1 request. If |req| is invalid, it may return another
 // value, or -1 on overflow.
@@ -600,6 +609,10 @@
 // would break existing callers. For now, we match upstream.
 OPENSSL_EXPORT STACK_OF(X509_REVOKED) *X509_CRL_get_REVOKED(X509_CRL *crl);
 
+// X509_CRL_get0_extensions returns |crl|'s extension list.
+OPENSSL_EXPORT const STACK_OF(X509_EXTENSION) *
+    X509_CRL_get0_extensions(const X509_CRL *crl);
+
 // X509_CINF_set_modified marks |cinf| as modified so that changes will be
 // reflected in serializing the structure.
 //
@@ -955,7 +968,9 @@
 OPENSSL_EXPORT int X509_set_pubkey(X509 *x, EVP_PKEY *pkey);
 OPENSSL_EXPORT EVP_PKEY *X509_get_pubkey(X509 *x);
 OPENSSL_EXPORT ASN1_BIT_STRING *X509_get0_pubkey_bitstr(const X509 *x);
-OPENSSL_EXPORT STACK_OF(X509_EXTENSION) * X509_get0_extensions(const X509 *x);
+// TODO(davidben): |X509_get0_extensions| should return a const pointer to
+// match upstream.
+OPENSSL_EXPORT STACK_OF(X509_EXTENSION) *X509_get0_extensions(const X509 *x);
 OPENSSL_EXPORT const X509_ALGOR *X509_get0_tbs_sigalg(const X509 *x);
 
 OPENSSL_EXPORT int X509_REQ_set_version(X509_REQ *x, long version);
@@ -1017,6 +1032,10 @@
 OPENSSL_EXPORT int X509_REVOKED_set_revocationDate(X509_REVOKED *r,
                                                    ASN1_TIME *tm);
 
+// X509_REVOKED_get0_extensions returns |r|'s extensions.
+OPENSSL_EXPORT const STACK_OF(X509_EXTENSION) *
+    X509_REVOKED_get0_extensions(const X509_REVOKED *r);
+
 OPENSSL_EXPORT X509_CRL *X509_CRL_diff(X509_CRL *base, X509_CRL *newer,
                                        EVP_PKEY *skey, const EVP_MD *md,
                                        unsigned int flags);
diff --git a/sources.cmake b/sources.cmake
index e20aef0..7daa7e2 100644
--- a/sources.cmake
+++ b/sources.cmake
@@ -58,6 +58,12 @@
   crypto/hpke/hpke_test_vectors.txt
   crypto/poly1305/poly1305_tests.txt
   crypto/siphash/siphash_tests.txt
+  crypto/x509/test/basic_constraints_ca.pem
+  crypto/x509/test/basic_constraints_ca_pathlen_0.pem
+  crypto/x509/test/basic_constraints_ca_pathlen_1.pem
+  crypto/x509/test/basic_constraints_ca_pathlen_10.pem
+  crypto/x509/test/basic_constraints_leaf.pem
+  crypto/x509/test/basic_constraints_none.pem
   crypto/x509/test/invalid_extension_intermediate.pem
   crypto/x509/test/invalid_extension_intermediate_authority_key_identifier.pem
   crypto/x509/test/invalid_extension_intermediate_basic_constraints.pem