1// Copyright (c) 2012 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 "chrome/browser/net/sqlite_server_bound_cert_store.h"
6
7#include <list>
8#include <set>
9
10#include "base/basictypes.h"
11#include "base/bind.h"
12#include "base/file_util.h"
13#include "base/files/file_path.h"
14#include "base/logging.h"
15#include "base/memory/scoped_ptr.h"
16#include "base/metrics/histogram.h"
17#include "base/strings/string_util.h"
18#include "base/threading/thread.h"
19#include "base/threading/thread_restrictions.h"
20#include "content/public/browser/browser_thread.h"
21#include "net/cert/x509_certificate.h"
22#include "net/cookies/cookie_util.h"
23#include "net/ssl/ssl_client_cert_type.h"
24#include "sql/error_delegate_util.h"
25#include "sql/meta_table.h"
26#include "sql/statement.h"
27#include "sql/transaction.h"
28#include "third_party/sqlite/sqlite3.h"
29#include "url/gurl.h"
30#include "webkit/browser/quota/special_storage_policy.h"
31
32using content::BrowserThread;
33
34// This class is designed to be shared between any calling threads and the
35// database thread. It batches operations and commits them on a timer.
36class SQLiteServerBoundCertStore::Backend
37    : public base::RefCountedThreadSafe<SQLiteServerBoundCertStore::Backend> {
38 public:
39  Backend(const base::FilePath& path,
40          quota::SpecialStoragePolicy* special_storage_policy)
41      : path_(path),
42        num_pending_(0),
43        force_keep_session_state_(false),
44        special_storage_policy_(special_storage_policy),
45        corruption_detected_(false) {}
46
47  // Creates or loads the SQLite database.
48  void Load(const LoadedCallback& loaded_callback);
49
50  // Batch a server bound cert addition.
51  void AddServerBoundCert(
52      const net::DefaultServerBoundCertStore::ServerBoundCert& cert);
53
54  // Batch a server bound cert deletion.
55  void DeleteServerBoundCert(
56      const net::DefaultServerBoundCertStore::ServerBoundCert& cert);
57
58  // Commit any pending operations and close the database.  This must be called
59  // before the object is destructed.
60  void Close();
61
62  void SetForceKeepSessionState();
63
64 private:
65  void LoadOnDBThreadAndNotify(const LoadedCallback& loaded_callback);
66  void LoadOnDBThread(
67      std::vector<net::DefaultServerBoundCertStore::ServerBoundCert*>* certs);
68
69  friend class base::RefCountedThreadSafe<SQLiteServerBoundCertStore::Backend>;
70
71  // You should call Close() before destructing this object.
72  ~Backend() {
73    DCHECK(!db_.get()) << "Close should have already been called.";
74    DCHECK(num_pending_ == 0 && pending_.empty());
75  }
76
77  // Database upgrade statements.
78  bool EnsureDatabaseVersion();
79
80  class PendingOperation {
81   public:
82    typedef enum {
83      CERT_ADD,
84      CERT_DELETE
85    } OperationType;
86
87    PendingOperation(
88        OperationType op,
89        const net::DefaultServerBoundCertStore::ServerBoundCert& cert)
90        : op_(op), cert_(cert) {}
91
92    OperationType op() const { return op_; }
93    const net::DefaultServerBoundCertStore::ServerBoundCert& cert() const {
94        return cert_;
95    }
96
97   private:
98    OperationType op_;
99    net::DefaultServerBoundCertStore::ServerBoundCert cert_;
100  };
101
102 private:
103  // Batch a server bound cert operation (add or delete).
104  void BatchOperation(
105      PendingOperation::OperationType op,
106      const net::DefaultServerBoundCertStore::ServerBoundCert& cert);
107  // Commit our pending operations to the database.
108  void Commit();
109  // Close() executed on the background thread.
110  void InternalBackgroundClose();
111
112  void DeleteCertificatesOnShutdown();
113
114  void DatabaseErrorCallback(int error, sql::Statement* stmt);
115  void KillDatabase();
116
117  base::FilePath path_;
118  scoped_ptr<sql::Connection> db_;
119  sql::MetaTable meta_table_;
120
121  typedef std::list<PendingOperation*> PendingOperationsList;
122  PendingOperationsList pending_;
123  PendingOperationsList::size_type num_pending_;
124  // True if the persistent store should skip clear on exit rules.
125  bool force_keep_session_state_;
126  // Guard |pending_|, |num_pending_| and |force_keep_session_state_|.
127  base::Lock lock_;
128
129  // Cache of origins we have certificates stored for.
130  std::set<std::string> cert_origins_;
131
132  scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy_;
133
134  // Indicates if the kill-database callback has been scheduled.
135  bool corruption_detected_;
136
137  DISALLOW_COPY_AND_ASSIGN(Backend);
138};
139
140// Version number of the database.
141static const int kCurrentVersionNumber = 4;
142static const int kCompatibleVersionNumber = 1;
143
144namespace {
145
146// Initializes the certs table, returning true on success.
147bool InitTable(sql::Connection* db) {
148  // The table is named "origin_bound_certs" for backwards compatability before
149  // we renamed this class to SQLiteServerBoundCertStore.  Likewise, the primary
150  // key is "origin", but now can be other things like a plain domain.
151  if (!db->DoesTableExist("origin_bound_certs")) {
152    if (!db->Execute("CREATE TABLE origin_bound_certs ("
153                     "origin TEXT NOT NULL UNIQUE PRIMARY KEY,"
154                     "private_key BLOB NOT NULL,"
155                     "cert BLOB NOT NULL,"
156                     "cert_type INTEGER,"
157                     "expiration_time INTEGER,"
158                     "creation_time INTEGER)"))
159      return false;
160  }
161
162  return true;
163}
164
165}  // namespace
166
167void SQLiteServerBoundCertStore::Backend::Load(
168    const LoadedCallback& loaded_callback) {
169  // This function should be called only once per instance.
170  DCHECK(!db_.get());
171
172  BrowserThread::PostTask(
173      BrowserThread::DB, FROM_HERE,
174      base::Bind(&Backend::LoadOnDBThreadAndNotify, this, loaded_callback));
175}
176
177void SQLiteServerBoundCertStore::Backend::LoadOnDBThreadAndNotify(
178    const LoadedCallback& loaded_callback) {
179  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
180  scoped_ptr<ScopedVector<net::DefaultServerBoundCertStore::ServerBoundCert> >
181      certs(new ScopedVector<net::DefaultServerBoundCertStore::ServerBoundCert>(
182          ));
183
184  LoadOnDBThread(&certs->get());
185
186  BrowserThread::PostTask(
187      BrowserThread::IO, FROM_HERE,
188      base::Bind(loaded_callback, base::Passed(&certs)));
189}
190
191void SQLiteServerBoundCertStore::Backend::LoadOnDBThread(
192    std::vector<net::DefaultServerBoundCertStore::ServerBoundCert*>* certs) {
193  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
194
195  // This method should be called only once per instance.
196  DCHECK(!db_.get());
197
198  base::TimeTicks start = base::TimeTicks::Now();
199
200  // Ensure the parent directory for storing certs is created before reading
201  // from it.
202  const base::FilePath dir = path_.DirName();
203  if (!base::PathExists(dir) && !file_util::CreateDirectory(dir))
204    return;
205
206  int64 db_size = 0;
207  if (file_util::GetFileSize(path_, &db_size))
208    UMA_HISTOGRAM_COUNTS("DomainBoundCerts.DBSizeInKB", db_size / 1024 );
209
210  db_.reset(new sql::Connection);
211  db_->set_histogram_tag("DomainBoundCerts");
212
213  // Unretained to avoid a ref loop with db_.
214  db_->set_error_callback(
215      base::Bind(&SQLiteServerBoundCertStore::Backend::DatabaseErrorCallback,
216                 base::Unretained(this)));
217
218  if (!db_->Open(path_)) {
219    NOTREACHED() << "Unable to open cert DB.";
220    if (corruption_detected_)
221      KillDatabase();
222    db_.reset();
223    return;
224  }
225
226  if (!EnsureDatabaseVersion() || !InitTable(db_.get())) {
227    NOTREACHED() << "Unable to open cert DB.";
228    if (corruption_detected_)
229      KillDatabase();
230    meta_table_.Reset();
231    db_.reset();
232    return;
233  }
234
235  db_->Preload();
236
237  // Slurp all the certs into the out-vector.
238  sql::Statement smt(db_->GetUniqueStatement(
239      "SELECT origin, private_key, cert, cert_type, expiration_time, "
240      "creation_time FROM origin_bound_certs"));
241  if (!smt.is_valid()) {
242    if (corruption_detected_)
243      KillDatabase();
244    meta_table_.Reset();
245    db_.reset();
246    return;
247  }
248
249  while (smt.Step()) {
250    net::SSLClientCertType type =
251        static_cast<net::SSLClientCertType>(smt.ColumnInt(3));
252    if (type != net::CLIENT_CERT_ECDSA_SIGN)
253      continue;
254    std::string private_key_from_db, cert_from_db;
255    smt.ColumnBlobAsString(1, &private_key_from_db);
256    smt.ColumnBlobAsString(2, &cert_from_db);
257    scoped_ptr<net::DefaultServerBoundCertStore::ServerBoundCert> cert(
258        new net::DefaultServerBoundCertStore::ServerBoundCert(
259            smt.ColumnString(0),  // origin
260            base::Time::FromInternalValue(smt.ColumnInt64(5)),
261            base::Time::FromInternalValue(smt.ColumnInt64(4)),
262            private_key_from_db,
263            cert_from_db));
264    cert_origins_.insert(cert->server_identifier());
265    certs->push_back(cert.release());
266  }
267
268  UMA_HISTOGRAM_COUNTS_10000("DomainBoundCerts.DBLoadedCount", certs->size());
269  base::TimeDelta load_time = base::TimeTicks::Now() - start;
270  UMA_HISTOGRAM_CUSTOM_TIMES("DomainBoundCerts.DBLoadTime",
271                             load_time,
272                             base::TimeDelta::FromMilliseconds(1),
273                             base::TimeDelta::FromMinutes(1),
274                             50);
275  DVLOG(1) << "loaded " << certs->size() << " in " << load_time.InMilliseconds()
276           << " ms";
277}
278
279bool SQLiteServerBoundCertStore::Backend::EnsureDatabaseVersion() {
280  // Version check.
281  if (!meta_table_.Init(
282      db_.get(), kCurrentVersionNumber, kCompatibleVersionNumber)) {
283    return false;
284  }
285
286  if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) {
287    LOG(WARNING) << "Server bound cert database is too new.";
288    return false;
289  }
290
291  int cur_version = meta_table_.GetVersionNumber();
292  if (cur_version == 1) {
293    sql::Transaction transaction(db_.get());
294    if (!transaction.Begin())
295      return false;
296    if (!db_->Execute("ALTER TABLE origin_bound_certs ADD COLUMN cert_type "
297                      "INTEGER")) {
298      LOG(WARNING) << "Unable to update server bound cert database to "
299                   << "version 2.";
300      return false;
301    }
302    // All certs in version 1 database are rsa_sign, which are unsupported.
303    // Just discard them all.
304    if (!db_->Execute("DELETE from origin_bound_certs")) {
305      LOG(WARNING) << "Unable to update server bound cert database to "
306                   << "version 2.";
307      return false;
308    }
309    ++cur_version;
310    meta_table_.SetVersionNumber(cur_version);
311    meta_table_.SetCompatibleVersionNumber(
312        std::min(cur_version, kCompatibleVersionNumber));
313    transaction.Commit();
314  }
315
316  if (cur_version <= 3) {
317    sql::Transaction transaction(db_.get());
318    if (!transaction.Begin())
319      return false;
320
321    if (cur_version == 2) {
322      if (!db_->Execute("ALTER TABLE origin_bound_certs ADD COLUMN "
323                        "expiration_time INTEGER")) {
324        LOG(WARNING) << "Unable to update server bound cert database to "
325                     << "version 4.";
326        return false;
327      }
328    }
329
330    if (!db_->Execute("ALTER TABLE origin_bound_certs ADD COLUMN "
331                      "creation_time INTEGER")) {
332      LOG(WARNING) << "Unable to update server bound cert database to "
333                   << "version 4.";
334      return false;
335    }
336
337    sql::Statement smt(db_->GetUniqueStatement(
338        "SELECT origin, cert FROM origin_bound_certs"));
339    sql::Statement update_expires_smt(db_->GetUniqueStatement(
340        "UPDATE origin_bound_certs SET expiration_time = ? WHERE origin = ?"));
341    sql::Statement update_creation_smt(db_->GetUniqueStatement(
342        "UPDATE origin_bound_certs SET creation_time = ? WHERE origin = ?"));
343    if (!smt.is_valid() ||
344        !update_expires_smt.is_valid() ||
345        !update_creation_smt.is_valid()) {
346      LOG(WARNING) << "Unable to update server bound cert database to "
347                   << "version 4.";
348      return false;
349    }
350
351    while (smt.Step()) {
352      std::string origin = smt.ColumnString(0);
353      std::string cert_from_db;
354      smt.ColumnBlobAsString(1, &cert_from_db);
355      // Parse the cert and extract the real value and then update the DB.
356      scoped_refptr<net::X509Certificate> cert(
357          net::X509Certificate::CreateFromBytes(
358              cert_from_db.data(), cert_from_db.size()));
359      if (cert.get()) {
360        if (cur_version == 2) {
361          update_expires_smt.Reset(true);
362          update_expires_smt.BindInt64(0,
363                                       cert->valid_expiry().ToInternalValue());
364          update_expires_smt.BindString(1, origin);
365          if (!update_expires_smt.Run()) {
366            LOG(WARNING) << "Unable to update server bound cert database to "
367                         << "version 4.";
368            return false;
369          }
370        }
371
372        update_creation_smt.Reset(true);
373        update_creation_smt.BindInt64(0, cert->valid_start().ToInternalValue());
374        update_creation_smt.BindString(1, origin);
375        if (!update_creation_smt.Run()) {
376          LOG(WARNING) << "Unable to update server bound cert database to "
377                       << "version 4.";
378          return false;
379        }
380      } else {
381        // If there's a cert we can't parse, just leave it.  It'll get replaced
382        // with a new one if we ever try to use it.
383        LOG(WARNING) << "Error parsing cert for database upgrade for origin "
384                     << smt.ColumnString(0);
385      }
386    }
387
388    cur_version = 4;
389    meta_table_.SetVersionNumber(cur_version);
390    meta_table_.SetCompatibleVersionNumber(
391        std::min(cur_version, kCompatibleVersionNumber));
392    transaction.Commit();
393  }
394
395  // Put future migration cases here.
396
397  // When the version is too old, we just try to continue anyway, there should
398  // not be a released product that makes a database too old for us to handle.
399  LOG_IF(WARNING, cur_version < kCurrentVersionNumber) <<
400      "Server bound cert database version " << cur_version <<
401      " is too old to handle.";
402
403  return true;
404}
405
406void SQLiteServerBoundCertStore::Backend::DatabaseErrorCallback(
407    int error,
408    sql::Statement* stmt) {
409  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
410
411  if (!sql::IsErrorCatastrophic(error))
412    return;
413
414  // TODO(shess): Running KillDatabase() multiple times should be
415  // safe.
416  if (corruption_detected_)
417    return;
418
419  corruption_detected_ = true;
420
421  // TODO(shess): Consider just calling RazeAndClose() immediately.
422  // db_ may not be safe to reset at this point, but RazeAndClose()
423  // would cause the stack to unwind safely with errors.
424  BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
425                          base::Bind(&Backend::KillDatabase, this));
426}
427
428void SQLiteServerBoundCertStore::Backend::KillDatabase() {
429  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
430
431  if (db_) {
432    // This Backend will now be in-memory only. In a future run the database
433    // will be recreated. Hopefully things go better then!
434    bool success = db_->RazeAndClose();
435    UMA_HISTOGRAM_BOOLEAN("DomainBoundCerts.KillDatabaseResult", success);
436    meta_table_.Reset();
437    db_.reset();
438  }
439}
440
441void SQLiteServerBoundCertStore::Backend::AddServerBoundCert(
442    const net::DefaultServerBoundCertStore::ServerBoundCert& cert) {
443  BatchOperation(PendingOperation::CERT_ADD, cert);
444}
445
446void SQLiteServerBoundCertStore::Backend::DeleteServerBoundCert(
447    const net::DefaultServerBoundCertStore::ServerBoundCert& cert) {
448  BatchOperation(PendingOperation::CERT_DELETE, cert);
449}
450
451void SQLiteServerBoundCertStore::Backend::BatchOperation(
452    PendingOperation::OperationType op,
453    const net::DefaultServerBoundCertStore::ServerBoundCert& cert) {
454  // Commit every 30 seconds.
455  static const int kCommitIntervalMs = 30 * 1000;
456  // Commit right away if we have more than 512 outstanding operations.
457  static const size_t kCommitAfterBatchSize = 512;
458
459  // We do a full copy of the cert here, and hopefully just here.
460  scoped_ptr<PendingOperation> po(new PendingOperation(op, cert));
461
462  PendingOperationsList::size_type num_pending;
463  {
464    base::AutoLock locked(lock_);
465    pending_.push_back(po.release());
466    num_pending = ++num_pending_;
467  }
468
469  if (num_pending == 1) {
470    // We've gotten our first entry for this batch, fire off the timer.
471    BrowserThread::PostDelayedTask(
472        BrowserThread::DB, FROM_HERE,
473        base::Bind(&Backend::Commit, this),
474        base::TimeDelta::FromMilliseconds(kCommitIntervalMs));
475  } else if (num_pending == kCommitAfterBatchSize) {
476    // We've reached a big enough batch, fire off a commit now.
477    BrowserThread::PostTask(
478        BrowserThread::DB, FROM_HERE,
479        base::Bind(&Backend::Commit, this));
480  }
481}
482
483void SQLiteServerBoundCertStore::Backend::Commit() {
484  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
485
486  PendingOperationsList ops;
487  {
488    base::AutoLock locked(lock_);
489    pending_.swap(ops);
490    num_pending_ = 0;
491  }
492
493  // Maybe an old timer fired or we are already Close()'ed.
494  if (!db_.get() || ops.empty())
495    return;
496
497  sql::Statement add_smt(db_->GetCachedStatement(SQL_FROM_HERE,
498      "INSERT INTO origin_bound_certs (origin, private_key, cert, cert_type, "
499      "expiration_time, creation_time) VALUES (?,?,?,?,?,?)"));
500  if (!add_smt.is_valid())
501    return;
502
503  sql::Statement del_smt(db_->GetCachedStatement(SQL_FROM_HERE,
504                             "DELETE FROM origin_bound_certs WHERE origin=?"));
505  if (!del_smt.is_valid())
506    return;
507
508  sql::Transaction transaction(db_.get());
509  if (!transaction.Begin())
510    return;
511
512  for (PendingOperationsList::iterator it = ops.begin();
513       it != ops.end(); ++it) {
514    // Free the certs as we commit them to the database.
515    scoped_ptr<PendingOperation> po(*it);
516    switch (po->op()) {
517      case PendingOperation::CERT_ADD: {
518        cert_origins_.insert(po->cert().server_identifier());
519        add_smt.Reset(true);
520        add_smt.BindString(0, po->cert().server_identifier());
521        const std::string& private_key = po->cert().private_key();
522        add_smt.BindBlob(1, private_key.data(), private_key.size());
523        const std::string& cert = po->cert().cert();
524        add_smt.BindBlob(2, cert.data(), cert.size());
525        add_smt.BindInt(3, net::CLIENT_CERT_ECDSA_SIGN);
526        add_smt.BindInt64(4, po->cert().expiration_time().ToInternalValue());
527        add_smt.BindInt64(5, po->cert().creation_time().ToInternalValue());
528        if (!add_smt.Run())
529          NOTREACHED() << "Could not add a server bound cert to the DB.";
530        break;
531      }
532      case PendingOperation::CERT_DELETE:
533        cert_origins_.erase(po->cert().server_identifier());
534        del_smt.Reset(true);
535        del_smt.BindString(0, po->cert().server_identifier());
536        if (!del_smt.Run())
537          NOTREACHED() << "Could not delete a server bound cert from the DB.";
538        break;
539
540      default:
541        NOTREACHED();
542        break;
543    }
544  }
545  transaction.Commit();
546}
547
548// Fire off a close message to the background thread. We could still have a
549// pending commit timer that will be holding a reference on us, but if/when
550// this fires we will already have been cleaned up and it will be ignored.
551void SQLiteServerBoundCertStore::Backend::Close() {
552  // Must close the backend on the background thread.
553  BrowserThread::PostTask(
554      BrowserThread::DB, FROM_HERE,
555      base::Bind(&Backend::InternalBackgroundClose, this));
556}
557
558void SQLiteServerBoundCertStore::Backend::InternalBackgroundClose() {
559  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
560  // Commit any pending operations
561  Commit();
562
563  if (!force_keep_session_state_ &&
564      special_storage_policy_.get() &&
565      special_storage_policy_->HasSessionOnlyOrigins()) {
566    DeleteCertificatesOnShutdown();
567  }
568
569  db_.reset();
570}
571
572void SQLiteServerBoundCertStore::Backend::DeleteCertificatesOnShutdown() {
573  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
574
575  if (!db_.get())
576    return;
577
578  if (cert_origins_.empty())
579    return;
580
581  if (!special_storage_policy_.get())
582    return;
583
584  sql::Statement del_smt(db_->GetCachedStatement(
585      SQL_FROM_HERE, "DELETE FROM origin_bound_certs WHERE origin=?"));
586  if (!del_smt.is_valid()) {
587    LOG(WARNING) << "Unable to delete certificates on shutdown.";
588    return;
589  }
590
591  sql::Transaction transaction(db_.get());
592  if (!transaction.Begin()) {
593    LOG(WARNING) << "Unable to delete certificates on shutdown.";
594    return;
595  }
596
597  for (std::set<std::string>::iterator it = cert_origins_.begin();
598       it != cert_origins_.end(); ++it) {
599    const GURL url(net::cookie_util::CookieOriginToURL(*it, true));
600    if (!url.is_valid() || !special_storage_policy_->IsStorageSessionOnly(url))
601      continue;
602    del_smt.Reset(true);
603    del_smt.BindString(0, *it);
604    if (!del_smt.Run())
605      NOTREACHED() << "Could not delete a certificate from the DB.";
606  }
607
608  if (!transaction.Commit())
609    LOG(WARNING) << "Unable to delete certificates on shutdown.";
610}
611
612void SQLiteServerBoundCertStore::Backend::SetForceKeepSessionState() {
613  base::AutoLock locked(lock_);
614  force_keep_session_state_ = true;
615}
616
617SQLiteServerBoundCertStore::SQLiteServerBoundCertStore(
618    const base::FilePath& path,
619    quota::SpecialStoragePolicy* special_storage_policy)
620    : backend_(new Backend(path, special_storage_policy)) {
621}
622
623void SQLiteServerBoundCertStore::Load(
624    const LoadedCallback& loaded_callback) {
625  backend_->Load(loaded_callback);
626}
627
628void SQLiteServerBoundCertStore::AddServerBoundCert(
629    const net::DefaultServerBoundCertStore::ServerBoundCert& cert) {
630  backend_->AddServerBoundCert(cert);
631}
632
633void SQLiteServerBoundCertStore::DeleteServerBoundCert(
634    const net::DefaultServerBoundCertStore::ServerBoundCert& cert) {
635  backend_->DeleteServerBoundCert(cert);
636}
637
638void SQLiteServerBoundCertStore::SetForceKeepSessionState() {
639  backend_->SetForceKeepSessionState();
640}
641
642SQLiteServerBoundCertStore::~SQLiteServerBoundCertStore() {
643  backend_->Close();
644  // We release our reference to the Backend, though it will probably still have
645  // a reference if the background thread has not run Close() yet.
646}
647