Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(143)

Side by Side Diff: net/cert/internal/trust_store_mac_unittest.cc

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

Powered by Google App Engine
This is Rietveld 408576698