Rename origin_bound_cert files to server_bound_cert.
[chromium-blink-merge.git] / chrome / browser / net / sqlite_server_bound_cert_store.cc
blob69dea95c36f4dd7e4b4f156e15ad7d6a6c2340b4
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.
5 #include "chrome/browser/net/sqlite_server_bound_cert_store.h"
7 #include <list>
9 #include "base/basictypes.h"
10 #include "base/bind.h"
11 #include "base/file_path.h"
12 #include "base/file_util.h"
13 #include "base/logging.h"
14 #include "base/memory/scoped_ptr.h"
15 #include "base/string_util.h"
16 #include "base/threading/thread.h"
17 #include "base/threading/thread_restrictions.h"
18 #include "chrome/browser/diagnostics/sqlite_diagnostics.h"
19 #include "content/public/browser/browser_thread.h"
20 #include "net/base/ssl_client_cert_type.h"
21 #include "net/base/x509_certificate.h"
22 #include "sql/meta_table.h"
23 #include "sql/statement.h"
24 #include "sql/transaction.h"
26 using content::BrowserThread;
28 // This class is designed to be shared between any calling threads and the
29 // database thread. It batches operations and commits them on a timer.
30 class SQLiteServerBoundCertStore::Backend
31 : public base::RefCountedThreadSafe<SQLiteServerBoundCertStore::Backend> {
32 public:
33 explicit Backend(const FilePath& path)
34 : path_(path),
35 db_(NULL),
36 num_pending_(0),
37 clear_local_state_on_exit_(false) {
40 // Creates or load the SQLite database.
41 bool Load(
42 std::vector<net::DefaultServerBoundCertStore::ServerBoundCert*>* certs);
44 // Batch a server bound cert addition.
45 void AddServerBoundCert(
46 const net::DefaultServerBoundCertStore::ServerBoundCert& cert);
48 // Batch a server bound cert deletion.
49 void DeleteServerBoundCert(
50 const net::DefaultServerBoundCertStore::ServerBoundCert& cert);
52 // Commit pending operations as soon as possible.
53 void Flush(const base::Closure& completion_task);
55 // Commit any pending operations and close the database. This must be called
56 // before the object is destructed.
57 void Close();
59 void SetClearLocalStateOnExit(bool clear_local_state);
61 private:
62 friend class base::RefCountedThreadSafe<SQLiteServerBoundCertStore::Backend>;
64 // You should call Close() before destructing this object.
65 ~Backend() {
66 DCHECK(!db_.get()) << "Close should have already been called.";
67 DCHECK(num_pending_ == 0 && pending_.empty());
70 // Database upgrade statements.
71 bool EnsureDatabaseVersion();
73 class PendingOperation {
74 public:
75 typedef enum {
76 CERT_ADD,
77 CERT_DELETE
78 } OperationType;
80 PendingOperation(
81 OperationType op,
82 const net::DefaultServerBoundCertStore::ServerBoundCert& cert)
83 : op_(op), cert_(cert) {}
85 OperationType op() const { return op_; }
86 const net::DefaultServerBoundCertStore::ServerBoundCert& cert() const {
87 return cert_;
90 private:
91 OperationType op_;
92 net::DefaultServerBoundCertStore::ServerBoundCert cert_;
95 private:
96 // Batch a server bound cert operation (add or delete)
97 void BatchOperation(
98 PendingOperation::OperationType op,
99 const net::DefaultServerBoundCertStore::ServerBoundCert& cert);
100 // Commit our pending operations to the database.
101 void Commit();
102 // Close() executed on the background thread.
103 void InternalBackgroundClose();
105 FilePath path_;
106 scoped_ptr<sql::Connection> db_;
107 sql::MetaTable meta_table_;
109 typedef std::list<PendingOperation*> PendingOperationsList;
110 PendingOperationsList pending_;
111 PendingOperationsList::size_type num_pending_;
112 // True if the persistent store should be deleted upon destruction.
113 bool clear_local_state_on_exit_;
114 // Guard |pending_|, |num_pending_| and |clear_local_state_on_exit_|.
115 base::Lock lock_;
117 DISALLOW_COPY_AND_ASSIGN(Backend);
120 // Version number of the database.
121 static const int kCurrentVersionNumber = 4;
122 static const int kCompatibleVersionNumber = 1;
124 namespace {
126 // Initializes the certs table, returning true on success.
127 bool InitTable(sql::Connection* db) {
128 // The table is named "origin_bound_certs" for backwards compatability before
129 // we renamed this class to SQLiteServerBoundCertStore. Likewise, the primary
130 // key is "origin", but now can be other things like a plain domain.
131 if (!db->DoesTableExist("origin_bound_certs")) {
132 if (!db->Execute("CREATE TABLE origin_bound_certs ("
133 "origin TEXT NOT NULL UNIQUE PRIMARY KEY,"
134 "private_key BLOB NOT NULL,"
135 "cert BLOB NOT NULL,"
136 "cert_type INTEGER,"
137 "expiration_time INTEGER,"
138 "creation_time INTEGER)"))
139 return false;
142 return true;
145 } // namespace
147 bool SQLiteServerBoundCertStore::Backend::Load(
148 std::vector<net::DefaultServerBoundCertStore::ServerBoundCert*>* certs) {
149 // This function should be called only once per instance.
150 DCHECK(!db_.get());
152 // Ensure the parent directory for storing certs is created before reading
153 // from it. We make an exception to allow IO on the UI thread here because
154 // we are going to disk anyway in db_->Open. (This code will be moved to the
155 // DB thread as part of http://crbug.com/52909.)
157 base::ThreadRestrictions::ScopedAllowIO allow_io;
158 const FilePath dir = path_.DirName();
159 if (!file_util::PathExists(dir) && !file_util::CreateDirectory(dir))
160 return false;
163 db_.reset(new sql::Connection);
164 if (!db_->Open(path_)) {
165 NOTREACHED() << "Unable to open cert DB.";
166 db_.reset();
167 return false;
170 if (!EnsureDatabaseVersion() || !InitTable(db_.get())) {
171 NOTREACHED() << "Unable to open cert DB.";
172 db_.reset();
173 return false;
176 db_->Preload();
178 // Slurp all the certs into the out-vector.
179 sql::Statement smt(db_->GetUniqueStatement(
180 "SELECT origin, private_key, cert, cert_type, expiration_time, "
181 "creation_time FROM origin_bound_certs"));
182 if (!smt.is_valid()) {
183 db_.reset();
184 return false;
187 while (smt.Step()) {
188 std::string private_key_from_db, cert_from_db;
189 smt.ColumnBlobAsString(1, &private_key_from_db);
190 smt.ColumnBlobAsString(2, &cert_from_db);
191 scoped_ptr<net::DefaultServerBoundCertStore::ServerBoundCert> cert(
192 new net::DefaultServerBoundCertStore::ServerBoundCert(
193 smt.ColumnString(0), // origin
194 static_cast<net::SSLClientCertType>(smt.ColumnInt(3)),
195 base::Time::FromInternalValue(smt.ColumnInt64(5)),
196 base::Time::FromInternalValue(smt.ColumnInt64(4)),
197 private_key_from_db,
198 cert_from_db));
199 certs->push_back(cert.release());
202 return true;
205 bool SQLiteServerBoundCertStore::Backend::EnsureDatabaseVersion() {
206 // Version check.
207 if (!meta_table_.Init(
208 db_.get(), kCurrentVersionNumber, kCompatibleVersionNumber)) {
209 return false;
212 if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) {
213 LOG(WARNING) << "Server bound cert database is too new.";
214 return false;
217 int cur_version = meta_table_.GetVersionNumber();
218 if (cur_version == 1) {
219 sql::Transaction transaction(db_.get());
220 if (!transaction.Begin())
221 return false;
222 if (!db_->Execute("ALTER TABLE origin_bound_certs ADD COLUMN cert_type "
223 "INTEGER")) {
224 LOG(WARNING) << "Unable to update server bound cert database to "
225 << "version 2.";
226 return false;
228 // All certs in version 1 database are rsa_sign, which has a value of 1.
229 if (!db_->Execute("UPDATE origin_bound_certs SET cert_type = 1")) {
230 LOG(WARNING) << "Unable to update server bound cert database to "
231 << "version 2.";
232 return false;
234 ++cur_version;
235 meta_table_.SetVersionNumber(cur_version);
236 meta_table_.SetCompatibleVersionNumber(
237 std::min(cur_version, kCompatibleVersionNumber));
238 transaction.Commit();
241 if (cur_version <= 3) {
242 sql::Transaction transaction(db_.get());
243 if (!transaction.Begin())
244 return false;
246 if (cur_version == 2) {
247 if (!db_->Execute("ALTER TABLE origin_bound_certs ADD COLUMN "
248 "expiration_time INTEGER")) {
249 LOG(WARNING) << "Unable to update server bound cert database to "
250 << "version 4.";
251 return false;
255 if (!db_->Execute("ALTER TABLE origin_bound_certs ADD COLUMN "
256 "creation_time INTEGER")) {
257 LOG(WARNING) << "Unable to update server bound cert database to "
258 << "version 4.";
259 return false;
262 sql::Statement smt(db_->GetUniqueStatement(
263 "SELECT origin, cert FROM origin_bound_certs"));
264 sql::Statement update_expires_smt(db_->GetUniqueStatement(
265 "UPDATE origin_bound_certs SET expiration_time = ? WHERE origin = ?"));
266 sql::Statement update_creation_smt(db_->GetUniqueStatement(
267 "UPDATE origin_bound_certs SET creation_time = ? WHERE origin = ?"));
268 if (!smt.is_valid() ||
269 !update_expires_smt.is_valid() ||
270 !update_creation_smt.is_valid()) {
271 LOG(WARNING) << "Unable to update server bound cert database to "
272 << "version 4.";
273 return false;
276 while (smt.Step()) {
277 std::string origin = smt.ColumnString(0);
278 std::string cert_from_db;
279 smt.ColumnBlobAsString(1, &cert_from_db);
280 // Parse the cert and extract the real value and then update the DB.
281 scoped_refptr<net::X509Certificate> cert(
282 net::X509Certificate::CreateFromBytes(
283 cert_from_db.data(), cert_from_db.size()));
284 if (cert) {
285 if (cur_version == 2) {
286 update_expires_smt.Reset();
287 update_expires_smt.BindInt64(0,
288 cert->valid_expiry().ToInternalValue());
289 update_expires_smt.BindString(1, origin);
290 if (!update_expires_smt.Run()) {
291 LOG(WARNING) << "Unable to update server bound cert database to "
292 << "version 4.";
293 return false;
297 update_creation_smt.Reset();
298 update_creation_smt.BindInt64(0, cert->valid_start().ToInternalValue());
299 update_creation_smt.BindString(1, origin);
300 if (!update_creation_smt.Run()) {
301 LOG(WARNING) << "Unable to update server bound cert database to "
302 << "version 4.";
303 return false;
305 } else {
306 // If there's a cert we can't parse, just leave it. It'll get replaced
307 // with a new one if we ever try to use it.
308 LOG(WARNING) << "Error parsing cert for database upgrade for origin "
309 << smt.ColumnString(0);
313 cur_version = 4;
314 meta_table_.SetVersionNumber(cur_version);
315 meta_table_.SetCompatibleVersionNumber(
316 std::min(cur_version, kCompatibleVersionNumber));
317 transaction.Commit();
320 // Put future migration cases here.
322 // When the version is too old, we just try to continue anyway, there should
323 // not be a released product that makes a database too old for us to handle.
324 LOG_IF(WARNING, cur_version < kCurrentVersionNumber) <<
325 "Server bound cert database version " << cur_version <<
326 " is too old to handle.";
328 return true;
331 void SQLiteServerBoundCertStore::Backend::AddServerBoundCert(
332 const net::DefaultServerBoundCertStore::ServerBoundCert& cert) {
333 BatchOperation(PendingOperation::CERT_ADD, cert);
336 void SQLiteServerBoundCertStore::Backend::DeleteServerBoundCert(
337 const net::DefaultServerBoundCertStore::ServerBoundCert& cert) {
338 BatchOperation(PendingOperation::CERT_DELETE, cert);
341 void SQLiteServerBoundCertStore::Backend::BatchOperation(
342 PendingOperation::OperationType op,
343 const net::DefaultServerBoundCertStore::ServerBoundCert& cert) {
344 // Commit every 30 seconds.
345 static const int kCommitIntervalMs = 30 * 1000;
346 // Commit right away if we have more than 512 outstanding operations.
347 static const size_t kCommitAfterBatchSize = 512;
348 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::DB));
350 // We do a full copy of the cert here, and hopefully just here.
351 scoped_ptr<PendingOperation> po(new PendingOperation(op, cert));
353 PendingOperationsList::size_type num_pending;
355 base::AutoLock locked(lock_);
356 pending_.push_back(po.release());
357 num_pending = ++num_pending_;
360 if (num_pending == 1) {
361 // We've gotten our first entry for this batch, fire off the timer.
362 BrowserThread::PostDelayedTask(
363 BrowserThread::DB, FROM_HERE,
364 base::Bind(&Backend::Commit, this),
365 base::TimeDelta::FromMilliseconds(kCommitIntervalMs));
366 } else if (num_pending == kCommitAfterBatchSize) {
367 // We've reached a big enough batch, fire off a commit now.
368 BrowserThread::PostTask(
369 BrowserThread::DB, FROM_HERE,
370 base::Bind(&Backend::Commit, this));
374 void SQLiteServerBoundCertStore::Backend::Commit() {
375 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
377 PendingOperationsList ops;
379 base::AutoLock locked(lock_);
380 pending_.swap(ops);
381 num_pending_ = 0;
384 // Maybe an old timer fired or we are already Close()'ed.
385 if (!db_.get() || ops.empty())
386 return;
388 sql::Statement add_smt(db_->GetCachedStatement(SQL_FROM_HERE,
389 "INSERT INTO origin_bound_certs (origin, private_key, cert, cert_type, "
390 "expiration_time, creation_time) VALUES (?,?,?,?,?,?)"));
391 if (!add_smt.is_valid())
392 return;
394 sql::Statement del_smt(db_->GetCachedStatement(SQL_FROM_HERE,
395 "DELETE FROM origin_bound_certs WHERE origin=?"));
396 if (!del_smt.is_valid())
397 return;
399 sql::Transaction transaction(db_.get());
400 if (!transaction.Begin())
401 return;
403 for (PendingOperationsList::iterator it = ops.begin();
404 it != ops.end(); ++it) {
405 // Free the certs as we commit them to the database.
406 scoped_ptr<PendingOperation> po(*it);
407 switch (po->op()) {
408 case PendingOperation::CERT_ADD: {
409 add_smt.Reset();
410 add_smt.BindString(0, po->cert().server_identifier());
411 const std::string& private_key = po->cert().private_key();
412 add_smt.BindBlob(1, private_key.data(), private_key.size());
413 const std::string& cert = po->cert().cert();
414 add_smt.BindBlob(2, cert.data(), cert.size());
415 add_smt.BindInt(3, po->cert().type());
416 add_smt.BindInt64(4, po->cert().expiration_time().ToInternalValue());
417 add_smt.BindInt64(5, po->cert().creation_time().ToInternalValue());
418 if (!add_smt.Run())
419 NOTREACHED() << "Could not add a server bound cert to the DB.";
420 break;
422 case PendingOperation::CERT_DELETE:
423 del_smt.Reset();
424 del_smt.BindString(0, po->cert().server_identifier());
425 if (!del_smt.Run())
426 NOTREACHED() << "Could not delete a server bound cert from the DB.";
427 break;
429 default:
430 NOTREACHED();
431 break;
434 transaction.Commit();
437 void SQLiteServerBoundCertStore::Backend::Flush(
438 const base::Closure& completion_task) {
439 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::DB));
440 BrowserThread::PostTask(
441 BrowserThread::DB, FROM_HERE, base::Bind(&Backend::Commit, this));
442 if (!completion_task.is_null()) {
443 // We want the completion task to run immediately after Commit() returns.
444 // Posting it from here means there is less chance of another task getting
445 // onto the message queue first, than if we posted it from Commit() itself.
446 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, completion_task);
450 // Fire off a close message to the background thread. We could still have a
451 // pending commit timer that will be holding a reference on us, but if/when
452 // this fires we will already have been cleaned up and it will be ignored.
453 void SQLiteServerBoundCertStore::Backend::Close() {
454 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::DB));
455 // Must close the backend on the background thread.
456 BrowserThread::PostTask(
457 BrowserThread::DB, FROM_HERE,
458 base::Bind(&Backend::InternalBackgroundClose, this));
461 void SQLiteServerBoundCertStore::Backend::InternalBackgroundClose() {
462 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
463 // Commit any pending operations
464 Commit();
466 db_.reset();
468 if (clear_local_state_on_exit_)
469 file_util::Delete(path_, false);
472 void SQLiteServerBoundCertStore::Backend::SetClearLocalStateOnExit(
473 bool clear_local_state) {
474 base::AutoLock locked(lock_);
475 clear_local_state_on_exit_ = clear_local_state;
478 SQLiteServerBoundCertStore::SQLiteServerBoundCertStore(const FilePath& path)
479 : backend_(new Backend(path)) {
482 SQLiteServerBoundCertStore::~SQLiteServerBoundCertStore() {
483 if (backend_.get()) {
484 backend_->Close();
485 // Release our reference, it will probably still have a reference if the
486 // background thread has not run Close() yet.
487 backend_ = NULL;
491 bool SQLiteServerBoundCertStore::Load(
492 std::vector<net::DefaultServerBoundCertStore::ServerBoundCert*>* certs) {
493 return backend_->Load(certs);
496 void SQLiteServerBoundCertStore::AddServerBoundCert(
497 const net::DefaultServerBoundCertStore::ServerBoundCert& cert) {
498 if (backend_.get())
499 backend_->AddServerBoundCert(cert);
502 void SQLiteServerBoundCertStore::DeleteServerBoundCert(
503 const net::DefaultServerBoundCertStore::ServerBoundCert& cert) {
504 if (backend_.get())
505 backend_->DeleteServerBoundCert(cert);
508 void SQLiteServerBoundCertStore::SetClearLocalStateOnExit(
509 bool clear_local_state) {
510 if (backend_.get())
511 backend_->SetClearLocalStateOnExit(clear_local_state);
514 void SQLiteServerBoundCertStore::Flush(const base::Closure& completion_task) {
515 if (backend_.get())
516 backend_->Flush(completion_task);
517 else if (!completion_task.is_null())
518 MessageLoop::current()->PostTask(FROM_HERE, completion_task);