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.
5 #include "content/browser/media/webrtc_identity_store_backend.h"
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"
17 #include "webkit/browser/quota/special_storage_policy.h"
21 static const char kWebRTCIdentityStoreDBName
[] = "webrtc_identity_store";
23 static const base::FilePath::CharType kWebRTCIdentityStoreDirectory
[] =
24 FILE_PATH_LITERAL("WebRTCIdentityStore");
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"))
37 if (!db
->Execute("DROP TABLE webrtc_identity_store"))
42 "CREATE TABLE webrtc_identity_store"
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)");
52 struct WebRTCIdentityStoreBackend::IdentityKey
{
53 IdentityKey(const GURL
& origin
, const std::string
& identity_name
)
54 : origin(origin
), identity_name(identity_name
) {}
56 bool operator<(const IdentityKey
& other
) const {
57 return origin
< other
.origin
||
58 (origin
== other
.origin
&& identity_name
< other
.identity_name
);
62 std::string identity_name
;
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()) {}
74 Identity(const std::string
& common_name
,
75 const std::string
& certificate
,
76 const std::string
& private_key
,
78 : common_name(common_name
),
79 certificate(certificate
),
80 private_key(private_key
),
81 creation_time(creation_time
) {}
83 std::string common_name
;
84 std::string certificate
;
85 std::string private_key
;
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
)
95 identity_name(identity_name
),
96 common_name(common_name
),
99 ~PendingFindRequest() {}
102 std::string identity_name
;
103 std::string common_name
;
104 FindIdentityCallback callback
;
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
> {
113 SqlLiteStorage(base::TimeDelta validity_period
,
114 const base::FilePath
& path
,
115 storage::SpecialStoragePolicy
* policy
)
116 : validity_period_(validity_period
), special_storage_policy_(policy
) {
118 path_
= path
.Append(kWebRTCIdentityStoreDirectory
);
121 void Load(IdentityMap
* out_map
);
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
);
131 void SetValidityPeriodForTesting(base::TimeDelta validity_period
) {
132 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
134 validity_period_
= validity_period
;
138 friend class base::RefCountedThreadSafe
<SqlLiteStorage
>;
144 struct PendingOperation
{
145 PendingOperation(OperationType type
,
147 const std::string
& identity_name
,
148 const Identity
& identity
)
151 identity_name(identity_name
),
152 identity(identity
) {}
156 std::string identity_name
;
159 typedef ScopedVector
<PendingOperation
> PendingOperationList
;
161 virtual ~SqlLiteStorage() {}
162 void OnDatabaseError(int error
, sql::Statement
* stmt
);
163 void BatchOperation(OperationType type
,
165 const std::string
& identity_name
,
166 const Identity
& identity
);
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_
;
177 DISALLOW_COPY_AND_ASSIGN(SqlLiteStorage
);
180 WebRTCIdentityStoreBackend::WebRTCIdentityStoreBackend(
181 const base::FilePath
& path
,
182 storage::SpecialStoragePolicy
* policy
,
183 base::TimeDelta validity_period
)
184 : validity_period_(validity_period
),
186 sql_lite_storage_(new SqlLiteStorage(validity_period
, path
, policy
)) {
189 bool WebRTCIdentityStoreBackend::FindIdentity(
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
)
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
)
205 DCHECK_EQ(state_
, NOT_STARTED
);
207 // Kick off loading the DB.
208 scoped_ptr
<IdentityMap
> out_map(new IdentityMap());
210 base::Bind(&SqlLiteStorage::Load
, sql_lite_storage_
, out_map
.get()));
211 // |out_map| will be NULL after this call.
212 if (BrowserThread::PostTaskAndReply(
216 base::Bind(&WebRTCIdentityStoreBackend::OnLoaded
,
218 base::Passed(&out_map
)))) {
222 // If it fails to post task, falls back to ERR_FILE_NOT_FOUND.
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_
) {
232 return BrowserThread::PostTask(BrowserThread::IO
,
236 iter
->second
.certificate
,
237 iter
->second
.private_key
));
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
);
244 return BrowserThread::PostTask(
247 base::Bind(callback
, net::ERR_FILE_NOT_FOUND
, "", ""));
250 void 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
)
259 // If there is an existing identity for the same origin and identity_name,
261 IdentityKey
key(origin
, identity_name
);
262 Identity
identity(common_name
, certificate
, private_key
);
264 if (identities_
.find(key
) != identities_
.end()) {
265 if (!BrowserThread::PostTask(BrowserThread::DB
,
267 base::Bind(&SqlLiteStorage::DeleteIdentity
,
271 identities_
.find(key
)->second
)))
274 identities_
.insert(std::pair
<IdentityKey
, Identity
>(key
, identity
));
276 BrowserThread::PostTask(BrowserThread::DB
,
278 base::Bind(&SqlLiteStorage::AddIdentity
,
285 void WebRTCIdentityStoreBackend::Close() {
286 if (!BrowserThread::CurrentlyOn(BrowserThread::IO
)) {
287 BrowserThread::PostTask(
290 base::Bind(&WebRTCIdentityStoreBackend::Close
, this));
294 if (state_
== CLOSED
)
298 BrowserThread::PostTask(
301 base::Bind(&SqlLiteStorage::Close
, sql_lite_storage_
));
304 void 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
)
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
++);
321 BrowserThread::PostTaskAndReply(BrowserThread::DB
,
323 base::Bind(&SqlLiteStorage::DeleteBetween
,
330 void WebRTCIdentityStoreBackend::SetValidityPeriodForTesting(
331 base::TimeDelta validity_period
) {
332 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
333 validity_period_
= validity_period
;
334 BrowserThread::PostTask(
337 base::Bind(&SqlLiteStorage::SetValidityPeriodForTesting
,
342 WebRTCIdentityStoreBackend::~WebRTCIdentityStoreBackend() {}
344 void WebRTCIdentityStoreBackend::OnLoaded(scoped_ptr
<IdentityMap
> out_map
) {
345 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
347 if (state_
!= LOADING
)
350 DVLOG(3) << "WebRTC identity store has loaded.";
353 identities_
.swap(*out_map
);
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
];
362 pending_find_requests_
.clear();
366 // Implementation of SqlLiteStorage.
369 void WebRTCIdentityStoreBackend::SqlLiteStorage::Load(IdentityMap
* out_map
) {
370 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
373 // Ensure the parent directory for storing certs is created before reading
375 const base::FilePath dir
= path_
.DirName();
376 if (!base::PathExists(dir
) && !base::CreateDirectory(dir
)) {
377 DVLOG(2) << "Unable to open DB file path.";
381 db_
.reset(new sql::Connection());
383 db_
->set_error_callback(base::Bind(&SqlLiteStorage::OnDatabaseError
, this));
385 if (!db_
->Open(path_
)) {
386 DVLOG(2) << "Unable to open DB.";
391 if (!InitDB(db_
.get())) {
392 DVLOG(2) << "Unable to init DB.";
399 // Delete expired identities.
400 DeleteBetween(base::Time(), base::Time::Now() - validity_period_
);
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());
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
);
423 void WebRTCIdentityStoreBackend::SqlLiteStorage::Close() {
424 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
429 void WebRTCIdentityStoreBackend::SqlLiteStorage::AddIdentity(
431 const std::string
& identity_name
,
432 const Identity
& identity
) {
433 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
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
)) {
443 BatchOperation(ADD_IDENTITY
, origin
, identity_name
, identity
);
446 void WebRTCIdentityStoreBackend::SqlLiteStorage::DeleteIdentity(
448 const std::string
& identity_name
,
449 const Identity
& identity
) {
450 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
453 BatchOperation(DELETE_IDENTITY
, origin
, identity_name
, identity
);
456 void WebRTCIdentityStoreBackend::SqlLiteStorage::DeleteBetween(
457 base::Time delete_begin
,
458 base::Time delete_end
) {
459 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
463 // Commit pending operations first.
466 sql::Statement
del_stmt(db_
->GetCachedStatement(
468 "DELETE FROM webrtc_identity_store"
469 " WHERE creation_time >= ? AND creation_time <= ?"));
470 CHECK(del_stmt
.is_valid());
472 del_stmt
.BindInt64(0, delete_begin
.ToInternalValue());
473 del_stmt
.BindInt64(1, delete_end
.ToInternalValue());
475 sql::Transaction
transaction(db_
.get());
476 if (!transaction
.Begin()) {
477 DVLOG(2) << "Failed to begin the transaction.";
481 if (!del_stmt
.Run()) {
482 DVLOG(2) << "Failed to run the delete statement.";
486 if (!transaction
.Commit())
487 DVLOG(2) << "Failed to commit the transaction.";
490 void WebRTCIdentityStoreBackend::SqlLiteStorage::OnDatabaseError(
492 sql::Statement
* stmt
) {
493 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
496 // It's not safe to reset |db_| here.
499 void WebRTCIdentityStoreBackend::SqlLiteStorage::BatchOperation(
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;
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
));
515 pending_operations_
.push_back(operation
.release());
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
,
521 base::Bind(&SqlLiteStorage::Commit
, this),
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
,
527 base::Bind(&SqlLiteStorage::Commit
, this));
531 void 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())
537 sql::Statement
add_stmt(db_
->GetCachedStatement(
539 "INSERT INTO webrtc_identity_store "
540 "(origin, identity_name, common_name, certificate,"
541 " private_key, creation_time) VALUES"
544 CHECK(add_stmt
.is_valid());
546 sql::Statement
del_stmt(db_
->GetCachedStatement(
548 "DELETE FROM webrtc_identity_store WHERE origin=? AND identity_name=?"));
550 CHECK(del_stmt
.is_valid());
552 sql::Transaction
transaction(db_
.get());
553 if (!transaction
.Begin()) {
554 DVLOG(2) << "Failed to begin the transaction.";
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
);
563 for (PendingOperationList::const_iterator it
=
564 pending_operations_copy
.begin();
565 it
!= pending_operations_copy
.end();
567 switch ((*it
)->type
) {
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.";
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.";
600 if (!transaction
.Commit())
601 DVLOG(2) << "Failed to commit the transaction.";
604 } // namespace content