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