blob: a609db78d8156ffc4447d2f9c45d222d8b416b38 [file] [log] [blame]
// Copyright (c) 2019 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_session_url_request.h"
#include "base/message_loop/message_loop_current.h"
#include "base/synchronization/condition_variable.h"
#include "base/synchronization/lock.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "net/base/elements_upload_data_stream.h"
#include "net/base/load_flags.h"
#include "net/base/upload_bytes_element_reader.h"
#include "net/cert_net/nss_ocsp.h"
#include "net/url_request/redirect_info.h"
#include "net/url_request/url_request_context.h"
#include "url/gurl.h"
namespace net {
namespace {
// Size of the IOBuffer that used for reading the result.
const int kRecvBufferSize = 4096;
// The maximum size in bytes for the response body when fetching an OCSP/CRL
// URL.
const int kMaxResponseSizeInBytes = 5 * 1024 * 1024;
} // namespace
class OCSPRequestSessionDelegateURLRequest;
class NET_EXPORT OCSPIOLoop {
public:
// This class is only instantiated in a base::NoDestructor, so its destructor
// is never called.
~OCSPIOLoop() = delete;
// Called on IO task runner.
void StartUsing();
// Called on IO task runner.
void Shutdown();
// Called from worker thread.
void PostTaskToIOLoop(const base::Location& from_here,
base::OnceClosure task);
// Returns true if and only if StartUsing() has been called, Shutdown() has
// not been called, and this is currently running on the OCSP IO task runner.
bool RunsTasksInCurrentSequence();
// Adds a request to cancel if |this|->Shutdown() is called during the
// request.
void AddRequest(OCSPRequestSessionDelegateURLRequest* request_delegate);
// Remove the request from tracking when the request has finished.
void RemoveRequest(OCSPRequestSessionDelegateURLRequest* request_delegate);
private:
friend class base::NoDestructor<OCSPIOLoop>;
OCSPIOLoop();
void CancelAllRequests();
// Protects all members below.
mutable base::Lock lock_;
std::set<OCSPRequestSessionDelegateURLRequest*> request_delegates_;
scoped_refptr<base::SequencedTaskRunner> io_task_runner_;
DISALLOW_COPY_AND_ASSIGN(OCSPIOLoop);
};
OCSPIOLoop* GetOCSPIOLoop() {
static base::NoDestructor<OCSPIOLoop> ocsp_io_loop;
return ocsp_io_loop.get();
}
class OCSPRequestSessionDelegateURLRequest : public OCSPRequestSessionDelegate,
public URLRequest::Delegate {
public:
OCSPRequestSessionDelegateURLRequest(URLRequestContext* request_context)
: buffer_(base::MakeRefCounted<IOBuffer>(kRecvBufferSize)),
request_context_(request_context),
cv_(&lock_) {}
// OCSPRequestSessionDelegate overrides.
std::unique_ptr<OCSPRequestSessionResult> StartAndWait(
const OCSPRequestSessionParams* params) override {
GetOCSPIOLoop()->PostTaskToIOLoop(
FROM_HERE, base::Bind(&OCSPRequestSessionDelegateURLRequest::StartLoad,
this, params));
// Wait with a timeout.
base::TimeDelta timeout = params->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_) {
// Safe to call CancelLoad even if the request successfully finished
// after our timeout, because if the request has finished it will be
// reset and CancelLoad will be a no-op.
GetOCSPIOLoop()->PostTaskToIOLoop(
FROM_HERE,
base::BindOnce(&OCSPRequestSessionDelegateURLRequest::CancelLoad,
this));
}
break;
}
}
if (!finished_)
return nullptr;
return std::move(result_);
}
void CancelLoad() {
DCHECK(GetOCSPIOLoop()->RunsTasksInCurrentSequence());
if (request_) {
FinishLoad();
}
}
// URLRequest::Delegate overrides
void OnReceivedRedirect(URLRequest* request,
const RedirectInfo& redirect_info,
bool* defer_redirect) override {
DCHECK_EQ(request_.get(), request);
DCHECK(GetOCSPIOLoop()->RunsTasksInCurrentSequence());
if (!redirect_info.new_url.SchemeIs("http")) {
// Prevent redirects to non-HTTP schemes, including HTTPS. This matches
// the initial check in OCSPServerSession::CreateRequest().
CancelLoad();
}
}
void OnResponseStarted(URLRequest* request, int net_error) override {
DCHECK_EQ(request_.get(), request);
DCHECK(GetOCSPIOLoop()->RunsTasksInCurrentSequence());
DCHECK_NE(ERR_IO_PENDING, net_error);
int bytes_read = 0;
if (net_error == OK) {
result_->response_code = request_->GetResponseCode();
result_->response_headers = request_->response_headers();
result_->response_headers->GetMimeType(&result_->response_content_type);
bytes_read = request_->Read(buffer_.get(), kRecvBufferSize);
}
OnReadCompleted(request_.get(), bytes_read);
}
void OnReadCompleted(URLRequest* request, int bytes_read) override {
DCHECK(!finished_);
DCHECK_EQ(request_.get(), request);
DCHECK(GetOCSPIOLoop()->RunsTasksInCurrentSequence());
while (bytes_read > 0) {
result_->data.append(buffer_->data(), bytes_read);
bytes_read = request_->Read(buffer_.get(), kRecvBufferSize);
}
// Check max size.
if (result_->data.size() > kMaxResponseSizeInBytes) {
// Reset the result to indicate error.
result_.reset();
FinishLoad();
}
// If we are done reading, return results.
if (bytes_read != ERR_IO_PENDING) {
FinishLoad();
}
}
private:
friend class base::RefCountedThreadSafe<OCSPRequestSessionDelegateURLRequest>;
~OCSPRequestSessionDelegateURLRequest() 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(finished_);
DCHECK(!request_);
}
// Runs on the OCSP IO task runner.
void StartLoad(const OCSPRequestSessionParams* params) {
DCHECK(GetOCSPIOLoop()->RunsTasksInCurrentSequence());
if (request_) {
NOTREACHED();
FinishLoad(); // Will return a nullptr as the result.
return;
}
if (finished_) {
// This may occur if the OCSPIOLoop has already called Shutdown() (i.e.
// SetURLRequestContextForNSSHttpIO(nullptr) has been called), but the
// NSS worker thread posted a StartLoad task before Shutdown() was called.
// Since Shutdown() was called already, the tasks should all have been
// cancelled and FinishLoad() should already have been called. We have
// to make sure we honor the cancellation because |request_context_| may
// no longer be valid.
return;
}
GetOCSPIOLoop()->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
destination_other:
"The URI specified in the certificate."
}
policy {
cookies_allowed: NO
setting: "This feature cannot be disabled by settings."
policy_exception_justification: "Not implemented."
})");
request_ = request_context_->CreateRequest(params->url, DEFAULT_PRIORITY,
this, traffic_annotation);
request_->SetLoadFlags(LOAD_DISABLE_CACHE);
request_->set_allow_credentials(false);
if (!params->extra_request_headers.IsEmpty())
request_->SetExtraRequestHeaders(params->extra_request_headers);
if (params->http_request_method == "POST") {
DCHECK(!params->upload_content.empty());
DCHECK(!params->upload_content_type.empty());
request_->set_method("POST");
request_->SetExtraRequestHeaderByName(HttpRequestHeaders::kContentType,
params->upload_content_type, true);
std::unique_ptr<UploadElementReader> reader(new UploadBytesElementReader(
params->upload_content.data(), params->upload_content.size()));
request_->set_upload(
ElementsUploadDataStream::CreateWithReader(std::move(reader), 0));
}
request_->Start();
result_ = std::make_unique<OCSPRequestSessionResult>();
AddRef(); // Release after |request_| deleted.
}
void FinishLoad() {
DCHECK(GetOCSPIOLoop()->RunsTasksInCurrentSequence());
{
base::AutoLock autolock(lock_);
finished_ = true;
}
request_context_ = nullptr;
request_.reset();
GetOCSPIOLoop()->RemoveRequest(this);
cv_.Signal();
Release(); // Balanced with StartLoad().
}
std::unique_ptr<URLRequest> request_; // The actual request this wraps
scoped_refptr<IOBuffer> buffer_; // Read buffer
URLRequestContext* request_context_;
std::unique_ptr<OCSPRequestSessionResult> result_;
bool finished_ = false;
// |lock_| protects |finished_|.
mutable base::Lock lock_;
base::ConditionVariable cv_;
};
OCSPIOLoop::OCSPIOLoop() = default;
void OCSPIOLoop::StartUsing() {
base::AutoLock autolock(lock_);
DCHECK(base::MessageLoopCurrentForIO::IsSet());
io_task_runner_ = base::SequencedTaskRunnerHandle::Get();
}
void OCSPIOLoop::Shutdown() {
// Safe to read outside lock since we only write on IO thread anyway.
DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
// Prevent the worker thread from trying to access |io_task_runner_|.
{
base::AutoLock autolock(lock_);
io_task_runner_ = nullptr;
}
CancelAllRequests();
SetOCSPRequestSessionDelegateFactory(nullptr);
}
void OCSPIOLoop::PostTaskToIOLoop(const base::Location& from_here,
base::OnceClosure task) {
base::AutoLock autolock(lock_);
if (io_task_runner_)
io_task_runner_->PostTask(from_here, std::move(task));
}
void OCSPIOLoop::AddRequest(
OCSPRequestSessionDelegateURLRequest* request_delegate) {
DCHECK(!base::Contains(request_delegates_, request_delegate));
request_delegates_.insert(request_delegate);
}
void OCSPIOLoop::RemoveRequest(
OCSPRequestSessionDelegateURLRequest* request_delegate) {
DCHECK(base::Contains(request_delegates_, request_delegate));
request_delegates_.erase(request_delegate);
}
bool OCSPIOLoop::RunsTasksInCurrentSequence() {
base::AutoLock autolock(lock_);
return io_task_runner_ && io_task_runner_->RunsTasksInCurrentSequence();
}
void OCSPIOLoop::CancelAllRequests() {
// CancelLoad() always removes the request from the requests_
// set synchronously.
while (!request_delegates_.empty())
(*request_delegates_.begin())->CancelLoad();
}
class OCSPRequestSessionDelegateFactoryURLRequest
: public OCSPRequestSessionDelegateFactory {
public:
OCSPRequestSessionDelegateFactoryURLRequest(
URLRequestContext* request_context)
: request_context_(request_context) {}
scoped_refptr<OCSPRequestSessionDelegate> CreateOCSPRequestSessionDelegate()
override {
return base::MakeRefCounted<OCSPRequestSessionDelegateURLRequest>(
request_context_);
}
~OCSPRequestSessionDelegateFactoryURLRequest() override = default;
private:
URLRequestContext* request_context_;
};
void SetURLRequestContextForNSSHttpIO(URLRequestContext* request_context) {
if (request_context) {
SetOCSPRequestSessionDelegateFactory(
std::make_unique<OCSPRequestSessionDelegateFactoryURLRequest>(
request_context));
} else {
SetOCSPRequestSessionDelegateFactory(nullptr);
}
if (request_context) {
GetOCSPIOLoop()->StartUsing();
} else {
GetOCSPIOLoop()->Shutdown();
}
}
} // namespace net