Fix some memory leaks in policy_cache_new.
If a certificate has policy constraints, but the certificate policies
extension is either missing or unsuitable (in a way not caught by the
parser), the policy constraints object is leaked.
As part of this, add some basic tests for policy constraints.
Change-Id: I4a2c618019d1f92b0f3b9ad4cf6e29d4926e3095
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/55752
Reviewed-by: Bob Beck <bbe@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/crypto/x509/test/make_policy_certs.go b/crypto/x509/test/make_policy_certs.go
index a4da0a2..73fcae3 100644
--- a/crypto/x509/test/make_policy_certs.go
+++ b/crypto/x509/test/make_policy_certs.go
@@ -25,6 +25,9 @@
"math/big"
"os"
"time"
+
+ "golang.org/x/crypto/cryptobyte"
+ cbasn1 "golang.org/x/crypto/cryptobyte/asn1"
)
var (
@@ -34,6 +37,9 @@
// https://www.rfc-editor.org/rfc/rfc5280.html#section-4.2.1.4
certificatePoliciesOID = asn1.ObjectIdentifier([]int{2, 5, 29, 32})
+
+ // https://www.rfc-editor.org/rfc/rfc5280.html#section-4.2.1.11
+ policyConstraintsOID = asn1.ObjectIdentifier([]int{2, 5, 29, 36})
)
var leafKey, intermediateKey, rootKey *ecdsa.PrivateKey
@@ -147,6 +153,24 @@
intermediateDuplicate.template.PolicyIdentifiers = []asn1.ObjectIdentifier{testOID1, testOID2, testOID2}
mustGenerateCertificate("policy_intermediate_duplicate.pem", &intermediateDuplicate, &root)
+ // A version of the intermediate that sets requireExplicitPolicy without
+ // skipping certificates.
+ b := cryptobyte.NewBuilder(nil)
+ b.AddASN1(cbasn1.SEQUENCE, func(seq *cryptobyte.Builder) {
+ seq.AddASN1Int64WithTag(0, cbasn1.Tag(0).ContextSpecific())
+ })
+ intermediateRequire := intermediate
+ intermediateRequire.template.ExtraExtensions = []pkix.Extension{{Id: policyConstraintsOID, Value: b.BytesOrPanic()}}
+ mustGenerateCertificate("policy_intermediate_require.pem", &intermediateRequire, &root)
+
+ // Same as above, but there are no policies on the intermediate.
+ intermediateRequire.template.PolicyIdentifiers = nil
+ mustGenerateCertificate("policy_intermediate_require_no_policies.pem", &intermediateRequire, &root)
+
+ // Same as above, but the policy list has duplicates.
+ intermediateRequire.template.PolicyIdentifiers = []asn1.ObjectIdentifier{testOID1, testOID2, testOID2}
+ mustGenerateCertificate("policy_intermediate_require_duplicate.pem", &intermediateRequire, &root)
+
// TODO(davidben): Generate more certificates to test policy validation more
// extensively, including an intermediate with constraints. For now this
// just tests the basic case.
diff --git a/crypto/x509/test/policy_intermediate_require.pem b/crypto/x509/test/policy_intermediate_require.pem
new file mode 100644
index 0000000..c141bfc
--- /dev/null
+++ b/crypto/x509/test/policy_intermediate_require.pem
@@ -0,0 +1,12 @@
+-----BEGIN CERTIFICATE-----
+MIIBuTCCAV+gAwIBAgIBAjAKBggqhkjOPQQDAjAWMRQwEgYDVQQDEwtQb2xpY3kg
+Um9vdDAgFw0wMDAxMDEwMDAwMDBaGA8yMTAwMDEwMTAwMDAwMFowHjEcMBoGA1UE
+AxMTUG9saWN5IEludGVybWVkaWF0ZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA
+BOI6fKiM3jFLkLyAn88cvlw4SwxuygRjopP3FFBKHyUQvh3VVvfqSpSCSmp50Qia
+jQ6Dg7CTpVZVVH+bguT7JTCjgZMwgZAwDgYDVR0PAQH/BAQDAgIEMBMGA1UdJQQM
+MAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJDS9/4O7qhr
+CIRhwsXrPVBagG2uMCsGA1UdIAQkMCIwDwYNKoZIhvcSBAGEtwkCATAPBg0qhkiG
+9xIEAYS3CQICMAwGA1UdJAQFMAOAAQAwCgYIKoZIzj0EAwIDSAAwRQIhALuEN9FY
+tVyp8yVAjc/XDZMY86/p/9nA0t1x4fYJz6T4AiACPS/Lx3PD4FfypUZGlY3uv6TJ
+Dv3tfaa/0GLD98VNIw==
+-----END CERTIFICATE-----
diff --git a/crypto/x509/test/policy_intermediate_require_duplicate.pem b/crypto/x509/test/policy_intermediate_require_duplicate.pem
new file mode 100644
index 0000000..b52a8d1
--- /dev/null
+++ b/crypto/x509/test/policy_intermediate_require_duplicate.pem
@@ -0,0 +1,12 @@
+-----BEGIN CERTIFICATE-----
+MIIByjCCAXCgAwIBAgIBAjAKBggqhkjOPQQDAjAWMRQwEgYDVQQDEwtQb2xpY3kg
+Um9vdDAgFw0wMDAxMDEwMDAwMDBaGA8yMTAwMDEwMTAwMDAwMFowHjEcMBoGA1UE
+AxMTUG9saWN5IEludGVybWVkaWF0ZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA
+BOI6fKiM3jFLkLyAn88cvlw4SwxuygRjopP3FFBKHyUQvh3VVvfqSpSCSmp50Qia
+jQ6Dg7CTpVZVVH+bguT7JTCjgaQwgaEwDgYDVR0PAQH/BAQDAgIEMBMGA1UdJQQM
+MAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJDS9/4O7qhr
+CIRhwsXrPVBagG2uMDwGA1UdIAQ1MDMwDwYNKoZIhvcSBAGEtwkCATAPBg0qhkiG
+9xIEAYS3CQICMA8GDSqGSIb3EgQBhLcJAgIwDAYDVR0kBAUwA4ABADAKBggqhkjO
+PQQDAgNIADBFAiEAlowPT0PE6vY2KKCdgh/7ji7x6NBD73OeFe07lk9aBUACIH6R
+ann4GsMCEqkptXfBZ00SIAHJIioAEW+E/8wXU8TX
+-----END CERTIFICATE-----
diff --git a/crypto/x509/test/policy_intermediate_require_no_policies.pem b/crypto/x509/test/policy_intermediate_require_no_policies.pem
new file mode 100644
index 0000000..a78878e
--- /dev/null
+++ b/crypto/x509/test/policy_intermediate_require_no_policies.pem
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE-----
+MIIBijCCATCgAwIBAgIBAjAKBggqhkjOPQQDAjAWMRQwEgYDVQQDEwtQb2xpY3kg
+Um9vdDAgFw0wMDAxMDEwMDAwMDBaGA8yMTAwMDEwMTAwMDAwMFowHjEcMBoGA1UE
+AxMTUG9saWN5IEludGVybWVkaWF0ZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA
+BOI6fKiM3jFLkLyAn88cvlw4SwxuygRjopP3FFBKHyUQvh3VVvfqSpSCSmp50Qia
+jQ6Dg7CTpVZVVH+bguT7JTCjZTBjMA4GA1UdDwEB/wQEAwICBDATBgNVHSUEDDAK
+BggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSQ0vf+Du6oawiE
+YcLF6z1QWoBtrjAMBgNVHSQEBTADgAEAMAoGCCqGSM49BAMCA0gAMEUCIF2jnBVg
+7hNV09+9ZS7bGaf5GcRifHOkAUC6M596ECXCAiEApuyn9lyP15qD/mHtMz8eYrKn
+8HygEypeq3ClqqKx2c4=
+-----END CERTIFICATE-----
diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc
index 5dca616..248544a 100644
--- a/crypto/x509/x509_test.cc
+++ b/crypto/x509/x509_test.cc
@@ -5119,6 +5119,19 @@
GetTestData("crypto/x509/test/policy_intermediate_duplicate.pem")
.c_str()));
ASSERT_TRUE(intermediate_duplicate);
+ bssl::UniquePtr<X509> intermediate_require(CertFromPEM(
+ GetTestData("crypto/x509/test/policy_intermediate_require.pem")
+ .c_str()));
+ ASSERT_TRUE(intermediate_require);
+ bssl::UniquePtr<X509> intermediate_require_duplicate(CertFromPEM(
+ GetTestData("crypto/x509/test/policy_intermediate_require_duplicate.pem")
+ .c_str()));
+ ASSERT_TRUE(intermediate_require_duplicate);
+ bssl::UniquePtr<X509> intermediate_require_no_policies(CertFromPEM(
+ GetTestData(
+ "crypto/x509/test/policy_intermediate_require_no_policies.pem")
+ .c_str()));
+ ASSERT_TRUE(intermediate_require_no_policies);
bssl::UniquePtr<X509> leaf(
CertFromPEM(GetTestData("crypto/x509/test/policy_leaf.pem").c_str()));
ASSERT_TRUE(leaf);
@@ -5145,6 +5158,9 @@
ASSERT_TRUE(X509_VERIFY_PARAM_add0_policy(param, copy.get()));
copy.release(); // |X509_VERIFY_PARAM_add0_policy| takes ownership on
// success.
+ // TODO(davidben): |X509_VERIFY_PARAM_add0_policy| does not set this flag,
+ // while |X509_VERIFY_PARAM_set1_policies| does. Is this a bug?
+ X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_POLICY_CHECK);
}
};
@@ -5204,4 +5220,49 @@
[&](X509_VERIFY_PARAM *param) {
set_policies(param, {oid1.get()});
}));
+
+ // Without |X509_V_FLAG_EXPLICIT_POLICY|, the policy tree is built and
+ // intersected with user-specified policies, but it is not required to result
+ // in any valid policies.
+ EXPECT_EQ(X509_V_OK,
+ Verify(leaf.get(), {root.get()}, {intermediate.get()}, /*crls=*/{},
+ /*flags=*/0, [&](X509_VERIFY_PARAM *param) {
+ set_policies(param, {oid1.get()});
+ }));
+ EXPECT_EQ(X509_V_OK,
+ Verify(leaf.get(), {root.get()}, {intermediate.get()}, /*crls=*/{},
+ /*flags=*/0, [&](X509_VERIFY_PARAM *param) {
+ set_policies(param, {oid3.get()});
+ }));
+
+ // However, a CA with policy constraints can require an explicit policy.
+ EXPECT_EQ(X509_V_OK, Verify(leaf.get(), {root.get()},
+ {intermediate_require.get()}, /*crls=*/{},
+ /*flags=*/0, [&](X509_VERIFY_PARAM *param) {
+ set_policies(param, {oid1.get()});
+ }));
+ EXPECT_EQ(X509_V_ERR_NO_EXPLICIT_POLICY,
+ Verify(leaf.get(), {root.get()}, {intermediate_require.get()},
+ /*crls=*/{},
+ /*flags=*/0, [&](X509_VERIFY_PARAM *param) {
+ set_policies(param, {oid3.get()});
+ }));
+
+ // An intermediate that requires an explicit policy, but then specifies no
+ // policies should fail verification as a result.
+ EXPECT_EQ(X509_V_ERR_NO_EXPLICIT_POLICY,
+ Verify(leaf.get(), {root.get()},
+ {intermediate_require_no_policies.get()}, /*crls=*/{},
+ /*flags=*/0, [&](X509_VERIFY_PARAM *param) {
+ set_policies(param, {oid1.get()});
+ }));
+
+ // A constrained intermediate's policy extension has a duplicate policy, which
+ // is invalid. Historically this, and the above case, leaked memory.
+ EXPECT_EQ(X509_V_ERR_INVALID_POLICY_EXTENSION,
+ Verify(leaf.get(), {root.get()},
+ {intermediate_require_duplicate.get()}, /*crls=*/{},
+ /*flags=*/0, [&](X509_VERIFY_PARAM *param) {
+ set_policies(param, {oid1.get()});
+ }));
}
diff --git a/crypto/x509v3/pcy_cache.c b/crypto/x509v3/pcy_cache.c
index 74f95f9..3a351dc 100644
--- a/crypto/x509v3/pcy_cache.c
+++ b/crypto/x509v3/pcy_cache.c
@@ -123,16 +123,15 @@
return ret;
}
-static int policy_cache_new(X509 *x) {
+static void policy_cache_new(X509 *x) {
X509_POLICY_CACHE *cache;
ASN1_INTEGER *ext_any = NULL;
POLICY_CONSTRAINTS *ext_pcons = NULL;
CERTIFICATEPOLICIES *ext_cpols = NULL;
POLICY_MAPPINGS *ext_pmaps = NULL;
- int i;
cache = OPENSSL_malloc(sizeof(X509_POLICY_CACHE));
if (!cache) {
- return 0;
+ return;
}
cache->anyPolicy = NULL;
cache->data = NULL;
@@ -144,10 +143,10 @@
// Handle requireExplicitPolicy *first*. Need to process this even if we
// don't have any policies.
- ext_pcons = X509_get_ext_d2i(x, NID_policy_constraints, &i, NULL);
-
+ int critical;
+ ext_pcons = X509_get_ext_d2i(x, NID_policy_constraints, &critical, NULL);
if (!ext_pcons) {
- if (i != -1) {
+ if (critical != -1) {
goto bad_cache;
}
} else {
@@ -164,45 +163,42 @@
}
}
- // Process CertificatePolicies
-
- ext_cpols = X509_get_ext_d2i(x, NID_certificate_policies, &i, NULL);
+ ext_cpols = X509_get_ext_d2i(x, NID_certificate_policies, &critical, NULL);
// If no CertificatePolicies extension or problem decoding then there is
// no point continuing because the valid policies will be NULL.
if (!ext_cpols) {
// If not absent some problem with extension
- if (i != -1) {
+ if (critical != -1) {
goto bad_cache;
}
- return 1;
+ goto done;
}
- i = policy_cache_create(x, ext_cpols, i);
-
- // NB: ext_cpols freed by policy_cache_set_policies
-
- if (i <= 0) {
- return i;
+ // This call frees |ext_cpols|.
+ if (policy_cache_create(x, ext_cpols, critical) <= 0) {
+ // |policy_cache_create| already sets |EXFLAG_INVALID_POLICY|.
+ //
+ // TODO(davidben): While it does, it's missing some spots. Align this and
+ // |policy_cache_set_mapping|.
+ goto done;
}
- ext_pmaps = X509_get_ext_d2i(x, NID_policy_mappings, &i, NULL);
-
+ ext_pmaps = X509_get_ext_d2i(x, NID_policy_mappings, &critical, NULL);
if (!ext_pmaps) {
// If not absent some problem with extension
- if (i != -1) {
+ if (critical != -1) {
goto bad_cache;
}
} else {
- i = policy_cache_set_mapping(x, ext_pmaps);
- if (i <= 0) {
+ // This call frees |ext_pmaps|.
+ if (policy_cache_set_mapping(x, ext_pmaps) <= 0) {
goto bad_cache;
}
}
- ext_any = X509_get_ext_d2i(x, NID_inhibit_any_policy, &i, NULL);
-
+ ext_any = X509_get_ext_d2i(x, NID_inhibit_any_policy, &critical, NULL);
if (!ext_any) {
- if (i != -1) {
+ if (critical != -1) {
goto bad_cache;
}
} else if (!policy_cache_set_int(&cache->any_skip, ext_any)) {
@@ -214,15 +210,9 @@
x->ex_flags |= EXFLAG_INVALID_POLICY;
}
- if (ext_pcons) {
- POLICY_CONSTRAINTS_free(ext_pcons);
- }
-
- if (ext_any) {
- ASN1_INTEGER_free(ext_any);
- }
-
- return 1;
+done:
+ POLICY_CONSTRAINTS_free(ext_pcons);
+ ASN1_INTEGER_free(ext_any);
}
void policy_cache_free(X509_POLICY_CACHE *cache) {
diff --git a/go.mod b/go.mod
index 5d8b372..5fd27f4 100644
--- a/go.mod
+++ b/go.mod
@@ -3,11 +3,11 @@
go 1.19
require (
- golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
- golang.org/x/net v0.0.0-20210614182718-04defd469f4e
+ golang.org/x/crypto v0.4.0
+ golang.org/x/net v0.3.0
)
require (
- golang.org/x/sys v0.0.0-20210423082822-04245dca01da // indirect
- golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
+ golang.org/x/sys v0.3.0 // indirect
+ golang.org/x/term v0.3.0 // indirect
)
diff --git a/go.sum b/go.sum
index e08ca5f..26fc6b6 100644
--- a/go.sum
+++ b/go.sum
@@ -1,9 +1,17 @@
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
+golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
+golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.3.0 h1:VWL6FNY2bEEmsGVKabSlHu5Irp34xmMRoqb/9lF9lxk=
+golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
+golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
+golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
diff --git a/sources.cmake b/sources.cmake
index 46788fa..4dcbbc1 100644
--- a/sources.cmake
+++ b/sources.cmake
@@ -114,6 +114,9 @@
crypto/x509/test/policy_root.pem
crypto/x509/test/policy_intermediate_duplicate.pem
crypto/x509/test/policy_intermediate_invalid.pem
+ crypto/x509/test/policy_intermediate_require.pem
+ crypto/x509/test/policy_intermediate_require_duplicate.pem
+ crypto/x509/test/policy_intermediate_require_no_policies.pem
crypto/x509/test/policy_intermediate.pem
crypto/x509/test/policy_leaf_duplicate.pem
crypto/x509/test/policy_leaf_invalid.pem