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/files/file_path.h" 8 #include "base/files/file_util.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 "storage/browser/quota/special_storage_policy.h" 17 #include "url/gurl.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 storage::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<storage::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 storage::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 189 bool WebRTCIdentityStoreBackend::FindIdentity( 190 const GURL& origin, 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) 196 return false; 197 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) 203 return true; 204 205 DCHECK_EQ(state_, NOT_STARTED); 206 207 // Kick off loading the DB. 208 scoped_ptr<IdentityMap> out_map(new IdentityMap()); 209 base::Closure task( 210 base::Bind(&SqlLiteStorage::Load, sql_lite_storage_, out_map.get())); 211 // |out_map| will be NULL after this call. 212 if (BrowserThread::PostTaskAndReply( 213 BrowserThread::DB, 214 FROM_HERE, 215 task, 216 base::Bind(&WebRTCIdentityStoreBackend::OnLoaded, 217 this, 218 base::Passed(&out_map)))) { 219 state_ = LOADING; 220 return true; 221 } 222 // If it fails to post task, falls back to ERR_FILE_NOT_FOUND. 223 } 224 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_) { 231 // Identity found. 232 return BrowserThread::PostTask(BrowserThread::IO, 233 FROM_HERE, 234 base::Bind(callback, 235 net::OK, 236 iter->second.certificate, 237 iter->second.private_key)); 238 } 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); 242 } 243 244 return BrowserThread::PostTask( 245 BrowserThread::IO, 246 FROM_HERE, 247 base::Bind(callback, net::ERR_FILE_NOT_FOUND, "", "")); 248 } 249 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) 257 return; 258 259 // If there is an existing identity for the same origin and identity_name, 260 // delete it. 261 IdentityKey key(origin, identity_name); 262 Identity identity(common_name, certificate, private_key); 263 264 if (identities_.find(key) != identities_.end()) { 265 if (!BrowserThread::PostTask(BrowserThread::DB, 266 FROM_HERE, 267 base::Bind(&SqlLiteStorage::DeleteIdentity, 268 sql_lite_storage_, 269 origin, 270 identity_name, 271 identities_.find(key)->second))) 272 return; 273 } 274 identities_.insert(std::pair<IdentityKey, Identity>(key, identity)); 275 276 BrowserThread::PostTask(BrowserThread::DB, 277 FROM_HERE, 278 base::Bind(&SqlLiteStorage::AddIdentity, 279 sql_lite_storage_, 280 origin, 281 identity_name, 282 identity)); 283 } 284 285 void WebRTCIdentityStoreBackend::Close() { 286 if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { 287 BrowserThread::PostTask( 288 BrowserThread::IO, 289 FROM_HERE, 290 base::Bind(&WebRTCIdentityStoreBackend::Close, this)); 291 return; 292 } 293 294 if (state_ == CLOSED) 295 return; 296 297 state_ = CLOSED; 298 BrowserThread::PostTask( 299 BrowserThread::DB, 300 FROM_HERE, 301 base::Bind(&SqlLiteStorage::Close, sql_lite_storage_)); 302 } 303 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) 309 return; 310 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++); 317 } else { 318 ++it; 319 } 320 } 321 BrowserThread::PostTaskAndReply(BrowserThread::DB, 322 FROM_HERE, 323 base::Bind(&SqlLiteStorage::DeleteBetween, 324 sql_lite_storage_, 325 delete_begin, 326 delete_end), 327 callback); 328 } 329 330 void WebRTCIdentityStoreBackend::SetValidityPeriodForTesting( 331 base::TimeDelta validity_period) { 332 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 333 validity_period_ = validity_period; 334 BrowserThread::PostTask( 335 BrowserThread::DB, 336 FROM_HERE, 337 base::Bind(&SqlLiteStorage::SetValidityPeriodForTesting, 338 sql_lite_storage_, 339 validity_period)); 340 } 341 342 WebRTCIdentityStoreBackend::~WebRTCIdentityStoreBackend() {} 343 344 void WebRTCIdentityStoreBackend::OnLoaded(scoped_ptr<IdentityMap> out_map) { 345 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 346 347 if (state_ != LOADING) 348 return; 349 350 DVLOG(3) << "WebRTC identity store has loaded."; 351 352 state_ = LOADED; 353 identities_.swap(*out_map); 354 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]; 361 } 362 pending_find_requests_.clear(); 363 } 364 365 // 366 // Implementation of SqlLiteStorage. 367 // 368 369 void WebRTCIdentityStoreBackend::SqlLiteStorage::Load(IdentityMap* out_map) { 370 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 371 DCHECK(!db_.get()); 372 373 // Ensure the parent directory for storing certs is created before reading 374 // from it. 375 const base::FilePath dir = path_.DirName(); 376 if (!base::PathExists(dir) && !base::CreateDirectory(dir)) { 377 DVLOG(2) << "Unable to open DB file path."; 378 return; 379 } 380 381 db_.reset(new sql::Connection()); 382 383 db_->set_error_callback(base::Bind(&SqlLiteStorage::OnDatabaseError, this)); 384 385 if (!db_->Open(path_)) { 386 DVLOG(2) << "Unable to open DB."; 387 db_.reset(); 388 return; 389 } 390 391 if (!InitDB(db_.get())) { 392 DVLOG(2) << "Unable to init DB."; 393 db_.reset(); 394 return; 395 } 396 397 db_->Preload(); 398 399 // Delete expired identities. 400 DeleteBetween(base::Time(), base::Time::Now() - validity_period_); 401 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()); 408 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); 420 } 421 } 422 423 void WebRTCIdentityStoreBackend::SqlLiteStorage::Close() { 424 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 425 Commit(); 426 db_.reset(); 427 } 428 429 void WebRTCIdentityStoreBackend::SqlLiteStorage::AddIdentity( 430 const GURL& origin, 431 const std::string& identity_name, 432 const Identity& identity) { 433 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 434 if (!db_.get()) 435 return; 436 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)) { 441 return; 442 } 443 BatchOperation(ADD_IDENTITY, origin, identity_name, identity); 444 } 445 446 void WebRTCIdentityStoreBackend::SqlLiteStorage::DeleteIdentity( 447 const GURL& origin, 448 const std::string& identity_name, 449 const Identity& identity) { 450 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 451 if (!db_.get()) 452 return; 453 BatchOperation(DELETE_IDENTITY, origin, identity_name, identity); 454 } 455 456 void WebRTCIdentityStoreBackend::SqlLiteStorage::DeleteBetween( 457 base::Time delete_begin, 458 base::Time delete_end) { 459 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 460 if (!db_.get()) 461 return; 462 463 // Commit pending operations first. 464 Commit(); 465 466 sql::Statement del_stmt(db_->GetCachedStatement( 467 SQL_FROM_HERE, 468 "DELETE FROM webrtc_identity_store" 469 " WHERE creation_time >= ? AND creation_time <= ?")); 470 CHECK(del_stmt.is_valid()); 471 472 del_stmt.BindInt64(0, delete_begin.ToInternalValue()); 473 del_stmt.BindInt64(1, delete_end.ToInternalValue()); 474 475 sql::Transaction transaction(db_.get()); 476 if (!transaction.Begin()) { 477 DVLOG(2) << "Failed to begin the transaction."; 478 return; 479 } 480 481 if (!del_stmt.Run()) { 482 DVLOG(2) << "Failed to run the delete statement."; 483 return; 484 } 485 486 if (!transaction.Commit()) 487 DVLOG(2) << "Failed to commit the transaction."; 488 } 489 490 void WebRTCIdentityStoreBackend::SqlLiteStorage::OnDatabaseError( 491 int error, 492 sql::Statement* stmt) { 493 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 494 495 db_->RazeAndClose(); 496 // It's not safe to reset |db_| here. 497 } 498 499 void WebRTCIdentityStoreBackend::SqlLiteStorage::BatchOperation( 500 OperationType type, 501 const GURL& origin, 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; 510 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)); 514 515 pending_operations_.push_back(operation.release()); 516 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, 520 FROM_HERE, 521 base::Bind(&SqlLiteStorage::Commit, this), 522 kCommitInterval); 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, 526 FROM_HERE, 527 base::Bind(&SqlLiteStorage::Commit, this)); 528 } 529 } 530 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()) 535 return; 536 537 sql::Statement add_stmt(db_->GetCachedStatement( 538 SQL_FROM_HERE, 539 "INSERT INTO webrtc_identity_store " 540 "(origin, identity_name, common_name, certificate," 541 " private_key, creation_time) VALUES" 542 " (?,?,?,?,?,?)")); 543 544 CHECK(add_stmt.is_valid()); 545 546 sql::Statement del_stmt(db_->GetCachedStatement( 547 SQL_FROM_HERE, 548 "DELETE FROM webrtc_identity_store WHERE origin=? AND identity_name=?")); 549 550 CHECK(del_stmt.is_valid()); 551 552 sql::Transaction transaction(db_.get()); 553 if (!transaction.Begin()) { 554 DVLOG(2) << "Failed to begin the transaction."; 555 return; 556 } 557 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); 562 563 for (PendingOperationList::const_iterator it = 564 pending_operations_copy.begin(); 565 it != pending_operations_copy.end(); 566 ++it) { 567 switch ((*it)->type) { 568 case ADD_IDENTITY: { 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."; 580 return; 581 } 582 break; 583 } 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."; 590 return; 591 } 592 break; 593 594 default: 595 NOTREACHED(); 596 break; 597 } 598 } 599 600 if (!transaction.Commit()) 601 DVLOG(2) << "Failed to commit the transaction."; 602 } 603 604 } // namespace content 605