xref: /aosp_15_r20/external/cronet/net/cert/internal/trust_store_mac_unittest.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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