Bob Beck | fb26074 | 2023-07-24 14:29:34 -0700 | [diff] [blame] | 1 | // Copyright 2019 The Chromium Authors |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "crl.h" |
| 6 | |
| 7 | #include <string_view> |
| 8 | |
Bob Beck | 5c7a2a0 | 2023-11-20 17:28:21 -0700 | [diff] [blame] | 9 | #include <gtest/gtest.h> |
| 10 | #include <openssl/pool.h> |
Bob Beck | fb26074 | 2023-07-24 14:29:34 -0700 | [diff] [blame] | 11 | #include "cert_errors.h" |
| 12 | #include "parsed_certificate.h" |
| 13 | #include "string_util.h" |
| 14 | #include "test_helpers.h" |
Bob Beck | fb26074 | 2023-07-24 14:29:34 -0700 | [diff] [blame] | 15 | |
| 16 | namespace bssl { |
| 17 | |
| 18 | namespace { |
| 19 | |
| 20 | constexpr int64_t kAgeOneWeek = 7 * 24 * 60 * 60; |
| 21 | |
| 22 | std::string GetFilePath(std::string_view file_name) { |
| 23 | return std::string("testdata/crl_unittest/") + std::string(file_name); |
| 24 | } |
| 25 | |
| 26 | std::shared_ptr<const ParsedCertificate> ParseCertificate( |
| 27 | std::string_view data) { |
| 28 | CertErrors errors; |
| 29 | return ParsedCertificate::Create( |
Bob Beck | 5c7a2a0 | 2023-11-20 17:28:21 -0700 | [diff] [blame] | 30 | bssl::UniquePtr<CRYPTO_BUFFER>( |
| 31 | CRYPTO_BUFFER_new(reinterpret_cast<const uint8_t *>(data.data()), |
| 32 | data.size(), nullptr)), |
Bob Beck | fb26074 | 2023-07-24 14:29:34 -0700 | [diff] [blame] | 33 | {}, &errors); |
| 34 | } |
| 35 | |
Bob Beck | 5c7a2a0 | 2023-11-20 17:28:21 -0700 | [diff] [blame] | 36 | class CheckCRLTest : public ::testing::TestWithParam<const char *> {}; |
Bob Beck | fb26074 | 2023-07-24 14:29:34 -0700 | [diff] [blame] | 37 | |
| 38 | // Test prefix naming scheme: |
| 39 | // good = valid CRL, cert affirmatively not revoked |
| 40 | // revoked = valid CRL, cert affirmatively revoked |
| 41 | // bad = valid CRL, but cert status is unknown (cases like unhandled features, |
| 42 | // mismatching issuer or signature, etc) |
| 43 | // invalid = corrupt or violates some spec requirement |
Bob Beck | 5c7a2a0 | 2023-11-20 17:28:21 -0700 | [diff] [blame] | 44 | constexpr char const *kTestParams[] = { |
Bob Beck | fb26074 | 2023-07-24 14:29:34 -0700 | [diff] [blame] | 45 | "good.pem", |
| 46 | "good_issuer_name_normalization.pem", |
| 47 | "good_issuer_no_keyusage.pem", |
| 48 | "good_no_nextupdate.pem", |
| 49 | "good_fake_extension.pem", |
| 50 | "good_fake_extension_no_nextupdate.pem", |
| 51 | "good_generalizedtime.pem", |
| 52 | "good_no_version.pem", |
| 53 | "good_no_crldp.pem", |
| 54 | "good_key_rollover.pem", |
| 55 | "good_idp_contains_uri.pem", |
| 56 | "good_idp_onlycontainsusercerts.pem", |
| 57 | "good_idp_onlycontainsusercerts_no_basic_constraints.pem", |
| 58 | "good_idp_onlycontainscacerts.pem", |
| 59 | "good_idp_uri_and_onlycontainsusercerts.pem", |
| 60 | "good_idp_uri_and_onlycontainscacerts.pem", |
| 61 | "revoked.pem", |
| 62 | "revoked_no_nextupdate.pem", |
| 63 | "revoked_fake_crlentryextension.pem", |
| 64 | "revoked_generalized_revocationdate.pem", |
| 65 | "revoked_key_rollover.pem", |
| 66 | "bad_crldp_has_crlissuer.pem", |
| 67 | "bad_fake_critical_extension.pem", |
| 68 | "bad_fake_critical_crlentryextension.pem", |
| 69 | "bad_signature.pem", |
| 70 | "bad_thisupdate_in_future.pem", |
| 71 | "bad_thisupdate_too_old.pem", |
| 72 | "bad_nextupdate_too_old.pem", |
| 73 | "bad_wrong_issuer.pem", |
| 74 | "bad_key_rollover_signature.pem", |
| 75 | "bad_idp_contains_wrong_uri.pem", |
| 76 | "bad_idp_indirectcrl.pem", |
| 77 | "bad_idp_onlycontainsusercerts.pem", |
| 78 | "bad_idp_onlycontainscacerts.pem", |
| 79 | "bad_idp_onlycontainscacerts_no_basic_constraints.pem", |
| 80 | "bad_idp_uri_and_onlycontainsusercerts.pem", |
| 81 | "bad_idp_uri_and_onlycontainscacerts.pem", |
| 82 | "invalid_mismatched_signature_algorithm.pem", |
| 83 | "invalid_revoked_empty_sequence.pem", |
| 84 | "invalid_v1_with_extension.pem", |
| 85 | "invalid_v1_with_crlentryextension.pem", |
| 86 | "invalid_v1_explicit.pem", |
| 87 | "invalid_v3.pem", |
| 88 | "invalid_issuer_keyusage_no_crlsign.pem", |
| 89 | "invalid_key_rollover_issuer_keyusage_no_crlsign.pem", |
| 90 | "invalid_garbage_version.pem", |
| 91 | "invalid_garbage_tbs_signature_algorithm.pem", |
| 92 | "invalid_garbage_issuer_name.pem", |
| 93 | "invalid_garbage_thisupdate.pem", |
| 94 | "invalid_garbage_after_thisupdate.pem", |
| 95 | "invalid_garbage_after_nextupdate.pem", |
| 96 | "invalid_garbage_after_revokedcerts.pem", |
| 97 | "invalid_garbage_after_extensions.pem", |
| 98 | "invalid_garbage_tbscertlist.pem", |
| 99 | "invalid_garbage_signaturealgorithm.pem", |
| 100 | "invalid_garbage_signaturevalue.pem", |
| 101 | "invalid_garbage_after_signaturevalue.pem", |
| 102 | "invalid_garbage_revoked_serial_number.pem", |
| 103 | "invalid_garbage_revocationdate.pem", |
| 104 | "invalid_garbage_after_revocationdate.pem", |
| 105 | "invalid_garbage_after_crlentryextensions.pem", |
| 106 | "invalid_garbage_crlentry.pem", |
| 107 | "invalid_idp_dpname_choice_extra_data.pem", |
| 108 | "invalid_idp_empty_sequence.pem", |
| 109 | "invalid_idp_onlycontains_user_and_ca_certs.pem", |
| 110 | "invalid_idp_onlycontainsusercerts_v1_leaf.pem", |
| 111 | }; |
| 112 | |
| 113 | struct PrintTestName { |
| 114 | std::string operator()( |
Bob Beck | 5c7a2a0 | 2023-11-20 17:28:21 -0700 | [diff] [blame] | 115 | const testing::TestParamInfo<const char *> &info) const { |
Bob Beck | fb26074 | 2023-07-24 14:29:34 -0700 | [diff] [blame] | 116 | std::string_view name(info.param); |
| 117 | // Strip ".pem" from the end as GTest names cannot contain period. |
| 118 | name.remove_suffix(4); |
| 119 | return std::string(name); |
| 120 | } |
| 121 | }; |
| 122 | |
Bob Beck | 5c7a2a0 | 2023-11-20 17:28:21 -0700 | [diff] [blame] | 123 | INSTANTIATE_TEST_SUITE_P(All, CheckCRLTest, ::testing::ValuesIn(kTestParams), |
Bob Beck | fb26074 | 2023-07-24 14:29:34 -0700 | [diff] [blame] | 124 | PrintTestName()); |
| 125 | |
| 126 | TEST_P(CheckCRLTest, FromFile) { |
| 127 | std::string_view file_name(GetParam()); |
| 128 | |
| 129 | std::string crl_data; |
| 130 | std::string ca_data_2; |
| 131 | std::string ca_data; |
| 132 | std::string cert_data; |
| 133 | const PemBlockMapping mappings[] = { |
| 134 | {"CRL", &crl_data}, |
| 135 | {"CA CERTIFICATE 2", &ca_data_2, /*optional=*/true}, |
| 136 | {"CA CERTIFICATE", &ca_data}, |
| 137 | {"CERTIFICATE", &cert_data}, |
| 138 | }; |
| 139 | |
| 140 | ASSERT_TRUE(ReadTestDataFromPemFile(GetFilePath(file_name), mappings)); |
| 141 | |
| 142 | std::shared_ptr<const ParsedCertificate> cert = ParseCertificate(cert_data); |
| 143 | ASSERT_TRUE(cert); |
| 144 | std::shared_ptr<const ParsedCertificate> issuer_cert = |
| 145 | ParseCertificate(ca_data); |
| 146 | ASSERT_TRUE(issuer_cert); |
| 147 | ParsedCertificateList certs = {cert, issuer_cert}; |
| 148 | if (!ca_data_2.empty()) { |
| 149 | std::shared_ptr<const ParsedCertificate> issuer_cert_2 = |
| 150 | ParseCertificate(ca_data_2); |
| 151 | ASSERT_TRUE(issuer_cert_2); |
| 152 | certs.push_back(issuer_cert_2); |
| 153 | } |
| 154 | |
| 155 | // Assumes that all the target certs in the test data certs have at most 1 |
| 156 | // CRL distributionPoint. If the cert has a CRL distributionPoint, it is |
| 157 | // used for verifying the CRL, otherwise the CRL is verified with a |
| 158 | // synthesized distributionPoint. This is allowed since there are some |
| 159 | // conditions that require a V1 certificate to test, which cannot have a |
| 160 | // crlDistributionPoints extension. |
| 161 | // TODO(https://crbug.com/749276): This seems slightly hacky. Maybe the |
| 162 | // distribution point to use should be specified separately in the test PEM? |
| 163 | ParsedDistributionPoint fake_cert_dp; |
Bob Beck | 5c7a2a0 | 2023-11-20 17:28:21 -0700 | [diff] [blame] | 164 | ParsedDistributionPoint *cert_dp = &fake_cert_dp; |
Bob Beck | fb26074 | 2023-07-24 14:29:34 -0700 | [diff] [blame] | 165 | std::vector<ParsedDistributionPoint> distribution_points; |
| 166 | ParsedExtension crl_dp_extension; |
| 167 | if (cert->GetExtension(der::Input(kCrlDistributionPointsOid), |
| 168 | &crl_dp_extension)) { |
| 169 | ASSERT_TRUE(ParseCrlDistributionPoints(crl_dp_extension.value, |
| 170 | &distribution_points)); |
| 171 | ASSERT_LE(distribution_points.size(), 1U); |
| 172 | if (!distribution_points.empty()) { |
| 173 | cert_dp = &distribution_points[0]; |
| 174 | } |
| 175 | } |
| 176 | ASSERT_TRUE(cert_dp); |
| 177 | |
| 178 | // Mar 9 00:00:00 2017 GMT |
| 179 | int64_t kVerifyTime = 1489017600; |
| 180 | |
| 181 | CRLRevocationStatus expected_revocation_status = CRLRevocationStatus::UNKNOWN; |
| 182 | if (string_util::StartsWith(file_name, "good")) { |
| 183 | expected_revocation_status = CRLRevocationStatus::GOOD; |
| 184 | } else if (string_util::StartsWith(file_name, "revoked")) { |
| 185 | expected_revocation_status = CRLRevocationStatus::REVOKED; |
| 186 | } |
| 187 | |
| 188 | CRLRevocationStatus revocation_status = |
| 189 | CheckCRL(crl_data, certs, /*target_cert_index=*/0, *cert_dp, kVerifyTime, |
| 190 | kAgeOneWeek); |
| 191 | EXPECT_EQ(expected_revocation_status, revocation_status); |
| 192 | |
| 193 | // Test with a random cert added to the front of the chain and |
| 194 | // |target_cert_index=1|. This is a hacky way to verify that |
| 195 | // target_cert_index is actually being honored. |
| 196 | ParsedCertificateList other_certs; |
| 197 | ASSERT_TRUE(ReadCertChainFromFile( |
| 198 | "testdata/parse_certificate_unittest/cert_version3.pem", &other_certs)); |
| 199 | ASSERT_FALSE(other_certs.empty()); |
| 200 | certs.insert(certs.begin(), other_certs[0]); |
| 201 | revocation_status = CheckCRL(crl_data, certs, /*target_cert_index=*/1, |
| 202 | *cert_dp, kVerifyTime, kAgeOneWeek); |
| 203 | EXPECT_EQ(expected_revocation_status, revocation_status); |
| 204 | } |
| 205 | |
| 206 | } // namespace |
| 207 | |
Bob Beck | 5c7a2a0 | 2023-11-20 17:28:21 -0700 | [diff] [blame] | 208 | } // namespace bssl |