| 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 |