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