Skip to content

Commit

Permalink
remove use of wincrypt in net/cert/pki
Browse files Browse the repository at this point in the history
This moves the path builder windows trust store specific unit
tests to cert/internal

Bug: 1322914
Change-Id: I6e2bd00691432854a9db65b50a32952e3fd540ba
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4091288
Reviewed-by: David Benjamin <davidben@chromium.org>
Commit-Queue: Bob Beck <bbe@google.com>
Cr-Commit-Position: refs/heads/main@{#1081639}
  • Loading branch information
Bob Beck authored and Chromium LUCI CQ committed Dec 9, 2022
1 parent 3112351 commit 7982e40
Show file tree
Hide file tree
Showing 4 changed files with 328 additions and 106 deletions.
1 change: 1 addition & 0 deletions net/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -4980,6 +4980,7 @@ test("net_unittests") {

if (is_win) {
sources += [ "cert/internal/trust_store_win_unittest.cc" ]
sources += [ "cert/internal/path_builder_trust_store_win_unittest.cc" ]
libs += [ "crypt32.lib" ]
}
}
Expand Down
327 changes: 327 additions & 0 deletions net/cert/internal/path_builder_trust_store_win_unittest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,327 @@
// 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/callback_forward.h"
#include "base/files/file_util.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();
}

const void* kKey = &kKey;
class TrustStoreThatStoresUserData : public TrustStore {
public:
class Data : public base::SupportsUserData::Data {
public:
explicit Data(int value) : value(value) {}

int value = 0;
};

// TrustStore implementation:
void SyncGetIssuersOf(const ParsedCertificate* cert,
ParsedCertificateList* issuers) override {}
CertificateTrust GetTrust(const ParsedCertificate* cert,
base::SupportsUserData* debug_data) const override {
debug_data->SetUserData(kKey, std::make_unique<Data>(1234));
return CertificateTrust::ForUnspecified();
}
};

TEST(PathBuilderResultUserDataTest, ModifyUserDataInConstructor) {
std::shared_ptr<const ParsedCertificate> a_by_b;
ASSERT_TRUE(ReadTestCert("multi-root-A-by-B.pem", &a_by_b));
SimplePathBuilderDelegate delegate(
1024, SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1);
der::GeneralizedTime verify_time = {2017, 3, 1, 0, 0, 0};
TrustStoreThatStoresUserData trust_store;

// |trust_store| will unconditionally store user data in the
// CertPathBuilder::Result. This ensures that the Result object has been
// initialized before the first GetTrust call occurs (otherwise the test will
// crash or fail on ASAN bots).
CertPathBuilder path_builder(
a_by_b, &trust_store, &delegate, verify_time, KeyPurpose::ANY_EKU,
InitialExplicitPolicy::kFalse, {der::Input(kAnyPolicyOid)},
InitialPolicyMappingInhibit::kFalse, InitialAnyPolicyInhibit::kFalse);
CertPathBuilder::Result result = path_builder.Run();
auto* data = static_cast<TrustStoreThatStoresUserData::Data*>(
result.GetUserData(kKey));
ASSERT_TRUE(data);
EXPECT_EQ(1234, data->value);
}

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) {
crypto::ScopedHCERTSTORE root_store(CertOpenStore(
CERT_STORE_PROV_MEMORY, X509_ASN_ENCODING, NULL, 0, nullptr));
crypto::ScopedHCERTSTORE intermediate_store(CertOpenStore(
CERT_STORE_PROV_MEMORY, X509_ASN_ENCODING, NULL, 0, nullptr));
crypto::ScopedHCERTSTORE disallowed_store(CertOpenStore(
CERT_STORE_PROV_MEMORY, X509_ASN_ENCODING, NULL, 0, nullptr));

AddToStoreWithEKURestriction(root_store.get(), d_by_d_,
szOID_PKIX_KP_SERVER_AUTH);
AddToStoreWithEKURestriction(root_store.get(), e_by_e_,
szOID_PKIX_KP_SERVER_AUTH);
AddToStoreWithEKURestriction(intermediate_store.get(), c_by_e_,
szOID_PKIX_KP_SERVER_AUTH);
AddToStoreWithEKURestriction(disallowed_store.get(), c_by_d_, nullptr);

std::unique_ptr<TrustStoreWin> trust_store = TrustStoreWin::CreateForTesting(
std::move(root_store), std::move(intermediate_store),
std::move(disallowed_store));

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) {
crypto::ScopedHCERTSTORE root_store(CertOpenStore(
CERT_STORE_PROV_MEMORY, X509_ASN_ENCODING, NULL, 0, nullptr));
crypto::ScopedHCERTSTORE intermediate_store(CertOpenStore(
CERT_STORE_PROV_MEMORY, X509_ASN_ENCODING, NULL, 0, nullptr));
crypto::ScopedHCERTSTORE disallowed_store(CertOpenStore(
CERT_STORE_PROV_MEMORY, X509_ASN_ENCODING, NULL, 0, nullptr));

AddToStoreWithEKURestriction(root_store.get(), d_by_d_,
szOID_PKIX_KP_SERVER_AUTH);
AddToStoreWithEKURestriction(disallowed_store.get(), c_by_d_, nullptr);
std::unique_ptr<TrustStoreWin> trust_store = TrustStoreWin::CreateForTesting(
std::move(root_store), std::move(intermediate_store),
std::move(disallowed_store));

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
1 change: 0 additions & 1 deletion net/cert/pki/DEPS
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ include_rules = [
"+base/numerics/clamped_math.h",
"+base/path_service.h",
"+base/supports_user_data.h",
"+base/win/wincrypt_shim.h",
]
specific_include_rules = {
"path_builder_unittest\.cc": [
Expand Down
Loading

0 comments on commit 7982e40

Please sign in to comment.