| // 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 |