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( | |
409 url_, DEFAULT_PRIORITY, this, NULL); | |
410 // To meet the privacy requirements of incognito mode. | |
411 request_->SetLoadFlags(LOAD_DISABLE_CACHE | LOAD_DO_NOT_SAVE_COOKIES | | |
412 LOAD_DO_NOT_SEND_COOKIES); | |
413 | |
414 if (http_request_method_ == "POST") { | |
415 DCHECK(!upload_content_.empty()); | |
416 DCHECK(!upload_content_type_.empty()); | |
417 | |
418 request_->set_method("POST"); | |
419 extra_request_headers_.SetHeader( | |
420 HttpRequestHeaders::kContentType, upload_content_type_); | |
421 | |
422 scoped_ptr<UploadElementReader> reader(new UploadBytesElementReader( | |
423 upload_content_.data(), upload_content_.size())); | |
424 request_->set_upload( | |
425 ElementsUploadDataStream::CreateWithReader(reader.Pass(), 0)); | |
426 } | |
427 if (!extra_request_headers_.IsEmpty()) | |
428 request_->SetExtraRequestHeaders(extra_request_headers_); | |
429 | |
430 request_->Start(); | |
431 AddRef(); // Release after |request_| deleted. | |
432 } | |
433 | |
434 GURL url_; // The URL we eventually wound up at | |
435 std::string http_request_method_; | |
436 base::TimeDelta timeout_; // The timeout for OCSP | |
437 scoped_ptr<URLRequest> request_; // The actual request this wraps | |
438 scoped_refptr<IOBuffer> buffer_; // Read buffer | |
439 HttpRequestHeaders extra_request_headers_; | |
440 | |
441 // HTTP POST payload. |request_| reads bytes from this. | |
442 std::string upload_content_; | |
443 std::string upload_content_type_; // MIME type of POST payload | |
444 | |
445 int response_code_; // HTTP status code for the request | |
446 std::string response_content_type_; | |
447 scoped_refptr<HttpResponseHeaders> response_headers_; | |
448 std::string data_; // Results of the request | |
449 | |
450 // |lock_| protects |finished_| and |io_loop_|. | |
451 mutable base::Lock lock_; | |
452 base::ConditionVariable cv_; | |
453 | |
454 base::MessageLoop* io_loop_; // Message loop of the IO thread | |
455 bool finished_; | |
456 | |
457 DISALLOW_COPY_AND_ASSIGN(OCSPRequestSession); | |
458 }; | |
459 | |
460 // Concrete class for SEC_HTTP_SERVER_SESSION. | |
461 class OCSPServerSession { | |
462 public: | |
463 OCSPServerSession(const char* host, PRUint16 port) | |
464 : host_and_port_(host, port) {} | |
465 ~OCSPServerSession() {} | |
466 | |
467 OCSPRequestSession* CreateRequest(const char* http_protocol_variant, | |
468 const char* path_and_query_string, | |
469 const char* http_request_method, | |
470 const PRIntervalTime timeout) { | |
471 // We dont' support "https" because we haven't thought about | |
472 // whether it's safe to re-enter this code from talking to an OCSP | |
473 // responder over SSL. | |
474 if (strcmp(http_protocol_variant, "http") != 0) { | |
475 PORT_SetError(PR_NOT_IMPLEMENTED_ERROR); | |
476 return NULL; | |
477 } | |
478 | |
479 std::string url_string(base::StringPrintf( | |
480 "%s://%s%s", | |
481 http_protocol_variant, | |
482 host_and_port_.ToString().c_str(), | |
483 path_and_query_string)); | |
484 VLOG(1) << "URL [" << url_string << "]"; | |
485 GURL url(url_string); | |
486 | |
487 // NSS does not expose public functions to adjust the fetch timeout when | |
488 // using libpkix, so hardcode the upper limit for network fetches. | |
489 base::TimeDelta actual_timeout = std::min( | |
490 base::TimeDelta::FromSeconds(kNetworkFetchTimeoutInSecs), | |
491 base::TimeDelta::FromMilliseconds(PR_IntervalToMilliseconds(timeout))); | |
492 | |
493 return new OCSPRequestSession(url, http_request_method, actual_timeout); | |
494 } | |
495 | |
496 | |
497 private: | |
498 HostPortPair host_and_port_; | |
499 | |
500 DISALLOW_COPY_AND_ASSIGN(OCSPServerSession); | |
501 }; | |
502 | |
503 OCSPIOLoop::OCSPIOLoop() | |
504 : shutdown_(false), | |
505 used_(false), | |
506 io_loop_(NULL) { | |
507 } | |
508 | |
509 OCSPIOLoop::~OCSPIOLoop() { | |
510 // IO thread was already deleted before the singleton is deleted | |
511 // in AtExitManager. | |
512 { | |
513 base::AutoLock autolock(lock_); | |
514 DCHECK(!io_loop_); | |
515 DCHECK(!used_); | |
516 DCHECK(shutdown_); | |
517 } | |
518 | |
519 pthread_mutex_lock(&g_request_context_lock); | |
520 DCHECK(!g_request_context); | |
521 pthread_mutex_unlock(&g_request_context_lock); | |
522 } | |
523 | |
524 void OCSPIOLoop::Shutdown() { | |
525 // Safe to read outside lock since we only write on IO thread anyway. | |
526 DCHECK(thread_checker_.CalledOnValidThread()); | |
527 | |
528 // Prevent the worker thread from trying to access |io_loop_|. | |
529 { | |
530 base::AutoLock autolock(lock_); | |
531 io_loop_ = NULL; | |
532 used_ = false; | |
533 shutdown_ = true; | |
534 } | |
535 | |
536 CancelAllRequests(); | |
537 | |
538 pthread_mutex_lock(&g_request_context_lock); | |
539 g_request_context = NULL; | |
540 pthread_mutex_unlock(&g_request_context_lock); | |
541 } | |
542 | |
543 void OCSPIOLoop::PostTaskToIOLoop( | |
544 const tracked_objects::Location& from_here, const base::Closure& task) { | |
545 base::AutoLock autolock(lock_); | |
546 if (io_loop_) | |
547 io_loop_->PostTask(from_here, task); | |
548 } | |
549 | |
550 void OCSPIOLoop::EnsureIOLoop() { | |
551 base::AutoLock autolock(lock_); | |
552 DCHECK_EQ(base::MessageLoopForIO::current(), io_loop_); | |
553 } | |
554 | |
555 void OCSPIOLoop::AddRequest(OCSPRequestSession* request) { | |
556 DCHECK(!ContainsKey(requests_, request)); | |
557 requests_.insert(request); | |
558 } | |
559 | |
560 void OCSPIOLoop::RemoveRequest(OCSPRequestSession* request) { | |
561 DCHECK(ContainsKey(requests_, request)); | |
562 requests_.erase(request); | |
563 } | |
564 | |
565 void OCSPIOLoop::CancelAllRequests() { | |
566 // CancelURLRequest() always removes the request from the requests_ | |
567 // set synchronously. | |
568 while (!requests_.empty()) | |
569 (*requests_.begin())->CancelURLRequest(); | |
570 } | |
571 | |
572 OCSPNSSInitialization::OCSPNSSInitialization() { | |
573 // NSS calls the functions in the function table to download certificates | |
574 // or CRLs or talk to OCSP responders over HTTP. These functions must | |
575 // set an NSS/NSPR error code when they fail. Otherwise NSS will get the | |
576 // residual error code from an earlier failed function call. | |
577 client_fcn_.version = 1; | |
578 SEC_HttpClientFcnV1Struct *ft = &client_fcn_.fcnTable.ftable1; | |
579 ft->createSessionFcn = OCSPCreateSession; | |
580 ft->keepAliveSessionFcn = OCSPKeepAliveSession; | |
581 ft->freeSessionFcn = OCSPFreeSession; | |
582 ft->createFcn = OCSPCreate; | |
583 ft->setPostDataFcn = OCSPSetPostData; | |
584 ft->addHeaderFcn = OCSPAddHeader; | |
585 ft->trySendAndReceiveFcn = OCSPTrySendAndReceive; | |
586 ft->cancelFcn = NULL; | |
587 ft->freeFcn = OCSPFree; | |
588 SECStatus status = SEC_RegisterDefaultHttpClient(&client_fcn_); | |
589 if (status != SECSuccess) { | |
590 NOTREACHED() << "Error initializing OCSP: " << PR_GetError(); | |
591 } | |
592 | |
593 // Work around NSS bugs 524013 and 564334. NSS incorrectly thinks the | |
594 // CRLs for Network Solutions Certificate Authority have bad signatures, | |
595 // which causes certificates issued by that CA to be reported as revoked. | |
596 // By using OCSP for those certificates, which don't have AIA extensions, | |
597 // we can work around these bugs. See http://crbug.com/41730. | |
598 CERT_StringFromCertFcn old_callback = NULL; | |
599 status = CERT_RegisterAlternateOCSPAIAInfoCallBack( | |
600 GetAlternateOCSPAIAInfo, &old_callback); | |
601 if (status == SECSuccess) { | |
602 DCHECK(!old_callback); | |
603 } else { | |
604 NOTREACHED() << "Error initializing OCSP: " << PR_GetError(); | |
605 } | |
606 } | |
607 | |
608 OCSPNSSInitialization::~OCSPNSSInitialization() { | |
609 SECStatus status = CERT_RegisterAlternateOCSPAIAInfoCallBack(NULL, NULL); | |
610 if (status != SECSuccess) { | |
611 LOG(ERROR) << "Error unregistering OCSP: " << PR_GetError(); | |
612 } | |
613 } | |
614 | |
615 | |
616 // OCSP Http Client functions. | |
617 // Our Http Client functions operate in blocking mode. | |
618 SECStatus OCSPCreateSession(const char* host, PRUint16 portnum, | |
619 SEC_HTTP_SERVER_SESSION* pSession) { | |
620 VLOG(1) << "OCSP create session: host=" << host << " port=" << portnum; | |
621 pthread_mutex_lock(&g_request_context_lock); | |
622 URLRequestContext* request_context = g_request_context; | |
623 pthread_mutex_unlock(&g_request_context_lock); | |
624 if (request_context == NULL) { | |
625 LOG(ERROR) << "No URLRequestContext for NSS HTTP handler. host: " << host; | |
626 // The application failed to call SetURLRequestContextForNSSHttpIO or | |
627 // has already called ShutdownNSSHttpIO, so we can't create and use | |
628 // URLRequest. PR_NOT_IMPLEMENTED_ERROR is not an accurate error | |
629 // code for these error conditions, but is close enough. | |
630 PORT_SetError(PR_NOT_IMPLEMENTED_ERROR); | |
631 return SECFailure; | |
632 } | |
633 *pSession = new OCSPServerSession(host, portnum); | |
634 return SECSuccess; | |
635 } | |
636 | |
637 SECStatus OCSPKeepAliveSession(SEC_HTTP_SERVER_SESSION session, | |
638 PRPollDesc **pPollDesc) { | |
639 VLOG(1) << "OCSP keep alive"; | |
640 if (pPollDesc) | |
641 *pPollDesc = NULL; | |
642 return SECSuccess; | |
643 } | |
644 | |
645 SECStatus OCSPFreeSession(SEC_HTTP_SERVER_SESSION session) { | |
646 VLOG(1) << "OCSP free session"; | |
647 delete reinterpret_cast<OCSPServerSession*>(session); | |
648 return SECSuccess; | |
649 } | |
650 | |
651 SECStatus OCSPCreate(SEC_HTTP_SERVER_SESSION session, | |
652 const char* http_protocol_variant, | |
653 const char* path_and_query_string, | |
654 const char* http_request_method, | |
655 const PRIntervalTime timeout, | |
656 SEC_HTTP_REQUEST_SESSION* pRequest) { | |
657 VLOG(1) << "OCSP create protocol=" << http_protocol_variant | |
658 << " path_and_query=" << path_and_query_string | |
659 << " http_request_method=" << http_request_method | |
660 << " timeout=" << timeout; | |
661 OCSPServerSession* ocsp_session = | |
662 reinterpret_cast<OCSPServerSession*>(session); | |
663 | |
664 OCSPRequestSession* req = ocsp_session->CreateRequest(http_protocol_variant, | |
665 path_and_query_string, | |
666 http_request_method, | |
667 timeout); | |
668 SECStatus rv = SECFailure; | |
669 if (req) { | |
670 req->AddRef(); // Release in OCSPFree(). | |
671 rv = SECSuccess; | |
672 } | |
673 *pRequest = req; | |
674 return rv; | |
675 } | |
676 | |
677 SECStatus OCSPSetPostData(SEC_HTTP_REQUEST_SESSION request, | |
678 const char* http_data, | |
679 const PRUint32 http_data_len, | |
680 const char* http_content_type) { | |
681 VLOG(1) << "OCSP set post data len=" << http_data_len; | |
682 OCSPRequestSession* req = reinterpret_cast<OCSPRequestSession*>(request); | |
683 | |
684 req->SetPostData(http_data, http_data_len, http_content_type); | |
685 return SECSuccess; | |
686 } | |
687 | |
688 SECStatus OCSPAddHeader(SEC_HTTP_REQUEST_SESSION request, | |
689 const char* http_header_name, | |
690 const char* http_header_value) { | |
691 VLOG(1) << "OCSP add header name=" << http_header_name | |
692 << " value=" << http_header_value; | |
693 OCSPRequestSession* req = reinterpret_cast<OCSPRequestSession*>(request); | |
694 | |
695 req->AddHeader(http_header_name, http_header_value); | |
696 return SECSuccess; | |
697 } | |
698 | |
699 // Sets response of |req| in the output parameters. | |
700 // It is helper routine for OCSP trySendAndReceiveFcn. | |
701 // |http_response_data_len| could be used as input parameter. If it has | |
702 // non-zero value, it is considered as maximum size of |http_response_data|. | |
703 SECStatus OCSPSetResponse(OCSPRequestSession* req, | |
704 PRUint16* http_response_code, | |
705 const char** http_response_content_type, | |
706 const char** http_response_headers, | |
707 const char** http_response_data, | |
708 PRUint32* http_response_data_len) { | |
709 DCHECK(req->Finished()); | |
710 const std::string& data = req->http_response_data(); | |
711 if (http_response_data_len && *http_response_data_len) { | |
712 if (*http_response_data_len < data.size()) { | |
713 LOG(ERROR) << "response body too large: " << *http_response_data_len | |
714 << " < " << data.size(); | |
715 *http_response_data_len = data.size(); | |
716 PORT_SetError(SEC_ERROR_BAD_HTTP_RESPONSE); | |
717 return SECFailure; | |
718 } | |
719 } | |
720 VLOG(1) << "OCSP response " | |
721 << " response_code=" << req->http_response_code() | |
722 << " content_type=" << req->http_response_content_type() | |
723 << " header=" << req->http_response_headers() | |
724 << " data_len=" << data.size(); | |
725 if (http_response_code) | |
726 *http_response_code = req->http_response_code(); | |
727 if (http_response_content_type) | |
728 *http_response_content_type = req->http_response_content_type().c_str(); | |
729 if (http_response_headers) | |
730 *http_response_headers = req->http_response_headers().c_str(); | |
731 if (http_response_data) | |
732 *http_response_data = data.data(); | |
733 if (http_response_data_len) | |
734 *http_response_data_len = data.size(); | |
735 return SECSuccess; | |
736 } | |
737 | |
738 SECStatus OCSPTrySendAndReceive(SEC_HTTP_REQUEST_SESSION request, | |
739 PRPollDesc** pPollDesc, | |
740 PRUint16* http_response_code, | |
741 const char** http_response_content_type, | |
742 const char** http_response_headers, | |
743 const char** http_response_data, | |
744 PRUint32* http_response_data_len) { | |
745 if (http_response_data_len) { | |
746 // We must always set an output value, even on failure. The output value 0 | |
747 // means the failure was unrelated to the acceptable response data length. | |
748 *http_response_data_len = 0; | |
749 } | |
750 | |
751 VLOG(1) << "OCSP try send and receive"; | |
752 OCSPRequestSession* req = reinterpret_cast<OCSPRequestSession*>(request); | |
753 // We support blocking mode only. | |
754 if (pPollDesc) | |
755 *pPollDesc = NULL; | |
756 | |
757 if (req->Started() || req->Finished()) { | |
758 // We support blocking mode only, so this function shouldn't be called | |
759 // again when req has stareted or finished. | |
760 NOTREACHED(); | |
761 PORT_SetError(SEC_ERROR_BAD_HTTP_RESPONSE); // Simple approximation. | |
762 return SECFailure; | |
763 } | |
764 | |
765 const base::Time start_time = base::Time::Now(); | |
766 bool request_ok = true; | |
767 req->Start(); | |
768 if (!req->Wait() || req->http_response_code() == static_cast<PRUint16>(-1)) { | |
769 // If the response code is -1, the request failed and there is no response. | |
770 request_ok = false; | |
771 } | |
772 const base::TimeDelta duration = base::Time::Now() - start_time; | |
773 | |
774 // For metrics, we want to know if the request was 'successful' or not. | |
775 // |request_ok| determines if we'll pass the response back to NSS and |ok| | |
776 // keep track of if we think the response was good. | |
777 bool ok = true; | |
778 if (!request_ok || | |
779 (req->http_response_code() >= 400 && req->http_response_code() < 600) || | |
780 req->http_response_data().size() == 0 || | |
781 // 0x30 is the ASN.1 DER encoding of a SEQUENCE. All valid OCSP/CRL/CRT | |
782 // responses must start with this. If we didn't check for this then a | |
783 // captive portal could provide an HTML reply that we would count as a | |
784 // 'success' (although it wouldn't count in NSS, of course). | |
785 req->http_response_data().data()[0] != 0x30) { | |
786 ok = false; | |
787 } | |
788 | |
789 // We want to know if this was: | |
790 // 1) An OCSP request | |
791 // 2) A CRL request | |
792 // 3) A request for a missing intermediate certificate | |
793 // There's no sure way to do this, so we use heuristics like MIME type and | |
794 // URL. | |
795 const char* mime_type = ""; | |
796 if (ok) | |
797 mime_type = req->http_response_content_type().c_str(); | |
798 bool is_ocsp = | |
799 strcasecmp(mime_type, "application/ocsp-response") == 0; | |
800 bool is_crl = strcasecmp(mime_type, "application/x-pkcs7-crl") == 0 || | |
801 strcasecmp(mime_type, "application/x-x509-crl") == 0 || | |
802 strcasecmp(mime_type, "application/pkix-crl") == 0; | |
803 bool is_cert = | |
804 strcasecmp(mime_type, "application/x-x509-ca-cert") == 0 || | |
805 strcasecmp(mime_type, "application/x-x509-server-cert") == 0 || | |
806 strcasecmp(mime_type, "application/pkix-cert") == 0 || | |
807 strcasecmp(mime_type, "application/pkcs7-mime") == 0; | |
808 | |
809 if (!is_cert && !is_crl && !is_ocsp) { | |
810 // We didn't get a hint from the MIME type, so do the best that we can. | |
811 const std::string path = req->url().path(); | |
812 const std::string host = req->url().host(); | |
813 is_crl = strcasestr(path.c_str(), ".crl") != NULL; | |
814 is_cert = strcasestr(path.c_str(), ".crt") != NULL || | |
815 strcasestr(path.c_str(), ".p7c") != NULL || | |
816 strcasestr(path.c_str(), ".cer") != NULL; | |
817 is_ocsp = strcasestr(host.c_str(), "ocsp") != NULL || | |
818 req->http_request_method() == "POST"; | |
819 } | |
820 | |
821 if (is_ocsp) { | |
822 if (ok) { | |
823 UMA_HISTOGRAM_TIMES("Net.OCSPRequestTimeMs", duration); | |
824 UMA_HISTOGRAM_BOOLEAN("Net.OCSPRequestSuccess", true); | |
825 } else { | |
826 UMA_HISTOGRAM_TIMES("Net.OCSPRequestFailedTimeMs", duration); | |
827 UMA_HISTOGRAM_BOOLEAN("Net.OCSPRequestSuccess", false); | |
828 } | |
829 } else if (is_crl) { | |
830 if (ok) { | |
831 UMA_HISTOGRAM_TIMES("Net.CRLRequestTimeMs", duration); | |
832 UMA_HISTOGRAM_BOOLEAN("Net.CRLRequestSuccess", true); | |
833 } else { | |
834 UMA_HISTOGRAM_TIMES("Net.CRLRequestFailedTimeMs", duration); | |
835 UMA_HISTOGRAM_BOOLEAN("Net.CRLRequestSuccess", false); | |
836 } | |
837 } else if (is_cert) { | |
838 if (ok) | |
839 UMA_HISTOGRAM_TIMES("Net.CRTRequestTimeMs", duration); | |
840 } else { | |
841 if (ok) | |
842 UMA_HISTOGRAM_TIMES("Net.UnknownTypeRequestTimeMs", duration); | |
843 } | |
844 | |
845 if (!request_ok) { | |
846 PORT_SetError(SEC_ERROR_BAD_HTTP_RESPONSE); // Simple approximation. | |
847 return SECFailure; | |
848 } | |
849 | |
850 return OCSPSetResponse( | |
851 req, http_response_code, | |
852 http_response_content_type, | |
853 http_response_headers, | |
854 http_response_data, | |
855 http_response_data_len); | |
856 } | |
857 | |
858 SECStatus OCSPFree(SEC_HTTP_REQUEST_SESSION request) { | |
859 VLOG(1) << "OCSP free"; | |
860 OCSPRequestSession* req = reinterpret_cast<OCSPRequestSession*>(request); | |
861 req->Cancel(); | |
862 req->Release(); | |
863 return SECSuccess; | |
864 } | |
865 | |
866 // Data for GetAlternateOCSPAIAInfo. | |
867 | |
868 // CN=Network Solutions Certificate Authority,O=Network Solutions L.L.C.,C=US | |
869 // | |
870 // There are two CAs with this name. Their key IDs are listed next. | |
871 const unsigned char network_solutions_ca_name[] = { | |
872 0x30, 0x62, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, | |
873 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x21, 0x30, 0x1f, 0x06, | |
874 0x03, 0x55, 0x04, 0x0a, 0x13, 0x18, 0x4e, 0x65, 0x74, 0x77, | |
875 0x6f, 0x72, 0x6b, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, | |
876 0x6f, 0x6e, 0x73, 0x20, 0x4c, 0x2e, 0x4c, 0x2e, 0x43, 0x2e, | |
877 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, | |
878 0x27, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x53, | |
879 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x43, | |
880 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, | |
881 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79 | |
882 }; | |
883 const unsigned int network_solutions_ca_name_len = 100; | |
884 | |
885 // This CA is an intermediate CA, subordinate to UTN-USERFirst-Hardware. | |
886 const unsigned char network_solutions_ca_key_id[] = { | |
887 0x3c, 0x41, 0xe2, 0x8f, 0x08, 0x08, 0xa9, 0x4c, 0x25, 0x89, | |
888 0x8d, 0x6d, 0xc5, 0x38, 0xd0, 0xfc, 0x85, 0x8c, 0x62, 0x17 | |
889 }; | |
890 const unsigned int network_solutions_ca_key_id_len = 20; | |
891 | |
892 // This CA is a root CA. It is also cross-certified by | |
893 // UTN-USERFirst-Hardware. | |
894 const unsigned char network_solutions_ca_key_id2[] = { | |
895 0x21, 0x30, 0xc9, 0xfb, 0x00, 0xd7, 0x4e, 0x98, 0xda, 0x87, | |
896 0xaa, 0x2a, 0xd0, 0xa7, 0x2e, 0xb1, 0x40, 0x31, 0xa7, 0x4c | |
897 }; | |
898 const unsigned int network_solutions_ca_key_id2_len = 20; | |
899 | |
900 // An entry in our OCSP responder table. |issuer| and |issuer_key_id| are | |
901 // the key. |ocsp_url| is the value. | |
902 struct OCSPResponderTableEntry { | |
903 SECItem issuer; | |
904 SECItem issuer_key_id; | |
905 const char *ocsp_url; | |
906 }; | |
907 | |
908 const OCSPResponderTableEntry g_ocsp_responder_table[] = { | |
909 { | |
910 { | |
911 siBuffer, | |
912 const_cast<unsigned char*>(network_solutions_ca_name), | |
913 network_solutions_ca_name_len | |
914 }, | |
915 { | |
916 siBuffer, | |
917 const_cast<unsigned char*>(network_solutions_ca_key_id), | |
918 network_solutions_ca_key_id_len | |
919 }, | |
920 "http://ocsp.netsolssl.com" | |
921 }, | |
922 { | |
923 { | |
924 siBuffer, | |
925 const_cast<unsigned char*>(network_solutions_ca_name), | |
926 network_solutions_ca_name_len | |
927 }, | |
928 { | |
929 siBuffer, | |
930 const_cast<unsigned char*>(network_solutions_ca_key_id2), | |
931 network_solutions_ca_key_id2_len | |
932 }, | |
933 "http://ocsp.netsolssl.com" | |
934 } | |
935 }; | |
936 | |
937 char* GetAlternateOCSPAIAInfo(CERTCertificate *cert) { | |
938 if (cert && !cert->isRoot && cert->authKeyID) { | |
939 for (unsigned int i=0; i < arraysize(g_ocsp_responder_table); i++) { | |
940 if (SECITEM_CompareItem(&g_ocsp_responder_table[i].issuer, | |
941 &cert->derIssuer) == SECEqual && | |
942 SECITEM_CompareItem(&g_ocsp_responder_table[i].issuer_key_id, | |
943 &cert->authKeyID->keyID) == SECEqual) { | |
944 return PORT_Strdup(g_ocsp_responder_table[i].ocsp_url); | |
945 } | |
946 } | |
947 } | |
948 | |
949 return NULL; | |
950 } | |
951 | |
952 } // anonymous namespace | |
953 | |
954 void SetMessageLoopForNSSHttpIO() { | |
955 // Must have a MessageLoopForIO. | |
956 DCHECK(base::MessageLoopForIO::current()); | |
957 | |
958 bool used = g_ocsp_io_loop.Get().used(); | |
959 | |
960 // Should not be called when g_ocsp_io_loop has already been used. | |
961 DCHECK(!used); | |
962 } | |
963 | |
964 void EnsureNSSHttpIOInit() { | |
965 g_ocsp_io_loop.Get().StartUsing(); | |
966 g_ocsp_nss_initialization.Get(); | |
967 } | |
968 | |
969 void ShutdownNSSHttpIO() { | |
970 g_ocsp_io_loop.Get().Shutdown(); | |
971 } | |
972 | |
973 void ResetNSSHttpIOForTesting() { | |
974 g_ocsp_io_loop.Get().ReuseForTesting(); | |
975 } | |
976 | |
977 // This function would be called before NSS initialization. | |
978 void SetURLRequestContextForNSSHttpIO(URLRequestContext* request_context) { | |
979 pthread_mutex_lock(&g_request_context_lock); | |
980 if (request_context) { | |
981 DCHECK(!g_request_context); | |
982 } | |
983 g_request_context = request_context; | |
984 pthread_mutex_unlock(&g_request_context_lock); | |
985 } | |
986 | |
987 } // namespace net | |
OLD | NEW |