summaryrefslogtreecommitdiffstats
path: root/net/ocsp/nss_ocsp.cc
diff options
context:
space:
mode:
authorukai@chromium.org <ukai@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-07-24 03:48:16 +0000
committerukai@chromium.org <ukai@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-07-24 03:48:16 +0000
commit3d4ae9a42320916079372a65c0ee527a150ec134 (patch)
tree5c61bf41db3356e4ab5846c50c7f6dca63a8d8c4 /net/ocsp/nss_ocsp.cc
parentcbdd7de00e63b0de23be30e30bf3bbe72f6935ca (diff)
downloadchromium_src-3d4ae9a42320916079372a65c0ee527a150ec134.zip
chromium_src-3d4ae9a42320916079372a65c0ee527a150ec134.tar.gz
chromium_src-3d4ae9a42320916079372a65c0ee527a150ec134.tar.bz2
Implement OCSP handler for NSS.
BUG=none TEST=none Review URL: http://codereview.chromium.org/126046 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@21508 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/ocsp/nss_ocsp.cc')
-rw-r--r--net/ocsp/nss_ocsp.cc600
1 files changed, 600 insertions, 0 deletions
diff --git a/net/ocsp/nss_ocsp.cc b/net/ocsp/nss_ocsp.cc
new file mode 100644
index 0000000..da584b2
--- /dev/null
+++ b/net/ocsp/nss_ocsp.cc
@@ -0,0 +1,600 @@
+// 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"
+
+// Work around https://bugzilla.mozilla.org/show_bug.cgi?id=455424
+// until NSS 3.12.2 comes out and we update to it.
+#define Lock FOO_NSS_Lock
+#include <certt.h>
+#undef Lock
+#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/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 {
+
+static 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.
+
+class OCSPInitSingleton {
+ public:
+ MessageLoop* io_thread() const {
+ DCHECK(io_loop_);
+ return io_loop_;
+ }
+
+ // 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;
+ }
+ URLRequestContext* url_request_context() const {
+ return request_context_.get();
+ }
+
+ private:
+ friend struct DefaultSingletonTraits<OCSPInitSingleton>;
+ OCSPInitSingleton();
+ ~OCSPInitSingleton() {
+ request_context_ = NULL;
+ }
+
+ SEC_HttpClientFcn client_fcn_;
+
+ // I/O thread.
+ MessageLoop* io_loop_; // I/O thread
+
+ // URLRequestContext for OCSP handlers.
+ static scoped_refptr<URLRequestContext> request_context_;
+
+ DISALLOW_COPY_AND_ASSIGN(OCSPInitSingleton);
+};
+
+scoped_refptr<URLRequestContext> OCSPInitSingleton::request_context_;
+
+// Concrete class for SEC_HTTP_REQUEST_SESSION.
+class OCSPRequestSession {
+ public:
+ OCSPRequestSession(const GURL& url,
+ const char* http_request_method,
+ base::TimeDelta timeout);
+ ~OCSPRequestSession() {}
+
+ void SetPostData(const char* http_data,
+ const PRUint32 http_data_len,
+ const char* http_content_type);
+
+ void AddHeader(const char* http_header_name,
+ const char* http_header_value);
+
+ void Start();
+ bool Started() const;
+
+ void Cancel();
+
+ bool Finished() const;
+
+ bool Wait();
+
+ 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;
+ const std::string& http_response_content_type() const;
+ const std::string& http_response_headers() const;
+ const std::string& http_response_data() const;
+
+ private:
+ class Core;
+
+ GURL url_;
+ std::string http_request_method_;
+ base::TimeDelta timeout_;
+
+ scoped_refptr<Core> core_;
+
+ DISALLOW_COPY_AND_ASSIGN(OCSPRequestSession);
+};
+
+// This class is the real guts of OCSP handlers.
+// 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::Core
+ : public base::RefCountedThreadSafe<OCSPRequestSession::Core>,
+ public URLRequest::Delegate {
+ public:
+ explicit Core(OCSPRequestSession* req)
+ : ocsp_req_(req),
+ url_(ocsp_req_->url()),
+ io_loop_(Singleton<OCSPInitSingleton>::get()->io_thread()),
+ request_(NULL),
+ buffer_(new net::IOBuffer(kRecvBufferSize)),
+ response_code_(-1),
+ cv_(&lock_),
+ finished_(false) {}
+ virtual ~Core() {}
+
+ 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() {
+ DCHECK(io_loop_);
+ io_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &Core::StartURLRequest));
+ }
+
+ bool Started() const {
+ return request_ != NULL;
+ }
+
+ bool Finished() const {
+ AutoLock autolock(lock_);
+ return finished_;
+ }
+
+ void Cancel() {
+ io_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &Core::CancelURLRequest));
+ }
+
+ bool Wait() {
+ base::TimeDelta timeout = ocsp_req_->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_)
+ Cancel();
+ break;
+ }
+ }
+ return finished_;
+ }
+
+ PRUint16 response_code() const {
+ DCHECK(finished_);
+ return response_code_;
+ }
+
+ const std::string &response_content_type() const {
+ DCHECK(finished_);
+ return response_content_type_;
+ }
+
+ const std::string &response_headers() const {
+ DCHECK(finished_);
+ return response_headers_->raw_headers();
+ }
+
+ const std::string &http_response_data() const {
+ DCHECK(finished_);
+ return data_;
+ }
+
+ // Follow rediect.
+ virtual void OnReceivedRedirect(URLRequest* request,
+ const GURL& new_url) {}
+
+ virtual void OnResponseStarted(URLRequest* request) {
+ DCHECK(request == request_);
+ DCHECK(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(request == request_);
+ DCHECK(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()) {
+ {
+ AutoLock autolock(lock_);
+ finished_ = true;
+ }
+ cv_.Signal();
+ delete request_;
+ request_ = NULL;
+ }
+ }
+
+ private:
+ void StartURLRequest() {
+ DCHECK(MessageLoopForIO::current() == io_loop_);
+ DCHECK(!request_);
+
+ request_ = new URLRequest(url_, this);
+ request_->set_context(
+ Singleton<OCSPInitSingleton>::get()->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);
+
+ if (ocsp_req_->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();
+ }
+
+ void CancelURLRequest() {
+ DCHECK(MessageLoopForIO::current() == io_loop_);
+ if (request_) {
+ delete request_;
+ request_ = NULL;
+ }
+ }
+
+ OCSPRequestSession* ocsp_req_; // corresponding OCSP session
+ GURL url_; // The URL we eventually wound up at
+ MessageLoop* io_loop_; // Message loop of the IO thread
+ 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_| only.
+ mutable Lock lock_;
+ ConditionVariable cv_;
+
+ bool finished_;
+
+ DISALLOW_COPY_AND_ASSIGN(Core);
+};
+
+OCSPRequestSession::OCSPRequestSession(const GURL& url,
+ const char* http_request_method,
+ base::TimeDelta timeout)
+ : url_(url), http_request_method_(http_request_method),
+ timeout_(timeout),
+ ALLOW_THIS_IN_INITIALIZER_LIST(core_(new Core(this))) {
+}
+
+void OCSPRequestSession::SetPostData(const char* http_data,
+ const PRUint32 http_data_len,
+ const char* http_content_type) {
+ core_->SetPostData(http_data, http_data_len, http_content_type);
+}
+
+void OCSPRequestSession::AddHeader(const char* http_header_name,
+ const char* http_header_value) {
+ core_->AddHeader(http_header_name, http_header_value);
+}
+
+void OCSPRequestSession::Start() {
+ core_->Start();
+}
+
+bool OCSPRequestSession::Started() const {
+ return core_->Started();
+}
+
+void OCSPRequestSession::Cancel() {
+ core_->Cancel();
+}
+
+bool OCSPRequestSession::Finished() const {
+ return core_->Finished();
+}
+
+bool OCSPRequestSession::Wait() {
+ return core_->Wait();
+}
+
+PRUint16 OCSPRequestSession::http_response_code() const {
+ return core_->response_code();
+}
+
+const std::string& OCSPRequestSession::http_response_content_type() const {
+ return core_->response_content_type();
+}
+
+const std::string& OCSPRequestSession::http_response_headers() const {
+ return core_->response_headers();
+}
+
+const std::string& OCSPRequestSession::http_response_data() const {
+ return core_->http_response_data();
+}
+
+// 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 (Singleton<OCSPInitSingleton>::get()->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);
+
+ *pRequest = ocsp_session->CreateRequest(http_protocol_variant,
+ path_and_query_string,
+ http_request_method,
+ timeout);
+ return *pRequest ? SECSuccess : SECFailure;
+}
+
+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 = 1;
+ return false;
+ }
+ }
+ LOG(INFO) << "OSCP 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 start 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();
+ return SECFailure;
+ }
+ req->Start();
+ if (!req->Wait())
+ return SECFailure;
+
+ return OCSPSetResponse(
+ req, http_response_code,
+ http_response_content_type,
+ http_response_headers,
+ http_response_data,
+ http_response_data_len) ? SECSuccess : SECFailure;
+}
+
+SECStatus OCSPFree(SEC_HTTP_REQUEST_SESSION request) {
+ LOG(INFO) << "OCSP free";
+ DCHECK(!MessageLoop::current());
+ OCSPRequestSession* req = reinterpret_cast<OCSPRequestSession*>(request);
+ delete req;
+ return SECSuccess;
+}
+
+OCSPInitSingleton::OCSPInitSingleton() : io_loop_(MessageLoopForIO::current()) {
+ 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();
+ }
+}
+
+} // 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);
+}
+
+} // namespace net