| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/cert/cert_verify_proc.h" |
| |
| #include <memory> |
| #include <vector> |
| |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/logging.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/message_loop/message_pump_type.h" |
| #include "base/rand_util.h" |
| #include "base/ranges/algorithm.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/string_util.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/task_environment.h" |
| #include "base/threading/thread.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "crypto/sha2.h" |
| #include "net/base/net_errors.h" |
| #include "net/cert/asn1_util.h" |
| #include "net/cert/cert_net_fetcher.h" |
| #include "net/cert/cert_status_flags.h" |
| #include "net/cert/cert_verifier.h" |
| #include "net/cert/cert_verify_proc_builtin.h" |
| #include "net/cert/cert_verify_result.h" |
| #include "net/cert/crl_set.h" |
| #include "net/cert/ev_root_ca_metadata.h" |
| #include "net/cert/internal/system_trust_store.h" |
| #include "net/cert/ocsp_revocation_status.h" |
| #include "net/cert/pem.h" |
| #include "net/cert/pki/extended_key_usage.h" |
| #include "net/cert/pki/parse_certificate.h" |
| #include "net/cert/pki/signature_algorithm.h" |
| #include "net/cert/pki/trust_store.h" |
| #include "net/cert/test_root_certs.h" |
| #include "net/cert/x509_certificate.h" |
| #include "net/cert/x509_util.h" |
| #include "net/cert_net/cert_net_fetcher_url_request.h" |
| #include "net/der/input.h" |
| #include "net/der/parser.h" |
| #include "net/log/test_net_log.h" |
| #include "net/proxy_resolution/proxy_config.h" |
| #include "net/proxy_resolution/proxy_config_service_fixed.h" |
| #include "net/test/cert_builder.h" |
| #include "net/test/cert_test_util.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "net/test/embedded_test_server/http_request.h" |
| #include "net/test/embedded_test_server/http_response.h" |
| #include "net/test/gtest_util.h" |
| #include "net/test/revocation_builder.h" |
| #include "net/test/test_certificate_data.h" |
| #include "net/test/test_data_directory.h" |
| #include "net/url_request/url_request_context.h" |
| #include "net/url_request/url_request_context_builder.h" |
| #include "net/url_request/url_request_context_getter.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/boringssl/src/include/openssl/bytestring.h" |
| #include "third_party/boringssl/src/include/openssl/mem.h" |
| #include "third_party/boringssl/src/include/openssl/pool.h" |
| |
| #if BUILDFLAG(IS_ANDROID) |
| #include "net/cert/cert_verify_proc_android.h" |
| #elif BUILDFLAG(IS_IOS) |
| #include "base/ios/ios_util.h" |
| #include "net/cert/cert_verify_proc_ios.h" |
| #elif BUILDFLAG(IS_MAC) |
| #include "base/mac/mac_util.h" |
| #include "net/cert/internal/trust_store_mac.h" |
| #endif |
| |
| #if BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED) |
| #include "net/cert/internal/trust_store_chrome.h" |
| #endif |
| |
| // TODO(crbug.com/649017): Add tests that only certificates with |
| // serverAuth are accepted. |
| |
| using net::test::IsError; |
| using net::test::IsOk; |
| |
| using base::HexEncode; |
| |
| namespace net { |
| |
| namespace { |
| |
| const char kTrustAnchorVerifyHistogram[] = "Net.Certificate.TrustAnchor.Verify"; |
| const char kTrustAnchorVerifyOutOfDateHistogram[] = |
| "Net.Certificate.TrustAnchor.VerifyOutOfDate"; |
| |
| // Returns a TLV to use as an unknown signature algorithm when building a cert. |
| // The specific contents are as follows (the OID is from |
| // https://davidben.net/oid): |
| // |
| // SEQUENCE { |
| // OBJECT_IDENTIFIER { 1.2.840.113554.4.1.72585.0 } |
| // NULL {} |
| // } |
| std::string TestOid0SignatureAlgorithmTLV() { |
| constexpr uint8_t kTestOid0SigAlgTLV[] = {0x30, 0x10, 0x06, 0x0c, 0x2a, 0x86, |
| 0x48, 0x86, 0xf7, 0x12, 0x04, 0x01, |
| 0x84, 0xb7, 0x09, 0x00, 0x05, 0x00}; |
| return std::string(std::begin(kTestOid0SigAlgTLV), |
| std::end(kTestOid0SigAlgTLV)); |
| } |
| |
| // An OID for use in tests, from https://davidben.net/oid |
| // OBJECT_IDENTIFIER { 1.2.840.113554.4.1.72585.0 } |
| der::Input TestOid0() { |
| static uint8_t kTestOid0[] = {0x06, 0x0c, 0x2a, 0x86, 0x48, 0x86, 0xf7, |
| 0x12, 0x04, 0x01, 0x84, 0xb7, 0x09, 0x00}; |
| return der::Input(kTestOid0); |
| } |
| |
| // Mock CertVerifyProc that sets the CertVerifyResult to a given value for |
| // all certificates that are Verify()'d |
| class MockCertVerifyProc : public CertVerifyProc { |
| public: |
| explicit MockCertVerifyProc(const CertVerifyResult& result) |
| : CertVerifyProc(CRLSet::BuiltinCRLSet()), result_(result) {} |
| MockCertVerifyProc(const CertVerifyResult& result, int error) |
| : CertVerifyProc(CRLSet::BuiltinCRLSet()), |
| result_(result), |
| error_(error) {} |
| |
| MockCertVerifyProc(const MockCertVerifyProc&) = delete; |
| MockCertVerifyProc& operator=(const MockCertVerifyProc&) = delete; |
| |
| // CertVerifyProc implementation: |
| bool SupportsAdditionalTrustAnchors() const override { return false; } |
| |
| protected: |
| ~MockCertVerifyProc() override = default; |
| |
| private: |
| int VerifyInternal(X509Certificate* cert, |
| const std::string& hostname, |
| const std::string& ocsp_response, |
| const std::string& sct_list, |
| int flags, |
| const CertificateList& additional_trust_anchors, |
| CertVerifyResult* verify_result, |
| const NetLogWithSource& net_log) override; |
| |
| const CertVerifyResult result_; |
| const int error_ = OK; |
| }; |
| |
| int MockCertVerifyProc::VerifyInternal( |
| X509Certificate* cert, |
| const std::string& hostname, |
| const std::string& ocsp_response, |
| const std::string& sct_list, |
| int flags, |
| const CertificateList& additional_trust_anchors, |
| CertVerifyResult* verify_result, |
| const NetLogWithSource& net_log) { |
| *verify_result = result_; |
| verify_result->verified_cert = cert; |
| return error_; |
| } |
| |
| // This enum identifies a concrete implemenation of CertVerifyProc. |
| // |
| // The type is erased by CreateCertVerifyProc(), however needs to be known for |
| // some of the test expectations. |
| enum CertVerifyProcType { |
| CERT_VERIFY_PROC_ANDROID, |
| CERT_VERIFY_PROC_IOS, |
| CERT_VERIFY_PROC_BUILTIN, |
| CERT_VERIFY_PROC_BUILTIN_CHROME_ROOTS, |
| }; |
| |
| // Returns a textual description of the CertVerifyProc implementation |
| // that is being tested, used to give better names to parameterized |
| // tests. |
| std::string VerifyProcTypeToName( |
| const testing::TestParamInfo<CertVerifyProcType>& params) { |
| switch (params.param) { |
| case CERT_VERIFY_PROC_ANDROID: |
| return "CertVerifyProcAndroid"; |
| case CERT_VERIFY_PROC_IOS: |
| return "CertVerifyProcIOS"; |
| case CERT_VERIFY_PROC_BUILTIN: |
| return "CertVerifyProcBuiltin"; |
| case CERT_VERIFY_PROC_BUILTIN_CHROME_ROOTS: |
| return "CertVerifyProcBuiltinChromeRoots"; |
| } |
| |
| return ""; |
| } |
| |
| scoped_refptr<CertVerifyProc> CreateCertVerifyProc( |
| CertVerifyProcType type, |
| scoped_refptr<CertNetFetcher> cert_net_fetcher, |
| scoped_refptr<CRLSet> crl_set) { |
| switch (type) { |
| #if BUILDFLAG(IS_ANDROID) |
| case CERT_VERIFY_PROC_ANDROID: |
| return base::MakeRefCounted<CertVerifyProcAndroid>( |
| std::move(cert_net_fetcher), std::move(crl_set)); |
| #elif BUILDFLAG(IS_IOS) |
| case CERT_VERIFY_PROC_IOS: |
| return base::MakeRefCounted<CertVerifyProcIOS>(std::move(crl_set)); |
| #endif |
| #if BUILDFLAG(IS_FUCHSIA) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || \ |
| defined(STARBOARD) |
| case CERT_VERIFY_PROC_BUILTIN: |
| return CreateCertVerifyProcBuiltin(std::move(cert_net_fetcher), |
| std::move(crl_set), |
| CreateSslSystemTrustStore()); |
| #endif |
| #if BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED) |
| case CERT_VERIFY_PROC_BUILTIN_CHROME_ROOTS: |
| return CreateCertVerifyProcBuiltin( |
| std::move(cert_net_fetcher), std::move(crl_set), |
| CreateSslSystemTrustStoreChromeRoot( |
| std::make_unique<net::TrustStoreChrome>())); |
| #endif |
| default: |
| return nullptr; |
| } |
| } |
| |
| // The set of all CertVerifyProcTypes that tests should be parameterized on. |
| // This needs to be kept in sync with CertVerifyProc::CreateSystemVerifyProc() |
| // and the platforms where CreateSslSystemTrustStore() is not a dummy store. |
| // TODO(crbug.com/649017): Enable CERT_VERIFY_PROC_BUILTIN everywhere. Right |
| // now this is gated on having CertVerifyProcBuiltin understand the roots added |
| // via TestRootCerts. |
| constexpr CertVerifyProcType kAllCertVerifiers[] = { |
| #if BUILDFLAG(IS_ANDROID) |
| CERT_VERIFY_PROC_ANDROID, |
| #elif BUILDFLAG(IS_IOS) |
| CERT_VERIFY_PROC_IOS, |
| #elif BUILDFLAG(IS_FUCHSIA) || BUILDFLAG(IS_LINUX) || \ |
| BUILDFLAG(IS_CHROMEOS) || defined(STARBOARD) |
| CERT_VERIFY_PROC_BUILTIN, |
| #endif |
| #if BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED) |
| CERT_VERIFY_PROC_BUILTIN_CHROME_ROOTS, |
| #endif |
| }; |
| static_assert(std::size(kAllCertVerifiers) != 0, "Unsupported platform"); |
| |
| // Returns true if a test root added through ScopedTestRoot can verify |
| // successfully as a target certificate with chain of length 1 on the given |
| // CertVerifyProcType. |
| bool ScopedTestRootCanTrustTargetCert(CertVerifyProcType verify_proc_type) { |
| return verify_proc_type == CERT_VERIFY_PROC_IOS || |
| verify_proc_type == CERT_VERIFY_PROC_ANDROID; |
| } |
| |
| // Returns true if a non-self-signed CA certificate added through |
| // ScopedTestRoot can verify successfully as the root of a chain by the given |
| // CertVerifyProcType. |
| bool ScopedTestRootCanTrustIntermediateCert( |
| CertVerifyProcType verify_proc_type) { |
| return verify_proc_type == CERT_VERIFY_PROC_IOS || |
| verify_proc_type == CERT_VERIFY_PROC_BUILTIN || |
| verify_proc_type == CERT_VERIFY_PROC_BUILTIN_CHROME_ROOTS || |
| verify_proc_type == CERT_VERIFY_PROC_ANDROID; |
| } |
| |
| std::string MakeRandomHexString(size_t num_bytes) { |
| std::vector<char> rand_bytes; |
| rand_bytes.resize(num_bytes); |
| |
| base::RandBytes(rand_bytes.data(), rand_bytes.size()); |
| return base::HexEncode(rand_bytes.data(), rand_bytes.size()); |
| } |
| |
| } // namespace |
| |
| // This fixture is for tests that apply to concrete implementations of |
| // CertVerifyProc. It will be run for all of the concrete CertVerifyProc types. |
| // |
| // It is called "Internal" as it tests the internal methods like |
| // "VerifyInternal()". |
| class CertVerifyProcInternalTest |
| : public testing::TestWithParam<CertVerifyProcType> { |
| protected: |
| void SetUp() override { SetUpCertVerifyProc(CRLSet::BuiltinCRLSet()); } |
| |
| // CertNetFetcher may be initialized by subclasses that want to use net |
| // fetching by calling SetUpWithCertNetFetcher instead of SetUp. |
| void SetUpWithCertNetFetcher(scoped_refptr<CertNetFetcher> cert_net_fetcher, |
| scoped_refptr<CRLSet> crl_set) { |
| CertVerifyProcType type = verify_proc_type(); |
| verify_proc_ = CreateCertVerifyProc(type, std::move(cert_net_fetcher), |
| std::move(crl_set)); |
| ASSERT_TRUE(verify_proc_); |
| } |
| |
| virtual void SetUpCertVerifyProc(scoped_refptr<CRLSet> crl_set) { |
| SetUpWithCertNetFetcher(nullptr, std::move(crl_set)); |
| } |
| |
| int Verify(X509Certificate* cert, |
| const std::string& hostname, |
| int flags, |
| const CertificateList& additional_trust_anchors, |
| CertVerifyResult* verify_result, |
| const NetLogWithSource& net_log) { |
| return verify_proc_->Verify(cert, hostname, /*ocsp_response=*/std::string(), |
| /*sct_list=*/std::string(), flags, |
| additional_trust_anchors, verify_result, |
| net_log); |
| } |
| |
| int Verify(X509Certificate* cert, |
| const std::string& hostname, |
| int flags, |
| const CertificateList& additional_trust_anchors, |
| CertVerifyResult* verify_result) { |
| return Verify(cert, hostname, flags, additional_trust_anchors, |
| verify_result, NetLogWithSource()); |
| } |
| |
| int Verify(X509Certificate* cert, const std::string& hostname) { |
| CertVerifyResult verify_result; |
| int flags = 0; |
| return Verify(cert, hostname, flags, CertificateList(), &verify_result); |
| } |
| |
| CertVerifyProcType verify_proc_type() const { return GetParam(); } |
| |
| bool SupportsAdditionalTrustAnchors() const { |
| return verify_proc_->SupportsAdditionalTrustAnchors(); |
| } |
| |
| // Returns true if the RSA/DSA keysize will be considered weak on the current |
| // platform. IsInvalidRsaDsaKeySize should be checked prior, since some very |
| // weak keys may be considered invalid. |
| bool IsWeakRsaDsaKeySize(int size) const { |
| #if BUILDFLAG(IS_IOS) |
| // Beginning with iOS 13, the minimum key size for RSA/DSA algorithms is |
| // 2048 bits. See https://support.apple.com/en-us/HT210176 |
| if (verify_proc_type() == CERT_VERIFY_PROC_IOS && |
| base::ios::IsRunningOnIOS13OrLater()) { |
| return size < 2048; |
| } |
| #endif |
| |
| return size < 1024; |
| } |
| |
| // Returns true if the RSA/DSA keysize will be considered invalid on the |
| // current platform. |
| bool IsInvalidRsaDsaKeySize(int size) const { |
| #if BUILDFLAG(IS_IOS) |
| if (base::ios::IsRunningOnIOS12OrLater()) { |
| // On iOS using SecTrustEvaluateWithError it is not possible to |
| // distinguish between weak and invalid key sizes. |
| return IsWeakRsaDsaKeySize(size); |
| } |
| #endif |
| |
| // This platform does not mark certificates with weak keys as invalid. |
| return false; |
| } |
| |
| static bool ParseKeyType(const std::string& key_type, |
| std::string* type, |
| int* size) { |
| size_t pos = key_type.find("-"); |
| std::string size_str = key_type.substr(0, pos); |
| *type = key_type.substr(pos + 1); |
| return base::StringToInt(size_str, size); |
| } |
| |
| // Some platforms may reject certificates with very weak keys as invalid. |
| bool IsInvalidKeyType(const std::string& key_type) const { |
| std::string type; |
| int size = 0; |
| if (!ParseKeyType(key_type, &type, &size)) |
| return false; |
| |
| if (type == "rsa" || type == "dsa") |
| return IsInvalidRsaDsaKeySize(size); |
| |
| return false; |
| } |
| |
| // Currently, only RSA and DSA keys are checked for weakness, and our example |
| // weak size is 768. These could change in the future. |
| // |
| // Note that this means there may be false negatives: keys for other |
| // algorithms and which are weak will pass this test. |
| // |
| // Also, IsInvalidKeyType should be checked prior, since some weak keys may be |
| // considered invalid. |
| bool IsWeakKeyType(const std::string& key_type) const { |
| std::string type; |
| int size = 0; |
| if (!ParseKeyType(key_type, &type, &size)) |
| return false; |
| |
| if (type == "rsa" || type == "dsa") |
| return IsWeakRsaDsaKeySize(size); |
| |
| return false; |
| } |
| |
| bool SupportsCRLSet() const { return VerifyProcTypeIsBuiltin(); } |
| |
| bool SupportsCRLSetsInPathBuilding() const { |
| return VerifyProcTypeIsBuiltin(); |
| } |
| |
| bool SupportsEV() const { |
| // Android and iOS do not support EV. See https://crbug.com/117478#7 |
| #if defined(PLATFORM_USES_CHROMIUM_EV_METADATA) |
| return true; |
| #else |
| return false; |
| #endif |
| } |
| |
| bool SupportsSoftFailRevChecking() const { return VerifyProcTypeIsBuiltin(); } |
| |
| bool SupportsRevCheckingRequiredLocalAnchors() const { |
| return VerifyProcTypeIsBuiltin(); |
| } |
| |
| bool VerifyProcTypeIsBuiltin() const { |
| return verify_proc_type() == CERT_VERIFY_PROC_BUILTIN || |
| verify_proc_type() == CERT_VERIFY_PROC_BUILTIN_CHROME_ROOTS; |
| } |
| |
| bool VerifyProcTypeIsIOSAtMostOS14() const { |
| #if BUILDFLAG(IS_IOS) |
| if (verify_proc_type() == CERT_VERIFY_PROC_IOS && |
| !base::ios::IsRunningOnIOS15OrLater()) { |
| return true; |
| } |
| #endif |
| return false; |
| } |
| |
| bool VerifyProcTypeIsIOSAtMostOS15() const { |
| #if BUILDFLAG(IS_IOS) |
| if (verify_proc_type() == CERT_VERIFY_PROC_IOS && |
| !base::ios::IsRunningOnIOS16OrLater()) { |
| return true; |
| } |
| #endif |
| return false; |
| } |
| |
| CertVerifyProc* verify_proc() const { return verify_proc_.get(); } |
| |
| private: |
| scoped_refptr<CertVerifyProc> verify_proc_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| CertVerifyProcInternalTest, |
| testing::ValuesIn(kAllCertVerifiers), |
| VerifyProcTypeToName); |
| |
| TEST_P(CertVerifyProcInternalTest, DistrustedIntermediate) { |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| constexpr char kHostname[] = "www.example.com"; |
| |
| // Chain should not verify without any ScopedTestRoots. |
| EXPECT_THAT(Verify(chain.get(), kHostname), |
| IsError(ERR_CERT_AUTHORITY_INVALID)); |
| |
| // Trusting root should cause chain to verify successfully. |
| ScopedTestRoot trust_root(root->GetX509Certificate().get(), |
| CertificateTrust::ForTrustAnchor()); |
| EXPECT_THAT(Verify(chain.get(), kHostname), IsOk()); |
| |
| ScopedTestRoot distrust_intermediate(intermediate->GetX509Certificate().get(), |
| CertificateTrust::ForDistrusted()); |
| if (VerifyProcTypeIsBuiltin()) { |
| // Distrusting intermediate should cause chain to not verify again. |
| EXPECT_THAT(Verify(chain.get(), kHostname), |
| IsError(ERR_CERT_AUTHORITY_INVALID)); |
| } else { |
| // Specifying trust types for the platform verifiers through ScopedTestRoot |
| // is not supported, so this should still verify successfully. |
| EXPECT_THAT(Verify(chain.get(), kHostname), IsOk()); |
| } |
| } |
| |
| // Tests that a certificate is recognized as EV, when the valid EV policy OID |
| // for the trust anchor is the second candidate EV oid in the target |
| // certificate. This is a regression test for crbug.com/705285. |
| TEST_P(CertVerifyProcInternalTest, EVVerificationMultipleOID) { |
| if (!SupportsEV()) { |
| LOG(INFO) << "Skipping test as EV verification is not yet supported"; |
| return; |
| } |
| |
| auto [leaf, root] = CertBuilder::CreateSimpleChain2(); |
| |
| // The policies that target certificate asserts. |
| static const char kOtherTestCertPolicy[] = "2.23.140.1.1"; |
| static const char kEVTestCertPolicy[] = "1.2.3.4"; |
| // Specify the extraneous policy first, then the actual policy. |
| leaf->SetCertificatePolicies({kOtherTestCertPolicy, kEVTestCertPolicy}); |
| |
| scoped_refptr<X509Certificate> cert = leaf->GetX509Certificate(); |
| ScopedTestRoot test_root(root->GetX509Certificate().get()); |
| |
| // Build a CRLSet that covers the target certificate. |
| // |
| // This way CRLSet coverage will be sufficient for EV revocation checking, |
| // so this test does not depend on online revocation checking. |
| base::StringPiece spki; |
| ASSERT_TRUE(asn1::ExtractSPKIFromDERCert( |
| x509_util::CryptoBufferAsStringPiece(root->GetCertBuffer()), &spki)); |
| SHA256HashValue spki_sha256; |
| crypto::SHA256HashString(spki, spki_sha256.data, sizeof(spki_sha256.data)); |
| SetUpCertVerifyProc(CRLSet::ForTesting(false, &spki_sha256, "", "", {})); |
| |
| // Consider the root of the test chain a valid EV root for the test policy. |
| ScopedTestEVPolicy scoped_test_ev_policy( |
| EVRootCAMetadata::GetInstance(), |
| X509Certificate::CalculateFingerprint256(root->GetCertBuffer()), |
| kEVTestCertPolicy); |
| ScopedTestEVPolicy scoped_test_other_policy( |
| EVRootCAMetadata::GetInstance(), SHA256HashValue(), kOtherTestCertPolicy); |
| |
| CertVerifyResult verify_result; |
| int flags = 0; |
| int error = Verify(cert.get(), "www.example.com", flags, CertificateList(), |
| &verify_result); |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_IS_EV); |
| } |
| |
| // Target cert has an EV policy, and verifies successfully, but has a chain of |
| // length 1 because the target cert was directly trusted in the trust store. |
| // Should verify OK but not with STATUS_IS_EV. |
| TEST_P(CertVerifyProcInternalTest, TrustedTargetCertWithEVPolicy) { |
| auto [leaf, root] = CertBuilder::CreateSimpleChain2(); |
| |
| static const char kEVTestCertPolicy[] = "1.2.3.4"; |
| leaf->SetCertificatePolicies({kEVTestCertPolicy}); |
| ScopedTestEVPolicy scoped_test_ev_policy( |
| EVRootCAMetadata::GetInstance(), SHA256HashValue(), kEVTestCertPolicy); |
| |
| scoped_refptr<X509Certificate> cert = leaf->GetX509Certificate(); |
| ScopedTestRoot scoped_test_root(cert.get()); |
| |
| CertVerifyResult verify_result; |
| int flags = 0; |
| int error = Verify(cert.get(), "www.example.com", flags, CertificateList(), |
| &verify_result); |
| if (ScopedTestRootCanTrustTargetCert(verify_proc_type())) { |
| EXPECT_THAT(error, IsOk()); |
| ASSERT_TRUE(verify_result.verified_cert); |
| EXPECT_TRUE(verify_result.verified_cert->intermediate_buffers().empty()); |
| } else { |
| EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID)); |
| } |
| EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_IS_EV); |
| } |
| |
| // Target cert has an EV policy, and verifies successfully with a chain of |
| // length 1, and its fingerprint matches the cert fingerprint for that ev |
| // policy. This should never happen in reality, but just test that things don't |
| // explode if it does. |
| TEST_P(CertVerifyProcInternalTest, |
| TrustedTargetCertWithEVPolicyAndEVFingerprint) { |
| auto [leaf, root] = CertBuilder::CreateSimpleChain2(); |
| |
| static const char kEVTestCertPolicy[] = "1.2.3.4"; |
| leaf->SetCertificatePolicies({kEVTestCertPolicy}); |
| ScopedTestEVPolicy scoped_test_ev_policy( |
| EVRootCAMetadata::GetInstance(), |
| X509Certificate::CalculateFingerprint256(leaf->GetCertBuffer()), |
| kEVTestCertPolicy); |
| scoped_refptr<X509Certificate> cert = leaf->GetX509Certificate(); |
| ScopedTestRoot scoped_test_root(cert.get()); |
| |
| CertVerifyResult verify_result; |
| int flags = 0; |
| int error = Verify(cert.get(), "www.example.com", flags, CertificateList(), |
| &verify_result); |
| if (ScopedTestRootCanTrustTargetCert(verify_proc_type())) { |
| EXPECT_THAT(error, IsOk()); |
| ASSERT_TRUE(verify_result.verified_cert); |
| EXPECT_TRUE(verify_result.verified_cert->intermediate_buffers().empty()); |
| } else { |
| EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID)); |
| } |
| // An EV Root certificate should never be used as an end-entity certificate. |
| EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_IS_EV); |
| } |
| |
| // Target cert has an EV policy, and has a valid path to the EV root, but the |
| // intermediate has been trusted directly. Should stop building the path at the |
| // intermediate and verify OK but not with STATUS_IS_EV. |
| // See https://crbug.com/979801 |
| TEST_P(CertVerifyProcInternalTest, TrustedIntermediateCertWithEVPolicy) { |
| if (!SupportsEV()) { |
| LOG(INFO) << "Skipping test as EV verification is not yet supported"; |
| return; |
| } |
| if (!ScopedTestRootCanTrustIntermediateCert(verify_proc_type())) { |
| LOG(INFO) << "Skipping test as intermediate cert cannot be trusted"; |
| return; |
| } |
| |
| for (bool trust_the_intermediate : {false, true}) { |
| SCOPED_TRACE(trust_the_intermediate); |
| |
| // Need to build unique certs for each try otherwise caching can break |
| // things. |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| static const char kEVTestCertPolicy[] = "1.2.3.4"; |
| leaf->SetCertificatePolicies({kEVTestCertPolicy}); |
| intermediate->SetCertificatePolicies({kEVTestCertPolicy}); |
| // Consider the root of the test chain a valid EV root for the test policy. |
| ScopedTestEVPolicy scoped_test_ev_policy( |
| EVRootCAMetadata::GetInstance(), |
| X509Certificate::CalculateFingerprint256(root->GetCertBuffer()), |
| kEVTestCertPolicy); |
| |
| scoped_refptr<X509Certificate> cert = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(cert.get()); |
| |
| scoped_refptr<X509Certificate> intermediate_cert = |
| intermediate->GetX509Certificate(); |
| ASSERT_TRUE(intermediate_cert.get()); |
| |
| scoped_refptr<X509Certificate> root_cert = root->GetX509Certificate(); |
| ASSERT_TRUE(root_cert.get()); |
| |
| if (!trust_the_intermediate) { |
| // First trust just the root. This verifies that the test setup is |
| // actually correct. |
| #if defined(STARBOARD) |
| ScopedTestRoot scoped_test_root(root_cert.get()); |
| #else |
| ScopedTestRoot scoped_test_root({root_cert}); |
| #endif |
| CertVerifyResult verify_result; |
| int flags = 0; |
| int error = Verify(cert.get(), "www.example.com", flags, |
| CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsOk()); |
| ASSERT_TRUE(verify_result.verified_cert); |
| // Verified chain should include the intermediate and the root. |
| EXPECT_EQ(2U, verify_result.verified_cert->intermediate_buffers().size()); |
| // Should be EV. |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_IS_EV); |
| } else { |
| // Now try with trusting both the intermediate and the root. |
| ScopedTestRoot scoped_test_root({intermediate_cert, root_cert}); |
| CertVerifyResult verify_result; |
| int flags = 0; |
| int error = Verify(cert.get(), "www.example.com", flags, |
| CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsOk()); |
| ASSERT_TRUE(verify_result.verified_cert); |
| // Verified chain should only go to the trusted intermediate, not the |
| // root. |
| EXPECT_EQ(1U, verify_result.verified_cert->intermediate_buffers().size()); |
| // Should not be EV. |
| EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_IS_EV); |
| } |
| } |
| } |
| |
| TEST_P(CertVerifyProcInternalTest, CertWithNullInCommonNameAndNoSAN) { |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| leaf->EraseExtension(der::Input(kSubjectAltNameOid)); |
| |
| std::string common_name; |
| common_name += "www.fake.com"; |
| common_name += '\0'; |
| common_name += "a" + MakeRandomHexString(12) + ".example.com"; |
| leaf->SetSubjectCommonName(common_name); |
| |
| // Trust the root and build a chain to verify that includes the intermediate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = Verify(chain.get(), "www.fake.com", flags, CertificateList(), |
| &verify_result); |
| |
| // This actually fails because Chrome only looks for hostnames in |
| // SubjectAltNames now and no SubjectAltName is present. |
| EXPECT_THAT(error, IsError(ERR_CERT_COMMON_NAME_INVALID)); |
| } |
| |
| TEST_P(CertVerifyProcInternalTest, CertWithNullInCommonNameAndValidSAN) { |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| leaf->SetSubjectAltName("www.fake.com"); |
| |
| std::string common_name; |
| common_name += "www.fake.com"; |
| common_name += '\0'; |
| common_name += "a" + MakeRandomHexString(12) + ".example.com"; |
| leaf->SetSubjectCommonName(common_name); |
| |
| // Trust the root and build a chain to verify that includes the intermediate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = Verify(chain.get(), "www.fake.com", flags, CertificateList(), |
| &verify_result); |
| |
| // SubjectAltName is valid and Chrome does not use the common name. |
| EXPECT_THAT(error, IsOk()); |
| } |
| |
| TEST_P(CertVerifyProcInternalTest, CertWithNullInSAN) { |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| std::string hostname; |
| hostname += "www.fake.com"; |
| hostname += '\0'; |
| hostname += "a" + MakeRandomHexString(12) + ".example.com"; |
| leaf->SetSubjectAltName(hostname); |
| |
| // Trust the root and build a chain to verify that includes the intermediate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = Verify(chain.get(), "www.fake.com", flags, CertificateList(), |
| &verify_result); |
| |
| // SubjectAltName is invalid. |
| EXPECT_THAT(error, IsError(ERR_CERT_COMMON_NAME_INVALID)); |
| } |
| |
| // Tests the case where the target certificate is accepted by |
| // X509CertificateBytes, but has errors that should cause verification to fail. |
| TEST_P(CertVerifyProcInternalTest, InvalidTarget) { |
| base::FilePath certs_dir = |
| GetTestNetDataDirectory().AppendASCII("parse_certificate_unittest"); |
| scoped_refptr<X509Certificate> bad_cert; |
| if (VerifyProcTypeIsBuiltin()) { |
| // Builtin verifier doesn't distinguish between invalid signature algorithm |
| // and unknown signature algorithm, so use a different test file that will |
| // fail in ParsedCertificate::Create. The other verifiers use a different |
| // test file since the platform verifiers don't all consider empty |
| // extensions sequence invalid. |
| bad_cert = ImportCertFromFile(certs_dir, "extensions_empty_sequence.pem"); |
| } else { |
| bad_cert = ImportCertFromFile(certs_dir, "signature_algorithm_null.pem"); |
| } |
| ASSERT_TRUE(bad_cert); |
| |
| scoped_refptr<X509Certificate> ok_cert( |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem")); |
| ASSERT_TRUE(ok_cert); |
| |
| std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates; |
| intermediates.push_back(bssl::UpRef(ok_cert->cert_buffer())); |
| scoped_refptr<X509Certificate> cert_with_bad_target( |
| X509Certificate::CreateFromBuffer(bssl::UpRef(bad_cert->cert_buffer()), |
| std::move(intermediates))); |
| ASSERT_TRUE(cert_with_bad_target); |
| EXPECT_EQ(1U, cert_with_bad_target->intermediate_buffers().size()); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = Verify(cert_with_bad_target.get(), "127.0.0.1", flags, |
| CertificateList(), &verify_result); |
| |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_INVALID); |
| EXPECT_THAT(error, IsError(ERR_CERT_INVALID)); |
| } |
| |
| // Tests the case where an intermediate certificate is accepted by |
| // X509CertificateBytes, but has errors that should prevent using it during |
| // verification. The verification should succeed, since the intermediate |
| // wasn't necessary. |
| TEST_P(CertVerifyProcInternalTest, UnnecessaryInvalidIntermediate) { |
| ScopedTestRoot test_root( |
| ImportCertFromFile(GetTestCertsDirectory(), "root_ca_cert.pem").get()); |
| |
| base::FilePath certs_dir = |
| GetTestNetDataDirectory().AppendASCII("parse_certificate_unittest"); |
| bssl::UniquePtr<CRYPTO_BUFFER> bad_cert = |
| x509_util::CreateCryptoBuffer(base::StringPiece("invalid")); |
| ASSERT_TRUE(bad_cert); |
| |
| scoped_refptr<X509Certificate> ok_cert( |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem")); |
| ASSERT_TRUE(ok_cert); |
| |
| std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates; |
| intermediates.push_back(std::move(bad_cert)); |
| scoped_refptr<X509Certificate> cert_with_bad_intermediate( |
| X509Certificate::CreateFromBuffer(bssl::UpRef(ok_cert->cert_buffer()), |
| std::move(intermediates))); |
| ASSERT_TRUE(cert_with_bad_intermediate); |
| EXPECT_EQ(1U, cert_with_bad_intermediate->intermediate_buffers().size()); |
| |
| RecordingNetLogObserver net_log_observer(NetLogCaptureMode::kDefault); |
| NetLogWithSource net_log(NetLogWithSource::Make( |
| net::NetLog::Get(), net::NetLogSourceType::CERT_VERIFIER_TASK)); |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = Verify(cert_with_bad_intermediate.get(), "127.0.0.1", flags, |
| CertificateList(), &verify_result, net_log); |
| |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_EQ(0u, verify_result.cert_status); |
| |
| auto events = net_log_observer.GetEntriesForSource(net_log.source()); |
| EXPECT_FALSE(events.empty()); |
| |
| auto event = base::ranges::find(events, NetLogEventType::CERT_VERIFY_PROC, |
| &NetLogEntry::type); |
| ASSERT_NE(event, events.end()); |
| EXPECT_EQ(net::NetLogEventPhase::BEGIN, event->phase); |
| const std::string* host = event->params.FindString("host"); |
| ASSERT_TRUE(host); |
| EXPECT_EQ("127.0.0.1", *host); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| event = |
| base::ranges::find(events, NetLogEventType::CERT_VERIFY_PROC_INPUT_CERT, |
| &NetLogEntry::type); |
| ASSERT_NE(event, events.end()); |
| EXPECT_EQ(net::NetLogEventPhase::NONE, event->phase); |
| const std::string* errors = event->params.FindString("errors"); |
| ASSERT_TRUE(errors); |
| EXPECT_EQ( |
| "ERROR: Failed parsing Certificate SEQUENCE\nERROR: Failed parsing " |
| "Certificate\n", |
| *errors); |
| } |
| } |
| |
| TEST_P(CertVerifyProcInternalTest, RejectExpiredCert) { |
| base::FilePath certs_dir = GetTestCertsDirectory(); |
| |
| // Load root_ca_cert.pem into the test root store. |
| ScopedTestRoot test_root( |
| ImportCertFromFile(certs_dir, "root_ca_cert.pem").get()); |
| |
| scoped_refptr<X509Certificate> cert = CreateCertificateChainFromFile( |
| certs_dir, "expired_cert.pem", X509Certificate::FORMAT_AUTO); |
| ASSERT_TRUE(cert); |
| ASSERT_EQ(0U, cert->intermediate_buffers().size()); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = |
| Verify(cert.get(), "127.0.0.1", flags, CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsError(ERR_CERT_DATE_INVALID)); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_DATE_INVALID); |
| } |
| |
| TEST_P(CertVerifyProcInternalTest, RejectWeakKeys) { |
| base::FilePath certs_dir = GetTestCertsDirectory(); |
| typedef std::vector<std::string> Strings; |
| Strings key_types; |
| |
| // generate-weak-test-chains.sh currently has: |
| // key_types="768-rsa 1024-rsa 2048-rsa prime256v1-ecdsa" |
| // We must use the same key types here. The filenames generated look like: |
| // 2048-rsa-ee-by-768-rsa-intermediate.pem |
| key_types.push_back("768-rsa"); |
| key_types.push_back("1024-rsa"); |
| key_types.push_back("2048-rsa"); |
| key_types.push_back("prime256v1-ecdsa"); |
| |
| // Add the root that signed the intermediates for this test. |
| scoped_refptr<X509Certificate> root_cert = |
| ImportCertFromFile(certs_dir, "2048-rsa-root.pem"); |
| ASSERT_NE(static_cast<X509Certificate*>(nullptr), root_cert.get()); |
| ScopedTestRoot scoped_root(root_cert.get()); |
| |
| // Now test each chain. |
| for (Strings::const_iterator ee_type = key_types.begin(); |
| ee_type != key_types.end(); ++ee_type) { |
| for (Strings::const_iterator signer_type = key_types.begin(); |
| signer_type != key_types.end(); ++signer_type) { |
| std::string basename = |
| *ee_type + "-ee-by-" + *signer_type + "-intermediate.pem"; |
| SCOPED_TRACE(basename); |
| scoped_refptr<X509Certificate> ee_cert = |
| ImportCertFromFile(certs_dir, basename); |
| ASSERT_NE(static_cast<X509Certificate*>(nullptr), ee_cert.get()); |
| |
| basename = *signer_type + "-intermediate.pem"; |
| scoped_refptr<X509Certificate> intermediate = |
| ImportCertFromFile(certs_dir, basename); |
| ASSERT_NE(static_cast<X509Certificate*>(nullptr), intermediate.get()); |
| |
| std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates; |
| intermediates.push_back(bssl::UpRef(intermediate->cert_buffer())); |
| scoped_refptr<X509Certificate> cert_chain = |
| X509Certificate::CreateFromBuffer(bssl::UpRef(ee_cert->cert_buffer()), |
| std::move(intermediates)); |
| ASSERT_TRUE(cert_chain); |
| |
| CertVerifyResult verify_result; |
| int error = Verify(cert_chain.get(), "127.0.0.1", 0, CertificateList(), |
| &verify_result); |
| |
| if (IsInvalidKeyType(*ee_type) || IsInvalidKeyType(*signer_type)) { |
| EXPECT_NE(OK, error); |
| EXPECT_EQ(CERT_STATUS_INVALID, |
| verify_result.cert_status & CERT_STATUS_INVALID); |
| } else if (IsWeakKeyType(*ee_type) || IsWeakKeyType(*signer_type)) { |
| EXPECT_NE(OK, error); |
| EXPECT_EQ(CERT_STATUS_WEAK_KEY, |
| verify_result.cert_status & CERT_STATUS_WEAK_KEY); |
| EXPECT_EQ(0u, verify_result.cert_status & CERT_STATUS_INVALID); |
| } else { |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_EQ(0U, verify_result.cert_status & CERT_STATUS_WEAK_KEY); |
| } |
| } |
| } |
| } |
| |
| // Regression test for http://crbug.com/108514. |
| TEST_P(CertVerifyProcInternalTest, ExtraneousMD5RootCert) { |
| base::FilePath certs_dir = GetTestCertsDirectory(); |
| |
| scoped_refptr<X509Certificate> server_cert = |
| ImportCertFromFile(certs_dir, "cross-signed-leaf.pem"); |
| ASSERT_NE(static_cast<X509Certificate*>(nullptr), server_cert.get()); |
| |
| scoped_refptr<X509Certificate> extra_cert = |
| ImportCertFromFile(certs_dir, "cross-signed-root-md5.pem"); |
| ASSERT_NE(static_cast<X509Certificate*>(nullptr), extra_cert.get()); |
| |
| scoped_refptr<X509Certificate> root_cert = |
| ImportCertFromFile(certs_dir, "cross-signed-root-sha256.pem"); |
| ASSERT_NE(static_cast<X509Certificate*>(nullptr), root_cert.get()); |
| |
| ScopedTestRoot scoped_root(root_cert.get()); |
| |
| std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates; |
| intermediates.push_back(bssl::UpRef(extra_cert->cert_buffer())); |
| scoped_refptr<X509Certificate> cert_chain = X509Certificate::CreateFromBuffer( |
| bssl::UpRef(server_cert->cert_buffer()), std::move(intermediates)); |
| ASSERT_TRUE(cert_chain); |
| |
| CertVerifyResult verify_result; |
| int flags = 0; |
| int error = Verify(cert_chain.get(), "127.0.0.1", flags, CertificateList(), |
| &verify_result); |
| EXPECT_THAT(error, IsOk()); |
| |
| // The extra MD5 root should be discarded |
| ASSERT_TRUE(verify_result.verified_cert.get()); |
| ASSERT_EQ(1u, verify_result.verified_cert->intermediate_buffers().size()); |
| EXPECT_TRUE(x509_util::CryptoBufferEqual( |
| verify_result.verified_cert->intermediate_buffers().front().get(), |
| root_cert->cert_buffer())); |
| } |
| |
| // Test for bug 94673. |
| TEST_P(CertVerifyProcInternalTest, GoogleDigiNotarTest) { |
| base::FilePath certs_dir = GetTestCertsDirectory(); |
| |
| scoped_refptr<X509Certificate> server_cert = |
| ImportCertFromFile(certs_dir, "google_diginotar.pem"); |
| ASSERT_NE(static_cast<X509Certificate*>(nullptr), server_cert.get()); |
| |
| scoped_refptr<X509Certificate> intermediate_cert = |
| ImportCertFromFile(certs_dir, "diginotar_public_ca_2025.pem"); |
| ASSERT_NE(static_cast<X509Certificate*>(nullptr), intermediate_cert.get()); |
| |
| std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates; |
| intermediates.push_back(bssl::UpRef(intermediate_cert->cert_buffer())); |
| scoped_refptr<X509Certificate> cert_chain = X509Certificate::CreateFromBuffer( |
| bssl::UpRef(server_cert->cert_buffer()), std::move(intermediates)); |
| ASSERT_TRUE(cert_chain); |
| |
| CertVerifyResult verify_result; |
| int flags = CertVerifyProc::VERIFY_REV_CHECKING_ENABLED; |
| int error = Verify(cert_chain.get(), "mail.google.com", flags, |
| CertificateList(), &verify_result); |
| EXPECT_NE(OK, error); |
| |
| // Now turn off revocation checking. Certificate verification should still |
| // fail. |
| flags = 0; |
| error = Verify(cert_chain.get(), "mail.google.com", flags, CertificateList(), |
| &verify_result); |
| EXPECT_NE(OK, error); |
| } |
| |
| TEST_P(CertVerifyProcInternalTest, NameConstraintsOk) { |
| auto [leaf, root] = CertBuilder::CreateSimpleChain2(); |
| |
| // Use the private key matching the public_key_hash of the kDomainsTest |
| // constraint in CertVerifyProc::HasNameConstraintsViolation. |
| ASSERT_TRUE(leaf->UseKeyFromFile( |
| GetTestCertsDirectory().AppendASCII("name_constrained_key.pem"))); |
| // example.com is allowed by kDomainsTest, and notarealtld is not a known |
| // TLD, so that's allowed too. |
| leaf->SetSubjectAltNames({"test.ExAmPlE.CoM", "example.notarealtld", |
| "*.test2.ExAmPlE.CoM", "*.example2.notarealtld"}, |
| {}); |
| |
| ScopedTestRoot test_root(root->GetX509Certificate().get()); |
| |
| scoped_refptr<X509Certificate> leaf_cert = leaf->GetX509Certificate(); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = Verify(leaf_cert.get(), "test.example.com", flags, |
| CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_EQ(0U, verify_result.cert_status); |
| |
| error = Verify(leaf_cert.get(), "foo.test2.example.com", flags, |
| CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_EQ(0U, verify_result.cert_status); |
| } |
| |
| TEST_P(CertVerifyProcInternalTest, NameConstraintsFailure) { |
| auto [leaf, root] = CertBuilder::CreateSimpleChain2(); |
| |
| // Use the private key matching the public_key_hash of the kDomainsTest |
| // constraint in CertVerifyProc::HasNameConstraintsViolation. |
| ASSERT_TRUE(leaf->UseKeyFromFile( |
| GetTestCertsDirectory().AppendASCII("name_constrained_key.pem"))); |
| // example.com is allowed by kDomainsTest, but example.org is not. |
| leaf->SetSubjectAltNames({"test.ExAmPlE.CoM", "test.ExAmPlE.OrG"}, {}); |
| |
| ScopedTestRoot test_root(root->GetX509Certificate().get()); |
| |
| scoped_refptr<X509Certificate> leaf_cert = leaf->GetX509Certificate(); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = Verify(leaf_cert.get(), "test.example.com", flags, |
| CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsError(ERR_CERT_NAME_CONSTRAINT_VIOLATION)); |
| EXPECT_EQ(CERT_STATUS_NAME_CONSTRAINT_VIOLATION, |
| verify_result.cert_status & CERT_STATUS_NAME_CONSTRAINT_VIOLATION); |
| } |
| |
| // This fixture is for testing the verification of a certificate chain which |
| // has some sort of mismatched signature algorithm (i.e. |
| // Certificate.signatureAlgorithm and TBSCertificate.algorithm are different). |
| class CertVerifyProcInspectSignatureAlgorithmsTest : public ::testing::Test { |
| protected: |
| // In the test setup, SHA384 is given special treatment as an unknown |
| // algorithm. |
| static constexpr DigestAlgorithm kUnknownDigestAlgorithm = |
| DigestAlgorithm::Sha384; |
| |
| struct CertParams { |
| // Certificate.signatureAlgorithm |
| DigestAlgorithm cert_algorithm; |
| |
| // TBSCertificate.algorithm |
| DigestAlgorithm tbs_algorithm; |
| }; |
| |
| // Shorthand for VerifyChain() where only the leaf's parameters need |
| // to be specified. |
| [[nodiscard]] int VerifyLeaf(const CertParams& leaf_params) { |
| return VerifyChain({// Target |
| leaf_params, |
| // Root |
| {DigestAlgorithm::Sha256, DigestAlgorithm::Sha256}}); |
| } |
| |
| // Shorthand for VerifyChain() where only the intermediate's parameters need |
| // to be specified. |
| [[nodiscard]] int VerifyIntermediate(const CertParams& intermediate_params) { |
| return VerifyChain({// Target |
| {DigestAlgorithm::Sha256, DigestAlgorithm::Sha256}, |
| // Intermediate |
| intermediate_params, |
| // Root |
| {DigestAlgorithm::Sha256, DigestAlgorithm::Sha256}}); |
| } |
| |
| // Shorthand for VerifyChain() where only the root's parameters need to be |
| // specified. |
| [[nodiscard]] int VerifyRoot(const CertParams& root_params) { |
| return VerifyChain({// Target |
| {DigestAlgorithm::Sha256, DigestAlgorithm::Sha256}, |
| // Intermediate |
| {DigestAlgorithm::Sha256, DigestAlgorithm::Sha256}, |
| // Root |
| root_params}); |
| } |
| |
| // Manufactures a certificate chain where each certificate has the indicated |
| // signature algorithms, and then returns the result of verifying this chain. |
| // |
| // TODO(mattm): Replace the custom cert mangling code in this test with |
| // CertBuilder. |
| [[nodiscard]] int VerifyChain(const std::vector<CertParams>& chain_params) { |
| auto chain = CreateChain(chain_params); |
| if (!chain) { |
| ADD_FAILURE() << "Failed creating certificate chain"; |
| return ERR_UNEXPECTED; |
| } |
| |
| int flags = 0; |
| CertVerifyResult dummy_result; |
| CertVerifyResult verify_result; |
| |
| auto verify_proc = base::MakeRefCounted<MockCertVerifyProc>(dummy_result); |
| |
| return verify_proc->Verify( |
| chain.get(), "127.0.0.1", /*ocsp_response=*/std::string(), |
| /*sct_list=*/std::string(), flags, CertificateList(), &verify_result, |
| NetLogWithSource()); |
| } |
| |
| private: |
| // Overwrites the AlgorithmIdentifier pointed to by |algorithm_sequence| with |
| // |algorithm|. Note this violates the constness of StringPiece. |
| [[nodiscard]] static bool SetAlgorithmSequence( |
| DigestAlgorithm algorithm, |
| base::StringPiece* algorithm_sequence) { |
| // This string of bytes is the full SEQUENCE for an AlgorithmIdentifier. |
| std::vector<uint8_t> replacement_sequence; |
| switch (algorithm) { |
| case DigestAlgorithm::Sha1: |
| // sha1WithRSAEncryption |
| replacement_sequence = {0x30, 0x0D, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, |
| 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00}; |
| break; |
| case DigestAlgorithm::Sha256: |
| // sha256WithRSAEncryption |
| replacement_sequence = {0x30, 0x0D, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, |
| 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00}; |
| break; |
| case kUnknownDigestAlgorithm: |
| // This shouldn't be anything meaningful (modified numbers at random). |
| replacement_sequence = {0x30, 0x0D, 0x06, 0x09, 0x8a, 0x87, 0x18, 0x46, |
| 0xd7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00}; |
| break; |
| default: |
| ADD_FAILURE() << "Unsupported digest algorithm"; |
| return false; |
| } |
| |
| // For this simple replacement to work (without modifying any |
| // other sequence lengths) the original algorithm and replacement |
| // algorithm must have the same encoded length. |
| if (algorithm_sequence->size() != replacement_sequence.size()) { |
| ADD_FAILURE() << "AlgorithmIdentifier must have length " |
| << replacement_sequence.size(); |
| return false; |
| } |
| |
| memcpy(const_cast<char*>(algorithm_sequence->data()), |
| replacement_sequence.data(), replacement_sequence.size()); |
| return true; |
| } |
| |
| // Locate the serial number bytes. |
| [[nodiscard]] static bool ExtractSerialNumberFromDERCert( |
| base::StringPiece der_cert, |
| base::StringPiece* serial_value) { |
| der::Parser parser((der::Input(der_cert))); |
| der::Parser certificate; |
| if (!parser.ReadSequence(&certificate)) |
| return false; |
| |
| der::Parser tbs_certificate; |
| if (!certificate.ReadSequence(&tbs_certificate)) |
| return false; |
| |
| bool unused; |
| if (!tbs_certificate.SkipOptionalTag( |
| der::kTagConstructed | der::kTagContextSpecific | 0, &unused)) { |
| return false; |
| } |
| |
| // serialNumber |
| der::Input serial_value_der; |
| if (!tbs_certificate.ReadTag(der::kInteger, &serial_value_der)) |
| return false; |
| |
| *serial_value = serial_value_der.AsStringView(); |
| return true; |
| } |
| |
| // Creates a certificate (based on some base certificate file) using the |
| // specified signature algorithms. |
| static scoped_refptr<X509Certificate> CreateCertificate( |
| const CertParams& params) { |
| // Dosn't really matter which base certificate is used, so long as it is |
| // valid and uses a signature AlgorithmIdentifier with the same encoded |
| // length as sha1WithRSASignature. |
| const char* kLeafFilename = "ok_cert.pem"; |
| |
| auto cert = CreateCertificateChainFromFile( |
| GetTestCertsDirectory(), kLeafFilename, X509Certificate::FORMAT_AUTO); |
| if (!cert) { |
| ADD_FAILURE() << "Failed to load certificate: " << kLeafFilename; |
| return nullptr; |
| } |
| |
| // Start with the DER bytes of a valid certificate. The der data is copied |
| // to a new std::string as it will modified to create a new certificate. |
| std::string cert_der( |
| x509_util::CryptoBufferAsStringPiece(cert->cert_buffer())); |
| |
| // Parse the certificate and identify the locations of interest within |
| // |cert_der|. |
| base::StringPiece cert_algorithm_sequence; |
| base::StringPiece tbs_algorithm_sequence; |
| if (!asn1::ExtractSignatureAlgorithmsFromDERCert( |
| cert_der, &cert_algorithm_sequence, &tbs_algorithm_sequence)) { |
| ADD_FAILURE() << "Failed parsing certificate algorithms"; |
| return nullptr; |
| } |
| |
| base::StringPiece serial_value; |
| if (!ExtractSerialNumberFromDERCert(cert_der, &serial_value)) { |
| ADD_FAILURE() << "Failed parsing certificate serial number"; |
| return nullptr; |
| } |
| |
| // Give each certificate a unique serial number based on its content (which |
| // in turn is a function of |params|, otherwise importing it may fail. |
| |
| // Upper bound for last entry in DigestAlgorithm |
| const int kNumDigestAlgorithms = 15; |
| *const_cast<char*>(serial_value.data()) += |
| static_cast<int>(params.tbs_algorithm) * kNumDigestAlgorithms + |
| static_cast<int>(params.cert_algorithm); |
| |
| // Change the signature AlgorithmIdentifiers. |
| if (!SetAlgorithmSequence(params.cert_algorithm, |
| &cert_algorithm_sequence) || |
| !SetAlgorithmSequence(params.tbs_algorithm, &tbs_algorithm_sequence)) { |
| return nullptr; |
| } |
| |
| // NOTE: The signature is NOT recomputed over TBSCertificate -- for these |
| // tests it isn't needed. |
| return X509Certificate::CreateFromBytes( |
| base::as_bytes(base::make_span(cert_der))); |
| } |
| |
| static scoped_refptr<X509Certificate> CreateChain( |
| const std::vector<CertParams>& params) { |
| // Manufacture a chain with the given combinations of signature algorithms. |
| // This chain isn't actually a valid chain, but it is good enough for |
| // testing the base CertVerifyProc. |
| CertificateList certs; |
| for (const auto& cert_params : params) { |
| certs.push_back(CreateCertificate(cert_params)); |
| if (!certs.back()) |
| return nullptr; |
| } |
| |
| std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates; |
| for (size_t i = 1; i < certs.size(); ++i) |
| intermediates.push_back(bssl::UpRef(certs[i]->cert_buffer())); |
| |
| return X509Certificate::CreateFromBuffer( |
| bssl::UpRef(certs[0]->cert_buffer()), std::move(intermediates)); |
| } |
| }; |
| |
| // This is a control test to make sure that the test helper |
| // VerifyLeaf() works as expected. There is no actual mismatch in the |
| // algorithms used here. |
| // |
| // Certificate.signatureAlgorithm: sha1WithRSASignature |
| // TBSCertificate.algorithm: sha1WithRSAEncryption |
| TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, LeafSha1Sha1) { |
| int rv = VerifyLeaf({DigestAlgorithm::Sha1, DigestAlgorithm::Sha1}); |
| ASSERT_THAT(rv, IsError(ERR_CERT_WEAK_SIGNATURE_ALGORITHM)); |
| } |
| |
| // This is a control test to make sure that the test helper |
| // VerifyLeaf() works as expected. There is no actual mismatch in the |
| // algorithms used here. |
| // |
| // Certificate.signatureAlgorithm: sha256WithRSASignature |
| // TBSCertificate.algorithm: sha256WithRSAEncryption |
| TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, LeafSha256Sha256) { |
| int rv = VerifyLeaf({DigestAlgorithm::Sha256, DigestAlgorithm::Sha256}); |
| ASSERT_THAT(rv, IsOk()); |
| } |
| |
| // Mismatched signature algorithms in the leaf certificate. |
| // |
| // Certificate.signatureAlgorithm: sha1WithRSASignature |
| // TBSCertificate.algorithm: sha256WithRSAEncryption |
| TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, LeafSha1Sha256) { |
| int rv = VerifyLeaf({DigestAlgorithm::Sha1, DigestAlgorithm::Sha256}); |
| ASSERT_THAT(rv, IsError(ERR_CERT_INVALID)); |
| } |
| |
| // Mismatched signature algorithms in the leaf certificate. |
| // |
| // Certificate.signatureAlgorithm: sha256WithRSAEncryption |
| // TBSCertificate.algorithm: sha1WithRSASignature |
| TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, LeafSha256Sha1) { |
| int rv = VerifyLeaf({DigestAlgorithm::Sha256, DigestAlgorithm::Sha1}); |
| ASSERT_THAT(rv, IsError(ERR_CERT_INVALID)); |
| } |
| |
| // Unrecognized signature algorithm in the leaf certificate. |
| // |
| // Certificate.signatureAlgorithm: sha256WithRSAEncryption |
| // TBSCertificate.algorithm: ? |
| TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, LeafSha256Unknown) { |
| int rv = VerifyLeaf({DigestAlgorithm::Sha256, kUnknownDigestAlgorithm}); |
| ASSERT_THAT(rv, IsError(ERR_CERT_INVALID)); |
| } |
| |
| // Unrecognized signature algorithm in the leaf certificate. |
| // |
| // Certificate.signatureAlgorithm: ? |
| // TBSCertificate.algorithm: sha256WithRSAEncryption |
| TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, LeafUnknownSha256) { |
| int rv = VerifyLeaf({kUnknownDigestAlgorithm, DigestAlgorithm::Sha256}); |
| ASSERT_THAT(rv, IsError(ERR_CERT_INVALID)); |
| } |
| |
| // Mismatched signature algorithms in the intermediate certificate. |
| // |
| // Certificate.signatureAlgorithm: sha1WithRSASignature |
| // TBSCertificate.algorithm: sha256WithRSAEncryption |
| TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, IntermediateSha1Sha256) { |
| int rv = VerifyIntermediate({DigestAlgorithm::Sha1, DigestAlgorithm::Sha256}); |
| ASSERT_THAT(rv, IsError(ERR_CERT_INVALID)); |
| } |
| |
| // Mismatched signature algorithms in the intermediate certificate. |
| // |
| // Certificate.signatureAlgorithm: sha256WithRSAEncryption |
| // TBSCertificate.algorithm: sha1WithRSASignature |
| TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, IntermediateSha256Sha1) { |
| int rv = VerifyIntermediate({DigestAlgorithm::Sha256, DigestAlgorithm::Sha1}); |
| ASSERT_THAT(rv, IsError(ERR_CERT_INVALID)); |
| } |
| |
| // Mismatched signature algorithms in the root certificate. |
| // |
| // Certificate.signatureAlgorithm: sha256WithRSAEncryption |
| // TBSCertificate.algorithm: sha1WithRSASignature |
| TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, RootSha256Sha1) { |
| int rv = VerifyRoot({DigestAlgorithm::Sha256, DigestAlgorithm::Sha1}); |
| ASSERT_THAT(rv, IsOk()); |
| } |
| |
| // Unrecognized signature algorithm in the root certificate. |
| // |
| // Certificate.signatureAlgorithm: ? |
| // TBSCertificate.algorithm: sha256WithRSAEncryption |
| TEST_F(CertVerifyProcInspectSignatureAlgorithmsTest, RootUnknownSha256) { |
| int rv = VerifyRoot({kUnknownDigestAlgorithm, DigestAlgorithm::Sha256}); |
| ASSERT_THAT(rv, IsOk()); |
| } |
| |
| TEST(CertVerifyProcTest, TestHasTooLongValidity) { |
| struct { |
| const char* const file; |
| bool is_valid_too_long; |
| } tests[] = { |
| {"start_after_expiry.pem", true}, |
| {"pre_br_validity_ok.pem", false}, |
| {"pre_br_validity_bad_121.pem", true}, |
| {"pre_br_validity_bad_2020.pem", true}, |
| {"10_year_validity.pem", false}, |
| {"11_year_validity.pem", true}, |
| {"39_months_after_2015_04.pem", false}, |
| {"40_months_after_2015_04.pem", true}, |
| {"60_months_after_2012_07.pem", false}, |
| {"61_months_after_2012_07.pem", true}, |
| {"825_days_after_2018_03_01.pem", false}, |
| {"826_days_after_2018_03_01.pem", true}, |
| {"825_days_1_second_after_2018_03_01.pem", true}, |
| {"39_months_based_on_last_day.pem", false}, |
| {"398_days_after_2020_09_01.pem", false}, |
| {"399_days_after_2020_09_01.pem", true}, |
| {"398_days_1_second_after_2020_09_01.pem", true}, |
| }; |
| |
| base::FilePath certs_dir = GetTestCertsDirectory(); |
| |
| for (const auto& test : tests) { |
| SCOPED_TRACE(test.file); |
| |
| scoped_refptr<X509Certificate> certificate = |
| ImportCertFromFile(certs_dir, test.file); |
| ASSERT_TRUE(certificate); |
| EXPECT_EQ(test.is_valid_too_long, |
| CertVerifyProc::HasTooLongValidity(*certificate)); |
| } |
| } |
| |
| // Integration test for CertVerifyProc::HasTooLongValidity. |
| // There isn't a way to add test entries to the known roots list for testing |
| // the full CertVerifyProc implementations, but HasTooLongValidity is checked |
| // by the outer CertVerifyProc::Verify. Thus the test can mock the |
| // VerifyInternal result to pretend there was a successful verification with |
| // is_issued_by_known_root and see that Verify overrides that with error. |
| TEST(CertVerifyProcTest, VerifyCertValidityTooLong) { |
| scoped_refptr<X509Certificate> cert(ImportCertFromFile( |
| GetTestCertsDirectory(), "900_days_after_2019_07_01.pem")); |
| ASSERT_TRUE(cert); |
| |
| { |
| // Locally trusted cert should be ok. |
| CertVerifyResult dummy_result; |
| dummy_result.is_issued_by_known_root = false; |
| auto verify_proc = base::MakeRefCounted<MockCertVerifyProc>(dummy_result); |
| CertVerifyResult verify_result; |
| int error = verify_proc->Verify( |
| cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(), |
| /*sct_list=*/std::string(), 0, CertificateList(), &verify_result, |
| NetLogWithSource()); |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_EQ(0u, verify_result.cert_status & CERT_STATUS_ALL_ERRORS); |
| } |
| |
| { |
| // Publicly trusted cert that was otherwise okay should get changed to |
| // ERR_CERT_VALIDITY_TOO_LONG. |
| CertVerifyResult dummy_result; |
| dummy_result.is_issued_by_known_root = true; |
| auto verify_proc = base::MakeRefCounted<MockCertVerifyProc>(dummy_result); |
| CertVerifyResult verify_result; |
| int error = verify_proc->Verify( |
| cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(), |
| /*sct_list=*/std::string(), 0, CertificateList(), &verify_result, |
| NetLogWithSource()); |
| EXPECT_THAT(error, IsError(ERR_CERT_VALIDITY_TOO_LONG)); |
| // TODO(mattm): generate a dedicated cert or use CertBuilder so that this |
| // test doesn't also hit CERT_STATUS_NON_UNIQUE_NAME. |
| EXPECT_EQ(CERT_STATUS_VALIDITY_TOO_LONG | CERT_STATUS_NON_UNIQUE_NAME, |
| verify_result.cert_status & CERT_STATUS_ALL_ERRORS); |
| } |
| |
| { |
| // Publicly trusted cert that had some other error should retain the |
| // original error, but CERT_STATUS_VALIDITY_TOO_LONG should be added to |
| // cert_status. |
| CertVerifyResult dummy_result; |
| dummy_result.is_issued_by_known_root = true; |
| dummy_result.cert_status = CERT_STATUS_AUTHORITY_INVALID; |
| auto verify_proc = base::MakeRefCounted<MockCertVerifyProc>( |
| dummy_result, ERR_CERT_AUTHORITY_INVALID); |
| CertVerifyResult verify_result; |
| int error = verify_proc->Verify( |
| cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(), |
| /*sct_list=*/std::string(), 0, CertificateList(), &verify_result, |
| NetLogWithSource()); |
| EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID)); |
| // TODO(mattm): generate a dedicated cert or use CertBuilder so that this |
| // test doesn't also hit CERT_STATUS_NON_UNIQUE_NAME. |
| EXPECT_EQ(CERT_STATUS_AUTHORITY_INVALID | CERT_STATUS_VALIDITY_TOO_LONG | |
| CERT_STATUS_NON_UNIQUE_NAME, |
| verify_result.cert_status & CERT_STATUS_ALL_ERRORS); |
| } |
| } |
| |
| TEST_P(CertVerifyProcInternalTest, DISABLED_TestKnownRoot) { |
| base::FilePath certs_dir = GetTestCertsDirectory(); |
| scoped_refptr<X509Certificate> cert_chain = CreateCertificateChainFromFile( |
| certs_dir, "leaf_from_known_root.pem", X509Certificate::FORMAT_AUTO); |
| ASSERT_TRUE(cert_chain); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = Verify(cert_chain.get(), "horseweather.com", flags, |
| CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsOk()) << "This test relies on a real certificate that " |
| << "expires on May 10 2024. If failing on/after " |
| << "that date, please disable and file a bug " |
| << "against mattm."; |
| EXPECT_TRUE(verify_result.is_issued_by_known_root); |
| #if BUILDFLAG(IS_MAC) |
| if (verify_proc_type() == CERT_VERIFY_PROC_BUILTIN) { |
| auto* mac_trust_debug_info = |
| net::TrustStoreMac::ResultDebugData::Get(&verify_result); |
| ASSERT_TRUE(mac_trust_debug_info); |
| // Since this test queries the real trust store, can't know exactly |
| // what bits should be set in the trust debug info, but it should at |
| // least have something set. |
| EXPECT_NE(0, mac_trust_debug_info->combined_trust_debug_info()); |
| } |
| #endif |
| } |
| |
| // This tests that on successful certificate verification, |
| // CertVerifyResult::public_key_hashes is filled with a SHA256 hash for each |
| // of the certificates in the chain. |
| TEST_P(CertVerifyProcInternalTest, PublicKeyHashes) { |
| base::FilePath certs_dir = GetTestCertsDirectory(); |
| CertificateList certs = CreateCertificateListFromFile( |
| certs_dir, "x509_verify_results.chain.pem", X509Certificate::FORMAT_AUTO); |
| ASSERT_EQ(3U, certs.size()); |
| |
| std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates; |
| intermediates.push_back(bssl::UpRef(certs[1]->cert_buffer())); |
| intermediates.push_back(bssl::UpRef(certs[2]->cert_buffer())); |
| |
| ScopedTestRoot scoped_root(certs[2].get()); |
| scoped_refptr<X509Certificate> cert_chain = X509Certificate::CreateFromBuffer( |
| bssl::UpRef(certs[0]->cert_buffer()), std::move(intermediates)); |
| ASSERT_TRUE(cert_chain); |
| ASSERT_EQ(2U, cert_chain->intermediate_buffers().size()); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = Verify(cert_chain.get(), "127.0.0.1", flags, CertificateList(), |
| &verify_result); |
| EXPECT_THAT(error, IsOk()); |
| |
| EXPECT_EQ(3u, verify_result.public_key_hashes.size()); |
| |
| // Convert |public_key_hashes| to strings for ease of comparison. |
| std::vector<std::string> public_key_hash_strings; |
| for (const auto& public_key_hash : verify_result.public_key_hashes) |
| public_key_hash_strings.push_back(public_key_hash.ToString()); |
| |
| std::vector<std::string> expected_public_key_hashes = { |
| // Target |
| "sha256/Ru/08Ru275Zlf42sbI6lqi2OUun3r4YgrrK/vJ3+Yzk=", |
| |
| // Intermediate |
| "sha256/D9u0epgvPYlG9YiVp7V+IMT+xhUpB5BhsS/INjDXc4Y=", |
| |
| // Trust anchor |
| "sha256/VypP3VWL7OaqTJ7mIBehWYlv8khPuFHpWiearZI2YjI="}; |
| |
| // |public_key_hashes| does not have an ordering guarantee. |
| EXPECT_THAT(expected_public_key_hashes, |
| testing::UnorderedElementsAreArray(public_key_hash_strings)); |
| } |
| |
| // Tests that a Netscape Server Gated crypto is accepted in place of a |
| // serverAuth EKU. |
| // TODO(crbug.com/843735): Deprecate support for this. |
| TEST_P(CertVerifyProcInternalTest, Sha1IntermediateUsesServerGatedCrypto) { |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| root->GenerateRSAKey(); |
| root->SetSignatureAlgorithm(SignatureAlgorithm::kRsaPkcs1Sha1); |
| |
| intermediate->SetExtendedKeyUsages({der::Input(kNetscapeServerGatedCrypto)}); |
| intermediate->SetSignatureAlgorithm(SignatureAlgorithm::kRsaPkcs1Sha1); |
| |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| // The cert chain including the root is passed to Verify, as on recent |
| // Android versions (something like 11+) the verifier fails on SHA1 certs and |
| // then the CertVerifyProc wrapper just returns the input chain, which this |
| // test then depends on for its expectations. (This is all kind of silly, but |
| // this is just matching how the test was originally written, and we'll |
| // delete this sometime soon anyway so there's not much benefit to thinking |
| // about it too hard.) |
| int error = |
| Verify(leaf->GetX509CertificateFullChain().get(), "www.example.com", |
| flags, CertificateList(), &verify_result); |
| |
| EXPECT_NE(error, OK); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_WEAK_SIGNATURE_ALGORITHM); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_SHA1_SIGNATURE_PRESENT); |
| } |
| |
| // Basic test for returning the chain in CertVerifyResult. Note that the |
| // returned chain may just be a reflection of the originally supplied chain; |
| // that is, if any errors occur, the default chain returned is an exact copy |
| // of the certificate to be verified. The remaining VerifyReturn* tests are |
| // used to ensure that the actual, verified chain is being returned by |
| // Verify(). |
| TEST_P(CertVerifyProcInternalTest, VerifyReturnChainBasic) { |
| base::FilePath certs_dir = GetTestCertsDirectory(); |
| CertificateList certs = CreateCertificateListFromFile( |
| certs_dir, "x509_verify_results.chain.pem", X509Certificate::FORMAT_AUTO); |
| ASSERT_EQ(3U, certs.size()); |
| |
| std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates; |
| intermediates.push_back(bssl::UpRef(certs[1]->cert_buffer())); |
| intermediates.push_back(bssl::UpRef(certs[2]->cert_buffer())); |
| |
| ScopedTestRoot scoped_root(certs[2].get()); |
| |
| scoped_refptr<X509Certificate> google_full_chain = |
| X509Certificate::CreateFromBuffer(bssl::UpRef(certs[0]->cert_buffer()), |
| std::move(intermediates)); |
| ASSERT_NE(static_cast<X509Certificate*>(nullptr), google_full_chain.get()); |
| ASSERT_EQ(2U, google_full_chain->intermediate_buffers().size()); |
| |
| CertVerifyResult verify_result; |
| EXPECT_EQ(static_cast<X509Certificate*>(nullptr), |
| verify_result.verified_cert.get()); |
| int error = Verify(google_full_chain.get(), "127.0.0.1", 0, CertificateList(), |
| &verify_result); |
| EXPECT_THAT(error, IsOk()); |
| ASSERT_NE(static_cast<X509Certificate*>(nullptr), |
| verify_result.verified_cert.get()); |
| |
| EXPECT_NE(google_full_chain, verify_result.verified_cert); |
| EXPECT_TRUE( |
| x509_util::CryptoBufferEqual(google_full_chain->cert_buffer(), |
| verify_result.verified_cert->cert_buffer())); |
| const auto& return_intermediates = |
| verify_result.verified_cert->intermediate_buffers(); |
| ASSERT_EQ(2U, return_intermediates.size()); |
| EXPECT_TRUE(x509_util::CryptoBufferEqual(return_intermediates[0].get(), |
| certs[1]->cert_buffer())); |
| EXPECT_TRUE(x509_util::CryptoBufferEqual(return_intermediates[1].get(), |
| certs[2]->cert_buffer())); |
| } |
| |
| // Test that certificates issued for 'intranet' names (that is, containing no |
| // known public registry controlled domain information) issued by well-known |
| // CAs are flagged appropriately, while certificates that are issued by |
| // internal CAs are not flagged. |
| TEST(CertVerifyProcTest, IntranetHostsRejected) { |
| const std::string kIntranetHostname = "webmail"; |
| |
| auto [leaf, root] = CertBuilder::CreateSimpleChain2(); |
| leaf->SetSubjectAltName(kIntranetHostname); |
| |
| scoped_refptr<X509Certificate> cert(leaf->GetX509Certificate()); |
| |
| CertVerifyResult verify_result; |
| int error = 0; |
| |
| // Intranet names for public CAs should be flagged: |
| CertVerifyResult dummy_result; |
| dummy_result.is_issued_by_known_root = true; |
| auto verify_proc = base::MakeRefCounted<MockCertVerifyProc>(dummy_result); |
| error = verify_proc->Verify(cert.get(), kIntranetHostname, |
| /*ocsp_response=*/std::string(), |
| /*sct_list=*/std::string(), 0, CertificateList(), |
| &verify_result, NetLogWithSource()); |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_NON_UNIQUE_NAME); |
| |
| // However, if the CA is not well known, these should not be flagged: |
| dummy_result.Reset(); |
| dummy_result.is_issued_by_known_root = false; |
| verify_proc = base::MakeRefCounted<MockCertVerifyProc>(dummy_result); |
| error = verify_proc->Verify(cert.get(), kIntranetHostname, |
| /*ocsp_response=*/std::string(), |
| /*sct_list=*/std::string(), 0, CertificateList(), |
| &verify_result, NetLogWithSource()); |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_NON_UNIQUE_NAME); |
| } |
| |
| // Tests that certificates issued by Symantec's legacy infrastructure |
| // are rejected according to the policies outlined in |
| // https://security.googleblog.com/2017/09/chromes-plan-to-distrust-symantec.html |
| // unless the caller has explicitly disabled that enforcement. |
| TEST(CertVerifyProcTest, SymantecCertsRejected) { |
| constexpr SHA256HashValue kSymantecHashValue = { |
| {0xb2, 0xde, 0xf5, 0x36, 0x2a, 0xd3, 0xfa, 0xcd, 0x04, 0xbd, 0x29, |
| 0x04, 0x7a, 0x43, 0x84, 0x4f, 0x76, 0x70, 0x34, 0xea, 0x48, 0x92, |
| 0xf8, 0x0e, 0x56, 0xbe, 0xe6, 0x90, 0x24, 0x3e, 0x25, 0x02}}; |
| constexpr SHA256HashValue kGoogleHashValue = { |
| {0xec, 0x72, 0x29, 0x69, 0xcb, 0x64, 0x20, 0x0a, 0xb6, 0x63, 0x8f, |
| 0x68, 0xac, 0x53, 0x8e, 0x40, 0xab, 0xab, 0x5b, 0x19, 0xa6, 0x48, |
| 0x56, 0x61, 0x04, 0x2a, 0x10, 0x61, 0xc4, 0x61, 0x27, 0x76}}; |
| |
| // Test that certificates from the legacy Symantec infrastructure are |
| // rejected: |
| // - dec_2017.pem : A certificate issued after 2017-12-01, which is rejected |
| // as of M65 |
| // - pre_june_2016.pem : A certificate issued prior to 2016-06-01, which is |
| // rejected as of M66. |
| for (const char* rejected_cert : {"dec_2017.pem", "pre_june_2016.pem"}) { |
| scoped_refptr<X509Certificate> cert = CreateCertificateChainFromFile( |
| GetTestCertsDirectory(), rejected_cert, X509Certificate::FORMAT_AUTO); |
| ASSERT_TRUE(cert); |
| |
| scoped_refptr<CertVerifyProc> verify_proc; |
| int error = 0; |
| |
| // Test that a legacy Symantec certificate is rejected. |
| CertVerifyResult symantec_result; |
| symantec_result.verified_cert = cert; |
| symantec_result.public_key_hashes.push_back(HashValue(kSymantecHashValue)); |
| symantec_result.is_issued_by_known_root = true; |
| verify_proc = base::MakeRefCounted<MockCertVerifyProc>(symantec_result); |
| |
| CertVerifyResult test_result_1; |
| error = verify_proc->Verify( |
| cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(), |
| /*sct_list=*/std::string(), 0, CertificateList(), &test_result_1, |
| NetLogWithSource()); |
| EXPECT_THAT(error, IsError(ERR_CERT_SYMANTEC_LEGACY)); |
| EXPECT_TRUE(test_result_1.cert_status & CERT_STATUS_SYMANTEC_LEGACY); |
| |
| // ... Unless the Symantec cert chains through a allowlisted intermediate. |
| CertVerifyResult allowlisted_result; |
| allowlisted_result.verified_cert = cert; |
| allowlisted_result.public_key_hashes.push_back( |
| HashValue(kSymantecHashValue)); |
| allowlisted_result.public_key_hashes.push_back(HashValue(kGoogleHashValue)); |
| allowlisted_result.is_issued_by_known_root = true; |
| verify_proc = base::MakeRefCounted<MockCertVerifyProc>(allowlisted_result); |
| |
| CertVerifyResult test_result_2; |
| error = verify_proc->Verify( |
| cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(), |
| /*sct_list=*/std::string(), 0, CertificateList(), &test_result_2, |
| NetLogWithSource()); |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_FALSE(test_result_2.cert_status & CERT_STATUS_AUTHORITY_INVALID); |
| |
| // ... Or the caller disabled enforcement of Symantec policies. |
| CertVerifyResult test_result_3; |
| error = verify_proc->Verify( |
| cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(), |
| /*sct_list=*/std::string(), |
| CertVerifyProc::VERIFY_DISABLE_SYMANTEC_ENFORCEMENT, CertificateList(), |
| &test_result_3, NetLogWithSource()); |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_FALSE(test_result_3.cert_status & CERT_STATUS_SYMANTEC_LEGACY); |
| } |
| |
| // Test that certificates from the legacy Symantec infrastructure issued |
| // after 2016-06-01 appropriately rejected. |
| scoped_refptr<X509Certificate> cert = CreateCertificateChainFromFile( |
| GetTestCertsDirectory(), "post_june_2016.pem", |
| X509Certificate::FORMAT_AUTO); |
| ASSERT_TRUE(cert); |
| |
| scoped_refptr<CertVerifyProc> verify_proc; |
| int error = 0; |
| |
| // Test that a legacy Symantec certificate is rejected if the feature |
| // flag is enabled, and accepted if it is not. |
| CertVerifyResult symantec_result; |
| symantec_result.verified_cert = cert; |
| symantec_result.public_key_hashes.push_back(HashValue(kSymantecHashValue)); |
| symantec_result.is_issued_by_known_root = true; |
| verify_proc = base::MakeRefCounted<MockCertVerifyProc>(symantec_result); |
| |
| CertVerifyResult test_result_1; |
| error = verify_proc->Verify(cert.get(), "127.0.0.1", |
| /*ocsp_response=*/std::string(), |
| /*sct_list=*/std::string(), 0, CertificateList(), |
| &test_result_1, NetLogWithSource()); |
| EXPECT_THAT(error, IsError(ERR_CERT_SYMANTEC_LEGACY)); |
| EXPECT_TRUE(test_result_1.cert_status & CERT_STATUS_SYMANTEC_LEGACY); |
| |
| // ... Unless the Symantec cert chains through a allowlisted intermediate. |
| CertVerifyResult allowlisted_result; |
| allowlisted_result.verified_cert = cert; |
| allowlisted_result.public_key_hashes.push_back(HashValue(kSymantecHashValue)); |
| allowlisted_result.public_key_hashes.push_back(HashValue(kGoogleHashValue)); |
| allowlisted_result.is_issued_by_known_root = true; |
| verify_proc = base::MakeRefCounted<MockCertVerifyProc>(allowlisted_result); |
| |
| CertVerifyResult test_result_2; |
| error = verify_proc->Verify(cert.get(), "127.0.0.1", |
| /*ocsp_response=*/std::string(), |
| /*sct_list=*/std::string(), 0, CertificateList(), |
| &test_result_2, NetLogWithSource()); |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_FALSE(test_result_2.cert_status & CERT_STATUS_AUTHORITY_INVALID); |
| |
| // ... Or the caller disabled enforcement of Symantec policies. |
| CertVerifyResult test_result_3; |
| error = verify_proc->Verify( |
| cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(), |
| /*sct_list=*/std::string(), |
| CertVerifyProc::VERIFY_DISABLE_SYMANTEC_ENFORCEMENT, CertificateList(), |
| &test_result_3, NetLogWithSource()); |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_FALSE(test_result_3.cert_status & CERT_STATUS_SYMANTEC_LEGACY); |
| } |
| |
| // Test that the certificate returned in CertVerifyResult is able to reorder |
| // certificates that are not ordered from end-entity to root. While this is |
| // a protocol violation if sent during a TLS handshake, if multiple sources |
| // of intermediate certificates are combined, it's possible that order may |
| // not be maintained. |
| TEST_P(CertVerifyProcInternalTest, VerifyReturnChainProperlyOrdered) { |
| base::FilePath certs_dir = GetTestCertsDirectory(); |
| CertificateList certs = CreateCertificateListFromFile( |
| certs_dir, "x509_verify_results.chain.pem", X509Certificate::FORMAT_AUTO); |
| ASSERT_EQ(3U, certs.size()); |
| |
| // Construct the chain out of order. |
| std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates; |
| intermediates.push_back(bssl::UpRef(certs[2]->cert_buffer())); |
| intermediates.push_back(bssl::UpRef(certs[1]->cert_buffer())); |
| |
| ScopedTestRoot scoped_root(certs[2].get()); |
| |
| scoped_refptr<X509Certificate> google_full_chain = |
| X509Certificate::CreateFromBuffer(bssl::UpRef(certs[0]->cert_buffer()), |
| std::move(intermediates)); |
| ASSERT_TRUE(google_full_chain); |
| ASSERT_EQ(2U, google_full_chain->intermediate_buffers().size()); |
| |
| CertVerifyResult verify_result; |
| EXPECT_FALSE(verify_result.verified_cert); |
| int error = Verify(google_full_chain.get(), "127.0.0.1", 0, CertificateList(), |
| &verify_result); |
| EXPECT_THAT(error, IsOk()); |
| ASSERT_TRUE(verify_result.verified_cert); |
| |
| EXPECT_NE(google_full_chain, verify_result.verified_cert); |
| EXPECT_TRUE( |
| x509_util::CryptoBufferEqual(google_full_chain->cert_buffer(), |
| verify_result.verified_cert->cert_buffer())); |
| const auto& return_intermediates = |
| verify_result.verified_cert->intermediate_buffers(); |
| ASSERT_EQ(2U, return_intermediates.size()); |
| EXPECT_TRUE(x509_util::CryptoBufferEqual(return_intermediates[0].get(), |
| certs[1]->cert_buffer())); |
| EXPECT_TRUE(x509_util::CryptoBufferEqual(return_intermediates[1].get(), |
| certs[2]->cert_buffer())); |
| } |
| |
| // Test that Verify() filters out certificates which are not related to |
| // or part of the certificate chain being verified. |
| TEST_P(CertVerifyProcInternalTest, VerifyReturnChainFiltersUnrelatedCerts) { |
| base::FilePath certs_dir = GetTestCertsDirectory(); |
| CertificateList certs = CreateCertificateListFromFile( |
| certs_dir, "x509_verify_results.chain.pem", X509Certificate::FORMAT_AUTO); |
| ASSERT_EQ(3U, certs.size()); |
| ScopedTestRoot scoped_root(certs[2].get()); |
| |
| scoped_refptr<X509Certificate> unrelated_certificate = |
| ImportCertFromFile(certs_dir, "duplicate_cn_1.pem"); |
| scoped_refptr<X509Certificate> unrelated_certificate2 = |
| ImportCertFromFile(certs_dir, "google.single.pem"); |
| ASSERT_NE(static_cast<X509Certificate*>(nullptr), |
| unrelated_certificate.get()); |
| ASSERT_NE(static_cast<X509Certificate*>(nullptr), |
| unrelated_certificate2.get()); |
| |
| // Interject unrelated certificates into the list of intermediates. |
| std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates; |
| intermediates.push_back(bssl::UpRef(unrelated_certificate->cert_buffer())); |
| intermediates.push_back(bssl::UpRef(certs[1]->cert_buffer())); |
| intermediates.push_back(bssl::UpRef(unrelated_certificate2->cert_buffer())); |
| intermediates.push_back(bssl::UpRef(certs[2]->cert_buffer())); |
| |
| scoped_refptr<X509Certificate> google_full_chain = |
| X509Certificate::CreateFromBuffer(bssl::UpRef(certs[0]->cert_buffer()), |
| std::move(intermediates)); |
| ASSERT_TRUE(google_full_chain); |
| ASSERT_EQ(4U, google_full_chain->intermediate_buffers().size()); |
| |
| CertVerifyResult verify_result; |
| EXPECT_FALSE(verify_result.verified_cert); |
| int error = Verify(google_full_chain.get(), "127.0.0.1", 0, CertificateList(), |
| &verify_result); |
| EXPECT_THAT(error, IsOk()); |
| ASSERT_TRUE(verify_result.verified_cert); |
| |
| EXPECT_NE(google_full_chain, verify_result.verified_cert); |
| EXPECT_TRUE( |
| x509_util::CryptoBufferEqual(google_full_chain->cert_buffer(), |
| verify_result.verified_cert->cert_buffer())); |
| const auto& return_intermediates = |
| verify_result.verified_cert->intermediate_buffers(); |
| ASSERT_EQ(2U, return_intermediates.size()); |
| EXPECT_TRUE(x509_util::CryptoBufferEqual(return_intermediates[0].get(), |
| certs[1]->cert_buffer())); |
| EXPECT_TRUE(x509_util::CryptoBufferEqual(return_intermediates[1].get(), |
| certs[2]->cert_buffer())); |
| } |
| |
| TEST_P(CertVerifyProcInternalTest, AdditionalTrustAnchors) { |
| if (!SupportsAdditionalTrustAnchors()) { |
| LOG(INFO) << "Skipping this test in this platform."; |
| return; |
| } |
| |
| // |ca_cert| is the issuer of |cert|. |
| CertificateList ca_cert_list = |
| CreateCertificateListFromFile(GetTestCertsDirectory(), "root_ca_cert.pem", |
| X509Certificate::FORMAT_AUTO); |
| ASSERT_EQ(1U, ca_cert_list.size()); |
| scoped_refptr<X509Certificate> ca_cert(ca_cert_list[0]); |
| |
| CertificateList cert_list = CreateCertificateListFromFile( |
| GetTestCertsDirectory(), "ok_cert.pem", X509Certificate::FORMAT_AUTO); |
| ASSERT_EQ(1U, cert_list.size()); |
| scoped_refptr<X509Certificate> cert(cert_list[0]); |
| |
| // Verification of |cert| fails when |ca_cert| is not in the trust anchors |
| // list. |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = |
| Verify(cert.get(), "127.0.0.1", flags, CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID)); |
| EXPECT_EQ(CERT_STATUS_AUTHORITY_INVALID, verify_result.cert_status); |
| EXPECT_FALSE(verify_result.is_issued_by_additional_trust_anchor); |
| |
| // Now add the |ca_cert| to the |trust_anchors|, and verification should pass. |
| CertificateList trust_anchors; |
| trust_anchors.push_back(ca_cert); |
| error = Verify(cert.get(), "127.0.0.1", flags, trust_anchors, &verify_result); |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_EQ(0U, verify_result.cert_status); |
| EXPECT_TRUE(verify_result.is_issued_by_additional_trust_anchor); |
| |
| // Clearing the |trust_anchors| makes verification fail again (the cache |
| // should be skipped). |
| error = |
| Verify(cert.get(), "127.0.0.1", flags, CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID)); |
| EXPECT_EQ(CERT_STATUS_AUTHORITY_INVALID, verify_result.cert_status); |
| EXPECT_FALSE(verify_result.is_issued_by_additional_trust_anchor); |
| } |
| |
| // Tests that certificates issued by user-supplied roots are not flagged as |
| // issued by a known root. This should pass whether or not the platform supports |
| // detecting known roots. |
| TEST_P(CertVerifyProcInternalTest, IsIssuedByKnownRootIgnoresTestRoots) { |
| // Load root_ca_cert.pem into the test root store. |
| ScopedTestRoot test_root( |
| ImportCertFromFile(GetTestCertsDirectory(), "root_ca_cert.pem").get()); |
| |
| scoped_refptr<X509Certificate> cert( |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem")); |
| |
| // Verification should pass. |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = |
| Verify(cert.get(), "127.0.0.1", flags, CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_EQ(0U, verify_result.cert_status); |
| // But should not be marked as a known root. |
| EXPECT_FALSE(verify_result.is_issued_by_known_root); |
| } |
| |
| // Test verification with a leaf that does not contain embedded SCTs, and which |
| // has a notBefore date after 2018/10/15, and passing a valid |sct_list| to |
| // Verify(). Verification should succeed on all platforms. (Assuming the |
| // verifier trusts the SCT Logs used in |sct_list|.) |
| // |
| // Fails on multiple plaforms, see crbug.com/1050152. |
| TEST_P(CertVerifyProcInternalTest, |
| DISABLED_LeafNewerThan20181015WithTlsSctList) { |
| scoped_refptr<X509Certificate> chain = CreateCertificateChainFromFile( |
| GetTestCertsDirectory(), "treadclimber.pem", |
| X509Certificate::FORMAT_PEM_CERT_SEQUENCE); |
| ASSERT_TRUE(chain); |
| if (base::Time::Now() > chain->valid_expiry()) { |
| FAIL() << "This test uses a certificate chain which is now expired. Please " |
| "disable and file a bug against mattm."; |
| } |
| |
| std::string sct_list; |
| ASSERT_TRUE(base::ReadFileToString( |
| GetTestCertsDirectory().AppendASCII("treadclimber.sctlist"), &sct_list)); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = verify_proc()->Verify(chain.get(), "treadclimber.com", |
| /*ocsp_response=*/std::string(), sct_list, |
| flags, CertificateList(), &verify_result, |
| NetLogWithSource()); |
| |
| // Since the valid |sct_list| was passed to Verify, verification should |
| // succeed on all verifiers and OS versions. |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_EQ(0U, verify_result.cert_status); |
| EXPECT_TRUE(verify_result.is_issued_by_known_root); |
| } |
| |
| // Test that CRLSets are effective in making a certificate appear to be |
| // revoked. |
| TEST_P(CertVerifyProcInternalTest, CRLSet) { |
| if (!SupportsCRLSet()) { |
| LOG(INFO) << "Skipping test as verifier doesn't support CRLSet"; |
| return; |
| } |
| |
| CertificateList ca_cert_list = |
| CreateCertificateListFromFile(GetTestCertsDirectory(), "root_ca_cert.pem", |
| X509Certificate::FORMAT_AUTO); |
| ASSERT_EQ(1U, ca_cert_list.size()); |
| ScopedTestRoot test_root(ca_cert_list[0].get()); |
| |
| CertificateList cert_list = CreateCertificateListFromFile( |
| GetTestCertsDirectory(), "ok_cert.pem", X509Certificate::FORMAT_AUTO); |
| ASSERT_EQ(1U, cert_list.size()); |
| scoped_refptr<X509Certificate> cert(cert_list[0]); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = |
| Verify(cert.get(), "127.0.0.1", flags, CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_EQ(0U, verify_result.cert_status); |
| |
| scoped_refptr<CRLSet> crl_set; |
| std::string crl_set_bytes; |
| |
| // First test blocking by SPKI. |
| EXPECT_TRUE(base::ReadFileToString( |
| GetTestCertsDirectory().AppendASCII("crlset_by_leaf_spki.raw"), |
| &crl_set_bytes)); |
| ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set)); |
| |
| SetUpCertVerifyProc(crl_set); |
| error = |
| Verify(cert.get(), "127.0.0.1", flags, CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsError(ERR_CERT_REVOKED)); |
| |
| // Second, test revocation by serial number of a cert directly under the |
| // root. |
| crl_set_bytes.clear(); |
| EXPECT_TRUE(base::ReadFileToString( |
| GetTestCertsDirectory().AppendASCII("crlset_by_root_serial.raw"), |
| &crl_set_bytes)); |
| ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set)); |
| |
| SetUpCertVerifyProc(crl_set); |
| error = |
| Verify(cert.get(), "127.0.0.1", flags, CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsError(ERR_CERT_REVOKED)); |
| } |
| |
| TEST_P(CertVerifyProcInternalTest, CRLSetLeafSerial) { |
| if (!SupportsCRLSet()) { |
| LOG(INFO) << "Skipping test as verifier doesn't support CRLSet"; |
| return; |
| } |
| |
| CertificateList ca_cert_list = |
| CreateCertificateListFromFile(GetTestCertsDirectory(), "root_ca_cert.pem", |
| X509Certificate::FORMAT_AUTO); |
| ASSERT_EQ(1U, ca_cert_list.size()); |
| ScopedTestRoot test_root(ca_cert_list[0].get()); |
| |
| scoped_refptr<X509Certificate> leaf = CreateCertificateChainFromFile( |
| GetTestCertsDirectory(), "ok_cert_by_intermediate.pem", |
| X509Certificate::FORMAT_AUTO); |
| ASSERT_TRUE(leaf); |
| ASSERT_EQ(1U, leaf->intermediate_buffers().size()); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = |
| Verify(leaf.get(), "127.0.0.1", flags, CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsOk()); |
| |
| // Test revocation by serial number of a certificate not under the root. |
| scoped_refptr<CRLSet> crl_set; |
| std::string crl_set_bytes; |
| ASSERT_TRUE(base::ReadFileToString( |
| GetTestCertsDirectory().AppendASCII("crlset_by_intermediate_serial.raw"), |
| &crl_set_bytes)); |
| ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set)); |
| |
| SetUpCertVerifyProc(crl_set); |
| error = |
| Verify(leaf.get(), "127.0.0.1", flags, CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsError(ERR_CERT_REVOKED)); |
| } |
| |
| TEST_P(CertVerifyProcInternalTest, CRLSetRootReturnsChain) { |
| if (!SupportsCRLSet()) { |
| LOG(INFO) << "Skipping test as verifier doesn't support CRLSet"; |
| return; |
| } |
| |
| CertificateList ca_cert_list = |
| CreateCertificateListFromFile(GetTestCertsDirectory(), "root_ca_cert.pem", |
| X509Certificate::FORMAT_AUTO); |
| ASSERT_EQ(1U, ca_cert_list.size()); |
| ScopedTestRoot test_root(ca_cert_list[0].get()); |
| |
| scoped_refptr<X509Certificate> leaf = CreateCertificateChainFromFile( |
| GetTestCertsDirectory(), "ok_cert_by_intermediate.pem", |
| X509Certificate::FORMAT_AUTO); |
| ASSERT_TRUE(leaf); |
| ASSERT_EQ(1U, leaf->intermediate_buffers().size()); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = |
| Verify(leaf.get(), "127.0.0.1", flags, CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsOk()); |
| |
| // Test revocation of the root itself. |
| scoped_refptr<CRLSet> crl_set; |
| std::string crl_set_bytes; |
| ASSERT_TRUE(base::ReadFileToString( |
| GetTestCertsDirectory().AppendASCII("crlset_by_root_spki.raw"), |
| &crl_set_bytes)); |
| ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set)); |
| |
| SetUpCertVerifyProc(crl_set); |
| error = |
| Verify(leaf.get(), "127.0.0.1", flags, CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsError(ERR_CERT_REVOKED)); |
| |
| EXPECT_EQ(3u, verify_result.public_key_hashes.size()); |
| ASSERT_TRUE(verify_result.verified_cert); |
| EXPECT_EQ(2u, verify_result.verified_cert->intermediate_buffers().size()); |
| } |
| |
| // Tests that CertVerifyProc implementations apply CRLSet revocations by |
| // subject. |
| TEST_P(CertVerifyProcInternalTest, CRLSetRevokedBySubject) { |
| if (!SupportsCRLSet()) { |
| LOG(INFO) << "Skipping test as verifier doesn't support CRLSet"; |
| return; |
| } |
| |
| scoped_refptr<X509Certificate> root( |
| ImportCertFromFile(GetTestCertsDirectory(), "root_ca_cert.pem")); |
| ASSERT_TRUE(root); |
| |
| scoped_refptr<X509Certificate> leaf( |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem")); |
| ASSERT_TRUE(leaf); |
| |
| ScopedTestRoot scoped_root(root.get()); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| |
| // Confirm that verifying the certificate chain with an empty CRLSet succeeds. |
| SetUpCertVerifyProc(CRLSet::EmptyCRLSetForTesting()); |
| int error = |
| Verify(leaf.get(), "127.0.0.1", flags, CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsOk()); |
| |
| std::string crl_set_bytes; |
| scoped_refptr<CRLSet> crl_set; |
| |
| // Revoke the leaf by subject. Verification should now fail. |
| ASSERT_TRUE(base::ReadFileToString( |
| GetTestCertsDirectory().AppendASCII("crlset_by_leaf_subject_no_spki.raw"), |
| &crl_set_bytes)); |
| ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set)); |
| |
| SetUpCertVerifyProc(crl_set); |
| error = |
| Verify(leaf.get(), "127.0.0.1", flags, CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsError(ERR_CERT_REVOKED)); |
| |
| // Revoke the root by subject. Verification should now fail. |
| ASSERT_TRUE(base::ReadFileToString( |
| GetTestCertsDirectory().AppendASCII("crlset_by_root_subject_no_spki.raw"), |
| &crl_set_bytes)); |
| ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set)); |
| |
| SetUpCertVerifyProc(crl_set); |
| error = |
| Verify(leaf.get(), "127.0.0.1", flags, CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsError(ERR_CERT_REVOKED)); |
| |
| // Revoke the leaf by subject, but only if the SPKI doesn't match the given |
| // one. Verification should pass when using the certificate's actual SPKI. |
| ASSERT_TRUE(base::ReadFileToString( |
| GetTestCertsDirectory().AppendASCII("crlset_by_root_subject.raw"), |
| &crl_set_bytes)); |
| ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set)); |
| |
| SetUpCertVerifyProc(crl_set); |
| error = |
| Verify(leaf.get(), "127.0.0.1", flags, CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsOk()); |
| } |
| |
| // Ensures that CRLSets can be used to block known interception roots on |
| // platforms that support CRLSets, while otherwise detect known interception |
| // on platforms that do not. |
| TEST_P(CertVerifyProcInternalTest, BlockedInterceptionByRoot) { |
| scoped_refptr<X509Certificate> root = |
| ImportCertFromFile(GetTestCertsDirectory(), "root_ca_cert.pem"); |
| ASSERT_TRUE(root); |
| ScopedTestRoot test_root(root.get()); |
| |
| scoped_refptr<X509Certificate> cert = CreateCertificateChainFromFile( |
| GetTestCertsDirectory(), "ok_cert_by_intermediate.pem", |
| X509Certificate::FORMAT_AUTO); |
| ASSERT_TRUE(cert); |
| |
| // A default/built-in CRLSet should not block |
| scoped_refptr<CRLSet> crl_set = CRLSet::BuiltinCRLSet(); |
| SetUpCertVerifyProc(crl_set); |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = |
| Verify(cert.get(), "127.0.0.1", flags, CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_EQ(0U, verify_result.cert_status); |
| |
| // Read in a CRLSet that marks the root as blocked for interception. |
| std::string crl_set_bytes; |
| ASSERT_TRUE( |
| base::ReadFileToString(GetTestCertsDirectory().AppendASCII( |
| "crlset_blocked_interception_by_root.raw"), |
| &crl_set_bytes)); |
| ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set)); |
| |
| SetUpCertVerifyProc(crl_set); |
| error = |
| Verify(cert.get(), "127.0.0.1", flags, CertificateList(), &verify_result); |
| if (SupportsCRLSet()) { |
| EXPECT_THAT(error, IsError(ERR_CERT_KNOWN_INTERCEPTION_BLOCKED)); |
| EXPECT_TRUE(verify_result.cert_status & |
| CERT_STATUS_KNOWN_INTERCEPTION_BLOCKED); |
| } else { |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_TRUE(verify_result.cert_status & |
| CERT_STATUS_KNOWN_INTERCEPTION_DETECTED); |
| } |
| } |
| |
| // Ensures that CRLSets can be used to block known interception intermediates, |
| // while still allowing other certificates from that root.. |
| TEST_P(CertVerifyProcInternalTest, BlockedInterceptionByIntermediate) { |
| scoped_refptr<X509Certificate> root = |
| ImportCertFromFile(GetTestCertsDirectory(), "root_ca_cert.pem"); |
| ASSERT_TRUE(root); |
| ScopedTestRoot test_root(root.get()); |
| |
| scoped_refptr<X509Certificate> cert = CreateCertificateChainFromFile( |
| GetTestCertsDirectory(), "ok_cert_by_intermediate.pem", |
| X509Certificate::FORMAT_AUTO); |
| ASSERT_TRUE(cert); |
| |
| // A default/built-in CRLSEt should not block |
| scoped_refptr<CRLSet> crl_set = CRLSet::BuiltinCRLSet(); |
| SetUpCertVerifyProc(crl_set); |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = |
| Verify(cert.get(), "127.0.0.1", flags, CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_EQ(0U, verify_result.cert_status); |
| |
| // Read in a CRLSet that marks the intermediate as blocked for interception. |
| std::string crl_set_bytes; |
| ASSERT_TRUE(base::ReadFileToString( |
| GetTestCertsDirectory().AppendASCII( |
| "crlset_blocked_interception_by_intermediate.raw"), |
| &crl_set_bytes)); |
| ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set)); |
| |
| SetUpCertVerifyProc(crl_set); |
| error = |
| Verify(cert.get(), "127.0.0.1", flags, CertificateList(), &verify_result); |
| if (SupportsCRLSet()) { |
| EXPECT_THAT(error, IsError(ERR_CERT_KNOWN_INTERCEPTION_BLOCKED)); |
| EXPECT_TRUE(verify_result.cert_status & |
| CERT_STATUS_KNOWN_INTERCEPTION_BLOCKED); |
| } else { |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_TRUE(verify_result.cert_status & |
| CERT_STATUS_KNOWN_INTERCEPTION_DETECTED); |
| } |
| |
| // Load a different certificate from that root, which should be unaffected. |
| scoped_refptr<X509Certificate> second_cert = CreateCertificateChainFromFile( |
| GetTestCertsDirectory(), "ok_cert.pem", X509Certificate::FORMAT_AUTO); |
| ASSERT_TRUE(second_cert); |
| |
| SetUpCertVerifyProc(crl_set); |
| error = Verify(second_cert.get(), "127.0.0.1", flags, CertificateList(), |
| &verify_result); |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_EQ(0U, verify_result.cert_status); |
| } |
| |
| // Ensures that CRLSets can be used to flag known interception roots, even |
| // when they are not blocked. |
| TEST_P(CertVerifyProcInternalTest, DetectsInterceptionByRoot) { |
| scoped_refptr<X509Certificate> root = |
| ImportCertFromFile(GetTestCertsDirectory(), "root_ca_cert.pem"); |
| ASSERT_TRUE(root); |
| ScopedTestRoot test_root(root.get()); |
| |
| scoped_refptr<X509Certificate> cert = CreateCertificateChainFromFile( |
| GetTestCertsDirectory(), "ok_cert_by_intermediate.pem", |
| X509Certificate::FORMAT_AUTO); |
| ASSERT_TRUE(cert); |
| |
| // A default/built-in CRLSet should not block |
| scoped_refptr<CRLSet> crl_set = CRLSet::BuiltinCRLSet(); |
| SetUpCertVerifyProc(crl_set); |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = |
| Verify(cert.get(), "127.0.0.1", flags, CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_EQ(0U, verify_result.cert_status); |
| |
| // Read in a CRLSet that marks the root as blocked for interception. |
| std::string crl_set_bytes; |
| ASSERT_TRUE( |
| base::ReadFileToString(GetTestCertsDirectory().AppendASCII( |
| "crlset_known_interception_by_root.raw"), |
| &crl_set_bytes)); |
| ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set)); |
| |
| SetUpCertVerifyProc(crl_set); |
| error = |
| Verify(cert.get(), "127.0.0.1", flags, CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_TRUE(verify_result.cert_status & |
| CERT_STATUS_KNOWN_INTERCEPTION_DETECTED); |
| } |
| |
| // Tests that CRLSets participate in path building functions, and that as |
| // long as a valid path exists within the verification graph, verification |
| // succeeds. |
| // |
| // In this test, there are two roots (D and E), and three possible paths |
| // to validate a leaf (A): |
| // 1. A(B) -> B(C) -> C(D) -> D(D) |
| // 2. A(B) -> B(C) -> C(E) -> E(E) |
| // 3. A(B) -> B(F) -> F(E) -> E(E) |
| // |
| // Each permutation of revocation is tried: |
| // 1. Revoking E by SPKI, so that only Path 1 is valid (as E is in Paths 2 & 3) |
| // 2. Revoking C(D) and F(E) by serial, so that only Path 2 is valid. |
| // 3. Revoking C by SPKI, so that only Path 3 is valid (as C is in Paths 1 & 2) |
| TEST_P(CertVerifyProcInternalTest, CRLSetDuringPathBuilding) { |
| if (!SupportsCRLSetsInPathBuilding()) { |
| LOG(INFO) << "Skipping this test on this platform."; |
| return; |
| } |
| |
| CertificateList path_1_certs; |
| ASSERT_TRUE( |
| LoadCertificateFiles({"multi-root-A-by-B.pem", "multi-root-B-by-C.pem", |
| "multi-root-C-by-D.pem", "multi-root-D-by-D.pem"}, |
| &path_1_certs)); |
| |
| CertificateList path_2_certs; |
| ASSERT_TRUE( |
| LoadCertificateFiles({"multi-root-A-by-B.pem", "multi-root-B-by-C.pem", |
| "multi-root-C-by-E.pem", "multi-root-E-by-E.pem"}, |
| &path_2_certs)); |
| |
| CertificateList path_3_certs; |
| ASSERT_TRUE( |
| LoadCertificateFiles({"multi-root-A-by-B.pem", "multi-root-B-by-F.pem", |
| "multi-root-F-by-E.pem", "multi-root-E-by-E.pem"}, |
| &path_3_certs)); |
| |
| // Add D and E as trust anchors. |
| ScopedTestRoot test_root_D(path_1_certs[3].get()); // D-by-D |
| ScopedTestRoot test_root_E(path_2_certs[3].get()); // E-by-E |
| |
| // Create a chain that contains all the certificate paths possible. |
| // CertVerifyProcInternalTest.VerifyReturnChainFiltersUnrelatedCerts already |
| // ensures that it's safe to send additional certificates as inputs, and |
| // that they're ignored if not necessary. |
| // This is to avoid relying on AIA or internal object caches when |
| // interacting with the underlying library. |
| std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates; |
| intermediates.push_back( |
| bssl::UpRef(path_1_certs[1]->cert_buffer())); // B-by-C |
| intermediates.push_back( |
| bssl::UpRef(path_1_certs[2]->cert_buffer())); // C-by-D |
| intermediates.push_back( |
| bssl::UpRef(path_2_certs[2]->cert_buffer())); // C-by-E |
| intermediates.push_back( |
| bssl::UpRef(path_3_certs[1]->cert_buffer())); // B-by-F |
| intermediates.push_back( |
| bssl::UpRef(path_3_certs[2]->cert_buffer())); // F-by-E |
| scoped_refptr<X509Certificate> cert = X509Certificate::CreateFromBuffer( |
| bssl::UpRef(path_1_certs[0]->cert_buffer()), std::move(intermediates)); |
| ASSERT_TRUE(cert); |
| |
| struct TestPermutations { |
| const char* crlset; |
| bool expect_valid; |
| scoped_refptr<X509Certificate> expected_intermediate; |
| } kTests[] = { |
| {"multi-root-crlset-D-and-E.raw", false, nullptr}, |
| {"multi-root-crlset-E.raw", true, path_1_certs[2].get()}, |
| {"multi-root-crlset-CD-and-FE.raw", true, path_2_certs[2].get()}, |
| {"multi-root-crlset-C.raw", true, path_3_certs[2].get()}, |
| {"multi-root-crlset-unrelated.raw", true, nullptr}}; |
| |
| for (const auto& testcase : kTests) { |
| SCOPED_TRACE(testcase.crlset); |
| scoped_refptr<CRLSet> crl_set; |
| std::string crl_set_bytes; |
| EXPECT_TRUE(base::ReadFileToString( |
| GetTestCertsDirectory().AppendASCII(testcase.crlset), &crl_set_bytes)); |
| ASSERT_TRUE(CRLSet::Parse(crl_set_bytes, &crl_set)); |
| |
| SetUpCertVerifyProc(crl_set); |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = Verify(cert.get(), "127.0.0.1", flags, CertificateList(), |
| &verify_result); |
| |
| if (!testcase.expect_valid) { |
| EXPECT_NE(OK, error); |
| EXPECT_NE(0U, verify_result.cert_status); |
| continue; |
| } |
| |
| ASSERT_THAT(error, IsOk()); |
| ASSERT_EQ(0U, verify_result.cert_status); |
| ASSERT_TRUE(verify_result.verified_cert.get()); |
| |
| if (!testcase.expected_intermediate) |
| continue; |
| |
| const auto& verified_intermediates = |
| verify_result.verified_cert->intermediate_buffers(); |
| ASSERT_EQ(3U, verified_intermediates.size()); |
| |
| scoped_refptr<X509Certificate> intermediate = |
| X509Certificate::CreateFromBuffer( |
| bssl::UpRef(verified_intermediates[1].get()), {}); |
| ASSERT_TRUE(intermediate); |
| |
| EXPECT_TRUE(testcase.expected_intermediate->EqualsExcludingChain( |
| intermediate.get())) |
| << "Expected: " << testcase.expected_intermediate->subject().common_name |
| << " issued by " << testcase.expected_intermediate->issuer().common_name |
| << "; Got: " << intermediate->subject().common_name << " issued by " |
| << intermediate->issuer().common_name; |
| } |
| } |
| |
| TEST_P(CertVerifyProcInternalTest, ValidityDayPlus5MinutesBeforeNotBefore) { |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| base::Time not_before = base::Time::Now() + base::Days(1) + base::Minutes(5); |
| base::Time not_after = base::Time::Now() + base::Days(30); |
| leaf->SetValidity(not_before, not_after); |
| |
| // Trust the root and build a chain to verify that includes the intermediate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = Verify(chain.get(), "www.example.com", flags, CertificateList(), |
| &verify_result); |
| // Current time is before certificate's notBefore. Verification should fail. |
| EXPECT_THAT(error, IsError(ERR_CERT_DATE_INVALID)); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_DATE_INVALID); |
| } |
| |
| TEST_P(CertVerifyProcInternalTest, ValidityDayBeforeNotBefore) { |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| base::Time not_before = base::Time::Now() + base::Days(1); |
| base::Time not_after = base::Time::Now() + base::Days(30); |
| leaf->SetValidity(not_before, not_after); |
| |
| // Trust the root and build a chain to verify that includes the intermediate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = Verify(chain.get(), "www.example.com", flags, CertificateList(), |
| &verify_result); |
| // Current time is before certificate's notBefore. Verification should fail. |
| EXPECT_THAT(error, IsError(ERR_CERT_DATE_INVALID)); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_DATE_INVALID); |
| } |
| |
| TEST_P(CertVerifyProcInternalTest, ValidityJustBeforeNotBefore) { |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| base::Time not_before = base::Time::Now() + base::Minutes(5); |
| base::Time not_after = base::Time::Now() + base::Days(30); |
| leaf->SetValidity(not_before, not_after); |
| |
| // Trust the root and build a chain to verify that includes the intermediate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = Verify(chain.get(), "www.example.com", flags, CertificateList(), |
| &verify_result); |
| // Current time is before certificate's notBefore. Verification should fail. |
| EXPECT_THAT(error, IsError(ERR_CERT_DATE_INVALID)); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_DATE_INVALID); |
| } |
| |
| TEST_P(CertVerifyProcInternalTest, ValidityJustAfterNotBefore) { |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| base::Time not_before = base::Time::Now() - base::Seconds(1); |
| base::Time not_after = base::Time::Now() + base::Days(30); |
| leaf->SetValidity(not_before, not_after); |
| |
| // Trust the root and build a chain to verify that includes the intermediate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = Verify(chain.get(), "www.example.com", flags, CertificateList(), |
| &verify_result); |
| // Current time is between notBefore and notAfter. Verification should |
| // succeed. |
| EXPECT_THAT(error, IsOk()); |
| } |
| |
| TEST_P(CertVerifyProcInternalTest, ValidityJustBeforeNotAfter) { |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| base::Time not_before = base::Time::Now() - base::Days(30); |
| base::Time not_after = base::Time::Now() + base::Minutes(5); |
| leaf->SetValidity(not_before, not_after); |
| |
| // Trust the root and build a chain to verify that includes the intermediate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = Verify(chain.get(), "www.example.com", flags, CertificateList(), |
| &verify_result); |
| // Current time is between notBefore and notAfter. Verification should |
| // succeed. |
| EXPECT_THAT(error, IsOk()); |
| } |
| |
| TEST_P(CertVerifyProcInternalTest, ValidityJustAfterNotAfter) { |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| base::Time not_before = base::Time::Now() - base::Days(30); |
| base::Time not_after = base::Time::Now() - base::Seconds(1); |
| leaf->SetValidity(not_before, not_after); |
| |
| // Trust the root and build a chain to verify that includes the intermediate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = Verify(chain.get(), "www.example.com", flags, CertificateList(), |
| &verify_result); |
| // Current time is after certificate's notAfter. Verification should fail. |
| EXPECT_THAT(error, IsError(ERR_CERT_DATE_INVALID)); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_DATE_INVALID); |
| } |
| |
| TEST_P(CertVerifyProcInternalTest, FailedIntermediateSignatureValidation) { |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| // Intermediate has no authorityKeyIdentifier. Also remove |
| // subjectKeyIdentifier from root for good measure. |
| intermediate->EraseExtension(der::Input(kAuthorityKeyIdentifierOid)); |
| root->EraseExtension(der::Input(kSubjectKeyIdentifierOid)); |
| |
| // Get the chain with the leaf and the intermediate signed by the original |
| // key of |root|. |
| scoped_refptr<X509Certificate> cert = leaf->GetX509CertificateChain(); |
| |
| // Generate a new key for root. |
| root->GenerateECKey(); |
| |
| // Trust the new root certificate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = Verify(cert.get(), "www.example.com", flags, CertificateList(), |
| &verify_result); |
| |
| // The intermediate was signed by a different root with a different key but |
| // with the same name as the trusted one, and the intermediate has no |
| // authorityKeyIdentifier, so the verifier must try verifying the signature. |
| // Should fail with AUTHORITY_INVALID. |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_AUTHORITY_INVALID); |
| EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID)); |
| } |
| |
| TEST_P(CertVerifyProcInternalTest, FailedTargetSignatureValidation) { |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| // Leaf has no authorityKeyIdentifier. Also remove subjectKeyIdentifier from |
| // intermediate for good measure. |
| leaf->EraseExtension(der::Input(kAuthorityKeyIdentifierOid)); |
| intermediate->EraseExtension(der::Input(kSubjectKeyIdentifierOid)); |
| |
| // Get a copy of the leaf signed by the original key of intermediate. |
| bssl::UniquePtr<CRYPTO_BUFFER> leaf_wrong_signature = leaf->DupCertBuffer(); |
| |
| // Generate a new key for intermediate. |
| intermediate->GenerateECKey(); |
| |
| // Make a chain that includes the original leaf with the wrong signature and |
| // the new intermediate. |
| std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates; |
| intermediates.push_back(intermediate->DupCertBuffer()); |
| |
| scoped_refptr<X509Certificate> cert = X509Certificate::CreateFromBuffer( |
| bssl::UpRef(leaf_wrong_signature), std::move(intermediates)); |
| ASSERT_TRUE(cert.get()); |
| |
| // Trust the root certificate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = Verify(cert.get(), "www.example.com", flags, CertificateList(), |
| &verify_result); |
| |
| // The leaf was signed by a different intermediate with a different key but |
| // with the same name as the one in the chain, and the leaf has no |
| // authorityKeyIdentifier, so the verifier must try verifying the signature. |
| // Should fail with AUTHORITY_INVALID. |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_AUTHORITY_INVALID); |
| EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID)); |
| } |
| |
| class CertVerifyProcNameNormalizationTest : public CertVerifyProcInternalTest { |
| protected: |
| std::string HistogramName() const { |
| std::string prefix("Net.CertVerifier.NameNormalizationPrivateRoots."); |
| switch (verify_proc_type()) { |
| case CERT_VERIFY_PROC_ANDROID: |
| return prefix + "Android"; |
| case CERT_VERIFY_PROC_IOS: |
| return prefix + "IOS"; |
| case CERT_VERIFY_PROC_BUILTIN: |
| case CERT_VERIFY_PROC_BUILTIN_CHROME_ROOTS: |
| return prefix + "Builtin"; |
| } |
| } |
| |
| void ExpectNormalizationHistogram(int verify_error) { |
| if (verify_error == OK) { |
| histograms_.ExpectUniqueSample( |
| HistogramName(), CertVerifyProc::NameNormalizationResult::kNormalized, |
| 1); |
| } else { |
| histograms_.ExpectTotalCount(HistogramName(), 0); |
| } |
| } |
| |
| void ExpectByteEqualHistogram() { |
| histograms_.ExpectUniqueSample( |
| HistogramName(), CertVerifyProc::NameNormalizationResult::kByteEqual, |
| 1); |
| } |
| |
| private: |
| base::HistogramTester histograms_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| CertVerifyProcNameNormalizationTest, |
| testing::ValuesIn(kAllCertVerifiers), |
| VerifyProcTypeToName); |
| |
| // Tries to verify a chain where the leaf's issuer CN is PrintableString, while |
| // the intermediate's subject CN is UTF8String, and verifies the proper |
| // histogram is logged. |
| TEST_P(CertVerifyProcNameNormalizationTest, StringType) { |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| std::string issuer_cn = CertBuilder::MakeRandomHexString(12); |
| leaf->SetIssuerTLV(CertBuilder::BuildNameWithCommonNameOfType( |
| issuer_cn, CBS_ASN1_PRINTABLESTRING)); |
| intermediate->SetSubjectTLV(CertBuilder::BuildNameWithCommonNameOfType( |
| issuer_cn, CBS_ASN1_UTF8STRING)); |
| |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = Verify(leaf->GetX509CertificateChain().get(), "www.example.com", |
| flags, CertificateList(), &verify_result); |
| |
| switch (verify_proc_type()) { |
| case CERT_VERIFY_PROC_IOS: |
| EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID)); |
| break; |
| case CERT_VERIFY_PROC_ANDROID: |
| case CERT_VERIFY_PROC_BUILTIN: |
| case CERT_VERIFY_PROC_BUILTIN_CHROME_ROOTS: |
| EXPECT_THAT(error, IsOk()); |
| break; |
| } |
| |
| ExpectNormalizationHistogram(error); |
| } |
| |
| // Tries to verify a chain where the leaf's issuer CN and intermediate's |
| // subject CN are both PrintableString but have differing case on the first |
| // character, and verifies the proper histogram is logged. |
| TEST_P(CertVerifyProcNameNormalizationTest, CaseFolding) { |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| std::string issuer_hex = CertBuilder::MakeRandomHexString(12); |
| leaf->SetIssuerTLV(CertBuilder::BuildNameWithCommonNameOfType( |
| "Z" + issuer_hex, CBS_ASN1_PRINTABLESTRING)); |
| intermediate->SetSubjectTLV(CertBuilder::BuildNameWithCommonNameOfType( |
| "z" + issuer_hex, CBS_ASN1_PRINTABLESTRING)); |
| |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = Verify(leaf->GetX509CertificateChain().get(), "www.example.com", |
| flags, CertificateList(), &verify_result); |
| |
| EXPECT_THAT(error, IsOk()); |
| ExpectNormalizationHistogram(error); |
| } |
| |
| // Confirms that a chain generated by the same pattern as the other |
| // NameNormalizationTest cases which does not require normalization validates |
| // ok, and that the ByteEqual histogram is logged. |
| TEST_P(CertVerifyProcNameNormalizationTest, ByteEqual) { |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| std::string issuer_hex = CertBuilder::MakeRandomHexString(12); |
| leaf->SetIssuerTLV(CertBuilder::BuildNameWithCommonNameOfType( |
| issuer_hex, CBS_ASN1_PRINTABLESTRING)); |
| intermediate->SetSubjectTLV(CertBuilder::BuildNameWithCommonNameOfType( |
| issuer_hex, CBS_ASN1_PRINTABLESTRING)); |
| |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = Verify(leaf->GetX509CertificateChain().get(), "www.example.com", |
| flags, CertificateList(), &verify_result); |
| |
| EXPECT_THAT(error, IsOk()); |
| ExpectByteEqualHistogram(); |
| } |
| |
| std::string Md5WithRSAEncryption() { |
| const uint8_t kMd5WithRSAEncryption[] = {0x30, 0x0d, 0x06, 0x09, 0x2a, |
| 0x86, 0x48, 0x86, 0xf7, 0x0d, |
| 0x01, 0x01, 0x04, 0x05, 0x00}; |
| return std::string(std::begin(kMd5WithRSAEncryption), |
| std::end(kMd5WithRSAEncryption)); |
| } |
| |
| // This is the same as CertVerifyProcInternalTest, but it additionally sets up |
| // networking capabilities for the cert verifiers, and a test server that can be |
| // used to serve mock responses for AIA/OCSP/CRL. |
| // |
| // An actual HTTP test server is used rather than simply mocking the network |
| // layer, since the certificate fetching networking layer is not mockable for |
| // all of the cert verifier implementations. |
| // |
| // The approach taken in this test fixture is to generate certificates |
| // on the fly so they use randomly chosen URLs, subjects, and serial |
| // numbers, in order to defeat global caching effects from the platform |
| // verifiers. Moreover, the AIA needs to be chosen dynamically since the |
| // test server's port number cannot be known statically. |
| class CertVerifyProcInternalWithNetFetchingTest |
| : public CertVerifyProcInternalTest { |
| protected: |
| CertVerifyProcInternalWithNetFetchingTest() |
| : task_environment_( |
| base::test::TaskEnvironment::MainThreadType::DEFAULT) {} |
| |
| void SetUp() override { |
| // Create a network thread to be used for network fetches, and wait for |
| // initialization to complete on that thread. |
| base::Thread::Options options(base::MessagePumpType::IO, 0); |
| network_thread_ = std::make_unique<base::Thread>("network_thread"); |
| CHECK(network_thread_->StartWithOptions(std::move(options))); |
| |
| base::WaitableEvent initialization_complete_event( |
| base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| network_thread_->task_runner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&SetUpOnNetworkThread, &context_, &cert_net_fetcher_, |
| &initialization_complete_event)); |
| initialization_complete_event.Wait(); |
| EXPECT_TRUE(cert_net_fetcher_); |
| |
| CertVerifyProcInternalTest::SetUp(); |
| |
| EXPECT_FALSE(test_server_.Started()); |
| |
| // Register a single request handler with the EmbeddedTestServer, that in |
| // turn dispatches to the internally managed registry of request handlers. |
| // |
| // This allows registering subsequent handlers dynamically during the course |
| // of the test, since EmbeddedTestServer requires its handlers be registered |
| // prior to Start(). |
| test_server_.RegisterRequestHandler(base::BindRepeating( |
| &CertVerifyProcInternalWithNetFetchingTest::DispatchToRequestHandler, |
| base::Unretained(this))); |
| EXPECT_TRUE(test_server_.Start()); |
| } |
| |
| void SetUpCertVerifyProc(scoped_refptr<CRLSet> crl_set) override { |
| EXPECT_TRUE(cert_net_fetcher_); |
| SetUpWithCertNetFetcher(cert_net_fetcher_, std::move(crl_set)); |
| } |
| |
| void TearDown() override { |
| // Do cleanup on network thread. |
| network_thread_->task_runner()->PostTask( |
| FROM_HERE, base::BindOnce(&ShutdownOnNetworkThread, &context_, |
| &cert_net_fetcher_)); |
| network_thread_->Stop(); |
| network_thread_.reset(); |
| |
| CertVerifyProcInternalTest::TearDown(); |
| } |
| |
| // Registers a handler with the test server that responds with the given |
| // Content-Type, HTTP status code, and response body, for GET requests |
| // to |path|. |
| // Returns the full URL to |path| for the current test server. |
| GURL RegisterSimpleTestServerHandler(std::string path, |
| HttpStatusCode status_code, |
| std::string content_type, |
| std::string content) { |
| GURL handler_url(GetTestServerAbsoluteUrl(path)); |
| base::AutoLock lock(request_handlers_lock_); |
| request_handlers_.push_back(base::BindRepeating( |
| &SimpleTestServerHandler, std::move(path), status_code, |
| std::move(content_type), std::move(content))); |
| return handler_url; |
| } |
| |
| // Returns a random URL path (starting with /) that has the given suffix. |
| static std::string MakeRandomPath(base::StringPiece suffix) { |
| return "/" + MakeRandomHexString(12) + std::string(suffix); |
| } |
| |
| // Returns a URL to |path| for the current test server. |
| GURL GetTestServerAbsoluteUrl(const std::string& path) { |
| return test_server_.GetURL(path); |
| } |
| |
| // Creates a certificate chain for www.example.com, where the leaf certificate |
| // has an AIA URL pointing to the test server. |
| void CreateSimpleChainWithAIA( |
| scoped_refptr<X509Certificate>* out_leaf, |
| std::string* ca_issuers_path, |
| bssl::UniquePtr<CRYPTO_BUFFER>* out_intermediate, |
| scoped_refptr<X509Certificate>* out_root) { |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| // Make the leaf certificate have an AIA (CA Issuers) that points to the |
| // embedded test server. This uses a random URL for predictable behavior in |
| // the presence of global caching. |
| *ca_issuers_path = MakeRandomPath(".cer"); |
| GURL ca_issuers_url = GetTestServerAbsoluteUrl(*ca_issuers_path); |
| leaf->SetCaIssuersUrl(ca_issuers_url); |
| |
| // The chain being verified is solely the leaf certificate (missing the |
| // intermediate and root). |
| *out_leaf = leaf->GetX509Certificate(); |
| *out_root = root->GetX509Certificate(); |
| *out_intermediate = intermediate->DupCertBuffer(); |
| } |
| |
| // Creates a CRL issued and signed by |crl_issuer|, marking |revoked_serials| |
| // as revoked, and registers it to be served by the test server. |
| // Returns the full URL to retrieve the CRL from the test server. |
| GURL CreateAndServeCrl( |
| CertBuilder* crl_issuer, |
| const std::vector<uint64_t>& revoked_serials, |
| absl::optional<SignatureAlgorithm> signature_algorithm = absl::nullopt) { |
| std::string crl = BuildCrl(crl_issuer->GetSubject(), crl_issuer->GetKey(), |
| revoked_serials, signature_algorithm); |
| std::string crl_path = MakeRandomPath(".crl"); |
| return RegisterSimpleTestServerHandler(crl_path, HTTP_OK, |
| "application/pkix-crl", crl); |
| } |
| |
| GURL CreateAndServeCrlWithAlgorithmTlvAndDigest( |
| CertBuilder* crl_issuer, |
| const std::vector<uint64_t>& revoked_serials, |
| const std::string& signature_algorithm_tlv, |
| const EVP_MD* digest) { |
| std::string crl = BuildCrlWithAlgorithmTlvAndDigest( |
| crl_issuer->GetSubject(), crl_issuer->GetKey(), revoked_serials, |
| signature_algorithm_tlv, digest); |
| std::string crl_path = MakeRandomPath(".crl"); |
| return RegisterSimpleTestServerHandler(crl_path, HTTP_OK, |
| "application/pkix-crl", crl); |
| } |
| |
| private: |
| std::unique_ptr<test_server::HttpResponse> DispatchToRequestHandler( |
| const test_server::HttpRequest& request) { |
| // Called on the embedded test server's IO thread. |
| base::AutoLock lock(request_handlers_lock_); |
| for (const auto& handler : request_handlers_) { |
| auto response = handler.Run(request); |
| if (response) |
| return response; |
| } |
| |
| return nullptr; |
| } |
| |
| // Serves (|status_code|, |content_type|, |content|) in response to GET |
| // requests for |path|. |
| static std::unique_ptr<test_server::HttpResponse> SimpleTestServerHandler( |
| const std::string& path, |
| HttpStatusCode status_code, |
| const std::string& content_type, |
| const std::string& content, |
| const test_server::HttpRequest& request) { |
| if (request.relative_url != path) |
| return nullptr; |
| |
| auto http_response = std::make_unique<test_server::BasicHttpResponse>(); |
| |
| http_response->set_code(status_code); |
| http_response->set_content_type(content_type); |
| http_response->set_content(content); |
| return http_response; |
| } |
| |
| static void SetUpOnNetworkThread( |
| std::unique_ptr<URLRequestContext>* context, |
| scoped_refptr<CertNetFetcherURLRequest>* cert_net_fetcher, |
| base::WaitableEvent* initialization_complete_event) { |
| URLRequestContextBuilder url_request_context_builder; |
| url_request_context_builder.set_user_agent("cert_verify_proc_unittest/0.1"); |
| url_request_context_builder.set_proxy_config_service( |
| std::make_unique<ProxyConfigServiceFixed>(ProxyConfigWithAnnotation())); |
| *context = url_request_context_builder.Build(); |
| |
| *cert_net_fetcher = base::MakeRefCounted<net::CertNetFetcherURLRequest>(); |
| (*cert_net_fetcher)->SetURLRequestContext(context->get()); |
| initialization_complete_event->Signal(); |
| } |
| |
| static void ShutdownOnNetworkThread( |
| std::unique_ptr<URLRequestContext>* context, |
| scoped_refptr<net::CertNetFetcherURLRequest>* cert_net_fetcher) { |
| (*cert_net_fetcher)->Shutdown(); |
| cert_net_fetcher->reset(); |
| context->reset(); |
| } |
| |
| base::test::TaskEnvironment task_environment_; |
| |
| std::unique_ptr<base::Thread> network_thread_; |
| |
| // Owned by this thread, but initialized, used, and shutdown on the network |
| // thread. |
| std::unique_ptr<URLRequestContext> context_; |
| scoped_refptr<CertNetFetcherURLRequest> cert_net_fetcher_; |
| |
| EmbeddedTestServer test_server_; |
| |
| // The list of registered handlers. Can only be accessed when the lock is |
| // held, as this data is shared between the embedded server's IO thread, and |
| // the test main thread. |
| base::Lock request_handlers_lock_; |
| std::vector<test_server::EmbeddedTestServer::HandleRequestCallback> |
| request_handlers_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| CertVerifyProcInternalWithNetFetchingTest, |
| testing::ValuesIn(kAllCertVerifiers), |
| VerifyProcTypeToName); |
| |
| // Tries verifying a certificate chain that is missing an intermediate. The |
| // intermediate is available via AIA, however the server responds with a 404. |
| // |
| // NOTE: This test is separate from IntermediateFromAia200 as a different URL |
| // needs to be used to avoid having the result depend on globally cached success |
| // or failure of the fetch. |
| // Test is flaky on iOS crbug.com/860189 |
| #if BUILDFLAG(IS_IOS) |
| #define MAYBE_IntermediateFromAia404 DISABLED_IntermediateFromAia404 |
| #else |
| #define MAYBE_IntermediateFromAia404 IntermediateFromAia404 |
| #endif |
| TEST_P(CertVerifyProcInternalWithNetFetchingTest, |
| MAYBE_IntermediateFromAia404) { |
| const char kHostname[] = "www.example.com"; |
| |
| // Create a chain where the leaf has an AIA that points to test server. |
| scoped_refptr<X509Certificate> leaf; |
| std::string ca_issuers_path; |
| bssl::UniquePtr<CRYPTO_BUFFER> intermediate; |
| scoped_refptr<X509Certificate> root; |
| CreateSimpleChainWithAIA(&leaf, &ca_issuers_path, &intermediate, &root); |
| |
| // Serve a 404 for the AIA url. |
| RegisterSimpleTestServerHandler(ca_issuers_path, HTTP_NOT_FOUND, "text/plain", |
| "Not Found"); |
| |
| // Trust the root certificate. |
| ScopedTestRoot scoped_root(root.get()); |
| |
| // The chain being verified is solely the leaf certificate (missing the |
| // intermediate and root). |
| ASSERT_EQ(0u, leaf->intermediate_buffers().size()); |
| |
| const int flags = 0; |
| int error; |
| CertVerifyResult verify_result; |
| |
| // Verifying the chain should fail as the intermediate is missing, and |
| // cannot be fetched via AIA. |
| error = |
| Verify(leaf.get(), kHostname, flags, CertificateList(), &verify_result); |
| EXPECT_NE(OK, error); |
| |
| EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID)); |
| } |
| #undef MAYBE_IntermediateFromAia404 |
| |
| // Tries verifying a certificate chain that is missing an intermediate. The |
| // intermediate is available via AIA. |
| // TODO(crbug.com/860189): Failing on iOS |
| #if BUILDFLAG(IS_IOS) |
| #define MAYBE_IntermediateFromAia200Der DISABLED_IntermediateFromAia200Der |
| #else |
| #define MAYBE_IntermediateFromAia200Der IntermediateFromAia200Der |
| #endif |
| TEST_P(CertVerifyProcInternalWithNetFetchingTest, |
| MAYBE_IntermediateFromAia200Der) { |
| const char kHostname[] = "www.example.com"; |
| |
| // Create a chain where the leaf has an AIA that points to test server. |
| scoped_refptr<X509Certificate> leaf; |
| std::string ca_issuers_path; |
| bssl::UniquePtr<CRYPTO_BUFFER> intermediate; |
| scoped_refptr<X509Certificate> root; |
| CreateSimpleChainWithAIA(&leaf, &ca_issuers_path, &intermediate, &root); |
| |
| // Setup the test server to reply with the correct intermediate. |
| RegisterSimpleTestServerHandler( |
| ca_issuers_path, HTTP_OK, "application/pkix-cert", |
| std::string(x509_util::CryptoBufferAsStringPiece(intermediate.get()))); |
| |
| // Trust the root certificate. |
| ScopedTestRoot scoped_root(root.get()); |
| |
| // The chain being verified is solely the leaf certificate (missing the |
| // intermediate and root). |
| ASSERT_EQ(0u, leaf->intermediate_buffers().size()); |
| |
| // VERIFY_DISABLE_NETWORK_FETCHES flag is not implemented in |
| // CertVerifyProcIOS, only test it on other verifiers. |
| if (verify_proc_type() != CERT_VERIFY_PROC_IOS) { |
| CertVerifyResult verify_result; |
| // If VERIFY_DISABLE_NETWORK_FETCHES is specified, AIA should not be |
| // attempted and verifying the chain should fail since the intermediate |
| // can't be found. |
| int error = Verify(leaf.get(), kHostname, |
| CertVerifyProc::VERIFY_DISABLE_NETWORK_FETCHES, |
| CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID)); |
| EXPECT_EQ(0u, verify_result.verified_cert->intermediate_buffers().size()); |
| } |
| |
| { |
| CertVerifyResult verify_result; |
| // Verifying the chain should succeed as the missing intermediate can be |
| // fetched via AIA. |
| int error = Verify(leaf.get(), kHostname, /*flags=*/0, CertificateList(), |
| &verify_result); |
| EXPECT_THAT(error, IsOk()); |
| } |
| } |
| |
| // This test is the same as IntermediateFromAia200Der, except the certificate is |
| // served as PEM rather than DER. |
| // |
| // Tries verifying a certificate chain that is missing an intermediate. The |
| // intermediate is available via AIA, however is served as a PEM file rather |
| // than DER. |
| // TODO(crbug.com/860189): Failing on iOS |
| #if BUILDFLAG(IS_IOS) |
| #define MAYBE_IntermediateFromAia200Pem DISABLED_IntermediateFromAia200Pem |
| #else |
| #define MAYBE_IntermediateFromAia200Pem IntermediateFromAia200Pem |
| #endif |
| TEST_P(CertVerifyProcInternalWithNetFetchingTest, |
| MAYBE_IntermediateFromAia200Pem) { |
| const char kHostname[] = "www.example.com"; |
| |
| // Create a chain where the leaf has an AIA that points to test server. |
| scoped_refptr<X509Certificate> leaf; |
| std::string ca_issuers_path; |
| bssl::UniquePtr<CRYPTO_BUFFER> intermediate; |
| scoped_refptr<X509Certificate> root; |
| CreateSimpleChainWithAIA(&leaf, &ca_issuers_path, &intermediate, &root); |
| |
| std::string intermediate_pem; |
| ASSERT_TRUE( |
| X509Certificate::GetPEMEncoded(intermediate.get(), &intermediate_pem)); |
| |
| // Setup the test server to reply with the correct intermediate. |
| RegisterSimpleTestServerHandler( |
| ca_issuers_path, HTTP_OK, "application/x-x509-ca-cert", intermediate_pem); |
| |
| // Trust the root certificate. |
| ScopedTestRoot scoped_root(root.get()); |
| |
| // The chain being verified is solely the leaf certificate (missing the |
| // intermediate and root). |
| ASSERT_EQ(0u, leaf->intermediate_buffers().size()); |
| |
| const int flags = 0; |
| int error; |
| CertVerifyResult verify_result; |
| |
| // Verifying the chain should succeed as the missing intermediate can be |
| // fetched via AIA. |
| error = |
| Verify(leaf.get(), kHostname, flags, CertificateList(), &verify_result); |
| |
| if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID) { |
| // Android doesn't support PEM - https://crbug.com/725180 |
| EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID)); |
| } else { |
| EXPECT_THAT(error, IsOk()); |
| } |
| } |
| |
| // This test is the same as IntermediateFromAia200Pem, but with a different |
| // formatting on the PEM data. |
| // |
| // TODO(crbug.com/860189): Failing on iOS |
| #if BUILDFLAG(IS_IOS) |
| #define MAYBE_IntermediateFromAia200Pem2 DISABLED_IntermediateFromAia200Pem2 |
| #else |
| #define MAYBE_IntermediateFromAia200Pem2 IntermediateFromAia200Pem2 |
| #endif |
| TEST_P(CertVerifyProcInternalWithNetFetchingTest, |
| MAYBE_IntermediateFromAia200Pem2) { |
| const char kHostname[] = "www.example.com"; |
| |
| // Create a chain where the leaf has an AIA that points to test server. |
| scoped_refptr<X509Certificate> leaf; |
| std::string ca_issuers_path; |
| bssl::UniquePtr<CRYPTO_BUFFER> intermediate; |
| scoped_refptr<X509Certificate> root; |
| CreateSimpleChainWithAIA(&leaf, &ca_issuers_path, &intermediate, &root); |
| |
| std::string intermediate_pem; |
| ASSERT_TRUE( |
| X509Certificate::GetPEMEncoded(intermediate.get(), &intermediate_pem)); |
| intermediate_pem = "Text at start of file\n" + intermediate_pem; |
| |
| // Setup the test server to reply with the correct intermediate. |
| RegisterSimpleTestServerHandler( |
| ca_issuers_path, HTTP_OK, "application/x-x509-ca-cert", intermediate_pem); |
| |
| // Trust the root certificate. |
| ScopedTestRoot scoped_root(root.get()); |
| |
| // The chain being verified is solely the leaf certificate (missing the |
| // intermediate and root). |
| ASSERT_EQ(0u, leaf->intermediate_buffers().size()); |
| |
| const int flags = 0; |
| int error; |
| CertVerifyResult verify_result; |
| |
| // Verifying the chain should succeed as the missing intermediate can be |
| // fetched via AIA. |
| error = |
| Verify(leaf.get(), kHostname, flags, CertificateList(), &verify_result); |
| |
| if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID) { |
| // Android doesn't support PEM - https://crbug.com/725180 |
| EXPECT_THAT(error, IsError(ERR_CERT_AUTHORITY_INVALID)); |
| } else { |
| EXPECT_THAT(error, IsOk()); |
| } |
| } |
| |
| // Tries verifying a certificate chain that uses a SHA1 intermediate, |
| // however, chasing the AIA can discover a SHA256 version of the intermediate. |
| // |
| // Path building should discover the stronger intermediate and use it. |
| TEST_P(CertVerifyProcInternalWithNetFetchingTest, |
| Sha1IntermediateButAIAHasSha256) { |
| const char kHostname[] = "www.example.com"; |
| |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| // Make the leaf certificate have an AIA (CA Issuers) that points to the |
| // embedded test server. This uses a random URL for predictable behavior in |
| // the presence of global caching. |
| std::string ca_issuers_path = MakeRandomPath(".cer"); |
| GURL ca_issuers_url = GetTestServerAbsoluteUrl(ca_issuers_path); |
| leaf->SetCaIssuersUrl(ca_issuers_url); |
| leaf->SetSubjectAltName(kHostname); |
| |
| // Make two versions of the intermediate - one that is SHA256 signed, and one |
| // that is SHA1 signed. Note that the subjectKeyIdentifier for `intermediate` |
| // is intentionally not changed, so that path building will consider both |
| // certificate paths. |
| intermediate->SetSignatureAlgorithm(SignatureAlgorithm::kEcdsaSha256); |
| intermediate->SetRandomSerialNumber(); |
| auto intermediate_sha256 = intermediate->DupCertBuffer(); |
| |
| intermediate->SetSignatureAlgorithm(SignatureAlgorithm::kEcdsaSha1); |
| intermediate->SetRandomSerialNumber(); |
| auto intermediate_sha1 = intermediate->DupCertBuffer(); |
| |
| // Trust the root certificate. |
| auto root_cert = root->GetX509Certificate(); |
| ScopedTestRoot scoped_root(root_cert.get()); |
| |
| // Setup the test server to reply with the SHA256 intermediate. |
| RegisterSimpleTestServerHandler( |
| ca_issuers_path, HTTP_OK, "application/pkix-cert", |
| std::string( |
| x509_util::CryptoBufferAsStringPiece(intermediate_sha256.get()))); |
| |
| // Build a chain to verify that includes the SHA1 intermediate. |
| std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates; |
| intermediates.push_back(bssl::UpRef(intermediate_sha1.get())); |
| scoped_refptr<X509Certificate> chain_sha1 = X509Certificate::CreateFromBuffer( |
| leaf->DupCertBuffer(), std::move(intermediates)); |
| ASSERT_TRUE(chain_sha1.get()); |
| |
| const int flags = 0; |
| CertVerifyResult verify_result; |
| int error = Verify(chain_sha1.get(), kHostname, flags, CertificateList(), |
| &verify_result); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| // Should have built a chain through the SHA256 intermediate. This was only |
| // available via AIA, and not the (SHA1) one provided directly to path |
| // building. |
| ASSERT_EQ(2u, verify_result.verified_cert->intermediate_buffers().size()); |
| EXPECT_TRUE(x509_util::CryptoBufferEqual( |
| verify_result.verified_cert->intermediate_buffers()[0].get(), |
| intermediate_sha256.get())); |
| ASSERT_EQ(2u, verify_result.verified_cert->intermediate_buffers().size()); |
| |
| EXPECT_FALSE(verify_result.has_sha1); |
| EXPECT_THAT(error, IsOk()); |
| } else { |
| EXPECT_NE(OK, error); |
| if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID && |
| error == ERR_CERT_AUTHORITY_INVALID) { |
| // Newer Android versions reject the chain due to the SHA1 intermediate, |
| // but do not build the correct chain by AIA. Since only the partial |
| // chain is returned, CertVerifyProc does not mark it as SHA1 as it does |
| // not examine the last cert in the chain. Therefore, if |
| // ERR_CERT_AUTHORITY_INVALID is returned, don't check the rest of the |
| // statuses. See https://crbug.com/1191795. |
| return; |
| } |
| EXPECT_TRUE(verify_result.cert_status & |
| CERT_STATUS_WEAK_SIGNATURE_ALGORITHM); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_SHA1_SIGNATURE_PRESENT); |
| EXPECT_TRUE(verify_result.has_sha1); |
| } |
| } |
| |
| TEST_P(CertVerifyProcInternalWithNetFetchingTest, RevocationHardFailNoCrls) { |
| if (!SupportsRevCheckingRequiredLocalAnchors()) { |
| LOG(INFO) << "Skipping test as verifier doesn't support " |
| "VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS"; |
| return; |
| } |
| |
| // Create certs which have no AIA or CRL distribution points. |
| const char kHostname[] = "www.example.com"; |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| // Trust the root and build a chain to verify that includes the intermediate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| // Verify with hard-fail revocation checking for local anchors. |
| const int flags = CertVerifyProc::VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS; |
| CertVerifyResult verify_result; |
| int error = |
| Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result); |
| |
| EXPECT_THAT(error, IsError(ERR_CERT_NO_REVOCATION_MECHANISM)); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED); |
| } |
| |
| TEST_P(CertVerifyProcInternalWithNetFetchingTest, |
| RevocationHardFailNoCrlsDisableNetworkFetches) { |
| if (!SupportsRevCheckingRequiredLocalAnchors()) { |
| LOG(INFO) << "Skipping test as verifier doesn't support " |
| "VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS"; |
| return; |
| } |
| |
| // Create certs which have no AIA or CRL distribution points. |
| const char kHostname[] = "www.example.com"; |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| // Trust the root and build a chain to verify that includes the intermediate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| // Verify with flags for both hard-fail revocation checking for local anchors |
| // and disabling network fetches. |
| const int flags = CertVerifyProc::VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS | |
| CertVerifyProc::VERIFY_DISABLE_NETWORK_FETCHES; |
| CertVerifyResult verify_result; |
| int error = |
| Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result); |
| |
| // Should succeed, VERIFY_DISABLE_NETWORK_FETCHES takes priority. |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED); |
| } |
| |
| // CRL hard fail test where both leaf and intermediate are covered by valid |
| // CRLs which have empty (non-present) revokedCertificates list. Verification |
| // should succeed. |
| TEST_P(CertVerifyProcInternalWithNetFetchingTest, |
| RevocationHardFailCrlGoodNoRevokedCertificates) { |
| if (!SupportsRevCheckingRequiredLocalAnchors()) { |
| LOG(INFO) << "Skipping test as verifier doesn't support " |
| "VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS"; |
| return; |
| } |
| |
| const char kHostname[] = "www.example.com"; |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| // Serve a root-issued CRL which does not revoke intermediate. |
| intermediate->SetCrlDistributionPointUrl(CreateAndServeCrl(root.get(), {})); |
| |
| // Serve an intermediate-issued CRL which does not revoke leaf. |
| leaf->SetCrlDistributionPointUrl(CreateAndServeCrl(intermediate.get(), {})); |
| |
| // Trust the root and build a chain to verify that includes the intermediate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| // Verify with hard-fail revocation checking for local anchors. |
| const int flags = CertVerifyProc::VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS; |
| CertVerifyResult verify_result; |
| int error = |
| Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result); |
| |
| // Should pass, leaf and intermediate were covered by CRLs and were not |
| // revoked. |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED); |
| } |
| |
| // CRL hard fail test where both leaf and intermediate are covered by valid |
| // CRLs which have revokedCertificates lists that revoke other irrelevant |
| // serial numbers. Verification should succeed. |
| TEST_P(CertVerifyProcInternalWithNetFetchingTest, |
| RevocationHardFailCrlGoodIrrelevantSerialsRevoked) { |
| if (!SupportsRevCheckingRequiredLocalAnchors()) { |
| LOG(INFO) << "Skipping test as verifier doesn't support " |
| "VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS"; |
| return; |
| } |
| |
| const char kHostname[] = "www.example.com"; |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| // Root-issued CRL revokes leaf's serial number. This is irrelevant. |
| intermediate->SetCrlDistributionPointUrl( |
| CreateAndServeCrl(root.get(), {leaf->GetSerialNumber()})); |
| |
| // Intermediate-issued CRL revokes intermediates's serial number. This is |
| // irrelevant. |
| leaf->SetCrlDistributionPointUrl( |
| CreateAndServeCrl(intermediate.get(), {intermediate->GetSerialNumber()})); |
| |
| // Trust the root and build a chain to verify that includes the intermediate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| // Verify with hard-fail revocation checking for local anchors. |
| const int flags = CertVerifyProc::VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS; |
| CertVerifyResult verify_result; |
| int error = |
| Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result); |
| |
| // Should pass, leaf and intermediate were covered by CRLs and were not |
| // revoked. |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED); |
| } |
| |
| TEST_P(CertVerifyProcInternalWithNetFetchingTest, |
| RevocationHardFailLeafRevokedByCrl) { |
| if (!SupportsRevCheckingRequiredLocalAnchors()) { |
| LOG(INFO) << "Skipping test as verifier doesn't support " |
| "VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS"; |
| return; |
| } |
| |
| const char kHostname[] = "www.example.com"; |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| // Root-issued CRL which does not revoke intermediate. |
| intermediate->SetCrlDistributionPointUrl(CreateAndServeCrl(root.get(), {})); |
| |
| // Leaf is revoked by intermediate issued CRL. |
| leaf->SetCrlDistributionPointUrl( |
| CreateAndServeCrl(intermediate.get(), {leaf->GetSerialNumber()})); |
| |
| // Trust the root and build a chain to verify that includes the intermediate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| // Verify with hard-fail revocation checking for local anchors. |
| const int flags = CertVerifyProc::VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS; |
| CertVerifyResult verify_result; |
| int error = |
| Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result); |
| |
| // Should fail, leaf is revoked. |
| EXPECT_THAT(error, IsError(ERR_CERT_REVOKED)); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED); |
| } |
| |
| TEST_P(CertVerifyProcInternalWithNetFetchingTest, |
| RevocationHardFailIntermediateRevokedByCrl) { |
| if (!SupportsRevCheckingRequiredLocalAnchors()) { |
| LOG(INFO) << "Skipping test as verifier doesn't support " |
| "VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS"; |
| return; |
| } |
| |
| const char kHostname[] = "www.example.com"; |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| // Intermediate is revoked by root issued CRL. |
| intermediate->SetCrlDistributionPointUrl( |
| CreateAndServeCrl(root.get(), {intermediate->GetSerialNumber()})); |
| |
| // Intermediate-issued CRL which does not revoke leaf. |
| leaf->SetCrlDistributionPointUrl(CreateAndServeCrl(intermediate.get(), {})); |
| |
| // Trust the root and build a chain to verify that includes the intermediate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| // Verify with hard-fail revocation checking for local anchors. |
| const int flags = CertVerifyProc::VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS; |
| CertVerifyResult verify_result; |
| int error = |
| Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result); |
| |
| // Should fail, intermediate is revoked. |
| EXPECT_THAT(error, IsError(ERR_CERT_REVOKED)); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED); |
| } |
| |
| // CRL hard fail test where the intermediate certificate has a good CRL, but |
| // the leaf's distribution point returns an http error. Verification should |
| // fail. |
| TEST_P(CertVerifyProcInternalWithNetFetchingTest, |
| RevocationHardFailLeafCrlDpHttpError) { |
| if (!SupportsRevCheckingRequiredLocalAnchors()) { |
| LOG(INFO) << "Skipping test as verifier doesn't support " |
| "VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS"; |
| return; |
| } |
| |
| const char kHostname[] = "www.example.com"; |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| // Serve a root-issued CRL which does not revoke intermediate. |
| intermediate->SetCrlDistributionPointUrl(CreateAndServeCrl(root.get(), {})); |
| |
| // Serve a 404 for the intermediate-issued CRL distribution point url. |
| leaf->SetCrlDistributionPointUrl(RegisterSimpleTestServerHandler( |
| MakeRandomPath(".crl"), HTTP_NOT_FOUND, "text/plain", "Not Found")); |
| |
| // Trust the root and build a chain to verify that includes the intermediate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| // Verify with hard-fail revocation checking for local anchors. |
| const int flags = CertVerifyProc::VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS; |
| CertVerifyResult verify_result; |
| int error = |
| Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result); |
| |
| // Should fail since no revocation information was available for the leaf. |
| EXPECT_THAT(error, IsError(ERR_CERT_UNABLE_TO_CHECK_REVOCATION)); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED); |
| } |
| |
| // CRL hard fail test where the leaf certificate has a good CRL, but |
| // the intermediate's distribution point returns an http error. Verification |
| // should fail. |
| TEST_P(CertVerifyProcInternalWithNetFetchingTest, |
| RevocationHardFailIntermediateCrlDpHttpError) { |
| if (!SupportsRevCheckingRequiredLocalAnchors()) { |
| LOG(INFO) << "Skipping test as verifier doesn't support " |
| "VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS"; |
| return; |
| } |
| |
| const char kHostname[] = "www.example.com"; |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| // Serve a 404 for the root-issued CRL distribution point url. |
| intermediate->SetCrlDistributionPointUrl(RegisterSimpleTestServerHandler( |
| MakeRandomPath(".crl"), HTTP_NOT_FOUND, "text/plain", "Not Found")); |
| |
| // Serve an intermediate-issued CRL which does not revoke leaf. |
| leaf->SetCrlDistributionPointUrl(CreateAndServeCrl(intermediate.get(), {})); |
| |
| // Trust the root and build a chain to verify that includes the intermediate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| // Verify with hard-fail revocation checking for local anchors. |
| const int flags = CertVerifyProc::VERIFY_REV_CHECKING_REQUIRED_LOCAL_ANCHORS; |
| CertVerifyResult verify_result; |
| int error = |
| Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result); |
| |
| // Should fail since no revocation information was available for the |
| // intermediate. |
| EXPECT_THAT(error, IsError(ERR_CERT_UNABLE_TO_CHECK_REVOCATION)); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED); |
| } |
| |
| TEST_P(CertVerifyProcInternalWithNetFetchingTest, RevocationSoftFailNoCrls) { |
| if (!SupportsSoftFailRevChecking()) { |
| LOG(INFO) << "Skipping test as verifier doesn't support " |
| "VERIFY_REV_CHECKING_ENABLED"; |
| return; |
| } |
| |
| // Create certs which have no AIA or CRL distribution points. |
| const char kHostname[] = "www.example.com"; |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| // Trust the root and build a chain to verify that includes the intermediate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| // Verify with soft-fail revocation checking. |
| const int flags = CertVerifyProc::VERIFY_REV_CHECKING_ENABLED; |
| CertVerifyResult verify_result; |
| int error = |
| Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result); |
| |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_NO_REVOCATION_MECHANISM); |
| EXPECT_FALSE(verify_result.cert_status & |
| CERT_STATUS_UNABLE_TO_CHECK_REVOCATION); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED); |
| } |
| |
| // CRL soft fail test where both leaf and intermediate are covered by valid |
| // CRLs which have empty (non-present) revokedCertificates list. Verification |
| // should succeed. |
| TEST_P(CertVerifyProcInternalWithNetFetchingTest, |
| RevocationSoftFailCrlGoodNoRevokedCertificates) { |
| if (!SupportsSoftFailRevChecking()) { |
| LOG(INFO) << "Skipping test as verifier doesn't support " |
| "VERIFY_REV_CHECKING_ENABLED"; |
| return; |
| } |
| |
| const char kHostname[] = "www.example.com"; |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| // Serve a root-issued CRL which does not revoke intermediate. |
| intermediate->SetCrlDistributionPointUrl(CreateAndServeCrl(root.get(), {})); |
| |
| // Serve an intermediate-issued CRL which does not revoke leaf. |
| leaf->SetCrlDistributionPointUrl(CreateAndServeCrl(intermediate.get(), {})); |
| |
| // Trust the root and build a chain to verify that includes the intermediate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| // Verify with soft-fail revocation checking. |
| const int flags = CertVerifyProc::VERIFY_REV_CHECKING_ENABLED; |
| CertVerifyResult verify_result; |
| int error = |
| Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result); |
| |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED); |
| } |
| |
| // CRL soft fail test where both leaf and intermediate are covered by valid |
| // CRLs which have revokedCertificates lists that revoke other irrelevant |
| // serial numbers. Verification should succeed. |
| TEST_P(CertVerifyProcInternalWithNetFetchingTest, |
| RevocationSoftFailCrlGoodIrrelevantSerialsRevoked) { |
| if (!SupportsSoftFailRevChecking()) { |
| LOG(INFO) << "Skipping test as verifier doesn't support " |
| "VERIFY_REV_CHECKING_ENABLED"; |
| return; |
| } |
| |
| const char kHostname[] = "www.example.com"; |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| // Root-issued CRL revokes leaf's serial number. This is irrelevant. |
| intermediate->SetCrlDistributionPointUrl( |
| CreateAndServeCrl(root.get(), {leaf->GetSerialNumber()})); |
| |
| // Intermediate-issued CRL revokes intermediates's serial number. This is |
| // irrelevant. |
| leaf->SetCrlDistributionPointUrl( |
| CreateAndServeCrl(intermediate.get(), {intermediate->GetSerialNumber()})); |
| |
| // Trust the root and build a chain to verify that includes the intermediate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| // Verify with soft-fail revocation checking. |
| const int flags = CertVerifyProc::VERIFY_REV_CHECKING_ENABLED; |
| CertVerifyResult verify_result; |
| int error = |
| Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result); |
| |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED); |
| } |
| |
| TEST_P(CertVerifyProcInternalWithNetFetchingTest, |
| RevocationSoftFailLeafRevokedByCrl) { |
| if (!SupportsSoftFailRevChecking()) { |
| LOG(INFO) << "Skipping test as verifier doesn't support " |
| "VERIFY_REV_CHECKING_ENABLED"; |
| return; |
| } |
| |
| const char kHostname[] = "www.example.com"; |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| // Root-issued CRL which does not revoke intermediate. |
| intermediate->SetCrlDistributionPointUrl(CreateAndServeCrl(root.get(), {})); |
| |
| // Leaf is revoked by intermediate issued CRL. |
| leaf->SetCrlDistributionPointUrl( |
| CreateAndServeCrl(intermediate.get(), {leaf->GetSerialNumber()})); |
| |
| // Trust the root and build a chain to verify that includes the intermediate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| // Verify with soft-fail revocation checking. |
| const int flags = CertVerifyProc::VERIFY_REV_CHECKING_ENABLED; |
| CertVerifyResult verify_result; |
| int error = |
| Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result); |
| |
| // Should fail, leaf is revoked. |
| EXPECT_THAT(error, IsError(ERR_CERT_REVOKED)); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED); |
| } |
| |
| TEST_P(CertVerifyProcInternalWithNetFetchingTest, |
| RevocationSoftFailLeafRevokedByCrlDisableNetworkFetches) { |
| if (!SupportsSoftFailRevChecking()) { |
| LOG(INFO) << "Skipping test as verifier doesn't support " |
| "VERIFY_REV_CHECKING_ENABLED"; |
| return; |
| } |
| |
| const char kHostname[] = "www.example.com"; |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| // Root-issued CRL which does not revoke intermediate. |
| intermediate->SetCrlDistributionPointUrl(CreateAndServeCrl(root.get(), {})); |
| |
| // Leaf is revoked by intermediate issued CRL. |
| leaf->SetCrlDistributionPointUrl( |
| CreateAndServeCrl(intermediate.get(), {leaf->GetSerialNumber()})); |
| |
| // Trust the root and build a chain to verify that includes the intermediate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| // Verify with flags for both soft-fail revocation checking and disabling |
| // network fetches. |
| const int flags = CertVerifyProc::VERIFY_REV_CHECKING_ENABLED | |
| CertVerifyProc::VERIFY_DISABLE_NETWORK_FETCHES; |
| CertVerifyResult verify_result; |
| int error = |
| Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result); |
| |
| // Should succeed, VERIFY_DISABLE_NETWORK_FETCHES takes priority. |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED); |
| } |
| |
| TEST_P(CertVerifyProcInternalWithNetFetchingTest, |
| RevocationSoftFailIntermediateRevokedByCrl) { |
| if (!SupportsSoftFailRevChecking()) { |
| LOG(INFO) << "Skipping test as verifier doesn't support " |
| "VERIFY_REV_CHECKING_ENABLED"; |
| return; |
| } |
| |
| const char kHostname[] = "www.example.com"; |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| // Intermediate is revoked by root issued CRL. |
| intermediate->SetCrlDistributionPointUrl( |
| CreateAndServeCrl(root.get(), {intermediate->GetSerialNumber()})); |
| |
| // Intermediate-issued CRL which does not revoke leaf. |
| leaf->SetCrlDistributionPointUrl(CreateAndServeCrl(intermediate.get(), {})); |
| |
| // Trust the root and build a chain to verify that includes the intermediate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| // Verify with soft-fail revocation checking. |
| const int flags = CertVerifyProc::VERIFY_REV_CHECKING_ENABLED; |
| CertVerifyResult verify_result; |
| int error = |
| Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result); |
| |
| // Should fail, intermediate is revoked. |
| EXPECT_THAT(error, IsError(ERR_CERT_REVOKED)); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED); |
| } |
| |
| TEST_P(CertVerifyProcInternalWithNetFetchingTest, |
| RevocationSoftFailLeafRevokedBySha1Crl) { |
| if (!SupportsSoftFailRevChecking()) { |
| LOG(INFO) << "Skipping test as verifier doesn't support " |
| "VERIFY_REV_CHECKING_ENABLED"; |
| return; |
| } |
| |
| const char kHostname[] = "www.example.com"; |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| // Root-issued CRL which does not revoke intermediate. |
| intermediate->SetCrlDistributionPointUrl(CreateAndServeCrl(root.get(), {})); |
| |
| // Leaf is revoked by intermediate issued CRL which is signed with |
| // ecdsaWithSha256. |
| leaf->SetCrlDistributionPointUrl( |
| CreateAndServeCrl(intermediate.get(), {leaf->GetSerialNumber()}, |
| SignatureAlgorithm::kEcdsaSha1)); |
| |
| // Trust the root and build a chain to verify that includes the intermediate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| // Verify with soft-fail revocation checking. |
| const int flags = CertVerifyProc::VERIFY_REV_CHECKING_ENABLED; |
| CertVerifyResult verify_result; |
| int error = |
| Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result); |
| |
| // Should fail, leaf is revoked. |
| EXPECT_THAT(error, IsError(ERR_CERT_REVOKED)); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED); |
| } |
| |
| TEST_P(CertVerifyProcInternalWithNetFetchingTest, |
| RevocationSoftFailLeafRevokedByMd5Crl) { |
| if (!SupportsSoftFailRevChecking()) { |
| LOG(INFO) << "Skipping test as verifier doesn't support " |
| "VERIFY_REV_CHECKING_ENABLED"; |
| return; |
| } |
| |
| const char kHostname[] = "www.example.com"; |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| // Root-issued CRL which does not revoke intermediate. |
| intermediate->SetCrlDistributionPointUrl(CreateAndServeCrl(root.get(), {})); |
| // This test wants to check handling of MD5 CRLs, but ecdsa-with-md5 |
| // signatureAlgorithm does not exist. Use an RSA private key for intermediate |
| // so that the CRL will be signed with the md5WithRSAEncryption algorithm. |
| intermediate->GenerateRSAKey(); |
| leaf->SetSignatureAlgorithm(SignatureAlgorithm::kRsaPkcs1Sha256); |
| |
| // Leaf is revoked by intermediate issued CRL which is signed with |
| // md5WithRSAEncryption. |
| leaf->SetCrlDistributionPointUrl(CreateAndServeCrlWithAlgorithmTlvAndDigest( |
| intermediate.get(), {leaf->GetSerialNumber()}, Md5WithRSAEncryption(), |
| EVP_md5())); |
| |
| // Trust the root and build a chain to verify that includes the intermediate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| // Verify with soft-fail revocation checking. |
| const int flags = CertVerifyProc::VERIFY_REV_CHECKING_ENABLED; |
| CertVerifyResult verify_result; |
| int error = |
| Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result); |
| |
| // Verification should succeed: MD5 signature algorithm is not supported |
| // and soft-fail checking will ignore the inability to get revocation |
| // status. |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED); |
| } |
| |
| // CRL soft fail test where the intermediate certificate has a good CRL, but |
| // the leaf's distribution point returns an http error. Verification should |
| // succeed. |
| TEST_P(CertVerifyProcInternalWithNetFetchingTest, |
| RevocationSoftFailLeafCrlDpHttpError) { |
| if (!SupportsSoftFailRevChecking()) { |
| LOG(INFO) << "Skipping test as verifier doesn't support " |
| "VERIFY_REV_CHECKING_ENABLED"; |
| return; |
| } |
| |
| const char kHostname[] = "www.example.com"; |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| // Serve a root-issued CRL which does not revoke intermediate. |
| intermediate->SetCrlDistributionPointUrl(CreateAndServeCrl(root.get(), {})); |
| |
| // Serve a 404 for the intermediate-issued CRL distribution point url. |
| leaf->SetCrlDistributionPointUrl(RegisterSimpleTestServerHandler( |
| MakeRandomPath(".crl"), HTTP_NOT_FOUND, "text/plain", "Not Found")); |
| |
| // Trust the root and build a chain to verify that includes the intermediate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| // Verify with soft-fail revocation checking. |
| const int flags = CertVerifyProc::VERIFY_REV_CHECKING_ENABLED; |
| CertVerifyResult verify_result; |
| int error = |
| Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result); |
| |
| // Should succeed due to soft-fail revocation checking. |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED); |
| } |
| |
| // CRL soft fail test where the leaf certificate has a good CRL, but |
| // the intermediate's distribution point returns an http error. Verification |
| // should succeed. |
| TEST_P(CertVerifyProcInternalWithNetFetchingTest, |
| RevocationSoftFailIntermediateCrlDpHttpError) { |
| if (!SupportsSoftFailRevChecking()) { |
| LOG(INFO) << "Skipping test as verifier doesn't support " |
| "VERIFY_REV_CHECKING_ENABLED"; |
| return; |
| } |
| |
| const char kHostname[] = "www.example.com"; |
| auto [leaf, intermediate, root] = CertBuilder::CreateSimpleChain3(); |
| |
| // Serve a 404 for the root-issued CRL distribution point url. |
| intermediate->SetCrlDistributionPointUrl(RegisterSimpleTestServerHandler( |
| MakeRandomPath(".crl"), HTTP_NOT_FOUND, "text/plain", "Not Found")); |
| |
| // Serve an intermediate-issued CRL which does not revoke leaf. |
| leaf->SetCrlDistributionPointUrl(CreateAndServeCrl(intermediate.get(), {})); |
| |
| // Trust the root and build a chain to verify that includes the intermediate. |
| ScopedTestRoot scoped_root(root->GetX509Certificate().get()); |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| // Verify with soft-fail revocation checking. |
| const int flags = CertVerifyProc::VERIFY_REV_CHECKING_ENABLED; |
| CertVerifyResult verify_result; |
| int error = |
| Verify(chain.get(), kHostname, flags, CertificateList(), &verify_result); |
| |
| // Should succeed due to soft-fail revocation checking. |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED); |
| } |
| |
| // Tests that an EV cert verification with successful online OCSP revocation |
| // checks is marked as CERT_STATUS_IS_EV. |
| TEST_P(CertVerifyProcInternalWithNetFetchingTest, |
| EVOnlineOCSPRevocationCheckingGood) { |
| if (!SupportsEV()) { |
| LOG(INFO) << "Skipping test as EV verification is not yet supported"; |
| return; |
| } |
| |
| const char kEVTestCertPolicy[] = "1.2.3.4"; |
| EmbeddedTestServer::ServerCertificateConfig cert_config; |
| cert_config.policy_oids = {kEVTestCertPolicy}; |
| cert_config.ocsp_config = EmbeddedTestServer::OCSPConfig( |
| {{OCSPRevocationStatus::GOOD, |
| EmbeddedTestServer::OCSPConfig::SingleResponse::Date::kValid}}); |
| |
| EmbeddedTestServer ocsp_test_server(EmbeddedTestServer::TYPE_HTTPS); |
| ocsp_test_server.SetSSLConfig(cert_config); |
| EXPECT_TRUE(ocsp_test_server.Start()); |
| |
| scoped_refptr<X509Certificate> root = |
| ImportCertFromFile(GetTestCertsDirectory(), "root_ca_cert.pem"); |
| ASSERT_TRUE(root.get()); |
| |
| scoped_refptr<X509Certificate> chain = ocsp_test_server.GetCertificate(); |
| ASSERT_TRUE(chain.get()); |
| |
| // Consider the root of the test chain a valid EV root for the test policy. |
| ScopedTestEVPolicy scoped_test_ev_policy( |
| EVRootCAMetadata::GetInstance(), |
| X509Certificate::CalculateFingerprint256(root->cert_buffer()), |
| kEVTestCertPolicy); |
| |
| CertVerifyResult verify_result; |
| int flags = 0; |
| int error = Verify(chain.get(), ocsp_test_server.host_port_pair().host(), |
| flags, CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_IS_EV); |
| EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED); |
| } |
| |
| // Tests that an EV cert verification with that could not retrieve online OCSP |
| // revocation information is verified but still marked as CERT_STATUS_IS_EV. |
| TEST_P(CertVerifyProcInternalWithNetFetchingTest, |
| EVOnlineOCSPRevocationCheckingSoftFail) { |
| if (!SupportsEV()) { |
| LOG(INFO) << "Skipping test as EV verification is not yet supported"; |
| return; |
| } |
| |
| const char kEVTestCertPolicy[] = "1.2.3.4"; |
| EmbeddedTestServer::ServerCertificateConfig cert_config; |
| cert_config.policy_oids = {kEVTestCertPolicy}; |
| // Retrieving OCSP status returns an error. |
| cert_config.ocsp_config = EmbeddedTestServer::OCSPConfig( |
| EmbeddedTestServer::OCSPConfig::ResponseType::kInternalError); |
| |
| EmbeddedTestServer ocsp_test_server(EmbeddedTestServer::TYPE_HTTPS); |
| ocsp_test_server.SetSSLConfig(cert_config); |
| EXPECT_TRUE(ocsp_test_server.Start()); |
| |
| scoped_refptr<X509Certificate> root = |
| ImportCertFromFile(GetTestCertsDirectory(), "root_ca_cert.pem"); |
| ASSERT_TRUE(root.get()); |
| |
| scoped_refptr<X509Certificate> chain = ocsp_test_server.GetCertificate(); |
| ASSERT_TRUE(chain.get()); |
| |
| // Consider the root of the test chain a valid EV root for the test policy. |
| ScopedTestEVPolicy scoped_test_ev_policy( |
| EVRootCAMetadata::GetInstance(), |
| X509Certificate::CalculateFingerprint256(root->cert_buffer()), |
| kEVTestCertPolicy); |
| |
| CertVerifyResult verify_result; |
| int flags = 0; |
| int error = Verify(chain.get(), ocsp_test_server.host_port_pair().host(), |
| flags, CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_IS_EV); |
| EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED); |
| } |
| |
| // Tests that an EV cert verification with online OCSP returning affirmatively |
| // revoked is marked as CERT_STATUS_IS_EV. |
| TEST_P(CertVerifyProcInternalWithNetFetchingTest, |
| EVOnlineOCSPRevocationCheckingRevoked) { |
| if (!SupportsEV()) { |
| LOG(INFO) << "Skipping test as EV verification is not yet supported"; |
| return; |
| } |
| |
| const char kEVTestCertPolicy[] = "1.2.3.4"; |
| EmbeddedTestServer::ServerCertificateConfig cert_config; |
| cert_config.policy_oids = {kEVTestCertPolicy}; |
| cert_config.ocsp_config = EmbeddedTestServer::OCSPConfig( |
| {{OCSPRevocationStatus::REVOKED, |
| EmbeddedTestServer::OCSPConfig::SingleResponse::Date::kValid}}); |
| |
| EmbeddedTestServer ocsp_test_server(EmbeddedTestServer::TYPE_HTTPS); |
| ocsp_test_server.SetSSLConfig(cert_config); |
| EXPECT_TRUE(ocsp_test_server.Start()); |
| |
| scoped_refptr<X509Certificate> root = |
| ImportCertFromFile(GetTestCertsDirectory(), "root_ca_cert.pem"); |
| ASSERT_TRUE(root.get()); |
| |
| scoped_refptr<X509Certificate> chain = ocsp_test_server.GetCertificate(); |
| ASSERT_TRUE(chain.get()); |
| |
| // Consider the root of the test chain a valid EV root for the test policy. |
| ScopedTestEVPolicy scoped_test_ev_policy( |
| EVRootCAMetadata::GetInstance(), |
| X509Certificate::CalculateFingerprint256(root->cert_buffer()), |
| kEVTestCertPolicy); |
| |
| CertVerifyResult verify_result; |
| int flags = 0; |
| int error = Verify(chain.get(), ocsp_test_server.host_port_pair().host(), |
| flags, CertificateList(), &verify_result); |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_IS_EV); |
| EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED); |
| } |
| |
| // A set of tests that check how various constraints are enforced when they |
| // appear at different points in the chain, such as on the trust anchor versus |
| // on intermediates. |
| class CertVerifyProcConstraintsTest : public CertVerifyProcInternalTest { |
| protected: |
| void SetUp() override { |
| CertVerifyProcInternalTest::SetUp(); |
| |
| chain_ = CertBuilder::CreateSimpleChain(/*chain_length=*/4); |
| } |
| |
| int VerifyWithTrust(CertificateTrust trust) { |
| ScopedTestRoot test_root(chain_.back()->GetX509Certificate().get(), trust); |
| CertVerifyResult verify_result; |
| int flags = 0; |
| return CertVerifyProcInternalTest::Verify( |
| chain_.front()->GetX509CertificateChain().get(), "www.example.com", |
| flags, CertificateList(), &verify_result); |
| } |
| |
| int Verify() { return VerifyWithTrust(CertificateTrust::ForTrustAnchor()); } |
| |
| int VerifyWithExpiryAndConstraints() { |
| return VerifyWithTrust(CertificateTrust::ForTrustAnchor() |
| .WithEnforceAnchorExpiry() |
| .WithEnforceAnchorConstraints()); |
| } |
| |
| int VerifyWithExpiryAndFullConstraints() { |
| return VerifyWithTrust(CertificateTrust::ForTrustAnchor() |
| .WithEnforceAnchorExpiry() |
| .WithEnforceAnchorConstraints() |
| .WithRequireAnchorBasicConstraints()); |
| } |
| |
| int ExpectedIntermediateConstraintError() { |
| if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID) |
| return ERR_CERT_AUTHORITY_INVALID; |
| return ERR_CERT_INVALID; |
| } |
| |
| std::vector<std::unique_ptr<CertBuilder>> chain_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| CertVerifyProcConstraintsTest, |
| testing::ValuesIn(kAllCertVerifiers), |
| VerifyProcTypeToName); |
| |
| TEST_P(CertVerifyProcConstraintsTest, BaseCase) { |
| // Without changing anything on the test chain, it should validate |
| // successfully. If this is not true then the rest of the tests in this class |
| // are unlikely to be useful. |
| EXPECT_THAT(Verify(), IsOk()); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk()); |
| EXPECT_THAT(VerifyWithExpiryAndFullConstraints(), IsOk()); |
| EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForTrustAnchorOrLeaf()), |
| IsOk()); |
| EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForTrustedLeaf()), |
| IsError(ERR_CERT_AUTHORITY_INVALID)); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, BasicConstraintsNotCaRoot) { |
| chain_[3]->SetBasicConstraints(/*is_ca=*/false, /*path_len=*/-1); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsOk()); |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsError(ERR_CERT_INVALID)); |
| EXPECT_THAT(VerifyWithExpiryAndFullConstraints(), |
| IsError(ERR_CERT_INVALID)); |
| } else if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID) { |
| EXPECT_THAT(Verify(), IsOk()); |
| } else { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID)); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, BasicConstraintsNotCaIntermediate) { |
| chain_[2]->SetBasicConstraints(/*is_ca=*/false, /*path_len=*/-1); |
| |
| EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError())); |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, BasicConstraintsIsCaLeaf) { |
| for (bool has_key_usage_cert_sign : {false, true}) { |
| chain_[0]->SetBasicConstraints(/*is_ca=*/true, /*path_len=*/-1); |
| |
| if (has_key_usage_cert_sign) { |
| chain_[0]->SetKeyUsages( |
| {KEY_USAGE_BIT_KEY_CERT_SIGN, KEY_USAGE_BIT_DIGITAL_SIGNATURE}); |
| } else { |
| chain_[0]->SetKeyUsages({KEY_USAGE_BIT_DIGITAL_SIGNATURE}); |
| } |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, BasicConstraintsPathlen0Root) { |
| chain_[3]->SetBasicConstraints(/*is_ca=*/true, /*path_len=*/0); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsOk()); |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsError(ERR_CERT_INVALID)); |
| } else if (VerifyProcTypeIsIOSAtMostOS14() || |
| verify_proc_type() == CERT_VERIFY_PROC_ANDROID) { |
| EXPECT_THAT(Verify(), IsOk()); |
| } else { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID)); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, BasicConstraintsPathlen1Root) { |
| chain_[3]->SetBasicConstraints(/*is_ca=*/true, /*path_len=*/1); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsOk()); |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsError(ERR_CERT_INVALID)); |
| } else if (VerifyProcTypeIsIOSAtMostOS14() || |
| verify_proc_type() == CERT_VERIFY_PROC_ANDROID) { |
| EXPECT_THAT(Verify(), IsOk()); |
| } else { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID)); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, BasicConstraintsPathlen2Root) { |
| chain_[3]->SetBasicConstraints(/*is_ca=*/true, /*path_len=*/2); |
| |
| EXPECT_THAT(Verify(), IsOk()); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, |
| BasicConstraintsPathlen0IntermediateParent) { |
| chain_[2]->SetBasicConstraints(/*is_ca=*/true, /*path_len=*/0); |
| |
| EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError())); |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, |
| BasicConstraintsPathlen1IntermediateParent) { |
| chain_[2]->SetBasicConstraints(/*is_ca=*/true, /*path_len=*/1); |
| |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, |
| BasicConstraintsPathlen0IntermediateChild) { |
| chain_[1]->SetBasicConstraints(/*is_ca=*/true, /*path_len=*/0); |
| |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, BasicConstraintsNotPresentRoot) { |
| chain_[3]->EraseExtension(der::Input(kBasicConstraintsOid)); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsOk()); |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk()); |
| EXPECT_THAT(VerifyWithExpiryAndFullConstraints(), |
| IsError(ERR_CERT_INVALID)); |
| } else if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID) { |
| EXPECT_THAT(Verify(), IsOk()); |
| } else { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID)); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, BasicConstraintsNotPresentRootX509V1) { |
| chain_[3]->SetCertificateVersion(CertificateVersion::V1); |
| chain_[3]->ClearExtensions(); |
| |
| EXPECT_THAT(Verify(), IsOk()); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk()); |
| EXPECT_THAT(VerifyWithExpiryAndFullConstraints(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, BasicConstraintsNotPresentIntermediate) { |
| chain_[2]->EraseExtension(der::Input(kBasicConstraintsOid)); |
| |
| EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError())); |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, BasicConstraintsNotPresentLeaf) { |
| chain_[0]->EraseExtension(der::Input(kBasicConstraintsOid)); |
| |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, NameConstraintsNotMatchingRoot) { |
| chain_[3]->SetNameConstraintsDnsNames(/*permitted_dns_names=*/{"example.org"}, |
| /*excluded_dns_names=*/{}); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsOk()); |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsError(ERR_CERT_INVALID)); |
| } else if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID) { |
| EXPECT_THAT(Verify(), IsOk()); |
| } else { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID)); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, NameConstraintsNotMatchingIntermediate) { |
| chain_[2]->SetNameConstraintsDnsNames( |
| /*permitted_dns_names=*/{"example.org"}, |
| /*excluded_dns_names=*/{}); |
| |
| EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError())); |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, NameConstraintsMatchingRoot) { |
| chain_[3]->SetNameConstraintsDnsNames(/*permitted_dns_names=*/{"example.com"}, |
| /*excluded_dns_names=*/{}); |
| |
| EXPECT_THAT(Verify(), IsOk()); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, NameConstraintsMatchingIntermediate) { |
| chain_[2]->SetNameConstraintsDnsNames( |
| /*permitted_dns_names=*/{"example.com"}, |
| /*excluded_dns_names=*/{}); |
| |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, NameConstraintsOnLeaf) { |
| chain_[0]->SetNameConstraintsDnsNames( |
| /*permitted_dns_names=*/{"example.com"}, |
| /*excluded_dns_names=*/{}); |
| |
| // TODO(mattm): this should be an error |
| // RFC 5280 4.2.1.10 says: "The name constraints extension, which MUST be |
| // used only in a CA certificate, ..." |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, ValidityExpiredRoot) { |
| chain_[3]->SetValidity(base::Time::Now() - base::Days(14), |
| base::Time::Now() - base::Days(7)); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsOk()); |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), |
| IsError(ERR_CERT_DATE_INVALID)); |
| EXPECT_THAT(VerifyWithExpiryAndFullConstraints(), |
| IsError(ERR_CERT_DATE_INVALID)); |
| } else if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID) { |
| EXPECT_THAT(Verify(), IsOk()); |
| } else { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_DATE_INVALID)); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, ValidityNotYetValidRoot) { |
| chain_[3]->SetValidity(base::Time::Now() + base::Days(7), |
| base::Time::Now() + base::Days(14)); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsOk()); |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), |
| IsError(ERR_CERT_DATE_INVALID)); |
| EXPECT_THAT(VerifyWithExpiryAndFullConstraints(), |
| IsError(ERR_CERT_DATE_INVALID)); |
| } else if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID) { |
| EXPECT_THAT(Verify(), IsOk()); |
| } else { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_DATE_INVALID)); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, ValidityExpiredIntermediate) { |
| chain_[2]->SetValidity(base::Time::Now() - base::Days(14), |
| base::Time::Now() - base::Days(7)); |
| |
| if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID) { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID)); |
| } else { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_DATE_INVALID)); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, ValidityNotYetValidIntermediate) { |
| chain_[2]->SetValidity(base::Time::Now() + base::Days(7), |
| base::Time::Now() + base::Days(14)); |
| |
| if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID) { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID)); |
| } else { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_DATE_INVALID)); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, PolicyConstraints0Root) { |
| for (bool leaf_has_policy : {false, true}) { |
| SCOPED_TRACE(leaf_has_policy); |
| |
| static const char kPolicy1[] = "1.2.3.4"; |
| static const char kPolicy2[] = "1.2.3.4.5"; |
| static const char kPolicy3[] = "1.2.3.5"; |
| chain_[3]->SetPolicyConstraints( |
| /*require_explicit_policy=*/0, |
| /*inhibit_policy_mapping=*/absl::nullopt); |
| chain_[3]->SetCertificatePolicies({kPolicy1, kPolicy2}); |
| chain_[2]->SetCertificatePolicies({kPolicy3, kPolicy1}); |
| chain_[1]->SetCertificatePolicies({kPolicy1}); |
| |
| if (leaf_has_policy) { |
| chain_[0]->SetCertificatePolicies({kPolicy1}); |
| EXPECT_THAT(Verify(), IsOk()); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk()); |
| } |
| } else { |
| chain_[0]->SetCertificatePolicies({}); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsOk()); |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), |
| IsError(ERR_CERT_INVALID)); |
| } else if (verify_proc_type() == CERT_VERIFY_PROC_IOS || |
| verify_proc_type() == CERT_VERIFY_PROC_ANDROID) { |
| EXPECT_THAT(Verify(), IsOk()); |
| } else { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID)); |
| } |
| } |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, PolicyConstraints4Root) { |
| // Explicit policy is required after 4 certs. Since the chain is 4 certs |
| // long, an explicit policy is never required. |
| chain_[3]->SetPolicyConstraints( |
| /*require_explicit_policy=*/4, |
| /*inhibit_policy_mapping=*/absl::nullopt); |
| |
| EXPECT_THAT(Verify(), IsOk()); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, PolicyConstraints3Root) { |
| // Explicit policy is required after 3 certs. Since the chain is 4 certs |
| // long, an explicit policy is required and the chain should fail if anchor |
| // constraints are enforced. |
| chain_[3]->SetPolicyConstraints( |
| /*require_explicit_policy=*/3, |
| /*inhibit_policy_mapping=*/absl::nullopt); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsOk()); |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsError(ERR_CERT_INVALID)); |
| } else { |
| // Windows seems to have an off-by-one error in how it enforces |
| // requireExplicitPolicy. |
| // (The mac/android verifiers are Ok here since they don't enforce |
| // policyConstraints on anchors.) |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, PolicyConstraints2Root) { |
| // Explicit policy is required after 2 certs. Since the chain is 4 certs |
| // long, an explicit policy is required and the chain should fail if anchor |
| // constraints are enforced. |
| chain_[3]->SetPolicyConstraints( |
| /*require_explicit_policy=*/2, |
| /*inhibit_policy_mapping=*/absl::nullopt); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsOk()); |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsError(ERR_CERT_INVALID)); |
| } else if (verify_proc_type() == CERT_VERIFY_PROC_IOS || |
| verify_proc_type() == CERT_VERIFY_PROC_ANDROID) { |
| EXPECT_THAT(Verify(), IsOk()); |
| } else { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID)); |
| } |
| } |
| |
| // This is also a regression test for https://crbug.com/31497: If an |
| // intermediate has requireExplicitPolicy in its policyConstraints extension, |
| // verification should still succeed as long as some policy is valid for the |
| // chain, since Chrome does not specify any required policy as an input to |
| // certificate verification (allows anyPolicy). |
| TEST_P(CertVerifyProcConstraintsTest, PolicyConstraints0Intermediate) { |
| for (bool leaf_has_policy : {false, true}) { |
| SCOPED_TRACE(leaf_has_policy); |
| |
| static const char kPolicy1[] = "1.2.3.4"; |
| static const char kPolicy2[] = "1.2.3.4.5"; |
| static const char kPolicy3[] = "1.2.3.5"; |
| chain_[2]->SetPolicyConstraints( |
| /*require_explicit_policy=*/0, |
| /*inhibit_policy_mapping=*/absl::nullopt); |
| chain_[2]->SetCertificatePolicies({kPolicy1, kPolicy2}); |
| chain_[1]->SetCertificatePolicies({kPolicy3, kPolicy1}); |
| |
| if (leaf_has_policy) { |
| chain_[0]->SetCertificatePolicies({kPolicy1}); |
| EXPECT_THAT(Verify(), IsOk()); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk()); |
| } |
| } else { |
| chain_[0]->SetCertificatePolicies({}); |
| EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError())); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), |
| IsError(ERR_CERT_INVALID)); |
| } |
| } |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, PolicyConstraints3Intermediate) { |
| // Explicit policy is required after 3 certs. Since the chain up to |
| // |chain_[2]| is 3 certs long, an explicit policy is never required. |
| chain_[2]->SetPolicyConstraints( |
| /*require_explicit_policy=*/3, |
| /*inhibit_policy_mapping=*/absl::nullopt); |
| |
| EXPECT_THAT(Verify(), IsOk()); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, PolicyConstraints2Intermediate) { |
| // Explicit policy is required after 2 certs. Since the chain up to |
| // |chain_[2]| is 3 certs long, an explicit policy will be required and this |
| // should fail to verify. |
| chain_[2]->SetPolicyConstraints( |
| /*require_explicit_policy=*/2, |
| /*inhibit_policy_mapping=*/absl::nullopt); |
| |
| EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError())); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsError(ERR_CERT_INVALID)); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, PolicyConstraints1Intermediate) { |
| // Explicit policy is required after 1 cert. Since the chain up to |
| // |chain_[2]| is 3 certs long, an explicit policy will be required and this |
| // should fail to verify. |
| chain_[2]->SetPolicyConstraints( |
| /*require_explicit_policy=*/1, |
| /*inhibit_policy_mapping=*/absl::nullopt); |
| |
| EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError())); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsError(ERR_CERT_INVALID)); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, PolicyConstraints0Leaf) { |
| // Setting requireExplicitPolicy to 0 on the target certificate should make |
| // an explicit policy required for the chain. (Ref: RFC 5280 section 6.1.5.b |
| // and the final paragraph of 6.1.5) |
| chain_[0]->SetPolicyConstraints( |
| /*require_explicit_policy=*/0, |
| /*inhibit_policy_mapping=*/absl::nullopt); |
| |
| EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError())); |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, InhibitPolicyMapping0Root) { |
| static const char kPolicy1[] = "1.2.3.4"; |
| static const char kPolicy2[] = "1.2.3.5"; |
| |
| // Root inhibits policy mapping immediately. |
| chain_[3]->SetPolicyConstraints( |
| /*require_explicit_policy=*/absl::nullopt, |
| /*inhibit_policy_mapping=*/0); |
| |
| // Policy constraints are specified on an intermediate so that an explicit |
| // policy will be required regardless if root constraints are applied. |
| chain_[2]->SetPolicyConstraints( |
| /*require_explicit_policy=*/0, |
| /*inhibit_policy_mapping=*/absl::nullopt); |
| |
| // Intermediate uses policy mappings. This should not be allowed if the root |
| // constraints were enforced. |
| chain_[2]->SetCertificatePolicies({kPolicy1}); |
| chain_[2]->SetPolicyMappings({{kPolicy1, kPolicy2}}); |
| |
| // Children require the policy mapping to have a valid policy. |
| chain_[1]->SetCertificatePolicies({kPolicy2}); |
| chain_[0]->SetCertificatePolicies({kPolicy2}); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsOk()); |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsError(ERR_CERT_INVALID)); |
| } else if (verify_proc_type() == CERT_VERIFY_PROC_IOS || |
| verify_proc_type() == CERT_VERIFY_PROC_ANDROID) { |
| EXPECT_THAT(Verify(), IsOk()); |
| } else { |
| // Windows enforces inhibitPolicyMapping on the root. |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID)); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, InhibitPolicyMapping1Root) { |
| static const char kPolicy1[] = "1.2.3.4"; |
| static const char kPolicy2[] = "1.2.3.5"; |
| |
| // Root inhibits policy mapping after 1 cert. |
| chain_[3]->SetPolicyConstraints( |
| /*require_explicit_policy=*/absl::nullopt, |
| /*inhibit_policy_mapping=*/1); |
| |
| // Policy constraints are specified on an intermediate so that an explicit |
| // policy will be required regardless if root constraints are applied. |
| chain_[2]->SetPolicyConstraints( |
| /*require_explicit_policy=*/0, |
| /*inhibit_policy_mapping=*/absl::nullopt); |
| |
| // Intermediate uses policy mappings. This should be allowed even if the root |
| // constraints were enforced, since policy mapping was allowed for 1 cert |
| // following the root. |
| chain_[2]->SetCertificatePolicies({kPolicy1}); |
| chain_[2]->SetPolicyMappings({{kPolicy1, kPolicy2}}); |
| |
| // Children require the policy mapping to have a valid policy. |
| chain_[1]->SetCertificatePolicies({kPolicy2}); |
| chain_[0]->SetCertificatePolicies({kPolicy2}); |
| |
| EXPECT_THAT(Verify(), IsOk()); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, InhibitAnyPolicy0Root) { |
| static const char kAnyPolicy[] = "2.5.29.32.0"; |
| static const char kPolicy1[] = "1.2.3.4"; |
| |
| // Since inhibitAnyPolicy is 0, anyPolicy should not be allow for any certs |
| // after the root. |
| chain_[3]->SetInhibitAnyPolicy(0); |
| chain_[3]->SetCertificatePolicies({kAnyPolicy}); |
| |
| // Policy constraints are specified on an intermediate so that an explicit |
| // policy will be required regardless if root constraints are applied. |
| chain_[2]->SetPolicyConstraints( |
| /*require_explicit_policy=*/0, |
| /*inhibit_policy_mapping=*/absl::nullopt); |
| |
| // This intermediate only asserts anyPolicy, so this chain should |
| // be invalid if policyConstraints from the root cert are enforced. |
| chain_[2]->SetCertificatePolicies({kAnyPolicy}); |
| |
| chain_[1]->SetCertificatePolicies({kPolicy1}); |
| chain_[0]->SetCertificatePolicies({kPolicy1}); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsOk()); |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsError(ERR_CERT_INVALID)); |
| } else if (verify_proc_type() == CERT_VERIFY_PROC_IOS || |
| verify_proc_type() == CERT_VERIFY_PROC_ANDROID) { |
| EXPECT_THAT(Verify(), IsOk()); |
| } else { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID)); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, InhibitAnyPolicy1Root) { |
| for (bool chain_1_has_any_policy : {false, true}) { |
| SCOPED_TRACE(chain_1_has_any_policy); |
| |
| static const char kAnyPolicy[] = "2.5.29.32.0"; |
| static const char kPolicy1[] = "1.2.3.4"; |
| |
| // Since inhibitAnyPolicy is 1, anyPolicy should be allowed for the root's |
| // immediate child, but not after that. |
| chain_[3]->SetInhibitAnyPolicy(1); |
| chain_[3]->SetCertificatePolicies({kAnyPolicy}); |
| |
| // Policy constraints are specified on an intermediate so that an explicit |
| // policy will be required regardless if root constraints are applied. |
| chain_[2]->SetPolicyConstraints( |
| /*require_explicit_policy=*/0, |
| /*inhibit_policy_mapping=*/absl::nullopt); |
| |
| // AnyPolicy should be allowed in this cert. |
| chain_[2]->SetCertificatePolicies({kAnyPolicy}); |
| |
| chain_[0]->SetCertificatePolicies({kPolicy1}); |
| |
| if (chain_1_has_any_policy) { |
| // AnyPolicy should not be allowed in this cert if the inhibitAnyPolicy |
| // constraint from the root is honored. |
| chain_[1]->SetCertificatePolicies({kAnyPolicy}); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsOk()); |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), |
| IsError(ERR_CERT_INVALID)); |
| } else if (verify_proc_type() == CERT_VERIFY_PROC_IOS || |
| verify_proc_type() == CERT_VERIFY_PROC_ANDROID) { |
| EXPECT_THAT(Verify(), IsOk()); |
| } else { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID)); |
| } |
| } else { |
| chain_[1]->SetCertificatePolicies({kPolicy1}); |
| |
| EXPECT_THAT(Verify(), IsOk()); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk()); |
| } |
| } |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, InhibitAnyPolicy0Intermediate) { |
| static const char kAnyPolicy[] = "2.5.29.32.0"; |
| static const char kPolicy1[] = "1.2.3.4"; |
| |
| chain_[2]->SetInhibitAnyPolicy(0); |
| chain_[2]->SetPolicyConstraints( |
| /*require_explicit_policy=*/0, |
| /*inhibit_policy_mapping=*/absl::nullopt); |
| |
| chain_[2]->SetCertificatePolicies({kAnyPolicy}); |
| // This shouldn't be allowed as the parent cert set inhibitAnyPolicy=0. |
| chain_[1]->SetCertificatePolicies({kAnyPolicy}); |
| chain_[0]->SetCertificatePolicies({kPolicy1}); |
| |
| EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError())); |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, InhibitAnyPolicy1Intermediate) { |
| static const char kAnyPolicy[] = "2.5.29.32.0"; |
| static const char kPolicy1[] = "1.2.3.4"; |
| |
| chain_[2]->SetInhibitAnyPolicy(1); |
| chain_[2]->SetPolicyConstraints( |
| /*require_explicit_policy=*/0, |
| /*inhibit_policy_mapping=*/absl::nullopt); |
| |
| chain_[2]->SetCertificatePolicies({kAnyPolicy}); |
| // This is okay as the parent cert set inhibitAnyPolicy=1. |
| chain_[1]->SetCertificatePolicies({kAnyPolicy}); |
| chain_[0]->SetCertificatePolicies({kPolicy1}); |
| |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, PoliciesRoot) { |
| static const char kPolicy1[] = "1.2.3.4"; |
| static const char kPolicy2[] = "1.2.3.5"; |
| |
| for (bool root_has_matching_policy : {false, true}) { |
| SCOPED_TRACE(root_has_matching_policy); |
| |
| if (root_has_matching_policy) { |
| // This chain should be valid whether or not policies from the root are |
| // processed. |
| chain_[3]->SetCertificatePolicies({kPolicy1}); |
| } else { |
| // If the policies from the root are processed, this chain will not be |
| // valid for any policy. |
| chain_[3]->SetCertificatePolicies({kPolicy2}); |
| } |
| |
| // Policy constraints are specified on an intermediate so that an explicit |
| // policy will be required regardless if root constraints are applied. |
| chain_[2]->SetPolicyConstraints( |
| /*require_explicit_policy=*/0, |
| /*inhibit_policy_mapping=*/absl::nullopt); |
| |
| chain_[2]->SetCertificatePolicies({kPolicy1}); |
| chain_[1]->SetCertificatePolicies({kPolicy1}); |
| chain_[0]->SetCertificatePolicies({kPolicy1}); |
| |
| if (root_has_matching_policy) { |
| EXPECT_THAT(Verify(), IsOk()); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk()); |
| } |
| } else { |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsOk()); |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), |
| IsError(ERR_CERT_INVALID)); |
| } else if (verify_proc_type() == CERT_VERIFY_PROC_IOS || |
| verify_proc_type() == CERT_VERIFY_PROC_ANDROID) { |
| EXPECT_THAT(Verify(), IsOk()); |
| } else { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID)); |
| } |
| } |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, PolicyMappingsRoot) { |
| static const char kPolicy1[] = "1.2.3.4"; |
| static const char kPolicy2[] = "1.2.3.5"; |
| static const char kPolicy3[] = "1.2.3.6"; |
| |
| for (bool root_has_matching_policy_mapping : {false, true}) { |
| SCOPED_TRACE(root_has_matching_policy_mapping); |
| |
| if (root_has_matching_policy_mapping) { |
| // This chain should be valid if the policies and policy mapping on the |
| // root are processed, or if neither is processed. It will not be valid |
| // if the policies were processed and the policyMappings were not. |
| chain_[3]->SetCertificatePolicies({kPolicy1}); |
| chain_[3]->SetPolicyMappings({{kPolicy1, kPolicy2}}); |
| } else { |
| // This chain should not be valid if the policies and policyMappings on |
| // the root were processed. It will be valid if the policies were |
| // processed and policyMappings were not. |
| chain_[3]->SetCertificatePolicies({kPolicy2}); |
| chain_[3]->SetPolicyMappings({{kPolicy2, kPolicy3}}); |
| } |
| |
| // Policy constraints are specified on an intermediate so that an explicit |
| // policy will be required regardless if root constraints are applied. |
| chain_[2]->SetPolicyConstraints( |
| /*require_explicit_policy=*/0, |
| /*inhibit_policy_mapping=*/absl::nullopt); |
| |
| chain_[2]->SetCertificatePolicies({kPolicy2}); |
| chain_[1]->SetCertificatePolicies({kPolicy2}); |
| chain_[0]->SetCertificatePolicies({kPolicy2}); |
| |
| if (root_has_matching_policy_mapping) { |
| EXPECT_THAT(Verify(), IsOk()); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk()); |
| } |
| } else { |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsOk()); |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), |
| IsError(ERR_CERT_INVALID)); |
| } else if (verify_proc_type() == CERT_VERIFY_PROC_IOS || |
| verify_proc_type() == CERT_VERIFY_PROC_ANDROID) { |
| EXPECT_THAT(Verify(), IsOk()); |
| } else { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID)); |
| } |
| } |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, KeyUsageNoCertSignRoot) { |
| chain_[3]->SetKeyUsages({KEY_USAGE_BIT_CRL_SIGN}); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsOk()); |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsError(ERR_CERT_INVALID)); |
| EXPECT_THAT(VerifyWithExpiryAndFullConstraints(), |
| IsError(ERR_CERT_INVALID)); |
| } else if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID) { |
| EXPECT_THAT(Verify(), IsOk()); |
| } else { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID)); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, KeyUsageNotPresentRoot) { |
| chain_[3]->EraseExtension(der::Input(kKeyUsageOid)); |
| |
| EXPECT_THAT(Verify(), IsOk()); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk()); |
| EXPECT_THAT(VerifyWithExpiryAndFullConstraints(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, KeyUsageNoCertSignIntermediate) { |
| chain_[2]->SetKeyUsages({KEY_USAGE_BIT_CRL_SIGN}); |
| |
| EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError())); |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, KeyUsageNotPresentIntermediate) { |
| chain_[2]->EraseExtension(der::Input(kKeyUsageOid)); |
| |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, KeyUsageNoDigitalSignatureLeaf) { |
| // This test is mostly uninteresting since keyUsage on the end-entity is only |
| // checked at the TLS layer, not during cert verification. |
| chain_[0]->SetKeyUsages({KEY_USAGE_BIT_CRL_SIGN}); |
| |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, KeyUsageNotPresentLeaf) { |
| // This test is mostly uninteresting since keyUsage on the end-entity is only |
| // checked at the TLS layer, not during cert verification. |
| chain_[0]->EraseExtension(der::Input(kKeyUsageOid)); |
| |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, KeyUsageCertSignLeaf) { |
| // Test a leaf that has keyUsage asserting keyCertSign and basicConstraints |
| // asserting CA=false. This should be an error according to 5280 section |
| // 4.2.1.3 and 4.2.1.9, however most implementations seem to allow it. |
| // Perhaps because 5280 section 6 does not explicitly say to enforce this on |
| // the target cert. |
| chain_[0]->SetKeyUsages( |
| {KEY_USAGE_BIT_KEY_CERT_SIGN, KEY_USAGE_BIT_DIGITAL_SIGNATURE}); |
| |
| EXPECT_THAT(Verify(), IsOk()); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk()); |
| EXPECT_THAT(VerifyWithExpiryAndFullConstraints(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, ExtendedKeyUsageNoServerAuthRoot) { |
| chain_[3]->SetExtendedKeyUsages({der::Input(kCodeSigning)}); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsOk()); |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsError(ERR_CERT_INVALID)); |
| EXPECT_THAT(VerifyWithExpiryAndFullConstraints(), |
| IsError(ERR_CERT_INVALID)); |
| } else if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID || |
| verify_proc_type() == CERT_VERIFY_PROC_IOS) { |
| EXPECT_THAT(Verify(), IsOk()); |
| } else { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID)); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, ExtendedKeyUsageServerAuthRoot) { |
| chain_[3]->SetExtendedKeyUsages({der::Input(kServerAuth)}); |
| |
| EXPECT_THAT(Verify(), IsOk()); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, |
| ExtendedKeyUsageNoServerAuthIntermediate) { |
| chain_[2]->SetExtendedKeyUsages({der::Input(kCodeSigning)}); |
| |
| if (verify_proc_type() == CERT_VERIFY_PROC_ANDROID || |
| VerifyProcTypeIsIOSAtMostOS15()) { |
| EXPECT_THAT(Verify(), IsOk()); |
| } else { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID)); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, ExtendedKeyUsageServerAuthIntermediate) { |
| chain_[2]->SetExtendedKeyUsages({der::Input(kServerAuth)}); |
| |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, ExtendedKeyUsageNoServerAuthLeaf) { |
| chain_[0]->SetExtendedKeyUsages({der::Input(kCodeSigning)}); |
| |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID)); |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, UnknownSignatureAlgorithmRoot) { |
| chain_[3]->SetSignatureAlgorithmTLV(TestOid0SignatureAlgorithmTLV()); |
| |
| EXPECT_THAT(Verify(), IsOk()); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, UnknownSignatureAlgorithmIntermediate) { |
| chain_[2]->SetSignatureAlgorithmTLV(TestOid0SignatureAlgorithmTLV()); |
| |
| if (verify_proc_type() == CERT_VERIFY_PROC_IOS) { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID)); |
| } else { |
| EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError())); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, UnknownSignatureAlgorithmLeaf) { |
| chain_[0]->SetSignatureAlgorithmTLV(TestOid0SignatureAlgorithmTLV()); |
| |
| if (verify_proc_type() == CERT_VERIFY_PROC_IOS) { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID)); |
| } else { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID)); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, UnknownExtensionRoot) { |
| for (bool critical : {true, false}) { |
| SCOPED_TRACE(critical); |
| chain_[3]->SetExtension(TestOid0(), "hello world", critical); |
| |
| if (critical) { |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsOk()); |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), |
| IsError(ERR_CERT_INVALID)); |
| EXPECT_THAT(VerifyWithExpiryAndFullConstraints(), |
| IsError(ERR_CERT_INVALID)); |
| } else if (verify_proc_type() == CERT_VERIFY_PROC_IOS || |
| verify_proc_type() == CERT_VERIFY_PROC_ANDROID) { |
| EXPECT_THAT(Verify(), IsOk()); |
| } else { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID)); |
| } |
| } else { |
| EXPECT_THAT(Verify(), IsOk()); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyWithExpiryAndConstraints(), IsOk()); |
| } |
| } |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, UnknownExtensionIntermediate) { |
| for (bool critical : {true, false}) { |
| SCOPED_TRACE(critical); |
| chain_[2]->SetExtension(TestOid0(), "hello world", critical); |
| |
| if (critical) { |
| EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError())); |
| } else { |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTest, UnknownExtensionLeaf) { |
| for (bool critical : {true, false}) { |
| SCOPED_TRACE(critical); |
| chain_[0]->SetExtension(TestOid0(), "hello world", critical); |
| |
| if (critical) { |
| EXPECT_THAT(Verify(), IsError(ExpectedIntermediateConstraintError())); |
| } else { |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| } |
| } |
| |
| // A set of tests that check how various constraints are enforced when they |
| // are applied to a directly trusted non-self-signed leaf certificate. |
| class CertVerifyProcConstraintsTrustedLeafTest |
| : public CertVerifyProcInternalTest { |
| protected: |
| void SetUp() override { |
| CertVerifyProcInternalTest::SetUp(); |
| |
| chain_ = CertBuilder::CreateSimpleChain(/*chain_length=*/2); |
| } |
| |
| int VerifyWithTrust(CertificateTrust trust) { |
| ScopedTestRoot test_root(chain_[0]->GetX509Certificate().get(), trust); |
| CertVerifyResult verify_result; |
| int flags = 0; |
| return CertVerifyProcInternalTest::Verify( |
| chain_.front()->GetX509Certificate().get(), "www.example.com", flags, |
| CertificateList(), &verify_result); |
| } |
| |
| int Verify() { return VerifyWithTrust(CertificateTrust::ForTrustAnchor()); } |
| |
| int VerifyAsTrustedLeaf() { |
| return VerifyWithTrust(CertificateTrust::ForTrustedLeaf()); |
| } |
| |
| std::vector<std::unique_ptr<CertBuilder>> chain_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| CertVerifyProcConstraintsTrustedLeafTest, |
| testing::ValuesIn(kAllCertVerifiers), |
| VerifyProcTypeToName); |
| |
| TEST_P(CertVerifyProcConstraintsTrustedLeafTest, BaseCase) { |
| // Without changing anything on the test chain, it should validate |
| // successfully. If this is not true then the rest of the tests in this class |
| // are unlikely to be useful. |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID)); |
| EXPECT_THAT(VerifyAsTrustedLeaf(), IsOk()); |
| EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForTrustAnchorOrLeaf()), |
| IsOk()); |
| EXPECT_THAT( |
| VerifyWithTrust( |
| CertificateTrust::ForTrustedLeaf().WithRequireLeafSelfSigned()), |
| IsError(ERR_CERT_AUTHORITY_INVALID)); |
| EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForTrustAnchorOrLeaf() |
| .WithRequireLeafSelfSigned()), |
| IsError(ERR_CERT_AUTHORITY_INVALID)); |
| } else { |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTrustedLeafTest, RootAlsoTrusted) { |
| // Test verifying a chain where both the leaf and the root are marked as |
| // trusted. |
| // (Repeating the ScopedTestRoot before each call is due to the limitation |
| // with destroying any ScopedTestRoot removing all test roots.) |
| { |
| ScopedTestRoot test_root(chain_[1]->GetX509Certificate().get()); |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| { |
| ScopedTestRoot test_root1(chain_[1]->GetX509Certificate().get()); |
| // An explicit trust entry for the leaf with a value of Unspecified |
| // should be no different than the leaf not being in the trust store at |
| // all. |
| EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForUnspecified()), IsOk()); |
| } |
| { |
| ScopedTestRoot test_root1(chain_[1]->GetX509Certificate().get()); |
| // If the leaf is explicitly distrusted, verification should fail even if |
| // the root is trusted. |
| EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForDistrusted()), |
| IsError(ERR_CERT_AUTHORITY_INVALID)); |
| } |
| { |
| ScopedTestRoot test_root(chain_[1]->GetX509Certificate().get()); |
| EXPECT_THAT(VerifyAsTrustedLeaf(), IsOk()); |
| } |
| { |
| ScopedTestRoot test_root(chain_[1]->GetX509Certificate().get()); |
| EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForTrustAnchorOrLeaf()), |
| IsOk()); |
| } |
| { |
| ScopedTestRoot test_root(chain_[1]->GetX509Certificate().get()); |
| EXPECT_THAT( |
| VerifyWithTrust( |
| CertificateTrust::ForTrustedLeaf().WithRequireLeafSelfSigned()), |
| IsOk()); |
| } |
| { |
| ScopedTestRoot test_root(chain_[1]->GetX509Certificate().get()); |
| EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForTrustAnchorOrLeaf() |
| .WithRequireLeafSelfSigned()), |
| IsOk()); |
| } |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTrustedLeafTest, BasicConstraintsIsCa) { |
| for (bool has_key_usage_cert_sign : {false, true}) { |
| chain_[0]->SetBasicConstraints(/*is_ca=*/true, /*path_len=*/-1); |
| |
| if (has_key_usage_cert_sign) { |
| chain_[0]->SetKeyUsages( |
| {KEY_USAGE_BIT_KEY_CERT_SIGN, KEY_USAGE_BIT_DIGITAL_SIGNATURE}); |
| } else { |
| chain_[0]->SetKeyUsages({KEY_USAGE_BIT_DIGITAL_SIGNATURE}); |
| } |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID)); |
| EXPECT_THAT(VerifyAsTrustedLeaf(), IsOk()); |
| } else { |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTrustedLeafTest, BasicConstraintsPathlen) { |
| chain_[0]->SetBasicConstraints(/*is_ca=*/false, /*path_len=*/0); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID)); |
| } else { |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTrustedLeafTest, BasicConstraintsMissing) { |
| chain_[0]->EraseExtension(der::Input(kBasicConstraintsOid)); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID)); |
| EXPECT_THAT(VerifyAsTrustedLeaf(), IsOk()); |
| } else { |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTrustedLeafTest, NameConstraintsNotMatching) { |
| chain_[0]->SetNameConstraintsDnsNames(/*permitted_dns_names=*/{"example.org"}, |
| /*excluded_dns_names=*/{}); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID)); |
| } else { |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTrustedLeafTest, ValidityExpired) { |
| chain_[0]->SetValidity(base::Time::Now() - base::Days(14), |
| base::Time::Now() - base::Days(7)); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID)); |
| EXPECT_THAT(VerifyAsTrustedLeaf(), IsError(ERR_CERT_DATE_INVALID)); |
| } else { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_DATE_INVALID)); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTrustedLeafTest, PolicyConstraints) { |
| static const char kPolicy1[] = "1.2.3.4"; |
| |
| for (bool leaf_has_policy : {false, true}) { |
| SCOPED_TRACE(leaf_has_policy); |
| |
| chain_[0]->SetPolicyConstraints( |
| /*require_explicit_policy=*/0, |
| /*inhibit_policy_mapping=*/absl::nullopt); |
| if (leaf_has_policy) { |
| chain_[0]->SetCertificatePolicies({kPolicy1}); |
| } else { |
| chain_[0]->SetCertificatePolicies({}); |
| } |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID)); |
| EXPECT_THAT(VerifyAsTrustedLeaf(), IsOk()); |
| } else { |
| // Succeeds since the ios/android verifiers appear to not enforce |
| // this constraint in the "directly trusted leaf" case. |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTrustedLeafTest, InhibitAnyPolicy) { |
| static const char kAnyPolicy[] = "2.5.29.32.0"; |
| chain_[0]->SetPolicyConstraints( |
| /*require_explicit_policy=*/0, |
| /*inhibit_policy_mapping=*/absl::nullopt); |
| chain_[0]->SetInhibitAnyPolicy(0); |
| chain_[0]->SetCertificatePolicies({kAnyPolicy}); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID)); |
| EXPECT_THAT(VerifyAsTrustedLeaf(), IsOk()); |
| } else { |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTrustedLeafTest, KeyUsageNoDigitalSignature) { |
| // This test is mostly uninteresting since keyUsage on the end-entity is only |
| // checked at the TLS layer, not during cert verification. |
| chain_[0]->SetKeyUsages({KEY_USAGE_BIT_CRL_SIGN}); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID)); |
| EXPECT_THAT(VerifyAsTrustedLeaf(), IsOk()); |
| } else { |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTrustedLeafTest, KeyUsageCertSignLeaf) { |
| // Test a leaf that has keyUsage asserting keyCertSign with basicConstraints |
| // CA=false, which is an error according to 5280 (4.2.1.3 and 4.2.1.9). |
| chain_[0]->SetKeyUsages( |
| {KEY_USAGE_BIT_KEY_CERT_SIGN, KEY_USAGE_BIT_DIGITAL_SIGNATURE}); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID)); |
| EXPECT_THAT(VerifyAsTrustedLeaf(), IsOk()); |
| } else { |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTrustedLeafTest, ExtendedKeyUsageNoServerAuth) { |
| chain_[0]->SetExtendedKeyUsages({der::Input(kCodeSigning)}); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID)); |
| EXPECT_THAT(VerifyAsTrustedLeaf(), IsError(ERR_CERT_INVALID)); |
| } else { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID)); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTrustedLeafTest, UnknownSignatureAlgorithm) { |
| chain_[0]->SetSignatureAlgorithmTLV(TestOid0SignatureAlgorithmTLV()); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| // Since no chain is found, signature is not checked, fails with generic |
| // error for untrusted chain. |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID)); |
| // Valid since signature on directly trusted leaf is not checked. |
| EXPECT_THAT(VerifyAsTrustedLeaf(), IsOk()); |
| } else { |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTrustedLeafTest, WeakSignatureAlgorithm) { |
| chain_[0]->SetSignatureAlgorithm(SignatureAlgorithm::kEcdsaSha1); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| // Since no chain is found, signature is not checked, fails with generic |
| // error for untrusted chain. |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID)); |
| |
| // Valid since signature on directly trusted leaf is not checked. |
| EXPECT_THAT(VerifyAsTrustedLeaf(), IsOk()); |
| |
| // Cert is not self-signed so directly trusted leaf with |
| // require_leaf_selfsigned should fail. |
| EXPECT_THAT( |
| VerifyWithTrust( |
| CertificateTrust::ForTrustedLeaf().WithRequireLeafSelfSigned()), |
| IsError(ERR_CERT_AUTHORITY_INVALID)); |
| } else if (verify_proc_type() == CERT_VERIFY_PROC_IOS) { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID)); |
| } else { |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTrustedLeafTest, UnknownExtension) { |
| for (bool critical : {true, false}) { |
| SCOPED_TRACE(critical); |
| chain_[0]->SetExtension(TestOid0(), "hello world", critical); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_AUTHORITY_INVALID)); |
| if (critical) { |
| EXPECT_THAT(VerifyAsTrustedLeaf(), IsError(ERR_CERT_INVALID)); |
| } else { |
| EXPECT_THAT(VerifyAsTrustedLeaf(), IsOk()); |
| } |
| } else { |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| } |
| } |
| |
| // A set of tests that check how various constraints are enforced when they |
| // are applied to a directly trusted self-signed leaf certificate. |
| class CertVerifyProcConstraintsTrustedSelfSignedTest |
| : public CertVerifyProcInternalTest { |
| protected: |
| void SetUp() override { |
| CertVerifyProcInternalTest::SetUp(); |
| |
| cert_ = std::move(CertBuilder::CreateSimpleChain(/*chain_length=*/1)[0]); |
| } |
| |
| int VerifyWithTrust(CertificateTrust trust) { |
| ScopedTestRoot test_root(cert_->GetX509Certificate().get(), trust); |
| CertVerifyResult verify_result; |
| int flags = 0; |
| return CertVerifyProcInternalTest::Verify( |
| cert_->GetX509Certificate().get(), "www.example.com", flags, |
| CertificateList(), &verify_result); |
| } |
| |
| int Verify() { return VerifyWithTrust(CertificateTrust::ForTrustAnchor()); } |
| |
| int VerifyAsTrustedSelfSignedLeaf() { |
| return VerifyWithTrust( |
| CertificateTrust::ForTrustedLeaf().WithRequireLeafSelfSigned()); |
| } |
| |
| std::unique_ptr<CertBuilder> cert_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| CertVerifyProcConstraintsTrustedSelfSignedTest, |
| testing::ValuesIn(kAllCertVerifiers), |
| VerifyProcTypeToName); |
| |
| TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest, BaseCase) { |
| // Without changing anything on the test cert, it should validate |
| // successfully. If this is not true then the rest of the tests in this class |
| // are unlikely to be useful. |
| if (VerifyProcTypeIsBuiltin()) { |
| // Should succeed when verified as a trusted leaf. |
| EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), IsOk()); |
| EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForTrustedLeaf()), IsOk()); |
| EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForTrustAnchorOrLeaf()), |
| IsOk()); |
| |
| // Should also be allowed by verifying as anchor for itself. |
| EXPECT_THAT(Verify(), IsOk()); |
| |
| // Should fail if verified as anchor of itself with constraints enabled, |
| // enforcing the basicConstraints on the anchor will fail since the cert |
| // has CA=false. |
| EXPECT_THAT( |
| VerifyWithTrust( |
| CertificateTrust::ForTrustAnchor().WithEnforceAnchorConstraints()), |
| IsError(ERR_CERT_INVALID)); |
| |
| // Should be allowed since it will be evaluated as a trusted leaf, so |
| // anchor constraints being enabled doesn't matter. |
| EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForTrustAnchorOrLeaf() |
| .WithEnforceAnchorConstraints()), |
| IsOk()); |
| } else { |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest, BasicConstraintsIsCa) { |
| for (bool has_key_usage_cert_sign : {false, true}) { |
| cert_->SetBasicConstraints(/*is_ca=*/true, /*path_len=*/-1); |
| |
| if (has_key_usage_cert_sign) { |
| cert_->SetKeyUsages( |
| {KEY_USAGE_BIT_KEY_CERT_SIGN, KEY_USAGE_BIT_DIGITAL_SIGNATURE}); |
| } else { |
| cert_->SetKeyUsages({KEY_USAGE_BIT_DIGITAL_SIGNATURE}); |
| } |
| EXPECT_THAT(Verify(), IsOk()); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), IsOk()); |
| } |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest, |
| BasicConstraintsNotCaPathlen) { |
| cert_->SetBasicConstraints(/*is_ca=*/false, /*path_len=*/0); |
| |
| EXPECT_THAT(Verify(), IsOk()); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest, |
| BasicConstraintsIsCaPathlen) { |
| cert_->SetBasicConstraints(/*is_ca=*/true, /*path_len=*/0); |
| |
| EXPECT_THAT(Verify(), IsOk()); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest, |
| BasicConstraintsMissing) { |
| cert_->EraseExtension(der::Input(kBasicConstraintsOid)); |
| |
| EXPECT_THAT(Verify(), IsOk()); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest, |
| NameConstraintsNotMatching) { |
| cert_->SetNameConstraintsDnsNames(/*permitted_dns_names=*/{"example.org"}, |
| /*excluded_dns_names=*/{}); |
| |
| EXPECT_THAT(Verify(), IsOk()); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest, ValidityExpired) { |
| cert_->SetValidity(base::Time::Now() - base::Days(14), |
| base::Time::Now() - base::Days(7)); |
| |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_DATE_INVALID)); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), |
| IsError(ERR_CERT_DATE_INVALID)); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest, PolicyConstraints) { |
| static const char kPolicy1[] = "1.2.3.4"; |
| |
| for (bool leaf_has_policy : {false, true}) { |
| SCOPED_TRACE(leaf_has_policy); |
| |
| cert_->SetPolicyConstraints( |
| /*require_explicit_policy=*/0, |
| /*inhibit_policy_mapping=*/absl::nullopt); |
| if (leaf_has_policy) { |
| cert_->SetCertificatePolicies({kPolicy1}); |
| |
| EXPECT_THAT(Verify(), IsOk()); |
| } else { |
| cert_->SetCertificatePolicies({}); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID)); |
| EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), IsOk()); |
| } else { |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| } |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest, InhibitAnyPolicy) { |
| static const char kAnyPolicy[] = "2.5.29.32.0"; |
| cert_->SetPolicyConstraints( |
| /*require_explicit_policy=*/0, |
| /*inhibit_policy_mapping=*/absl::nullopt); |
| cert_->SetInhibitAnyPolicy(0); |
| cert_->SetCertificatePolicies({kAnyPolicy}); |
| |
| EXPECT_THAT(Verify(), IsOk()); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest, |
| KeyUsageNoDigitalSignature) { |
| // This test is mostly uninteresting since keyUsage on the end-entity is only |
| // checked at the TLS layer, not during cert verification. |
| cert_->SetKeyUsages({KEY_USAGE_BIT_CRL_SIGN}); |
| |
| EXPECT_THAT(Verify(), IsOk()); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest, KeyUsageCertSignLeaf) { |
| // Test a leaf that has keyUsage asserting keyCertSign with basicConstraints |
| // CA=false, which is an error according to 5280 (4.2.1.3 and 4.2.1.9). |
| cert_->SetKeyUsages( |
| {KEY_USAGE_BIT_KEY_CERT_SIGN, KEY_USAGE_BIT_DIGITAL_SIGNATURE}); |
| |
| EXPECT_THAT(Verify(), IsOk()); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT( |
| VerifyWithTrust( |
| CertificateTrust::ForTrustAnchor().WithEnforceAnchorConstraints()), |
| IsError(ERR_CERT_INVALID)); |
| EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), IsOk()); |
| EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForTrustAnchorOrLeaf() |
| .WithEnforceAnchorConstraints() |
| .WithRequireLeafSelfSigned()), |
| IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest, |
| ExtendedKeyUsageNoServerAuth) { |
| cert_->SetExtendedKeyUsages({der::Input(kCodeSigning)}); |
| |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID)); |
| if (VerifyProcTypeIsBuiltin()) { |
| EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), IsError(ERR_CERT_INVALID)); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest, |
| UnknownSignatureAlgorithm) { |
| cert_->SetSignatureAlgorithmTLV(TestOid0SignatureAlgorithmTLV()); |
| if (VerifyProcTypeIsBuiltin()) { |
| // Attempts to verify as anchor of itself, which fails when verifying the |
| // signature. |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID)); |
| |
| // Signature not checked when verified as a directly trusted leaf without |
| // require_leaf_selfsigned. |
| EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForTrustedLeaf()), IsOk()); |
| |
| // PathBuilder override ignores require_leaf_selfsigned due to the |
| // self-signed check returning false (due to the invalid signature |
| // algorithm), thus this fails with AUTHORITY_INVALID due to failing to |
| // find a chain to another root. |
| EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), |
| IsError(ERR_CERT_AUTHORITY_INVALID)); |
| |
| // PathBuilder override ignores require_leaf_selfsigned due to the invalid |
| // signature algorithm, thus this tries to verify as anchor of itself, |
| // which fails when verifying the signature. |
| EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForTrustAnchorOrLeaf() |
| .WithRequireLeafSelfSigned()), |
| IsError(ERR_CERT_INVALID)); |
| } else { |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest, WeakSignatureAlgorithm) { |
| cert_->SetSignatureAlgorithm(SignatureAlgorithm::kEcdsaSha1); |
| if (VerifyProcTypeIsBuiltin()) { |
| // Attempts to verify as anchor of itself, which fails due to the weak |
| // signature algorithm. |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_WEAK_SIGNATURE_ALGORITHM)); |
| |
| // Signature not checked when verified as a directly trusted leaf without |
| // require_leaf_selfsigned. |
| EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForTrustedLeaf()), IsOk()); |
| |
| // require_leaf_selfsigned allows any supported signature algorithm when |
| // doing the self-signed check, so this is okay. |
| EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), IsOk()); |
| EXPECT_THAT(VerifyWithTrust(CertificateTrust::ForTrustAnchorOrLeaf() |
| .WithRequireLeafSelfSigned()), |
| IsOk()); |
| } else { |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| } |
| |
| TEST_P(CertVerifyProcConstraintsTrustedSelfSignedTest, UnknownExtension) { |
| for (bool critical : {true, false}) { |
| SCOPED_TRACE(critical); |
| cert_->SetExtension(TestOid0(), "hello world", critical); |
| |
| if (VerifyProcTypeIsBuiltin()) { |
| if (critical) { |
| EXPECT_THAT(Verify(), IsError(ERR_CERT_INVALID)); |
| EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), IsError(ERR_CERT_INVALID)); |
| } else { |
| EXPECT_THAT(Verify(), IsOk()); |
| EXPECT_THAT(VerifyAsTrustedSelfSignedLeaf(), IsOk()); |
| } |
| } else { |
| EXPECT_THAT(Verify(), IsOk()); |
| } |
| } |
| } |
| |
| TEST(CertVerifyProcTest, RejectsPublicSHA1) { |
| scoped_refptr<X509Certificate> cert( |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem")); |
| ASSERT_TRUE(cert); |
| |
| CertVerifyResult result; |
| result.has_sha1 = true; |
| result.is_issued_by_known_root = true; |
| auto verify_proc = base::MakeRefCounted<MockCertVerifyProc>(result); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = verify_proc->Verify( |
| cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(), |
| /*sct_list=*/std::string(), flags, CertificateList(), &verify_result, |
| NetLogWithSource()); |
| EXPECT_THAT(error, IsError(ERR_CERT_WEAK_SIGNATURE_ALGORITHM)); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_WEAK_SIGNATURE_ALGORITHM); |
| |
| // VERIFY_ENABLE_SHA1_LOCAL_ANCHORS should not impact this. |
| flags = CertVerifyProc::VERIFY_ENABLE_SHA1_LOCAL_ANCHORS; |
| verify_result.Reset(); |
| error = verify_proc->Verify( |
| cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(), |
| /*sct_list=*/std::string(), flags, CertificateList(), &verify_result, |
| NetLogWithSource()); |
| EXPECT_THAT(error, IsError(ERR_CERT_WEAK_SIGNATURE_ALGORITHM)); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_WEAK_SIGNATURE_ALGORITHM); |
| } |
| |
| TEST(CertVerifyProcTest, RejectsPrivateSHA1UnlessFlag) { |
| scoped_refptr<X509Certificate> cert( |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem")); |
| ASSERT_TRUE(cert); |
| |
| CertVerifyResult result; |
| result.has_sha1 = true; |
| result.is_issued_by_known_root = false; |
| auto verify_proc = base::MakeRefCounted<MockCertVerifyProc>(result); |
| |
| // SHA-1 should be rejected by default for private roots... |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = verify_proc->Verify( |
| cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(), |
| /*sct_list=*/std::string(), flags, CertificateList(), &verify_result, |
| NetLogWithSource()); |
| EXPECT_THAT(error, IsError(ERR_CERT_WEAK_SIGNATURE_ALGORITHM)); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_SHA1_SIGNATURE_PRESENT); |
| |
| // ... unless VERIFY_ENABLE_SHA1_LOCAL_ANCHORS was supplied. |
| flags = CertVerifyProc::VERIFY_ENABLE_SHA1_LOCAL_ANCHORS; |
| verify_result.Reset(); |
| error = verify_proc->Verify( |
| cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(), |
| /*sct_list=*/std::string(), flags, CertificateList(), &verify_result, |
| NetLogWithSource()); |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_SHA1_SIGNATURE_PRESENT); |
| } |
| |
| enum ExpectedAlgorithms { |
| EXPECT_SHA1 = 1 << 0, |
| EXPECT_STATUS_INVALID = 1 << 1, |
| }; |
| |
| struct WeakDigestTestData { |
| const char* root_cert_filename; |
| const char* intermediate_cert_filename; |
| const char* ee_cert_filename; |
| int expected_algorithms; |
| }; |
| |
| const char* StringOrDefault(const char* str, const char* default_value) { |
| if (!str) |
| return default_value; |
| return str; |
| } |
| |
| // GTest 'magic' pretty-printer, so that if/when a test fails, it knows how |
| // to output the parameter that was passed. Without this, it will simply |
| // attempt to print out the first twenty bytes of the object, which depending |
| // on platform and alignment, may result in an invalid read. |
| void PrintTo(const WeakDigestTestData& data, std::ostream* os) { |
| *os << "root: " << StringOrDefault(data.root_cert_filename, "none") |
| << "; intermediate: " |
| << StringOrDefault(data.intermediate_cert_filename, "none") |
| << "; end-entity: " << data.ee_cert_filename; |
| } |
| |
| class CertVerifyProcWeakDigestTest |
| : public testing::TestWithParam<WeakDigestTestData> { |
| public: |
| CertVerifyProcWeakDigestTest() = default; |
| ~CertVerifyProcWeakDigestTest() override = default; |
| }; |
| |
| // Tests that the CertVerifyProc::Verify() properly surfaces the (weak) hash |
| // algorithms used in the chain. |
| TEST_P(CertVerifyProcWeakDigestTest, VerifyDetectsAlgorithm) { |
| WeakDigestTestData data = GetParam(); |
| base::FilePath certs_dir = GetTestCertsDirectory(); |
| |
| // Build |intermediates| as the full chain (including trust anchor). |
| std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates; |
| |
| if (data.intermediate_cert_filename) { |
| scoped_refptr<X509Certificate> intermediate_cert = |
| ImportCertFromFile(certs_dir, data.intermediate_cert_filename); |
| ASSERT_TRUE(intermediate_cert); |
| intermediates.push_back(bssl::UpRef(intermediate_cert->cert_buffer())); |
| } |
| |
| if (data.root_cert_filename) { |
| scoped_refptr<X509Certificate> root_cert = |
| ImportCertFromFile(certs_dir, data.root_cert_filename); |
| ASSERT_TRUE(root_cert); |
| intermediates.push_back(bssl::UpRef(root_cert->cert_buffer())); |
| } |
| |
| scoped_refptr<X509Certificate> ee_cert = |
| ImportCertFromFile(certs_dir, data.ee_cert_filename); |
| ASSERT_TRUE(ee_cert); |
| |
| scoped_refptr<X509Certificate> ee_chain = X509Certificate::CreateFromBuffer( |
| bssl::UpRef(ee_cert->cert_buffer()), std::move(intermediates)); |
| ASSERT_TRUE(ee_chain); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| |
| // Use a mock CertVerifyProc that returns success with a verified_cert of |
| // |ee_chain|. |
| // |
| // This is sufficient for the purposes of this test, as the checking for weak |
| // hash algorithms is done by CertVerifyProc::Verify(). |
| auto proc = base::MakeRefCounted<MockCertVerifyProc>(CertVerifyResult()); |
| int error = proc->Verify(ee_chain.get(), "127.0.0.1", |
| /*ocsp_response=*/std::string(), |
| /*sct_list=*/std::string(), flags, CertificateList(), |
| &verify_result, NetLogWithSource()); |
| EXPECT_EQ(!!(data.expected_algorithms & EXPECT_SHA1), verify_result.has_sha1); |
| EXPECT_EQ(!!(data.expected_algorithms & EXPECT_STATUS_INVALID), |
| !!(verify_result.cert_status & CERT_STATUS_INVALID)); |
| EXPECT_EQ(!!(data.expected_algorithms & EXPECT_STATUS_INVALID), |
| error == ERR_CERT_INVALID); |
| } |
| |
| // The signature algorithm of the root CA should not matter. |
| const WeakDigestTestData kVerifyRootCATestData[] = { |
| {"weak_digest_md5_root.pem", "weak_digest_sha1_intermediate.pem", |
| "weak_digest_sha1_ee.pem", EXPECT_SHA1}, |
| {"weak_digest_md4_root.pem", "weak_digest_sha1_intermediate.pem", |
| "weak_digest_sha1_ee.pem", EXPECT_SHA1}, |
| {"weak_digest_md2_root.pem", "weak_digest_sha1_intermediate.pem", |
| "weak_digest_sha1_ee.pem", EXPECT_SHA1}, |
| }; |
| INSTANTIATE_TEST_SUITE_P(VerifyRoot, |
| CertVerifyProcWeakDigestTest, |
| testing::ValuesIn(kVerifyRootCATestData)); |
| |
| // The signature algorithm of intermediates should be properly detected. |
| const WeakDigestTestData kVerifyIntermediateCATestData[] = { |
| {"weak_digest_sha1_root.pem", "weak_digest_md5_intermediate.pem", |
| "weak_digest_sha1_ee.pem", EXPECT_STATUS_INVALID | EXPECT_SHA1}, |
| {"weak_digest_sha1_root.pem", "weak_digest_md4_intermediate.pem", |
| "weak_digest_sha1_ee.pem", EXPECT_STATUS_INVALID | EXPECT_SHA1}, |
| {"weak_digest_sha1_root.pem", "weak_digest_md2_intermediate.pem", |
| "weak_digest_sha1_ee.pem", EXPECT_STATUS_INVALID | EXPECT_SHA1}, |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(VerifyIntermediate, |
| CertVerifyProcWeakDigestTest, |
| testing::ValuesIn(kVerifyIntermediateCATestData)); |
| |
| // The signature algorithm of end-entity should be properly detected. |
| const WeakDigestTestData kVerifyEndEntityTestData[] = { |
| {"weak_digest_sha1_root.pem", "weak_digest_sha1_intermediate.pem", |
| "weak_digest_md5_ee.pem", EXPECT_STATUS_INVALID}, |
| {"weak_digest_sha1_root.pem", "weak_digest_sha1_intermediate.pem", |
| "weak_digest_md4_ee.pem", EXPECT_STATUS_INVALID}, |
| {"weak_digest_sha1_root.pem", "weak_digest_sha1_intermediate.pem", |
| "weak_digest_md2_ee.pem", EXPECT_STATUS_INVALID}, |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(VerifyEndEntity, |
| CertVerifyProcWeakDigestTest, |
| testing::ValuesIn(kVerifyEndEntityTestData)); |
| |
| // Incomplete chains do not report the status of the intermediate. |
| // Note: really each of these tests should also expect the digest algorithm of |
| // the intermediate (included as a comment). However CertVerifyProc::Verify() is |
| // unable to distinguish that this is an intermediate and not a trust anchor, so |
| // this intermediate is treated like a trust anchor. |
| const WeakDigestTestData kVerifyIncompleteIntermediateTestData[] = { |
| {nullptr, "weak_digest_md5_intermediate.pem", "weak_digest_sha1_ee.pem", |
| EXPECT_SHA1}, |
| {nullptr, "weak_digest_md4_intermediate.pem", "weak_digest_sha1_ee.pem", |
| EXPECT_SHA1}, |
| {nullptr, "weak_digest_md2_intermediate.pem", "weak_digest_sha1_ee.pem", |
| EXPECT_SHA1}, |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| MAYBE_VerifyIncompleteIntermediate, |
| CertVerifyProcWeakDigestTest, |
| testing::ValuesIn(kVerifyIncompleteIntermediateTestData)); |
| |
| // Incomplete chains should report the status of the end-entity. |
| // since the intermediate is treated as a trust anchor these should |
| // be still simply be invalid. |
| const WeakDigestTestData kVerifyIncompleteEETestData[] = { |
| {nullptr, "weak_digest_sha1_intermediate.pem", "weak_digest_md5_ee.pem", |
| EXPECT_STATUS_INVALID}, |
| {nullptr, "weak_digest_sha1_intermediate.pem", "weak_digest_md4_ee.pem", |
| EXPECT_STATUS_INVALID}, |
| {nullptr, "weak_digest_sha1_intermediate.pem", "weak_digest_md2_ee.pem", |
| EXPECT_STATUS_INVALID}, |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(VerifyIncompleteEndEntity, |
| CertVerifyProcWeakDigestTest, |
| testing::ValuesIn(kVerifyIncompleteEETestData)); |
| |
| // Md2, Md4, and Md5 are all considered invalid. |
| const WeakDigestTestData kVerifyMixedTestData[] = { |
| {"weak_digest_sha1_root.pem", "weak_digest_md5_intermediate.pem", |
| "weak_digest_md2_ee.pem", EXPECT_STATUS_INVALID}, |
| {"weak_digest_sha1_root.pem", "weak_digest_md2_intermediate.pem", |
| "weak_digest_md5_ee.pem", EXPECT_STATUS_INVALID}, |
| {"weak_digest_sha1_root.pem", "weak_digest_md4_intermediate.pem", |
| "weak_digest_md2_ee.pem", EXPECT_STATUS_INVALID}, |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(VerifyMixed, |
| CertVerifyProcWeakDigestTest, |
| testing::ValuesIn(kVerifyMixedTestData)); |
| |
| // The EE is a trusted certificate. Even though it uses weak hashes, these |
| // should not be reported. |
| const WeakDigestTestData kVerifyTrustedEETestData[] = { |
| {nullptr, nullptr, "weak_digest_md5_ee.pem", 0}, |
| {nullptr, nullptr, "weak_digest_md4_ee.pem", 0}, |
| {nullptr, nullptr, "weak_digest_md2_ee.pem", 0}, |
| {nullptr, nullptr, "weak_digest_sha1_ee.pem", 0}, |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(VerifyTrustedEE, |
| CertVerifyProcWeakDigestTest, |
| testing::ValuesIn(kVerifyTrustedEETestData)); |
| |
| // Test fixture for verifying certificate names. |
| class CertVerifyProcNameTest : public ::testing::Test { |
| protected: |
| void VerifyCertName(const char* hostname, bool valid) { |
| scoped_refptr<X509Certificate> cert(ImportCertFromFile( |
| GetTestCertsDirectory(), "subjectAltName_sanity_check.pem")); |
| ASSERT_TRUE(cert); |
| CertVerifyResult result; |
| result.is_issued_by_known_root = false; |
| auto verify_proc = base::MakeRefCounted<MockCertVerifyProc>(result); |
| |
| CertVerifyResult verify_result; |
| int error = verify_proc->Verify( |
| cert.get(), hostname, /*ocsp_response=*/std::string(), |
| /*sct_list=*/std::string(), 0, CertificateList(), &verify_result, |
| NetLogWithSource()); |
| if (valid) { |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_COMMON_NAME_INVALID); |
| } else { |
| EXPECT_THAT(error, IsError(ERR_CERT_COMMON_NAME_INVALID)); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_COMMON_NAME_INVALID); |
| } |
| } |
| }; |
| |
| // Don't match the common name |
| TEST_F(CertVerifyProcNameTest, DontMatchCommonName) { |
| VerifyCertName("127.0.0.1", false); |
| } |
| |
| // Matches the iPAddress SAN (IPv4) |
| TEST_F(CertVerifyProcNameTest, MatchesIpSanIpv4) { |
| VerifyCertName("127.0.0.2", true); |
| } |
| |
| // Matches the iPAddress SAN (IPv6) |
| TEST_F(CertVerifyProcNameTest, MatchesIpSanIpv6) { |
| VerifyCertName("FE80:0:0:0:0:0:0:1", true); |
| } |
| |
| // Should not match the iPAddress SAN |
| TEST_F(CertVerifyProcNameTest, DoesntMatchIpSanIpv6) { |
| VerifyCertName("[FE80:0:0:0:0:0:0:1]", false); |
| } |
| |
| // Compressed form matches the iPAddress SAN (IPv6) |
| TEST_F(CertVerifyProcNameTest, MatchesIpSanCompressedIpv6) { |
| VerifyCertName("FE80::1", true); |
| } |
| |
| // IPv6 mapped form should NOT match iPAddress SAN |
| TEST_F(CertVerifyProcNameTest, DoesntMatchIpSanIPv6Mapped) { |
| VerifyCertName("::127.0.0.2", false); |
| } |
| |
| // Matches the dNSName SAN |
| TEST_F(CertVerifyProcNameTest, MatchesDnsSan) { |
| VerifyCertName("test.example", true); |
| } |
| |
| // Matches the dNSName SAN (trailing . ignored) |
| TEST_F(CertVerifyProcNameTest, MatchesDnsSanTrailingDot) { |
| VerifyCertName("test.example.", true); |
| } |
| |
| // Should not match the dNSName SAN |
| TEST_F(CertVerifyProcNameTest, DoesntMatchDnsSan) { |
| VerifyCertName("www.test.example", false); |
| } |
| |
| // Should not match the dNSName SAN |
| TEST_F(CertVerifyProcNameTest, DoesntMatchDnsSanInvalid) { |
| VerifyCertName("test..example", false); |
| } |
| |
| // Should not match the dNSName SAN |
| TEST_F(CertVerifyProcNameTest, DoesntMatchDnsSanTwoTrailingDots) { |
| VerifyCertName("test.example..", false); |
| } |
| |
| // Should not match the dNSName SAN |
| TEST_F(CertVerifyProcNameTest, DoesntMatchDnsSanLeadingAndTrailingDot) { |
| VerifyCertName(".test.example.", false); |
| } |
| |
| // Should not match the dNSName SAN |
| TEST_F(CertVerifyProcNameTest, DoesntMatchDnsSanTrailingDot) { |
| VerifyCertName(".test.example", false); |
| } |
| |
| // Test that trust anchors are appropriately recorded via UMA. |
| TEST(CertVerifyProcTest, HasTrustAnchorVerifyUMA) { |
| base::HistogramTester histograms; |
| scoped_refptr<X509Certificate> cert( |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem")); |
| ASSERT_TRUE(cert); |
| |
| CertVerifyResult result; |
| |
| // Simulate a certificate chain issued by "C=US, O=Google Trust Services LLC, |
| // CN=GTS Root R4". This publicly-trusted root was chosen as it was included |
| // in 2017 and is not anticipated to be removed from all supported platforms |
| // for a few decades. |
| // Note: The actual cert in |cert| does not matter for this testing, so long |
| // as it's not violating any CertVerifyProc::Verify() policies. |
| SHA256HashValue leaf_hash = {{0}}; |
| SHA256HashValue intermediate_hash = {{1}}; |
| SHA256HashValue root_hash = { |
| {0x98, 0x47, 0xe5, 0x65, 0x3e, 0x5e, 0x9e, 0x84, 0x75, 0x16, 0xe5, |
| 0xcb, 0x81, 0x86, 0x06, 0xaa, 0x75, 0x44, 0xa1, 0x9b, 0xe6, 0x7f, |
| 0xd7, 0x36, 0x6d, 0x50, 0x69, 0x88, 0xe8, 0xd8, 0x43, 0x47}}; |
| result.public_key_hashes.push_back(HashValue(leaf_hash)); |
| result.public_key_hashes.push_back(HashValue(intermediate_hash)); |
| result.public_key_hashes.push_back(HashValue(root_hash)); |
| |
| const base::HistogramBase::Sample kGTSRootR4HistogramID = 486; |
| |
| auto verify_proc = base::MakeRefCounted<MockCertVerifyProc>(result); |
| |
| histograms.ExpectTotalCount(kTrustAnchorVerifyHistogram, 0); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = verify_proc->Verify( |
| cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(), |
| /*sct_list=*/std::string(), flags, CertificateList(), &verify_result, |
| NetLogWithSource()); |
| EXPECT_EQ(OK, error); |
| histograms.ExpectUniqueSample(kTrustAnchorVerifyHistogram, |
| kGTSRootR4HistogramID, 1); |
| } |
| |
| // Test that certificates with multiple trust anchors present result in |
| // only a single trust anchor being recorded, and that being the most specific |
| // trust anchor. |
| TEST(CertVerifyProcTest, LogsOnlyMostSpecificTrustAnchorUMA) { |
| base::HistogramTester histograms; |
| scoped_refptr<X509Certificate> cert( |
| ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem")); |
| ASSERT_TRUE(cert); |
| |
| CertVerifyResult result; |
| |
| // Simulate a chain of "C=US, O=Google Trust Services LLC, CN=GTS Root R4" |
| // signing "C=US, O=Google Trust Services LLC, CN=GTS Root R3" signing an |
| // intermediate and a leaf. |
| // Note: The actual cert in |cert| does not matter for this testing, so long |
| // as it's not violating any CertVerifyProc::Verify() policies. |
| SHA256HashValue leaf_hash = {{0}}; |
| SHA256HashValue intermediate_hash = {{1}}; |
| SHA256HashValue gts_root_r3_hash = { |
| {0x41, 0x79, 0xed, 0xd9, 0x81, 0xef, 0x74, 0x74, 0x77, 0xb4, 0x96, |
| 0x26, 0x40, 0x8a, 0xf4, 0x3d, 0xaa, 0x2c, 0xa7, 0xab, 0x7f, 0x9e, |
| 0x08, 0x2c, 0x10, 0x60, 0xf8, 0x40, 0x96, 0x77, 0x43, 0x48}}; |
| SHA256HashValue gts_root_r4_hash = { |
| {0x98, 0x47, 0xe5, 0x65, 0x3e, 0x5e, 0x9e, 0x84, 0x75, 0x16, 0xe5, |
| 0xcb, 0x81, 0x86, 0x06, 0xaa, 0x75, 0x44, 0xa1, 0x9b, 0xe6, 0x7f, |
| 0xd7, 0x36, 0x6d, 0x50, 0x69, 0x88, 0xe8, 0xd8, 0x43, 0x47}}; |
| result.public_key_hashes.push_back(HashValue(leaf_hash)); |
| result.public_key_hashes.push_back(HashValue(intermediate_hash)); |
| result.public_key_hashes.push_back(HashValue(gts_root_r3_hash)); |
| result.public_key_hashes.push_back(HashValue(gts_root_r4_hash)); |
| |
| const base::HistogramBase::Sample kGTSRootR3HistogramID = 485; |
| |
| auto verify_proc = base::MakeRefCounted<MockCertVerifyProc>(result); |
| |
| histograms.ExpectTotalCount(kTrustAnchorVerifyHistogram, 0); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = verify_proc->Verify( |
| cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(), |
| /*sct_list=*/std::string(), flags, CertificateList(), &verify_result, |
| NetLogWithSource()); |
| EXPECT_EQ(OK, error); |
| |
| // Only GTS Root R3 should be recorded. |
| histograms.ExpectUniqueSample(kTrustAnchorVerifyHistogram, |
| kGTSRootR3HistogramID, 1); |
| } |
| |
| // Test that trust anchors histograms record whether or not |
| // is_issued_by_known_root was derived from the OS. |
| TEST(CertVerifyProcTest, HasTrustAnchorVerifyOutOfDateUMA) { |
| base::HistogramTester histograms; |
| scoped_refptr<X509Certificate> cert(ImportCertFromFile( |
| GetTestCertsDirectory(), "39_months_based_on_last_day.pem")); |
| ASSERT_TRUE(cert); |
| |
| CertVerifyResult result; |
| |
| // Simulate a certificate chain that is recognized as trusted (from a known |
| // root), but no certificates in the chain are tracked as known trust |
| // anchors. |
| SHA256HashValue leaf_hash = {{0}}; |
| SHA256HashValue intermediate_hash = {{1}}; |
| SHA256HashValue root_hash = {{2}}; |
| result.public_key_hashes.push_back(HashValue(leaf_hash)); |
| result.public_key_hashes.push_back(HashValue(intermediate_hash)); |
| result.public_key_hashes.push_back(HashValue(root_hash)); |
| result.is_issued_by_known_root = true; |
| |
| auto verify_proc = base::MakeRefCounted<MockCertVerifyProc>(result); |
| |
| histograms.ExpectTotalCount(kTrustAnchorVerifyHistogram, 0); |
| histograms.ExpectTotalCount(kTrustAnchorVerifyOutOfDateHistogram, 0); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = verify_proc->Verify( |
| cert.get(), "127.0.0.1", /*ocsp_response=*/std::string(), |
| /*sct_list=*/std::string(), flags, CertificateList(), &verify_result, |
| NetLogWithSource()); |
| EXPECT_EQ(OK, error); |
| const base::HistogramBase::Sample kUnknownRootHistogramID = 0; |
| histograms.ExpectUniqueSample(kTrustAnchorVerifyHistogram, |
| kUnknownRootHistogramID, 1); |
| histograms.ExpectUniqueSample(kTrustAnchorVerifyOutOfDateHistogram, true, 1); |
| } |
| |
| // If the CertVerifyProc::VerifyInternal implementation calculated the stapled |
| // OCSP results in the CertVerifyResult, CertVerifyProc::Verify should not |
| // re-calculate them. |
| TEST(CertVerifyProcTest, DoesNotRecalculateStapledOCSPResult) { |
| scoped_refptr<X509Certificate> cert = CreateCertificateChainFromFile( |
| GetTestCertsDirectory(), "ok_cert_by_intermediate.pem", |
| X509Certificate::FORMAT_AUTO); |
| ASSERT_TRUE(cert); |
| ASSERT_EQ(1U, cert->intermediate_buffers().size()); |
| |
| CertVerifyResult result; |
| |
| result.ocsp_result.response_status = OCSPVerifyResult::PROVIDED; |
| result.ocsp_result.revocation_status = OCSPRevocationStatus::GOOD; |
| |
| auto verify_proc = base::MakeRefCounted<MockCertVerifyProc>(result); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = |
| verify_proc->Verify(cert.get(), "127.0.0.1", |
| /*ocsp_response=*/"invalid OCSP data", |
| /*sct_list=*/std::string(), flags, CertificateList(), |
| &verify_result, NetLogWithSource()); |
| EXPECT_EQ(OK, error); |
| |
| EXPECT_EQ(OCSPVerifyResult::PROVIDED, |
| verify_result.ocsp_result.response_status); |
| EXPECT_EQ(OCSPRevocationStatus::GOOD, |
| verify_result.ocsp_result.revocation_status); |
| } |
| |
| TEST(CertVerifyProcTest, CalculateStapledOCSPResultIfNotAlreadyDone) { |
| scoped_refptr<X509Certificate> cert = CreateCertificateChainFromFile( |
| GetTestCertsDirectory(), "ok_cert_by_intermediate.pem", |
| X509Certificate::FORMAT_AUTO); |
| ASSERT_TRUE(cert); |
| ASSERT_EQ(1U, cert->intermediate_buffers().size()); |
| |
| CertVerifyResult result; |
| |
| // Confirm the default-constructed values are as expected. |
| EXPECT_EQ(OCSPVerifyResult::NOT_CHECKED, result.ocsp_result.response_status); |
| EXPECT_EQ(OCSPRevocationStatus::UNKNOWN, |
| result.ocsp_result.revocation_status); |
| |
| auto verify_proc = base::MakeRefCounted<MockCertVerifyProc>(result); |
| |
| int flags = 0; |
| CertVerifyResult verify_result; |
| int error = verify_proc->Verify( |
| cert.get(), "127.0.0.1", /*ocsp_response=*/"invalid OCSP data", |
| /*sct_list=*/std::string(), flags, CertificateList(), &verify_result, |
| NetLogWithSource()); |
| EXPECT_EQ(OK, error); |
| |
| EXPECT_EQ(OCSPVerifyResult::PARSE_RESPONSE_ERROR, |
| verify_result.ocsp_result.response_status); |
| EXPECT_EQ(OCSPRevocationStatus::UNKNOWN, |
| verify_result.ocsp_result.revocation_status); |
| } |
| |
| } // namespace net |