普通文本  |  599行  |  15.5 KB

// Copyright (c) 2014 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/http/disk_based_cert_cache.h"

#include <vector>

#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/histogram.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/disk_cache/disk_cache.h"

namespace net {

namespace {

// TODO(brandonsalmon): change this number to improve performance.
const size_t kMemoryCacheMaxSize = 30;

// Used to obtain a unique cache key for a certificate in the form of
// "cert:<hash>".
std::string GetCacheKeyForCert(
    const X509Certificate::OSCertHandle cert_handle) {
  SHA1HashValue fingerprint =
      X509Certificate::CalculateFingerprint(cert_handle);

  return "cert:" +
         base::HexEncode(fingerprint.data, arraysize(fingerprint.data));
}

enum CacheResult {
  MEMORY_CACHE_HIT = 0,
  DISK_CACHE_HIT,
  DISK_CACHE_ENTRY_CORRUPT,
  DISK_CACHE_ERROR,
  CACHE_RESULT_MAX
};

void RecordCacheResult(CacheResult result) {
  UMA_HISTOGRAM_ENUMERATION(
      "DiskBasedCertCache.CertIoCacheResult", result, CACHE_RESULT_MAX);
}

}  // namespace

// WriteWorkers represent pending SetCertificate jobs in the DiskBasedCertCache.
// Each certificate requested to be stored is assigned a WriteWorker.
// The same certificate should not have multiple WriteWorkers at the same
// time; instead, add a user callback to the existing WriteWorker.
class DiskBasedCertCache::WriteWorker {
 public:
  // |backend| is the backend to store |certificate| in, using
  // |key| as the key for the disk_cache::Entry.
  // |cleanup_callback| is called to clean up this ReadWorker,
  // regardless of success or failure.
  WriteWorker(disk_cache::Backend* backend,
              const std::string& key,
              X509Certificate::OSCertHandle cert_handle,
              const base::Closure& cleanup_callback);

  ~WriteWorker();

  // Writes the given certificate to the cache. On completion, will invoke all
  // user callbacks.
  void Start();

  // Adds a callback to the set of callbacks to be run when this
  // WriteWorker finishes processing.
  void AddCallback(const SetCallback& user_callback);

  // Signals the WriteWorker to abort early. The WriteWorker will be destroyed
  // upon the completion of any pending callbacks. User callbacks will be
  // invoked with an empty string.
  void Cancel();

 private:
  enum State {
    STATE_OPEN,
    STATE_OPEN_COMPLETE,
    STATE_CREATE,
    STATE_CREATE_COMPLETE,
    STATE_WRITE,
    STATE_WRITE_COMPLETE,
    STATE_NONE
  };

  void OnIOComplete(int rv);
  int DoLoop(int rv);

  int DoOpen();
  int DoOpenComplete(int rv);
  int DoCreate();
  int DoCreateComplete(int rv);
  int DoWrite();
  int DoWriteComplete(int rv);

  void Finish(int rv);

  // Invokes all of the |user_callbacks_|
  void RunCallbacks(int rv);

  disk_cache::Backend* backend_;
  const X509Certificate::OSCertHandle cert_handle_;
  std::string key_;
  bool canceled_;

  disk_cache::Entry* entry_;
  State next_state_;
  scoped_refptr<IOBuffer> buffer_;
  int io_buf_len_;

