webrtc_identity_store_backend.cc revision 1e9bf3e0803691d0a228da41fc608347b6db4340
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 del_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