// Copyright (c) 2009 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 <secerr.h> #include <string> #include "base/compiler_specific.h" #include "base/condition_variable.h" #include "base/logging.h" #include "base/message_loop.h" #include "base/singleton.h" #include "base/thread.h" #include "base/time.h" #include "googleurl/src/gurl.h" #include "net/base/io_buffer.h" #include "net/base/load_flags.h" #include "net/http/http_response_headers.h" #include "net/url_request/url_request.h" #include "net/url_request/url_request_context.h" namespace { 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); class OCSPInitSingleton : public MessageLoop::DestructionObserver { public: // Called on IO thread. virtual void WillDestroyCurrentMessageLoop() { AutoLock autolock(lock_); DCHECK_EQ(MessageLoopForIO::current(), io_loop_); io_loop_ = NULL; request_context_ = NULL; }; // Called from worker thread. void PostTaskToIOLoop( const tracked_objects::Location& from_here, Task* task) { AutoLock autolock(lock_); if (io_loop_) io_loop_->PostTask(from_here, task); } // This is static method because it is called before NSS initialization, // that is, before OCSPInitSingleton is initialized. static void set_url_request_context(URLRequestContext* request_context) { request_context_ = request_context; } static URLRequestContext* url_request_context() { return request_context_; } private: friend struct DefaultSingletonTraits<OCSPInitSingleton>; OCSPInitSingleton() : io_loop_(MessageLoopForIO::current()) { DCHECK(io_loop_); io_loop_->AddDestructionObserver(this); 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(); } } virtual ~OCSPInitSingleton() { // IO thread was already deleted before the singleton is deleted // in AtExitManager. AutoLock autolock(lock_); DCHECK(!io_loop_); DCHECK(!request_context_); } SEC_HttpClientFcn client_fcn_; // |lock_| protects |io_loop_|. Lock lock_; // I/O thread. MessageLoop* io_loop_; // I/O thread // URLRequestContext for OCSP handlers. static URLRequestContext* request_context_; DISALLOW_COPY_AND_ASSIGN(OCSPInitSingleton); }; URLRequestContext* OCSPInitSingleton::request_context_ = NULL; // 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 MessageLoop::DestructionObserver { public: OCSPRequestSession(const GURL& url, const char* http_request_method, base::TimeDelta timeout) : url_(url), http_request_method_(http_request_method), timeout_(timeout), request_(NULL), buffer_(new net::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_.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) { if (!extra_request_headers_.empty()) extra_request_headers_ += "\r\n"; StringAppendF(&extra_request_headers_, "%s: %s", 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_); Singleton<OCSPInitSingleton>()->PostTaskToIOLoop( FROM_HERE, NewRunnableMethod(this, &OCSPRequestSession::StartURLRequest)); } bool Started() const { return request_ != NULL; } void Cancel() { // IO thread may set |io_loop_| to NULL, so protect by |lock_|. AutoLock autolock(lock_); CancelLocked(); } bool Finished() const { AutoLock autolock(lock_); return finished_; } bool Wait() { base::TimeDelta timeout = timeout_; 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()) { LOG(INFO) << "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 OnResponseStarted(URLRequest* request) { DCHECK_EQ(request, request_); DCHECK_EQ(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_, kRecvBufferSize, &bytes_read); } OnReadCompleted(request_, bytes_read); } virtual void OnReadCompleted(URLRequest* request, int bytes_read) { DCHECK_EQ(request, request_); DCHECK_EQ(MessageLoopForIO::current(), io_loop_); do { if (!request_->status().is_success() || bytes_read <= 0) break; data_.append(buffer_->data(), bytes_read); } while (request_->Read(buffer_, kRecvBufferSize, &bytes_read)); if (!request_->status().is_io_pending()) { delete request_; request_ = NULL; io_loop_->RemoveDestructionObserver(this); { AutoLock autolock(lock_); finished_ = true; io_loop_ = NULL; } cv_.Signal(); Release(); // Balanced with StartURLRequest(). } } virtual void WillDestroyCurrentMessageLoop() { DCHECK_EQ(MessageLoopForIO::current(), io_loop_); { AutoLock autolock(lock_); io_loop_ = NULL; } CancelURLRequest(); } 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, NewRunnableMethod(this, &OCSPRequestSession::CancelURLRequest)); } } void StartURLRequest() { DCHECK(!request_); URLRequestContext* url_request_context = OCSPInitSingleton::url_request_context(); if (url_request_context == NULL) return; { AutoLock autolock(lock_); DCHECK(!io_loop_); io_loop_ = MessageLoopForIO::current(); io_loop_->AddDestructionObserver(this); } request_ = new URLRequest(url_, this); request_->set_context(url_request_context); // To meet the privacy requirements of off-the-record mode. request_->set_load_flags( net::LOAD_DISABLE_CACHE | net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_DO_NOT_SEND_COOKIES); if (http_request_method_ == "POST") { DCHECK(!upload_content_.empty()); DCHECK(!upload_content_type_.empty()); request_->set_method("POST"); if (!extra_request_headers_.empty()) extra_request_headers_ += "\r\n"; StringAppendF(&extra_request_headers_, "Content-Type: %s", upload_content_type_.c_str()); request_->AppendBytesToUpload(upload_content_.data(), static_cast<int>(upload_content_.size())); } if (!extra_request_headers_.empty()) request_->SetExtraRequestHeaders(extra_request_headers_); request_->Start(); AddRef(); // Release after |request_| deleted. } void CancelURLRequest() { #ifndef NDEBUG { AutoLock autolock(lock_); if (io_loop_) DCHECK_EQ(MessageLoopForIO::current(), io_loop_); } #endif if (request_) { request_->Cancel(); delete request_; request_ = NULL; // |io_loop_| may be NULL here if it called from // WillDestroyCurrentMessageLoop(). if (io_loop_) io_loop_->RemoveDestructionObserver(this); { AutoLock autolock(lock_); finished_ = true; io_loop_ = NULL; } cv_.Signal(); Release(); // Balanced with StartURLRequest(). } } GURL url_; // The URL we eventually wound up at std::string http_request_method_; base::TimeDelta timeout_; // The timeout for OCSP URLRequest* request_; // The actual request this wraps scoped_refptr<net::IOBuffer> buffer_; // Read buffer std::string extra_request_headers_; // Extra headers for the request, if any std::string upload_content_; // HTTP POST payload 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<net::HttpResponseHeaders> response_headers_; std::string data_; // Results of the requst // |lock_| protects |finished_| and |io_loop_|. mutable Lock lock_; ConditionVariable cv_; 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_(host), port_(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) return NULL; // TODO(ukai): If |host| is an IPv6 literal, we need to quote it with // square brackets []. std::string url_string(StringPrintf("%s://%s:%d%s", http_protocol_variant, host_.c_str(), port_, path_and_query_string)); LOG(INFO) << "URL [" << url_string << "]"; GURL url(url_string); return new OCSPRequestSession( url, http_request_method, base::TimeDelta::FromMilliseconds(PR_IntervalToMilliseconds(timeout))); } private: std::string host_; int port_; DISALLOW_COPY_AND_ASSIGN(OCSPServerSession); }; // OCSP Http Client functions. // Our Http Client functions operate in blocking mode. SECStatus OCSPCreateSession(const char* host, PRUint16 portnum, SEC_HTTP_SERVER_SESSION* pSession) { LOG(INFO) << "OCSP create session: host=" << host << " port=" << portnum; DCHECK(!MessageLoop::current()); if (OCSPInitSingleton::url_request_context() == NULL) { LOG(ERROR) << "No URLRequestContext for OCSP handler."; return SECFailure; } *pSession = new OCSPServerSession(host, portnum); return SECSuccess; } SECStatus OCSPKeepAliveSession(SEC_HTTP_SERVER_SESSION session, PRPollDesc **pPollDesc) { LOG(INFO) << "OCSP keep alive"; DCHECK(!MessageLoop::current()); if (pPollDesc) *pPollDesc = NULL; return SECSuccess; } SECStatus OCSPFreeSession(SEC_HTTP_SERVER_SESSION session) { LOG(INFO) << "OCSP free session"; DCHECK(!MessageLoop::current()); 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) { LOG(INFO) << "OCSP create protocol=" << http_protocol_variant << " path_and_query=" << path_and_query_string << " http_request_method=" << http_request_method << " timeout=" << timeout; DCHECK(!MessageLoop::current()); 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) { LOG(INFO) << "OCSP set post data len=" << http_data_len; DCHECK(!MessageLoop::current()); 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) { LOG(INFO) << "OCSP add header name=" << http_header_name << " value=" << http_header_value; DCHECK(!MessageLoop::current()); 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|. bool 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) << "data size too large: " << *http_response_data_len << " < " << data.size(); *http_response_data_len = data.size(); return false; } } LOG(INFO) << "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 true; } 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) { LOG(INFO) << "OCSP try send and receive"; DCHECK(!MessageLoop::current()); 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(); goto failed; } req->Start(); if (!req->Wait()) goto failed; // If the response code is -1, the request failed and there is no response. if (req->http_response_code() == static_cast<PRUint16>(-1)) goto failed; return OCSPSetResponse( req, http_response_code, http_response_content_type, http_response_headers, http_response_data, http_response_data_len) ? SECSuccess : SECFailure; failed: 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; } return SECFailure; } SECStatus OCSPFree(SEC_HTTP_REQUEST_SESSION request) { LOG(INFO) << "OCSP free"; DCHECK(!MessageLoop::current()); OCSPRequestSession* req = reinterpret_cast<OCSPRequestSession*>(request); req->Cancel(); req->Release(); return SECSuccess; } } // anonymous namespace namespace net { void EnsureOCSPInit() { Singleton<OCSPInitSingleton>::get(); } // This function would be called before NSS initialization. void SetURLRequestContextForOCSP(URLRequestContext* request_context) { OCSPInitSingleton::set_url_request_context(request_context); } URLRequestContext* GetURLRequestContextForOCSP() { return OCSPInitSingleton::url_request_context(); } } // namespace net