1 // Copyright 2017 The Chromium Authors. All rights reserved.
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 "base/base_paths.h"
8 #include "base/files/file_util.h"
9 #include "base/files/scoped_temp_dir.h"
10 #include "base/logging.h"
11 #include "base/path_service.h"
12 #include "base/process/launch.h"
13 #include "base/strings/string_split.h"
14 #include "base/synchronization/lock.h"
15 #include "crypto/mac_security_services_lock.h"
16 #include "net/cert/internal/cert_errors.h"
17 #include "net/cert/internal/test_helpers.h"
18 #include "net/cert/pem.h"
19 #include "net/cert/test_keychain_search_list_mac.h"
20 #include "net/cert/x509_certificate.h"
21 #include "net/cert/x509_util.h"
22 #include "net/cert/x509_util_mac.h"
23 #include "net/test/test_data_directory.h"
24 #include "testing/gmock/include/gmock/gmock.h"
25 #include "testing/gtest/include/gtest/gtest.h"
26
27 using ::testing::UnorderedElementsAreArray;
28
29 namespace net {
30
31 namespace {
32
33 // The PEM block header used for DER certificates
34 const char kCertificateHeader[] = "CERTIFICATE";
35
36 // Parses a PEM encoded certificate from |file_name| and stores in |result|.
ReadTestCert(const std::string & file_name,scoped_refptr<ParsedCertificate> * result)37 ::testing::AssertionResult ReadTestCert(
38 const std::string& file_name,
39 scoped_refptr<ParsedCertificate>* result) {
40 std::string der;
41 const PemBlockMapping mappings[] = {
42 {kCertificateHeader, &der},
43 };
44
45 ::testing::AssertionResult r = ReadTestDataFromPemFile(
46 "net/data/ssl/certificates/" + file_name, mappings);
47 if (!r)
48 return r;
49
50 CertErrors errors;
51 *result = ParsedCertificate::Create(x509_util::CreateCryptoBuffer(der), {},
52 &errors);
53 if (!*result) {
54 return ::testing::AssertionFailure()
55 << "ParseCertificate::Create() failed:\n"
56 << errors.ToDebugString();
57 }
58 return ::testing::AssertionSuccess();
59 }
60
61 // Returns the DER encodings of the SecCertificates in |array|.
SecCertificateArrayAsDER(CFArrayRef array)62 std::vector<std::string> SecCertificateArrayAsDER(CFArrayRef array) {
63 std::vector<std::string> result;
64
65 for (CFIndex i = 0, item_count = CFArrayGetCount(array); i < item_count;
66 ++i) {
67 SecCertificateRef match_cert_handle = reinterpret_cast<SecCertificateRef>(
68 const_cast<void*>(CFArrayGetValueAtIndex(array, i)));
69 if (!match_cert_handle) {
70 ADD_FAILURE() << "null item " << i;
71 continue;
72 }
73 base::ScopedCFTypeRef<CFDataRef> der_data(
74 SecCertificateCopyData(match_cert_handle));
75 if (!der_data) {
76 ADD_FAILURE() << "SecCertificateCopyData error";
77 continue;
78 }
79 result.push_back(std::string(
80 reinterpret_cast<const char*>(CFDataGetBytePtr(der_data.get())),
81 CFDataGetLength(der_data.get())));
82 }
83
84 return result;
85 }
86
87 // Returns the DER encodings of the ParsedCertificates in |list|.
ParsedCertificateListAsDER(ParsedCertificateList list)88 std::vector<std::string> ParsedCertificateListAsDER(
89 ParsedCertificateList list) {
90 std::vector<std::string> result;
91 for (const auto& it : list)
92 result.push_back(it->der_cert().AsString());
93 return result;
94 }
95
96 class DebugData : public base::SupportsUserData {
97 public:
98 ~DebugData() override = default;
99 };
100
101 } // namespace
102
103 // Test the trust store using known test certificates in a keychain. Tests
104 // that issuer searching returns the expected certificates, and that none of
105 // the certificates are trusted.
TEST(TrustStoreMacTest,MultiRootNotTrusted)106 TEST(TrustStoreMacTest, MultiRootNotTrusted) {
107 std::unique_ptr<TestKeychainSearchList> test_keychain_search_list(
108 TestKeychainSearchList::Create());
109 ASSERT_TRUE(test_keychain_search_list);
110 base::FilePath keychain_path(
111 GetTestCertsDirectory().AppendASCII("multi-root.keychain"));
112 // SecKeychainOpen does not fail if the file doesn't exist, so assert it here
113 // for easier debugging.
114 ASSERT_TRUE(base::PathExists(keychain_path));
115 base::ScopedCFTypeRef<SecKeychainRef> keychain;
116 OSStatus status = SecKeychainOpen(keychain_path.MaybeAsASCII().c_str(),
117 keychain.InitializeInto());
118 ASSERT_EQ(errSecSuccess, status);
119 ASSERT_TRUE(keychain);
120 test_keychain_search_list->AddKeychain(keychain);
121
122 TrustStoreMac trust_store(kSecPolicyAppleSSL);
123
124 scoped_refptr<ParsedCertificate> a_by_b, b_by_c, b_by_f, c_by_d, c_by_e,
125 f_by_e, d_by_d, e_by_e;
126 ASSERT_TRUE(ReadTestCert("multi-root-A-by-B.pem", &a_by_b));
127 ASSERT_TRUE(ReadTestCert("multi-root-B-by-C.pem", &b_by_c));
128 ASSERT_TRUE(ReadTestCert("multi-root-B-by-F.pem", &b_by_f));
129 ASSERT_TRUE(ReadTestCert("multi-root-C-by-D.pem", &c_by_d));
130 ASSERT_TRUE(ReadTestCert("multi-root-C-by-E.pem", &c_by_e));
131 ASSERT_TRUE(ReadTestCert("multi-root-F-by-E.pem", &f_by_e));
132 ASSERT_TRUE(ReadTestCert("multi-root-D-by-D.pem", &d_by_d));
133 ASSERT_TRUE(ReadTestCert("multi-root-E-by-E.pem", &e_by_e));
134
135 base::ScopedCFTypeRef<CFDataRef> normalized_name_b =
136 TrustStoreMac::GetMacNormalizedIssuer(a_by_b.get());
137 ASSERT_TRUE(normalized_name_b);
138 base::ScopedCFTypeRef<CFDataRef> normalized_name_c =
139 TrustStoreMac::GetMacNormalizedIssuer(b_by_c.get());
140 ASSERT_TRUE(normalized_name_c);
141 base::ScopedCFTypeRef<CFDataRef> normalized_name_f =
142 TrustStoreMac::GetMacNormalizedIssuer(b_by_f.get());
143 ASSERT_TRUE(normalized_name_f);
144 base::ScopedCFTypeRef<CFDataRef> normalized_name_d =
145 TrustStoreMac::GetMacNormalizedIssuer(c_by_d.get());
146 ASSERT_TRUE(normalized_name_d);
147 base::ScopedCFTypeRef<CFDataRef> normalized_name_e =
148 TrustStoreMac::GetMacNormalizedIssuer(f_by_e.get());
149 ASSERT_TRUE(normalized_name_e);
150
151 // Test that the matching keychain items are found, even though they aren't
152 // trusted.
153 // TODO(eroman): These tests could be using TrustStore::SyncGetIssuersOf().
154 {
155 base::ScopedCFTypeRef<CFArrayRef> scoped_matching_items =
156 TrustStoreMac::FindMatchingCertificatesForMacNormalizedSubject(
157 normalized_name_b.get());
158
159 EXPECT_THAT(SecCertificateArrayAsDER(scoped_matching_items),
160 UnorderedElementsAreArray(
161 ParsedCertificateListAsDER({b_by_c, b_by_f})));
162 }
163
164 {
165 base::ScopedCFTypeRef<CFArrayRef> scoped_matching_items =
166 TrustStoreMac::FindMatchingCertificatesForMacNormalizedSubject(
167 normalized_name_c.get());
168 EXPECT_THAT(SecCertificateArrayAsDER(scoped_matching_items),
169 UnorderedElementsAreArray(
170 ParsedCertificateListAsDER({c_by_d, c_by_e})));
171 }
172
173 {
174 base::ScopedCFTypeRef<CFArrayRef> scoped_matching_items =
175 TrustStoreMac::FindMatchingCertificatesForMacNormalizedSubject(
176 normalized_name_f.get());
177 EXPECT_THAT(
178 SecCertificateArrayAsDER(scoped_matching_items),
179 UnorderedElementsAreArray(ParsedCertificateListAsDER({f_by_e})));
180 }
181
182 {
183 base::ScopedCFTypeRef<CFArrayRef> scoped_matching_items =
184 TrustStoreMac::FindMatchingCertificatesForMacNormalizedSubject(
185 normalized_name_d.get());
186 EXPECT_THAT(
187 SecCertificateArrayAsDER(scoped_matching_items),
188 UnorderedElementsAreArray(ParsedCertificateListAsDER({d_by_d})));
189 }
190
191 {
192 base::ScopedCFTypeRef<CFArrayRef> scoped_matching_items =
193 TrustStoreMac::FindMatchingCertificatesForMacNormalizedSubject(
194 normalized_name_e.get());
195 EXPECT_THAT(
196 SecCertificateArrayAsDER(scoped_matching_items),
197 UnorderedElementsAreArray(ParsedCertificateListAsDER({e_by_e})));
198 }
199
200 // Verify that none of the added certificates are considered trusted (since
201 // the test certs in the keychain aren't trusted, unless someone manually
202 // added and trusted the test certs on the machine the test is being run on).
203 for (const auto& cert :
204 {a_by_b, b_by_c, b_by_f, c_by_d, c_by_e, f_by_e, d_by_d, e_by_e}) {
205 CertificateTrust trust = CertificateTrust::ForTrustAnchor();
206 DebugData debug_data;
207 trust_store.GetTrust(cert.get(), &trust, &debug_data);
208 EXPECT_EQ(CertificateTrustType::UNSPECIFIED, trust.type);
209 // Certs without trust settings should not add debug info to debug_data.
210 EXPECT_FALSE(TrustStoreMac::ResultDebugData::Get(&debug_data));
211 }
212 }
213
214 // Test against all the certificates in the default keychains. Confirms that
215 // the computed trust value matches that of SecTrustEvaluate.
TEST(TrustStoreMacTest,SystemCerts)216 TEST(TrustStoreMacTest, SystemCerts) {
217 // Get the list of all certificates in the user & system keychains.
218 // This may include both trusted and untrusted certificates.
219 //
220 // The output contains zero or more repetitions of:
221 // "SHA-1 hash: <hash>\n<PEM encoded cert>\n"
222 // Starting with macOS 10.15, it includes both SHA-256 and SHA-1 hashes:
223 // "SHA-256 hash: <hash>\nSHA-1 hash: <hash>\n<PEM encoded cert>\n"
224 std::string find_certificate_default_search_list_output;
225 ASSERT_TRUE(
226 base::GetAppOutput({"security", "find-certificate", "-a", "-p", "-Z"},
227 &find_certificate_default_search_list_output));
228 // Get the list of all certificates in the system roots keychain.
229 // (Same details as above.)
230 std::string find_certificate_system_roots_output;
231 ASSERT_TRUE(base::GetAppOutput(
232 {"security", "find-certificate", "-a", "-p", "-Z",
233 "/System/Library/Keychains/SystemRootCertificates.keychain"},
234 &find_certificate_system_roots_output));
235
236 TrustStoreMac trust_store(kSecPolicyAppleX509Basic);
237
238 base::ScopedCFTypeRef<SecPolicyRef> sec_policy(SecPolicyCreateBasicX509());
239 ASSERT_TRUE(sec_policy);
240 for (const std::string& hash_and_pem_partial : base::SplitStringUsingSubstr(
241 find_certificate_system_roots_output +
242 find_certificate_default_search_list_output,
243 "-----END CERTIFICATE-----", base::TRIM_WHITESPACE,
244 base::SPLIT_WANT_NONEMPTY)) {
245 // Re-add the PEM ending mark, since SplitStringUsingSubstr eats it.
246 const std::string hash_and_pem =
247 hash_and_pem_partial + "\n-----END CERTIFICATE-----\n";
248
249 // Use the first hash value found in the text. This might be SHA-256 or
250 // SHA-1, but it's only for debugging purposes so it doesn't matter as long
251 // as one exists.
252 std::string::size_type hash_pos = hash_and_pem.find("hash: ");
253 ASSERT_NE(std::string::npos, hash_pos);
254 hash_pos += 6;
255 std::string::size_type eol_pos = hash_and_pem.find_first_of("\r\n");
256 ASSERT_NE(std::string::npos, eol_pos);
257 // Extract the hash of the certificate. This isn't necessary for the
258 // test, but is a convenient identifier to use in any error messages.
259 std::string hash_text = hash_and_pem.substr(hash_pos, eol_pos - hash_pos);
260
261 SCOPED_TRACE(hash_text);
262 // TODO(mattm): The same cert might exist in both lists, could de-dupe
263 // before testing?
264
265 // Parse the PEM encoded text to DER bytes.
266 PEMTokenizer pem_tokenizer(hash_and_pem, {kCertificateHeader});
267 ASSERT_TRUE(pem_tokenizer.GetNext());
268 std::string cert_der(pem_tokenizer.data());
269 ASSERT_FALSE(pem_tokenizer.GetNext());
270
271 CertErrors errors;
272 // Note: don't actually need to make a ParsedCertificate here, just need
273 // the DER bytes. But parsing it here ensures the test can skip any certs
274 // that won't be returned due to parsing failures inside TrustStoreMac.
275 // The parsing options set here need to match the ones used in
276 // trust_store_mac.cc.
277 ParseCertificateOptions options;
278 // For https://crt.sh/?q=D3EEFBCBBCF49867838626E23BB59CA01E305DB7:
279 options.allow_invalid_serial_numbers = true;
280 scoped_refptr<ParsedCertificate> cert = ParsedCertificate::Create(
281 x509_util::CreateCryptoBuffer(cert_der), options, &errors);
282 if (!cert) {
283 LOG(WARNING) << "ParseCertificate::Create " << hash_text << " failed:\n"
284 << errors.ToDebugString();
285 continue;
286 }
287 // Check if this cert is considered a trust anchor by TrustStoreMac.
288 CertificateTrust cert_trust;
289 DebugData debug_data;
290 trust_store.GetTrust(cert, &cert_trust, &debug_data);
291 bool is_trust_anchor = cert_trust.IsTrustAnchor();
292
293 // Check if this cert is considered a trust anchor by the OS.
294 base::ScopedCFTypeRef<SecCertificateRef> cert_handle(
295 x509_util::CreateSecCertificateFromBytes(cert->der_cert().UnsafeData(),
296 cert->der_cert().Length()));
297 if (!cert_handle) {
298 ADD_FAILURE() << "CreateCertBufferFromBytes " << hash_text;
299 continue;
300 }
301 base::ScopedCFTypeRef<SecTrustRef> trust;
302 {
303 base::AutoLock lock(crypto::GetMacSecurityServicesLock());
304 ASSERT_EQ(noErr,
305 SecTrustCreateWithCertificates(cert_handle, sec_policy,
306 trust.InitializeInto()));
307 ASSERT_EQ(noErr,
308 SecTrustSetOptions(trust, kSecTrustOptionLeafIsCA |
309 kSecTrustOptionAllowExpired |
310 kSecTrustOptionAllowExpiredRoot));
311
312 SecTrustResultType trust_result;
313 ASSERT_EQ(noErr, SecTrustEvaluate(trust, &trust_result));
314 bool expected_trust_anchor =
315 ((trust_result == kSecTrustResultProceed) ||
316 (trust_result == kSecTrustResultUnspecified)) &&
317 (SecTrustGetCertificateCount(trust) == 1);
318 EXPECT_EQ(expected_trust_anchor, is_trust_anchor);
319 if (is_trust_anchor) {
320 auto* trust_debug_data =
321 TrustStoreMac::ResultDebugData::Get(&debug_data);
322 ASSERT_TRUE(trust_debug_data);
323 // Since this test queries the real trust store, can't know exactly
324 // what bits should be set in the trust debug info, but it should at
325 // least have something set.
326 EXPECT_NE(0, trust_debug_data->combined_trust_debug_info());
327 }
328 }
329 }
330 }
331
332 } // namespace net
333