1 // Copyright 2017 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/cert/internal/trust_store_mac.h"
6
7 #include <algorithm>
8 #include <set>
9
10 #include "base/base_paths.h"
11 #include "base/files/file_util.h"
12 #include "base/files/scoped_temp_dir.h"
13 #include "base/logging.h"
14 #include "base/path_service.h"
15 #include "base/process/launch.h"
16 #include "base/strings/strcat.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "base/strings/string_split.h"
19 #include "base/synchronization/lock.h"
20 #include "base/test/metrics/histogram_tester.h"
21 #include "base/test/scoped_feature_list.h"
22 #include "crypto/mac_security_services_lock.h"
23 #include "crypto/sha2.h"
24 #include "net/base/features.h"
25 #include "net/cert/internal/test_helpers.h"
26 #include "net/cert/internal/trust_store_features.h"
27 #include "net/cert/test_keychain_search_list_mac.h"
28 #include "net/cert/x509_certificate.h"
29 #include "net/cert/x509_util.h"
30 #include "net/cert/x509_util_apple.h"
31 #include "net/test/test_data_directory.h"
32 #include "testing/gmock/include/gmock/gmock.h"
33 #include "testing/gtest/include/gtest/gtest.h"
34 #include "third_party/boringssl/src/pki/cert_errors.h"
35 #include "third_party/boringssl/src/pki/parsed_certificate.h"
36 #include "third_party/boringssl/src/pki/pem.h"
37 #include "third_party/boringssl/src/pki/trust_store.h"
38
39 using ::testing::UnorderedElementsAreArray;
40
41 namespace net {
42
43 namespace {
44
45 // The PEM block header used for DER certificates
46 const char kCertificateHeader[] = "CERTIFICATE";
47
48 // Parses a PEM encoded certificate from |file_name| and stores in |result|.
ReadTestCert(const std::string & file_name,std::shared_ptr<const bssl::ParsedCertificate> * result)49 ::testing::AssertionResult ReadTestCert(
50 const std::string& file_name,
51 std::shared_ptr<const bssl::ParsedCertificate>* result) {
52 std::string der;
53 const PemBlockMapping mappings[] = {
54 {kCertificateHeader, &der},
55 };
56
57 ::testing::AssertionResult r = ReadTestDataFromPemFile(
58 "net/data/ssl/certificates/" + file_name, mappings);
59 if (!r)
60 return r;
61
62 bssl::CertErrors errors;
63 *result = bssl::ParsedCertificate::Create(x509_util::CreateCryptoBuffer(der),
64 {}, &errors);
65 if (!*result) {
66 return ::testing::AssertionFailure()
67 << "bssl::ParseCertificate::Create() failed:\n"
68 << errors.ToDebugString();
69 }
70 return ::testing::AssertionSuccess();
71 }
72
73 // Returns the DER encodings of the ParsedCertificates in |list|.
ParsedCertificateListAsDER(bssl::ParsedCertificateList list)74 std::vector<std::string> ParsedCertificateListAsDER(
75 bssl::ParsedCertificateList list) {
76 std::vector<std::string> result;
77 for (const auto& it : list)
78 result.push_back(it->der_cert().AsString());
79 return result;
80 }
81
ParseFindCertificateOutputToDerCerts(std::string output)82 std::set<std::string> ParseFindCertificateOutputToDerCerts(std::string output) {
83 std::set<std::string> certs;
84 for (const std::string& hash_and_pem_partial : base::SplitStringUsingSubstr(
85 output, "-----END CERTIFICATE-----", base::TRIM_WHITESPACE,
86 base::SPLIT_WANT_NONEMPTY)) {
87 // Re-add the PEM ending mark, since SplitStringUsingSubstr eats it.
88 const std::string hash_and_pem =
89 hash_and_pem_partial + "\n-----END CERTIFICATE-----\n";
90
91 // Parse the PEM encoded text to DER bytes.
92 bssl::PEMTokenizer pem_tokenizer(hash_and_pem, {kCertificateHeader});
93 if (!pem_tokenizer.GetNext()) {
94 ADD_FAILURE() << "!pem_tokenizer.GetNext()";
95 continue;
96 }
97 std::string cert_der(pem_tokenizer.data());
98 EXPECT_FALSE(pem_tokenizer.GetNext());
99 certs.insert(cert_der);
100 }
101 return certs;
102 }
103
TrustImplTypeToString(TrustStoreMac::TrustImplType t)104 const char* TrustImplTypeToString(TrustStoreMac::TrustImplType t) {
105 switch (t) {
106 case TrustStoreMac::TrustImplType::kSimple:
107 return "Simple";
108 case TrustStoreMac::TrustImplType::kDomainCacheFullCerts:
109 return "DomainCacheFullCerts";
110 case TrustStoreMac::TrustImplType::kKeychainCacheFullCerts:
111 return "KeychainCacheFullCerts";
112 case TrustStoreMac::TrustImplType::kUnknown:
113 return "Unknown";
114 }
115 }
116
117 } // namespace
118
119 class TrustStoreMacImplTest
120 : public testing::TestWithParam<
121 std::tuple<TrustStoreMac::TrustImplType, bool, bool>> {
122 public:
TrustStoreMacImplTest()123 TrustStoreMacImplTest()
124 : scoped_enforce_local_anchor_constraints_(
125 ExpectedEnforceLocalAnchorConstraintsEnabled()) {
126 if (ExpectedTrustedLeafSupportEnabled()) {
127 feature_list_.InitAndEnableFeature(
128 features::kTrustStoreTrustedLeafSupport);
129 } else {
130 feature_list_.InitAndDisableFeature(
131 features::kTrustStoreTrustedLeafSupport);
132 }
133 }
134
GetImplParam() const135 TrustStoreMac::TrustImplType GetImplParam() const {
136 return std::get<0>(GetParam());
137 }
138
ExpectedTrustedLeafSupportEnabled() const139 bool ExpectedTrustedLeafSupportEnabled() const {
140 return std::get<1>(GetParam());
141 }
142
ExpectedEnforceLocalAnchorConstraintsEnabled() const143 bool ExpectedEnforceLocalAnchorConstraintsEnabled() const {
144 return std::get<2>(GetParam());
145 }
146
ExpectedTrustForAnchor() const147 bssl::CertificateTrust ExpectedTrustForAnchor() const {
148 bssl::CertificateTrust trust;
149
150 if (ExpectedTrustedLeafSupportEnabled()) {
151 trust = bssl::CertificateTrust::ForTrustAnchorOrLeaf()
152 .WithEnforceAnchorExpiry();
153 } else {
154 trust =
155 bssl::CertificateTrust::ForTrustAnchor().WithEnforceAnchorExpiry();
156 }
157
158 if (ExpectedEnforceLocalAnchorConstraintsEnabled()) {
159 trust = trust.WithEnforceAnchorConstraints()
160 .WithRequireAnchorBasicConstraints();
161 }
162
163 return trust;
164 }
165
166 private:
167 base::test::ScopedFeatureList feature_list_;
168 ScopedLocalAnchorConstraintsEnforcementForTesting
169 scoped_enforce_local_anchor_constraints_;
170 };
171
172 // Much of the Keychain API was marked deprecated as of the macOS 13 SDK.
173 // Removal of its use is tracked in https://crbug.com/1348251 but deprecation
174 // warnings are disabled in the meanwhile.
175 #pragma clang diagnostic push
176 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
177
178 // Test the trust store using known test certificates in a keychain. Tests
179 // that issuer searching returns the expected certificates, and that none of
180 // the certificates are trusted.
TEST_P(TrustStoreMacImplTest,MultiRootNotTrusted)181 TEST_P(TrustStoreMacImplTest, MultiRootNotTrusted) {
182 std::unique_ptr<TestKeychainSearchList> test_keychain_search_list(
183 TestKeychainSearchList::Create());
184 ASSERT_TRUE(test_keychain_search_list);
185 base::FilePath keychain_path(
186 GetTestCertsDirectory().AppendASCII("multi-root.keychain"));
187 // SecKeychainOpen does not fail if the file doesn't exist, so assert it here
188 // for easier debugging.
189 ASSERT_TRUE(base::PathExists(keychain_path));
190 base::apple::ScopedCFTypeRef<SecKeychainRef> keychain;
191 OSStatus status = SecKeychainOpen(keychain_path.MaybeAsASCII().c_str(),
192 keychain.InitializeInto());
193 ASSERT_EQ(errSecSuccess, status);
194 ASSERT_TRUE(keychain);
195 test_keychain_search_list->AddKeychain(keychain.get());
196
197 #pragma clang diagnostic pop
198
199 const TrustStoreMac::TrustImplType trust_impl = GetImplParam();
200 TrustStoreMac trust_store(kSecPolicyAppleSSL, trust_impl);
201
202 std::shared_ptr<const bssl::ParsedCertificate> a_by_b, b_by_c, b_by_f, c_by_d,
203 c_by_e, f_by_e, d_by_d, e_by_e;
204 ASSERT_TRUE(ReadTestCert("multi-root-A-by-B.pem", &a_by_b));
205 ASSERT_TRUE(ReadTestCert("multi-root-B-by-C.pem", &b_by_c));
206 ASSERT_TRUE(ReadTestCert("multi-root-B-by-F.pem", &b_by_f));
207 ASSERT_TRUE(ReadTestCert("multi-root-C-by-D.pem", &c_by_d));
208 ASSERT_TRUE(ReadTestCert("multi-root-C-by-E.pem", &c_by_e));
209 ASSERT_TRUE(ReadTestCert("multi-root-F-by-E.pem", &f_by_e));
210 ASSERT_TRUE(ReadTestCert("multi-root-D-by-D.pem", &d_by_d));
211 ASSERT_TRUE(ReadTestCert("multi-root-E-by-E.pem", &e_by_e));
212
213 // Test that the untrusted keychain certs would be found during issuer
214 // searching.
215 {
216 bssl::ParsedCertificateList found_issuers;
217 trust_store.SyncGetIssuersOf(a_by_b.get(), &found_issuers);
218 EXPECT_THAT(ParsedCertificateListAsDER(found_issuers),
219 UnorderedElementsAreArray(
220 ParsedCertificateListAsDER({b_by_c, b_by_f})));
221 }
222
223 {
224 bssl::ParsedCertificateList found_issuers;
225 trust_store.SyncGetIssuersOf(b_by_c.get(), &found_issuers);
226 EXPECT_THAT(ParsedCertificateListAsDER(found_issuers),
227 UnorderedElementsAreArray(
228 ParsedCertificateListAsDER({c_by_d, c_by_e})));
229 }
230
231 {
232 bssl::ParsedCertificateList found_issuers;
233 trust_store.SyncGetIssuersOf(b_by_f.get(), &found_issuers);
234 EXPECT_THAT(
235 ParsedCertificateListAsDER(found_issuers),
236 UnorderedElementsAreArray(ParsedCertificateListAsDER({f_by_e})));
237 }
238
239 {
240 bssl::ParsedCertificateList found_issuers;
241 trust_store.SyncGetIssuersOf(c_by_d.get(), &found_issuers);
242 EXPECT_THAT(
243 ParsedCertificateListAsDER(found_issuers),
244 UnorderedElementsAreArray(ParsedCertificateListAsDER({d_by_d})));
245 }
246
247 {
248 bssl::ParsedCertificateList found_issuers;
249 trust_store.SyncGetIssuersOf(f_by_e.get(), &found_issuers);
250 EXPECT_THAT(
251 ParsedCertificateListAsDER(found_issuers),
252 UnorderedElementsAreArray(ParsedCertificateListAsDER({e_by_e})));
253 }
254
255 // Verify that none of the added certificates are considered trusted (since
256 // the test certs in the keychain aren't trusted, unless someone manually
257 // added and trusted the test certs on the machine the test is being run on).
258 for (const auto& cert :
259 {a_by_b, b_by_c, b_by_f, c_by_d, c_by_e, f_by_e, d_by_d, e_by_e}) {
260 bssl::CertificateTrust trust = trust_store.GetTrust(cert.get());
261 EXPECT_EQ(bssl::CertificateTrust::ForUnspecified().ToDebugString(),
262 trust.ToDebugString());
263 }
264 }
265
266 // Test against all the certificates in the default keychains. Confirms that
267 // the computed trust value matches that of SecTrustEvaluateWithError.
TEST_P(TrustStoreMacImplTest,SystemCerts)268 TEST_P(TrustStoreMacImplTest, SystemCerts) {
269 // Get the list of all certificates in the user & system keychains.
270 // This may include both trusted and untrusted certificates.
271 //
272 // The output contains zero or more repetitions of:
273 // "SHA-1 hash: <hash>\n<PEM encoded cert>\n"
274 // Starting with macOS 10.15, it includes both SHA-256 and SHA-1 hashes:
275 // "SHA-256 hash: <hash>\nSHA-1 hash: <hash>\n<PEM encoded cert>\n"
276 std::string find_certificate_default_search_list_output;
277 ASSERT_TRUE(
278 base::GetAppOutput({"security", "find-certificate", "-a", "-p", "-Z"},
279 &find_certificate_default_search_list_output));
280 // Get the list of all certificates in the system roots keychain.
281 // (Same details as above.)
282 std::string find_certificate_system_roots_output;
283 ASSERT_TRUE(base::GetAppOutput(
284 {"security", "find-certificate", "-a", "-p", "-Z",
285 "/System/Library/Keychains/SystemRootCertificates.keychain"},
286 &find_certificate_system_roots_output));
287
288 std::set<std::string> find_certificate_default_search_list_certs =
289 ParseFindCertificateOutputToDerCerts(
290 find_certificate_default_search_list_output);
291 std::set<std::string> find_certificate_system_roots_certs =
292 ParseFindCertificateOutputToDerCerts(
293 find_certificate_system_roots_output);
294
295 const TrustStoreMac::TrustImplType trust_impl = GetImplParam();
296
297 base::HistogramTester histogram_tester;
298 TrustStoreMac trust_store(kSecPolicyAppleX509Basic, trust_impl);
299
300 base::apple::ScopedCFTypeRef<SecPolicyRef> sec_policy(
301 SecPolicyCreateBasicX509());
302 ASSERT_TRUE(sec_policy);
303 std::vector<std::string> all_certs;
304 std::set_union(find_certificate_default_search_list_certs.begin(),
305 find_certificate_default_search_list_certs.end(),
306 find_certificate_system_roots_certs.begin(),
307 find_certificate_system_roots_certs.end(),
308 std::back_inserter(all_certs));
309 for (const std::string& cert_der : all_certs) {
310 std::string hash = crypto::SHA256HashString(cert_der);
311 std::string hash_text = base::HexEncode(hash);
312 SCOPED_TRACE(hash_text);
313
314 bssl::CertErrors errors;
315 // Note: don't actually need to make a bssl::ParsedCertificate here, just
316 // need the DER bytes. But parsing it here ensures the test can skip any
317 // certs that won't be returned due to parsing failures inside
318 // TrustStoreMac. The parsing options set here need to match the ones used
319 // in trust_store_mac.cc.
320 bssl::ParseCertificateOptions options;
321 // For https://crt.sh/?q=D3EEFBCBBCF49867838626E23BB59CA01E305DB7:
322 options.allow_invalid_serial_numbers = true;
323 std::shared_ptr<const bssl::ParsedCertificate> cert =
324 bssl::ParsedCertificate::Create(x509_util::CreateCryptoBuffer(cert_der),
325 options, &errors);
326 if (!cert) {
327 LOG(WARNING) << "bssl::ParseCertificate::Create " << hash_text
328 << " failed:\n"
329 << errors.ToDebugString();
330 continue;
331 }
332
333 base::apple::ScopedCFTypeRef<SecCertificateRef> cert_handle(
334 x509_util::CreateSecCertificateFromBytes(cert->der_cert()));
335 if (!cert_handle) {
336 ADD_FAILURE() << "CreateCertBufferFromBytes " << hash_text;
337 continue;
338 }
339
340 // Check if this cert is considered a trust anchor by TrustStoreMac.
341 bssl::CertificateTrust cert_trust = trust_store.GetTrust(cert.get());
342 bool is_trusted = cert_trust.IsTrustAnchor() || cert_trust.IsTrustLeaf();
343 if (is_trusted) {
344 EXPECT_EQ(ExpectedTrustForAnchor().ToDebugString(),
345 cert_trust.ToDebugString());
346 }
347
348 // Check if this cert is considered a trust anchor by the OS.
349 base::apple::ScopedCFTypeRef<SecTrustRef> trust;
350 {
351 base::AutoLock lock(crypto::GetMacSecurityServicesLock());
352 ASSERT_EQ(noErr, SecTrustCreateWithCertificates(cert_handle.get(),
353 sec_policy.get(),
354 trust.InitializeInto()));
355 ASSERT_EQ(noErr, SecTrustSetOptions(trust.get(),
356 kSecTrustOptionLeafIsCA |
357 kSecTrustOptionAllowExpired |
358 kSecTrustOptionAllowExpiredRoot));
359
360 if (find_certificate_default_search_list_certs.count(cert_der) &&
361 find_certificate_system_roots_certs.count(cert_der)) {
362 // If the same certificate is present in both the System and User/Admin
363 // domains, and TrustStoreMac is only using trust settings from
364 // User/Admin, then it's not possible for this test to know whether the
365 // result from SecTrustEvaluate should match the TrustStoreMac result.
366 // Just ignore such certificates.
367 } else if (!find_certificate_default_search_list_certs.count(cert_der)) {
368 // Cert is only in the system domain. It should be untrusted.
369 EXPECT_FALSE(is_trusted);
370 } else {
371 bool trusted = SecTrustEvaluateWithError(trust.get(), nullptr);
372 bool expected_trust_anchor =
373 trusted && (SecTrustGetCertificateCount(trust.get()) == 1);
374 EXPECT_EQ(expected_trust_anchor, is_trusted);
375 }
376 }
377
378 // Call GetTrust again on the same cert. This should exercise the code
379 // that checks the trust value for a cert which has already been cached.
380 bssl::CertificateTrust cert_trust2 = trust_store.GetTrust(cert.get());
381 EXPECT_EQ(cert_trust.ToDebugString(), cert_trust2.ToDebugString());
382 }
383
384 // Since this is testing the actual platform certs and trust settings, we
385 // don't know what values the histograms should be, so just verify that the
386 // histogram is recorded (or not) depending on the requested trust impl.
387
388 {
389 // Histograms only logged by DomainCacheFullCerts impl:
390 const int expected_count =
391 (trust_impl == TrustStoreMac::TrustImplType::kDomainCacheFullCerts) ? 1
392 : 0;
393 histogram_tester.ExpectTotalCount(
394 "Net.CertVerifier.MacTrustDomainCertCount.User", expected_count);
395 histogram_tester.ExpectTotalCount(
396 "Net.CertVerifier.MacTrustDomainCertCount.Admin", expected_count);
397 histogram_tester.ExpectTotalCount(
398 "Net.CertVerifier.MacTrustDomainCacheInitTime", expected_count);
399 histogram_tester.ExpectTotalCount(
400 "Net.CertVerifier.MacKeychainCerts.IntermediateCacheInitTime",
401 expected_count);
402 }
403
404 {
405 // Histograms only logged by KeychainCacheFullCerts impl:
406 const int expected_count =
407 (trust_impl == TrustStoreMac::TrustImplType::kKeychainCacheFullCerts)
408 ? 1
409 : 0;
410 histogram_tester.ExpectTotalCount(
411 "Net.CertVerifier.MacKeychainCerts.TrustCount", expected_count);
412 }
413
414 {
415 // Histograms logged by both DomainCacheFullCerts and KeychainCacheFullCerts
416 // impls:
417 const int expected_count =
418 (trust_impl == TrustStoreMac::TrustImplType::kDomainCacheFullCerts ||
419 trust_impl == TrustStoreMac::TrustImplType::kKeychainCacheFullCerts)
420 ? 1
421 : 0;
422 histogram_tester.ExpectTotalCount(
423 "Net.CertVerifier.MacKeychainCerts.IntermediateCount", expected_count);
424 histogram_tester.ExpectTotalCount(
425 "Net.CertVerifier.MacKeychainCerts.TotalCount", expected_count);
426 histogram_tester.ExpectTotalCount(
427 "Net.CertVerifier.MacTrustImplCacheInitTime", expected_count);
428 }
429 }
430
431 INSTANTIATE_TEST_SUITE_P(
432 Impl,
433 TrustStoreMacImplTest,
434 testing::Combine(
435 testing::Values(TrustStoreMac::TrustImplType::kSimple,
436 TrustStoreMac::TrustImplType::kDomainCacheFullCerts,
437 TrustStoreMac::TrustImplType::kKeychainCacheFullCerts),
438 testing::Bool(),
439 testing::Bool()),
__anonf80f93310202(const testing::TestParamInfo<TrustStoreMacImplTest::ParamType>& info) 440 [](const testing::TestParamInfo<TrustStoreMacImplTest::ParamType>& info) {
441 return base::StrCat(
442 {TrustImplTypeToString(std::get<0>(info.param)),
443 std::get<1>(info.param) ? "TrustedLeafSupported" : "TrustAnchorOnly",
444 std::get<2>(info.param) ? "EnforceLocalAnchorConstraints"
445 : "NoLocalAnchorConstraints"});
446 });
447
448 } // namespace net
449