  base::Closure cleanup_callback_;
  std::vector<SetCallback> user_callbacks_;
  CompletionCallback io_callback_;
};

DiskBasedCertCache::WriteWorker::WriteWorker(
    disk_cache::Backend* backend,
    const std::string& key,
    X509Certificate::OSCertHandle cert_handle,
    const base::Closure& cleanup_callback)
    : backend_(backend),
      cert_handle_(X509Certificate::DupOSCertHandle(cert_handle)),
      key_(key),
      canceled_(false),
      entry_(NULL),
      next_state_(STATE_NONE),
      io_buf_len_(0),
      cleanup_callback_(cleanup_callback),
      io_callback_(
          base::Bind(&WriteWorker::OnIOComplete, base::Unretained(this))) {
}

DiskBasedCertCache::WriteWorker::~WriteWorker() {
  if (cert_handle_)
    X509Certificate::FreeOSCertHandle(cert_handle_);
  if (entry_)
    entry_->Close();
}

void DiskBasedCertCache::WriteWorker::Start() {
  DCHECK_EQ(STATE_NONE, next_state_);

  next_state_ = STATE_OPEN;
  int rv = DoLoop(OK);

  if (rv == ERR_IO_PENDING)
    return;

  Finish(rv);
}

void DiskBasedCertCache::WriteWorker::AddCallback(
    const SetCallback& user_callback) {
  user_callbacks_.push_back(user_callback);
}

void DiskBasedCertCache::WriteWorker::Cancel() {
  canceled_ = true;
}

void DiskBasedCertCache::WriteWorker::OnIOComplete(int rv) {
  if (canceled_) {
    Finish(ERR_FAILED);
    return;
  }

  rv = DoLoop(rv);

  if (rv == ERR_IO_PENDING)
    return;

  Finish(rv);
}

int DiskBasedCertCache::WriteWorker::DoLoop(int rv) {
  do {
    State state = next_state_;
    next_state_ = STATE_NONE;
    switch (state) {
      case STATE_OPEN:
        rv = DoOpen();
        break;
      case STATE_OPEN_COMPLETE:
        rv = DoOpenComplete(rv);
        break;
      case STATE_CREATE:
        rv = DoCreate();
        break;
      case STATE_CREATE_COMPLETE:
        rv = DoCreateComplete(rv);
        break;
      case STATE_WRITE:
        rv = DoWrite();
        break;
      case STATE_WRITE_COMPLETE:
        rv = DoWriteComplete(rv);
        break;
      case STATE_NONE:
        NOTREACHED();
        break;
    }
  } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);

  return rv;
}

int DiskBasedCertCache::WriteWorker::DoOpen() {
  next_state_ = STATE_OPEN_COMPLETE;
  return backend_->OpenEntry(key_, &entry_, io_callback_);
}

int DiskBasedCertCache::WriteWorker::DoOpenComplete(int rv) {
  // The entry doesn't exist yet, so we should create it.
  if (rv < 0) {
    next_state_ = STATE_CREATE;
    return OK;
  }

  next_state_ = STATE_WRITE;
  return OK;
}

int DiskBasedCertCache::WriteWorker::DoCreate() {
  next_state_ = STATE_CREATE_COMPLETE;
  return backend_->CreateEntry(key_, &entry_, io_callback_);
}

int DiskBasedCertCache::WriteWorker::DoCreateComplete(int rv) {
  if (rv < 0)
    return rv;

  next_state_ = STATE_WRITE;
  return OK;
}

int DiskBasedCertCache::WriteWorker::DoWrite() {
  std::string write_data;
  bool encoded = X509Certificate::GetDEREncoded(cert_handle_, &write_data);

  if (!encoded)
    return ERR_FAILED;

  buffer_ = new IOBuffer(write_data.size());
  io_buf_len_ = write_data.size();
  memcpy(buffer_->data(), write_data.data(), io_buf_len_);

  next_state_ = STATE_WRITE_COMPLETE;

  return entry_->WriteData(0 /* index */,
                           0 /* offset */,
                           buffer_.get(),
                           write_data.size(),
                           io_callback_,
                           true /* truncate */);
}

int DiskBasedCertCache::WriteWorker::DoWriteComplete(int rv) {
  if (rv < io_buf_len_)
    return ERR_FAILED;

  return OK;
}

void DiskBasedCertCache::WriteWorker::Finish(int rv) {
  cleanup_callback_.Run();
  cleanup_callback_.Reset();
  RunCallbacks(rv);
  delete this;
}

void DiskBasedCertCache::WriteWorker::RunCallbacks(int rv) {
  std::string key;
  if (rv >= 0)
    key = key_;

  for (std::vector<SetCallback>::const_iterator it = user_callbacks_.begin();
       it != user_callbacks_.end();
       ++it) {
    it->Run(key);
  }
  user_callbacks_.clear();
}

// ReadWorkers represent pending GetCertificate jobs in the DiskBasedCertCache.
// Each certificate requested to be retrieved from the cache is assigned a
// ReadWorker. The same |key| should not have multiple ReadWorkers at the
// same time; instead, call AddCallback to add a user callback to the
// existing ReadWorker.
class DiskBasedCertCache::ReadWorker {
 public:
  // |backend| is the backend to read |certificate| from, using
  // |key| as the key for the disk_cache::Entry.
  // |cleanup_callback| is called to clean up this ReadWorker,
  // regardless of success or failure.
  ReadWorker(disk_cache::Backend* backend,
             const std::string& key,
             const GetCallback& cleanup_callback);

  ~ReadWorker();

  // Reads the given certificate from the cache. On completion, will invoke all
  // user callbacks.
  void Start();

