• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
InitDB(sql::Connection * db)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 {
IdentityKeycontent::WebRTCIdentityStoreBackend::IdentityKey53   IdentityKey(const GURL& origin, const std::string& identity_name)
54       : origin(origin), identity_name(identity_name) {}
55 
operator <content::WebRTCIdentityStoreBackend::IdentityKey56   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 {
Identitycontent::WebRTCIdentityStoreBackend::Identity66   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 
Identitycontent::WebRTCIdentityStoreBackend::Identity74   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 {
PendingFindRequestcontent::WebRTCIdentityStoreBackend::PendingFindRequest90   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 
~PendingFindRequestcontent::WebRTCIdentityStoreBackend::PendingFindRequest99   ~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:
SqlLiteStorage(base::TimeDelta validity_period,const base::FilePath & path,quota::SpecialStoragePolicy * policy)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 
SetValidityPeriodForTesting(base::TimeDelta validity_period)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 {
PendingOperationcontent::WebRTCIdentityStoreBackend::SqlLiteStorage::PendingOperation145     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 
~SqlLiteStorage()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 
WebRTCIdentityStoreBackend(const base::FilePath & path,quota::SpecialStoragePolicy * policy,base::TimeDelta validity_period)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 
FindIdentity(const GURL & origin,const std::string & identity_name,const std::string & common_name,const FindIdentityCallback & callback)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 
AddIdentity(const GURL & origin,const std::string & identity_name,const std::string & common_name,const std::string & certificate,const std::string & private_key)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 
Close()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 
DeleteBetween(base::Time delete_begin,base::Time delete_end,const base::Closure & callback)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 
SetValidityPeriodForTesting(base::TimeDelta validity_period)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 
~WebRTCIdentityStoreBackend()341 WebRTCIdentityStoreBackend::~WebRTCIdentityStoreBackend() {}
342 
OnLoaded(scoped_ptr<IdentityMap> out_map)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 
Load(IdentityMap * out_map)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 
Close()422 void WebRTCIdentityStoreBackend::SqlLiteStorage::Close() {
423   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
424   Commit();
425   db_.reset();
426 }
427 
AddIdentity(const GURL & origin,const std::string & identity_name,const Identity & identity)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 
DeleteIdentity(const GURL & origin,const std::string & identity_name,const Identity & identity)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 
DeleteBetween(base::Time delete_begin,base::Time delete_end)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 
OnDatabaseError(int error,sql::Statement * stmt)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 
BatchOperation(OperationType type,const GURL & origin,const std::string & identity_name,const Identity & identity)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 
Commit()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