// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "content/browser/media/webrtc_identity_store_backend.h" #include "base/file_util.h" #include "base/files/file_path.h" #include "content/public/browser/browser_thread.h" #include "net/base/net_errors.h" #include "sql/error_delegate_util.h" #include "sql/statement.h" #include "sql/transaction.h" #include "url/gurl.h" #include "webkit/browser/quota/special_storage_policy.h" namespace content { static const char* kWebRTCIdentityStoreDBName = "webrtc_identity_store"; static const base::FilePath::CharType kWebRTCIdentityStoreDirectory[] = FILE_PATH_LITERAL("WebRTCIdentityStore"); // Initializes the identity table, returning true on success. static bool InitDB(sql::Connection* db) { if (db->DoesTableExist(kWebRTCIdentityStoreDBName)) { if (db->DoesColumnExist(kWebRTCIdentityStoreDBName, "origin") && db->DoesColumnExist(kWebRTCIdentityStoreDBName, "identity_name") && db->DoesColumnExist(kWebRTCIdentityStoreDBName, "common_name") && db->DoesColumnExist(kWebRTCIdentityStoreDBName, "certificate") && db->DoesColumnExist(kWebRTCIdentityStoreDBName, "private_key") && db->DoesColumnExist(kWebRTCIdentityStoreDBName, "creation_time")) return true; if (!db->Execute("DROP TABLE webrtc_identity_store")) return false; } return db->Execute( "CREATE TABLE webrtc_identity_store" " (" "origin TEXT NOT NULL," "identity_name TEXT NOT NULL," "common_name TEXT NOT NULL," "certificate BLOB NOT NULL," "private_key BLOB NOT NULL," "creation_time INTEGER)"); } struct WebRTCIdentityStoreBackend::IdentityKey { IdentityKey(const GURL& origin, const std::string& identity_name) : origin(origin), identity_name(identity_name) {} bool operator<(const IdentityKey& other) const { return origin < other.origin || (origin == other.origin && identity_name < other.identity_name); } GURL origin; std::string identity_name; }; struct WebRTCIdentityStoreBackend::Identity { Identity(const std::string& common_name, const std::string& certificate, const std::string& private_key) : common_name(common_name), certificate(certificate), private_key(private_key), creation_time(base::Time::Now().ToInternalValue()) {} Identity(const std::string& common_name, const std::string& certificate, const std::string& private_key, int64 creation_time) : common_name(common_name), certificate(certificate), private_key(private_key), creation_time(creation_time) {} std::string common_name; std::string certificate; std::string private_key; int64 creation_time; }; struct WebRTCIdentityStoreBackend::PendingFindRequest { PendingFindRequest(const GURL& origin, const std::string& identity_name, const std::string& common_name, const FindIdentityCallback& callback) : origin(origin), identity_name(identity_name), common_name(common_name), callback(callback) {} ~PendingFindRequest() {} GURL origin; std::string identity_name; std::string common_name; FindIdentityCallback callback; }; // The class encapsulates the database operations. All members except ctor and // dtor should be accessed on the DB thread. // It can be created/destroyed on any thread. class WebRTCIdentityStoreBackend::SqlLiteStorage : public base::RefCountedThreadSafe { public: SqlLiteStorage(const base::FilePath& path, quota::SpecialStoragePolicy* policy) : special_storage_policy_(policy) { if (!path.empty()) path_ = path.Append(kWebRTCIdentityStoreDirectory); } void Load(IdentityMap* out_map); void Close(); void AddIdentity(const GURL& origin, const std::string& identity_name, const Identity& identity); void DeleteIdentity(const GURL& origin, const std::string& identity_name, const Identity& identity); void DeleteBetween(base::Time delete_begin, base::Time delete_end, const base::Closure& callback); private: friend class base::RefCountedThreadSafe; enum OperationType { ADD_IDENTITY, DELETE_IDENTITY }; struct PendingOperation { PendingOperation(OperationType type, const GURL& origin, const std::string& identity_name, const Identity& identity) : type(type), origin(origin), identity_name(identity_name), identity(identity) {} OperationType type; GURL origin; std::string identity_name; Identity identity; }; typedef std::vector PendingOperationList; virtual ~SqlLiteStorage() {} void OnDatabaseError(int error, sql::Statement* stmt); void BatchOperation(OperationType type, const GURL& origin, const std::string& identity_name, const Identity& identity); void Commit(); // The file path of the DB. Empty if temporary. base::FilePath path_; scoped_refptr special_storage_policy_; scoped_ptr db_; // Batched DB operations pending to commit. PendingOperationList pending_operations_; DISALLOW_COPY_AND_ASSIGN(SqlLiteStorage); }; WebRTCIdentityStoreBackend::WebRTCIdentityStoreBackend( const base::FilePath& path, quota::SpecialStoragePolicy* policy) : state_(NOT_STARTED), sql_lite_storage_(new SqlLiteStorage(path, policy)) {} bool WebRTCIdentityStoreBackend::FindIdentity( const GURL& origin, const std::string& identity_name, const std::string& common_name, const FindIdentityCallback& callback) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); if (state_ == CLOSED) return false; if (state_ != LOADED) { // Queues the request to wait for the DB to load. pending_find_requests_.push_back( new PendingFindRequest(origin, identity_name, common_name, callback)); if (state_ == LOADING) return true; DCHECK_EQ(state_, NOT_STARTED); // Kick off loading the DB. scoped_ptr out_map(new IdentityMap()); base::Closure task( base::Bind(&SqlLiteStorage::Load, sql_lite_storage_, out_map.get())); // |out_map| will be NULL after this call. if (BrowserThread::PostTaskAndReply( BrowserThread::DB, FROM_HERE, task, base::Bind(&WebRTCIdentityStoreBackend::OnLoaded, this, base::Passed(&out_map)))) { state_ = LOADING; return true; } // If it fails to post task, falls back to ERR_FILE_NOT_FOUND. } IdentityKey key(origin, identity_name); IdentityMap::iterator iter = identities_.find(key); if (iter != identities_.end() && iter->second.common_name == common_name) { // Identity found. return BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(callback, net::OK, iter->second.certificate, iter->second.private_key)); } return BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(callback, net::ERR_FILE_NOT_FOUND, "", "")); } void WebRTCIdentityStoreBackend::AddIdentity(const GURL& origin, const std::string& identity_name, const std::string& common_name, const std::string& certificate, const std::string& private_key) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); if (state_ == CLOSED) return; // If there is an existing identity for the same origin and identity_name, // delete it. IdentityKey key(origin, identity_name); Identity identity(common_name, certificate, private_key); if (identities_.find(key) != identities_.end()) { if (!BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, base::Bind(&SqlLiteStorage::DeleteIdentity, sql_lite_storage_, origin, identity_name, identities_.find(key)->second))) return; } identities_.insert(std::pair(key, identity)); BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, base::Bind(&SqlLiteStorage::AddIdentity, sql_lite_storage_, origin, identity_name, identity)); } void WebRTCIdentityStoreBackend::Close() { if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&WebRTCIdentityStoreBackend::Close, this)); return; } if (state_ == CLOSED) return; state_ = CLOSED; BrowserThread::PostTask( BrowserThread::DB, FROM_HERE, base::Bind(&SqlLiteStorage::Close, sql_lite_storage_)); } void WebRTCIdentityStoreBackend::DeleteBetween(base::Time delete_begin, base::Time delete_end, const base::Closure& callback) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); // Delete the in-memory cache. IdentityMap::iterator it = identities_.begin(); while (it != identities_.end()) { if (it->second.creation_time >= delete_begin.ToInternalValue() && it->second.creation_time <= delete_end.ToInternalValue()) identities_.erase(it++); else it++; } BrowserThread::PostTaskAndReply(BrowserThread::DB, FROM_HERE, base::Bind(&SqlLiteStorage::DeleteBetween, sql_lite_storage_, delete_begin, delete_end, callback), callback); } WebRTCIdentityStoreBackend::~WebRTCIdentityStoreBackend() {} void WebRTCIdentityStoreBackend::OnLoaded(scoped_ptr out_map) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); state_ = LOADED; identities_.swap(*out_map); for (size_t i = 0; i < pending_find_requests_.size(); ++i) { FindIdentity(pending_find_requests_[i]->origin, pending_find_requests_[i]->identity_name, pending_find_requests_[i]->common_name, pending_find_requests_[i]->callback); delete pending_find_requests_[i]; } pending_find_requests_.clear(); } // // Implementation of SqlLiteStorage. // void WebRTCIdentityStoreBackend::SqlLiteStorage::Load(IdentityMap* out_map) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); DCHECK(!db_.get()); // Ensure the parent directory for storing certs is created before reading // from it. const base::FilePath dir = path_.DirName(); if (!base::PathExists(dir) && !file_util::CreateDirectory(dir)) { DLOG(ERROR) << "Unable to open DB file path."; return; } db_.reset(new sql::Connection()); db_->set_error_callback(base::Bind(&SqlLiteStorage::OnDatabaseError, this)); if (!db_->Open(path_)) { DLOG(ERROR) << "Unable to open DB."; db_.reset(); return; } if (!InitDB(db_.get())) { DLOG(ERROR) << "Unable to init DB."; db_.reset(); return; } db_->Preload(); // Slurp all the identities into the out_map. sql::Statement stmt(db_->GetUniqueStatement( "SELECT origin, identity_name, common_name, " "certificate, private_key, creation_time " "FROM webrtc_identity_store")); CHECK(stmt.is_valid()); while (stmt.Step()) { IdentityKey key(GURL(stmt.ColumnString(0)), stmt.ColumnString(1)); std::string common_name(stmt.ColumnString(2)); std::string cert, private_key; stmt.ColumnBlobAsString(3, &cert); stmt.ColumnBlobAsString(4, &private_key); int64 creation_time = stmt.ColumnInt64(5); std::pair result = out_map->insert(std::pair( key, Identity(common_name, cert, private_key, creation_time))); DCHECK(result.second); } } void WebRTCIdentityStoreBackend::SqlLiteStorage::Close() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); Commit(); db_.reset(); } void WebRTCIdentityStoreBackend::SqlLiteStorage::AddIdentity( const GURL& origin, const std::string& identity_name, const Identity& identity) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); if (!db_.get()) return; // Do not add for session only origins. if (special_storage_policy_.get() && !special_storage_policy_->IsStorageProtected(origin) && special_storage_policy_->IsStorageSessionOnly(origin)) { return; } BatchOperation(ADD_IDENTITY, origin, identity_name, identity); } void WebRTCIdentityStoreBackend::SqlLiteStorage::DeleteIdentity( const GURL& origin, const std::string& identity_name, const Identity& identity) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); if (!db_.get()) return; BatchOperation(DELETE_IDENTITY, origin, identity_name, identity); } void WebRTCIdentityStoreBackend::SqlLiteStorage::OnDatabaseError( int error, sql::Statement* stmt) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); if (!sql::IsErrorCatastrophic(error)) return; db_->RazeAndClose(); } void WebRTCIdentityStoreBackend::SqlLiteStorage::BatchOperation( OperationType type, const GURL& origin, const std::string& identity_name, const Identity& identity) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); // Commit every 30 seconds. static const base::TimeDelta kCommitInterval( base::TimeDelta::FromSeconds(30)); // Commit right away if we have more than 512 outstanding operations. static const size_t kCommitAfterBatchSize = 512; // We do a full copy of the cert here, and hopefully just here. scoped_ptr operation( new PendingOperation(type, origin, identity_name, identity)); pending_operations_.push_back(operation.release()); if (pending_operations_.size() == 1) { // We've gotten our first entry for this batch, fire off the timer. BrowserThread::PostDelayedTask(BrowserThread::DB, FROM_HERE, base::Bind(&SqlLiteStorage::Commit, this), kCommitInterval); } else if (pending_operations_.size() >= kCommitAfterBatchSize) { // We've reached a big enough batch, fire off a commit now. BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, base::Bind(&SqlLiteStorage::Commit, this)); } } void WebRTCIdentityStoreBackend::SqlLiteStorage::Commit() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); // Maybe an old timer fired or we are already Close()'ed. if (!db_.get() || pending_operations_.empty()) return; sql::Statement add_stmt(db_->GetCachedStatement( SQL_FROM_HERE, "INSERT INTO webrtc_identity_store " "(origin, identity_name, common_name, certificate," " private_key, creation_time) VALUES" " (?,?,?,?,?,?)")); CHECK(add_stmt.is_valid()); sql::Statement del_stmt(db_->GetCachedStatement( SQL_FROM_HERE, "DELETE FROM webrtc_identity_store WHERE origin=? AND identity_name=?")); CHECK(del_stmt.is_valid()); sql::Transaction transaction(db_.get()); if (!transaction.Begin()) { DLOG(ERROR) << "Failed to begin the transaction."; return; } for (PendingOperationList::iterator it = pending_operations_.begin(); it != pending_operations_.end(); ++it) { scoped_ptr po(*it); switch (po->type) { case ADD_IDENTITY: { add_stmt.Reset(true); add_stmt.BindString(0, po->origin.spec()); add_stmt.BindString(1, po->identity_name); add_stmt.BindString(2, po->identity.common_name); const std::string& cert = po->identity.certificate; add_stmt.BindBlob(3, cert.data(), cert.size()); const std::string& private_key = po->identity.private_key; add_stmt.BindBlob(4, private_key.data(), private_key.size()); add_stmt.BindInt64(5, po->identity.creation_time); CHECK(add_stmt.Run()); break; } case DELETE_IDENTITY: del_stmt.Reset(true); del_stmt.BindString(0, po->origin.spec()); add_stmt.BindString(1, po->identity_name); CHECK(del_stmt.Run()); break; default: NOTREACHED(); break; } } transaction.Commit(); pending_operations_.clear(); } void WebRTCIdentityStoreBackend::SqlLiteStorage::DeleteBetween( base::Time delete_begin, base::Time delete_end, const base::Closure& callback) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); if (!db_.get()) return; // Commit pending operations first. Commit(); sql::Statement del_stmt(db_->GetCachedStatement( SQL_FROM_HERE, "DELETE FROM webrtc_identity_store" " WHERE creation_time >= ? AND creation_time <= ?")); CHECK(del_stmt.is_valid()); del_stmt.BindInt64(0, delete_begin.ToInternalValue()); del_stmt.BindInt64(1, delete_end.ToInternalValue()); sql::Transaction transaction(db_.get()); if (!transaction.Begin()) { DLOG(ERROR) << "Failed to begin the transaction."; return; } CHECK(del_stmt.Run()); transaction.Commit(); } } // namespace content