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