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