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

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

Issue 2585963003: PKI library Mac trust store integration (Closed)
Patch Set: review changes for comment #19 Created 3 years, 10 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
OLDNEW
(Empty)
1 // Copyright 2016 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 "net/cert/internal/cert_errors.h"
14 #include "net/cert/internal/test_helpers.h"
15 #include "net/cert/pem_tokenizer.h"
16 #include "net/cert/test_keychain_search_list_mac.h"
17 #include "net/cert/x509_certificate.h"
18 #include "net/test/test_data_directory.h"
19 #include "testing/gmock/include/gmock/gmock.h"
20 #include "testing/gtest/include/gtest/gtest.h"
21
22 using ::testing::UnorderedElementsAreArray;
23
24 namespace net {
25
26 namespace {
27
28 // The PEM block header used for DER certificates
29 const char kCertificateHeader[] = "CERTIFICATE";
30
31 // Parses a PEM encoded certificate from |file_name| and stores in |result|.
32 ::testing::AssertionResult ReadTestCert(
33 const std::string& file_name,
34 scoped_refptr<ParsedCertificate>* result) {
35 std::string der;
36 const PemBlockMapping mappings[] = {
37 {kCertificateHeader, &der},
38 };
39
40 ::testing::AssertionResult r = ReadTestDataFromPemFile(
41 "net/data/ssl/certificates/" + file_name, mappings);
42 if (!r)
43 return r;
44
45 CertErrors errors;
46 *result = ParsedCertificate::Create(der, {}, &errors);
47 if (!*result) {
48 return ::testing::AssertionFailure()
49 << "ParseCertificate::Create() failed:\n"
50 << errors.ToDebugString();
51 }
52 return ::testing::AssertionSuccess();
53 }
54
55 // Returns the DER encodings of the SecCertificates in |array|.
56 std::vector<std::string> SecCertificateArrayAsDER(CFArrayRef array) {
57 std::vector<std::string> result;
58
59 for (CFIndex i = 0, item_count = CFArrayGetCount(array); i < item_count;
60 ++i) {
61 SecCertificateRef match_cert_handle = reinterpret_cast<SecCertificateRef>(
62 const_cast<void*>(CFArrayGetValueAtIndex(array, i)));
63 if (!match_cert_handle) {
64 ADD_FAILURE() << "null item " << i;
65 continue;
66 }
67 base::ScopedCFTypeRef<CFDataRef> der_data(
68 SecCertificateCopyData(match_cert_handle));
69 if (!der_data) {
70 ADD_FAILURE() << "SecCertificateCopyData error";
71 continue;
72 }
73 result.push_back(std::string(
74 reinterpret_cast<const char*>(CFDataGetBytePtr(der_data.get())),
75 CFDataGetLength(der_data.get())));
76 }
77
78 return result;
79 }
80
81 // Returns the DER encodings of the ParsedCertificates in |list|.
82 std::vector<std::string> ParsedCertificateListAsDER(
83 ParsedCertificateList list) {
84 std::vector<std::string> result;
85 for (const auto& it : list)
86 result.push_back(it->der_cert().AsString());
87 return result;
88 }
89
90 } // namespace
91
92 // Test the trust store using known test certificates in a keychain. Tests
93 // that issuer searching returns the expected certificates, and that none of
94 // the certificates are trusted.
95 TEST(TrustStoreMacTest, MultiRootNotTrusted) {
96 std::unique_ptr<TestKeychainSearchList> test_keychain_search_list(
97 TestKeychainSearchList::Create());
98 ASSERT_TRUE(test_keychain_search_list);
99 base::FilePath keychain_path(
100 GetTestCertsDirectory().AppendASCII("multi-root.keychain"));
101 // SecKeychainOpen does not fail if the file doesn't exist, so assert it here
102 // for easier debugging.
103 ASSERT_TRUE(base::PathExists(keychain_path));
104 base::ScopedCFTypeRef<SecKeychainRef> keychain;
105 OSStatus status = SecKeychainOpen(keychain_path.MaybeAsASCII().c_str(),
106 keychain.InitializeInto());
107 ASSERT_EQ(errSecSuccess, status);
108 ASSERT_TRUE(keychain);
109 test_keychain_search_list->AddKeychain(keychain);
110
111 TrustStoreMac trust_store(kSecPolicyAppleSSL);
112
113 scoped_refptr<ParsedCertificate> a_by_b, b_by_c, b_by_f, c_by_d, c_by_e,
114 f_by_e, d_by_d, e_by_e;
115 ASSERT_TRUE(ReadTestCert("multi-root-A-by-B.pem", &a_by_b));
116 ASSERT_TRUE(ReadTestCert("multi-root-B-by-C.pem", &b_by_c));
117 ASSERT_TRUE(ReadTestCert("multi-root-B-by-F.pem", &b_by_f));
118 ASSERT_TRUE(ReadTestCert("multi-root-C-by-D.pem", &c_by_d));
119 ASSERT_TRUE(ReadTestCert("multi-root-C-by-E.pem", &c_by_e));
120 ASSERT_TRUE(ReadTestCert("multi-root-F-by-E.pem", &f_by_e));
121 ASSERT_TRUE(ReadTestCert("multi-root-D-by-D.pem", &d_by_d));
122 ASSERT_TRUE(ReadTestCert("multi-root-E-by-E.pem", &e_by_e));
123
124 base::ScopedCFTypeRef<CFDataRef> normalized_name_b =
125 TrustStoreMac::GetMacNormalizedIssuer(a_by_b);
126 ASSERT_TRUE(normalized_name_b);
127 base::ScopedCFTypeRef<CFDataRef> normalized_name_c =
128 TrustStoreMac::GetMacNormalizedIssuer(b_by_c);
129 ASSERT_TRUE(normalized_name_c);
130 base::ScopedCFTypeRef<CFDataRef> normalized_name_f =
131 TrustStoreMac::GetMacNormalizedIssuer(b_by_f);
132 ASSERT_TRUE(normalized_name_f);
133 base::ScopedCFTypeRef<CFDataRef> normalized_name_d =
134 TrustStoreMac::GetMacNormalizedIssuer(c_by_d);
135 ASSERT_TRUE(normalized_name_d);
136 base::ScopedCFTypeRef<CFDataRef> normalized_name_e =
137 TrustStoreMac::GetMacNormalizedIssuer(f_by_e);
138 ASSERT_TRUE(normalized_name_e);
139
140 // Test that the matching keychain items are found, even though they aren't
141 // trusted.
142 {
143 base::ScopedCFTypeRef<CFArrayRef> scoped_matching_items =
144 TrustStoreMac::FindMatchingCertificatesForMacNormalizedSubject(
145 normalized_name_b.get());
146
147 EXPECT_THAT(SecCertificateArrayAsDER(scoped_matching_items),
148 UnorderedElementsAreArray(
149 ParsedCertificateListAsDER({b_by_c, b_by_f})));
150 }
151
152 {
153 base::ScopedCFTypeRef<CFArrayRef> scoped_matching_items =
154 TrustStoreMac::FindMatchingCertificatesForMacNormalizedSubject(
155 normalized_name_c.get());
156 EXPECT_THAT(SecCertificateArrayAsDER(scoped_matching_items),
157 UnorderedElementsAreArray(
158 ParsedCertificateListAsDER({c_by_d, c_by_e})));
159 }
160
161 {
162 base::ScopedCFTypeRef<CFArrayRef> scoped_matching_items =
163 TrustStoreMac::FindMatchingCertificatesForMacNormalizedSubject(
164 normalized_name_f.get());
165 EXPECT_THAT(
166 SecCertificateArrayAsDER(scoped_matching_items),
167 UnorderedElementsAreArray(ParsedCertificateListAsDER({f_by_e})));
168 }
169
170 {
171 base::ScopedCFTypeRef<CFArrayRef> scoped_matching_items =
172 TrustStoreMac::FindMatchingCertificatesForMacNormalizedSubject(
173 normalized_name_d.get());
174 EXPECT_THAT(
175 SecCertificateArrayAsDER(scoped_matching_items),
176 UnorderedElementsAreArray(ParsedCertificateListAsDER({d_by_d})));
177 }
178
179 {
180 base::ScopedCFTypeRef<CFArrayRef> scoped_matching_items =
181 TrustStoreMac::FindMatchingCertificatesForMacNormalizedSubject(
182 normalized_name_e.get());
183 EXPECT_THAT(
184 SecCertificateArrayAsDER(scoped_matching_items),
185 UnorderedElementsAreArray(ParsedCertificateListAsDER({e_by_e})));
186 }
187
188 // None of the certs should return any matching TrustAnchors, since the test
189 // certs in the keychain aren't trusted (unless someone manually added and
190 // trusted the test certs on the machine the test is being run on).
191 for (const auto& cert :
192 {a_by_b, b_by_c, b_by_f, c_by_d, c_by_e, f_by_e, d_by_d, e_by_e}) {
193 TrustAnchors matching_anchors;
194 trust_store.FindTrustAnchorsForCert(cert, &matching_anchors);
195 EXPECT_EQ(0u, matching_anchors.size());
196 }
197 }
198
199 // Test against all the certificates in the default keychains. Confirms that
200 // the computed trust value matches that of SecTrustEvaluate.
201 TEST(TrustStoreMacTest, SystemCerts) {
202 // Get the list of all certificates in the user & system keychains.
203 // This may include both trusted and untrusted certificates.
204 //
205 // The output contains zero or more repetitions of:
206 // "SHA-1 hash: <hash>\n<PEM encoded cert>\n"
207 std::string find_certificate_default_search_list_output;
208 ASSERT_TRUE(
209 base::GetAppOutput({"security", "find-certificate", "-a", "-p", "-Z"},
210 &find_certificate_default_search_list_output));
211 // Get the list of all certificates in the system roots keychain.
212 // (Same details as above.)
213 std::string find_certificate_system_roots_output;
214 ASSERT_TRUE(base::GetAppOutput(
215 {"security", "find-certificate", "-a", "-p", "-Z",
216 "/System/Library/Keychains/SystemRootCertificates.keychain"},
217 &find_certificate_system_roots_output));
218
219 TrustStoreMac trust_store(kSecPolicyAppleX509Basic);
220
221 base::ScopedCFTypeRef<SecPolicyRef> sec_policy(SecPolicyCreateBasicX509());
222 ASSERT_TRUE(sec_policy);
223
224 int true_pos = 0;
225 int true_neg = 0;
226 int false_pos = 0;
227 int false_neg = 0;
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 =
258 ParsedCertificate::Create(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 SecCertificateCopyNormalizedSubjectContent(cert_handle, nullptr));
Ryan Sleevi 2017/02/16 22:27:23 Locked (although in practice shouldn't matter for
mattm 2017/02/17 00:04:19 Well, I guess it's a good idea just for the docume
275 if (!mac_normalized_subject) {
276 ADD_FAILURE() << "SecCertificateCopyNormalizedSubjectContent "
277 << hash_text;
278 continue;
279 }
280
281 // Check if this cert is considered a trust anchor by TrustStoreMac.
282 TrustAnchors trust_anchors;
283 trust_store.FindTrustAnchorsByMacNormalizedSubject(mac_normalized_subject,
284 &trust_anchors);
285 bool is_trust_anchor = false;
286 for (const auto& anchor : trust_anchors) {
287 ASSERT_TRUE(anchor->cert());
288 if (anchor->cert()->der_cert() == cert->der_cert())
289 is_trust_anchor = true;
290 }
291
292 // Check if this cert is considered a trust anchor by the OS.
293 base::ScopedCFTypeRef<SecTrustRef> trust;
294 ASSERT_EQ(noErr, SecTrustCreateWithCertificates(cert_handle, sec_policy,
295 trust.InitializeInto()));
296 ASSERT_EQ(noErr,
297 SecTrustSetOptions(trust, kSecTrustOptionLeafIsCA |
298 kSecTrustOptionAllowExpiredRoot));
299
300 SecTrustResultType trust_result;
301 ASSERT_EQ(noErr, SecTrustEvaluate(trust, &trust_result));
302 bool expected_trust_anchor =
303 ((trust_result == kSecTrustResultProceed) ||
304 (trust_result == kSecTrustResultUnspecified)) &&
305 (SecTrustGetCertificateCount(trust) == 1);
306
307 EXPECT_EQ(expected_trust_anchor, is_trust_anchor);
308
309 if (expected_trust_anchor) {
310 if (is_trust_anchor) {
311 true_pos++;
312 } else {
313 false_neg++;
314 }
315 } else {
316 if (is_trust_anchor) {
317 false_pos++;
318 } else {
319 true_neg++;
320 }
321 }
322 }
323
324 LOG(INFO) << "true_pos: " << true_pos;
325 LOG(INFO) << "true_neg: " << true_neg;
326 LOG(INFO) << "false_pos: " << false_pos;
327 LOG(INFO) << "false_neg: " << false_neg;
Ryan Sleevi 2017/02/16 22:27:23 Thought: This will end up logging on every test ru
mattm 2017/02/17 00:04:19 Done.
328 }
329
330 } // namespace net
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698