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