webrtc_identity_store_backend.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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
19namespace content {
20
21static const char kWebRTCIdentityStoreDBName[] = "webrtc_identity_store";
22
23static const base::FilePath::CharType kWebRTCIdentityStoreDirectory[] =
24    FILE_PATH_LITERAL("WebRTCIdentityStore");
25
26// Initializes the identity table, returning true on success.
27static 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
52struct 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
65struct 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
89struct 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.
110class 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
180WebRTCIdentityStoreBackend::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
189bool 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
250void 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
285void 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
304void 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
330void 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
342WebRTCIdentityStoreBackend::~WebRTCIdentityStoreBackend() {}
343
344void 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
369void 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
423void WebRTCIdentityStoreBackend::SqlLiteStorage::Close() {
424  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
425  Commit();
426  db_.reset();
427}
428
429void 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
446void 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
456void 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
490void 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
499void 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
531void 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