disk_based_cert_cache.cc revision 5f1c94371a64b3196d4be9466099bb892df9b88e
1// Copyright (c) 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "net/http/disk_based_cert_cache.h"
6
7#include <vector>
8
9#include "base/bind.h"
10#include "base/callback_helpers.h"
11#include "base/memory/ref_counted.h"
12#include "base/metrics/histogram.h"
13#include "base/stl_util.h"
14#include "base/strings/string_number_conversions.h"
15#include "net/base/io_buffer.h"
16#include "net/base/net_errors.h"
17#include "net/disk_cache/disk_cache.h"
18
19namespace net {
20
21namespace {
22
23// TODO(brandonsalmon): change this number to improve performance.
24const size_t kMemoryCacheMaxSize = 30;
25
26// Used to obtain a unique cache key for a certificate in the form of
27// "cert:<hash>".
28std::string GetCacheKeyForCert(
29    const X509Certificate::OSCertHandle cert_handle) {
30  SHA1HashValue fingerprint =
31      X509Certificate::CalculateFingerprint(cert_handle);
32
33  return "cert:" +
34         base::HexEncode(fingerprint.data, arraysize(fingerprint.data));
35}
36
37enum CacheResult {
38  MEMORY_CACHE_HIT = 0,
39  DISK_CACHE_HIT,
40  DISK_CACHE_ENTRY_CORRUPT,
41  DISK_CACHE_ERROR,
42  CACHE_RESULT_MAX
43};
44
45void RecordCacheResult(CacheResult result) {
46  UMA_HISTOGRAM_ENUMERATION(
47      "DiskBasedCertCache.CertIoCacheResult", result, CACHE_RESULT_MAX);
48}
49
50}  // namespace
51
52// WriteWorkers represent pending SetCertificate jobs in the DiskBasedCertCache.
53// Each certificate requested to be stored is assigned a WriteWorker.
54// The same certificate should not have multiple WriteWorkers at the same
55// time; instead, add a user callback to the existing WriteWorker.
56class DiskBasedCertCache::WriteWorker {
57 public:
58  // |backend| is the backend to store |certificate| in, using
59  // |key| as the key for the disk_cache::Entry.
60  // |cleanup_callback| is called to clean up this ReadWorker,
61  // regardless of success or failure.
62  WriteWorker(disk_cache::Backend* backend,
63              const std::string& key,
64              X509Certificate::OSCertHandle cert_handle,
65              const base::Closure& cleanup_callback);
66
67  ~WriteWorker();
68
69  // Writes the given certificate to the cache. On completion, will invoke all
70  // user callbacks.
71  void Start();
72
73  // Adds a callback to the set of callbacks to be run when this
74  // WriteWorker finishes processing.
75  void AddCallback(const SetCallback& user_callback);
76
77  // Signals the WriteWorker to abort early. The WriteWorker will be destroyed
78  // upon the completion of any pending callbacks. User callbacks will be
79  // invoked with an empty string.
80  void Cancel();
81
82 private:
83  enum State {
84    STATE_CREATE,
85    STATE_CREATE_COMPLETE,
86    STATE_OPEN,
87    STATE_OPEN_COMPLETE,
88    STATE_WRITE,
89    STATE_WRITE_COMPLETE,
90    STATE_NONE
91  };
92
93  void OnIOComplete(int rv);
94  int DoLoop(int rv);
95
96  int DoCreate();
97  int DoCreateComplete(int rv);
98  int DoOpen();
99  int DoOpenComplete(int rv);
100  int DoWrite();
101  int DoWriteComplete(int rv);
102
103  void Finish(int rv);
104
105  // Invokes all of the |user_callbacks_|
106  void RunCallbacks(int rv);
107
108  disk_cache::Backend* backend_;
109  const X509Certificate::OSCertHandle cert_handle_;
110  std::string key_;
111  bool canceled_;
112
113  disk_cache::Entry* entry_;
114  State next_state_;
115  scoped_refptr<IOBuffer> buffer_;
116  int io_buf_len_;
117
118  base::Closure cleanup_callback_;
119  std::vector<SetCallback> user_callbacks_;
120  CompletionCallback io_callback_;
121};
122
123DiskBasedCertCache::WriteWorker::WriteWorker(
124    disk_cache::Backend* backend,
125    const std::string& key,
126    X509Certificate::OSCertHandle cert_handle,
127    const base::Closure& cleanup_callback)
128    : backend_(backend),
129      cert_handle_(X509Certificate::DupOSCertHandle(cert_handle)),
130      key_(key),
131      canceled_(false),
132      entry_(NULL),
133      next_state_(STATE_NONE),
134      io_buf_len_(0),
135      cleanup_callback_(cleanup_callback),
136      io_callback_(
137          base::Bind(&WriteWorker::OnIOComplete, base::Unretained(this))) {
138}
139
140DiskBasedCertCache::WriteWorker::~WriteWorker() {
141  if (cert_handle_)
142    X509Certificate::FreeOSCertHandle(cert_handle_);
143  if (entry_)
144    entry_->Close();
145}
146
147void DiskBasedCertCache::WriteWorker::Start() {
148  DCHECK_EQ(STATE_NONE, next_state_);
149  next_state_ = STATE_CREATE;
150  int rv = DoLoop(OK);
151
152  if (rv == ERR_IO_PENDING)
153    return;
154
155  Finish(rv);
156}
157
158void DiskBasedCertCache::WriteWorker::AddCallback(
159    const SetCallback& user_callback) {
160  user_callbacks_.push_back(user_callback);
161}
162
163void DiskBasedCertCache::WriteWorker::Cancel() {
164  canceled_ = true;
165}
166
167void DiskBasedCertCache::WriteWorker::OnIOComplete(int rv) {
168  if (canceled_) {
169    Finish(ERR_FAILED);
170    return;
171  }
172
173  rv = DoLoop(rv);
174
175  if (rv == ERR_IO_PENDING)
176    return;
177
178  Finish(rv);
179}
180
181int DiskBasedCertCache::WriteWorker::DoLoop(int rv) {
182  do {
183    State state = next_state_;
184    next_state_ = STATE_NONE;
185    switch (state) {
186      case STATE_CREATE:
187        rv = DoCreate();
188        break;
189      case STATE_CREATE_COMPLETE:
190        rv = DoCreateComplete(rv);
191        break;
192      case STATE_OPEN:
193        rv = DoOpen();
194        break;
195      case STATE_OPEN_COMPLETE:
196        rv = DoOpenComplete(rv);
197        break;
198      case STATE_WRITE:
199        rv = DoWrite();
200        break;
201      case STATE_WRITE_COMPLETE:
202        rv = DoWriteComplete(rv);
203        break;
204      case STATE_NONE:
205        NOTREACHED();
206        break;
207    }
208  } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
209
210  return rv;
211}
212
213int DiskBasedCertCache::WriteWorker::DoCreate() {
214  next_state_ = STATE_CREATE_COMPLETE;
215
216  return backend_->CreateEntry(key_, &entry_, io_callback_);
217}
218
219int DiskBasedCertCache::WriteWorker::DoCreateComplete(int rv) {
220  // An error here usually signifies that the entry already exists.
221  // If this occurs, it is necessary to instead open the previously
222  // existing entry.
223  if (rv < 0) {
224    next_state_ = STATE_OPEN;
225    return OK;
226  }
227
228  next_state_ = STATE_WRITE;
229  return OK;
230}
231
232int DiskBasedCertCache::WriteWorker::DoOpen() {
233  next_state_ = STATE_OPEN_COMPLETE;
234  return backend_->OpenEntry(key_, &entry_, io_callback_);
235}
236
237int DiskBasedCertCache::WriteWorker::DoOpenComplete(int rv) {
238  if (rv < 0)
239    return rv;
240
241  next_state_ = STATE_WRITE;
242  return OK;
243}
244
245int DiskBasedCertCache::WriteWorker::DoWrite() {
246  std::string write_data;
247  bool encoded = X509Certificate::GetDEREncoded(cert_handle_, &write_data);
248
249  if (!encoded)
250    return ERR_FAILED;
251
252  buffer_ = new IOBuffer(write_data.size());
253  io_buf_len_ = write_data.size();
254  memcpy(buffer_->data(), write_data.data(), io_buf_len_);
255
256  next_state_ = STATE_WRITE_COMPLETE;
257
258  return entry_->WriteData(0 /* index */,
259                           0 /* offset */,
260                           buffer_,
261                           write_data.size(),
262                           io_callback_,
263                           true /* truncate */);
264}
265
266int DiskBasedCertCache::WriteWorker::DoWriteComplete(int rv) {
267  if (rv < io_buf_len_)
268    return ERR_FAILED;
269
270  return OK;
271}
272
273void DiskBasedCertCache::WriteWorker::Finish(int rv) {
274  cleanup_callback_.Run();
275  cleanup_callback_.Reset();
276  RunCallbacks(rv);
277  delete this;
278}
279
280void DiskBasedCertCache::WriteWorker::RunCallbacks(int rv) {
281  std::string key;
282  if (rv >= 0)
283    key = key_;
284
285  for (std::vector<SetCallback>::const_iterator it = user_callbacks_.begin();
286       it != user_callbacks_.end();
287       ++it) {
288    it->Run(key);
289  }
290  user_callbacks_.clear();
291}
292
293// ReadWorkers represent pending GetCertificate jobs in the DiskBasedCertCache.
294// Each certificate requested to be retrieved from the cache is assigned a
295// ReadWorker. The same |key| should not have multiple ReadWorkers at the
296// same time; instead, call AddCallback to add a user callback to the
297// existing ReadWorker.
298class DiskBasedCertCache::ReadWorker {
299 public:
300  // |backend| is the backend to read |certificate| from, using
301  // |key| as the key for the disk_cache::Entry.
302  // |cleanup_callback| is called to clean up this ReadWorker,
303  // regardless of success or failure.
304  ReadWorker(disk_cache::Backend* backend,
305             const std::string& key,
306             const GetCallback& cleanup_callback);
307
308  ~ReadWorker();
309
310  // Reads the given certificate from the cache. On completion, will invoke all
311  // user callbacks.
312  void Start();
313
314  // Adds a callback to the set of callbacks to be run when this
315  // ReadWorker finishes processing.
316  void AddCallback(const GetCallback& user_callback);
317
318  // Signals the ReadWorker to abort early. The ReadWorker will be destroyed
319  // upon the completion of any pending callbacks. User callbacks will be
320  // invoked with a NULL cert handle.
321  void Cancel();
322
323 private:
324  enum State {
325    STATE_OPEN,
326    STATE_OPEN_COMPLETE,
327    STATE_READ,
328    STATE_READ_COMPLETE,
329    STATE_NONE
330  };
331
332  void OnIOComplete(int rv);
333  int DoLoop(int rv);
334  int DoOpen();
335  int DoOpenComplete(int rv);
336  int DoRead();
337  int DoReadComplete(int rv);
338  void Finish(int rv);
339
340  // Invokes all of |user_callbacks_|
341  void RunCallbacks();
342
343  disk_cache::Backend* backend_;
344  X509Certificate::OSCertHandle cert_handle_;
345  std::string key_;
346  bool canceled_;
347
348  disk_cache::Entry* entry_;
349
350  State next_state_;
351  scoped_refptr<IOBuffer> buffer_;
352  int io_buf_len_;
353
354  GetCallback cleanup_callback_;
355  std::vector<GetCallback> user_callbacks_;
356  CompletionCallback io_callback_;
357};
358
359DiskBasedCertCache::ReadWorker::ReadWorker(disk_cache::Backend* backend,
360                                           const std::string& key,
361                                           const GetCallback& cleanup_callback)
362    : backend_(backend),
363      cert_handle_(NULL),
364      key_(key),
365      canceled_(false),
366      entry_(NULL),
367      next_state_(STATE_NONE),
368      io_buf_len_(0),
369      cleanup_callback_(cleanup_callback),
370      io_callback_(
371          base::Bind(&ReadWorker::OnIOComplete, base::Unretained(this))) {
372}
373
374DiskBasedCertCache::ReadWorker::~ReadWorker() {
375  if (entry_)
376    entry_->Close();
377  if (cert_handle_)
378    X509Certificate::FreeOSCertHandle(cert_handle_);
379}
380
381void DiskBasedCertCache::ReadWorker::Start() {
382  DCHECK_EQ(STATE_NONE, next_state_);
383  next_state_ = STATE_OPEN;
384  int rv = DoLoop(OK);
385
386  if (rv == ERR_IO_PENDING)
387    return;
388
389  Finish(rv);
390}
391
392void DiskBasedCertCache::ReadWorker::AddCallback(
393    const GetCallback& user_callback) {
394  user_callbacks_.push_back(user_callback);
395}
396
397void DiskBasedCertCache::ReadWorker::Cancel() {
398  canceled_ = true;
399}
400
401void DiskBasedCertCache::ReadWorker::OnIOComplete(int rv) {
402  if (canceled_) {
403    Finish(ERR_FAILED);
404    return;
405  }
406
407  rv = DoLoop(rv);
408
409  if (rv == ERR_IO_PENDING)
410    return;
411
412  Finish(rv);
413}
414
415int DiskBasedCertCache::ReadWorker::DoLoop(int rv) {
416  do {
417    State state = next_state_;
418    next_state_ = STATE_NONE;
419    switch (state) {
420      case STATE_OPEN:
421        rv = DoOpen();
422        break;
423      case STATE_OPEN_COMPLETE:
424        rv = DoOpenComplete(rv);
425        break;
426      case STATE_READ:
427        rv = DoRead();
428        break;
429      case STATE_READ_COMPLETE:
430        rv = DoReadComplete(rv);
431        break;
432      case STATE_NONE:
433        NOTREACHED();
434        break;
435    }
436  } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
437
438  return rv;
439}
440
441int DiskBasedCertCache::ReadWorker::DoOpen() {
442  next_state_ = STATE_OPEN_COMPLETE;
443  return backend_->OpenEntry(key_, &entry_, io_callback_);
444}
445
446int DiskBasedCertCache::ReadWorker::DoOpenComplete(int rv) {
447  if (rv < 0) {
448    RecordCacheResult(DISK_CACHE_ERROR);
449    return rv;
450  }
451
452  next_state_ = STATE_READ;
453  return OK;
454}
455
456int DiskBasedCertCache::ReadWorker::DoRead() {
457  next_state_ = STATE_READ_COMPLETE;
458  io_buf_len_ = entry_->GetDataSize(0 /* index */);
459  buffer_ = new IOBuffer(io_buf_len_);
460  return entry_->ReadData(
461      0 /* index */, 0 /* offset */, buffer_, io_buf_len_, io_callback_);
462}
463
464int DiskBasedCertCache::ReadWorker::DoReadComplete(int rv) {
465  // The cache should return the entire buffer length. If it does not,
466  // it is probably indicative of an issue other than corruption.
467  if (rv < io_buf_len_) {
468    RecordCacheResult(DISK_CACHE_ERROR);
469    return ERR_FAILED;
470  }
471  cert_handle_ = X509Certificate::CreateOSCertHandleFromBytes(buffer_->data(),
472                                                              io_buf_len_);
473  if (!cert_handle_) {
474    RecordCacheResult(DISK_CACHE_ENTRY_CORRUPT);
475    return ERR_FAILED;
476  }
477
478  RecordCacheResult(DISK_CACHE_HIT);
479  return OK;
480}
481
482void DiskBasedCertCache::ReadWorker::Finish(int rv) {
483  cleanup_callback_.Run(cert_handle_);
484  cleanup_callback_.Reset();
485  RunCallbacks();
486  delete this;
487}
488
489void DiskBasedCertCache::ReadWorker::RunCallbacks() {
490  for (std::vector<GetCallback>::const_iterator it = user_callbacks_.begin();
491       it != user_callbacks_.end();
492       ++it) {
493    it->Run(cert_handle_);
494  }
495  user_callbacks_.clear();
496}
497
498void DiskBasedCertCache::CertFree::operator()(
499    X509Certificate::OSCertHandle cert_handle) {
500  X509Certificate::FreeOSCertHandle(cert_handle);
501}
502
503DiskBasedCertCache::DiskBasedCertCache(disk_cache::Backend* backend)
504    : backend_(backend),
505      mru_cert_cache_(kMemoryCacheMaxSize),
506      mem_cache_hits_(0),
507      mem_cache_misses_(0),
508      weak_factory_(this) {
509  DCHECK(backend_);
510}
511
512DiskBasedCertCache::~DiskBasedCertCache() {
513  for (WriteWorkerMap::iterator it = write_worker_map_.begin();
514       it != write_worker_map_.end();
515       ++it) {
516    it->second->Cancel();
517  }
518  for (ReadWorkerMap::iterator it = read_worker_map_.begin();
519       it != read_worker_map_.end();
520       ++it) {
521    it->second->Cancel();
522  }
523}
524
525void DiskBasedCertCache::GetCertificate(const std::string& key,
526                                        const GetCallback& cb) {
527  DCHECK(!key.empty());
528
529  // If the handle is already in the MRU cache, just return that (via callback).
530  // Note, this will also bring the cert_handle to the front of the recency
531  // list in the MRU cache.
532  MRUCertCache::iterator mru_it = mru_cert_cache_.Get(key);
533  if (mru_it != mru_cert_cache_.end()) {
534    RecordCacheResult(MEMORY_CACHE_HIT);
535    ++mem_cache_hits_;
536    cb.Run(mru_it->second);
537    return;
538  }
539  ++mem_cache_misses_;
540
541  ReadWorkerMap::iterator it = read_worker_map_.find(key);
542
543  if (it == read_worker_map_.end()) {
544    ReadWorker* worker =
545        new ReadWorker(backend_,
546                       key,
547                       base::Bind(&DiskBasedCertCache::FinishedReadOperation,
548                                  weak_factory_.GetWeakPtr(),
549                                  key));
550    read_worker_map_[key] = worker;
551    worker->AddCallback(cb);
552    worker->Start();
553  } else {
554    it->second->AddCallback(cb);
555  }
556}
557
558void DiskBasedCertCache::SetCertificate(
559    const X509Certificate::OSCertHandle cert_handle,
560    const SetCallback& cb) {
561  DCHECK(!cb.is_null());
562  DCHECK(cert_handle);
563  std::string key = GetCacheKeyForCert(cert_handle);
564
565  WriteWorkerMap::iterator it = write_worker_map_.find(key);
566
567  if (it == write_worker_map_.end()) {
568    WriteWorker* worker =
569        new WriteWorker(backend_,
570                        key,
571                        cert_handle,
572                        base::Bind(&DiskBasedCertCache::FinishedWriteOperation,
573                                   weak_factory_.GetWeakPtr(),
574                                   key,
575                                   cert_handle));
576    write_worker_map_[key] = worker;
577    worker->AddCallback(cb);
578    worker->Start();
579  } else {
580    it->second->AddCallback(cb);
581  }
582}
583
584void DiskBasedCertCache::FinishedReadOperation(
585    const std::string& key,
586    X509Certificate::OSCertHandle cert_handle) {
587  if (cert_handle)
588    mru_cert_cache_.Put(key, X509Certificate::DupOSCertHandle(cert_handle));
589  read_worker_map_.erase(key);
590}
591
592void DiskBasedCertCache::FinishedWriteOperation(
593    const std::string& key,
594    X509Certificate::OSCertHandle cert_handle) {
595  write_worker_map_.erase(key);
596  if (!key.empty())
597    mru_cert_cache_.Put(key, X509Certificate::DupOSCertHandle(cert_handle));
598}
599
600}  // namespace net
601