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/ocsp/nss_ocsp.h" | |
6 | |
7 #include <certt.h> | |
8 #include <certdb.h> | |
9 #include <ocsp.h> | |
10 #include <nspr.h> | |
11 #include <nss.h> | |
12 #include <pthread.h> | |
13 #include <secerr.h> | |
14 | |
15 #include <algorithm> | |
16 #include <string> | |
17 | |
18 #include "base/basictypes.h" | |
19 #include "base/callback.h" | |
20 #include "base/compiler_specific.h" | |
21 #include "base/lazy_instance.h" | |
22 #include "base/logging.h" | |
23 #include "base/memory/scoped_ptr.h" | |
24 #include "base/message_loop/message_loop.h" | |
25 #include "base/metrics/histogram.h" | |
26 #include "base/profiler/scoped_tracker.h" | |
27 #include "base/stl_util.h" | |
28 #include "base/strings/string_util.h" | |
29 #include "base/strings/stringprintf.h" | |
30 #include "base/synchronization/condition_variable.h" | |
31 #include "base/synchronization/lock.h" | |
32 #include "base/threading/thread_checker.h" | |
33 #include "base/time/time.h" | |
34 #include "net/base/elements_upload_data_stream.h" | |
35 #include "net/base/host_port_pair.h" | |
36 #include "net/base/io_buffer.h" | |
37 #include "net/base/load_flags.h" | |
38 #include "net/base/request_priority.h" | |
39 #include "net/base/upload_bytes_element_reader.h" | |
40 #include "net/http/http_request_headers.h" | |
41 #include "net/http/http_response_headers.h" | |
42 #include "net/url_request/redirect_info.h" | |
43 #include "net/url_request/url_request.h" | |
44 #include "net/url_request/url_request_context.h" | |
45 #include "url/gurl.h" | |
46 | |
47 namespace net { | |
48 | |
49 namespace { | |
50 | |
51 // Protects |g_request_context|. | |
52 pthread_mutex_t g_request_context_lock = PTHREAD_MUTEX_INITIALIZER; | |
53 URLRequestContext* g_request_context = NULL; | |
54 | |
55 // The default timeout for network fetches in NSS is 60 seconds. Choose a | |
56 // saner upper limit for OCSP/CRL/AIA fetches. | |
57 const int kNetworkFetchTimeoutInSecs = 15; | |
58 | |
59 class OCSPRequestSession; | |
60 | |
61 class OCSPIOLoop { | |
62 public: | |
63 void StartUsing() { | |
64 base::AutoLock autolock(lock_); | |
65 used_ = true; | |
66 io_loop_ = base::MessageLoopForIO::current(); | |
67 DCHECK(io_loop_); | |
68 } | |
69 | |
70 // Called on IO loop. | |
71 void Shutdown(); | |
72 | |
73 bool used() const { | |
74 base::AutoLock autolock(lock_); | |
75 return used_; | |
76 } | |
77 | |
78 // Called from worker thread. | |
79 void PostTaskToIOLoop(const tracked_objects::Location& from_here, | |
80 const base::Closure& task); | |
81 | |
82 void EnsureIOLoop(); | |
83 | |
84 void AddRequest(OCSPRequestSession* request); | |
85 void RemoveRequest(OCSPRequestSession* request); | |
86 | |
87 // Clears internal state and calls |StartUsing()|. Should be called only in | |
88 // the context of testing. | |
89 void ReuseForTesting() { | |
90 { | |
91 base::AutoLock autolock(lock_); | |
92 DCHECK(base::MessageLoopForIO::current()); | |
93 thread_checker_.DetachFromThread(); | |
94 | |
95 // CalledOnValidThread is the only available API to reassociate | |
96 // thread_checker_ with the current thread. Result ignored intentionally. | |
97 ignore_result(thread_checker_.CalledOnValidThread()); | |
98 shutdown_ = false; | |
99 used_ = false; | |
100 } | |
101 StartUsing(); | |
102 } | |
103 | |
104 private: | |
105 friend struct base::DefaultLazyInstanceTraits<OCSPIOLoop>; | |
106 | |
107 OCSPIOLoop(); | |
108 ~OCSPIOLoop(); | |
109 | |
110 void CancelAllRequests(); | |
111 | |
112 mutable base::Lock lock_; | |
113 bool shutdown_; // Protected by |lock_|. | |
114 std::set<OCSPRequestSession*> requests_; // Protected by |lock_|. | |
115 bool used_; // Protected by |lock_|. | |
116 // This should not be modified after |used_|. | |
117 base::MessageLoopForIO* io_loop_; // Protected by |lock_|. | |
118 base::ThreadChecker thread_checker_; | |
119 | |
120 DISALLOW_COPY_AND_ASSIGN(OCSPIOLoop); | |
121 }; | |
122 | |
123 base::LazyInstance<OCSPIOLoop>::Leaky | |
124 g_ocsp_io_loop = LAZY_INSTANCE_INITIALIZER; | |
125 | |
126 const int kRecvBufferSize = 4096; | |
127 | |
128 // All OCSP handlers should be called in the context of | |
129 // CertVerifier's thread (i.e. worker pool, not on the I/O thread). | |
130 // It supports blocking mode only. | |
131 | |
132 SECStatus OCSPCreateSession(const char* host, PRUint16 portnum, | |
133 SEC_HTTP_SERVER_SESSION* pSession); | |
134 SECStatus OCSPKeepAliveSession(SEC_HTTP_SERVER_SESSION session, | |
135 PRPollDesc **pPollDesc); | |
136 SECStatus OCSPFreeSession(SEC_HTTP_SERVER_SESSION session); | |
137 | |
138 SECStatus OCSPCreate(SEC_HTTP_SERVER_SESSION session, | |
139 const char* http_protocol_variant, | |
140 const char* path_and_query_string, | |
141 const char* http_request_method, | |
142 const PRIntervalTime timeout, | |
143 SEC_HTTP_REQUEST_SESSION* pRequest); | |
144 SECStatus OCSPSetPostData(SEC_HTTP_REQUEST_SESSION request, | |
145 const char* http_data, | |
146 const PRUint32 http_data_len, | |
147 const char* http_content_type); | |
148 SECStatus OCSPAddHeader(SEC_HTTP_REQUEST_SESSION request, | |
149 const char* http_header_name, | |
150 const char* http_header_value); | |
151 SECStatus OCSPTrySendAndReceive(SEC_HTTP_REQUEST_SESSION request, | |
152 PRPollDesc** pPollDesc, | |
153 PRUint16* http_response_code, | |
154 const char** http_response_content_type, | |
155 const char** http_response_headers, | |
156 const char** http_response_data, | |
157 PRUint32* http_response_data_len); | |
158 SECStatus OCSPFree(SEC_HTTP_REQUEST_SESSION request); | |
159 | |
160 char* GetAlternateOCSPAIAInfo(CERTCertificate *cert); | |
161 | |
162 class OCSPNSSInitialization { | |
163 private: | |
164 friend struct base::DefaultLazyInstanceTraits<OCSPNSSInitialization>; | |
165 | |
166 OCSPNSSInitialization(); | |
167 ~OCSPNSSInitialization(); | |
168 | |
169 SEC_HttpClientFcn client_fcn_; | |
170 | |
171 DISALLOW_COPY_AND_ASSIGN(OCSPNSSInitialization); | |
172 }; | |
173 | |
174 base::LazyInstance<OCSPNSSInitialization> g_ocsp_nss_initialization = | |
175 LAZY_INSTANCE_INITIALIZER; | |
176 | |
177 // Concrete class for SEC_HTTP_REQUEST_SESSION. | |
178 // Public methods except virtual methods of URLRequest::Delegate | |
179 // (On* methods) run on certificate verifier thread (worker thread). | |
180 // Virtual methods of URLRequest::Delegate and private methods run | |
181 // on IO thread. | |
182 class OCSPRequestSession | |
183 : public base::RefCountedThreadSafe<OCSPRequestSession>, | |
184 public URLRequest::Delegate { | |
185 public: | |
186 OCSPRequestSession(const GURL& url, | |
187 const char* http_request_method, | |
188 base::TimeDelta timeout) | |
189 : url_(url), | |
190 http_request_method_(http_request_method), | |
191 timeout_(timeout), | |
192 buffer_(new IOBuffer(kRecvBufferSize)), | |
193 response_code_(-1), | |
194 cv_(&lock_), | |
195 io_loop_(NULL), | |
196 finished_(false) {} | |
197 | |
198 void SetPostData(const char* http_data, PRUint32 http_data_len, | |
199 const char* http_content_type) { | |
200 // |upload_content_| should not be modified if |request_| is active. | |
201 DCHECK(!request_); | |
202 upload_content_.assign(http_data, http_data_len); | |
203 upload_content_type_.assign(http_content_type); | |
204 } | |
205 | |
206 void AddHeader(const char* http_header_name, const char* http_header_value) { | |
207 extra_request_headers_.SetHeader(http_header_name, | |
208 http_header_value); | |
209 } | |
210 | |
211 void Start() { | |
212 // At this point, it runs on worker thread. | |
213 // |io_loop_| was initialized to be NULL in constructor, and | |
214 // set only in StartURLRequest, so no need to lock |lock_| here. | |
215 DCHECK(!io_loop_); | |
216 g_ocsp_io_loop.Get().PostTaskToIOLoop( | |
217 FROM_HERE, | |
218 base::Bind(&OCSPRequestSession::StartURLRequest, this)); | |
219 } | |
220 | |
221 bool Started() const { | |
222 return request_.get() != NULL; | |
223 } | |
224 | |
225 void Cancel() { | |
226 // IO thread may set |io_loop_| to NULL, so protect by |lock_|. | |
227 base::AutoLock autolock(lock_); | |
228 CancelLocked(); | |
229 } | |
230 | |
231 bool Finished() const { | |
232 base::AutoLock autolock(lock_); | |
233 return finished_; | |
234 } | |
235 | |
236 bool Wait() { | |
237 base::TimeDelta timeout = timeout_; | |
238 base::AutoLock autolock(lock_); | |
239 while (!finished_) { | |
240 base::TimeTicks last_time = base::TimeTicks::Now(); | |
241 cv_.TimedWait(timeout); | |
242 // Check elapsed time | |
243 base::TimeDelta elapsed_time = base::TimeTicks::Now() - last_time; | |
244 timeout -= elapsed_time; | |
245 if (timeout < base::TimeDelta()) { | |
246 VLOG(1) << "OCSP Timed out"; | |
247 if (!finished_) | |
248 CancelLocked(); | |
249 break; | |
250 } | |
251 } | |
252 return finished_; | |
253 } | |
254 | |
255 const GURL& url() const { | |
256 return url_; | |
257 } | |
258 | |
259 const std::string& http_request_method() const { | |
260 return http_request_method_; | |
261 } | |
262 | |
263 base::TimeDelta timeout() const { | |
264 return timeout_; | |
265 } | |
266 | |
267 PRUint16 http_response_code() const { | |
268 DCHECK(finished_); | |
269 return response_code_; | |
270 } | |
271 | |
272 const std::string& http_response_content_type() const { | |
273 DCHECK(finished_); | |
274 return response_content_type_; | |
275 } | |
276 | |
277 const std::string& http_response_headers() const { | |
278 DCHECK(finished_); | |
279 return response_headers_->raw_headers(); | |
280 } | |
281 | |
282 const std::string& http_response_data() const { | |
283 DCHECK(finished_); | |
284 return data_; | |
285 } | |
286 | |
287 void OnReceivedRedirect(URLRequest* request, | |
288 const RedirectInfo& redirect_info, | |
289 bool* defer_redirect) override { | |
290 DCHECK_EQ(request_.get(), request); | |
291 DCHECK_EQ(base::MessageLoopForIO::current(), io_loop_); | |
292 | |
293 if (!redirect_info.new_url.SchemeIs("http")) { | |
294 // Prevent redirects to non-HTTP schemes, including HTTPS. This matches | |
295 // the initial check in OCSPServerSession::CreateRequest(). | |
296 CancelURLRequest(); | |
297 } | |
298 } | |
299 | |
300 void OnResponseStarted(URLRequest* request) override { | |
301 // TODO(vadimt): Remove ScopedTracker below once crbug.com/423948 is fixed. | |
302 tracked_objects::ScopedTracker tracking_profile( | |
303 FROM_HERE_WITH_EXPLICIT_FUNCTION( | |
304 "423948 OCSPRequestSession::OnResponseStarted")); | |
305 | |
306 DCHECK_EQ(request_.get(), request); | |
307 DCHECK_EQ(base::MessageLoopForIO::current(), io_loop_); | |
308 | |
309 int bytes_read = 0; | |
310 if (request->status().is_success()) { | |
311 response_code_ = request_->GetResponseCode(); | |
312 response_headers_ = request_->response_headers(); | |
313 response_headers_->GetMimeType(&response_content_type_); | |
314 request_->Read(buffer_.get(), kRecvBufferSize, &bytes_read); | |
315 } | |
316 OnReadCompleted(request_.get(), bytes_read); | |
317 } | |
318 | |
319 void OnReadCompleted(URLRequest* request, int bytes_read) override { | |
320 // TODO(vadimt): Remove ScopedTracker below once crbug.com/423948 is fixed. | |
321 tracked_objects::ScopedTracker tracking_profile( | |
322 FROM_HERE_WITH_EXPLICIT_FUNCTION( | |
323 "423948 OCSPRequestSession::OnReadCompleted")); | |
324 | |
325 DCHECK_EQ(request_.get(), request); | |
326 DCHECK_EQ(base::MessageLoopForIO::current(), io_loop_); | |
327 | |
328 do { | |
329 if (!request_->status().is_success() || bytes_read <= 0) | |
330 break; | |
331 data_.append(buffer_->data(), bytes_read); | |
332 } while (request_->Read(buffer_.get(), kRecvBufferSize, &bytes_read)); | |
333 | |
334 if (!request_->status().is_io_pending()) { | |
335 request_.reset(); | |
336 g_ocsp_io_loop.Get().RemoveRequest(this); | |
337 { | |
338 base::AutoLock autolock(lock_); | |
339 finished_ = true; | |
340 io_loop_ = NULL; | |
341 } | |
342 cv_.Signal(); | |
343 Release(); // Balanced with StartURLRequest(). | |
344 } | |
345 } | |
346 | |
347 // Must be called on the IO loop thread. | |
348 void CancelURLRequest() { | |
349 #ifndef NDEBUG | |
350 { | |
351 base::AutoLock autolock(lock_); | |
352 if (io_loop_) | |
353 DCHECK_EQ(base::MessageLoopForIO::current(), io_loop_); | |
354 } | |
355 #endif | |
356 if (request_) { | |
357 request_.reset(); | |
358 g_ocsp_io_loop.Get().RemoveRequest(this); | |
359 { | |
360 base::AutoLock autolock(lock_); | |
361 finished_ = true; | |
362 io_loop_ = NULL; | |
363 } | |
364 cv_.Signal(); | |
365 Release(); // Balanced with StartURLRequest(). | |
366 } | |
367 } | |
368 | |
369 private: | |
370 friend class base::RefCountedThreadSafe<OCSPRequestSession>; | |
371 | |
372 ~OCSPRequestSession() override { | |
373 // When this destructor is called, there should be only one thread that has | |
374 // a reference to this object, and so that thread doesn't need to lock | |
375 // |lock_| here. | |
376 DCHECK(!request_); | |
377 DCHECK(!io_loop_); | |
378 } | |
379 | |
380 // Must call this method while holding |lock_|. | |
381 void CancelLocked() { | |
382 lock_.AssertAcquired(); | |
383 if (io_loop_) { | |
384 io_loop_->PostTask( | |
385 FROM_HERE, | |
386 base::Bind(&OCSPRequestSession::CancelURLRequest, this)); | |
387 } | |
388 } | |
389 | |
390 // Runs on |g_ocsp_io_loop|'s IO loop. | |
391 void StartURLRequest() { | |
392 DCHECK(!request_); | |
393 | |
394 pthread_mutex_lock(&g_request_context_lock); | |
395 URLRequestContext* url_request_context = g_request_context; | |
396 pthread_mutex_unlock(&g_request_context_lock); | |
397 | |
398 if (url_request_context == NULL) | |
399 return; | |
400 | |
401 { | |
402 base::AutoLock autolock(lock_); | |
403 DCHECK(!io_loop_); | |
404 io_loop_ = base::MessageLoopForIO::current(); | |
405 g_ocsp_io_loop.Get().AddRequest(this); | |
406 } | |
407 | |
408 request_ = url_request_context->CreateRequest(url_, DEFAULT_PRIORITY, this); | |
409 // To meet the privacy requirements of incognito mode. | |
410 request_->SetLoadFlags(LOAD_DISABLE_CACHE | LOAD_DO_NOT_SAVE_COOKIES | | |
411 LOAD_DO_NOT_SEND_COOKIES); | |
412 | |
413 if (http_request_method_ == "POST") { | |
414 DCHECK(!upload_content_.empty()); | |
415 DCHECK(!upload_content_type_.empty()); | |
416 | |
417 request_->set_method("POST"); | |
418 extra_request_headers_.SetHeader( | |
419 HttpRequestHeaders::kContentType, upload_content_type_); | |
420 | |
421 scoped_ptr<UploadElementReader> reader(new UploadBytesElementReader( | |
422 upload_content_.data(), upload_content_.size())); | |
423 request_->set_upload( | |
424 ElementsUploadDataStream::CreateWithReader(reader.Pass(), 0)); | |
425 } | |
426 if (!extra_request_headers_.IsEmpty()) | |
427 request_->SetExtraRequestHeaders(extra_request_headers_); | |
428 | |
429 request_->Start(); | |
430 AddRef(); // Release after |request_| deleted. | |
431 } | |
432 | |
433 GURL url_; // The URL we eventually wound up at | |
434 std::string http_request_method_; | |
435 base::TimeDelta timeout_; // The timeout for OCSP | |
436 scoped_ptr<URLRequest> request_; // The actual request this wraps | |
437 scoped_refptr<IOBuffer> buffer_; // Read buffer | |
438 HttpRequestHeaders extra_request_headers_; | |
439 | |
440 // HTTP POST payload. |request_| reads bytes from this. | |
441 std::string upload_content_; | |
442 std::string upload_content_type_; // MIME type of POST payload | |
443 | |
444 int response_code_; // HTTP status code for the request | |
445 std::string response_content_type_; | |
446 scoped_refptr<HttpResponseHeaders> response_headers_; | |
447 std::string data_; // Results of the request | |
448 | |
449 // |lock_| protects |finished_| and |io_loop_|. | |
450 mutable base::Lock lock_; | |
451 base::ConditionVariable cv_; | |
452 | |
453 base::MessageLoop* io_loop_; // Message loop of the IO thread | |
454 bool finished_; | |
455 | |
456 DISALLOW_COPY_AND_ASSIGN(OCSPRequestSession); | |
457 }; | |
458 | |
459 // Concrete class for SEC_HTTP_SERVER_SESSION. | |
460 class OCSPServerSession { | |
461 public: | |
462 OCSPServerSession(const char* host, PRUint16 port) | |
463 : host_and_port_(host, port) {} | |
464 ~OCSPServerSession() {} | |
465 | |
466 OCSPRequestSession* CreateRequest(const char* http_protocol_variant, | |
467 const char* path_and_query_string, | |
468 const char* http_request_method, | |
469 const PRIntervalTime timeout) { | |
470 // We dont' support "https" because we haven't thought about | |
471 // whether it's safe to re-enter this code from talking to an OCSP | |
472 // responder over SSL. | |
473 if (strcmp(http_protocol_variant, "http") != 0) { | |
474 PORT_SetError(PR_NOT_IMPLEMENTED_ERROR); | |
475 return NULL; | |
476 } | |
477 | |
478 std::string url_string(base::StringPrintf( | |
479 "%s://%s%s", | |
480 http_protocol_variant, | |
481 host_and_port_.ToString().c_str(), | |
482 path_and_query_string)); | |
483 VLOG(1) << "URL [" << url_string << "]"; | |
484 GURL url(url_string); | |
485 | |
486 // NSS does not expose public functions to adjust the fetch timeout when | |
487 // using libpkix, so hardcode the upper limit for network fetches. | |
488 base::TimeDelta actual_timeout = std::min( | |
489 base::TimeDelta::FromSeconds(kNetworkFetchTimeoutInSecs), | |
490 base::TimeDelta::FromMilliseconds(PR_IntervalToMilliseconds(timeout))); | |
491 | |
492 return new OCSPRequestSession(url, http_request_method, actual_timeout); | |
493 } | |
494 | |
495 | |
496 private: | |
497 HostPortPair host_and_port_; | |
498 | |
499 DISALLOW_COPY_AND_ASSIGN(OCSPServerSession); | |
500 }; | |
501 | |
502 OCSPIOLoop::OCSPIOLoop() | |
503 : shutdown_(false), | |
504 used_(false), | |
505 io_loop_(NULL) { | |
506 } | |
507 | |
508 OCSPIOLoop::~OCSPIOLoop() { | |
509 // IO thread was already deleted before the singleton is deleted | |
510 // in AtExitManager. | |
511 { | |
512 base::AutoLock autolock(lock_); | |
513 DCHECK(!io_loop_); | |
514 DCHECK(!used_); | |
515 DCHECK(shutdown_); | |
516 } | |
517 | |
518 pthread_mutex_lock(&g_request_context_lock); | |
519 DCHECK(!g_request_context); | |
520 pthread_mutex_unlock(&g_request_context_lock); | |
521 } | |
522 | |
523 void OCSPIOLoop::Shutdown() { | |
524 // Safe to read outside lock since we only write on IO thread anyway. | |
525 DCHECK(thread_checker_.CalledOnValidThread()); | |
526 | |
527 // Prevent the worker thread from trying to access |io_loop_|. | |
528 { | |
529 base::AutoLock autolock(lock_); | |
530 io_loop_ = NULL; | |
531 used_ = false; | |
532 shutdown_ = true; | |
533 } | |
534 | |
535 CancelAllRequests(); | |
536 | |
537 pthread_mutex_lock(&g_request_context_lock); | |
538 g_request_context = NULL; | |
539 pthread_mutex_unlock(&g_request_context_lock); | |
540 } | |
541 | |
542 void OCSPIOLoop::PostTaskToIOLoop( | |
543 const tracked_objects::Location& from_here, const base::Closure& task) { | |
544 base::AutoLock autolock(lock_); | |
545 if (io_loop_) | |
546 io_loop_->PostTask(from_here, task); | |
547 } | |
548 | |
549 void OCSPIOLoop::EnsureIOLoop() { | |
550 base::AutoLock autolock(lock_); | |
551 DCHECK_EQ(base::MessageLoopForIO::current(), io_loop_); | |
552 } | |
553 | |
554 void OCSPIOLoop::AddRequest(OCSPRequestSession* request) { | |
555 DCHECK(!ContainsKey(requests_, request)); | |
556 requests_.insert(request); | |
557 } | |
558 | |
559 void OCSPIOLoop::RemoveRequest(OCSPRequestSession* request) { | |
560 DCHECK(ContainsKey(requests_, request)); | |
561 requests_.erase(request); | |
562 } | |
563 | |
564 void OCSPIOLoop::CancelAllRequests() { | |
565 // CancelURLRequest() always removes the request from the requests_ | |
566 // set synchronously. | |
567 while (!requests_.empty()) | |
568 (*requests_.begin())->CancelURLRequest(); | |
569 } | |
570 | |
571 OCSPNSSInitialization::OCSPNSSInitialization() { | |
572 // NSS calls the functions in the function table to download certificates | |
573 // or CRLs or talk to OCSP responders over HTTP. These functions must | |
574 // set an NSS/NSPR error code when they fail. Otherwise NSS will get the | |
575 // residual error code from an earlier failed function call. | |
576 client_fcn_.version = 1; | |
577 SEC_HttpClientFcnV1Struct *ft = &client_fcn_.fcnTable.ftable1; | |
578 ft->createSessionFcn = OCSPCreateSession; | |
579 ft->keepAliveSessionFcn = OCSPKeepAliveSession; | |
580 ft->freeSessionFcn = OCSPFreeSession; | |
581 ft->createFcn = OCSPCreate; | |
582 ft->setPostDataFcn = OCSPSetPostData; | |
583 ft->addHeaderFcn = OCSPAddHeader; | |
584 ft->trySendAndReceiveFcn = OCSPTrySendAndReceive; | |
585 ft->cancelFcn = NULL; | |
586 ft->freeFcn = OCSPFree; | |
587 SECStatus status = SEC_RegisterDefaultHttpClient(&client_fcn_); | |
588 if (status != SECSuccess) { | |
589 NOTREACHED() << "Error initializing OCSP: " << PR_GetError(); | |
590 } | |
591 | |
592 // Work around NSS bugs 524013 and 564334. NSS incorrectly thinks the | |
593 // CRLs for Network Solutions Certificate Authority have bad signatures, | |
594 // which causes certificates issued by that CA to be reported as revoked. | |
595 // By using OCSP for those certificates, which don't have AIA extensions, | |
596 // we can work around these bugs. See http://crbug.com/41730. | |
597 CERT_StringFromCertFcn old_callback = NULL; | |
598 status = CERT_RegisterAlternateOCSPAIAInfoCallBack( | |
599 GetAlternateOCSPAIAInfo, &old_callback); | |
600 if (status == SECSuccess) { | |
601 DCHECK(!old_callback); | |
602 } else { | |
603 NOTREACHED() << "Error initializing OCSP: " << PR_GetError(); | |
604 } | |
605 } | |
606 | |
607 OCSPNSSInitialization::~OCSPNSSInitialization() { | |
608 SECStatus status = CERT_RegisterAlternateOCSPAIAInfoCallBack(NULL, NULL); | |
609 if (status != SECSuccess) { | |
610 LOG(ERROR) << "Error unregistering OCSP: " << PR_GetError(); | |
611 } | |
612 } | |
613 | |
614 | |
615 // OCSP Http Client functions. | |
616 // Our Http Client functions operate in blocking mode. | |
617 SECStatus OCSPCreateSession(const char* host, PRUint16 portnum, | |
618 SEC_HTTP_SERVER_SESSION* pSession) { | |
619 VLOG(1) << "OCSP create session: host=" << host << " port=" << portnum; | |
620 pthread_mutex_lock(&g_request_context_lock); | |
621 URLRequestContext* request_context = g_request_context; | |
622 pthread_mutex_unlock(&g_request_context_lock); | |
623 if (request_context == NULL) { | |
624 LOG(ERROR) << "No URLRequestContext for NSS HTTP handler. host: " << host; | |
625 // The application failed to call SetURLRequestContextForNSSHttpIO or | |
626 // has already called ShutdownNSSHttpIO, so we can't create and use | |
627 // URLRequest. PR_NOT_IMPLEMENTED_ERROR is not an accurate error | |
628 // code for these error conditions, but is close enough. | |
629 PORT_SetError(PR_NOT_IMPLEMENTED_ERROR); | |
630 return SECFailure; | |
631 } | |
632 *pSession = new OCSPServerSession(host, portnum); | |
633 return SECSuccess; | |
634 } | |
635 | |
636 SECStatus OCSPKeepAliveSession(SEC_HTTP_SERVER_SESSION session, | |
637 PRPollDesc **pPollDesc) { | |
638 VLOG(1) << "OCSP keep alive"; | |
639 if (pPollDesc) | |
640 *pPollDesc = NULL; | |
641 return SECSuccess; | |
642 } | |
643 | |
644 SECStatus OCSPFreeSession(SEC_HTTP_SERVER_SESSION session) { | |
645 VLOG(1) << "OCSP free session"; | |
646 delete reinterpret_cast<OCSPServerSession*>(session); | |
647 return SECSuccess; | |
648 } | |
649 | |
650 SECStatus OCSPCreate(SEC_HTTP_SERVER_SESSION session, | |
651 const char* http_protocol_variant, | |
652 const char* path_and_query_string, | |
653 const char* http_request_method, | |
654 const PRIntervalTime timeout, | |
655 SEC_HTTP_REQUEST_SESSION* pRequest) { | |
656 VLOG(1) << "OCSP create protocol=" << http_protocol_variant | |
657 << " path_and_query=" << path_and_query_string | |
658 << " http_request_method=" << http_request_method | |
659 << " timeout=" << timeout; | |
660 OCSPServerSession* ocsp_session = | |
661 reinterpret_cast<OCSPServerSession*>(session); | |
662 | |
663 OCSPRequestSession* req = ocsp_session->CreateRequest(http_protocol_variant, | |
664 path_and_query_string, | |
665 http_request_method, | |
666 timeout); | |
667 SECStatus rv = SECFailure; | |
668 if (req) { | |
669 req->AddRef(); // Release in OCSPFree(). | |
670 rv = SECSuccess; | |
671 } | |
672 *pRequest = req; | |
673 return rv; | |
674 } | |
675 | |
676 SECStatus OCSPSetPostData(SEC_HTTP_REQUEST_SESSION request, | |
677 const char* http_data, | |
678 const PRUint32 http_data_len, | |
679 const char* http_content_type) { | |
680 VLOG(1) << "OCSP set post data len=" << http_data_len; | |
681 OCSPRequestSession* req = reinterpret_cast<OCSPRequestSession*>(request); | |
682 | |
683 req->SetPostData(http_data, http_data_len, http_content_type); | |
684 return SECSuccess; | |
685 } | |
686 | |
687 SECStatus OCSPAddHeader(SEC_HTTP_REQUEST_SESSION request, | |
688 const char* http_header_name, | |
689 const char* http_header_value) { | |
690 VLOG(1) << "OCSP add header name=" << http_header_name | |
691 << " value=" << http_header_value; | |
692 OCSPRequestSession* req = reinterpret_cast<OCSPRequestSession*>(request); | |
693 | |
694 req->AddHeader(http_header_name, http_header_value); | |
695 return SECSuccess; | |
696 } | |
697 | |
698 // Sets response of |req| in the output parameters. | |
699 // It is helper routine for OCSP trySendAndReceiveFcn. | |
700 // |http_response_data_len| could be used as input parameter. If it has | |
701 // non-zero value, it is considered as maximum size of |http_response_data|. | |
702 SECStatus OCSPSetResponse(OCSPRequestSession* req, | |
703 PRUint16* http_response_code, | |
704 const char** http_response_content_type, | |
705 const char** http_response_headers, | |
706 const char** http_response_data, | |
707 PRUint32* http_response_data_len) { | |
708 DCHECK(req->Finished()); | |
709 const std::string& data = req->http_response_data(); | |
710 if (http_response_data_len && *http_response_data_len) { | |
711 if (*http_response_data_len < data.size()) { | |
712 LOG(ERROR) << "response body too large: " << *http_response_data_len | |
713 << " < " << data.size(); | |
714 *http_response_data_len = data.size(); | |
715 PORT_SetError(SEC_ERROR_BAD_HTTP_RESPONSE); | |
716 return SECFailure; | |
717 } | |
718 } | |
719 VLOG(1) << "OCSP response " | |
720 << " response_code=" << req->http_response_code() | |
721 << " content_type=" << req->http_response_content_type() | |
722 << " header=" << req->http_response_headers() | |
723 << " data_len=" << data.size(); | |
724 if (http_response_code) | |
725 *http_response_code = req->http_response_code(); | |
726 if (http_response_content_type) | |
727 *http_response_content_type = req->http_response_content_type().c_str(); | |
728 if (http_response_headers) | |
729 *http_response_headers = req->http_response_headers().c_str(); | |
730 if (http_response_data) | |
731 *http_response_data = data.data(); | |
732 if (http_response_data_len) | |
733 *http_response_data_len = data.size(); | |
734 return SECSuccess; | |
735 } | |
736 | |
737 SECStatus OCSPTrySendAndReceive(SEC_HTTP_REQUEST_SESSION request, | |
738 PRPollDesc** pPollDesc, | |
739 PRUint16* http_response_code, | |
740 const char** http_response_content_type, | |
741 const char** http_response_headers, | |
742 const char** http_response_data, | |
743 PRUint32* http_response_data_len) { | |
744 if (http_response_data_len) { | |
745 // We must always set an output value, even on failure. The output value 0 | |
746 // means the failure was unrelated to the acceptable response data length. | |
747 *http_response_data_len = 0; | |
748 } | |
749 | |
750 VLOG(1) << "OCSP try send and receive"; | |
751 OCSPRequestSession* req = reinterpret_cast<OCSPRequestSession*>(request); | |
752 // We support blocking mode only. | |
753 if (pPollDesc) | |
754 *pPollDesc = NULL; | |
755 | |
756 if (req->Started() || req->Finished()) { | |
757 // We support blocking mode only, so this function shouldn't be called | |
758 // again when req has stareted or finished. | |
759 NOTREACHED(); | |
760 PORT_SetError(SEC_ERROR_BAD_HTTP_RESPONSE); // Simple approximation. | |
761 return SECFailure; | |
762 } | |
763 | |
764 const base::Time start_time = base::Time::Now(); | |
765 bool request_ok = true; | |
766 req->Start(); | |
767 if (!req->Wait() || req->http_response_code() == static_cast<PRUint16>(-1)) { | |
768 // If the response code is -1, the request failed and there is no response. | |
769 request_ok = false; | |
770 } | |
771 const base::TimeDelta duration = base::Time::Now() - start_time; | |
772 | |
773 // For metrics, we want to know if the request was 'successful' or not. | |
774 // |request_ok| determines if we'll pass the response back to NSS and |ok| | |
775 // keep track of if we think the response was good. | |
776 bool ok = true; | |
777 if (!request_ok || | |
778 (req->http_response_code() >= 400 && req->http_response_code() < 600) || | |
779 req->http_response_data().size() == 0 || | |
780 // 0x30 is the ASN.1 DER encoding of a SEQUENCE. All valid OCSP/CRL/CRT | |
781 // responses must start with this. If we didn't check for this then a | |
782 // captive portal could provide an HTML reply that we would count as a | |
783 // 'success' (although it wouldn't count in NSS, of course). | |
784 req->http_response_data().data()[0] != 0x30) { | |
785 ok = false; | |
786 } | |
787 | |
788 // We want to know if this was: | |
789 // 1) An OCSP request | |
790 // 2) A CRL request | |
791 // 3) A request for a missing intermediate certificate | |
792 // There's no sure way to do this, so we use heuristics like MIME type and | |
793 // URL. | |
794 const char* mime_type = ""; | |
795 if (ok) | |
796 mime_type = req->http_response_content_type().c_str(); | |
797 bool is_ocsp = | |
798 strcasecmp(mime_type, "application/ocsp-response") == 0; | |
799 bool is_crl = strcasecmp(mime_type, "application/x-pkcs7-crl") == 0 || | |
800 strcasecmp(mime_type, "application/x-x509-crl") == 0 || | |
801 strcasecmp(mime_type, "application/pkix-crl") == 0; | |
802 bool is_cert = | |
803 strcasecmp(mime_type, "application/x-x509-ca-cert") == 0 || | |
804 strcasecmp(mime_type, "application/x-x509-server-cert") == 0 || | |
805 strcasecmp(mime_type, "application/pkix-cert") == 0 || | |
806 strcasecmp(mime_type, "application/pkcs7-mime") == 0; | |
807 | |
808 if (!is_cert && !is_crl && !is_ocsp) { | |
809 // We didn't get a hint from the MIME type, so do the best that we can. | |
810 const std::string path = req->url().path(); | |
811 const std::string host = req->url().host(); | |
812 is_crl = strcasestr(path.c_str(), ".crl") != NULL; | |
813 is_cert = strcasestr(path.c_str(), ".crt") != NULL || | |
814 strcasestr(path.c_str(), ".p7c") != NULL || | |
815 strcasestr(path.c_str(), ".cer") != NULL; | |
816 is_ocsp = strcasestr(host.c_str(), "ocsp") != NULL || | |
817 req->http_request_method() == "POST"; | |
818 } | |
819 | |
820 if (is_ocsp) { | |
821 if (ok) { | |
822 UMA_HISTOGRAM_TIMES("Net.OCSPRequestTimeMs", duration); | |
823 UMA_HISTOGRAM_BOOLEAN("Net.OCSPRequestSuccess", true); | |
824 } else { | |
825 UMA_HISTOGRAM_TIMES("Net.OCSPRequestFailedTimeMs", duration); | |
826 UMA_HISTOGRAM_BOOLEAN("Net.OCSPRequestSuccess", false); | |
827 } | |
828 } else if (is_crl) { | |
829 if (ok) { | |
830 UMA_HISTOGRAM_TIMES("Net.CRLRequestTimeMs", duration); | |
831 UMA_HISTOGRAM_BOOLEAN("Net.CRLRequestSuccess", true); | |
832 } else { | |
833 UMA_HISTOGRAM_TIMES("Net.CRLRequestFailedTimeMs", duration); | |
834 UMA_HISTOGRAM_BOOLEAN("Net.CRLRequestSuccess", false); | |
835 } | |
836 } else if (is_cert) { | |
837 if (ok) | |
838 UMA_HISTOGRAM_TIMES("Net.CRTRequestTimeMs", duration); | |
839 } else { | |
840 if (ok) | |
841 UMA_HISTOGRAM_TIMES("Net.UnknownTypeRequestTimeMs", duration); | |
842 } | |
843 | |
844 if (!request_ok) { | |
845 PORT_SetError(SEC_ERROR_BAD_HTTP_RESPONSE); // Simple approximation. | |
846 return SECFailure; | |
847 } | |
848 | |
849 return OCSPSetResponse( | |
850 req, http_response_code, | |
851 http_response_content_type, | |
852 http_response_headers, | |
853 http_response_data, | |
854 http_response_data_len); | |
855 } | |
856 | |
857 SECStatus OCSPFree(SEC_HTTP_REQUEST_SESSION request) { | |
858 VLOG(1) << "OCSP free"; | |
859 OCSPRequestSession* req = reinterpret_cast<OCSPRequestSession*>(request); | |
860 req->Cancel(); | |
861 req->Release(); | |
862 return SECSuccess; | |
863 } | |
864 | |
865 // Data for GetAlternateOCSPAIAInfo. | |
866 | |
867 // CN=Network Solutions Certificate Authority,O=Network Solutions L.L.C.,C=US | |
868 // | |
869 // There are two CAs with this name. Their key IDs are listed next. | |
870 const unsigned char network_solutions_ca_name[] = { | |
871 0x30, 0x62, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, | |
872 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x21, 0x30, 0x1f, 0x06, | |
873 0x03, 0x55, 0x04, 0x0a, 0x13, 0x18, 0x4e, 0x65, 0x74, 0x77, | |
874 0x6f, 0x72, 0x6b, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, | |
875 0x6f, 0x6e, 0x73, 0x20, 0x4c, 0x2e, 0x4c, 0x2e, 0x43, 0x2e, | |
876 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, | |
877 0x27, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x53, | |
878 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x43, | |
879 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, | |
880 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79 | |
881 }; | |
882 const unsigned int network_solutions_ca_name_len = 100; | |
883 | |
884 // This CA is an intermediate CA, subordinate to UTN-USERFirst-Hardware. | |
885 const unsigned char network_solutions_ca_key_id[] = { | |
886 0x3c, 0x41, 0xe2, 0x8f, 0x08, 0x08, 0xa9, 0x4c, 0x25, 0x89, | |
887 0x8d, 0x6d, 0xc5, 0x38, 0xd0, 0xfc, 0x85, 0x8c, 0x62, 0x17 | |
888 }; | |
889 const unsigned int network_solutions_ca_key_id_len = 20; | |
890 | |
891 // This CA is a root CA. It is also cross-certified by | |
892 // UTN-USERFirst-Hardware. | |
893 const unsigned char network_solutions_ca_key_id2[] = { | |
894 0x21, 0x30, 0xc9, 0xfb, 0x00, 0xd7, 0x4e, 0x98, 0xda, 0x87, | |
895 0xaa, 0x2a, 0xd0, 0xa7, 0x2e, 0xb1, 0x40, 0x31, 0xa7, 0x4c | |
896 }; | |
897 const unsigned int network_solutions_ca_key_id2_len = 20; | |
898 | |
899 // An entry in our OCSP responder table. |issuer| and |issuer_key_id| are | |
900 // the key. |ocsp_url| is the value. | |
901 struct OCSPResponderTableEntry { | |
902 SECItem issuer; | |
903 SECItem issuer_key_id; | |
904 const char *ocsp_url; | |
905 }; | |
906 | |
907 const OCSPResponderTableEntry g_ocsp_responder_table[] = { | |
908 { | |
909 { | |
910 siBuffer, | |
911 const_cast<unsigned char*>(network_solutions_ca_name), | |
912 network_solutions_ca_name_len | |
913 }, | |
914 { | |
915 siBuffer, | |
916 const_cast<unsigned char*>(network_solutions_ca_key_id), | |
917 network_solutions_ca_key_id_len | |
918 }, | |
919 "http://ocsp.netsolssl.com" | |
920 }, | |
921 { | |
922 { | |
923 siBuffer, | |
924 const_cast<unsigned char*>(network_solutions_ca_name), | |
925 network_solutions_ca_name_len | |
926 }, | |
927 { | |
928 siBuffer, | |
929 const_cast<unsigned char*>(network_solutions_ca_key_id2), | |
930 network_solutions_ca_key_id2_len | |
931 }, | |
932 "http://ocsp.netsolssl.com" | |
933 } | |
934 }; | |
935 | |
936 char* GetAlternateOCSPAIAInfo(CERTCertificate *cert) { | |
937 if (cert && !cert->isRoot && cert->authKeyID) { | |
938 for (unsigned int i=0; i < arraysize(g_ocsp_responder_table); i++) { | |
939 if (SECITEM_CompareItem(&g_ocsp_responder_table[i].issuer, | |
940 &cert->derIssuer) == SECEqual && | |
941 SECITEM_CompareItem(&g_ocsp_responder_table[i].issuer_key_id, | |
942 &cert->authKeyID->keyID) == SECEqual) { | |
943 return PORT_Strdup(g_ocsp_responder_table[i].ocsp_url); | |
944 } | |
945 } | |
946 } | |
947 | |
948 return NULL; | |
949 } | |
950 | |
951 } // anonymous namespace | |
952 | |
953 void SetMessageLoopForNSSHttpIO() { | |
954 // Must have a MessageLoopForIO. | |
955 DCHECK(base::MessageLoopForIO::current()); | |
956 | |
957 bool used = g_ocsp_io_loop.Get().used(); | |
958 | |
959 // Should not be called when g_ocsp_io_loop has already been used. | |
960 DCHECK(!used); | |
961 } | |
962 | |
963 void EnsureNSSHttpIOInit() { | |
964 g_ocsp_io_loop.Get().StartUsing(); | |
965 g_ocsp_nss_initialization.Get(); | |
966 } | |
967 | |
968 void ShutdownNSSHttpIO() { | |
969 g_ocsp_io_loop.Get().Shutdown(); | |
970 } | |
971 | |
972 void ResetNSSHttpIOForTesting() { | |
973 g_ocsp_io_loop.Get().ReuseForTesting(); | |
974 } | |
975 | |
976 // This function would be called before NSS initialization. | |
977 void SetURLRequestContextForNSSHttpIO(URLRequestContext* request_context) { | |
978 pthread_mutex_lock(&g_request_context_lock); | |
979 if (request_context) { | |
980 DCHECK(!g_request_context); | |
981 } | |
982 g_request_context = request_context; | |
983 pthread_mutex_unlock(&g_request_context_lock); | |
984 } | |
985 | |
986 } // namespace net | |
OLD | NEW |