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. 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 { 53 IdentityKey(const GURL& origin, const std::string& identity_name) 54 : origin(origin), identity_name(identity_name) {} 55 56 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 { 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()) {} 73 74 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 { 90 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 99 ~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: 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 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 { 145 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 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 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 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 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 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 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 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 341 WebRTCIdentityStoreBackend::~WebRTCIdentityStoreBackend() {} 342 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 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 422 void WebRTCIdentityStoreBackend::SqlLiteStorage::Close() { 423 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 424 Commit(); 425 db_.reset(); 426 } 427 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 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 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 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 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 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