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_OPEN,
85    STATE_OPEN_COMPLETE,
86    STATE_CREATE,
87    STATE_CREATE_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 DoOpen();
97  int DoOpenComplete(int rv);
98  int DoCreate();
99  int DoCreateComplete(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
150  next_state_ = STATE_OPEN;
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 state = next_state_;
185    next_state_ = STATE_NONE;
186    switch (state) {
187      case STATE_OPEN:
188        rv = DoOpen();
189        break;
190      case STATE_OPEN_COMPLETE:
191        rv = DoOpenComplete(rv);
192        break;
193      case STATE_CREATE:
194        rv = DoCreate();
195        break;
196      case STATE_CREATE_COMPLETE:
197        rv = DoCreateComplete(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 && next_state_ != STATE_NONE);
210
211  return rv;
212}
213
214int DiskBasedCertCache::WriteWorker::DoOpen() {
215  next_state_ = STATE_OPEN_COMPLETE;
216  return backend_->OpenEntry(key_, &entry_, io_callback_);
217}
218
219int DiskBasedCertCache::WriteWorker::DoOpenComplete(int rv) {
220  // The entry doesn't exist yet, so we should create it.
221  if (rv < 0) {
222    next_state_ = STATE_CREATE;
223    return OK;
224  }
225
226  next_state_ = STATE_WRITE;
227  return OK;
228}
229
230int DiskBasedCertCache::WriteWorker::DoCreate() {
231  next_state_ = STATE_CREATE_COMPLETE;
232  return backend_->CreateEntry(key_, &entry_, io_callback_);
233}
234
235int DiskBasedCertCache::WriteWorker::DoCreateComplete(int rv) {
236  if (rv < 0)
237    return rv;
238
239  next_state_ = STATE_WRITE;
240  return OK;
241}
242
243int DiskBasedCertCache::WriteWorker::DoWrite() {
244  std::string write_data;
245  bool encoded = X509Certificate::GetDEREncoded(cert_handle_, &write_data);
246
247  if (!encoded)
248    return ERR_FAILED;
249
250  buffer_ = new IOBuffer(write_data.size());
251  io_buf_len_ = write_data.size();
252  memcpy(buffer_->data(), write_data.data(), io_buf_len_);
253
254  next_state_ = STATE_WRITE_COMPLETE;
255
256  return entry_->WriteData(0 /* index */,
257                           0 /* offset */,
258                           buffer_.get(),
259                           write_data.size(),
260                           io_callback_,
261                           true /* truncate */);
262}
263
264int DiskBasedCertCache::WriteWorker::DoWriteComplete(int rv) {
265  if (rv < io_buf_len_)
266    return ERR_FAILED;
267
268  return OK;
269}
270
271void DiskBasedCertCache::WriteWorker::Finish(int rv) {
272  cleanup_callback_.Run();
273  cleanup_callback_.Reset();
274  RunCallbacks(rv);
275  delete this;
276}
277
278void DiskBasedCertCache::WriteWorker::RunCallbacks(int rv) {
279  std::string key;
280  if (rv >= 0)
281    key = key_;
282
283  for (std::vector<SetCallback>::const_iterator it = user_callbacks_.begin();
284       it != user_callbacks_.end();
285       ++it) {
286    it->Run(key);
287  }
288  user_callbacks_.clear();
289}
290
291// ReadWorkers represent pending GetCertificate jobs in the DiskBasedCertCache.
292// Each certificate requested to be retrieved from the cache is assigned a
293// ReadWorker. The same |key| should not have multiple ReadWorkers at the
294// same time; instead, call AddCallback to add a user callback to the
295// existing ReadWorker.
296class DiskBasedCertCache::ReadWorker {
297 public:
298  // |backend| is the backend to read |certificate| from, using
299  // |key| as the key for the disk_cache::Entry.
300  // |cleanup_callback| is called to clean up this ReadWorker,
301  // regardless of success or failure.
302  ReadWorker(disk_cache::Backend* backend,
303             const std::string& key,
304             const GetCallback& cleanup_callback);
305
306  ~ReadWorker();
307
308  // Reads the given certificate from the cache. On completion, will invoke all
309  // user callbacks.
310  void Start();
311
312  // Adds a callback to the set of callbacks to be run when this
313  // ReadWorker finishes processing.
314  void AddCallback(const GetCallback& user_callback);
315
316  // Signals the ReadWorker to abort early. The ReadWorker will be destroyed
317  // upon the completion of any pending callbacks. User callbacks will be
318  // invoked with a NULL cert handle.
319  void Cancel();
320
321 private:
322  enum State {
323    STATE_OPEN,
324    STATE_OPEN_COMPLETE,
325    STATE_READ,
326    STATE_READ_COMPLETE,
327    STATE_NONE
328  };
329
330  void OnIOComplete(int rv);
331  int DoLoop(int rv);
332  int DoOpen();
333  int DoOpenComplete(int rv);
334  int DoRead();
335  int DoReadComplete(int rv);
336  void Finish(int rv);
337
338  // Invokes all of |user_callbacks_|
339  void RunCallbacks();
340
341  disk_cache::Backend* backend_;
342  X509Certificate::OSCertHandle cert_handle_;
343  std::string key_;
344  bool canceled_;
345
346  disk_cache::Entry* entry_;
347
348  State next_state_;
349  scoped_refptr<IOBuffer> buffer_;
350  int io_buf_len_;
351
352  GetCallback cleanup_callback_;
353  std::vector<GetCallback> user_callbacks_;
354  CompletionCallback io_callback_;
355};
356
357DiskBasedCertCache::ReadWorker::ReadWorker(disk_cache::Backend* backend,
358                                           const std::string& key,
359                                           const GetCallback& cleanup_callback)
360    : backend_(backend),
361      cert_handle_(NULL),
362      key_(key),
363      canceled_(false),
364      entry_(NULL),
365      next_state_(STATE_NONE),
366      io_buf_len_(0),
367      cleanup_callback_(cleanup_callback),
368      io_callback_(
369          base::Bind(&ReadWorker::OnIOComplete, base::Unretained(this))) {
370}
371
372DiskBasedCertCache::ReadWorker::~ReadWorker() {
373  if (entry_)
374    entry_->Close();
375  if (cert_handle_)
376    X509Certificate::FreeOSCertHandle(cert_handle_);
377}
378
379void DiskBasedCertCache::ReadWorker::Start() {
380  DCHECK_EQ(STATE_NONE, next_state_);
381  next_state_ = STATE_OPEN;
382  int rv = DoLoop(OK);
383
384  if (rv == ERR_IO_PENDING)
385    return;
386
387  Finish(rv);
388}
389
390void DiskBasedCertCache::ReadWorker::AddCallback(
391    const GetCallback& user_callback) {
392  user_callbacks_.push_back(user_callback);
393}
394
395void DiskBasedCertCache::ReadWorker::Cancel() {
396  canceled_ = true;
397}
398
399void DiskBasedCertCache::ReadWorker::OnIOComplete(int rv) {
400  if (canceled_) {
401    Finish(ERR_FAILED);
402    return;
403  }
404
405  rv = DoLoop(rv);
406
407  if (rv == ERR_IO_PENDING)
408    return;
409
410  Finish(rv);
411}
412
413int DiskBasedCertCache::ReadWorker::DoLoop(int rv) {
414  do {
415    State state = next_state_;
416    next_state_ = STATE_NONE;
417    switch (state) {
418      case STATE_OPEN:
419        rv = DoOpen();
420        break;
421      case STATE_OPEN_COMPLETE:
422        rv = DoOpenComplete(rv);
423        break;
424      case STATE_READ:
425        rv = DoRead();
426        break;
427      case STATE_READ_COMPLETE:
428        rv = DoReadComplete(rv);
429        break;
430      case STATE_NONE:
431        NOTREACHED();
432        break;
433    }
434  } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
435
436  return rv;
437}
438
439int DiskBasedCertCache::ReadWorker::DoOpen() {
440  next_state_ = STATE_OPEN_COMPLETE;
441  return backend_->OpenEntry(key_, &entry_, io_callback_);
442}
443
444int DiskBasedCertCache::ReadWorker::DoOpenComplete(int rv) {
445  if (rv < 0) {
446    RecordCacheResult(DISK_CACHE_ERROR);
447    return rv;
448  }
449
450  next_state_ = STATE_READ;
451  return OK;
452}
453
454int DiskBasedCertCache::ReadWorker::DoRead() {
455  next_state_ = STATE_READ_COMPLETE;
456  io_buf_len_ = entry_->GetDataSize(0 /* index */);
457  buffer_ = new IOBuffer(io_buf_len_);
458  return entry_->ReadData(
459      0 /* index */, 0 /* offset */, buffer_.get(), io_buf_len_, io_callback_);
460}
461
462int DiskBasedCertCache::ReadWorker::DoReadComplete(int rv) {
463  // The cache should return the entire buffer length. If it does not,
464  // it is probably indicative of an issue other than corruption.
465  if (rv < io_buf_len_) {
466    RecordCacheResult(DISK_CACHE_ERROR);
467    return ERR_FAILED;
468  }
469  cert_handle_ = X509Certificate::CreateOSCertHandleFromBytes(buffer_->data(),
470                                                              io_buf_len_);
471  if (!cert_handle_) {
472    RecordCacheResult(DISK_CACHE_ENTRY_CORRUPT);
473    return ERR_FAILED;
474  }
475
476  RecordCacheResult(DISK_CACHE_HIT);
477  return OK;
478}
479
480void DiskBasedCertCache::ReadWorker::Finish(int rv) {
481  cleanup_callback_.Run(cert_handle_);
482  cleanup_callback_.Reset();
483  RunCallbacks();
484  delete this;
485}
486
487void DiskBasedCertCache::ReadWorker::RunCallbacks() {
488  for (std::vector<GetCallback>::const_iterator it = user_callbacks_.begin();
489       it != user_callbacks_.end();
490       ++it) {
491    it->Run(cert_handle_);
492  }
493  user_callbacks_.clear();
494}
495
496void DiskBasedCertCache::CertFree::operator()(
497    X509Certificate::OSCertHandle cert_handle) {
498  X509Certificate::FreeOSCertHandle(cert_handle);
499}
500
501DiskBasedCertCache::DiskBasedCertCache(disk_cache::Backend* backend)
502    : backend_(backend),
503      mru_cert_cache_(kMemoryCacheMaxSize),
504      mem_cache_hits_(0),
505      mem_cache_misses_(0),
506      weak_factory_(this) {
507  DCHECK(backend_);
508}
509
510DiskBasedCertCache::~DiskBasedCertCache() {
511  for (WriteWorkerMap::iterator it = write_worker_map_.begin();
512       it != write_worker_map_.end();
513       ++it) {
514    it->second->Cancel();
515  }
516  for (ReadWorkerMap::iterator it = read_worker_map_.begin();
517       it != read_worker_map_.end();
518       ++it) {
519    it->second->Cancel();
520  }
521}
522
523void DiskBasedCertCache::GetCertificate(const std::string& key,
524                                        const GetCallback& cb) {
525  DCHECK(!key.empty());
526
527  // If the handle is already in the MRU cache, just return that (via callback).
528  // Note, this will also bring the cert_handle to the front of the recency
529  // list in the MRU cache.
530  MRUCertCache::iterator mru_it = mru_cert_cache_.Get(key);
531  if (mru_it != mru_cert_cache_.end()) {
532    RecordCacheResult(MEMORY_CACHE_HIT);
533    ++mem_cache_hits_;
534    cb.Run(mru_it->second);
535    return;
536  }
537  ++mem_cache_misses_;
538
539  ReadWorkerMap::iterator it = read_worker_map_.find(key);
540
541  if (it == read_worker_map_.end()) {
542    ReadWorker* worker =
543        new ReadWorker(backend_,
544                       key,
545                       base::Bind(&DiskBasedCertCache::FinishedReadOperation,
546                                  weak_factory_.GetWeakPtr(),
547                                  key));
548    read_worker_map_[key] = worker;
549    worker->AddCallback(cb);
550    worker->Start();
551  } else {
552    it->second->AddCallback(cb);
553  }
554}
555
556void DiskBasedCertCache::SetCertificate(
557    const X509Certificate::OSCertHandle cert_handle,
558    const SetCallback& cb) {
559  DCHECK(!cb.is_null());
560  DCHECK(cert_handle);
561  std::string key = GetCacheKeyForCert(cert_handle);
562
563  WriteWorkerMap::iterator it = write_worker_map_.find(key);
564
565  if (it == write_worker_map_.end()) {
566    WriteWorker* worker =
567        new WriteWorker(backend_,
568                        key,
569                        cert_handle,
570                        base::Bind(&DiskBasedCertCache::FinishedWriteOperation,
571                                   weak_factory_.GetWeakPtr(),
572                                   key,
573                                   cert_handle));
574    write_worker_map_[key] = worker;
575    worker->AddCallback(cb);
576    worker->Start();
577  } else {
578    it->second->AddCallback(cb);
579  }
580}
581
582void DiskBasedCertCache::FinishedReadOperation(
583    const std::string& key,
584    X509Certificate::OSCertHandle cert_handle) {
585  if (cert_handle)
586    mru_cert_cache_.Put(key, X509Certificate::DupOSCertHandle(cert_handle));
587  read_worker_map_.erase(key);
588}
589
590void DiskBasedCertCache::FinishedWriteOperation(
591    const std::string& key,
592    X509Certificate::OSCertHandle cert_handle) {
593  write_worker_map_.erase(key);
594  if (!key.empty())
595    mru_cert_cache_.Put(key, X509Certificate::DupOSCertHandle(cert_handle));
596}
597
598}  // namespace net
599