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 "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 "url/gurl.h"
     17 #include "webkit/browser/quota/special_storage_policy.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                  quota::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<quota::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     quota::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 bool WebRTCIdentityStoreBackend::FindIdentity(
    189     const GURL& origin,
    190     const std::string& identity_name,
    191     const std::string& common_name,
    192     const FindIdentityCallback& callback) {
    193   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    194   if (state_ == CLOSED)
    195     return false;
    196 
    197   if (state_ != LOADED) {
    198     // Queues the request to wait for the DB to load.
    199     pending_find_requests_.push_back(
    200         new PendingFindRequest(origin, identity_name, common_name, callback));
    201     if (state_ == LOADING)
    202       return true;
    203 
    204     DCHECK_EQ(state_, NOT_STARTED);
    205 
    206     // Kick off loading the DB.
    207     scoped_ptr<IdentityMap> out_map(new IdentityMap());
    208     base::Closure task(
    209         base::Bind(&SqlLiteStorage::Load, sql_lite_storage_, out_map.get()));
    210     // |out_map| will be NULL after this call.
    211     if (BrowserThread::PostTaskAndReply(
    212             BrowserThread::DB,
    213             FROM_HERE,
    214             task,
    215             base::Bind(&WebRTCIdentityStoreBackend::OnLoaded,
    216                        this,
    217                        base::Passed(&out_map)))) {
    218       state_ = LOADING;
    219       return true;
    220     }
    221     // If it fails to post task, falls back to ERR_FILE_NOT_FOUND.
    222   }
    223 
    224   IdentityKey key(origin, identity_name);
    225   IdentityMap::iterator iter = identities_.find(key);
    226   if (iter != identities_.end() && iter->second.common_name == common_name) {
    227     base::TimeDelta age = base::Time::Now() - base::Time::FromInternalValue(
    228                                                   iter->second.creation_time);
    229     if (age < validity_period_) {
    230       // Identity found.
    231       return BrowserThread::PostTask(BrowserThread::IO,
    232                                      FROM_HERE,
    233                                      base::Bind(callback,
    234                                                 net::OK,
    235                                                 iter->second.certificate,
    236                                                 iter->second.private_key));
    237     }
    238     // Removes the expired identity from the in-memory cache. The copy in the
    239     // database will be removed on the next load.
    240     identities_.erase(iter);
    241   }
    242 
    243   return BrowserThread::PostTask(
    244       BrowserThread::IO,
    245       FROM_HERE,
    246       base::Bind(callback, net::ERR_FILE_NOT_FOUND, "", ""));
    247 }
    248 
    249 void WebRTCIdentityStoreBackend::AddIdentity(const GURL& origin,
    250                                              const std::string& identity_name,
    251                                              const std::string& common_name,
    252                                              const std::string& certificate,
    253                                              const std::string& private_key) {
    254   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    255   if (state_ == CLOSED)
    256     return;
    257 
    258   // If there is an existing identity for the same origin and identity_name,
    259   // delete it.
    260   IdentityKey key(origin, identity_name);
    261   Identity identity(common_name, certificate, private_key);
    262 
    263   if (identities_.find(key) != identities_.end()) {
    264     if (!BrowserThread::PostTask(BrowserThread::DB,
    265                                  FROM_HERE,
    266                                  base::Bind(&SqlLiteStorage::DeleteIdentity,
    267                                             sql_lite_storage_,
    268                                             origin,
    269                                             identity_name,
    270                                             identities_.find(key)->second)))
    271       return;
    272   }
    273   identities_.insert(std::pair<IdentityKey, Identity>(key, identity));
    274 
    275   BrowserThread::PostTask(BrowserThread::DB,
    276                           FROM_HERE,
    277                           base::Bind(&SqlLiteStorage::AddIdentity,
    278                                      sql_lite_storage_,
    279                                      origin,
    280                                      identity_name,
    281                                      identity));
    282 }
    283 
    284 void WebRTCIdentityStoreBackend::Close() {
    285   if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
    286     BrowserThread::PostTask(
    287         BrowserThread::IO,
    288         FROM_HERE,
    289         base::Bind(&WebRTCIdentityStoreBackend::Close, this));
    290     return;
    291   }
    292 
    293   if (state_ == CLOSED)
    294     return;
    295 
    296   state_ = CLOSED;
    297   BrowserThread::PostTask(
    298       BrowserThread::DB,
    299       FROM_HERE,
    300       base::Bind(&SqlLiteStorage::Close, sql_lite_storage_));
    301 }
    302 
    303 void WebRTCIdentityStoreBackend::DeleteBetween(base::Time delete_begin,
    304                                                base::Time delete_end,
    305                                                const base::Closure& callback) {
    306   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    307   if (state_ == CLOSED)
    308     return;
    309 
    310   // Delete the in-memory cache.
    311   IdentityMap::iterator it = identities_.begin();
    312   while (it != identities_.end()) {
    313     if (it->second.creation_time >= delete_begin.ToInternalValue() &&
    314         it->second.creation_time <= delete_end.ToInternalValue()) {
    315       identities_.erase(it++);
    316     } else {
    317       ++it;
    318     }
    319   }
    320   BrowserThread::PostTaskAndReply(BrowserThread::DB,
    321                                   FROM_HERE,
    322                                   base::Bind(&SqlLiteStorage::DeleteBetween,
    323                                              sql_lite_storage_,
    324                                              delete_begin,
    325                                              delete_end),
    326                                   callback);
    327 }
    328 
    329 void WebRTCIdentityStoreBackend::SetValidityPeriodForTesting(
    330     base::TimeDelta validity_period) {
    331   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    332   validity_period_ = validity_period;
    333   BrowserThread::PostTask(
    334       BrowserThread::DB,
    335       FROM_HERE,
    336       base::Bind(&SqlLiteStorage::SetValidityPeriodForTesting,
    337                  sql_lite_storage_,
    338                  validity_period));
    339 }
    340 
    341 WebRTCIdentityStoreBackend::~WebRTCIdentityStoreBackend() {}
    342 
    343 void WebRTCIdentityStoreBackend::OnLoaded(scoped_ptr<IdentityMap> out_map) {
    344   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    345 
    346   if (state_ != LOADING)
    347     return;
    348 
    349   DVLOG(3) << "WebRTC identity store has loaded.";
    350 
    351   state_ = LOADED;
    352   identities_.swap(*out_map);
    353 
    354   for (size_t i = 0; i < pending_find_requests_.size(); ++i) {
    355     FindIdentity(pending_find_requests_[i]->origin,
    356                  pending_find_requests_[i]->identity_name,
    357                  pending_find_requests_[i]->common_name,
    358                  pending_find_requests_[i]->callback);
    359     delete pending_find_requests_[i];
    360   }
    361   pending_find_requests_.clear();
    362 }
    363 
    364 //
    365 // Implementation of SqlLiteStorage.
    366 //
    367 
    368 void WebRTCIdentityStoreBackend::SqlLiteStorage::Load(IdentityMap* out_map) {
    369   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
    370   DCHECK(!db_.get());
    371 
    372   // Ensure the parent directory for storing certs is created before reading
    373   // from it.
    374   const base::FilePath dir = path_.DirName();
    375   if (!base::PathExists(dir) && !base::CreateDirectory(dir)) {
    376     DVLOG(2) << "Unable to open DB file path.";
    377     return;
    378   }
    379 
    380   db_.reset(new sql::Connection());
    381 
    382   db_->set_error_callback(base::Bind(&SqlLiteStorage::OnDatabaseError, this));
    383 
    384   if (!db_->Open(path_)) {
    385     DVLOG(2) << "Unable to open DB.";
    386     db_.reset();
    387     return;
    388   }
    389 
    390   if (!InitDB(db_.get())) {
    391     DVLOG(2) << "Unable to init DB.";
    392     db_.reset();
    393     return;
    394   }
    395 
    396   db_->Preload();
    397 
    398   // Delete expired identities.
    399   DeleteBetween(base::Time(), base::Time::Now() - validity_period_);
    400 
    401   // Slurp all the identities into the out_map.
    402   sql::Statement stmt(db_->GetUniqueStatement(
    403       "SELECT origin, identity_name, common_name, "
    404       "certificate, private_key, creation_time "
    405       "FROM webrtc_identity_store"));
    406   CHECK(stmt.is_valid());
    407 
    408   while (stmt.Step()) {
    409     IdentityKey key(GURL(stmt.ColumnString(0)), stmt.ColumnString(1));
    410     std::string common_name(stmt.ColumnString(2));
    411     std::string cert, private_key;
    412     stmt.ColumnBlobAsString(3, &cert);
    413     stmt.ColumnBlobAsString(4, &private_key);
    414     int64 creation_time = stmt.ColumnInt64(5);
    415     std::pair<IdentityMap::iterator, bool> result =
    416         out_map->insert(std::pair<IdentityKey, Identity>(
    417             key, Identity(common_name, cert, private_key, creation_time)));
    418     DCHECK(result.second);
    419   }
    420 }
    421 
    422 void WebRTCIdentityStoreBackend::SqlLiteStorage::Close() {
    423   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
    424   Commit();
    425   db_.reset();
    426 }
    427 
    428 void WebRTCIdentityStoreBackend::SqlLiteStorage::AddIdentity(
    429     const GURL& origin,
    430     const std::string& identity_name,
    431     const Identity& identity) {
    432   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
    433   if (!db_.get())
    434     return;
    435 
    436   // Do not add for session only origins.
    437   if (special_storage_policy_.get() &&
    438       !special_storage_policy_->IsStorageProtected(origin) &&
    439       special_storage_policy_->IsStorageSessionOnly(origin)) {
    440     return;
    441   }
    442   BatchOperation(ADD_IDENTITY, origin, identity_name, identity);
    443 }
    444 
    445 void WebRTCIdentityStoreBackend::SqlLiteStorage::DeleteIdentity(
    446     const GURL& origin,
    447     const std::string& identity_name,
    448     const Identity& identity) {
    449   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
    450   if (!db_.get())
    451     return;
    452   BatchOperation(DELETE_IDENTITY, origin, identity_name, identity);
    453 }
    454 
    455 void WebRTCIdentityStoreBackend::SqlLiteStorage::DeleteBetween(
    456     base::Time delete_begin,
    457     base::Time delete_end) {
    458   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
    459   if (!db_.get())
    460     return;
    461 
    462   // Commit pending operations first.
    463   Commit();
    464 
    465   sql::Statement del_stmt(db_->GetCachedStatement(
    466       SQL_FROM_HERE,
    467       "DELETE FROM webrtc_identity_store"
    468       " WHERE creation_time >= ? AND creation_time <= ?"));
    469   CHECK(del_stmt.is_valid());
    470 
    471   del_stmt.BindInt64(0, delete_begin.ToInternalValue());
    472   del_stmt.BindInt64(1, delete_end.ToInternalValue());
    473 
    474   sql::Transaction transaction(db_.get());
    475   if (!transaction.Begin()) {
    476     DVLOG(2) << "Failed to begin the transaction.";
    477     return;
    478   }
    479 
    480   if (!del_stmt.Run()) {
    481     DVLOG(2) << "Failed to run the delete statement.";
    482     return;
    483   }
    484 
    485   if (!transaction.Commit())
    486     DVLOG(2) << "Failed to commit the transaction.";
    487 }
    488 
    489 void WebRTCIdentityStoreBackend::SqlLiteStorage::OnDatabaseError(
    490     int error,
    491     sql::Statement* stmt) {
    492   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
    493 
    494   db_->RazeAndClose();
    495   // It's not safe to reset |db_| here.
    496 }
    497 
    498 void WebRTCIdentityStoreBackend::SqlLiteStorage::BatchOperation(
    499     OperationType type,
    500     const GURL& origin,
    501     const std::string& identity_name,
    502     const Identity& identity) {
    503   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
    504   // Commit every 30 seconds.
    505   static const base::TimeDelta kCommitInterval(
    506       base::TimeDelta::FromSeconds(30));
    507   // Commit right away if we have more than 512 outstanding operations.
    508   static const size_t kCommitAfterBatchSize = 512;
    509 
    510   // We do a full copy of the cert here, and hopefully just here.
    511   scoped_ptr<PendingOperation> operation(
    512       new PendingOperation(type, origin, identity_name, identity));
    513 
    514   pending_operations_.push_back(operation.release());
    515 
    516   if (pending_operations_.size() == 1) {
    517     // We've gotten our first entry for this batch, fire off the timer.
    518     BrowserThread::PostDelayedTask(BrowserThread::DB,
    519                                    FROM_HERE,
    520                                    base::Bind(&SqlLiteStorage::Commit, this),
    521                                    kCommitInterval);
    522   } else if (pending_operations_.size() >= kCommitAfterBatchSize) {
    523     // We've reached a big enough batch, fire off a commit now.
    524     BrowserThread::PostTask(BrowserThread::DB,
    525                             FROM_HERE,
    526                             base::Bind(&SqlLiteStorage::Commit, this));
    527   }
    528 }
    529 
    530 void WebRTCIdentityStoreBackend::SqlLiteStorage::Commit() {
    531   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
    532   // Maybe an old timer fired or we are already Close()'ed.
    533   if (!db_.get() || pending_operations_.empty())
    534     return;
    535 
    536   sql::Statement add_stmt(db_->GetCachedStatement(
    537       SQL_FROM_HERE,
    538       "INSERT INTO webrtc_identity_store "
    539       "(origin, identity_name, common_name, certificate,"
    540       " private_key, creation_time) VALUES"
    541       " (?,?,?,?,?,?)"));
    542 
    543   CHECK(add_stmt.is_valid());
    544 
    545   sql::Statement del_stmt(db_->GetCachedStatement(
    546       SQL_FROM_HERE,
    547       "DELETE FROM webrtc_identity_store WHERE origin=? AND identity_name=?"));
    548 
    549   CHECK(del_stmt.is_valid());
    550 
    551   sql::Transaction transaction(db_.get());
    552   if (!transaction.Begin()) {
    553     DVLOG(2) << "Failed to begin the transaction.";
    554     return;
    555   }
    556 
    557   // Swaps |pending_operations_| into a temporary list to make sure
    558   // |pending_operations_| is always cleared in case of DB errors.
    559   PendingOperationList pending_operations_copy;
    560   pending_operations_.swap(pending_operations_copy);
    561 
    562   for (PendingOperationList::const_iterator it =
    563            pending_operations_copy.begin();
    564        it != pending_operations_copy.end();
    565        ++it) {
    566     switch ((*it)->type) {
    567       case ADD_IDENTITY: {
    568         add_stmt.Reset(true);
    569         add_stmt.BindString(0, (*it)->origin.spec());
    570         add_stmt.BindString(1, (*it)->identity_name);
    571         add_stmt.BindString(2, (*it)->identity.common_name);
    572         const std::string& cert = (*it)->identity.certificate;
    573         add_stmt.BindBlob(3, cert.data(), cert.size());
    574         const std::string& private_key = (*it)->identity.private_key;
    575         add_stmt.BindBlob(4, private_key.data(), private_key.size());
    576         add_stmt.BindInt64(5, (*it)->identity.creation_time);
    577         if (!add_stmt.Run()) {
    578           DVLOG(2) << "Failed to add the identity to DB.";
    579           return;
    580         }
    581         break;
    582       }
    583       case DELETE_IDENTITY:
    584         del_stmt.Reset(true);
    585         del_stmt.BindString(0, (*it)->origin.spec());
    586         del_stmt.BindString(1, (*it)->identity_name);
    587         if (!del_stmt.Run()) {
    588           DVLOG(2) << "Failed to delete the identity from DB.";
    589           return;
    590         }
    591         break;
    592 
    593       default:
    594         NOTREACHED();
    595         break;
    596     }
    597   }
    598 
    599   if (!transaction.Commit())
    600     DVLOG(2) << "Failed to commit the transaction.";
    601 }
    602 
    603 }  // namespace content
    604