  // Adds a callback to the set of callbacks to be run when this
  // ReadWorker finishes processing.
  void AddCallback(const GetCallback& user_callback);

  // Signals the ReadWorker to abort early. The ReadWorker will be destroyed
  // upon the completion of any pending callbacks. User callbacks will be
  // invoked with a NULL cert handle.
  void Cancel();

 private:
  enum State {
    STATE_OPEN,
    STATE_OPEN_COMPLETE,
    STATE_READ,
    STATE_READ_COMPLETE,
    STATE_NONE
  };

  void OnIOComplete(int rv);
  int DoLoop(int rv);
  int DoOpen();
  int DoOpenComplete(int rv);
  int DoRead();
  int DoReadComplete(int rv);
  void Finish(int rv);

  // Invokes all of |user_callbacks_|
  void RunCallbacks();

  disk_cache::Backend* backend_;
  X509Certificate::OSCertHandle cert_handle_;
  std::string key_;
  bool canceled_;

  disk_cache::Entry* entry_;

  State next_state_;
  scoped_refptr<IOBuffer> buffer_;
  int io_buf_len_;

  GetCallback cleanup_callback_;
  std::vector<GetCallback> user_callbacks_;
  CompletionCallback io_callback_;
};

DiskBasedCertCache::ReadWorker::ReadWorker(disk_cache::Backend* backend,
                                           const std::string& key,
                                           const GetCallback& cleanup_callback)
    : backend_(backend),
      cert_handle_(NULL),
      key_(key),
      canceled_(false),
      entry_(NULL),
      next_state_(STATE_NONE),
      io_buf_len_(0),
      cleanup_callback_(cleanup_callback),
      io_callback_(
          base::Bind(&ReadWorker::OnIOComplete, base::Unretained(this))) {
}

DiskBasedCertCache::ReadWorker::~ReadWorker() {
  if (entry_)
    entry_->Close();
  if (cert_handle_)
    X509Certificate::FreeOSCertHandle(cert_handle_);
}

void DiskBasedCertCache::ReadWorker::Start() {
  DCHECK_EQ(STATE_NONE, next_state_);
  next_state_ = STATE_OPEN;
  int rv = DoLoop(OK);

  if (rv == ERR_IO_PENDING)
    return;

  Finish(rv);
}

void DiskBasedCertCache::ReadWorker::AddCallback(
    const GetCallback& user_callback) {
  user_callbacks_.push_back(user_callback);
}

void DiskBasedCertCache::ReadWorker::Cancel() {
  canceled_ = true;
}

void DiskBasedCertCache::ReadWorker::OnIOComplete(int rv) {
  if (canceled_) {
    Finish(ERR_FAILED);
    return;
  }

  rv = DoLoop(rv);

  if (rv == ERR_IO_PENDING)
    return;

  Finish(rv);
}

int DiskBasedCertCache::ReadWorker::DoLoop(int rv) {
  do {
    State state = next_state_;
    next_state_ = STATE_NONE;
    switch (state) {
      case STATE_OPEN:
        rv = DoOpen();
        break;
      case STATE_OPEN_COMPLETE:
        rv = DoOpenComplete(rv);
        break;
      case STATE_READ:
        rv = DoRead();
        break;
      case STATE_READ_COMPLETE:
        rv = DoReadComplete(rv);
        break;
      case STATE_NONE:
        NOTREACHED();
        break;
    }
  } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);

  return rv;
}

int DiskBasedCertCache::ReadWorker::DoOpen() {
  next_state_ = STATE_OPEN_COMPLETE;
  return backend_->OpenEntry(key_, &entry_, io_callback_);
}

int DiskBasedCertCache::ReadWorker::DoOpenComplete(int rv) {
  if (rv < 0) {
    RecordCacheResult(DISK_CACHE_ERROR);
    return rv;
  }

  next_state_ = STATE_READ;
  return OK;
}

int DiskBasedCertCache::ReadWorker::DoRead() {
  next_state_ = STATE_READ_COMPLETE;
  io_buf_len_ = entry_->GetDataSize(0 /* index */);
  buffer_ = new IOBuffer(io_buf_len_);
  return entry_->ReadData(
      0 /* index */, 0 /* offset */, buffer_.get(), io_buf_len_, io_callback_);
}

