OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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/multi_threaded_cert_verifier.h" | |
6 | |
7 #include "base/bind.h" | |
8 #include "base/files/file_path.h" | |
9 #include "base/format_macros.h" | |
10 #include "base/strings/stringprintf.h" | |
11 #include "net/base/net_errors.h" | |
12 #include "net/base/net_log.h" | |
13 #include "net/base/test_completion_callback.h" | |
14 #include "net/base/test_data_directory.h" | |
15 #include "net/cert/cert_trust_anchor_provider.h" | |
16 #include "net/cert/cert_verify_proc.h" | |
17 #include "net/cert/cert_verify_result.h" | |
18 #include "net/cert/x509_certificate.h" | |
19 #include "net/test/cert_test_util.h" | |
20 #include "testing/gmock/include/gmock/gmock.h" | |
21 #include "testing/gtest/include/gtest/gtest.h" | |
22 | |
23 using testing::Mock; | |
24 using testing::ReturnRef; | |
25 | |
26 namespace net { | |
27 | |
28 namespace { | |
29 | |
30 void FailTest(int /* result */) { | |
31 FAIL(); | |
32 } | |
33 | |
34 class MockCertVerifyProc : public CertVerifyProc { | |
35 public: | |
36 MockCertVerifyProc() {} | |
37 | |
38 private: | |
39 ~MockCertVerifyProc() override {} | |
40 | |
41 // CertVerifyProc implementation | |
42 bool SupportsAdditionalTrustAnchors() const override { return false; } | |
43 | |
44 int VerifyInternal(X509Certificate* cert, | |
45 const std::string& hostname, | |
46 int flags, | |
47 CRLSet* crl_set, | |
48 const CertificateList& additional_trust_anchors, | |
49 CertVerifyResult* verify_result) override { | |
50 verify_result->Reset(); | |
51 verify_result->verified_cert = cert; | |
52 verify_result->cert_status = CERT_STATUS_COMMON_NAME_INVALID; | |
53 return ERR_CERT_COMMON_NAME_INVALID; | |
54 } | |
55 }; | |
56 | |
57 class MockCertTrustAnchorProvider : public CertTrustAnchorProvider { | |
58 public: | |
59 MockCertTrustAnchorProvider() {} | |
60 virtual ~MockCertTrustAnchorProvider() {} | |
61 | |
62 MOCK_METHOD0(GetAdditionalTrustAnchors, const CertificateList&()); | |
63 }; | |
64 | |
65 } // namespace | |
66 | |
67 class MultiThreadedCertVerifierTest : public ::testing::Test { | |
68 public: | |
69 MultiThreadedCertVerifierTest() : verifier_(new MockCertVerifyProc()) {} | |
70 ~MultiThreadedCertVerifierTest() override {} | |
71 | |
72 protected: | |
73 MultiThreadedCertVerifier verifier_; | |
74 }; | |
75 | |
76 TEST_F(MultiThreadedCertVerifierTest, CacheHit) { | |
77 base::FilePath certs_dir = GetTestCertsDirectory(); | |
78 scoped_refptr<X509Certificate> test_cert( | |
79 ImportCertFromFile(certs_dir, "ok_cert.pem")); | |
80 ASSERT_NE(static_cast<X509Certificate*>(NULL), test_cert.get()); | |
81 | |
82 int error; | |
83 CertVerifyResult verify_result; | |
84 TestCompletionCallback callback; | |
85 CertVerifier::RequestHandle request_handle; | |
86 | |
87 error = verifier_.Verify(test_cert.get(), | |
88 "www.example.com", | |
89 0, | |
90 NULL, | |
91 &verify_result, | |
92 callback.callback(), | |
93 &request_handle, | |
94 BoundNetLog()); | |
95 ASSERT_EQ(ERR_IO_PENDING, error); | |
96 EXPECT_TRUE(request_handle); | |
97 error = callback.WaitForResult(); | |
98 ASSERT_TRUE(IsCertificateError(error)); | |
99 ASSERT_EQ(1u, verifier_.requests()); | |
100 ASSERT_EQ(0u, verifier_.cache_hits()); | |
101 ASSERT_EQ(0u, verifier_.inflight_joins()); | |
102 ASSERT_EQ(1u, verifier_.GetCacheSize()); | |
103 | |
104 error = verifier_.Verify(test_cert.get(), | |
105 "www.example.com", | |
106 0, | |
107 NULL, | |
108 &verify_result, | |
109 callback.callback(), | |
110 &request_handle, | |
111 BoundNetLog()); | |
112 // Synchronous completion. | |
113 ASSERT_NE(ERR_IO_PENDING, error); | |
114 ASSERT_TRUE(IsCertificateError(error)); | |
115 ASSERT_TRUE(request_handle == NULL); | |
116 ASSERT_EQ(2u, verifier_.requests()); | |
117 ASSERT_EQ(1u, verifier_.cache_hits()); | |
118 ASSERT_EQ(0u, verifier_.inflight_joins()); | |
119 ASSERT_EQ(1u, verifier_.GetCacheSize()); | |
120 } | |
121 | |
122 // Tests the same server certificate with different intermediate CA | |
123 // certificates. These should be treated as different certificate chains even | |
124 // though the two X509Certificate objects contain the same server certificate. | |
125 TEST_F(MultiThreadedCertVerifierTest, DifferentCACerts) { | |
126 base::FilePath certs_dir = GetTestCertsDirectory(); | |
127 | |
128 scoped_refptr<X509Certificate> server_cert = | |
129 ImportCertFromFile(certs_dir, "salesforce_com_test.pem"); | |
130 ASSERT_NE(static_cast<X509Certificate*>(NULL), server_cert.get()); | |
131 | |
132 scoped_refptr<X509Certificate> intermediate_cert1 = | |
133 ImportCertFromFile(certs_dir, "verisign_intermediate_ca_2011.pem"); | |
134 ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate_cert1.get()); | |
135 | |
136 scoped_refptr<X509Certificate> intermediate_cert2 = | |
137 ImportCertFromFile(certs_dir, "verisign_intermediate_ca_2016.pem"); | |
138 ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate_cert2.get()); | |
139 | |
140 X509Certificate::OSCertHandles intermediates; | |
141 intermediates.push_back(intermediate_cert1->os_cert_handle()); | |
142 scoped_refptr<X509Certificate> cert_chain1 = | |
143 X509Certificate::CreateFromHandle(server_cert->os_cert_handle(), | |
144 intermediates); | |
145 | |
146 intermediates.clear(); | |
147 intermediates.push_back(intermediate_cert2->os_cert_handle()); | |
148 scoped_refptr<X509Certificate> cert_chain2 = | |
149 X509Certificate::CreateFromHandle(server_cert->os_cert_handle(), | |
150 intermediates); | |
151 | |
152 int error; | |
153 CertVerifyResult verify_result; | |
154 TestCompletionCallback callback; | |
155 CertVerifier::RequestHandle request_handle; | |
156 | |
157 error = verifier_.Verify(cert_chain1.get(), | |
158 "www.example.com", | |
159 0, | |
160 NULL, | |
161 &verify_result, | |
162 callback.callback(), | |
163 &request_handle, | |
164 BoundNetLog()); | |
165 ASSERT_EQ(ERR_IO_PENDING, error); | |
166 EXPECT_TRUE(request_handle); | |
167 error = callback.WaitForResult(); | |
168 ASSERT_TRUE(IsCertificateError(error)); | |
169 ASSERT_EQ(1u, verifier_.requests()); | |
170 ASSERT_EQ(0u, verifier_.cache_hits()); | |
171 ASSERT_EQ(0u, verifier_.inflight_joins()); | |
172 ASSERT_EQ(1u, verifier_.GetCacheSize()); | |
173 | |
174 error = verifier_.Verify(cert_chain2.get(), | |
175 "www.example.com", | |
176 0, | |
177 NULL, | |
178 &verify_result, | |
179 callback.callback(), | |
180 &request_handle, | |
181 BoundNetLog()); | |
182 ASSERT_EQ(ERR_IO_PENDING, error); | |
183 EXPECT_TRUE(request_handle); | |
184 error = callback.WaitForResult(); | |
185 ASSERT_TRUE(IsCertificateError(error)); | |
186 ASSERT_EQ(2u, verifier_.requests()); | |
187 ASSERT_EQ(0u, verifier_.cache_hits()); | |
188 ASSERT_EQ(0u, verifier_.inflight_joins()); | |
189 ASSERT_EQ(2u, verifier_.GetCacheSize()); | |
190 } | |
191 | |
192 // Tests an inflight join. | |
193 TEST_F(MultiThreadedCertVerifierTest, InflightJoin) { | |
194 base::FilePath certs_dir = GetTestCertsDirectory(); | |
195 scoped_refptr<X509Certificate> test_cert( | |
196 ImportCertFromFile(certs_dir, "ok_cert.pem")); | |
197 ASSERT_NE(static_cast<X509Certificate*>(NULL), test_cert.get()); | |
198 | |
199 int error; | |
200 CertVerifyResult verify_result; | |
201 TestCompletionCallback callback; | |
202 CertVerifier::RequestHandle request_handle; | |
203 CertVerifyResult verify_result2; | |
204 TestCompletionCallback callback2; | |
205 CertVerifier::RequestHandle request_handle2; | |
206 | |
207 error = verifier_.Verify(test_cert.get(), | |
208 "www.example.com", | |
209 0, | |
210 NULL, | |
211 &verify_result, | |
212 callback.callback(), | |
213 &request_handle, | |
214 BoundNetLog()); | |
215 ASSERT_EQ(ERR_IO_PENDING, error); | |
216 EXPECT_TRUE(request_handle); | |
217 error = verifier_.Verify(test_cert.get(), | |
218 "www.example.com", | |
219 0, | |
220 NULL, | |
221 &verify_result2, | |
222 callback2.callback(), | |
223 &request_handle2, | |
224 BoundNetLog()); | |
225 EXPECT_EQ(ERR_IO_PENDING, error); | |
226 EXPECT_TRUE(request_handle2 != NULL); | |
227 error = callback.WaitForResult(); | |
228 EXPECT_TRUE(IsCertificateError(error)); | |
229 error = callback2.WaitForResult(); | |
230 ASSERT_TRUE(IsCertificateError(error)); | |
231 ASSERT_EQ(2u, verifier_.requests()); | |
232 ASSERT_EQ(0u, verifier_.cache_hits()); | |
233 ASSERT_EQ(1u, verifier_.inflight_joins()); | |
234 } | |
235 | |
236 // Tests that the callback of a canceled request is never made. | |
237 TEST_F(MultiThreadedCertVerifierTest, CancelRequest) { | |
238 base::FilePath certs_dir = GetTestCertsDirectory(); | |
239 scoped_refptr<X509Certificate> test_cert( | |
240 ImportCertFromFile(certs_dir, "ok_cert.pem")); | |
241 ASSERT_NE(static_cast<X509Certificate*>(NULL), test_cert.get()); | |
242 | |
243 int error; | |
244 CertVerifyResult verify_result; | |
245 CertVerifier::RequestHandle request_handle; | |
246 | |
247 error = verifier_.Verify(test_cert.get(), | |
248 "www.example.com", | |
249 0, | |
250 NULL, | |
251 &verify_result, | |
252 base::Bind(&FailTest), | |
253 &request_handle, | |
254 BoundNetLog()); | |
255 ASSERT_EQ(ERR_IO_PENDING, error); | |
256 ASSERT_TRUE(request_handle != NULL); | |
257 verifier_.CancelRequest(request_handle); | |
258 | |
259 // Issue a few more requests to the worker pool and wait for their | |
260 // completion, so that the task of the canceled request (which runs on a | |
261 // worker thread) is likely to complete by the end of this test. | |
262 TestCompletionCallback callback; | |
263 for (int i = 0; i < 5; ++i) { | |
264 error = verifier_.Verify(test_cert.get(), | |
265 "www2.example.com", | |
266 0, | |
267 NULL, | |
268 &verify_result, | |
269 callback.callback(), | |
270 &request_handle, | |
271 BoundNetLog()); | |
272 ASSERT_EQ(ERR_IO_PENDING, error); | |
273 EXPECT_TRUE(request_handle); | |
274 error = callback.WaitForResult(); | |
275 verifier_.ClearCache(); | |
276 } | |
277 } | |
278 | |
279 // Tests that a canceled request is not leaked. | |
280 #if !defined(LEAK_SANITIZER) | |
281 #define MAYBE_CancelRequestThenQuit CancelRequestThenQuit | |
282 #else | |
283 // See PR303886. LeakSanitizer flags a leak here. | |
284 #define MAYBE_CancelRequestThenQuit DISABLED_CancelRequestThenQuit | |
285 #endif | |
286 TEST_F(MultiThreadedCertVerifierTest, MAYBE_CancelRequestThenQuit) { | |
287 base::FilePath certs_dir = GetTestCertsDirectory(); | |
288 scoped_refptr<X509Certificate> test_cert( | |
289 ImportCertFromFile(certs_dir, "ok_cert.pem")); | |
290 ASSERT_NE(static_cast<X509Certificate*>(NULL), test_cert.get()); | |
291 | |
292 int error; | |
293 CertVerifyResult verify_result; | |
294 TestCompletionCallback callback; | |
295 CertVerifier::RequestHandle request_handle; | |
296 | |
297 error = verifier_.Verify(test_cert.get(), | |
298 "www.example.com", | |
299 0, | |
300 NULL, | |
301 &verify_result, | |
302 callback.callback(), | |
303 &request_handle, | |
304 BoundNetLog()); | |
305 ASSERT_EQ(ERR_IO_PENDING, error); | |
306 EXPECT_TRUE(request_handle); | |
307 verifier_.CancelRequest(request_handle); | |
308 // Destroy |verifier| by going out of scope. | |
309 } | |
310 | |
311 TEST_F(MultiThreadedCertVerifierTest, RequestParamsComparators) { | |
312 SHA1HashValue a_key; | |
313 memset(a_key.data, 'a', sizeof(a_key.data)); | |
314 | |
315 SHA1HashValue z_key; | |
316 memset(z_key.data, 'z', sizeof(z_key.data)); | |
317 | |
318 const CertificateList empty_list; | |
319 CertificateList test_list; | |
320 test_list.push_back( | |
321 ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem")); | |
322 | |
323 struct { | |
324 // Keys to test | |
325 MultiThreadedCertVerifier::RequestParams key1; | |
326 MultiThreadedCertVerifier::RequestParams key2; | |
327 | |
328 // Expectation: | |
329 // -1 means key1 is less than key2 | |
330 // 0 means key1 equals key2 | |
331 // 1 means key1 is greater than key2 | |
332 int expected_result; | |
333 } tests[] = { | |
334 { // Test for basic equivalence. | |
335 MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test", | |
336 0, test_list), | |
337 MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test", | |
338 0, test_list), | |
339 0, | |
340 }, | |
341 { // Test that different certificates but with the same CA and for | |
342 // the same host are different validation keys. | |
343 MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test", | |
344 0, test_list), | |
345 MultiThreadedCertVerifier::RequestParams(z_key, a_key, "www.example.test", | |
346 0, test_list), | |
347 -1, | |
348 }, | |
349 { // Test that the same EE certificate for the same host, but with | |
350 // different chains are different validation keys. | |
351 MultiThreadedCertVerifier::RequestParams(a_key, z_key, "www.example.test", | |
352 0, test_list), | |
353 MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test", | |
354 0, test_list), | |
355 1, | |
356 }, | |
357 { // The same certificate, with the same chain, but for different | |
358 // hosts are different validation keys. | |
359 MultiThreadedCertVerifier::RequestParams(a_key, a_key, | |
360 "www1.example.test", 0, | |
361 test_list), | |
362 MultiThreadedCertVerifier::RequestParams(a_key, a_key, | |
363 "www2.example.test", 0, | |
364 test_list), | |
365 -1, | |
366 }, | |
367 { // The same certificate, chain, and host, but with different flags | |
368 // are different validation keys. | |
369 MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test", | |
370 CertVerifier::VERIFY_EV_CERT, | |
371 test_list), | |
372 MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test", | |
373 0, test_list), | |
374 1, | |
375 }, | |
376 { // Different additional_trust_anchors. | |
377 MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test", | |
378 0, empty_list), | |
379 MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test", | |
380 0, test_list), | |
381 -1, | |
382 }, | |
383 }; | |
384 for (size_t i = 0; i < arraysize(tests); ++i) { | |
385 SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]", i)); | |
386 | |
387 const MultiThreadedCertVerifier::RequestParams& key1 = tests[i].key1; | |
388 const MultiThreadedCertVerifier::RequestParams& key2 = tests[i].key2; | |
389 | |
390 switch (tests[i].expected_result) { | |
391 case -1: | |
392 EXPECT_TRUE(key1 < key2); | |
393 EXPECT_FALSE(key2 < key1); | |
394 break; | |
395 case 0: | |
396 EXPECT_FALSE(key1 < key2); | |
397 EXPECT_FALSE(key2 < key1); | |
398 break; | |
399 case 1: | |
400 EXPECT_FALSE(key1 < key2); | |
401 EXPECT_TRUE(key2 < key1); | |
402 break; | |
403 default: | |
404 FAIL() << "Invalid expectation. Can be only -1, 0, 1"; | |
405 } | |
406 } | |
407 } | |
408 | |
409 TEST_F(MultiThreadedCertVerifierTest, CertTrustAnchorProvider) { | |
410 MockCertTrustAnchorProvider trust_provider; | |
411 verifier_.SetCertTrustAnchorProvider(&trust_provider); | |
412 | |
413 scoped_refptr<X509Certificate> test_cert( | |
414 ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem")); | |
415 ASSERT_TRUE(test_cert.get()); | |
416 | |
417 const CertificateList empty_cert_list; | |
418 CertificateList cert_list; | |
419 cert_list.push_back(test_cert); | |
420 | |
421 // Check that Verify() asks the |trust_provider| for the current list of | |
422 // additional trust anchors. | |
423 int error; | |
424 CertVerifyResult verify_result; | |
425 TestCompletionCallback callback; | |
426 CertVerifier::RequestHandle request_handle; | |
427 EXPECT_CALL(trust_provider, GetAdditionalTrustAnchors()) | |
428 .WillOnce(ReturnRef(empty_cert_list)); | |
429 error = verifier_.Verify(test_cert.get(), | |
430 "www.example.com", | |
431 0, | |
432 NULL, | |
433 &verify_result, | |
434 callback.callback(), | |
435 &request_handle, | |
436 BoundNetLog()); | |
437 Mock::VerifyAndClearExpectations(&trust_provider); | |
438 ASSERT_EQ(ERR_IO_PENDING, error); | |
439 EXPECT_TRUE(request_handle); | |
440 error = callback.WaitForResult(); | |
441 EXPECT_EQ(ERR_CERT_COMMON_NAME_INVALID, error); | |
442 ASSERT_EQ(1u, verifier_.requests()); | |
443 ASSERT_EQ(0u, verifier_.cache_hits()); | |
444 | |
445 // The next Verify() uses the cached result. | |
446 EXPECT_CALL(trust_provider, GetAdditionalTrustAnchors()) | |
447 .WillOnce(ReturnRef(empty_cert_list)); | |
448 error = verifier_.Verify(test_cert.get(), | |
449 "www.example.com", | |
450 0, | |
451 NULL, | |
452 &verify_result, | |
453 callback.callback(), | |
454 &request_handle, | |
455 BoundNetLog()); | |
456 Mock::VerifyAndClearExpectations(&trust_provider); | |
457 EXPECT_EQ(ERR_CERT_COMMON_NAME_INVALID, error); | |
458 EXPECT_FALSE(request_handle); | |
459 ASSERT_EQ(2u, verifier_.requests()); | |
460 ASSERT_EQ(1u, verifier_.cache_hits()); | |
461 | |
462 // Another Verify() for the same certificate but with a different list of | |
463 // trust anchors will not reuse the cache. | |
464 EXPECT_CALL(trust_provider, GetAdditionalTrustAnchors()) | |
465 .WillOnce(ReturnRef(cert_list)); | |
466 error = verifier_.Verify(test_cert.get(), | |
467 "www.example.com", | |
468 0, | |
469 NULL, | |
470 &verify_result, | |
471 callback.callback(), | |
472 &request_handle, | |
473 BoundNetLog()); | |
474 Mock::VerifyAndClearExpectations(&trust_provider); | |
475 ASSERT_EQ(ERR_IO_PENDING, error); | |
476 EXPECT_TRUE(request_handle); | |
477 error = callback.WaitForResult(); | |
478 EXPECT_EQ(ERR_CERT_COMMON_NAME_INVALID, error); | |
479 ASSERT_EQ(3u, verifier_.requests()); | |
480 ASSERT_EQ(1u, verifier_.cache_hits()); | |
481 } | |
482 | |
483 } // namespace net | |
OLD | NEW |