blob: 001728d2e3acfb5cff5a6901a9987b46fa37bc31 [file] [log] [blame]
// Copyright 2021 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/internal/trust_store_win.h"
#include <memory>
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/win/wincrypt_shim.h"
#include "crypto/scoped_capi_types.h"
#include "net/base/features.h"
#include "net/cert/cert_net_fetcher.h"
#include "net/cert/internal/test_helpers.h"
#include "net/cert/internal/trust_store_features.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "net/cert/x509_util_win.h"
#include "net/test/cert_test_util.h"
#include "net/test/test_data_directory.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 "third_party/boringssl/src/pki/cert_errors.h"
#include "third_party/boringssl/src/pki/parsed_certificate.h"
#include "third_party/boringssl/src/pki/trust_store.h"
namespace net {
namespace {
::testing::AssertionResult ParseCertFromFile(
base::StringPiece file_name,
std::shared_ptr<const bssl::ParsedCertificate>* out_cert) {
const scoped_refptr<X509Certificate> cert =
ImportCertFromFile(net::GetTestCertsDirectory(), file_name);
if (!cert) {
return ::testing::AssertionFailure() << "ImportCertFromFile failed";
}
bssl::CertErrors errors;
std::shared_ptr<const bssl::ParsedCertificate> parsed =
bssl::ParsedCertificate::Create(
bssl::UpRef(cert->cert_buffer()),
x509_util::DefaultParseCertificateOptions(), &errors);
if (!parsed) {
return ::testing::AssertionFailure()
<< "bssl::ParseCertificate::Create failed:\n"
<< errors.ToDebugString();
}
*out_cert = parsed;
return ::testing::AssertionSuccess();
}
class TrustStoreWinTest
: public testing::TestWithParam<std::tuple<bool, bool>> {
public:
TrustStoreWinTest()
: scoped_enforce_local_anchor_constraints_(
ExpectedEnforceLocalAnchorConstraintsEnabled()) {
if (ExpectedTrustedLeafSupportEnabled()) {
feature_list_.InitAndEnableFeature(
features::kTrustStoreTrustedLeafSupport);
} else {
feature_list_.InitAndDisableFeature(
features::kTrustStoreTrustedLeafSupport);
}
}
void SetUp() override {
ASSERT_TRUE(ParseCertFromFile("multi-root-A-by-B.pem", &a_by_b_));
ASSERT_TRUE(ParseCertFromFile("multi-root-B-by-C.pem", &b_by_c_));
ASSERT_TRUE(ParseCertFromFile("multi-root-B-by-F.pem", &b_by_f_));
ASSERT_TRUE(ParseCertFromFile("multi-root-C-by-D.pem", &c_by_d_));
ASSERT_TRUE(ParseCertFromFile("multi-root-C-by-E.pem", &c_by_e_));
ASSERT_TRUE(ParseCertFromFile("multi-root-D-by-D.pem", &d_by_d_));
ASSERT_TRUE(ParseCertFromFile("multi-root-E-by-E.pem", &e_by_e_));
ASSERT_TRUE(ParseCertFromFile("multi-root-F-by-E.pem", &f_by_e_));
}
bool ExpectedTrustedLeafSupportEnabled() const {
return std::get<0>(GetParam());
}
bool ExpectedEnforceLocalAnchorConstraintsEnabled() const {
return std::get<1>(GetParam());
}
bssl::CertificateTrust ExpectedTrustForAnchor() const {
if (ExpectedTrustedLeafSupportEnabled()) {
return bssl::CertificateTrust::ForTrustAnchorOrLeaf()
.WithEnforceAnchorExpiry()
.WithEnforceAnchorConstraints(
ExpectedEnforceLocalAnchorConstraintsEnabled())
.WithRequireLeafSelfSigned();
} else {
return bssl::CertificateTrust::ForTrustAnchor()
.WithEnforceAnchorExpiry()
.WithEnforceAnchorConstraints(
ExpectedEnforceLocalAnchorConstraintsEnabled());
}
}
bssl::CertificateTrust ExpectedTrustForPeer() const {
if (ExpectedTrustedLeafSupportEnabled()) {
return bssl::CertificateTrust::ForTrustedLeaf()
.WithRequireLeafSelfSigned();
} else {
return bssl::CertificateTrust::ForUnspecified();
}
}
// Returns true if |cert| successfully added to store, false otherwise.
bool AddToStore(HCERTSTORE store,
std::shared_ptr<const bssl::ParsedCertificate> cert) {
crypto::ScopedPCCERT_CONTEXT os_cert(CertCreateCertificateContext(
X509_ASN_ENCODING, CRYPTO_BUFFER_data(cert->cert_buffer()),
CRYPTO_BUFFER_len(cert->cert_buffer())));
return CertAddCertificateContextToStore(store, os_cert.get(),
CERT_STORE_ADD_ALWAYS, nullptr);
}
// Returns true if cert at file_name successfully added to store with
// restricted usage, false otherwise.
bool AddToStoreWithEKURestriction(
HCERTSTORE store,
std::shared_ptr<const bssl::ParsedCertificate> cert,
LPCSTR usage_identifier) {
crypto::ScopedPCCERT_CONTEXT os_cert(CertCreateCertificateContext(
X509_ASN_ENCODING, CRYPTO_BUFFER_data(cert->cert_buffer()),
CRYPTO_BUFFER_len(cert->cert_buffer())));
CERT_ENHKEY_USAGE usage;
memset(&usage, 0, sizeof(usage));
if (!CertSetEnhancedKeyUsage(os_cert.get(), &usage)) {
return false;
}
if (usage_identifier) {
if (!CertAddEnhancedKeyUsageIdentifier(os_cert.get(), usage_identifier)) {
return false;
}
}
return !!CertAddCertificateContextToStore(store, os_cert.get(),
CERT_STORE_ADD_ALWAYS, nullptr);
}
std::unique_ptr<TrustStoreWin> CreateTrustStoreWin() {
return TrustStoreWin::CreateForTesting(std::move(stores_));
}
// The cert stores that will be used to create the trust store. These handles
// will be null after CreateTrustStoreWin() is called.
TrustStoreWin::CertStores stores_ =
TrustStoreWin::CertStores::CreateInMemoryStoresForTesting();
std::shared_ptr<const bssl::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_;
private:
base::test::ScopedFeatureList feature_list_;
ScopedLocalAnchorConstraintsEnforcementForTesting
scoped_enforce_local_anchor_constraints_;
};
TEST_P(TrustStoreWinTest, GetTrustInitializationError) {
// Simulate an initialization error by using null stores.
std::unique_ptr<TrustStoreWin> trust_store_win =
TrustStoreWin::CreateForTesting(
TrustStoreWin::CertStores::CreateNullStoresForTesting());
ASSERT_TRUE(trust_store_win);
bssl::CertificateTrust trust = trust_store_win->GetTrust(d_by_d_.get());
EXPECT_EQ(bssl::CertificateTrust::ForUnspecified().ToDebugString(),
trust.ToDebugString());
}
TEST_P(TrustStoreWinTest, GetTrust) {
ASSERT_TRUE(AddToStore(stores_.roots.get(), d_by_d_));
ASSERT_TRUE(AddToStore(stores_.intermediates.get(), c_by_d_));
ASSERT_TRUE(AddToStore(stores_.trusted_people.get(), a_by_b_));
std::unique_ptr<TrustStoreWin> trust_store_win = CreateTrustStoreWin();
ASSERT_TRUE(trust_store_win);
// Explicitly trusted root should be trusted.
EXPECT_EQ(ExpectedTrustForAnchor().ToDebugString(),
trust_store_win->GetTrust(d_by_d_.get()).ToDebugString());
// Explicitly trusted peer should be trusted.
// (Although it wouldn't actually verify since it's not self-signed but has
// require_leaf_selfsigned set. That doesn't matter for the purposes of these
// tests.)
EXPECT_EQ(ExpectedTrustForPeer().ToDebugString(),
trust_store_win->GetTrust(a_by_b_.get()).ToDebugString());
// Intermediate for path building should not be trusted.
EXPECT_EQ(bssl::CertificateTrust::ForUnspecified().ToDebugString(),
trust_store_win->GetTrust(c_by_d_.get()).ToDebugString());
// Unknown roots should not be trusted (e.g. just because they're
// self-signed doesn't make them a root)
EXPECT_EQ(bssl::CertificateTrust::ForUnspecified().ToDebugString(),
trust_store_win->GetTrust(e_by_e_.get()).ToDebugString());
}
// This test has a special TrustStoreWin setup with restricted EKU usages.
// Specifically, the only certs set up in the root store are set up
// as follows:
//
// - kMultiRootDByD: only has szOID_PKIX_KP_SERVER_AUTH EKU set
// - kMultiRootEByE: only has szOID_PKIX_KP_CLIENT_AUTH set
// - kMultiRootCByE: only has szOID_ANY_ENHANCED_KEY_USAGE set
// - kMultiRootCByD: no EKU usages set
TEST_P(TrustStoreWinTest, GetTrustRestrictedEKU) {
ASSERT_TRUE(AddToStoreWithEKURestriction(stores_.roots.get(), d_by_d_,
szOID_PKIX_KP_SERVER_AUTH));
ASSERT_TRUE(AddToStoreWithEKURestriction(stores_.roots.get(), e_by_e_,
szOID_PKIX_KP_CLIENT_AUTH));
ASSERT_TRUE(AddToStoreWithEKURestriction(stores_.roots.get(), c_by_e_,
szOID_ANY_ENHANCED_KEY_USAGE));
ASSERT_TRUE(
AddToStoreWithEKURestriction(stores_.roots.get(), c_by_d_, nullptr));
std::unique_ptr<TrustStoreWin> trust_store_win = CreateTrustStoreWin();
ASSERT_TRUE(trust_store_win);
// Root cert with EKU szOID_PKIX_KP_SERVER_AUTH usage set should be
// trusted.
EXPECT_EQ(ExpectedTrustForAnchor().ToDebugString(),
trust_store_win->GetTrust(d_by_d_.get()).ToDebugString());
// Root cert with EKU szOID_ANY_ENHANCED_KEY_USAGE usage set should be
// trusted.
EXPECT_EQ(ExpectedTrustForAnchor().ToDebugString(),
trust_store_win->GetTrust(c_by_e_.get()).ToDebugString());
// Root cert with EKU szOID_PKIX_KP_CLIENT_AUTH does not allow usage of
// cert for server auth, return UNSPECIFIED.
EXPECT_EQ(bssl::CertificateTrust::ForUnspecified().ToDebugString(),
trust_store_win->GetTrust(e_by_e_.get()).ToDebugString());
// Root cert with no EKU usages, return UNSPECIFIED.
EXPECT_EQ(bssl::CertificateTrust::ForUnspecified().ToDebugString(),
trust_store_win->GetTrust(c_by_d_.get()).ToDebugString());
// Unknown cert has unspecified trust.
EXPECT_EQ(bssl::CertificateTrust::ForUnspecified().ToDebugString(),
trust_store_win->GetTrust(f_by_e_.get()).ToDebugString());
}
// Same as GetTrustRestrictedEKU but for the Trusted People store.
TEST_P(TrustStoreWinTest, GetTrustTrustedPeopleRestrictedEKU) {
ASSERT_TRUE(AddToStoreWithEKURestriction(stores_.trusted_people.get(),
d_by_d_, szOID_PKIX_KP_SERVER_AUTH));
ASSERT_TRUE(AddToStoreWithEKURestriction(stores_.trusted_people.get(),
e_by_e_, szOID_PKIX_KP_CLIENT_AUTH));
ASSERT_TRUE(AddToStoreWithEKURestriction(
stores_.trusted_people.get(), c_by_e_, szOID_ANY_ENHANCED_KEY_USAGE));
ASSERT_TRUE(AddToStoreWithEKURestriction(stores_.trusted_people.get(),
c_by_d_, nullptr));
std::unique_ptr<TrustStoreWin> trust_store_win = CreateTrustStoreWin();
ASSERT_TRUE(trust_store_win);
// TrustedPeople cert with EKU szOID_PKIX_KP_SERVER_AUTH usage set should be
// trusted.
EXPECT_EQ(ExpectedTrustForPeer().ToDebugString(),
trust_store_win->GetTrust(d_by_d_.get()).ToDebugString());
// TrustedPeople cert with EKU szOID_ANY_ENHANCED_KEY_USAGE usage set should
// be trusted.
EXPECT_EQ(ExpectedTrustForPeer().ToDebugString(),
trust_store_win->GetTrust(c_by_e_.get()).ToDebugString());
// TrustedPeople cert with EKU szOID_PKIX_KP_CLIENT_AUTH does not allow usage
// of cert for server auth, return UNSPECIFIED.
EXPECT_EQ(bssl::CertificateTrust::ForUnspecified().ToDebugString(),
trust_store_win->GetTrust(e_by_e_.get()).ToDebugString());
// TrustedPeople cert with no EKU usages, return UNSPECIFIED.
EXPECT_EQ(bssl::CertificateTrust::ForUnspecified().ToDebugString(),
trust_store_win->GetTrust(c_by_d_.get()).ToDebugString());
// Unknown cert has unspecified trust.
EXPECT_EQ(bssl::CertificateTrust::ForUnspecified().ToDebugString(),
trust_store_win->GetTrust(f_by_e_.get()).ToDebugString());
}
// If duplicate certs are added to the root store with different EKU usages,
// the cert should be trusted if any one of the usages is valid.
// Root store set up as follows:
//
// - kMultiRootDByD: only has szOID_PKIX_KP_CLIENT_AUTH EKU set
// - kMultiRootDByD (dupe): only has szOID_PKIX_KP_SERVER_AUTH set
// - kMultiRootDByD (dupe 2): no EKU usages set
TEST_P(TrustStoreWinTest, GetTrustRestrictedEKUDuplicateCerts) {
ASSERT_TRUE(AddToStoreWithEKURestriction(stores_.roots.get(), d_by_d_,
szOID_PKIX_KP_CLIENT_AUTH));
ASSERT_TRUE(AddToStoreWithEKURestriction(stores_.roots.get(), d_by_d_,
szOID_PKIX_KP_SERVER_AUTH));
ASSERT_TRUE(
AddToStoreWithEKURestriction(stores_.roots.get(), d_by_d_, nullptr));
std::unique_ptr<TrustStoreWin> trust_store_win = CreateTrustStoreWin();
ASSERT_TRUE(trust_store_win);
// One copy of the Root cert is trusted for TLS Server Auth.
EXPECT_EQ(ExpectedTrustForAnchor().ToDebugString(),
trust_store_win->GetTrust(d_by_d_.get()).ToDebugString());
}
// Test that disallowed certs will be distrusted regardless of EKU settings.
TEST_P(TrustStoreWinTest, GetTrustDisallowedCerts) {
ASSERT_TRUE(AddToStore(stores_.roots.get(), d_by_d_));
ASSERT_TRUE(AddToStore(stores_.roots.get(), e_by_e_));
ASSERT_TRUE(AddToStore(stores_.trusted_people.get(), f_by_e_));
ASSERT_TRUE(AddToStoreWithEKURestriction(stores_.disallowed.get(), d_by_d_,
szOID_PKIX_KP_CLIENT_AUTH));
ASSERT_TRUE(AddToStore(stores_.disallowed.get(), e_by_e_));
ASSERT_TRUE(AddToStore(stores_.disallowed.get(), f_by_e_));
std::unique_ptr<TrustStoreWin> trust_store_win = CreateTrustStoreWin();
ASSERT_TRUE(trust_store_win);
// E-by-E is in both root and distrusted store. Distrust takes precedence.
EXPECT_EQ(bssl::CertificateTrust::ForDistrusted().ToDebugString(),
trust_store_win->GetTrust(e_by_e_.get()).ToDebugString());
// F-by-E is in both trusted people and distrusted store. Distrust takes
// precedence.
EXPECT_EQ(bssl::CertificateTrust::ForDistrusted().ToDebugString(),
trust_store_win->GetTrust(f_by_e_.get()).ToDebugString());
// D-by-D is in root and in distrusted but without szOID_PKIX_KP_SERVER_AUTH
// set. It should still be distrusted since the EKU settings aren't checked
// on distrust.
EXPECT_EQ(bssl::CertificateTrust::ForDistrusted().ToDebugString(),
trust_store_win->GetTrust(d_by_d_.get()).ToDebugString());
}
MATCHER_P(ParsedCertEq, expected_cert, "") {
return arg && expected_cert &&
base::ranges::equal(arg->der_cert().AsSpan(),
expected_cert->der_cert().AsSpan());
}
TEST_P(TrustStoreWinTest, GetIssuersInitializationError) {
// Simulate an initialization error by using null stores.
std::unique_ptr<TrustStoreWin> trust_store_win =
TrustStoreWin::CreateForTesting(
TrustStoreWin::CertStores::CreateNullStoresForTesting());
ASSERT_TRUE(trust_store_win);
bssl::ParsedCertificateList issuers;
trust_store_win->SyncGetIssuersOf(b_by_f_.get(), &issuers);
ASSERT_EQ(0U, issuers.size());
}
TEST_P(TrustStoreWinTest, GetIssuers) {
ASSERT_TRUE(AddToStore(stores_.roots.get(), d_by_d_));
ASSERT_TRUE(AddToStore(stores_.intermediates.get(), c_by_d_));
ASSERT_TRUE(AddToStore(stores_.intermediates.get(), c_by_e_));
ASSERT_TRUE(AddToStore(stores_.intermediates.get(), f_by_e_));
ASSERT_TRUE(AddToStore(stores_.trusted_people.get(), b_by_c_));
ASSERT_TRUE(AddToStore(stores_.disallowed.get(), b_by_f_));
std::unique_ptr<TrustStoreWin> trust_store_win = CreateTrustStoreWin();
// No matching issuer (Trusted People and Disallowed are not consulted).
{
bssl::ParsedCertificateList issuers;
trust_store_win->SyncGetIssuersOf(a_by_b_.get(), &issuers);
ASSERT_EQ(0U, issuers.size());
}
// Single matching issuer found in intermediates.
{
bssl::ParsedCertificateList issuers;
trust_store_win->SyncGetIssuersOf(b_by_f_.get(), &issuers);
ASSERT_EQ(1U, issuers.size());
EXPECT_THAT(issuers, testing::UnorderedElementsAre(ParsedCertEq(f_by_e_)));
}
// Single matching issuer found in roots.
{
bssl::ParsedCertificateList issuers;
trust_store_win->SyncGetIssuersOf(d_by_d_.get(), &issuers);
ASSERT_EQ(1U, issuers.size());
EXPECT_THAT(issuers, testing::UnorderedElementsAre(ParsedCertEq(d_by_d_)));
}
// Multiple issuers found.
{
bssl::ParsedCertificateList issuers;
trust_store_win->SyncGetIssuersOf(b_by_c_.get(), &issuers);
ASSERT_EQ(2U, issuers.size());
EXPECT_THAT(issuers, testing::UnorderedElementsAre(ParsedCertEq(c_by_d_),
ParsedCertEq(c_by_e_)));
}
}
INSTANTIATE_TEST_SUITE_P(
All,
TrustStoreWinTest,
testing::Combine(testing::Bool(), testing::Bool()),
[](const testing::TestParamInfo<TrustStoreWinTest::ParamType>& info) {
return std::string(std::get<0>(info.param) ? "TrustedLeafSupported"
: "TrustAnchorOnly") +
(std::get<1>(info.param) ? "EnforceLocalAnchorConstraints"
: "NoLocalAnchorConstraints");
});
} // namespace
} // namespace net