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 "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"
15 #include "webkit/browser/quota/special_storage_policy.h"
19 static const char* kWebRTCIdentityStoreDBName
= "webrtc_identity_store";
21 static const base::FilePath::CharType kWebRTCIdentityStoreDirectory
[] =
22 FILE_PATH_LITERAL("WebRTCIdentityStore");
24 // Initializes the identity table, returning true on success.
25 static 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"))
34 if (!db
->Execute("DROP TABLE webrtc_identity_store"))
39 "CREATE TABLE webrtc_identity_store"
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)");
49 struct WebRTCIdentityStoreBackend::IdentityKey
{
50 IdentityKey(const GURL
& origin
, const std::string
& identity_name
)
51 : origin(origin
), identity_name(identity_name
) {}
53 bool operator<(const IdentityKey
& other
) const {
54 return origin
< other
.origin
||
55 (origin
== other
.origin
&& identity_name
< other
.identity_name
);
59 std::string identity_name
;
62 struct 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()) {}
71 Identity(const std::string
& common_name
,
72 const std::string
& certificate
,
73 const std::string
& private_key
,
75 : common_name(common_name
),
76 certificate(certificate
),
77 private_key(private_key
),
78 creation_time(creation_time
) {}
80 std::string common_name
;
81 std::string certificate
;
82 std::string private_key
;
86 struct WebRTCIdentityStoreBackend::PendingFindRequest
{
87 PendingFindRequest(const GURL
& origin
,
88 const std::string
& identity_name
,
89 const std::string
& common_name
,
90 const FindIdentityCallback
& callback
)
92 identity_name(identity_name
),
93 common_name(common_name
),
96 ~PendingFindRequest() {}
99 std::string identity_name
;
100 std::string common_name
;
101 FindIdentityCallback callback
;
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.
107 class WebRTCIdentityStoreBackend::SqlLiteStorage
108 : public base::RefCountedThreadSafe
<SqlLiteStorage
> {
110 SqlLiteStorage(base::TimeDelta validity_period
,
111 const base::FilePath
& path
,
112 quota::SpecialStoragePolicy
* policy
)
113 : validity_period_(validity_period
), special_storage_policy_(policy
) {
115 path_
= path
.Append(kWebRTCIdentityStoreDirectory
);
118 void Load(IdentityMap
* out_map
);
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
);
128 void SetValidityPeriodForTesting(base::TimeDelta validity_period
) {
129 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
131 validity_period_
= validity_period
;
135 friend class base::RefCountedThreadSafe
<SqlLiteStorage
>;
141 struct PendingOperation
{
142 PendingOperation(OperationType type
,
144 const std::string
& identity_name
,
145 const Identity
& identity
)
148 identity_name(identity_name
),
149 identity(identity
) {}
153 std::string identity_name
;
156 typedef std::vector
<PendingOperation
*> PendingOperationList
;
158 virtual ~SqlLiteStorage() {}
159 void OnDatabaseError(int error
, sql::Statement
* stmt
);
160 void BatchOperation(OperationType type
,
162 const std::string
& identity_name
,
163 const Identity
& identity
);
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_
;
174 DISALLOW_COPY_AND_ASSIGN(SqlLiteStorage
);
177 WebRTCIdentityStoreBackend::WebRTCIdentityStoreBackend(
178 const base::FilePath
& path
,
179 quota::SpecialStoragePolicy
* policy
,
180 base::TimeDelta validity_period
)
181 : validity_period_(validity_period
),
183 sql_lite_storage_(new SqlLiteStorage(validity_period
, path
, policy
)) {}
185 bool WebRTCIdentityStoreBackend::FindIdentity(
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
)
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
)
201 DCHECK_EQ(state_
, NOT_STARTED
);
203 // Kick off loading the DB.
204 scoped_ptr
<IdentityMap
> out_map(new IdentityMap());
206 base::Bind(&SqlLiteStorage::Load
, sql_lite_storage_
, out_map
.get()));
207 // |out_map| will be NULL after this call.
208 if (BrowserThread::PostTaskAndReply(
212 base::Bind(&WebRTCIdentityStoreBackend::OnLoaded
,
214 base::Passed(&out_map
)))) {
218 // If it fails to post task, falls back to ERR_FILE_NOT_FOUND.
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_
) {
228 return BrowserThread::PostTask(BrowserThread::IO
,
232 iter
->second
.certificate
,
233 iter
->second
.private_key
));
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
);
240 return BrowserThread::PostTask(
243 base::Bind(callback
, net::ERR_FILE_NOT_FOUND
, "", ""));
246 void 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
)
255 // If there is an existing identity for the same origin and identity_name,
257 IdentityKey
key(origin
, identity_name
);
258 Identity
identity(common_name
, certificate
, private_key
);
260 if (identities_
.find(key
) != identities_
.end()) {
261 if (!BrowserThread::PostTask(BrowserThread::DB
,
263 base::Bind(&SqlLiteStorage::DeleteIdentity
,
267 identities_
.find(key
)->second
)))
270 identities_
.insert(std::pair
<IdentityKey
, Identity
>(key
, identity
));
272 BrowserThread::PostTask(BrowserThread::DB
,
274 base::Bind(&SqlLiteStorage::AddIdentity
,
281 void WebRTCIdentityStoreBackend::Close() {
282 if (!BrowserThread::CurrentlyOn(BrowserThread::IO
)) {
283 BrowserThread::PostTask(
286 base::Bind(&WebRTCIdentityStoreBackend::Close
, this));
290 if (state_
== CLOSED
)
294 BrowserThread::PostTask(
297 base::Bind(&SqlLiteStorage::Close
, sql_lite_storage_
));
300 void 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
)
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
++);
317 BrowserThread::PostTaskAndReply(BrowserThread::DB
,
319 base::Bind(&SqlLiteStorage::DeleteBetween
,
326 void WebRTCIdentityStoreBackend::SetValidityPeriodForTesting(
327 base::TimeDelta validity_period
) {
328 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
329 validity_period_
= validity_period
;
330 BrowserThread::PostTask(
333 base::Bind(&SqlLiteStorage::SetValidityPeriodForTesting
,
338 WebRTCIdentityStoreBackend::~WebRTCIdentityStoreBackend() {}
340 void WebRTCIdentityStoreBackend::OnLoaded(scoped_ptr
<IdentityMap
> out_map
) {
341 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
343 if (state_
!= LOADING
)
346 DVLOG(2) << "WebRTC identity store has loaded.";
349 identities_
.swap(*out_map
);
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
];
358 pending_find_requests_
.clear();
362 // Implementation of SqlLiteStorage.
365 void WebRTCIdentityStoreBackend::SqlLiteStorage::Load(IdentityMap
* out_map
) {
366 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
369 // Ensure the parent directory for storing certs is created before reading
371 const base::FilePath dir
= path_
.DirName();
372 if (!base::PathExists(dir
) && !base::CreateDirectory(dir
)) {
373 DLOG(ERROR
) << "Unable to open DB file path.";
377 db_
.reset(new sql::Connection());
379 db_
->set_error_callback(base::Bind(&SqlLiteStorage::OnDatabaseError
, this));
381 if (!db_
->Open(path_
)) {
382 DLOG(ERROR
) << "Unable to open DB.";
387 if (!InitDB(db_
.get())) {
388 DLOG(ERROR
) << "Unable to init DB.";
395 // Delete expired identities.
396 DeleteBetween(base::Time(), base::Time::Now() - validity_period_
);
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());
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
);
419 void WebRTCIdentityStoreBackend::SqlLiteStorage::Close() {
420 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
425 void WebRTCIdentityStoreBackend::SqlLiteStorage::AddIdentity(
427 const std::string
& identity_name
,
428 const Identity
& identity
) {
429 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
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
)) {
439 BatchOperation(ADD_IDENTITY
, origin
, identity_name
, identity
);
442 void WebRTCIdentityStoreBackend::SqlLiteStorage::DeleteIdentity(
444 const std::string
& identity_name
,
445 const Identity
& identity
) {
446 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
449 BatchOperation(DELETE_IDENTITY
, origin
, identity_name
, identity
);
452 void WebRTCIdentityStoreBackend::SqlLiteStorage::DeleteBetween(
453 base::Time delete_begin
,
454 base::Time delete_end
) {
455 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
459 // Commit pending operations first.
462 sql::Statement
del_stmt(db_
->GetCachedStatement(
464 "DELETE FROM webrtc_identity_store"
465 " WHERE creation_time >= ? AND creation_time <= ?"));
466 CHECK(del_stmt
.is_valid());
468 del_stmt
.BindInt64(0, delete_begin
.ToInternalValue());
469 del_stmt
.BindInt64(1, delete_end
.ToInternalValue());
471 sql::Transaction
transaction(db_
.get());
472 if (!transaction
.Begin()) {
473 DLOG(ERROR
) << "Failed to begin the transaction.";
477 CHECK(del_stmt
.Run());
478 transaction
.Commit();
481 void WebRTCIdentityStoreBackend::SqlLiteStorage::OnDatabaseError(
483 sql::Statement
* stmt
) {
484 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
485 if (!sql::IsErrorCatastrophic(error
))
490 void WebRTCIdentityStoreBackend::SqlLiteStorage::BatchOperation(
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;
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
));
506 pending_operations_
.push_back(operation
.release());
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
,
512 base::Bind(&SqlLiteStorage::Commit
, this),
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
,
518 base::Bind(&SqlLiteStorage::Commit
, this));
522 void 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())
528 sql::Statement
add_stmt(db_
->GetCachedStatement(
530 "INSERT INTO webrtc_identity_store "
531 "(origin, identity_name, common_name, certificate,"
532 " private_key, creation_time) VALUES"
535 CHECK(add_stmt
.is_valid());
537 sql::Statement
del_stmt(db_
->GetCachedStatement(
539 "DELETE FROM webrtc_identity_store WHERE origin=? AND identity_name=?"));
541 CHECK(del_stmt
.is_valid());
543 sql::Transaction
transaction(db_
.get());
544 if (!transaction
.Begin()) {
545 DLOG(ERROR
) << "Failed to begin the transaction.";
549 for (PendingOperationList::iterator it
= pending_operations_
.begin();
550 it
!= pending_operations_
.end();
552 scoped_ptr
<PendingOperation
> po(*it
);
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());
567 case DELETE_IDENTITY
:
568 del_stmt
.Reset(true);
569 del_stmt
.BindString(0, po
->origin
.spec());
570 del_stmt
.BindString(1, po
->identity_name
);
571 CHECK(del_stmt
.Run());
579 transaction
.Commit();
580 pending_operations_
.clear();
583 } // namespace content