// 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 #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:". 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 buffer_; int io_buf_len_; base::Closure cleanup_callback_; std::vector 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::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 buffer_; int io_buf_len_; GetCallback cleanup_callback_; std::vector 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::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