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