Home | History | Annotate | Download | only in media
      1 // Copyright 2013 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 "content/browser/media/webrtc_identity_store_backend.h"
      6 
      7 #include "base/file_util.h"
      8 #include "base/files/file_path.h"
      9 #include "content/public/browser/browser_thread.h"
     10 #include "net/base/net_errors.h"
     11 #include "sql/error_delegate_util.h"
     12 #include "sql/statement.h"
     13 #include "sql/transaction.h"
     14 #include "url/gurl.h"
     15 #include "webkit/browser/quota/special_storage_policy.h"
     16 
     17 namespace content {
     18 
     19 static const char* kWebRTCIdentityStoreDBName = "webrtc_identity_store";
     20 
     21 static const base::FilePath::CharType kWebRTCIdentityStoreDirectory[] =
     22     FILE_PATH_LITERAL("WebRTCIdentityStore");
     23 
     24 // Initializes the identity table, returning true on success.
     25 static bool InitDB(sql::Connection* db) {
     26   if (db->DoesTableExist(kWebRTCIdentityStoreDBName)) {
     27     if (db->DoesColumnExist(kWebRTCIdentityStoreDBName, "origin") &&
     28         db->DoesColumnExist(kWebRTCIdentityStoreDBName, "identity_name") &&
     29         db->DoesColumnExist(kWebRTCIdentityStoreDBName, "common_name") &&
     30         db->DoesColumnExist(kWebRTCIdentityStoreDBName, "certificate") &&
     31         db->DoesColumnExist(kWebRTCIdentityStoreDBName, "private_key") &&
     32         db->DoesColumnExist(kWebRTCIdentityStoreDBName, "creation_time"))
     33       return true;
     34     if (!db->Execute("DROP TABLE webrtc_identity_store"))
     35       return false;
     36   }
     37 
     38   return db->Execute(
     39       "CREATE TABLE webrtc_identity_store"
     40       " ("
     41       "origin TEXT NOT NULL,"
     42       "identity_name TEXT NOT NULL,"
     43       "common_name TEXT NOT NULL,"
     44       "certificate BLOB NOT NULL,"
     45       "private_key BLOB NOT NULL,"
     46       "creation_time INTEGER)");
     47 }
     48 
     49 struct WebRTCIdentityStoreBackend::IdentityKey {
     50   IdentityKey(const GURL& origin, const std::string& identity_name)
     51       : origin(origin), identity_name(identity_name) {}
     52 
     53   bool operator<(const IdentityKey& other) const {
     54     return origin < other.origin ||
     55            (origin == other.origin && identity_name < other.identity_name);
     56   }
     57 
     58   GURL origin;
     59   std::string identity_name;
     60 };
     61 
     62 struct WebRTCIdentityStoreBackend::Identity {
     63   Identity(const std::string& common_name,
     64            const std::string& certificate,
     65            const std::string& private_key)
     66       : common_name(common_name),
     67         certificate(certificate),
     68         private_key(private_key),
     69         creation_time(base::Time::Now().ToInternalValue()) {}
     70 
     71   Identity(const std::string& common_name,
     72            const std::string& certificate,
     73            const std::string& private_key,
     74            int64 creation_time)
     75       : common_name(common_name),
     76         certificate(certificate),
     77         private_key(private_key),
     78         creation_time(creation_time) {}
     79 
     80   std::string common_name;
     81   std::string certificate;
     82   std::string private_key;
     83   int64 creation_time;
     84 };
     85 
     86 struct WebRTCIdentityStoreBackend::PendingFindRequest {
     87   PendingFindRequest(const GURL& origin,
     88                      const std::string& identity_name,
     89                      const std::string& common_name,
     90                      const FindIdentityCallback& callback)
     91       : origin(origin),
     92         identity_name(identity_name),
     93         common_name(common_name),
     94         callback(callback) {}
     95 
     96   ~PendingFindRequest() {}
     97 
     98   GURL origin;
     99   std::string identity_name;
    100   std::string common_name;
    101   FindIdentityCallback callback;
    102 };
    103 
    104 // The class encapsulates the database operations. All members except ctor and
    105 // dtor should be accessed on the DB thread.
    106 // It can be created/destroyed on any thread.
    107 class WebRTCIdentityStoreBackend::SqlLiteStorage
    108     : public base::RefCountedThreadSafe<SqlLiteStorage> {
    109  public:
    110   SqlLiteStorage(const base::FilePath& path,
    111                  quota::SpecialStoragePolicy* policy)
    112       : special_storage_policy_(policy) {
    113     if (!path.empty())
    114       path_ = path.Append(kWebRTCIdentityStoreDirectory);
    115   }
    116 
    117   void Load(IdentityMap* out_map);
    118   void Close();
    119   void AddIdentity(const GURL& origin,
    120                    const std::string& identity_name,
    121                    const Identity& identity);
    122   void DeleteIdentity(const GURL& origin,
    123                       const std::string& identity_name,
    124                       const Identity& identity);
    125   void DeleteBetween(base::Time delete_begin,
    126                      base::Time delete_end,
    127                      const base::Closure& callback);
    128 
    129  private:
    130   friend class base::RefCountedThreadSafe<SqlLiteStorage>;
    131 
    132   enum OperationType {
    133     ADD_IDENTITY,
    134     DELETE_IDENTITY
    135   };
    136   struct PendingOperation {
    137     PendingOperation(OperationType type,
    138                      const GURL& origin,
    139                      const std::string& identity_name,
    140                      const Identity& identity)
    141         : type(type),
    142           origin(origin),
    143           identity_name(identity_name),
    144           identity(identity) {}
    145 
    146     OperationType type;
    147     GURL origin;
    148     std::string identity_name;
    149     Identity identity;
    150   };
    151   typedef std::vector<PendingOperation*> PendingOperationList;
    152 
    153   virtual ~SqlLiteStorage() {}
    154   void OnDatabaseError(int error, sql::Statement* stmt);
    155   void BatchOperation(OperationType type,
    156                       const GURL& origin,
    157                       const std::string& identity_name,
    158                       const Identity& identity);
    159   void Commit();
    160 
    161   // The file path of the DB. Empty if temporary.
    162   base::FilePath path_;
    163   scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy_;
    164   scoped_ptr<sql::Connection> db_;
    165   // Batched DB operations pending to commit.
    166   PendingOperationList pending_operations_;
    167 
    168   DISALLOW_COPY_AND_ASSIGN(SqlLiteStorage);
    169 };
    170 
    171 WebRTCIdentityStoreBackend::WebRTCIdentityStoreBackend(
    172     const base::FilePath& path,
    173     quota::SpecialStoragePolicy* policy)
    174     : state_(NOT_STARTED),
    175       sql_lite_storage_(new SqlLiteStorage(path, policy)) {}
    176 
    177 bool WebRTCIdentityStoreBackend::FindIdentity(
    178     const GURL& origin,
    179     const std::string& identity_name,
    180     const std::string& common_name,
    181     const FindIdentityCallback& callback) {
    182   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    183   if (state_ == CLOSED)
    184     return false;
    185 
    186   if (state_ != LOADED) {
    187     // Queues the request to wait for the DB to load.
    188     pending_find_requests_.push_back(
    189         new PendingFindRequest(origin, identity_name, common_name, callback));
    190     if (state_ == LOADING)
    191       return true;
    192 
    193     DCHECK_EQ(state_, NOT_STARTED);
    194 
    195     // Kick off loading the DB.
    196     scoped_ptr<IdentityMap> out_map(new IdentityMap());
    197     base::Closure task(
    198         base::Bind(&SqlLiteStorage::Load, sql_lite_storage_, out_map.get()));
    199     // |out_map| will be NULL after this call.
    200     if (BrowserThread::PostTaskAndReply(
    201             BrowserThread::DB,
    202             FROM_HERE,
    203             task,
    204             base::Bind(&WebRTCIdentityStoreBackend::OnLoaded,
    205                        this,
    206                        base::Passed(&out_map)))) {
    207       state_ = LOADING;
    208       return true;
    209     }
    210     // If it fails to post task, falls back to ERR_FILE_NOT_FOUND.
    211   }
    212 
    213   IdentityKey key(origin, identity_name);
    214   IdentityMap::iterator iter = identities_.find(key);
    215   if (iter != identities_.end() && iter->second.common_name == common_name) {
    216     // Identity found.
    217     return BrowserThread::PostTask(BrowserThread::IO,
    218                                    FROM_HERE,
    219                                    base::Bind(callback,
    220                                               net::OK,
    221                                               iter->second.certificate,
    222                                               iter->second.private_key));
    223   }
    224 
    225   return BrowserThread::PostTask(
    226       BrowserThread::IO,
    227       FROM_HERE,
    228       base::Bind(callback, net::ERR_FILE_NOT_FOUND, "", ""));
    229 }
    230 
    231 void WebRTCIdentityStoreBackend::AddIdentity(const GURL& origin,
    232                                              const std::string& identity_name,
    233                                              const std::string& common_name,
    234                                              const std::string& certificate,
    235                                              const std::string& private_key) {
    236   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    237   if (state_ == CLOSED)
    238     return;
    239 
    240   // If there is an existing identity for the same origin and identity_name,
    241   // delete it.
    242   IdentityKey key(origin, identity_name);
    243   Identity identity(common_name, certificate, private_key);
    244 
    245   if (identities_.find(key) != identities_.end()) {
    246     if (!BrowserThread::PostTask(BrowserThread::DB,
    247                                  FROM_HERE,
    248                                  base::Bind(&SqlLiteStorage::DeleteIdentity,
    249                                             sql_lite_storage_,
    250                                             origin,
    251                                             identity_name,
    252                                             identities_.find(key)->second)))
    253       return;
    254   }
    255   identities_.insert(std::pair<IdentityKey, Identity>(key, identity));
    256 
    257   BrowserThread::PostTask(BrowserThread::DB,
    258                           FROM_HERE,
    259                           base::Bind(&SqlLiteStorage::AddIdentity,
    260                                      sql_lite_storage_,
    261                                      origin,
    262                                      identity_name,
    263                                      identity));
    264 }
    265 
    266 void WebRTCIdentityStoreBackend::Close() {
    267   if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
    268     BrowserThread::PostTask(
    269         BrowserThread::IO,
    270         FROM_HERE,
    271         base::Bind(&WebRTCIdentityStoreBackend::Close, this));
    272     return;
    273   }
    274 
    275   if (state_ == CLOSED)
    276     return;
    277 
    278   state_ = CLOSED;
    279   BrowserThread::PostTask(
    280       BrowserThread::DB,
    281       FROM_HERE,
    282       base::Bind(&SqlLiteStorage::Close, sql_lite_storage_));
    283 }
    284 
    285 void WebRTCIdentityStoreBackend::DeleteBetween(base::Time delete_begin,
    286                                                base::Time delete_end,
    287                                                const base::Closure& callback) {
    288   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    289   // Delete the in-memory cache.
    290   IdentityMap::iterator it = identities_.begin();
    291   while (it != identities_.end()) {
    292     if (it->second.creation_time >= delete_begin.ToInternalValue() &&
    293         it->second.creation_time <= delete_end.ToInternalValue())
    294       identities_.erase(it++);
    295     else
    296       it++;
    297   }
    298 
    299   BrowserThread::PostTaskAndReply(BrowserThread::DB,
    300                                   FROM_HERE,
    301                                   base::Bind(&SqlLiteStorage::DeleteBetween,
    302                                              sql_lite_storage_,
    303                                              delete_begin,
    304                                              delete_end,
    305                                              callback),
    306                                   callback);
    307 }
    308 
    309 WebRTCIdentityStoreBackend::~WebRTCIdentityStoreBackend() {}
    310 
    311 void WebRTCIdentityStoreBackend::OnLoaded(scoped_ptr<IdentityMap> out_map) {
    312   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    313   state_ = LOADED;
    314   identities_.swap(*out_map);
    315 
    316   for (size_t i = 0; i < pending_find_requests_.size(); ++i) {
    317     FindIdentity(pending_find_requests_[i]->origin,
    318                  pending_find_requests_[i]->identity_name,
    319                  pending_find_requests_[i]->common_name,
    320                  pending_find_requests_[i]->callback);
    321     delete pending_find_requests_[i];
    322   }
    323   pending_find_requests_.clear();
    324 }
    325 
    326 //
    327 // Implementation of SqlLiteStorage.
    328 //
    329 
    330 void WebRTCIdentityStoreBackend::SqlLiteStorage::Load(IdentityMap* out_map) {
    331   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
    332   DCHECK(!db_.get());
    333 
    334   // Ensure the parent directory for storing certs is created before reading
    335   // from it.
    336   const base::FilePath dir = path_.DirName();
    337   if (!base::PathExists(dir) && !file_util::CreateDirectory(dir)) {
    338     DLOG(ERROR) << "Unable to open DB file path.";
    339     return;
    340   }
    341 
    342   db_.reset(new sql::Connection());
    343 
    344   db_->set_error_callback(base::Bind(&SqlLiteStorage::OnDatabaseError, this));
    345 
    346   if (!db_->Open(path_)) {
    347     DLOG(ERROR) << "Unable to open DB.";
    348     db_.reset();
    349     return;
    350   }
    351 
    352   if (!InitDB(db_.get())) {
    353     DLOG(ERROR) << "Unable to init DB.";
    354     db_.reset();
    355     return;
    356   }
    357 
    358   db_->Preload();
    359 
    360   // Slurp all the identities into the out_map.
    361   sql::Statement stmt(db_->GetUniqueStatement(
    362       "SELECT origin, identity_name, common_name, "
    363       "certificate, private_key, creation_time "
    364       "FROM webrtc_identity_store"));
    365   CHECK(stmt.is_valid());
    366 
    367   while (stmt.Step()) {
    368     IdentityKey key(GURL(stmt.ColumnString(0)), stmt.ColumnString(1));
    369     std::string common_name(stmt.ColumnString(2));
    370     std::string cert, private_key;
    371     stmt.ColumnBlobAsString(3, &cert);
    372     stmt.ColumnBlobAsString(4, &private_key);
    373     int64 creation_time = stmt.ColumnInt64(5);
    374     std::pair<IdentityMap::iterator, bool> result =
    375         out_map->insert(std::pair<IdentityKey, Identity>(
    376             key, Identity(common_name, cert, private_key, creation_time)));
    377     DCHECK(result.second);
    378   }
    379 }
    380 
    381 void WebRTCIdentityStoreBackend::SqlLiteStorage::Close() {
    382   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
    383   Commit();
    384   db_.reset();
    385 }
    386 
    387 void WebRTCIdentityStoreBackend::SqlLiteStorage::AddIdentity(
    388     const GURL& origin,
    389     const std::string& identity_name,
    390     const Identity& identity) {
    391   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
    392   if (!db_.get())
    393     return;
    394 
    395   // Do not add for session only origins.
    396   if (special_storage_policy_.get() &&
    397       !special_storage_policy_->IsStorageProtected(origin) &&
    398       special_storage_policy_->IsStorageSessionOnly(origin)) {
    399     return;
    400   }
    401   BatchOperation(ADD_IDENTITY, origin, identity_name, identity);
    402 }
    403 
    404 void WebRTCIdentityStoreBackend::SqlLiteStorage::DeleteIdentity(
    405     const GURL& origin,
    406     const std::string& identity_name,
    407     const Identity& identity) {
    408   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
    409   if (!db_.get())
    410     return;
    411   BatchOperation(DELETE_IDENTITY, origin, identity_name, identity);
    412 }
    413 
    414 void WebRTCIdentityStoreBackend::SqlLiteStorage::OnDatabaseError(
    415     int error,
    416     sql::Statement* stmt) {
    417   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
    418   if (!sql::IsErrorCatastrophic(error))
    419     return;
    420   db_->RazeAndClose();
    421 }
    422 
    423 void WebRTCIdentityStoreBackend::SqlLiteStorage::BatchOperation(
    424     OperationType type,
    425     const GURL& origin,
    426     const std::string& identity_name,
    427     const Identity& identity) {
    428   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
    429   // Commit every 30 seconds.
    430   static const base::TimeDelta kCommitInterval(
    431       base::TimeDelta::FromSeconds(30));
    432   // Commit right away if we have more than 512 outstanding operations.
    433   static const size_t kCommitAfterBatchSize = 512;
    434 
    435   // We do a full copy of the cert here, and hopefully just here.
    436   scoped_ptr<PendingOperation> operation(
    437       new PendingOperation(type, origin, identity_name, identity));
    438 
    439   pending_operations_.push_back(operation.release());
    440 
    441   if (pending_operations_.size() == 1) {
    442     // We've gotten our first entry for this batch, fire off the timer.
    443     BrowserThread::PostDelayedTask(BrowserThread::DB,
    444                                    FROM_HERE,
    445                                    base::Bind(&SqlLiteStorage::Commit, this),
    446                                    kCommitInterval);
    447   } else if (pending_operations_.size() >= kCommitAfterBatchSize) {
    448     // We've reached a big enough batch, fire off a commit now.
    449     BrowserThread::PostTask(BrowserThread::DB,
    450                             FROM_HERE,
    451                             base::Bind(&SqlLiteStorage::Commit, this));
    452   }
    453 }
    454 
    455 void WebRTCIdentityStoreBackend::SqlLiteStorage::Commit() {
    456   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
    457   // Maybe an old timer fired or we are already Close()'ed.
    458   if (!db_.get() || pending_operations_.empty())
    459     return;
    460 
    461   sql::Statement add_stmt(db_->GetCachedStatement(
    462       SQL_FROM_HERE,
    463       "INSERT INTO webrtc_identity_store "
    464       "(origin, identity_name, common_name, certificate,"
    465       " private_key, creation_time) VALUES"
    466       " (?,?,?,?,?,?)"));
    467 
    468   CHECK(add_stmt.is_valid());
    469 
    470   sql::Statement del_stmt(db_->GetCachedStatement(
    471       SQL_FROM_HERE,
    472       "DELETE FROM webrtc_identity_store WHERE origin=? AND identity_name=?"));
    473 
    474   CHECK(del_stmt.is_valid());
    475 
    476   sql::Transaction transaction(db_.get());
    477   if (!transaction.Begin()) {
    478     DLOG(ERROR) << "Failed to begin the transaction.";
    479     return;
    480   }
    481 
    482   for (PendingOperationList::iterator it = pending_operations_.begin();
    483        it != pending_operations_.end();
    484        ++it) {
    485     scoped_ptr<PendingOperation> po(*it);
    486     switch (po->type) {
    487       case ADD_IDENTITY: {
    488         add_stmt.Reset(true);
    489         add_stmt.BindString(0, po->origin.spec());
    490         add_stmt.BindString(1, po->identity_name);
    491         add_stmt.BindString(2, po->identity.common_name);
    492         const std::string& cert = po->identity.certificate;
    493         add_stmt.BindBlob(3, cert.data(), cert.size());
    494         const std::string& private_key = po->identity.private_key;
    495         add_stmt.BindBlob(4, private_key.data(), private_key.size());
    496         add_stmt.BindInt64(5, po->identity.creation_time);
    497         CHECK(add_stmt.Run());
    498         break;
    499       }
    500       case DELETE_IDENTITY:
    501         del_stmt.Reset(true);
    502         del_stmt.BindString(0, po->origin.spec());
    503         add_stmt.BindString(1, po->identity_name);
    504         CHECK(del_stmt.Run());
    505         break;
    506 
    507       default:
    508         NOTREACHED();
    509         break;
    510     }
    511   }
    512   transaction.Commit();
    513   pending_operations_.clear();
    514 }
    515 
    516 void WebRTCIdentityStoreBackend::SqlLiteStorage::DeleteBetween(
    517     base::Time delete_begin,
    518     base::Time delete_end,
    519     const base::Closure& callback) {
    520   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
    521   if (!db_.get())
    522     return;
    523 
    524   // Commit pending operations first.
    525   Commit();
    526 
    527   sql::Statement del_stmt(db_->GetCachedStatement(
    528       SQL_FROM_HERE,
    529       "DELETE FROM webrtc_identity_store"
    530       " WHERE creation_time >= ? AND creation_time <= ?"));
    531   CHECK(del_stmt.is_valid());
    532 
    533   del_stmt.BindInt64(0, delete_begin.ToInternalValue());
    534   del_stmt.BindInt64(1, delete_end.ToInternalValue());
    535 
    536   sql::Transaction transaction(db_.get());
    537   if (!transaction.Begin()) {
    538     DLOG(ERROR) << "Failed to begin the transaction.";
    539     return;
    540   }
    541 
    542   CHECK(del_stmt.Run());
    543   transaction.Commit();
    544 }
    545 
    546 }  // namespace content
    547