Add X509_V_FLAG_REQUIRE_CA_BASIC_CONSTRAINTS.
This change adds a new flag, X509_V_FLAG_REQUIRE_CA_BASIC_CONSTRAINTS,
which causes basicConstraints with isCA to be required for intermediate
CA certificates. Without this, intermediates are also acceptable if
they're missing basicConstraints, but include either a certSign
keyUsage, or a CA Netscape certificate type.
This is a short-term change for patching. I'll undo a lot of it and make
this the default in the next change.
Change-Id: I7f42ffd76c57de3037f054108951e230c1b4e415
Reviewed-on: https://boringssl-review.googlesource.com/30184
Commit-Queue: Adam Langley <agl@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
Reviewed-by: Matt Braithwaite <mab@google.com>
diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc
index 0711992..df236ae 100644
--- a/crypto/x509/x509_test.cc
+++ b/crypto/x509/x509_test.cc
@@ -495,6 +495,137 @@
// /JKuuFGmzkG+rUbXFmo/Zg2ozVplw71NnQJ4znPsf7A=
// -----END RSA PRIVATE KEY-----
+// The following four certificates were generated with this Go program, varying
+// |includeNetscapeExtension| and defining rootKeyPEM and rootCertPEM to be
+// strings containing the kSANTypesRoot, above.
+
+// package main
+
+// import (
+// "crypto/ecdsa"
+// "crypto/elliptic"
+// "crypto/rand"
+// "crypto/x509"
+// "crypto/x509/pkix"
+// "encoding/asn1"
+// "encoding/pem"
+// "math/big"
+// "os"
+// "time"
+// )
+
+// const includeNetscapeExtension = true
+
+// func main() {
+// block, _ := pem.Decode([]byte(rootKeyPEM))
+// rootPriv, _ := x509.ParsePKCS1PrivateKey(block.Bytes)
+// block, _ = pem.Decode([]byte(rootCertPEM))
+// root, _ := x509.ParseCertificate(block.Bytes)
+
+// interTemplate := &x509.Certificate{
+// SerialNumber: big.NewInt(2),
+// Subject: pkix.Name{
+// CommonName: "No Basic Constraints (Netscape)",
+// },
+// NotBefore: time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC),
+// NotAfter: time.Date(2099, time.January, 1, 0, 0, 0, 0, time.UTC),
+// }
+
+// if includeNetscapeExtension {
+// interTemplate.ExtraExtensions = []pkix.Extension{
+// pkix.Extension{
+// Id: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 113730, 1, 1}),
+// Value: []byte{0x03, 0x02, 2, 0x04},
+// },
+// }
+// } else {
+// interTemplate.KeyUsage = x509.KeyUsageCertSign
+// }
+
+// interKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+
+// interDER, err := x509.CreateCertificate(rand.Reader, interTemplate, root, &interKey.PublicKey, rootPriv)
+// if err != nil {
+// panic(err)
+// }
+
+// pem.Encode(os.Stdout, &pem.Block{Type: "CERTIFICATE", Bytes: interDER})
+
+// inter, _ := x509.ParseCertificate(interDER)
+
+// leafTemplate := &x509.Certificate{
+// SerialNumber: big.NewInt(3),
+// Subject: pkix.Name{
+// CommonName: "Leaf from CA with no Basic Constraints",
+// },
+// NotBefore: time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC),
+// NotAfter: time.Date(2099, time.January, 1, 0, 0, 0, 0, time.UTC),
+// BasicConstraintsValid: true,
+// }
+// leafKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+
+// leafDER, err := x509.CreateCertificate(rand.Reader, leafTemplate, inter, &leafKey.PublicKey, interKey)
+// if err != nil {
+// panic(err)
+// }
+
+// pem.Encode(os.Stdout, &pem.Block{Type: "CERTIFICATE", Bytes: leafDER})
+// }
+
+// kNoBasicConstraintsCertSignIntermediate doesn't have isCA set, but contains
+// certSign in the keyUsage.
+static const char kNoBasicConstraintsCertSignIntermediate[] =
+ "-----BEGIN CERTIFICATE-----\n"
+ "MIIBqjCCAROgAwIBAgIBAjANBgkqhkiG9w0BAQsFADArMRcwFQYDVQQKEw5Cb3Jp\n"
+ "bmdTU0wgVGVzdDEQMA4GA1UEAxMHUm9vdCBDQTAgFw0wMDAxMDEwMDAwMDBaGA8y\n"
+ "MDk5MDEwMTAwMDAwMFowHzEdMBsGA1UEAxMUTm8gQmFzaWMgQ29uc3RyYWludHMw\n"
+ "WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASEFMblfxIEDO8My7wHtHWTuDzNyID1\n"
+ "OsPkMGkn32O/pSyXxXuAqDeFoMVffUMTyfm8JcYugSEbrv2qEXXM4bZRoy8wLTAO\n"
+ "BgNVHQ8BAf8EBAMCAgQwGwYDVR0jBBQwEoAQQDfXAftAL7gcflQEJ4xZATANBgkq\n"
+ "hkiG9w0BAQsFAAOBgQC1Lh6hIAm3K5kRh5iIydU0YAEm7eV6ZSskERDUq3DLJyl9\n"
+ "ZUZCHUzvb464dkwZjeNzaUVS1pdElJslwX3DtGgeJLJGCnk8zUjBjaNrrDm0kzPW\n"
+ "xKt/6oif1ci/KCKqKNXJAIFbc4e+IiBpenwpxHk3If4NM+Ek0nKoO8Uj0NkgTQ==\n"
+ "-----END CERTIFICATE-----\n";
+
+static const char kNoBasicConstraintsCertSignLeaf[] =
+ "-----BEGIN CERTIFICATE-----\n"
+ "MIIBUDCB96ADAgECAgEDMAoGCCqGSM49BAMCMB8xHTAbBgNVBAMTFE5vIEJhc2lj\n"
+ "IENvbnN0cmFpbnRzMCAXDTAwMDEwMTAwMDAwMFoYDzIwOTkwMTAxMDAwMDAwWjAx\n"
+ "MS8wLQYDVQQDEyZMZWFmIGZyb20gQ0Egd2l0aCBubyBCYXNpYyBDb25zdHJhaW50\n"
+ "czBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEsYPMwzdJKjB+2gpC90ib2ilHoB\n"
+ "w/arQ6ikUX0CNUDDaKaOu/jF39ogzVlg4lDFrjCKShSfCCcrwgONv70IZGijEDAO\n"
+ "MAwGA1UdEwEB/wQCMAAwCgYIKoZIzj0EAwIDSAAwRQIgbV7R99yM+okXSIs6Fp3o\n"
+ "eCOXiDL60IBxaTOcLS44ywcCIQDbn87Gj5cFgHBYAkzdHqDsyGXkxQTHDq9jmX24\n"
+ "Djy3Zw==\n"
+ "-----END CERTIFICATE-----\n";
+
+// kNoBasicConstraintsNetscapeCAIntermediate doesn't have isCA set, but contains
+// a Netscape certificate-type extension that asserts a type of "SSL CA".
+static const char kNoBasicConstraintsNetscapeCAIntermediate[] =
+ "-----BEGIN CERTIFICATE-----\n"
+ "MIIBuDCCASGgAwIBAgIBAjANBgkqhkiG9w0BAQsFADArMRcwFQYDVQQKEw5Cb3Jp\n"
+ "bmdTU0wgVGVzdDEQMA4GA1UEAxMHUm9vdCBDQTAgFw0wMDAxMDEwMDAwMDBaGA8y\n"
+ "MDk5MDEwMTAwMDAwMFowKjEoMCYGA1UEAxMfTm8gQmFzaWMgQ29uc3RyYWludHMg\n"
+ "KE5ldHNjYXBlKTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCeMbmCaOtMzXBqi\n"
+ "PrCdNOH23CkaawUA+pAezitAN4RXS1O2CGK5sJjGPVVeogROU8G7/b+mU+ciZIzH\n"
+ "1PP8FJKjMjAwMBsGA1UdIwQUMBKAEEA31wH7QC+4HH5UBCeMWQEwEQYJYIZIAYb4\n"
+ "QgEBBAQDAgIEMA0GCSqGSIb3DQEBCwUAA4GBAAgNWjh7cfBTClTAk+Ml//5xb9Ju\n"
+ "tkBhG6Rm+kkMD+qiSMO6t7xS7CsA0+jIBjkdEYaLZ3oxtQCBdZsVNxUvRxZ0AUfF\n"
+ "G3DtRFTsrI1f7IQhpMuqEMF4shPW+5x54hrq0Fo6xMs6XoinJZcTUaaB8EeXRF6M\n"
+ "P9p6HuyLrmn0c/F0\n"
+ "-----END CERTIFICATE-----\n";
+
+static const char kNoBasicConstraintsNetscapeCALeaf[] =
+ "-----BEGIN CERTIFICATE-----\n"
+ "MIIBXDCCAQKgAwIBAgIBAzAKBggqhkjOPQQDAjAqMSgwJgYDVQQDEx9ObyBCYXNp\n"
+ "YyBDb25zdHJhaW50cyAoTmV0c2NhcGUpMCAXDTAwMDEwMTAwMDAwMFoYDzIwOTkw\n"
+ "MTAxMDAwMDAwWjAxMS8wLQYDVQQDEyZMZWFmIGZyb20gQ0Egd2l0aCBubyBCYXNp\n"
+ "YyBDb25zdHJhaW50czBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDlJKolDu3R2\n"
+ "tPqSDycr0QJcWhxdBv76V0EEVflcHRxED6vAioTEcnQszt1OfKtBZvjlo0yp6i6Q\n"
+ "DaYit0ZInmWjEDAOMAwGA1UdEwEB/wQCMAAwCgYIKoZIzj0EAwIDSAAwRQIhAJsh\n"
+ "aZL6BHeEfoUBj1oZ2Ln91qzj3UCVMJ+vrmwAFdYyAiA3wp2JphgchvmoUFuzPXwj\n"
+ "XyPwWPbymSTpzKhB4xB7qQ==\n"
+ "-----END CERTIFICATE-----\n";
// CertFromPEM parses the given, NUL-terminated pem block and returns an
// |X509*|.
@@ -1341,3 +1472,55 @@
}
}
}
+
+TEST(X509Test, NoBasicConstraintsCertSign) {
+ bssl::UniquePtr<X509> root(CertFromPEM(kSANTypesRoot));
+ bssl::UniquePtr<X509> intermediate(
+ CertFromPEM(kNoBasicConstraintsCertSignIntermediate));
+ bssl::UniquePtr<X509> leaf(CertFromPEM(kNoBasicConstraintsCertSignLeaf));
+
+ ASSERT_TRUE(root);
+ ASSERT_TRUE(intermediate);
+ ASSERT_TRUE(leaf);
+
+ // The intermediate has keyUsage certSign, but is not marked as a CA in the
+ // basicConstraints. Sadly, since BoringSSL is based on OpenSSL 1.0.2, this is
+ // considered acceptable by default.
+ EXPECT_EQ(X509_V_OK,
+ Verify(leaf.get(), {root.get()}, {intermediate.get()}, {}, 0));
+
+ // Setting either STRICT or REQUIRE_CA_BASIC_CONSTRAINTS should trigger an
+ // error.
+ EXPECT_EQ(X509_V_ERR_INVALID_CA,
+ Verify(leaf.get(), {root.get()}, {intermediate.get()}, {},
+ X509_V_FLAG_X509_STRICT));
+ EXPECT_EQ(X509_V_ERR_INVALID_CA,
+ Verify(leaf.get(), {root.get()}, {intermediate.get()}, {},
+ X509_V_FLAG_REQUIRE_CA_BASIC_CONSTRAINTS));
+}
+
+TEST(X509Test, NoBasicConstraintsNetscapeCA) {
+ bssl::UniquePtr<X509> root(CertFromPEM(kSANTypesRoot));
+ bssl::UniquePtr<X509> intermediate(
+ CertFromPEM(kNoBasicConstraintsNetscapeCAIntermediate));
+ bssl::UniquePtr<X509> leaf(CertFromPEM(kNoBasicConstraintsNetscapeCALeaf));
+
+ ASSERT_TRUE(root);
+ ASSERT_TRUE(intermediate);
+ ASSERT_TRUE(leaf);
+
+ // The intermediate has a Netscape certificate type of "SSL CA", but is not
+ // marked as a CA in the basicConstraints. Sadly, since BoringSSL is based on
+ // OpenSSL 1.0.2, this is considered acceptable by default.
+ EXPECT_EQ(X509_V_OK,
+ Verify(leaf.get(), {root.get()}, {intermediate.get()}, {}, 0));
+
+ // Setting either STRICT or REQUIRE_CA_BASIC_CONSTRAINTS should trigger an
+ // error.
+ EXPECT_EQ(X509_V_ERR_INVALID_CA,
+ Verify(leaf.get(), {root.get()}, {intermediate.get()}, {},
+ X509_V_FLAG_X509_STRICT));
+ EXPECT_EQ(X509_V_ERR_INVALID_CA,
+ Verify(leaf.get(), {root.get()}, {intermediate.get()}, {},
+ X509_V_FLAG_REQUIRE_CA_BASIC_CONSTRAINTS));
+}
diff --git a/crypto/x509/x509_vfy.c b/crypto/x509/x509_vfy.c
index d788c5e..fdce251 100644
--- a/crypto/x509/x509_vfy.c
+++ b/crypto/x509/x509_vfy.c
@@ -648,7 +648,11 @@
default:
if ((ret == 0)
|| ((ctx->param->flags & X509_V_FLAG_X509_STRICT)
- && (ret != 1))) {
+ && (ret != 1))
+ || ((ctx->param->flags &
+ X509_V_FLAG_REQUIRE_CA_BASIC_CONSTRAINTS)
+ && (ret != 1))
+ ) {
ret = 0;
ctx->error = X509_V_ERR_INVALID_CA;
} else
diff --git a/include/openssl/x509_vfy.h b/include/openssl/x509_vfy.h
index 97a07d5..6a7c554 100644
--- a/include/openssl/x509_vfy.h
+++ b/include/openssl/x509_vfy.h
@@ -410,6 +410,8 @@
#define X509_V_FLAG_SUITEB_192_LOS 0x20000
/* Suite B 128 bit mode allowing 192 bit algorithms */
#define X509_V_FLAG_SUITEB_128_LOS 0x30000
+/* Disable workarounds for broken certificates */
+#define X509_V_FLAG_REQUIRE_CA_BASIC_CONSTRAINTS 0x40000
/* Allow partial chains if at least one certificate is in trusted store */
#define X509_V_FLAG_PARTIAL_CHAIN 0x80000