#include "net/cert/internal/trust_store_mac.h"
#include <algorithm>
#include <set>
#include "base/apple/scoped_cftyperef.h"
#include "base/base_paths.h"
#include "base/containers/to_vector.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/process/launch.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_view_util.h"
#include "base/synchronization/lock.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "crypto/apple/security_framework_lock.h"
#include "crypto/sha2.h"
#include "net/base/features.h"
#include "net/cert/internal/test_helpers.h"
#include "net/cert/test_keychain_search_list_mac.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "net/cert/x509_util_apple.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/pki/cert_errors.h"
#include "third_party/boringssl/src/pki/parsed_certificate.h"
#include "third_party/boringssl/src/pki/pem.h"
#include "third_party/boringssl/src/pki/trust_store.h"
using ::testing::UnorderedElementsAreArray;
namespace net {
namespace {
const char kCertificateHeader[] = "CERTIFICATE";
::testing::AssertionResult ReadTestCert(
const std::string& file_name,
std::shared_ptr<const bssl::ParsedCertificate>* result) {
const std::string path = "net/data/ssl/certificates/" + file_name;
*result = ReadCertFromFile(path);
if (!*result) {
return ::testing::AssertionFailure()
<< "ReadCertFromFile(" << path << ") failed";
}
return ::testing::AssertionSuccess();
}
std::vector<std::string> ParsedCertificateListAsDER(
bssl::ParsedCertificateList list) {
std::vector<std::string> result;
for (const auto& it : list) {
result.emplace_back(base::as_string_view(it->der_cert()));
}
return result;
}
std::set<std::string> ParseFindCertificateOutputToDerCerts(std::string output) {
std::set<std::string> certs;
for (const std::string& hash_and_pem_partial : base::SplitStringUsingSubstr(
output, "-----END CERTIFICATE-----", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY)) {
const std::string hash_and_pem =
hash_and_pem_partial + "\n-----END CERTIFICATE-----\n";
bssl::PEMTokenizer pem_tokenizer(hash_and_pem, {kCertificateHeader});
if (!pem_tokenizer.GetNext()) {
ADD_FAILURE() << "!pem_tokenizer.GetNext()";
continue;
}
std::string cert_der(pem_tokenizer.data());
EXPECT_FALSE(pem_tokenizer.GetNext());
certs.insert(cert_der);
}
return certs;
}
const char* TrustImplTypeToString(TrustStoreMac::TrustImplType t) {
switch (t) {
case TrustStoreMac::TrustImplType::kDomainCacheFullCerts:
return "DomainCacheFullCerts";
case TrustStoreMac::TrustImplType::kKeychainCacheFullCerts:
return "KeychainCacheFullCerts";
case TrustStoreMac::TrustImplType::kUnknown:
return "Unknown";
}
}
}
class TrustStoreMacImplTest
: public testing::TestWithParam<TrustStoreMac::TrustImplType> {
public:
TrustStoreMac::TrustImplType GetImplParam() const { return GetParam(); }
bssl::CertificateTrust ExpectedTrustForAnchor() const {
return bssl::CertificateTrust::ForTrustAnchorOrLeaf()
.WithEnforceAnchorExpiry()
.WithEnforceAnchorConstraints()
.WithRequireAnchorBasicConstraints();
}
};
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
TEST_P(TrustStoreMacImplTest, MultiRootNotTrusted) {
std::unique_ptr<TestKeychainSearchList> test_keychain_search_list(
TestKeychainSearchList::Create());
ASSERT_TRUE(test_keychain_search_list);
base::FilePath keychain_path(
GetTestCertsDirectory().AppendASCII("multi-root.keychain"));
ASSERT_TRUE(base::PathExists(keychain_path));
base::apple::ScopedCFTypeRef<SecKeychainRef> keychain;
OSStatus status = SecKeychainOpen(keychain_path.MaybeAsASCII().c_str(),
keychain.InitializeInto());
ASSERT_EQ(errSecSuccess, status);
ASSERT_TRUE(keychain);
test_keychain_search_list->AddKeychain(keychain.get());
#pragma clang diagnostic pop
const TrustStoreMac::TrustImplType trust_impl = GetImplParam();
TrustStoreMac trust_store(kSecPolicyAppleSSL, trust_impl);
std::map<std::vector<uint8_t>, bssl::CertificateTrust> user_added_certs;
for (const auto& cert_with_trust : trust_store.GetAllUserAddedCerts()) {
user_added_certs[cert_with_trust.cert_bytes] = cert_with_trust.trust;
}
std::shared_ptr<const bssl::ParsedCertificate> a_by_b, b_by_c, b_by_f, c_by_d,
c_by_e, f_by_e, d_by_d, e_by_e;
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-F-by-E.pem", &f_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));
{
bssl::ParsedCertificateList found_issuers;
trust_store.SyncGetIssuersOf(a_by_b.get(), &found_issuers);
EXPECT_THAT(ParsedCertificateListAsDER(found_issuers),
UnorderedElementsAreArray(
ParsedCertificateListAsDER({b_by_c, b_by_f})));
}
{
bssl::ParsedCertificateList found_issuers;
trust_store.SyncGetIssuersOf(b_by_c.get(), &found_issuers);
EXPECT_THAT(ParsedCertificateListAsDER(found_issuers),
UnorderedElementsAreArray(
ParsedCertificateListAsDER({c_by_d, c_by_e})));
}
{
bssl::ParsedCertificateList found_issuers;
trust_store.SyncGetIssuersOf(b_by_f.get(), &found_issuers);
EXPECT_THAT(
ParsedCertificateListAsDER(found_issuers),
UnorderedElementsAreArray(ParsedCertificateListAsDER({f_by_e})));
}
{
bssl::ParsedCertificateList found_issuers;
trust_store.SyncGetIssuersOf(c_by_d.get(), &found_issuers);
EXPECT_THAT(
ParsedCertificateListAsDER(found_issuers),
UnorderedElementsAreArray(ParsedCertificateListAsDER({d_by_d})));
}
{
bssl::ParsedCertificateList found_issuers;
trust_store.SyncGetIssuersOf(f_by_e.get(), &found_issuers);
EXPECT_THAT(
ParsedCertificateListAsDER(found_issuers),
UnorderedElementsAreArray(ParsedCertificateListAsDER({e_by_e})));
}
for (const auto& cert :
{a_by_b, b_by_c, b_by_f, c_by_d, c_by_e, f_by_e, d_by_d, e_by_e}) {
bssl::CertificateTrust trust = trust_store.GetTrust(cert.get());
EXPECT_EQ(bssl::CertificateTrust::ForUnspecified().ToDebugString(),
trust.ToDebugString());
std::vector<uint8_t> cert_bytes = base::ToVector(cert->der_cert());
if (cert == a_by_b) {
EXPECT_FALSE(user_added_certs.contains(cert_bytes));
} else {
EXPECT_TRUE(user_added_certs.contains(cert_bytes));
EXPECT_TRUE(user_added_certs[cert_bytes].HasUnspecifiedTrust());
}
}
}
TEST_P(TrustStoreMacImplTest, SystemCerts) {
std::string find_certificate_default_search_list_output;
ASSERT_TRUE(
base::GetAppOutput({"security", "find-certificate", "-a", "-p", "-Z"},
&find_certificate_default_search_list_output));
std::string find_certificate_system_roots_output;
ASSERT_TRUE(base::GetAppOutput(
{"security", "find-certificate", "-a", "-p", "-Z",
"/System/Library/Keychains/SystemRootCertificates.keychain"},
&find_certificate_system_roots_output));
std::set<std::string> find_certificate_default_search_list_certs =
ParseFindCertificateOutputToDerCerts(
find_certificate_default_search_list_output);
std::set<std::string> find_certificate_system_roots_certs =
ParseFindCertificateOutputToDerCerts(
find_certificate_system_roots_output);
const TrustStoreMac::TrustImplType trust_impl = GetImplParam();
base::HistogramTester histogram_tester;
TrustStoreMac trust_store(kSecPolicyAppleX509Basic, trust_impl);
std::map<std::string, bssl::CertificateTrust> user_added_certs;
for (const auto& cert_with_trust : trust_store.GetAllUserAddedCerts()) {
user_added_certs[std::string(base::as_string_view(
cert_with_trust.cert_bytes))] = cert_with_trust.trust;
}
base::apple::ScopedCFTypeRef<SecPolicyRef> sec_policy(
SecPolicyCreateBasicX509());
ASSERT_TRUE(sec_policy);
std::vector<std::string> all_certs;
std::set_union(find_certificate_default_search_list_certs.begin(),
find_certificate_default_search_list_certs.end(),
find_certificate_system_roots_certs.begin(),
find_certificate_system_roots_certs.end(),
std::back_inserter(all_certs));
for (const std::string& cert_der : all_certs) {
std::string hash = crypto::SHA256HashString(cert_der);
std::string hash_text = base::HexEncode(hash);
SCOPED_TRACE(hash_text);
bssl::CertErrors errors;
bssl::ParseCertificateOptions options;
options.allow_invalid_serial_numbers = true;
std::shared_ptr<const bssl::ParsedCertificate> cert =
bssl::ParsedCertificate::Create(x509_util::CreateCryptoBuffer(cert_der),
options, &errors);
if (!cert) {
LOG(WARNING) << "bssl::ParseCertificate::Create " << hash_text
<< " failed:\n"
<< errors.ToDebugString();
continue;
}
base::apple::ScopedCFTypeRef<SecCertificateRef> cert_handle(
x509_util::CreateSecCertificateFromBytes(cert->der_cert()));
if (!cert_handle) {
ADD_FAILURE() << "CreateCertBufferFromBytes " << hash_text;
continue;
}
bssl::CertificateTrust cert_trust = trust_store.GetTrust(cert.get());
bool is_trusted = cert_trust.IsTrustAnchor() || cert_trust.IsTrustLeaf();
if (is_trusted) {
EXPECT_EQ(ExpectedTrustForAnchor().ToDebugString(),
cert_trust.ToDebugString());
EXPECT_TRUE(user_added_certs.contains(cert_der));
EXPECT_EQ(user_added_certs[cert_der].ToDebugString(),
cert_trust.ToDebugString());
}
base::apple::ScopedCFTypeRef<SecTrustRef> trust;
{
base::AutoLock lock(crypto::apple::GetSecurityFrameworkLock());
ASSERT_EQ(noErr, SecTrustCreateWithCertificates(cert_handle.get(),
sec_policy.get(),
trust.InitializeInto()));
ASSERT_EQ(noErr, SecTrustSetOptions(trust.get(),
kSecTrustOptionLeafIsCA |
kSecTrustOptionAllowExpired |
kSecTrustOptionAllowExpiredRoot));
if (find_certificate_default_search_list_certs.count(cert_der) &&
find_certificate_system_roots_certs.count(cert_der)) {
} else if (!find_certificate_default_search_list_certs.count(cert_der)) {
EXPECT_FALSE(is_trusted);
EXPECT_FALSE(user_added_certs.contains(cert_der));
} else {
bool trusted = SecTrustEvaluateWithError(trust.get(), nullptr);
bool expected_trust_anchor =
trusted && (SecTrustGetCertificateCount(trust.get()) == 1);
EXPECT_EQ(expected_trust_anchor, is_trusted);
}
}
bssl::CertificateTrust cert_trust2 = trust_store.GetTrust(cert.get());
EXPECT_EQ(cert_trust.ToDebugString(), cert_trust2.ToDebugString());
}
{
const int expected_count =
(trust_impl == TrustStoreMac::TrustImplType::kDomainCacheFullCerts) ? 1
: 0;
histogram_tester.ExpectTotalCount(
"Net.CertVerifier.MacTrustDomainCertCount.User", expected_count);
histogram_tester.ExpectTotalCount(
"Net.CertVerifier.MacTrustDomainCertCount.Admin", expected_count);
histogram_tester.ExpectTotalCount(
"Net.CertVerifier.MacTrustDomainCacheInitTime", expected_count);
histogram_tester.ExpectTotalCount(
"Net.CertVerifier.MacKeychainCerts.IntermediateCacheInitTime",
expected_count);
}
{
const int expected_count =
(trust_impl == TrustStoreMac::TrustImplType::kKeychainCacheFullCerts)
? 1
: 0;
histogram_tester.ExpectTotalCount(
"Net.CertVerifier.MacKeychainCerts.TrustCount", expected_count);
}
{
const int expected_count =
(trust_impl == TrustStoreMac::TrustImplType::kDomainCacheFullCerts ||
trust_impl == TrustStoreMac::TrustImplType::kKeychainCacheFullCerts)
? 1
: 0;
histogram_tester.ExpectTotalCount(
"Net.CertVerifier.MacKeychainCerts.IntermediateCount", expected_count);
histogram_tester.ExpectTotalCount(
"Net.CertVerifier.MacKeychainCerts.TotalCount", expected_count);
histogram_tester.ExpectTotalCount(
"Net.CertVerifier.MacTrustImplCacheInitTime", expected_count);
}
}
INSTANTIATE_TEST_SUITE_P(
Impl,
TrustStoreMacImplTest,
testing::Values(TrustStoreMac::TrustImplType::kDomainCacheFullCerts,
TrustStoreMac::TrustImplType::kKeychainCacheFullCerts),
[](const testing::TestParamInfo<TrustStoreMacImplTest::ParamType>& info) {
return TrustImplTypeToString(info.param);
});
}