int DiskBasedCertCache::ReadWorker::DoReadComplete(int rv) {
  // The cache should return the entire buffer length. If it does not,
  // it is probably indicative of an issue other than corruption.
  if (rv < io_buf_len_) {
    RecordCacheResult(DISK_CACHE_ERROR);
    return ERR_FAILED;
  }
  cert_handle_ = X509Certificate::CreateOSCertHandleFromBytes(buffer_->data(),
                                                              io_buf_len_);
  if (!cert_handle_) {
    RecordCacheResult(DISK_CACHE_ENTRY_CORRUPT);
    return ERR_FAILED;
  }

  RecordCacheResult(DISK_CACHE_HIT);
  return OK;
}

void DiskBasedCertCache::ReadWorker::Finish(int rv) {
  cleanup_callback_.Run(cert_handle_);
  cleanup_callback_.Reset();
  RunCallbacks();
  delete this;
}

void DiskBasedCertCache::ReadWorker::RunCallbacks() {
  for (std::vector<GetCallback>::const_iterator it = user_callbacks_.begin();
       it != user_callbacks_.end();
       ++it) {
    it->Run(cert_handle_);
  }
  user_callbacks_.clear();
}

void DiskBasedCertCache::CertFree::operator()(
    X509Certificate::OSCertHandle cert_handle) {
  X509Certificate::FreeOSCertHandle(cert_handle);
}

DiskBasedCertCache::DiskBasedCertCache(disk_cache::Backend* backend)
    : backend_(backend),
      mru_cert_cache_(kMemoryCacheMaxSize),
      mem_cache_hits_(0),
      mem_cache_misses_(0),
      weak_factory_(this) {
  DCHECK(backend_);
}

DiskBasedCertCache::~DiskBasedCertCache() {
  for (WriteWorkerMap::iterator it = write_worker_map_.begin();
       it != write_worker_map_.end();
       ++it) {
    it->second->Cancel();
  }
  for (ReadWorkerMap::iterator it = read_worker_map_.begin();
       it != read_worker_map_.end();
       ++it) {
    it->second->Cancel();
  }
}

void DiskBasedCertCache::GetCertificate(const std::string& key,
                                        const GetCallback& cb) {
  DCHECK(!key.empty());

  // If the handle is already in the MRU cache, just return that (via callback).
  // Note, this will also bring the cert_handle to the front of the recency
  // list in the MRU cache.
  MRUCertCache::iterator mru_it = mru_cert_cache_.Get(key);
  if (mru_it != mru_cert_cache_.end()) {
    RecordCacheResult(MEMORY_CACHE_HIT);
    ++mem_cache_hits_;
    cb.Run(mru_it->second);
    return;
  }
  ++mem_cache_misses_;

  ReadWorkerMap::iterator it = read_worker_map_.find(key);

  if (it == read_worker_map_.end()) {
    ReadWorker* worker =
        new ReadWorker(backend_,
                       key,
                       base::Bind(&DiskBasedCertCache::FinishedReadOperation,
                                  weak_factory_.GetWeakPtr(),
                                  key));
    read_worker_map_[key] = worker;
    worker->AddCallback(cb);
    worker->Start();
  } else {
    it->second->AddCallback(cb);
  }
}

void DiskBasedCertCache::SetCertificate(
    const X509Certificate::OSCertHandle cert_handle,
    const SetCallback& cb) {
  DCHECK(!cb.is_null());
  DCHECK(cert_handle);
  std::string key = GetCacheKeyForCert(cert_handle);

  WriteWorkerMap::iterator it = write_worker_map_.find(key);

  if (it == write_worker_map_.end()) {
    WriteWorker* worker =
        new WriteWorker(backend_,
                        key,
                        cert_handle,
                        base::Bind(&DiskBasedCertCache::FinishedWriteOperation,
                                   weak_factory_.GetWeakPtr(),
                                   key,
                                   cert_handle));
    write_worker_map_[key] = worker;
    worker->AddCallback(cb);
    worker->Start();
  } else {
    it->second->AddCallback(cb);
  }
}

void DiskBasedCertCache::FinishedReadOperation(
    const std::string& key,
    X509Certificate::OSCertHandle cert_handle) {
  if (cert_handle)
    mru_cert_cache_.Put(key, X509Certificate::DupOSCertHandle(cert_handle));
  read_worker_map_.erase(key);
}

void DiskBasedCertCache::FinishedWriteOperation(
    const std::string& key,
    X509Certificate::OSCertHandle cert_handle) {
  write_worker_map_.erase(key);
  if (!key.empty())
    mru_cert_cache_.Put(key, X509Certificate::DupOSCertHandle(cert_handle));
}

}  // namespace net