• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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