// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/ocsp/nss_ocsp.h" #include <certt.h> #include <certdb.h> #include <ocsp.h> #include <nspr.h> #include <nss.h> #include <pthread.h> #include <secerr.h> #include <algorithm> #include <string> #include "base/basictypes.h" #include "base/callback.h" #include "base/compiler_specific.h" #include "base/lazy_instance.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" #include "base/metrics/histogram.h" #include "base/stl_util.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/synchronization/condition_variable.h" #include "base/synchronization/lock.h" #include "base/threading/thread_checker.h" #include "base/time/time.h" #include "net/base/host_port_pair.h" #include "net/base/io_buffer.h" #include "net/base/load_flags.h" #include "net/base/request_priority.h" #include "net/base/upload_bytes_element_reader.h" #include "net/base/upload_data_stream.h" #include "net/http/http_request_headers.h" #include "net/http/http_response_headers.h" #include "net/url_request/redirect_info.h" #include "net/url_request/url_request.h" #include "net/url_request/url_request_context.h" #include "url/gurl.h" namespace net { namespace { // Protects |g_request_context|. pthread_mutex_t g_request_context_lock = PTHREAD_MUTEX_INITIALIZER; URLRequestContext* g_request_context = NULL; // The default timeout for network fetches in NSS is 60 seconds. Choose a // saner upper limit for OCSP/CRL/AIA fetches. const int kNetworkFetchTimeoutInSecs = 15; class OCSPRequestSession; class OCSPIOLoop { public: void StartUsing() { base::AutoLock autolock(lock_); used_ = true; io_loop_ = base::MessageLoopForIO::current(); DCHECK(io_loop_); } // Called on IO loop. void Shutdown(); bool used() const { base::AutoLock autolock(lock_); return used_; } // Called from worker thread. void PostTaskToIOLoop(const tracked_objects::Location& from_here, const base::Closure& task); void EnsureIOLoop(); void AddRequest(OCSPRequestSession* request); void RemoveRequest(OCSPRequestSession* request); // Clears internal state and calls |StartUsing()|. Should be called only in // the context of testing. void ReuseForTesting() { { base::AutoLock autolock(lock_); DCHECK(base::MessageLoopForIO::current()); thread_checker_.DetachFromThread(); // CalledOnValidThread is the only available API to reassociate // thread_checker_ with the current thread. Result ignored intentionally. ignore_result(thread_checker_.CalledOnValidThread()); shutdown_ = false; used_ = false; } StartUsing(); } private: friend struct base::DefaultLazyInstanceTraits<OCSPIOLoop>; OCSPIOLoop(); ~OCSPIOLoop(); void CancelAllRequests(); mutable base::Lock lock_; bool shutdown_; // Protected by |lock_|. std::set<OCSPRequestSession*> requests_; // Protected by |lock_|. bool used_; // Protected by |lock_|. // This should not be modified after |used_|. base::MessageLoopForIO* io_loop_; // Protected by |lock_|. base::ThreadChecker thread_checker_; DISALLOW_COPY_AND_ASSIGN(OCSPIOLoop); }; base::LazyInstance<OCSPIOLoop>::Leaky g_ocsp_io_loop = LAZY_INSTANCE_INITIALIZER; const int kRecvBufferSize = 4096; // All OCSP handlers should be called in the context of // CertVerifier's thread (i.e. worker pool, not on the I/O thread). // It supports blocking mode only. SECStatus OCSPCreateSession(const char* host, PRUint16 portnum, SEC_HTTP_SERVER_SESSION* pSession); SECStatus OCSPKeepAliveSession(SEC_HTTP_SERVER_SESSION session, PRPollDesc **pPollDesc); SECStatus OCSPFreeSession(SEC_HTTP_SERVER_SESSION session); SECStatus OCSPCreate(SEC_HTTP_SERVER_SESSION session, const char* http_protocol_variant, const char* path_and_query_string, const char* http_request_method, const PRIntervalTime timeout, SEC_HTTP_REQUEST_SESSION* pRequest); SECStatus OCSPSetPostData(SEC_HTTP_REQUEST_SESSION request, const char* http_data, const PRUint32 http_data_len, const char* http_content_type); SECStatus OCSPAddHeader(SEC_HTTP_REQUEST_SESSION request, const char* http_header_name, const char* http_header_value); SECStatus OCSPTrySendAndReceive(SEC_HTTP_REQUEST_SESSION request, PRPollDesc** pPollDesc, PRUint16* http_response_code, const char** http_response_content_type, const char** http_response_headers, const char** http_response_data, PRUint32* http_response_data_len); SECStatus OCSPFree(SEC_HTTP_REQUEST_SESSION request); char* GetAlternateOCSPAIAInfo(CERTCertificate *cert); class OCSPNSSInitialization { private: friend struct base::DefaultLazyInstanceTraits<OCSPNSSInitialization>; OCSPNSSInitialization(); ~OCSPNSSInitialization(); SEC_HttpClientFcn client_fcn_; DISALLOW_COPY_AND_ASSIGN(OCSPNSSInitialization); }; base::LazyInstance<OCSPNSSInitialization> g_ocsp_nss_initialization = LAZY_INSTANCE_INITIALIZER; // Concrete class for SEC_HTTP_REQUEST_SESSION. // Public methods except virtual methods of URLRequest::Delegate // (On* methods) run on certificate verifier thread (worker thread). // Virtual methods of URLRequest::Delegate and private methods run // on IO thread. class OCSPRequestSession : public base::RefCountedThreadSafe<OCSPRequestSession>, public URLRequest::Delegate { public: OCSPRequestSession(const GURL& url, const char* http_request_method, base::TimeDelta timeout) : url_(url), http_request_method_(http_request_method), timeout_(timeout), buffer_(new IOBuffer(kRecvBufferSize)), response_code_(-1), cv_(&lock_), io_loop_(NULL), finished_(false) {} void SetPostData(const char* http_data, PRUint32 http_data_len, const char* http_content_type) { // |upload_content_| should not be modified if |request_| is active. DCHECK(!request_); upload_content_.assign(http_data, http_data_len); upload_content_type_.assign(http_content_type); } void AddHeader(const char* http_header_name, const char* http_header_value) { extra_request_headers_.SetHeader(http_header_name, http_header_value); } void Start() { // At this point, it runs on worker thread. // |io_loop_| was initialized to be NULL in constructor, and // set only in StartURLRequest, so no need to lock |lock_| here. DCHECK(!io_loop_); g_ocsp_io_loop.Get().PostTaskToIOLoop( FROM_HERE, base::Bind(&OCSPRequestSession::StartURLRequest, this)); } bool Started() const { return request_.get() != NULL; } void Cancel() { // IO thread may set |io_loop_| to NULL, so protect by |lock_|. base::AutoLock autolock(lock_); CancelLocked(); } bool Finished() const { base::AutoLock autolock(lock_); return finished_; } bool Wait() { base::TimeDelta timeout = timeout_; base::AutoLock autolock(lock_); while (!finished_) { base::TimeTicks last_time = base::TimeTicks::Now(); cv_.TimedWait(timeout); // Check elapsed time base::TimeDelta elapsed_time = base::TimeTicks::Now() - last_time; timeout -= elapsed_time; if (timeout < base::TimeDelta()) { VLOG(1) << "OCSP Timed out"; if (!finished_) CancelLocked(); break; } } return finished_; } const GURL& url() const { return url_; } const std::string& http_request_method() const { return http_request_method_; } base::TimeDelta timeout() const { return timeout_; } PRUint16 http_response_code() const { DCHECK(finished_); return response_code_; } const std::string& http_response_content_type() const { DCHECK(finished_); return response_content_type_; } const std::string& http_response_headers() const { DCHECK(finished_); return response_headers_->raw_headers(); } const std::string& http_response_data() const { DCHECK(finished_); return data_; } virtual void OnReceivedRedirect(URLRequest* request, const RedirectInfo& redirect_info, bool* defer_redirect) OVERRIDE { DCHECK_EQ(request_.get(), request); DCHECK_EQ(base::MessageLoopForIO::current(), io_loop_); if (!redirect_info.new_url.SchemeIs("http")) { // Prevent redirects to non-HTTP schemes, including HTTPS. This matches // the initial check in OCSPServerSession::CreateRequest(). CancelURLRequest(); } } virtual void OnResponseStarted(URLRequest* request) OVERRIDE { DCHECK_EQ(request_.get(), request); DCHECK_EQ(base::MessageLoopForIO::current(), io_loop_); int bytes_read = 0; if (request->status().is_success()) { response_code_ = request_->GetResponseCode(); response_headers_ = request_->response_headers(); response_headers_->GetMimeType(&response_content_type_); request_->Read(buffer_.get(), kRecvBufferSize, &bytes_read); } OnReadCompleted(request_.get(), bytes_read); } virtual void OnReadCompleted(URLRequest* request, int bytes_read) OVERRIDE { DCHECK_EQ(request_.get(), request); DCHECK_EQ(base::MessageLoopForIO::current(), io_loop_); do { if (!request_->status().is_success() || bytes_read <= 0) break; data_.append(buffer_->data(), bytes_read); } while (request_->Read(buffer_.get(), kRecvBufferSize, &bytes_read)); if (!request_->status().is_io_pending()) { request_.reset(); g_ocsp_io_loop.Get().RemoveRequest(this); { base::AutoLock autolock(lock_); finished_ = true; io_loop_ = NULL; } cv_.Signal(); Release(); // Balanced with StartURLRequest(). } } // Must be called on the IO loop thread. void CancelURLRequest() { #ifndef NDEBUG { base::AutoLock autolock(lock_); if (io_loop_) DCHECK_EQ(base::MessageLoopForIO::current(), io_loop_); } #endif if (request_) { request_.reset(); g_ocsp_io_loop.Get().RemoveRequest(this); { base::AutoLock autolock(lock_); finished_ = true; io_loop_ = NULL; } cv_.Signal(); Release(); // Balanced with StartURLRequest(). } } private: friend class base::RefCountedThreadSafe<OCSPRequestSession>; virtual ~OCSPRequestSession() { // When this destructor is called, there should be only one thread that has // a reference to this object, and so that thread doesn't need to lock // |lock_| here. DCHECK(!request_); DCHECK(!io_loop_); } // Must call this method while holding |lock_|. void CancelLocked() { lock_.AssertAcquired(); if (io_loop_) { io_loop_->PostTask( FROM_HERE, base::Bind(&OCSPRequestSession::CancelURLRequest, this)); } } // Runs on |g_ocsp_io_loop|'s IO loop. void StartURLRequest() { DCHECK(!request_); pthread_mutex_lock(&g_request_context_lock); URLRequestContext* url_request_context = g_request_context; pthread_mutex_unlock(&g_request_context_lock); if (url_request_context == NULL) return; { base::AutoLock autolock(lock_); DCHECK(!io_loop_); io_loop_ = base::MessageLoopForIO::current(); g_ocsp_io_loop.Get().AddRequest(this); } request_ = url_request_context->CreateRequest( url_, DEFAULT_PRIORITY, this, NULL); // To meet the privacy requirements of incognito mode. request_->SetLoadFlags(LOAD_DISABLE_CACHE | LOAD_DO_NOT_SAVE_COOKIES | LOAD_DO_NOT_SEND_COOKIES); if (http_request_method_ == "POST") { DCHECK(!upload_content_.empty()); DCHECK(!upload_content_type_.empty()); request_->set_method("POST"); extra_request_headers_.SetHeader( HttpRequestHeaders::kContentType, upload_content_type_); scoped_ptr<UploadElementReader> reader(new UploadBytesElementReader( upload_content_.data(), upload_content_.size())); request_->set_upload(make_scoped_ptr( UploadDataStream::CreateWithReader(reader.Pass(), 0))); } if (!extra_request_headers_.IsEmpty()) request_->SetExtraRequestHeaders(extra_request_headers_); request_->Start(); AddRef(); // Release after |request_| deleted. } GURL url_; // The URL we eventually wound up at std::string http_request_method_; base::TimeDelta timeout_; // The timeout for OCSP scoped_ptr<URLRequest> request_; // The actual request this wraps scoped_refptr<IOBuffer> buffer_; // Read buffer HttpRequestHeaders extra_request_headers_; // HTTP POST payload. |request_| reads bytes from this. std::string upload_content_; std::string upload_content_type_; // MIME type of POST payload int response_code_; // HTTP status code for the request std::string response_content_type_; scoped_refptr<HttpResponseHeaders> response_headers_; std::string data_; // Results of the request // |lock_| protects |finished_| and |io_loop_|. mutable base::Lock lock_; base::ConditionVariable cv_; base::MessageLoop* io_loop_; // Message loop of the IO thread bool finished_; DISALLOW_COPY_AND_ASSIGN(OCSPRequestSession); }; // Concrete class for SEC_HTTP_SERVER_SESSION. class OCSPServerSession { public: OCSPServerSession(const char* host, PRUint16 port) : host_and_port_(host, port) {} ~OCSPServerSession() {} OCSPRequestSession* CreateRequest(const char* http_protocol_variant, const char* path_and_query_string, const char* http_request_method, const PRIntervalTime timeout) { // We dont' support "https" because we haven't thought about // whether it's safe to re-enter this code from talking to an OCSP // responder over SSL. if (strcmp(http_protocol_variant, "http") != 0) { PORT_SetError(PR_NOT_IMPLEMENTED_ERROR); return NULL; } std::string url_string(base::StringPrintf( "%s://%s%s", http_protocol_variant, host_and_port_.ToString().c_str(), path_and_query_string)); VLOG(1) << "URL [" << url_string << "]"; GURL url(url_string); // NSS does not expose public functions to adjust the fetch timeout when // using libpkix, so hardcode the upper limit for network fetches. base::TimeDelta actual_timeout = std::min( base::TimeDelta::FromSeconds(kNetworkFetchTimeoutInSecs), base::TimeDelta::FromMilliseconds(PR_IntervalToMilliseconds(timeout))); return new OCSPRequestSession(url, http_request_method, actual_timeout); } private: HostPortPair host_and_port_; DISALLOW_COPY_AND_ASSIGN(OCSPServerSession); }; OCSPIOLoop::OCSPIOLoop() : shutdown_(false), used_(false), io_loop_(NULL) { } OCSPIOLoop::~OCSPIOLoop() { // IO thread was already deleted before the singleton is deleted // in AtExitManager. { base::AutoLock autolock(lock_); DCHECK(!io_loop_); DCHECK(!used_); DCHECK(shutdown_); } pthread_mutex_lock(&g_request_context_lock); DCHECK(!g_request_context); pthread_mutex_unlock(&g_request_context_lock); } void OCSPIOLoop::Shutdown() { // Safe to read outside lock since we only write on IO thread anyway. DCHECK(thread_checker_.CalledOnValidThread()); // Prevent the worker thread from trying to access |io_loop_|. { base::AutoLock autolock(lock_); io_loop_ = NULL; used_ = false; shutdown_ = true; } CancelAllRequests(); pthread_mutex_lock(&g_request_context_lock); g_request_context = NULL; pthread_mutex_unlock(&g_request_context_lock); } void OCSPIOLoop::PostTaskToIOLoop( const tracked_objects::Location& from_here, const base::Closure& task) { base::AutoLock autolock(lock_); if (io_loop_) io_loop_->PostTask(from_here, task); } void OCSPIOLoop::EnsureIOLoop() { base::AutoLock autolock(lock_); DCHECK_EQ(base::MessageLoopForIO::current(), io_loop_); } void OCSPIOLoop::AddRequest(OCSPRequestSession* request) { DCHECK(!ContainsKey(requests_, request)); requests_.insert(request); } void OCSPIOLoop::RemoveRequest(OCSPRequestSession* request) { DCHECK(ContainsKey(requests_, request)); requests_.erase(request); } void OCSPIOLoop::CancelAllRequests() { // CancelURLRequest() always removes the request from the requests_ // set synchronously. while (!requests_.empty()) (*requests_.begin())->CancelURLRequest(); } OCSPNSSInitialization::OCSPNSSInitialization() { // NSS calls the functions in the function table to download certificates // or CRLs or talk to OCSP responders over HTTP. These functions must // set an NSS/NSPR error code when they fail. Otherwise NSS will get the // residual error code from an earlier failed function call. client_fcn_.version = 1; SEC_HttpClientFcnV1Struct *ft = &client_fcn_.fcnTable.ftable1; ft->createSessionFcn = OCSPCreateSession; ft->keepAliveSessionFcn = OCSPKeepAliveSession; ft->freeSessionFcn = OCSPFreeSession; ft->createFcn = OCSPCreate; ft->setPostDataFcn = OCSPSetPostData; ft->addHeaderFcn = OCSPAddHeader; ft->trySendAndReceiveFcn = OCSPTrySendAndReceive; ft->cancelFcn = NULL; ft->freeFcn = OCSPFree; SECStatus status = SEC_RegisterDefaultHttpClient(&client_fcn_); if (status != SECSuccess) { NOTREACHED() << "Error initializing OCSP: " << PR_GetError(); } // Work around NSS bugs 524013 and 564334. NSS incorrectly thinks the // CRLs for Network Solutions Certificate Authority have bad signatures, // which causes certificates issued by that CA to be reported as revoked. // By using OCSP for those certificates, which don't have AIA extensions, // we can work around these bugs. See http://crbug.com/41730. CERT_StringFromCertFcn old_callback = NULL; status = CERT_RegisterAlternateOCSPAIAInfoCallBack( GetAlternateOCSPAIAInfo, &old_callback); if (status == SECSuccess) { DCHECK(!old_callback); } else { NOTREACHED() << "Error initializing OCSP: " << PR_GetError(); } } OCSPNSSInitialization::~OCSPNSSInitialization() { SECStatus status = CERT_RegisterAlternateOCSPAIAInfoCallBack(NULL, NULL); if (status != SECSuccess) { LOG(ERROR) << "Error unregistering OCSP: " << PR_GetError(); } } // OCSP Http Client functions. // Our Http Client functions operate in blocking mode. SECStatus OCSPCreateSession(const char* host, PRUint16 portnum, SEC_HTTP_SERVER_SESSION* pSession) { VLOG(1) << "OCSP create session: host=" << host << " port=" << portnum; pthread_mutex_lock(&g_request_context_lock); URLRequestContext* request_context = g_request_context; pthread_mutex_unlock(&g_request_context_lock); if (request_context == NULL) { LOG(ERROR) << "No URLRequestContext for NSS HTTP handler. host: " << host; // The application failed to call SetURLRequestContextForNSSHttpIO or // has already called ShutdownNSSHttpIO, so we can't create and use // URLRequest. PR_NOT_IMPLEMENTED_ERROR is not an accurate error // code for these error conditions, but is close enough. PORT_SetError(PR_NOT_IMPLEMENTED_ERROR); return SECFailure; } *pSession = new OCSPServerSession(host, portnum); return SECSuccess; } SECStatus OCSPKeepAliveSession(SEC_HTTP_SERVER_SESSION session, PRPollDesc **pPollDesc) { VLOG(1) << "OCSP keep alive"; if (pPollDesc) *pPollDesc = NULL; return SECSuccess; } SECStatus OCSPFreeSession(SEC_HTTP_SERVER_SESSION session) { VLOG(1) << "OCSP free session"; delete reinterpret_cast<OCSPServerSession*>(session); return SECSuccess; } SECStatus OCSPCreate(SEC_HTTP_SERVER_SESSION session, const char* http_protocol_variant, const char* path_and_query_string, const char* http_request_method, const PRIntervalTime timeout, SEC_HTTP_REQUEST_SESSION* pRequest) { VLOG(1) << "OCSP create protocol=" << http_protocol_variant << " path_and_query=" << path_and_query_string << " http_request_method=" << http_request_method << " timeout=" << timeout; OCSPServerSession* ocsp_session = reinterpret_cast<OCSPServerSession*>(session); OCSPRequestSession* req = ocsp_session->CreateRequest(http_protocol_variant, path_and_query_string, http_request_method, timeout); SECStatus rv = SECFailure; if (req) { req->AddRef(); // Release in OCSPFree(). rv = SECSuccess; } *pRequest = req; return rv; } SECStatus OCSPSetPostData(SEC_HTTP_REQUEST_SESSION request, const char* http_data, const PRUint32 http_data_len, const char* http_content_type) { VLOG(1) << "OCSP set post data len=" << http_data_len; OCSPRequestSession* req = reinterpret_cast<OCSPRequestSession*>(request); req->SetPostData(http_data, http_data_len, http_content_type); return SECSuccess; } SECStatus OCSPAddHeader(SEC_HTTP_REQUEST_SESSION request, const char* http_header_name, const char* http_header_value) { VLOG(1) << "OCSP add header name=" << http_header_name << " value=" << http_header_value; OCSPRequestSession* req = reinterpret_cast<OCSPRequestSession*>(request); req->AddHeader(http_header_name, http_header_value); return SECSuccess; } // Sets response of |req| in the output parameters. // It is helper routine for OCSP trySendAndReceiveFcn. // |http_response_data_len| could be used as input parameter. If it has // non-zero value, it is considered as maximum size of |http_response_data|. SECStatus OCSPSetResponse(OCSPRequestSession* req, PRUint16* http_response_code, const char** http_response_content_type, const char** http_response_headers, const char** http_response_data, PRUint32* http_response_data_len) { DCHECK(req->Finished()); const std::string& data = req->http_response_data(); if (http_response_data_len && *http_response_data_len) { if (*http_response_data_len < data.size()) { LOG(ERROR) << "response body too large: " << *http_response_data_len << " < " << data.size(); *http_response_data_len = data.size(); PORT_SetError(SEC_ERROR_BAD_HTTP_RESPONSE); return SECFailure; } } VLOG(1) << "OCSP response " << " response_code=" << req->http_response_code() << " content_type=" << req->http_response_content_type() << " header=" << req->http_response_headers() << " data_len=" << data.size(); if (http_response_code) *http_response_code = req->http_response_code(); if (http_response_content_type) *http_response_content_type = req->http_response_content_type().c_str(); if (http_response_headers) *http_response_headers = req->http_response_headers().c_str(); if (http_response_data) *http_response_data = data.data(); if (http_response_data_len) *http_response_data_len = data.size(); return SECSuccess; } SECStatus OCSPTrySendAndReceive(SEC_HTTP_REQUEST_SESSION request, PRPollDesc** pPollDesc, PRUint16* http_response_code, const char** http_response_content_type, const char** http_response_headers, const char** http_response_data, PRUint32* http_response_data_len) { if (http_response_data_len) { // We must always set an output value, even on failure. The output value 0 // means the failure was unrelated to the acceptable response data length. *http_response_data_len = 0; } VLOG(1) << "OCSP try send and receive"; OCSPRequestSession* req = reinterpret_cast<OCSPRequestSession*>(request); // We support blocking mode only. if (pPollDesc) *pPollDesc = NULL; if (req->Started() || req->Finished()) { // We support blocking mode only, so this function shouldn't be called // again when req has stareted or finished. NOTREACHED(); PORT_SetError(SEC_ERROR_BAD_HTTP_RESPONSE); // Simple approximation. return SECFailure; } const base::Time start_time = base::Time::Now(); bool request_ok = true; req->Start(); if (!req->Wait() || req->http_response_code() == static_cast<PRUint16>(-1)) { // If the response code is -1, the request failed and there is no response. request_ok = false; } const base::TimeDelta duration = base::Time::Now() - start_time; // For metrics, we want to know if the request was 'successful' or not. // |request_ok| determines if we'll pass the response back to NSS and |ok| // keep track of if we think the response was good. bool ok = true; if (!request_ok || (req->http_response_code() >= 400 && req->http_response_code() < 600) || req->http_response_data().size() == 0 || // 0x30 is the ASN.1 DER encoding of a SEQUENCE. All valid OCSP/CRL/CRT // responses must start with this. If we didn't check for this then a // captive portal could provide an HTML reply that we would count as a // 'success' (although it wouldn't count in NSS, of course). req->http_response_data().data()[0] != 0x30) { ok = false; } // We want to know if this was: // 1) An OCSP request // 2) A CRL request // 3) A request for a missing intermediate certificate // There's no sure way to do this, so we use heuristics like MIME type and // URL. const char* mime_type = ""; if (ok) mime_type = req->http_response_content_type().c_str(); bool is_ocsp = strcasecmp(mime_type, "application/ocsp-response") == 0; bool is_crl = strcasecmp(mime_type, "application/x-pkcs7-crl") == 0 || strcasecmp(mime_type, "application/x-x509-crl") == 0 || strcasecmp(mime_type, "application/pkix-crl") == 0; bool is_cert = strcasecmp(mime_type, "application/x-x509-ca-cert") == 0 || strcasecmp(mime_type, "application/x-x509-server-cert") == 0 || strcasecmp(mime_type, "application/pkix-cert") == 0 || strcasecmp(mime_type, "application/pkcs7-mime") == 0; if (!is_cert && !is_crl && !is_ocsp) { // We didn't get a hint from the MIME type, so do the best that we can. const std::string path = req->url().path(); const std::string host = req->url().host(); is_crl = strcasestr(path.c_str(), ".crl") != NULL; is_cert = strcasestr(path.c_str(), ".crt") != NULL || strcasestr(path.c_str(), ".p7c") != NULL || strcasestr(path.c_str(), ".cer") != NULL; is_ocsp = strcasestr(host.c_str(), "ocsp") != NULL || req->http_request_method() == "POST"; } if (is_ocsp) { if (ok) { UMA_HISTOGRAM_TIMES("Net.OCSPRequestTimeMs", duration); UMA_HISTOGRAM_BOOLEAN("Net.OCSPRequestSuccess", true); } else { UMA_HISTOGRAM_TIMES("Net.OCSPRequestFailedTimeMs", duration); UMA_HISTOGRAM_BOOLEAN("Net.OCSPRequestSuccess", false); } } else if (is_crl) { if (ok) { UMA_HISTOGRAM_TIMES("Net.CRLRequestTimeMs", duration); UMA_HISTOGRAM_BOOLEAN("Net.CRLRequestSuccess", true); } else { UMA_HISTOGRAM_TIMES("Net.CRLRequestFailedTimeMs", duration); UMA_HISTOGRAM_BOOLEAN("Net.CRLRequestSuccess", false); } } else if (is_cert) { if (ok) UMA_HISTOGRAM_TIMES("Net.CRTRequestTimeMs", duration); } else { if (ok) UMA_HISTOGRAM_TIMES("Net.UnknownTypeRequestTimeMs", duration); } if (!request_ok) { PORT_SetError(SEC_ERROR_BAD_HTTP_RESPONSE); // Simple approximation. return SECFailure; } return OCSPSetResponse( req, http_response_code, http_response_content_type, http_response_headers, http_response_data, http_response_data_len); } SECStatus OCSPFree(SEC_HTTP_REQUEST_SESSION request) { VLOG(1) << "OCSP free"; OCSPRequestSession* req = reinterpret_cast<OCSPRequestSession*>(request); req->Cancel(); req->Release(); return SECSuccess; } // Data for GetAlternateOCSPAIAInfo. // CN=Network Solutions Certificate Authority,O=Network Solutions L.L.C.,C=US // // There are two CAs with this name. Their key IDs are listed next. const unsigned char network_solutions_ca_name[] = { 0x30, 0x62, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x18, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x4c, 0x2e, 0x4c, 0x2e, 0x43, 0x2e, 0x31, 0x30, 0x30, 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x27, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x53, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79 }; const unsigned int network_solutions_ca_name_len = 100; // This CA is an intermediate CA, subordinate to UTN-USERFirst-Hardware. const unsigned char network_solutions_ca_key_id[] = { 0x3c, 0x41, 0xe2, 0x8f, 0x08, 0x08, 0xa9, 0x4c, 0x25, 0x89, 0x8d, 0x6d, 0xc5, 0x38, 0xd0, 0xfc, 0x85, 0x8c, 0x62, 0x17 }; const unsigned int network_solutions_ca_key_id_len = 20; // This CA is a root CA. It is also cross-certified by // UTN-USERFirst-Hardware. const unsigned char network_solutions_ca_key_id2[] = { 0x21, 0x30, 0xc9, 0xfb, 0x00, 0xd7, 0x4e, 0x98, 0xda, 0x87, 0xaa, 0x2a, 0xd0, 0xa7, 0x2e, 0xb1, 0x40, 0x31, 0xa7, 0x4c }; const unsigned int network_solutions_ca_key_id2_len = 20; // An entry in our OCSP responder table. |issuer| and |issuer_key_id| are // the key. |ocsp_url| is the value. struct OCSPResponderTableEntry { SECItem issuer; SECItem issuer_key_id; const char *ocsp_url; }; const OCSPResponderTableEntry g_ocsp_responder_table[] = { { { siBuffer, const_cast<unsigned char*>(network_solutions_ca_name), network_solutions_ca_name_len }, { siBuffer, const_cast<unsigned char*>(network_solutions_ca_key_id), network_solutions_ca_key_id_len }, "http://ocsp.netsolssl.com" }, { { siBuffer, const_cast<unsigned char*>(network_solutions_ca_name), network_solutions_ca_name_len }, { siBuffer, const_cast<unsigned char*>(network_solutions_ca_key_id2), network_solutions_ca_key_id2_len }, "http://ocsp.netsolssl.com" } }; char* GetAlternateOCSPAIAInfo(CERTCertificate *cert) { if (cert && !cert->isRoot && cert->authKeyID) { for (unsigned int i=0; i < arraysize(g_ocsp_responder_table); i++) { if (SECITEM_CompareItem(&g_ocsp_responder_table[i].issuer, &cert->derIssuer) == SECEqual && SECITEM_CompareItem(&g_ocsp_responder_table[i].issuer_key_id, &cert->authKeyID->keyID) == SECEqual) { return PORT_Strdup(g_ocsp_responder_table[i].ocsp_url); } } } return NULL; } } // anonymous namespace void SetMessageLoopForNSSHttpIO() { // Must have a MessageLoopForIO. DCHECK(base::MessageLoopForIO::current()); bool used = g_ocsp_io_loop.Get().used(); // Should not be called when g_ocsp_io_loop has already been used. DCHECK(!used); } void EnsureNSSHttpIOInit() { g_ocsp_io_loop.Get().StartUsing(); g_ocsp_nss_initialization.Get(); } void ShutdownNSSHttpIO() { g_ocsp_io_loop.Get().Shutdown(); } void ResetNSSHttpIOForTesting() { g_ocsp_io_loop.Get().ReuseForTesting(); } // This function would be called before NSS initialization. void SetURLRequestContextForNSSHttpIO(URLRequestContext* request_context) { pthread_mutex_lock(&g_request_context_lock); if (request_context) { DCHECK(!g_request_context); } g_request_context = request_context; pthread_mutex_unlock(&g_request_context_lock); } } // namespace net