#include "chrome/browser/net/sqlite_server_bound_cert_store.h"
#include <list>
#include <set>
#include "base/basictypes.h"
#include "base/bind.h"
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/string_util.h"
#include "base/threading/thread.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/diagnostics/sqlite_diagnostics.h"
#include "chrome/browser/net/clear_on_exit_policy.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/ssl_client_cert_type.h"
#include "net/base/x509_certificate.h"
#include "sql/meta_table.h"
#include "sql/statement.h"
#include "sql/transaction.h"
using content::BrowserThread;
class SQLiteServerBoundCertStore::Backend
: public base::RefCountedThreadSafe<SQLiteServerBoundCertStore::Backend> {
public:
Backend(const FilePath& path, ClearOnExitPolicy* clear_on_exit_policy)
: path_(path),
db_(NULL),
num_pending_(0),
force_keep_session_state_(false),
clear_on_exit_policy_(clear_on_exit_policy) {
}
bool Load(
std::vector<net::DefaultServerBoundCertStore::ServerBoundCert*>* certs);
void AddServerBoundCert(
const net::DefaultServerBoundCertStore::ServerBoundCert& cert);
void DeleteServerBoundCert(
const net::DefaultServerBoundCertStore::ServerBoundCert& cert);
void Flush(const base::Closure& completion_task);
void Close();
void SetForceKeepSessionState();
private:
friend class base::RefCountedThreadSafe<SQLiteServerBoundCertStore::Backend>;
~Backend() {
DCHECK(!db_.get()) << "Close should have already been called.";
DCHECK(num_pending_ == 0 && pending_.empty());
}
bool EnsureDatabaseVersion();
class PendingOperation {
public:
typedef enum {
CERT_ADD,
CERT_DELETE
} OperationType;
PendingOperation(
OperationType op,
const net::DefaultServerBoundCertStore::ServerBoundCert& cert)
: op_(op), cert_(cert) {}
OperationType op() const { return op_; }
const net::DefaultServerBoundCertStore::ServerBoundCert& cert() const {
return cert_;
}
private:
OperationType op_;
net::DefaultServerBoundCertStore::ServerBoundCert cert_;
};
private:
void BatchOperation(
PendingOperation::OperationType op,
const net::DefaultServerBoundCertStore::ServerBoundCert& cert);
void Commit();
void InternalBackgroundClose();
void DeleteCertificatesOnShutdown();
FilePath path_;
scoped_ptr<sql::Connection> db_;
sql::MetaTable meta_table_;
typedef std::list<PendingOperation*> PendingOperationsList;
PendingOperationsList pending_;
PendingOperationsList::size_type num_pending_;
bool force_keep_session_state_;
base::Lock lock_;
std::set<std::string> cert_origins_;
scoped_refptr<ClearOnExitPolicy> clear_on_exit_policy_;
DISALLOW_COPY_AND_ASSIGN(Backend);
};
static const int kCurrentVersionNumber = 4;
static const int kCompatibleVersionNumber = 1;
namespace {
bool InitTable(sql::Connection* db) {
if (!db->DoesTableExist("origin_bound_certs")) {
if (!db->Execute("CREATE TABLE origin_bound_certs ("
"origin TEXT NOT NULL UNIQUE PRIMARY KEY,"
"private_key BLOB NOT NULL,"
"cert BLOB NOT NULL,"
"cert_type INTEGER,"
"expiration_time INTEGER,"
"creation_time INTEGER)"))
return false;
}
return true;
}
}
bool SQLiteServerBoundCertStore::Backend::Load(
std::vector<net::DefaultServerBoundCertStore::ServerBoundCert*>* certs) {
DCHECK(!db_.get());
base::ThreadRestrictions::ScopedAllowIO allow_io;
const FilePath dir = path_.DirName();
if (!file_util::PathExists(dir) && !file_util::CreateDirectory(dir))
return false;
db_.reset(new sql::Connection);
if (!db_->Open(path_)) {
NOTREACHED() << "Unable to open cert DB.";
db_.reset();
return false;
}
if (!EnsureDatabaseVersion() || !InitTable(db_.get())) {
NOTREACHED() << "Unable to open cert DB.";
db_.reset();
return false;
}
db_->Preload();
sql::Statement smt(db_->GetUniqueStatement(
"SELECT origin, private_key, cert, cert_type, expiration_time, "
"creation_time FROM origin_bound_certs"));
if (!smt.is_valid()) {
db_.reset();
return false;
}
while (smt.Step()) {
std::string private_key_from_db, cert_from_db;
smt.ColumnBlobAsString(1, &private_key_from_db);
smt.ColumnBlobAsString(2, &cert_from_db);
scoped_ptr<net::DefaultServerBoundCertStore::ServerBoundCert> cert(
new net::DefaultServerBoundCertStore::ServerBoundCert(
smt.ColumnString(0),
static_cast<net::SSLClientCertType>(smt.ColumnInt(3)),
base::Time::FromInternalValue(smt.ColumnInt64(5)),
base::Time::FromInternalValue(smt.ColumnInt64(4)),
private_key_from_db,
cert_from_db));
cert_origins_.insert(cert->server_identifier());
certs->push_back(cert.release());
}
return true;
}
bool SQLiteServerBoundCertStore::Backend::EnsureDatabaseVersion() {
if (!meta_table_.Init(
db_.get(), kCurrentVersionNumber, kCompatibleVersionNumber)) {
return false;
}
if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) {
LOG(WARNING) << "Server bound cert database is too new.";
return false;
}
int cur_version = meta_table_.GetVersionNumber();
if (cur_version == 1) {
sql::Transaction transaction(db_.get());
if (!transaction.Begin())
return false;
if (!db_->Execute("ALTER TABLE origin_bound_certs ADD COLUMN cert_type "
"INTEGER")) {
LOG(WARNING) << "Unable to update server bound cert database to "
<< "version 2.";
return false;
}
if (!db_->Execute("UPDATE origin_bound_certs SET cert_type = 1")) {
LOG(WARNING) << "Unable to update server bound cert database to "
<< "version 2.";
return false;
}
++cur_version;
meta_table_.SetVersionNumber(cur_version);
meta_table_.SetCompatibleVersionNumber(
std::min(cur_version, kCompatibleVersionNumber));
transaction.Commit();
}
if (cur_version <= 3) {
sql::Transaction transaction(db_.get());
if (!transaction.Begin())
return false;
if (cur_version == 2) {
if (!db_->Execute("ALTER TABLE origin_bound_certs ADD COLUMN "
"expiration_time INTEGER")) {
LOG(WARNING) << "Unable to update server bound cert database to "
<< "version 4.";
return false;
}
}
if (!db_->Execute("ALTER TABLE origin_bound_certs ADD COLUMN "
"creation_time INTEGER")) {
LOG(WARNING) << "Unable to update server bound cert database to "
<< "version 4.";
return false;
}
sql::Statement smt(db_->GetUniqueStatement(
"SELECT origin, cert FROM origin_bound_certs"));
sql::Statement update_expires_smt(db_->GetUniqueStatement(
"UPDATE origin_bound_certs SET expiration_time = ? WHERE origin = ?"));
sql::Statement update_creation_smt(db_->GetUniqueStatement(
"UPDATE origin_bound_certs SET creation_time = ? WHERE origin = ?"));
if (!smt.is_valid() ||
!update_expires_smt.is_valid() ||
!update_creation_smt.is_valid()) {
LOG(WARNING) << "Unable to update server bound cert database to "
<< "version 4.";
return false;
}
while (smt.Step()) {
std::string origin = smt.ColumnString(0);
std::string cert_from_db;
smt.ColumnBlobAsString(1, &cert_from_db);
scoped_refptr<net::X509Certificate> cert(
net::X509Certificate::CreateFromBytes(
cert_from_db.data(), cert_from_db.size()));
if (cert) {
if (cur_version == 2) {
update_expires_smt.Reset(true);
update_expires_smt.BindInt64(0,
cert->valid_expiry().ToInternalValue());
update_expires_smt.BindString(1, origin);
if (!update_expires_smt.Run()) {
LOG(WARNING) << "Unable to update server bound cert database to "
<< "version 4.";
return false;
}
}
update_creation_smt.Reset(true);
update_creation_smt.BindInt64(0, cert->valid_start().ToInternalValue());
update_creation_smt.BindString(1, origin);
if (!update_creation_smt.Run()) {
LOG(WARNING) << "Unable to update server bound cert database to "
<< "version 4.";
return false;
}
} else {
LOG(WARNING) << "Error parsing cert for database upgrade for origin "
<< smt.ColumnString(0);
}
}
cur_version = 4;
meta_table_.SetVersionNumber(cur_version);
meta_table_.SetCompatibleVersionNumber(
std::min(cur_version, kCompatibleVersionNumber));
transaction.Commit();
}
LOG_IF(WARNING, cur_version < kCurrentVersionNumber) <<
"Server bound cert database version " << cur_version <<
" is too old to handle.";
return true;
}
void SQLiteServerBoundCertStore::Backend::AddServerBoundCert(
const net::DefaultServerBoundCertStore::ServerBoundCert& cert) {
BatchOperation(PendingOperation::CERT_ADD, cert);
}
void SQLiteServerBoundCertStore::Backend::DeleteServerBoundCert(
const net::DefaultServerBoundCertStore::ServerBoundCert& cert) {
BatchOperation(PendingOperation::CERT_DELETE, cert);
}
void SQLiteServerBoundCertStore::Backend::BatchOperation(
PendingOperation::OperationType op,
const net::DefaultServerBoundCertStore::ServerBoundCert& cert) {
static const int kCommitIntervalMs = 30 * 1000;
static const size_t kCommitAfterBatchSize = 512;
DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::DB));
scoped_ptr<PendingOperation> po(new PendingOperation(op, cert));
PendingOperationsList::size_type num_pending;
{
base::AutoLock locked(lock_);
pending_.push_back(po.release());
num_pending = ++num_pending_;
}
if (num_pending == 1) {
BrowserThread::PostDelayedTask(
BrowserThread::DB, FROM_HERE,
base::Bind(&Backend::Commit, this),
base::TimeDelta::FromMilliseconds(kCommitIntervalMs));
} else if (num_pending == kCommitAfterBatchSize) {
BrowserThread::PostTask(
BrowserThread::DB, FROM_HERE,
base::Bind(&Backend::Commit, this));
}
}
void SQLiteServerBoundCertStore::Backend::Commit() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
PendingOperationsList ops;
{
base::AutoLock locked(lock_);
pending_.swap(ops);
num_pending_ = 0;
}
if (!db_.get() || ops.empty())
return;
sql::Statement add_smt(db_->GetCachedStatement(SQL_FROM_HERE,
"INSERT INTO origin_bound_certs (origin, private_key, cert, cert_type, "
"expiration_time, creation_time) VALUES (?,?,?,?,?,?)"));
if (!add_smt.is_valid())
return;
sql::Statement del_smt(db_->GetCachedStatement(SQL_FROM_HERE,
"DELETE FROM origin_bound_certs WHERE origin=?"));
if (!del_smt.is_valid())
return;
sql::Transaction transaction(db_.get());
if (!transaction.Begin())
return;
for (PendingOperationsList::iterator it = ops.begin();
it != ops.end(); ++it) {
scoped_ptr<PendingOperation> po(*it);
switch (po->op()) {
case PendingOperation::CERT_ADD: {
cert_origins_.insert(po->cert().server_identifier());
add_smt.Reset(true);
add_smt.BindString(0, po->cert().server_identifier());
const std::string& private_key = po->cert().private_key();
add_smt.BindBlob(1, private_key.data(), private_key.size());
const std::string& cert = po->cert().cert();
add_smt.BindBlob(2, cert.data(), cert.size());
add_smt.BindInt(3, po->cert().type());
add_smt.BindInt64(4, po->cert().expiration_time().ToInternalValue());
add_smt.BindInt64(5, po->cert().creation_time().ToInternalValue());
if (!add_smt.Run())
NOTREACHED() << "Could not add a server bound cert to the DB.";
break;
}
case PendingOperation::CERT_DELETE:
cert_origins_.erase(po->cert().server_identifier());
del_smt.Reset(true);
del_smt.BindString(0, po->cert().server_identifier());
if (!del_smt.Run())
NOTREACHED() << "Could not delete a server bound cert from the DB.";
break;
default:
NOTREACHED();
break;
}
}
transaction.Commit();
}
void SQLiteServerBoundCertStore::Backend::Flush(
const base::Closure& completion_task) {
DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::DB));
BrowserThread::PostTask(
BrowserThread::DB, FROM_HERE, base::Bind(&Backend::Commit, this));
if (!completion_task.is_null()) {
BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, completion_task);
}
}
void SQLiteServerBoundCertStore::Backend::Close() {
DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::DB));
BrowserThread::PostTask(
BrowserThread::DB, FROM_HERE,
base::Bind(&Backend::InternalBackgroundClose, this));
}
void SQLiteServerBoundCertStore::Backend::InternalBackgroundClose() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
Commit();
if (!force_keep_session_state_ && clear_on_exit_policy_.get() &&
clear_on_exit_policy_->HasClearOnExitOrigins()) {
DeleteCertificatesOnShutdown();
}
db_.reset();
}
void SQLiteServerBoundCertStore::Backend::DeleteCertificatesOnShutdown() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
if (!db_.get())
return;
if (cert_origins_.empty())
return;
sql::Statement del_smt(db_->GetCachedStatement(
SQL_FROM_HERE, "DELETE FROM origin_bound_certs WHERE origin=?"));
if (!del_smt.is_valid()) {
LOG(WARNING) << "Unable to delete certificates on shutdown.";
return;
}
sql::Transaction transaction(db_.get());
if (!transaction.Begin()) {
LOG(WARNING) << "Unable to delete certificates on shutdown.";
return;
}
for (std::set<std::string>::iterator it = cert_origins_.begin();
it != cert_origins_.end(); ++it) {
if (!clear_on_exit_policy_->ShouldClearOriginOnExit(*it, true))
continue;
del_smt.Reset(true);
del_smt.BindString(0, *it);
if (!del_smt.Run())
NOTREACHED() << "Could not delete a certificate from the DB.";
}
if (!transaction.Commit())
LOG(WARNING) << "Unable to delete certificates on shutdown.";
}
void SQLiteServerBoundCertStore::Backend::SetForceKeepSessionState() {
base::AutoLock locked(lock_);
force_keep_session_state_ = true;
}
SQLiteServerBoundCertStore::SQLiteServerBoundCertStore(
const FilePath& path,
ClearOnExitPolicy* clear_on_exit_policy)
: backend_(new Backend(path, clear_on_exit_policy)) {
}
bool SQLiteServerBoundCertStore::Load(
std::vector<net::DefaultServerBoundCertStore::ServerBoundCert*>* certs) {
return backend_->Load(certs);
}
void SQLiteServerBoundCertStore::AddServerBoundCert(
const net::DefaultServerBoundCertStore::ServerBoundCert& cert) {
if (backend_.get())
backend_->AddServerBoundCert(cert);
}
void SQLiteServerBoundCertStore::DeleteServerBoundCert(
const net::DefaultServerBoundCertStore::ServerBoundCert& cert) {
if (backend_.get())
backend_->DeleteServerBoundCert(cert);
}
void SQLiteServerBoundCertStore::SetForceKeepSessionState() {
if (backend_.get())
backend_->SetForceKeepSessionState();
}
void SQLiteServerBoundCertStore::Flush(const base::Closure& completion_task) {
if (backend_.get())
backend_->Flush(completion_task);
else if (!completion_task.is_null())
MessageLoop::current()->PostTask(FROM_HERE, completion_task);
}
SQLiteServerBoundCertStore::~SQLiteServerBoundCertStore() {
if (backend_.get()) {
backend_->Close();
backend_ = NULL;
}
}