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 17 namespace content { 18 19 static const char* kWebRTCIdentityStoreDBName = "webrtc_identity_store"; 20 21 static const base::FilePath::CharType kWebRTCIdentityStoreDirectory[] = 22 FILE_PATH_LITERAL("WebRTCIdentityStore"); 23 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")) 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 49 struct 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 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()) {} 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 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) 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. 107 class WebRTCIdentityStoreBackend::SqlLiteStorage 108 : public base::RefCountedThreadSafe<SqlLiteStorage> { 109 public: 110 SqlLiteStorage(const base::FilePath& path, 111 quota::SpecialStoragePolicy* policy) 112 : special_storage_policy_(policy) { 113 if (!path.empty()) 114 path_ = path.Append(kWebRTCIdentityStoreDirectory); 115 } 116 117 void Load(IdentityMap* out_map); 118 void Close(); 119 void AddIdentity(const GURL& origin, 120 const std::string& identity_name, 121 const Identity& identity); 122 void DeleteIdentity(const GURL& origin, 123 const std::string& identity_name, 124 const Identity& identity); 125 void DeleteBetween(base::Time delete_begin, 126 base::Time delete_end, 127 const base::Closure& callback); 128 129 private: 130 friend class base::RefCountedThreadSafe<SqlLiteStorage>; 131 132 enum OperationType { 133 ADD_IDENTITY, 134 DELETE_IDENTITY 135 }; 136 struct PendingOperation { 137 PendingOperation(OperationType type, 138 const GURL& origin, 139 const std::string& identity_name, 140 const Identity& identity) 141 : type(type), 142 origin(origin), 143 identity_name(identity_name), 144 identity(identity) {} 145 146 OperationType type; 147 GURL origin; 148 std::string identity_name; 149 Identity identity; 150 }; 151 typedef std::vector<PendingOperation*> PendingOperationList; 152 153 virtual ~SqlLiteStorage() {} 154 void OnDatabaseError(int error, sql::Statement* stmt); 155 void BatchOperation(OperationType type, 156 const GURL& origin, 157 const std::string& identity_name, 158 const Identity& identity); 159 void Commit(); 160 161 // The file path of the DB. Empty if temporary. 162 base::FilePath path_; 163 scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy_; 164 scoped_ptr<sql::Connection> db_; 165 // Batched DB operations pending to commit. 166 PendingOperationList pending_operations_; 167 168 DISALLOW_COPY_AND_ASSIGN(SqlLiteStorage); 169 }; 170 171 WebRTCIdentityStoreBackend::WebRTCIdentityStoreBackend( 172 const base::FilePath& path, 173 quota::SpecialStoragePolicy* policy) 174 : state_(NOT_STARTED), 175 sql_lite_storage_(new SqlLiteStorage(path, policy)) {} 176 177 bool WebRTCIdentityStoreBackend::FindIdentity( 178 const GURL& origin, 179 const std::string& identity_name, 180 const std::string& common_name, 181 const FindIdentityCallback& callback) { 182 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 183 if (state_ == CLOSED) 184 return false; 185 186 if (state_ != LOADED) { 187 // Queues the request to wait for the DB to load. 188 pending_find_requests_.push_back( 189 new PendingFindRequest(origin, identity_name, common_name, callback)); 190 if (state_ == LOADING) 191 return true; 192 193 DCHECK_EQ(state_, NOT_STARTED); 194 195 // Kick off loading the DB. 196 scoped_ptr<IdentityMap> out_map(new IdentityMap()); 197 base::Closure task( 198 base::Bind(&SqlLiteStorage::Load, sql_lite_storage_, out_map.get())); 199 // |out_map| will be NULL after this call. 200 if (BrowserThread::PostTaskAndReply( 201 BrowserThread::DB, 202 FROM_HERE, 203 task, 204 base::Bind(&WebRTCIdentityStoreBackend::OnLoaded, 205 this, 206 base::Passed(&out_map)))) { 207 state_ = LOADING; 208 return true; 209 } 210 // If it fails to post task, falls back to ERR_FILE_NOT_FOUND. 211 } 212 213 IdentityKey key(origin, identity_name); 214 IdentityMap::iterator iter = identities_.find(key); 215 if (iter != identities_.end() && iter->second.common_name == common_name) { 216 // Identity found. 217 return BrowserThread::PostTask(BrowserThread::IO, 218 FROM_HERE, 219 base::Bind(callback, 220 net::OK, 221 iter->second.certificate, 222 iter->second.private_key)); 223 } 224 225 return BrowserThread::PostTask( 226 BrowserThread::IO, 227 FROM_HERE, 228 base::Bind(callback, net::ERR_FILE_NOT_FOUND, "", "")); 229 } 230 231 void WebRTCIdentityStoreBackend::AddIdentity(const GURL& origin, 232 const std::string& identity_name, 233 const std::string& common_name, 234 const std::string& certificate, 235 const std::string& private_key) { 236 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 237 if (state_ == CLOSED) 238 return; 239 240 // If there is an existing identity for the same origin and identity_name, 241 // delete it. 242 IdentityKey key(origin, identity_name); 243 Identity identity(common_name, certificate, private_key); 244 245 if (identities_.find(key) != identities_.end()) { 246 if (!BrowserThread::PostTask(BrowserThread::DB, 247 FROM_HERE, 248 base::Bind(&SqlLiteStorage::DeleteIdentity, 249 sql_lite_storage_, 250 origin, 251 identity_name, 252 identities_.find(key)->second))) 253 return; 254 } 255 identities_.insert(std::pair<IdentityKey, Identity>(key, identity)); 256 257 BrowserThread::PostTask(BrowserThread::DB, 258 FROM_HERE, 259 base::Bind(&SqlLiteStorage::AddIdentity, 260 sql_lite_storage_, 261 origin, 262 identity_name, 263 identity)); 264 } 265 266 void WebRTCIdentityStoreBackend::Close() { 267 if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { 268 BrowserThread::PostTask( 269 BrowserThread::IO, 270 FROM_HERE, 271 base::Bind(&WebRTCIdentityStoreBackend::Close, this)); 272 return; 273 } 274 275 if (state_ == CLOSED) 276 return; 277 278 state_ = CLOSED; 279 BrowserThread::PostTask( 280 BrowserThread::DB, 281 FROM_HERE, 282 base::Bind(&SqlLiteStorage::Close, sql_lite_storage_)); 283 } 284 285 void WebRTCIdentityStoreBackend::DeleteBetween(base::Time delete_begin, 286 base::Time delete_end, 287 const base::Closure& callback) { 288 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 289 // Delete the in-memory cache. 290 IdentityMap::iterator it = identities_.begin(); 291 while (it != identities_.end()) { 292 if (it->second.creation_time >= delete_begin.ToInternalValue() && 293 it->second.creation_time <= delete_end.ToInternalValue()) 294 identities_.erase(it++); 295 else 296 it++; 297 } 298 299 BrowserThread::PostTaskAndReply(BrowserThread::DB, 300 FROM_HERE, 301 base::Bind(&SqlLiteStorage::DeleteBetween, 302 sql_lite_storage_, 303 delete_begin, 304 delete_end, 305 callback), 306 callback); 307 } 308 309 WebRTCIdentityStoreBackend::~WebRTCIdentityStoreBackend() {} 310 311 void WebRTCIdentityStoreBackend::OnLoaded(scoped_ptr<IdentityMap> out_map) { 312 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 313 state_ = LOADED; 314 identities_.swap(*out_map); 315 316 for (size_t i = 0; i < pending_find_requests_.size(); ++i) { 317 FindIdentity(pending_find_requests_[i]->origin, 318 pending_find_requests_[i]->identity_name, 319 pending_find_requests_[i]->common_name, 320 pending_find_requests_[i]->callback); 321 delete pending_find_requests_[i]; 322 } 323 pending_find_requests_.clear(); 324 } 325 326 // 327 // Implementation of SqlLiteStorage. 328 // 329 330 void WebRTCIdentityStoreBackend::SqlLiteStorage::Load(IdentityMap* out_map) { 331 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 332 DCHECK(!db_.get()); 333 334 // Ensure the parent directory for storing certs is created before reading 335 // from it. 336 const base::FilePath dir = path_.DirName(); 337 if (!base::PathExists(dir) && !file_util::CreateDirectory(dir)) { 338 DLOG(ERROR) << "Unable to open DB file path."; 339 return; 340 } 341 342 db_.reset(new sql::Connection()); 343 344 db_->set_error_callback(base::Bind(&SqlLiteStorage::OnDatabaseError, this)); 345 346 if (!db_->Open(path_)) { 347 DLOG(ERROR) << "Unable to open DB."; 348 db_.reset(); 349 return; 350 } 351 352 if (!InitDB(db_.get())) { 353 DLOG(ERROR) << "Unable to init DB."; 354 db_.reset(); 355 return; 356 } 357 358 db_->Preload(); 359 360 // Slurp all the identities into the out_map. 361 sql::Statement stmt(db_->GetUniqueStatement( 362 "SELECT origin, identity_name, common_name, " 363 "certificate, private_key, creation_time " 364 "FROM webrtc_identity_store")); 365 CHECK(stmt.is_valid()); 366 367 while (stmt.Step()) { 368 IdentityKey key(GURL(stmt.ColumnString(0)), stmt.ColumnString(1)); 369 std::string common_name(stmt.ColumnString(2)); 370 std::string cert, private_key; 371 stmt.ColumnBlobAsString(3, &cert); 372 stmt.ColumnBlobAsString(4, &private_key); 373 int64 creation_time = stmt.ColumnInt64(5); 374 std::pair<IdentityMap::iterator, bool> result = 375 out_map->insert(std::pair<IdentityKey, Identity>( 376 key, Identity(common_name, cert, private_key, creation_time))); 377 DCHECK(result.second); 378 } 379 } 380 381 void WebRTCIdentityStoreBackend::SqlLiteStorage::Close() { 382 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 383 Commit(); 384 db_.reset(); 385 } 386 387 void WebRTCIdentityStoreBackend::SqlLiteStorage::AddIdentity( 388 const GURL& origin, 389 const std::string& identity_name, 390 const Identity& identity) { 391 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 392 if (!db_.get()) 393 return; 394 395 // Do not add for session only origins. 396 if (special_storage_policy_.get() && 397 !special_storage_policy_->IsStorageProtected(origin) && 398 special_storage_policy_->IsStorageSessionOnly(origin)) { 399 return; 400 } 401 BatchOperation(ADD_IDENTITY, origin, identity_name, identity); 402 } 403 404 void WebRTCIdentityStoreBackend::SqlLiteStorage::DeleteIdentity( 405 const GURL& origin, 406 const std::string& identity_name, 407 const Identity& identity) { 408 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 409 if (!db_.get()) 410 return; 411 BatchOperation(DELETE_IDENTITY, origin, identity_name, identity); 412 } 413 414 void WebRTCIdentityStoreBackend::SqlLiteStorage::OnDatabaseError( 415 int error, 416 sql::Statement* stmt) { 417 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 418 if (!sql::IsErrorCatastrophic(error)) 419 return; 420 db_->RazeAndClose(); 421 } 422 423 void WebRTCIdentityStoreBackend::SqlLiteStorage::BatchOperation( 424 OperationType type, 425 const GURL& origin, 426 const std::string& identity_name, 427 const Identity& identity) { 428 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 429 // Commit every 30 seconds. 430 static const base::TimeDelta kCommitInterval( 431 base::TimeDelta::FromSeconds(30)); 432 // Commit right away if we have more than 512 outstanding operations. 433 static const size_t kCommitAfterBatchSize = 512; 434 435 // We do a full copy of the cert here, and hopefully just here. 436 scoped_ptr<PendingOperation> operation( 437 new PendingOperation(type, origin, identity_name, identity)); 438 439 pending_operations_.push_back(operation.release()); 440 441 if (pending_operations_.size() == 1) { 442 // We've gotten our first entry for this batch, fire off the timer. 443 BrowserThread::PostDelayedTask(BrowserThread::DB, 444 FROM_HERE, 445 base::Bind(&SqlLiteStorage::Commit, this), 446 kCommitInterval); 447 } else if (pending_operations_.size() >= kCommitAfterBatchSize) { 448 // We've reached a big enough batch, fire off a commit now. 449 BrowserThread::PostTask(BrowserThread::DB, 450 FROM_HERE, 451 base::Bind(&SqlLiteStorage::Commit, this)); 452 } 453 } 454 455 void WebRTCIdentityStoreBackend::SqlLiteStorage::Commit() { 456 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 457 // Maybe an old timer fired or we are already Close()'ed. 458 if (!db_.get() || pending_operations_.empty()) 459 return; 460 461 sql::Statement add_stmt(db_->GetCachedStatement( 462 SQL_FROM_HERE, 463 "INSERT INTO webrtc_identity_store " 464 "(origin, identity_name, common_name, certificate," 465 " private_key, creation_time) VALUES" 466 " (?,?,?,?,?,?)")); 467 468 CHECK(add_stmt.is_valid()); 469 470 sql::Statement del_stmt(db_->GetCachedStatement( 471 SQL_FROM_HERE, 472 "DELETE FROM webrtc_identity_store WHERE origin=? AND identity_name=?")); 473 474 CHECK(del_stmt.is_valid()); 475 476 sql::Transaction transaction(db_.get()); 477 if (!transaction.Begin()) { 478 DLOG(ERROR) << "Failed to begin the transaction."; 479 return; 480 } 481 482 for (PendingOperationList::iterator it = pending_operations_.begin(); 483 it != pending_operations_.end(); 484 ++it) { 485 scoped_ptr<PendingOperation> po(*it); 486 switch (po->type) { 487 case ADD_IDENTITY: { 488 add_stmt.Reset(true); 489 add_stmt.BindString(0, po->origin.spec()); 490 add_stmt.BindString(1, po->identity_name); 491 add_stmt.BindString(2, po->identity.common_name); 492 const std::string& cert = po->identity.certificate; 493 add_stmt.BindBlob(3, cert.data(), cert.size()); 494 const std::string& private_key = po->identity.private_key; 495 add_stmt.BindBlob(4, private_key.data(), private_key.size()); 496 add_stmt.BindInt64(5, po->identity.creation_time); 497 CHECK(add_stmt.Run()); 498 break; 499 } 500 case DELETE_IDENTITY: 501 del_stmt.Reset(true); 502 del_stmt.BindString(0, po->origin.spec()); 503 add_stmt.BindString(1, po->identity_name); 504 CHECK(del_stmt.Run()); 505 break; 506 507 default: 508 NOTREACHED(); 509 break; 510 } 511 } 512 transaction.Commit(); 513 pending_operations_.clear(); 514 } 515 516 void WebRTCIdentityStoreBackend::SqlLiteStorage::DeleteBetween( 517 base::Time delete_begin, 518 base::Time delete_end, 519 const base::Closure& callback) { 520 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 521 if (!db_.get()) 522 return; 523 524 // Commit pending operations first. 525 Commit(); 526 527 sql::Statement del_stmt(db_->GetCachedStatement( 528 SQL_FROM_HERE, 529 "DELETE FROM webrtc_identity_store" 530 " WHERE creation_time >= ? AND creation_time <= ?")); 531 CHECK(del_stmt.is_valid()); 532 533 del_stmt.BindInt64(0, delete_begin.ToInternalValue()); 534 del_stmt.BindInt64(1, delete_end.ToInternalValue()); 535 536 sql::Transaction transaction(db_.get()); 537 if (!transaction.Begin()) { 538 DLOG(ERROR) << "Failed to begin the transaction."; 539 return; 540 } 541 542 CHECK(del_stmt.Run()); 543 transaction.Commit(); 544 } 545 546 } // namespace content 547