#include "components/certificate_transparency/chrome_require_ct_delegate.h"
#include <iterator>
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/task_environment.h"
#include "base/test/test_message_loop.h"
#include "base/values.h"
#include "components/certificate_transparency/pref_names.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "net/base/hash_value.h"
#include "net/cert/require_ct_delegate.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "net/test/cert_builder.h"
#include "net/test/cert_test_util.h"
#include "net/test/test_data_directory.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace certificate_transparency {
namespace {
class ChromeRequireCTDelegateTest : public ::testing::Test {
public:
void SetUp() override {
cert_ = net::CreateCertificateChainFromFile(
net::GetTestCertsDirectory(), "expired_cert.pem",
net::X509Certificate::FORMAT_PEM_CERT_SEQUENCE);
ASSERT_TRUE(cert_);
net::SHA256HashValue spki_hash;
ASSERT_TRUE(net::x509_util::CalculateSha256SpkiHash(cert_->cert_buffer(),
&spki_hash));
hashes_.push_back(spki_hash);
}
protected:
base::test::SingleThreadTaskEnvironment task_environment_{
base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
scoped_refptr<net::X509Certificate> cert_;
std::vector<net::SHA256HashValue> hashes_;
};
TEST_F(ChromeRequireCTDelegateTest, RegistersPrefs) {
TestingPrefServiceSimple pref_service;
auto registered_prefs = std::distance(pref_service.registry()->begin(),
pref_service.registry()->end());
certificate_transparency::prefs::RegisterPrefs(pref_service.registry());
auto newly_registered_prefs = std::distance(pref_service.registry()->begin(),
pref_service.registry()->end());
EXPECT_NE(registered_prefs, newly_registered_prefs);
}
TEST_F(ChromeRequireCTDelegateTest, DelegateChecksExcludedHosts) {
using CTRequirementLevel = net::RequireCTDelegate::CTRequirementLevel;
scoped_refptr<ChromeRequireCTDelegate> delegate =
base::MakeRefCounted<ChromeRequireCTDelegate>();
EXPECT_EQ(CTRequirementLevel::REQUIRED,
delegate->IsCTRequiredForHost("google.com", cert_.get(), hashes_));
delegate->UpdateCTPolicies({"google.com"}, {});
EXPECT_EQ(CTRequirementLevel::NOT_REQUIRED,
delegate->IsCTRequiredForHost("google.com", cert_.get(), hashes_));
}
TEST_F(ChromeRequireCTDelegateTest, DelegateChecksExcludedSPKIs) {
using CTRequirementLevel = net::RequireCTDelegate::CTRequirementLevel;
scoped_refptr<ChromeRequireCTDelegate> delegate =
base::MakeRefCounted<ChromeRequireCTDelegate>();
EXPECT_EQ(CTRequirementLevel::REQUIRED,
delegate->IsCTRequiredForHost("google.com", cert_.get(), hashes_));
delegate->UpdateCTPolicies({}, {net::HashValue(hashes_.front()).ToString()});
EXPECT_EQ(CTRequirementLevel::NOT_REQUIRED,
delegate->IsCTRequiredForHost("google.com", cert_.get(), hashes_));
}
TEST_F(ChromeRequireCTDelegateTest, IgnoresInvalidEntries) {
using CTRequirementLevel = net::RequireCTDelegate::CTRequirementLevel;
scoped_refptr<ChromeRequireCTDelegate> delegate =
base::MakeRefCounted<ChromeRequireCTDelegate>();
EXPECT_EQ(CTRequirementLevel::REQUIRED,
delegate->IsCTRequiredForHost("google.com", cert_.get(), hashes_));
delegate->UpdateCTPolicies(
{"file:///etc/fstab", "file://withahost/etc/fstab", "file:///c|/Windows",
"*", "https://*", "example.com", "https://example.test:invalid_port"},
{});
EXPECT_EQ(CTRequirementLevel::REQUIRED,
delegate->IsCTRequiredForHost("google.com", cert_.get(), hashes_));
EXPECT_EQ(
CTRequirementLevel::NOT_REQUIRED,
delegate->IsCTRequiredForHost("example.test", cert_.get(), hashes_));
EXPECT_EQ(CTRequirementLevel::NOT_REQUIRED,
delegate->IsCTRequiredForHost("example.com", cert_.get(), hashes_));
}
TEST_F(ChromeRequireCTDelegateTest, SupportsOrgRestrictions) {
using CTRequirementLevel = net::RequireCTDelegate::CTRequirementLevel;
scoped_refptr<ChromeRequireCTDelegate> delegate =
base::MakeRefCounted<ChromeRequireCTDelegate>();
base::FilePath test_directory = net::GetTestNetDataDirectory().Append(
FILE_PATH_LITERAL("ov_name_constraints"));
scoped_refptr<net::X509Certificate> tmp =
net::ImportCertFromFile(test_directory, "leaf-o1.pem");
ASSERT_TRUE(tmp);
net::SHA256HashValue leaf_spki;
ASSERT_TRUE(
net::x509_util::CalculateSha256SpkiHash(tmp->cert_buffer(), &leaf_spki));
tmp = net::ImportCertFromFile(test_directory, "int-o3.pem");
ASSERT_TRUE(tmp);
net::SHA256HashValue intermediate_spki;
ASSERT_TRUE(net::x509_util::CalculateSha256SpkiHash(tmp->cert_buffer(),
&intermediate_spki));
struct {
const char* const leaf_file;
const char* const intermediate_file;
const net::SHA256HashValue spki;
CTRequirementLevel expected;
} kTestCases[] = {
{"leaf-o1.pem", nullptr, leaf_spki, CTRequirementLevel::NOT_REQUIRED},
{"leaf-no-o.pem", nullptr, leaf_spki, CTRequirementLevel::NOT_REQUIRED},
{"leaf-o1.pem", "int-cn.pem", leaf_spki,
CTRequirementLevel::NOT_REQUIRED},
{"leaf-o1-o2.pem", "int-o1-o2.pem", intermediate_spki,
CTRequirementLevel::NOT_REQUIRED},
{"leaf-o1-o2.pem", "int-o1-plus-o2.pem", intermediate_spki,
CTRequirementLevel::NOT_REQUIRED},
{"leaf-o1.pem", "nc-int-permit-o1.pem", intermediate_spki,
CTRequirementLevel::NOT_REQUIRED},
{"leaf-o1.pem", "nc-int-permit-o2-o1-o3.pem", intermediate_spki,
CTRequirementLevel::NOT_REQUIRED},
{"leaf-o1.pem", "int-bmp-o1.pem", intermediate_spki,
CTRequirementLevel::NOT_REQUIRED},
{"leaf-no-o.pem", "int-o1-o2.pem", intermediate_spki,
CTRequirementLevel::REQUIRED},
{"leaf-no-o.pem", "int-cn.pem", intermediate_spki,
CTRequirementLevel::REQUIRED},
{"leaf-o1.pem", "int-o3.pem", intermediate_spki,
CTRequirementLevel::REQUIRED},
{"leaf-o1-o2.pem", "int-o2-o1.pem", intermediate_spki,
CTRequirementLevel::REQUIRED},
{"leaf-o1.pem", "nc-int-permit-cn.pem", intermediate_spki,
CTRequirementLevel::REQUIRED},
{"leaf-o1.pem", "nc-int-permit-dns.pem", intermediate_spki,
CTRequirementLevel::REQUIRED},
{"leaf-o1.pem", "nc-int-exclude-o1.pem", intermediate_spki,
CTRequirementLevel::REQUIRED},
{"leaf-o1.pem", "nc-int-permit-bmp-o1.pem", intermediate_spki,
CTRequirementLevel::REQUIRED},
};
for (const auto& test : kTestCases) {
SCOPED_TRACE(::testing::Message()
<< "leaf=" << test.leaf_file
<< ",intermediate=" << test.intermediate_file);
scoped_refptr<net::X509Certificate> leaf =
net::ImportCertFromFile(test_directory, test.leaf_file);
ASSERT_TRUE(leaf);
std::vector<net::SHA256HashValue> hashes;
net::SHA256HashValue leaf_spki_hash;
ASSERT_TRUE(net::x509_util::CalculateSha256SpkiHash(leaf->cert_buffer(),
&leaf_spki_hash));
hashes.push_back(std::move(leaf_spki_hash));
if (test.intermediate_file) {
scoped_refptr<net::X509Certificate> intermediate =
net::ImportCertFromFile(test_directory, test.intermediate_file);
ASSERT_TRUE(intermediate);
net::SHA256HashValue intermediate_hash;
ASSERT_TRUE(net::x509_util::CalculateSha256SpkiHash(
intermediate->cert_buffer(), &intermediate_hash));
hashes.push_back(std::move(intermediate_hash));
std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates;
intermediates.push_back(bssl::UpRef(intermediate->cert_buffer()));
leaf = net::X509Certificate::CreateFromBuffer(
bssl::UpRef(leaf->cert_buffer()), std::move(intermediates));
}
delegate->UpdateCTPolicies({}, {});
EXPECT_EQ(CTRequirementLevel::REQUIRED,
delegate->IsCTRequiredForHost("google.com", leaf.get(), hashes));
delegate->UpdateCTPolicies({}, {net::HashValue(test.spki).ToString()});
EXPECT_EQ(test.expected,
delegate->IsCTRequiredForHost("google.com", leaf.get(), hashes));
}
}
TEST_F(ChromeRequireCTDelegateTest, OrgRestrictionsMatchCorrectCert) {
using CTRequirementLevel = net::RequireCTDelegate::CTRequirementLevel;
scoped_refptr<ChromeRequireCTDelegate> delegate =
base::MakeRefCounted<ChromeRequireCTDelegate>();
auto [leaf, i1, i2] = net::CertBuilder::CreateSimpleChain3();
constexpr uint8_t leaf_subject[] = {
0x30, 0x1c, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04,
0x0a, 0x0c, 0x02, 0x4f, 0x31, 0x31, 0x0d, 0x30, 0x0b, 0x06,
0x03, 0x55, 0x04, 0x03, 0x0c, 0x04, 0x4c, 0x65, 0x61, 0x66};
constexpr uint8_t o1_subject[] = {0x30, 0x0d, 0x31, 0x0b, 0x30,
0x09, 0x06, 0x03, 0x55, 0x04,
0x0a, 0x0c, 0x02, 0x4f, 0x31};
constexpr uint8_t o2_subject[] = {0x30, 0x0d, 0x31, 0x0b, 0x30,
0x09, 0x06, 0x03, 0x55, 0x04,
0x0a, 0x0c, 0x02, 0x4f, 0x32};
leaf->SetSubjectTLV(leaf_subject);
i1->SetSubjectTLV(o1_subject);
i2->SetSubjectTLV(o2_subject);
net::SHA256HashValue leaf_spki_hash;
ASSERT_TRUE(net::x509_util::CalculateSha256SpkiHash(leaf->GetCertBuffer(),
&leaf_spki_hash));
net::SHA256HashValue i1_spki_hash;
ASSERT_TRUE(net::x509_util::CalculateSha256SpkiHash(i1->GetCertBuffer(),
&i1_spki_hash));
net::SHA256HashValue i2_spki_hash;
ASSERT_TRUE(net::x509_util::CalculateSha256SpkiHash(i2->GetCertBuffer(),
&i2_spki_hash));
std::vector<net::SHA256HashValue> hashes;
hashes.push_back(leaf_spki_hash);
hashes.push_back(i1_spki_hash);
hashes.push_back(i2_spki_hash);
delegate->UpdateCTPolicies({}, {});
EXPECT_EQ(
CTRequirementLevel::REQUIRED,
delegate->IsCTRequiredForHost(
"google.com", leaf->GetX509CertificateFullChain().get(), hashes));
delegate->UpdateCTPolicies({}, {net::HashValue(i1_spki_hash).ToString()});
EXPECT_EQ(
CTRequirementLevel::NOT_REQUIRED,
delegate->IsCTRequiredForHost(
"google.com", leaf->GetX509CertificateFullChain().get(), hashes));
delegate->UpdateCTPolicies({}, {net::HashValue(i2_spki_hash).ToString()});
EXPECT_EQ(
CTRequirementLevel::REQUIRED,
delegate->IsCTRequiredForHost(
"google.com", leaf->GetX509CertificateFullChain().get(), hashes));
}
}
}