| // 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/cert_net/nss_ocsp.h" |
| |
| #include <certt.h> |
| #include <certdb.h> |
| #include <nspr.h> |
| #include <nss.h> |
| #include <ocsp.h> |
| #include <pthread.h> |
| #include <secerr.h> |
| |
| #include <algorithm> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/callback.h" |
| #include "base/compiler_specific.h" |
| #include "base/lazy_instance.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/macros.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/single_thread_task_runner.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_restrictions.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/time.h" |
| #include "net/base/elements_upload_data_stream.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/http/http_request_headers.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/traffic_annotation/network_traffic_annotation.h" |
| #include "net/url_request/redirect_info.h" |
| #include "net/url_request/url_request.h" |
| #include "net/url_request/url_request_context.h" |
| #include "starboard/types.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: |
| // This class is only instantiated as a leaky LazyInstance, so its destructor |
| // is never called. |
| ~OCSPIOLoop() = delete; |
| |
| void StartUsing() { |
| base::AutoLock autolock(lock_); |
| DCHECK(base::MessageLoopForIO::IsCurrent()); |
| io_task_runner_ = base::ThreadTaskRunnerHandle::Get(); |
| } |
| |
| // Called on IO loop. |
| void Shutdown(); |
| |
| // Called from worker thread. |
| void PostTaskToIOLoop(const base::Location& from_here, |
| const base::Closure& task); |
| |
| void AddRequest(OCSPRequestSession* request); |
| void RemoveRequest(OCSPRequestSession* request); |
| |
| private: |
| friend struct base::LazyInstanceTraitsBase<OCSPIOLoop>; |
| |
| OCSPIOLoop() = default; |
| |
| void CancelAllRequests(); |
| |
| // Protects all members below. |
| mutable base::Lock lock_; |
| std::set<OCSPRequestSession*> requests_; |
| scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; |
| |
| 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::LazyInstanceTraitsBase<OCSPNSSInitialization>; |
| |
| OCSPNSSInitialization(); |
| // This class is only instantiated as a leaky LazyInstance, so its destructor |
| // is never called. |
| ~OCSPNSSInitialization() = delete; |
| |
| SEC_HttpClientFcn client_fcn_; |
| |
| DISALLOW_COPY_AND_ASSIGN(OCSPNSSInitialization); |
| }; |
| |
| base::LazyInstance<OCSPNSSInitialization>::Leaky 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_(base::MakeRefCounted<IOBuffer>(kRecvBufferSize)), |
| response_code_(-1), |
| cv_(&lock_), |
| 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_task_runner_| is only initialized in StartURLRequest, so no need to |
| // lock |lock_| here. |
| DCHECK(!io_task_runner_); |
| 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 reset |io_task_runner_|, 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_; |
| } |
| |
| void OnReceivedRedirect(URLRequest* request, |
| const RedirectInfo& redirect_info, |
| bool* defer_redirect) override { |
| DCHECK_EQ(request_.get(), request); |
| DCHECK(io_task_runner_->BelongsToCurrentThread()); |
| |
| if (!redirect_info.new_url.SchemeIs("http")) { |
| // Prevent redirects to non-HTTP schemes, including HTTPS. This matches |
| // the initial check in OCSPServerSession::CreateRequest(). |
| CancelURLRequest(); |
| } |
| } |
| |
| void OnResponseStarted(URLRequest* request, int net_error) override { |
| DCHECK_EQ(request_.get(), request); |
| DCHECK(io_task_runner_->BelongsToCurrentThread()); |
| DCHECK_NE(ERR_IO_PENDING, net_error); |
| |
| int bytes_read = 0; |
| if (net_error == OK) { |
| response_code_ = request_->GetResponseCode(); |
| response_headers_ = request_->response_headers(); |
| response_headers_->GetMimeType(&response_content_type_); |
| bytes_read = request_->Read(buffer_.get(), kRecvBufferSize); |
| } |
| OnReadCompleted(request_.get(), bytes_read); |
| } |
| |
| void OnReadCompleted(URLRequest* request, int bytes_read) override { |
| DCHECK_EQ(request_.get(), request); |
| DCHECK(io_task_runner_->BelongsToCurrentThread()); |
| |
| while (bytes_read > 0) { |
| data_.append(buffer_->data(), bytes_read); |
| bytes_read = request_->Read(buffer_.get(), kRecvBufferSize); |
| } |
| |
| if (bytes_read != ERR_IO_PENDING) { |
| request_.reset(); |
| g_ocsp_io_loop.Get().RemoveRequest(this); |
| { |
| base::AutoLock autolock(lock_); |
| finished_ = true; |
| io_task_runner_ = nullptr; |
| } |
| cv_.Signal(); |
| Release(); // Balanced with StartURLRequest(). |
| } |
| } |
| |
| // Must be called on the IO loop thread. |
| void CancelURLRequest() { |
| #ifndef NDEBUG |
| { |
| base::AutoLock autolock(lock_); |
| if (io_task_runner_) |
| DCHECK(io_task_runner_->BelongsToCurrentThread()); |
| } |
| #endif |
| if (request_) { |
| request_.reset(); |
| g_ocsp_io_loop.Get().RemoveRequest(this); |
| { |
| base::AutoLock autolock(lock_); |
| finished_ = true; |
| io_task_runner_ = nullptr; |
| } |
| cv_.Signal(); |
| Release(); // Balanced with StartURLRequest(). |
| } |
| } |
| |
| private: |
| friend class base::RefCountedThreadSafe<OCSPRequestSession>; |
| |
| ~OCSPRequestSession() override { |
| // 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_task_runner_); |
| } |
| |
| // Must call this method while holding |lock_|. |
| void CancelLocked() { |
| lock_.AssertAcquired(); |
| if (io_task_runner_) { |
| io_task_runner_->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_task_runner_); |
| DCHECK(base::MessageLoopForIO::IsCurrent()); |
| io_task_runner_ = base::ThreadTaskRunnerHandle::Get(); |
| g_ocsp_io_loop.Get().AddRequest(this); |
| } |
| |
| net::NetworkTrafficAnnotationTag traffic_annotation = |
| net::DefineNetworkTrafficAnnotation("ocsp_start_url_request", R"( |
| semantics { |
| sender: "OCSP" |
| description: |
| "Verifying the revocation status of a certificate via OCSP." |
| trigger: |
| "This may happen in response to visiting a website that uses " |
| "https://" |
| data: |
| "Identifier for the certificate whose revocation status is being " |
| "checked. See https://tools.ietf.org/html/rfc6960#section-2.1 for " |
| "more details." |
| destination: OTHER |
| } |
| policy { |
| cookies_allowed: NO |
| setting: "This feature cannot be disabled by settings." |
| policy_exception_justification: "Not implemented." |
| })"); |
| request_ = url_request_context->CreateRequest(url_, DEFAULT_PRIORITY, this, |
| traffic_annotation); |
| // 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_); |
| |
| std::unique_ptr<UploadElementReader> reader(new UploadBytesElementReader( |
| upload_content_.data(), upload_content_.size())); |
| request_->set_upload( |
| ElementsUploadDataStream::CreateWithReader(std::move(reader), 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 |
| std::unique_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_task_runner_|. |
| mutable base::Lock lock_; |
| base::ConditionVariable cv_; |
| |
| // TaskRunner for the IO thread. Set when StartURLRequest() is invoked (on the |
| // IO thread). |
| scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; |
| 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() = default; |
| |
| 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); |
| }; |
| |
| void OCSPIOLoop::Shutdown() { |
| // Safe to read outside lock since we only write on IO thread anyway. |
| DCHECK(io_task_runner_->BelongsToCurrentThread()); |
| |
| // Prevent the worker thread from trying to access |io_task_runner_|. |
| { |
| base::AutoLock autolock(lock_); |
| io_task_runner_ = nullptr; |
| } |
| |
| CancelAllRequests(); |
| |
| pthread_mutex_lock(&g_request_context_lock); |
| g_request_context = NULL; |
| pthread_mutex_unlock(&g_request_context_lock); |
| } |
| |
| void OCSPIOLoop::PostTaskToIOLoop(const base::Location& from_here, |
| const base::Closure& task) { |
| base::AutoLock autolock(lock_); |
| if (io_task_runner_) |
| io_task_runner_->PostTask(from_here, task); |
| } |
| |
| void OCSPIOLoop::AddRequest(OCSPRequestSession* request) { |
| DCHECK(!base::ContainsKey(requests_, request)); |
| requests_.insert(request); |
| } |
| |
| void OCSPIOLoop::RemoveRequest(OCSPRequestSession* request) { |
| DCHECK(base::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(); |
| } |
| } |
| |
| |
| // 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; |
| } |
| |
| 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. |
| 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 = |
| base::size(network_solutions_ca_name); |
| |
| // 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 = |
| base::size(network_solutions_ca_key_id); |
| |
| // 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 = |
| base::size(network_solutions_ca_key_id2); |
| |
| // 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 (const auto& responder : g_ocsp_responder_table) { |
| if (SECITEM_CompareItem(&responder.issuer, &cert->derIssuer) == |
| SECEqual && |
| SECITEM_CompareItem(&responder.issuer_key_id, |
| &cert->authKeyID->keyID) == SECEqual) { |
| return PORT_Strdup(responder.ocsp_url); |
| } |
| } |
| } |
| |
| return NULL; |
| } |
| |
| } // anonymous namespace |
| |
| void EnsureNSSHttpIOInit() { |
| g_ocsp_nss_initialization.Get(); |
| } |
| |
| 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); |
| |
| if (request_context) { |
| g_ocsp_io_loop.Get().StartUsing(); |
| } else { |
| g_ocsp_io_loop.Get().Shutdown(); |
| } |
| } |
| |
| } // namespace net |