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