blob: acc0b868ba8193a5b579cdff7ff1397c6e64f71c [file] [log] [blame]
// Copyright 2016 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/pki/path_builder.h"
#include <algorithm>
#include "base/base_paths.h"
#include "base/files/file_util.h"
#include "base/functional/callback_forward.h"
#include "base/path_service.h"
#include "base/test/bind.h"
#include "build/build_config.h"
#include "net/cert/pem.h"
#include "net/cert/pki/cert_error_params.h"
#include "net/cert/pki/cert_issuer_source_static.h"
#include "net/cert/pki/common_cert_errors.h"
#include "net/cert/pki/parsed_certificate.h"
#include "net/cert/pki/simple_path_builder_delegate.h"
#include "net/cert/pki/test_helpers.h"
#include "net/cert/pki/trust_store_collection.h"
#include "net/cert/pki/trust_store_in_memory.h"
#include "net/cert/pki/verify_certificate_chain.h"
#include "net/der/input.h"
#include "net/net_buildflags.h"
#include "net/test/test_certificate_data.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/boringssl/src/include/openssl/pool.h"
#include "base/win/wincrypt_shim.h"
#include "crypto/scoped_capi_types.h"
#include "net/cert/internal/trust_store_win.h"
namespace net {
namespace {
using ::testing::_;
using ::testing::ElementsAre;
using ::testing::Invoke;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::SetArgPointee;
using ::testing::StrictMock;
class DeadlineTestingPathBuilderDelegate : public SimplePathBuilderDelegate {
public:
DeadlineTestingPathBuilderDelegate(size_t min_rsa_modulus_length_bits,
DigestPolicy digest_policy)
: SimplePathBuilderDelegate(min_rsa_modulus_length_bits, digest_policy) {}
bool IsDeadlineExpired() override { return deadline_is_expired_; }
void SetDeadlineExpiredForTesting(bool deadline_is_expired) {
deadline_is_expired_ = deadline_is_expired;
}
private:
bool deadline_is_expired_ = false;
};
// AsyncCertIssuerSourceStatic always returns its certs asynchronously.
class AsyncCertIssuerSourceStatic : public CertIssuerSource {
public:
class StaticAsyncRequest : public Request {
public:
explicit StaticAsyncRequest(ParsedCertificateList&& issuers) {
issuers_.swap(issuers);
issuers_iter_ = issuers_.begin();
}
StaticAsyncRequest(const StaticAsyncRequest&) = delete;
StaticAsyncRequest& operator=(const StaticAsyncRequest&) = delete;
~StaticAsyncRequest() override = default;
void GetNext(ParsedCertificateList* out_certs) override {
if (issuers_iter_ != issuers_.end())
out_certs->push_back(std::move(*issuers_iter_++));
}
ParsedCertificateList issuers_;
ParsedCertificateList::iterator issuers_iter_;
};
~AsyncCertIssuerSourceStatic() override = default;
void SetAsyncGetCallback(base::RepeatingClosure closure) {
async_get_callback_ = std::move(closure);
}
void AddCert(std::shared_ptr<const ParsedCertificate> cert) {
static_cert_issuer_source_.AddCert(std::move(cert));
}
void SyncGetIssuersOf(const ParsedCertificate* cert,
ParsedCertificateList* issuers) override {}
void AsyncGetIssuersOf(const ParsedCertificate* cert,
std::unique_ptr<Request>* out_req) override {
num_async_gets_++;
ParsedCertificateList issuers;
static_cert_issuer_source_.SyncGetIssuersOf(cert, &issuers);
auto req = std::make_unique<StaticAsyncRequest>(std::move(issuers));
*out_req = std::move(req);
if (!async_get_callback_.is_null())
async_get_callback_.Run();
}
int num_async_gets() const { return num_async_gets_; }
private:
CertIssuerSourceStatic static_cert_issuer_source_;
int num_async_gets_ = 0;
base::RepeatingClosure async_get_callback_;
};
::testing::AssertionResult ReadTestPem(const std::string& file_name,
const std::string& block_name,
std::string* result) {
const PemBlockMapping mappings[] = {
{block_name.c_str(), result},
};
return ReadTestDataFromPemFile(file_name, mappings);
}
::testing::AssertionResult ReadTestCert(
const std::string& file_name,
std::shared_ptr<const ParsedCertificate>* result) {
std::string der;
::testing::AssertionResult r = ReadTestPem(
"net/data/ssl/certificates/" + file_name, "CERTIFICATE", &der);
if (!r)
return r;
CertErrors errors;
*result = ParsedCertificate::Create(
bssl::UniquePtr<CRYPTO_BUFFER>(CRYPTO_BUFFER_new(
reinterpret_cast<const uint8_t*>(der.data()), der.size(), nullptr)),
{}, &errors);
if (!*result) {
return ::testing::AssertionFailure()
<< "ParseCertificate::Create() failed:\n"
<< errors.ToDebugString();
}
return ::testing::AssertionSuccess();
}
class PathBuilderMultiRootWindowsTest : public ::testing::Test {
public:
PathBuilderMultiRootWindowsTest()
: delegate_(
1024,
DeadlineTestingPathBuilderDelegate::DigestPolicy::kWeakAllowSha1) {}
void SetUp() override {
ASSERT_TRUE(ReadTestCert("multi-root-A-by-B.pem", &a_by_b_));
ASSERT_TRUE(ReadTestCert("multi-root-B-by-C.pem", &b_by_c_));
ASSERT_TRUE(ReadTestCert("multi-root-B-by-F.pem", &b_by_f_));
ASSERT_TRUE(ReadTestCert("multi-root-C-by-D.pem", &c_by_d_));
ASSERT_TRUE(ReadTestCert("multi-root-C-by-E.pem", &c_by_e_));
ASSERT_TRUE(ReadTestCert("multi-root-D-by-D.pem", &d_by_d_));
ASSERT_TRUE(ReadTestCert("multi-root-E-by-E.pem", &e_by_e_));
ASSERT_TRUE(ReadTestCert("multi-root-F-by-E.pem", &f_by_e_));
}
protected:
std::shared_ptr<const ParsedCertificate> a_by_b_, b_by_c_, b_by_f_, c_by_d_,
c_by_e_, d_by_d_, e_by_e_, f_by_e_;
DeadlineTestingPathBuilderDelegate delegate_;
der::GeneralizedTime time_ = {2017, 3, 1, 0, 0, 0};
const InitialExplicitPolicy initial_explicit_policy_ =
InitialExplicitPolicy::kFalse;
const std::set<der::Input> user_initial_policy_set_ = {
der::Input(kAnyPolicyOid)};
const InitialPolicyMappingInhibit initial_policy_mapping_inhibit_ =
InitialPolicyMappingInhibit::kFalse;
const InitialAnyPolicyInhibit initial_any_policy_inhibit_ =
InitialAnyPolicyInhibit::kFalse;
};
void AddToStoreWithEKURestriction(
HCERTSTORE store,
const std::shared_ptr<const ParsedCertificate>& cert,
LPCSTR usage_identifier) {
crypto::ScopedPCCERT_CONTEXT os_cert(CertCreateCertificateContext(
X509_ASN_ENCODING, cert->der_cert().UnsafeData(),
cert->der_cert().Length()));
CERT_ENHKEY_USAGE usage;
memset(&usage, 0, sizeof(usage));
CertSetEnhancedKeyUsage(os_cert.get(), &usage);
if (usage_identifier) {
CertAddEnhancedKeyUsageIdentifier(os_cert.get(), usage_identifier);
}
CertAddCertificateContextToStore(store, os_cert.get(), CERT_STORE_ADD_ALWAYS,
nullptr);
}
bool AreCertsEq(const std::shared_ptr<const ParsedCertificate> cert_1,
const std::shared_ptr<const ParsedCertificate> cert_2) {
return cert_1 && cert_2 && cert_1->der_cert() == cert_2->der_cert();
}
// Test to ensure that path building stops when an intermediate cert is
// encountered that is not usable for TLS because it is explicitly distrusted.
TEST_F(PathBuilderMultiRootWindowsTest, TrustStoreWinOnlyFindTrustedTLSPath) {
TrustStoreWin::CertStores stores =
TrustStoreWin::CertStores::CreateInMemoryStoresForTesting();
AddToStoreWithEKURestriction(stores.roots.get(), d_by_d_,
szOID_PKIX_KP_SERVER_AUTH);
AddToStoreWithEKURestriction(stores.roots.get(), e_by_e_,
szOID_PKIX_KP_SERVER_AUTH);
AddToStoreWithEKURestriction(stores.intermediates.get(), c_by_e_,
szOID_PKIX_KP_SERVER_AUTH);
AddToStoreWithEKURestriction(stores.disallowed.get(), c_by_d_, nullptr);
std::unique_ptr<TrustStoreWin> trust_store =
TrustStoreWin::CreateForTesting(std::move(stores));
CertPathBuilder path_builder(
b_by_c_, trust_store.get(), &delegate_, time_, KeyPurpose::ANY_EKU,
initial_explicit_policy_, user_initial_policy_set_,
initial_policy_mapping_inhibit_, initial_any_policy_inhibit_);
// Check all paths.
path_builder.SetExploreAllPaths(true);
auto result = path_builder.Run();
ASSERT_TRUE(result.HasValidPath());
ASSERT_EQ(1U, result.paths.size());
const auto& path = *result.GetBestValidPath();
ASSERT_EQ(3U, path.certs.size());
EXPECT_TRUE(AreCertsEq(b_by_c_, path.certs[0]));
EXPECT_TRUE(AreCertsEq(c_by_e_, path.certs[1]));
EXPECT_TRUE(AreCertsEq(e_by_e_, path.certs[2]));
// Should only be one valid path, the one above.
const int valid_paths = std::count_if(
result.paths.begin(), result.paths.end(),
[](const auto& candidate_path) { return candidate_path->IsValid(); });
ASSERT_EQ(1, valid_paths);
}
// Test that if an intermediate is untrusted, and it is the only
// path, then path building should fail, even if the root is enabled for
// TLS.
TEST_F(PathBuilderMultiRootWindowsTest, TrustStoreWinNoPathEKURestrictions) {
TrustStoreWin::CertStores stores =
TrustStoreWin::CertStores::CreateInMemoryStoresForTesting();
AddToStoreWithEKURestriction(stores.roots.get(), d_by_d_,
szOID_PKIX_KP_SERVER_AUTH);
AddToStoreWithEKURestriction(stores.disallowed.get(), c_by_d_, nullptr);
std::unique_ptr<TrustStoreWin> trust_store =
TrustStoreWin::CreateForTesting(std::move(stores));
CertPathBuilder path_builder(
b_by_c_, trust_store.get(), &delegate_, time_, KeyPurpose::ANY_EKU,
initial_explicit_policy_, user_initial_policy_set_,
initial_policy_mapping_inhibit_, initial_any_policy_inhibit_);
auto result = path_builder.Run();
ASSERT_FALSE(result.HasValidPath());
}
} // namespace
} // namespace net