diff options
16 files changed, 1182 insertions, 121 deletions
diff --git a/chrome/browser/browsing_data/browsing_data_remover.cc b/chrome/browser/browsing_data/browsing_data_remover.cc index 008d5cf..b0a38f4 100644 --- a/chrome/browser/browsing_data/browsing_data_remover.cc +++ b/chrome/browser/browsing_data/browsing_data_remover.cc @@ -173,6 +173,7 @@ BrowsingDataRemover::BrowsingDataRemover(Profile* profile, waiting_for_clear_server_bound_certs_(false), waiting_for_clear_session_storage_(false), waiting_for_clear_shader_cache_(false), + waiting_for_clear_webrtc_identity_store_(false), remove_mask_(0), remove_origin_(GURL()), origin_set_mask_(0) { @@ -545,6 +546,15 @@ void BrowsingDataRemover::RemoveImpl(int remove_mask, content::RecordAction(UserMetricsAction("ClearBrowsingData_ShaderCache")); ClearShaderCacheOnUIThread(); + + waiting_for_clear_webrtc_identity_store_ = true; + BrowserContext::GetDefaultStoragePartition(profile_)->ClearDataForRange( + content::StoragePartition::REMOVE_DATA_MASK_WEBRTC_IDENTITY, + content::StoragePartition::QUOTA_MANAGED_STORAGE_MASK_ALL, + delete_begin_, + delete_end_, + base::Bind(&BrowsingDataRemover::OnClearWebRTCIdentityStore, + base::Unretained(this))); } #if defined(ENABLE_PLUGINS) @@ -617,24 +627,21 @@ base::Time BrowsingDataRemover::CalculateBeginDeleteTime( } bool BrowsingDataRemover::AllDone() { - return registrar_.IsEmpty() && - !waiting_for_clear_autofill_origin_urls_ && - !waiting_for_clear_cache_ && - !waiting_for_clear_nacl_cache_ && - !waiting_for_clear_cookies_count_&& - !waiting_for_clear_history_ && - !waiting_for_clear_local_storage_ && - !waiting_for_clear_logged_in_predictor_ && - !waiting_for_clear_session_storage_ && - !waiting_for_clear_networking_history_ && - !waiting_for_clear_server_bound_certs_ && - !waiting_for_clear_plugin_data_ && - !waiting_for_clear_quota_managed_data_ && - !waiting_for_clear_content_licenses_ && - !waiting_for_clear_form_ && - !waiting_for_clear_hostname_resolution_cache_ && - !waiting_for_clear_network_predictor_ && - !waiting_for_clear_shader_cache_; + return registrar_.IsEmpty() && !waiting_for_clear_autofill_origin_urls_ && + !waiting_for_clear_cache_ && !waiting_for_clear_nacl_cache_ && + !waiting_for_clear_cookies_count_ && !waiting_for_clear_history_ && + !waiting_for_clear_local_storage_ && + !waiting_for_clear_logged_in_predictor_ && + !waiting_for_clear_session_storage_ && + !waiting_for_clear_networking_history_ && + !waiting_for_clear_server_bound_certs_ && + !waiting_for_clear_plugin_data_ && + !waiting_for_clear_quota_managed_data_ && + !waiting_for_clear_content_licenses_ && !waiting_for_clear_form_ && + !waiting_for_clear_hostname_resolution_cache_ && + !waiting_for_clear_network_predictor_ && + !waiting_for_clear_shader_cache_ && + !waiting_for_clear_webrtc_identity_store_; } void BrowsingDataRemover::Observe(int type, @@ -1123,3 +1130,9 @@ void BrowsingDataRemover::OnClearedAutofillOriginURLs() { waiting_for_clear_autofill_origin_urls_ = false; NotifyAndDeleteIfDone(); } + +void BrowsingDataRemover::OnClearWebRTCIdentityStore() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + waiting_for_clear_webrtc_identity_store_ = false; + NotifyAndDeleteIfDone(); +} diff --git a/chrome/browser/browsing_data/browsing_data_remover.h b/chrome/browser/browsing_data/browsing_data_remover.h index af1b441..d597d9d 100644 --- a/chrome/browser/browsing_data/browsing_data_remover.h +++ b/chrome/browser/browsing_data/browsing_data_remover.h @@ -361,6 +361,9 @@ class BrowsingDataRemover : public content::NotificationObserver // Invoked on the IO thread to delete from the shader cache. void ClearShaderCacheOnUIThread(); + // Callback on UI thread when the WebRTC identities are cleared. + void OnClearWebRTCIdentityStore(); + // Returns true if we're all done. bool AllDone(); @@ -425,6 +428,7 @@ class BrowsingDataRemover : public content::NotificationObserver bool waiting_for_clear_server_bound_certs_; bool waiting_for_clear_session_storage_; bool waiting_for_clear_shader_cache_; + bool waiting_for_clear_webrtc_identity_store_; // Tracking how many origins need to be deleted, and whether we're finished // gathering origins. diff --git a/content/browser/media/webrtc_identity_store.cc b/content/browser/media/webrtc_identity_store.cc index ee19f1e..1c77862 100644 --- a/content/browser/media/webrtc_identity_store.cc +++ b/content/browser/media/webrtc_identity_store.cc @@ -4,11 +4,14 @@ #include "content/browser/media/webrtc_identity_store.h" +#include <map> + #include "base/bind.h" #include "base/callback_helpers.h" #include "base/logging.h" #include "base/rand_util.h" #include "base/threading/worker_pool.h" +#include "content/browser/media/webrtc_identity_store_backend.h" #include "content/public/browser/browser_thread.h" #include "crypto/rsa_private_key.h" #include "net/base/net_errors.h" @@ -18,11 +21,18 @@ namespace content { struct WebRTCIdentityRequestResult { + WebRTCIdentityRequestResult(int error, + const std::string& certificate, + const std::string& private_key) + : error(error), certificate(certificate), private_key(private_key) {} + int error; std::string certificate; std::string private_key; }; +// Generates a new identity using |common_name| and returns the result in +// |result|. static void GenerateIdentityWorker(const std::string& common_name, WebRTCIdentityRequestResult* result) { result->error = net::OK; @@ -60,33 +70,57 @@ static void GenerateIdentityWorker(const std::string& common_name, std::string(private_key_info.begin(), private_key_info.end()); } +class WebRTCIdentityRequestHandle; + // The class represents an identity request internal to WebRTCIdentityStore. -// It has a one-to-one mapping to the external version of the request -// WebRTCIdentityRequestHandle, which is the target of the -// WebRTCIdentityRequest's completion callback. +// It has a one-to-many mapping to the external version of the request, +// WebRTCIdentityRequestHandle, i.e. multiple identical external requests are +// combined into one internal request. // It's deleted automatically when the request is completed. class WebRTCIdentityRequest { public: - WebRTCIdentityRequest(const WebRTCIdentityStore::CompletionCallback& callback) - : callback_(callback) {} + WebRTCIdentityRequest(const GURL& origin, + const std::string& identity_name, + const std::string& common_name) + : origin_(origin), + identity_name_(identity_name), + common_name_(common_name) {} - void Cancel() { + void Cancel(WebRTCIdentityRequestHandle* handle) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - callback_.Reset(); + if (callbacks_.find(handle) == callbacks_.end()) + return; + callbacks_.erase(handle); } private: friend class WebRTCIdentityStore; - void Post(WebRTCIdentityRequestResult* result) { + void AddCallback(WebRTCIdentityRequestHandle* handle, + const WebRTCIdentityStore::CompletionCallback& callback) { + DCHECK(callbacks_.find(handle) == callbacks_.end()); + callbacks_[handle] = callback; + } + + // This method deletes "this" and no one should access it after the request + // completes. + // We do not use base::Owned to tie its lifetime to the callback for + // WebRTCIdentityStoreBackend::FindIdentity, because it needs to live longer + // than that if the identity does not exist in DB. + void Post(const WebRTCIdentityRequestResult& result) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - if (callback_.is_null()) - return; - callback_.Run(result->error, result->certificate, result->private_key); - // "this" will be deleted after this point. + for (CallbackMap::iterator it = callbacks_.begin(); it != callbacks_.end(); + ++it) + it->second.Run(result.error, result.certificate, result.private_key); + delete this; } - WebRTCIdentityStore::CompletionCallback callback_; + GURL origin_; + std::string identity_name_; + std::string common_name_; + typedef std::map<WebRTCIdentityRequestHandle*, + WebRTCIdentityStore::CompletionCallback> CallbackMap; + CallbackMap callbacks_; }; // The class represents an identity request which calls back to the external @@ -115,7 +149,7 @@ class WebRTCIdentityRequestHandle { request_ = NULL; // "this" will be deleted after the following call, because "this" is // owned by the Callback held by |request|. - request->Cancel(); + request->Cancel(this); } void OnRequestStarted(WebRTCIdentityRequest* request) { @@ -140,10 +174,12 @@ class WebRTCIdentityRequestHandle { DISALLOW_COPY_AND_ASSIGN(WebRTCIdentityRequestHandle); }; -WebRTCIdentityStore::WebRTCIdentityStore() - : task_runner_(base::WorkerPool::GetTaskRunner(true)) {} +WebRTCIdentityStore::WebRTCIdentityStore(const base::FilePath& path, + quota::SpecialStoragePolicy* policy) + : task_runner_(base::WorkerPool::GetTaskRunner(true)), + backend_(new WebRTCIdentityStoreBackend(path, policy)) {} -WebRTCIdentityStore::~WebRTCIdentityStore() {} +WebRTCIdentityStore::~WebRTCIdentityStore() { backend_->Close(); } base::Closure WebRTCIdentityStore::RequestIdentity( const GURL& origin, @@ -155,26 +191,118 @@ base::Closure WebRTCIdentityStore::RequestIdentity( WebRTCIdentityRequestHandle* handle = new WebRTCIdentityRequestHandle(this, callback); - WebRTCIdentityRequest* request = new WebRTCIdentityRequest(base::Bind( - &WebRTCIdentityRequestHandle::OnRequestComplete, base::Owned(handle))); - handle->OnRequestStarted(request); + WebRTCIdentityRequest* request = + FindRequest(origin, identity_name, common_name); - WebRTCIdentityRequestResult* result = new WebRTCIdentityRequestResult(); - if (!task_runner_->PostTaskAndReply( - FROM_HERE, - base::Bind(&GenerateIdentityWorker, common_name, result), - base::Bind(&WebRTCIdentityRequest::Post, - base::Owned(request), - base::Owned(result)))) - return base::Closure(); + // If there is no identical request in flight, create a new one, queue it, + // and make the backend request. + if (!request) { + request = new WebRTCIdentityRequest(origin, identity_name, common_name); + if (!backend_->FindIdentity( + origin, + identity_name, + common_name, + base::Bind( + &WebRTCIdentityStore::BackendFindCallback, this, request))) { + delete request; + return base::Closure(); + } + in_flight_requests_.push_back(request); + } + + request->AddCallback( + handle, + base::Bind(&WebRTCIdentityRequestHandle::OnRequestComplete, + base::Owned(handle))); + handle->OnRequestStarted(request); return base::Bind(&WebRTCIdentityRequestHandle::Cancel, base::Unretained(handle)); } +void WebRTCIdentityStore::DeleteBetween(base::Time delete_begin, + base::Time delete_end, + const base::Closure& callback) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + backend_->DeleteBetween(delete_begin, delete_end, callback); +} + void WebRTCIdentityStore::SetTaskRunnerForTesting( const scoped_refptr<base::TaskRunner>& task_runner) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); task_runner_ = task_runner; } +void WebRTCIdentityStore::BackendFindCallback(WebRTCIdentityRequest* request, + int error, + const std::string& certificate, + const std::string& private_key) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + if (error == net::OK) { + DVLOG(2) << "Identity found in DB."; + WebRTCIdentityRequestResult result(error, certificate, private_key); + PostRequestResult(request, result); + return; + } + // Generate a new identity if not found in the DB. + WebRTCIdentityRequestResult* result = + new WebRTCIdentityRequestResult(0, "", ""); + if (!task_runner_->PostTaskAndReply( + FROM_HERE, + base::Bind(&GenerateIdentityWorker, request->common_name_, result), + base::Bind(&WebRTCIdentityStore::GenerateIdentityCallback, + this, + request, + base::Owned(result)))) { + // Completes the request with error if failed to post the task. + WebRTCIdentityRequestResult result(net::ERR_UNEXPECTED, "", ""); + PostRequestResult(request, result); + } +} + +void WebRTCIdentityStore::GenerateIdentityCallback( + WebRTCIdentityRequest* request, + WebRTCIdentityRequestResult* result) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + if (result->error == net::OK) { + DVLOG(2) << "New identity generated and added to the backend."; + backend_->AddIdentity(request->origin_, + request->identity_name_, + request->common_name_, + result->certificate, + result->private_key); + } + PostRequestResult(request, *result); +} + +void WebRTCIdentityStore::PostRequestResult( + WebRTCIdentityRequest* request, + const WebRTCIdentityRequestResult& result) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + // Removes the in flight request from the queue. + for (size_t i = 0; i < in_flight_requests_.size(); ++i) { + if (in_flight_requests_[i] == request) { + in_flight_requests_.erase(in_flight_requests_.begin() + i); + break; + } + } + // |request| will be deleted after this call. + request->Post(result); +} + +// Find an identical request from the in flight requests. +WebRTCIdentityRequest* WebRTCIdentityStore::FindRequest( + const GURL& origin, + const std::string& identity_name, + const std::string& common_name) { + for (size_t i = 0; i < in_flight_requests_.size(); ++i) { + if (in_flight_requests_[i]->origin_ == origin && + in_flight_requests_[i]->identity_name_ == identity_name && + in_flight_requests_[i]->common_name_ == common_name) { + return in_flight_requests_[i]; + } + } + return NULL; +} + } // namespace content diff --git a/content/browser/media/webrtc_identity_store.h b/content/browser/media/webrtc_identity_store.h index 1649384..c2b523e 100644 --- a/content/browser/media/webrtc_identity_store.h +++ b/content/browser/media/webrtc_identity_store.h @@ -6,32 +6,44 @@ #define CONTENT_BROWSER_MEDIA_WEBRTC_IDENTITY_STORE_H_ #include <string> +#include <vector> #include "base/callback.h" +#include "base/time/time.h" #include "content/common/content_export.h" class GURL; namespace base { +class FilePath; class TaskRunner; } // namespace base -namespace content { +namespace quota { +class SpecialStoragePolicy; +} // namespace quota +namespace content { +class WebRTCIdentityRequest; +struct WebRTCIdentityRequestResult; +class WebRTCIdentityStoreBackend; class WebRTCIdentityStoreTest; // A class for creating and fetching DTLS identities, i.e. the private key and // the self-signed certificate. -class CONTENT_EXPORT WebRTCIdentityStore { +// It can be created/destroyed on any thread, but the public methods must be +// called on the IO thread. +class CONTENT_EXPORT WebRTCIdentityStore + : public base::RefCountedThreadSafe<WebRTCIdentityStore> { public: typedef base::Callback<void(int error, const std::string& certificate, const std::string& private_key)> CompletionCallback; - WebRTCIdentityStore(); - // Only virtual to allow subclassing for test mock. - virtual ~WebRTCIdentityStore(); + // If |path| is empty, nothing will be saved to disk. + WebRTCIdentityStore(const base::FilePath& path, + quota::SpecialStoragePolicy* policy); // Retrieve the cached DTLS private key and certificate, i.e. identity, for // the |origin| and |identity_name| pair, or generate a new identity using @@ -39,7 +51,6 @@ class CONTENT_EXPORT WebRTCIdentityStore { // If the given |common_name| is different from the common name in the cached // identity that has the same origin and identity_name, a new private key and // a new certificate will be generated, overwriting the old one. - // TODO(jiayl): implement identity caching through a persistent storage. // // |origin| is the origin of the DTLS connection; // |identity_name| is used to identify an identity within an origin; it is @@ -49,7 +60,7 @@ class CONTENT_EXPORT WebRTCIdentityStore { // be shared with the peer of the DTLS connection. Identities created for // different origins or different identity names may have the same common // name. - // |callback| is the callback to return the result. + // |callback| is the callback to return the result as DER strings. // // Returns the Closure used to cancel the request if the request is accepted. // The Closure can only be called before the request completes. @@ -58,14 +69,42 @@ class CONTENT_EXPORT WebRTCIdentityStore { const std::string& common_name, const CompletionCallback& callback); + // Delete the identities created between |delete_begin| and |delete_end|. + // |callback| will be called when the operation is done. + void DeleteBetween(base::Time delete_begin, + base::Time delete_end, + const base::Closure& callback); + + protected: + // Only virtual to allow subclassing for test mock. + virtual ~WebRTCIdentityStore(); + private: + friend class base::RefCountedThreadSafe<WebRTCIdentityStore>; friend class WebRTCIdentityStoreTest; void SetTaskRunnerForTesting( const scoped_refptr<base::TaskRunner>& task_runner); + void BackendFindCallback(WebRTCIdentityRequest* request, + int error, + const std::string& certificate, + const std::string& private_key); + void GenerateIdentityCallback(WebRTCIdentityRequest* request, + WebRTCIdentityRequestResult* result); + WebRTCIdentityRequest* FindRequest(const GURL& origin, + const std::string& identity_name, + const std::string& common_name); + void PostRequestResult(WebRTCIdentityRequest* request, + const WebRTCIdentityRequestResult& result); + // The TaskRunner for doing work on a worker thread. scoped_refptr<base::TaskRunner> task_runner_; + // Weak references of the in flight requests. Used to join identical external + // requests. + std::vector<WebRTCIdentityRequest*> in_flight_requests_; + + scoped_refptr<WebRTCIdentityStoreBackend> backend_; DISALLOW_COPY_AND_ASSIGN(WebRTCIdentityStore); }; diff --git a/content/browser/media/webrtc_identity_store_backend.cc b/content/browser/media/webrtc_identity_store_backend.cc new file mode 100644 index 0000000..a86cc20 --- /dev/null +++ b/content/browser/media/webrtc_identity_store_backend.cc @@ -0,0 +1,543 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/media/webrtc_identity_store_backend.h" + +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "content/public/browser/browser_thread.h" +#include "net/base/net_errors.h" +#include "sql/error_delegate_util.h" +#include "sql/statement.h" +#include "sql/transaction.h" +#include "url/gurl.h" +#include "webkit/browser/quota/special_storage_policy.h" + +namespace content { + +static const char* kWebRTCIdentityStoreDBName = "webrtc_identity_store"; + +static const base::FilePath::CharType kWebRTCIdentityStoreDirectory[] = + FILE_PATH_LITERAL("WebRTCIdentityStore"); + +// Initializes the identity table, returning true on success. +static bool InitDB(sql::Connection* db) { + if (db->DoesTableExist(kWebRTCIdentityStoreDBName)) { + if (db->DoesColumnExist(kWebRTCIdentityStoreDBName, "origin") && + db->DoesColumnExist(kWebRTCIdentityStoreDBName, "identity_name") && + db->DoesColumnExist(kWebRTCIdentityStoreDBName, "common_name") && + db->DoesColumnExist(kWebRTCIdentityStoreDBName, "certificate") && + db->DoesColumnExist(kWebRTCIdentityStoreDBName, "private_key") && + db->DoesColumnExist(kWebRTCIdentityStoreDBName, "creation_time")) + return true; + if (!db->Execute("DROP TABLE webrtc_identity_store")) + return false; + } + + return db->Execute( + "CREATE TABLE webrtc_identity_store" + " (" + "origin TEXT NOT NULL," + "identity_name TEXT NOT NULL," + "common_name TEXT NOT NULL," + "certificate BLOB NOT NULL," + "private_key BLOB NOT NULL," + "creation_time INTEGER)"); +} + +struct WebRTCIdentityStoreBackend::IdentityKey { + IdentityKey(const GURL& origin, const std::string& identity_name) + : origin(origin), identity_name(identity_name) {} + + bool operator<(const IdentityKey& other) const { + return origin < other.origin || + (origin == other.origin && identity_name < other.identity_name); + } + + GURL origin; + std::string identity_name; +}; + +struct WebRTCIdentityStoreBackend::Identity { + Identity(const std::string& common_name, + const std::string& certificate, + const std::string& private_key) + : common_name(common_name), + certificate(certificate), + private_key(private_key), + creation_time(base::Time::Now().ToInternalValue()) {} + + Identity(const std::string& common_name, + const std::string& certificate, + const std::string& private_key, + int64 creation_time) + : common_name(common_name), + certificate(certificate), + private_key(private_key), + creation_time(creation_time) {} + + std::string common_name; + std::string certificate; + std::string private_key; + int64 creation_time; +}; + +struct WebRTCIdentityStoreBackend::PendingFindRequest { + PendingFindRequest(const GURL& origin, + const std::string& identity_name, + const std::string& common_name, + const FindIdentityCallback& callback) + : origin(origin), + identity_name(identity_name), + common_name(common_name), + callback(callback) {} + + ~PendingFindRequest() {} + + GURL origin; + std::string identity_name; + std::string common_name; + FindIdentityCallback callback; +}; + +// The class encapsulates the database operations. All members except ctor and +// dtor should be accessed on the DB thread. +// It can be created/destroyed on any thread. +class WebRTCIdentityStoreBackend::SqlLiteStorage + : public base::RefCountedThreadSafe<SqlLiteStorage> { + public: + SqlLiteStorage(const base::FilePath& path, + quota::SpecialStoragePolicy* policy) + : special_storage_policy_(policy) { + if (!path.empty()) + path_ = path.Append(kWebRTCIdentityStoreDirectory); + } + + void Load(IdentityMap* out_map); + void Close(); + void AddIdentity(const GURL& origin, + const std::string& identity_name, + const Identity& identity); + void DeleteIdentity(const GURL& origin, + const std::string& identity_name, + const Identity& identity); + void DeleteBetween(base::Time delete_begin, + base::Time delete_end, + const base::Closure& callback); + + private: + friend class base::RefCountedThreadSafe<SqlLiteStorage>; + + enum OperationType { + ADD_IDENTITY, + DELETE_IDENTITY + }; + struct PendingOperation { + PendingOperation(OperationType type, + const GURL& origin, + const std::string& identity_name, + const Identity& identity) + : type(type), + origin(origin), + identity_name(identity_name), + identity(identity) {} + + OperationType type; + GURL origin; + std::string identity_name; + Identity identity; + }; + typedef std::vector<PendingOperation*> PendingOperationList; + + virtual ~SqlLiteStorage() {} + void OnDatabaseError(int error, sql::Statement* stmt); + void BatchOperation(OperationType type, + const GURL& origin, + const std::string& identity_name, + const Identity& identity); + void Commit(); + + // The file path of the DB. Empty if temporary. + base::FilePath path_; + scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy_; + scoped_ptr<sql::Connection> db_; + // Batched DB operations pending to commit. + PendingOperationList pending_operations_; + + DISALLOW_COPY_AND_ASSIGN(SqlLiteStorage); +}; + +WebRTCIdentityStoreBackend::WebRTCIdentityStoreBackend( + const base::FilePath& path, + quota::SpecialStoragePolicy* policy) + : state_(NOT_STARTED), + sql_lite_storage_(new SqlLiteStorage(path, policy)) {} + +bool WebRTCIdentityStoreBackend::FindIdentity( + const GURL& origin, + const std::string& identity_name, + const std::string& common_name, + const FindIdentityCallback& callback) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + if (state_ == CLOSED) + return false; + + if (state_ != LOADED) { + // Queues the request to wait for the DB to load. + pending_find_requests_.push_back( + new PendingFindRequest(origin, identity_name, common_name, callback)); + if (state_ == LOADING) + return true; + + DCHECK_EQ(state_, NOT_STARTED); + + // Kick off loading the DB. + scoped_ptr<IdentityMap> out_map(new IdentityMap()); + if (BrowserThread::PostTaskAndReply( + BrowserThread::DB, + FROM_HERE, + base::Bind(&SqlLiteStorage::Load, sql_lite_storage_, out_map.get()), + base::Bind(&WebRTCIdentityStoreBackend::OnLoaded, + this, + base::Passed(&out_map)))) { + state_ = LOADING; + return true; + } + // If it fails to post task, falls back to ERR_FILE_NOT_FOUND. + } + + IdentityKey key(origin, identity_name); + IdentityMap::iterator iter = identities_.find(key); + if (iter != identities_.end() && iter->second.common_name == common_name) { + // Identity found. + return BrowserThread::PostTask(BrowserThread::IO, + FROM_HERE, + base::Bind(callback, + net::OK, + iter->second.certificate, + iter->second.private_key)); + } + + return BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(callback, net::ERR_FILE_NOT_FOUND, "", "")); +} + +void WebRTCIdentityStoreBackend::AddIdentity(const GURL& origin, + const std::string& identity_name, + const std::string& common_name, + const std::string& certificate, + const std::string& private_key) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + if (state_ == CLOSED) + return; + + // If there is an existing identity for the same origin and identity_name, + // delete it. + IdentityKey key(origin, identity_name); + Identity identity(common_name, certificate, private_key); + + if (identities_.find(key) != identities_.end()) { + if (!BrowserThread::PostTask(BrowserThread::DB, + FROM_HERE, + base::Bind(&SqlLiteStorage::DeleteIdentity, + sql_lite_storage_, + origin, + identity_name, + identities_.find(key)->second))) + return; + } + identities_.insert(std::pair<IdentityKey, Identity>(key, identity)); + + BrowserThread::PostTask(BrowserThread::DB, + FROM_HERE, + base::Bind(&SqlLiteStorage::AddIdentity, + sql_lite_storage_, + origin, + identity_name, + identity)); +} + +void WebRTCIdentityStoreBackend::Close() { + if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(&WebRTCIdentityStoreBackend::Close, this)); + return; + } + + if (state_ == CLOSED) + return; + + state_ = CLOSED; + BrowserThread::PostTask( + BrowserThread::DB, + FROM_HERE, + base::Bind(&SqlLiteStorage::Close, sql_lite_storage_)); +} + +void WebRTCIdentityStoreBackend::DeleteBetween(base::Time delete_begin, + base::Time delete_end, + const base::Closure& callback) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + // Delete the in-memory cache. + IdentityMap::iterator it = identities_.begin(); + while (it != identities_.end()) { + if (it->second.creation_time >= delete_begin.ToInternalValue() && + it->second.creation_time <= delete_end.ToInternalValue()) + identities_.erase(it++); + else + it++; + } + + BrowserThread::PostTaskAndReply(BrowserThread::DB, + FROM_HERE, + base::Bind(&SqlLiteStorage::DeleteBetween, + sql_lite_storage_, + delete_begin, + delete_end, + callback), + callback); +} + +WebRTCIdentityStoreBackend::~WebRTCIdentityStoreBackend() {} + +void WebRTCIdentityStoreBackend::OnLoaded(scoped_ptr<IdentityMap> out_map) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + state_ = LOADED; + identities_.swap(*out_map); + + for (size_t i = 0; i < pending_find_requests_.size(); ++i) { + FindIdentity(pending_find_requests_[i]->origin, + pending_find_requests_[i]->identity_name, + pending_find_requests_[i]->common_name, + pending_find_requests_[i]->callback); + delete pending_find_requests_[i]; + } + pending_find_requests_.clear(); +} + +// +// Implementation of SqlLiteStorage. +// + +void WebRTCIdentityStoreBackend::SqlLiteStorage::Load(IdentityMap* out_map) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + DCHECK(!db_.get()); + + // Ensure the parent directory for storing certs is created before reading + // from it. + const base::FilePath dir = path_.DirName(); + if (!base::PathExists(dir) && !file_util::CreateDirectory(dir)) { + DLOG(ERROR) << "Unable to open DB file path."; + return; + } + + db_.reset(new sql::Connection()); + + db_->set_error_callback(base::Bind(&SqlLiteStorage::OnDatabaseError, this)); + + if (!db_->Open(path_)) { + DLOG(ERROR) << "Unable to open DB."; + db_.reset(); + return; + } + + if (!InitDB(db_.get())) { + DLOG(ERROR) << "Unable to init DB."; + db_.reset(); + return; + } + + db_->Preload(); + + // Slurp all the identities into the out_map. + sql::Statement stmt(db_->GetUniqueStatement( + "SELECT origin, identity_name, common_name, " + "certificate, private_key, creation_time " + "FROM webrtc_identity_store")); + CHECK(stmt.is_valid()); + + while (stmt.Step()) { + IdentityKey key(GURL(stmt.ColumnString(0)), stmt.ColumnString(1)); + std::string common_name(stmt.ColumnString(2)); + std::string cert, private_key; + stmt.ColumnBlobAsString(3, &cert); + stmt.ColumnBlobAsString(4, &private_key); + int64 creation_time = stmt.ColumnInt64(5); + std::pair<IdentityMap::iterator, bool> result = + out_map->insert(std::pair<IdentityKey, Identity>( + key, Identity(common_name, cert, private_key, creation_time))); + DCHECK(result.second); + } +} + +void WebRTCIdentityStoreBackend::SqlLiteStorage::Close() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + Commit(); + db_.reset(); +} + +void WebRTCIdentityStoreBackend::SqlLiteStorage::AddIdentity( + const GURL& origin, + const std::string& identity_name, + const Identity& identity) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + if (!db_.get()) + return; + + // Do not add for session only origins. + if (special_storage_policy_.get() && + !special_storage_policy_->IsStorageProtected(origin) && + special_storage_policy_->IsStorageSessionOnly(origin)) { + return; + } + BatchOperation(ADD_IDENTITY, origin, identity_name, identity); +} + +void WebRTCIdentityStoreBackend::SqlLiteStorage::DeleteIdentity( + const GURL& origin, + const std::string& identity_name, + const Identity& identity) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + if (!db_.get()) + return; + BatchOperation(DELETE_IDENTITY, origin, identity_name, identity); +} + +void WebRTCIdentityStoreBackend::SqlLiteStorage::OnDatabaseError( + int error, + sql::Statement* stmt) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + if (!sql::IsErrorCatastrophic(error)) + return; + db_->RazeAndClose(); +} + +void WebRTCIdentityStoreBackend::SqlLiteStorage::BatchOperation( + OperationType type, + const GURL& origin, + const std::string& identity_name, + const Identity& identity) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + // Commit every 30 seconds. + static const base::TimeDelta kCommitInterval( + base::TimeDelta::FromSeconds(30)); + // Commit right away if we have more than 512 outstanding operations. + static const size_t kCommitAfterBatchSize = 512; + + // We do a full copy of the cert here, and hopefully just here. + scoped_ptr<PendingOperation> operation( + new PendingOperation(type, origin, identity_name, identity)); + + pending_operations_.push_back(operation.release()); + + if (pending_operations_.size() == 1) { + // We've gotten our first entry for this batch, fire off the timer. + BrowserThread::PostDelayedTask(BrowserThread::DB, + FROM_HERE, + base::Bind(&SqlLiteStorage::Commit, this), + kCommitInterval); + } else if (pending_operations_.size() >= kCommitAfterBatchSize) { + // We've reached a big enough batch, fire off a commit now. + BrowserThread::PostTask(BrowserThread::DB, + FROM_HERE, + base::Bind(&SqlLiteStorage::Commit, this)); + } +} + +void WebRTCIdentityStoreBackend::SqlLiteStorage::Commit() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + // Maybe an old timer fired or we are already Close()'ed. + if (!db_.get() || pending_operations_.empty()) + return; + + sql::Statement add_stmt(db_->GetCachedStatement( + SQL_FROM_HERE, + "INSERT INTO webrtc_identity_store " + "(origin, identity_name, common_name, certificate," + " private_key, creation_time) VALUES" + " (?,?,?,?,?,?)")); + + CHECK(add_stmt.is_valid()); + + sql::Statement del_stmt(db_->GetCachedStatement( + SQL_FROM_HERE, + "DELETE FROM webrtc_identity_store WHERE origin=? AND identity_name=?")); + + CHECK(del_stmt.is_valid()); + + sql::Transaction transaction(db_.get()); + if (!transaction.Begin()) { + DLOG(ERROR) << "Failed to begin the transaction."; + return; + } + + for (PendingOperationList::iterator it = pending_operations_.begin(); + it != pending_operations_.end(); + ++it) { + scoped_ptr<PendingOperation> po(*it); + switch (po->type) { + case ADD_IDENTITY: { + add_stmt.Reset(true); + add_stmt.BindString(0, po->origin.spec()); + add_stmt.BindString(1, po->identity_name); + add_stmt.BindString(2, po->identity.common_name); + const std::string& cert = po->identity.certificate; + add_stmt.BindBlob(3, cert.data(), cert.size()); + const std::string& private_key = po->identity.private_key; + add_stmt.BindBlob(4, private_key.data(), private_key.size()); + add_stmt.BindInt64(5, po->identity.creation_time); + CHECK(add_stmt.Run()); + break; + } + case DELETE_IDENTITY: + del_stmt.Reset(true); + del_stmt.BindString(0, po->origin.spec()); + add_stmt.BindString(1, po->identity_name); + CHECK(del_stmt.Run()); + break; + + default: + NOTREACHED(); + break; + } + } + transaction.Commit(); + pending_operations_.clear(); +} + +void WebRTCIdentityStoreBackend::SqlLiteStorage::DeleteBetween( + base::Time delete_begin, + base::Time delete_end, + const base::Closure& callback) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + if (!db_.get()) + return; + + // Commit pending operations first. + Commit(); + + sql::Statement del_stmt(db_->GetCachedStatement( + SQL_FROM_HERE, + "DELETE FROM webrtc_identity_store" + " WHERE creation_time >= ? AND creation_time <= ?")); + CHECK(del_stmt.is_valid()); + + del_stmt.BindInt64(0, delete_begin.ToInternalValue()); + del_stmt.BindInt64(1, delete_end.ToInternalValue()); + + sql::Transaction transaction(db_.get()); + if (!transaction.Begin()) { + DLOG(ERROR) << "Failed to begin the transaction."; + return; + } + + CHECK(del_stmt.Run()); + transaction.Commit(); +} + +} // namespace content diff --git a/content/browser/media/webrtc_identity_store_backend.h b/content/browser/media/webrtc_identity_store_backend.h new file mode 100644 index 0000000..ab4e1ed --- /dev/null +++ b/content/browser/media/webrtc_identity_store_backend.h @@ -0,0 +1,111 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_MEDIA_WEBRTC_IDENTITY_STORE_BACKEND_H_ +#define CONTENT_BROWSER_MEDIA_WEBRTC_IDENTITY_STORE_BACKEND_H_ + +#include <map> +#include <string> + +#include "base/time/time.h" +#include "sql/connection.h" +#include "sql/meta_table.h" + +class GURL; + +namespace base { +class FilePath; +} // namespace base + +namespace quota { +class SpecialStoragePolicy; +} // namespace quota + +namespace content { + +// This class represents a persistent cache of WebRTC identities. +// It can be created/destroyed/Close() on any thread. All other members should +// be accessed on the IO thread. +class WebRTCIdentityStoreBackend + : public base::RefCountedThreadSafe<WebRTCIdentityStoreBackend> { + public: + typedef base::Callback<void(int error, + const std::string& certificate, + const std::string& private_key)> + FindIdentityCallback; + + // No data is saved on disk if |path| is empty. + WebRTCIdentityStoreBackend(const base::FilePath& path, + quota::SpecialStoragePolicy* policy); + + // Finds the identity with |origin|, |identity_name|, and |common_name| from + // the DB. + // |origin| is the origin of the identity; + // |identity_name| is used to identify an identity within an origin; + // |common_name| is the common name used to generate the certificate; + // |callback| is the callback to return the find result. + // Returns true if |callback| will be called. + // Should be called on the IO thread. + bool FindIdentity(const GURL& origin, + const std::string& identity_name, + const std::string& common_name, + const FindIdentityCallback& callback); + + // Adds the identity to the DB and overwrites any existing identity having the + // same origin and identity_name. + // |origin| is the origin of the identity; + // |identity_name| is used to identify an identity within an origin; + // |common_name| is the common name used to generate the certificate; + // |certificate| is the DER string of the certificate; + // |private_key| is the DER string of the private key. + // Should be called on the IO thread. + void AddIdentity(const GURL& origin, + const std::string& identity_name, + const std::string& common_name, + const std::string& certificate, + const std::string& private_key); + + // Commits all pending DB operations and closes the DB connection. Any API + // call after this will fail. + // Can be called on any thread. + void Close(); + + // Delete the data created between |delete_begin| and |delete_end|. + // Should be called on the IO thread. + void DeleteBetween(base::Time delete_begin, + base::Time delete_end, + const base::Closure& callback); + + private: + friend class base::RefCountedThreadSafe<WebRTCIdentityStoreBackend>; + class SqlLiteStorage; + enum LoadingState { + NOT_STARTED, + LOADING, + LOADED, + CLOSED, + }; + struct PendingFindRequest; + struct IdentityKey; + struct Identity; + typedef std::map<IdentityKey, Identity> IdentityMap; + + ~WebRTCIdentityStoreBackend(); + + void OnLoaded(scoped_ptr<IdentityMap> out_map); + + // In-memory copy of the identities. + IdentityMap identities_; + // "Find identity" requests waiting for the DB to load. + std::vector<PendingFindRequest*> pending_find_requests_; + // The persistent storage loading state. + LoadingState state_; + // The persistent storage of identities. + scoped_refptr<SqlLiteStorage> sql_lite_storage_; + + DISALLOW_COPY_AND_ASSIGN(WebRTCIdentityStoreBackend); +}; +} + +#endif // CONTENT_BROWSER_MEDIA_WEBRTC_IDENTITY_STORE_BACKEND_H_ diff --git a/content/browser/media/webrtc_identity_store_unittest.cc b/content/browser/media/webrtc_identity_store_unittest.cc index a975dd4..81c25d7 100644 --- a/content/browser/media/webrtc_identity_store_unittest.cc +++ b/content/browser/media/webrtc_identity_store_unittest.cc @@ -17,15 +17,37 @@ namespace content { // TODO(jiayl): the tests fail on Android since the openssl version of // CreateSelfSignedCert is not implemented. We should mock out this dependency // and remove the if-defined. - #if !defined(OS_ANDROID) + +static const char* kFakeOrigin = "http://foo.com"; +static const char* kFakeIdentityName1 = "name1"; +static const char* kFakeIdentityName2 = "name2"; +static const char* kFakeCommonName1 = "cname1"; +static const char* kFakeCommonName2 = "cname2"; + +static void OnRequestCompleted(bool* completed, + std::string* out_cert, + std::string* out_key, + int error, + const std::string& certificate, + const std::string& private_key) { + ASSERT_EQ(net::OK, error); + ASSERT_NE("", certificate); + ASSERT_NE("", private_key); + *completed = true; + *out_cert = certificate; + *out_key = private_key; +} + class WebRTCIdentityStoreTest : public testing::Test { public: WebRTCIdentityStoreTest() - : browser_thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP), + : browser_thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP | + TestBrowserThreadBundle::REAL_DB_THREAD), pool_owner_( new base::SequencedWorkerPoolOwner(3, "WebRTCIdentityStoreTest")), - webrtc_identity_store_(new WebRTCIdentityStore()) { + webrtc_identity_store_( + new WebRTCIdentityStore(base::FilePath(), NULL)) { webrtc_identity_store_->SetTaskRunnerForTesting(pool_owner_->pool()); } @@ -33,74 +55,224 @@ class WebRTCIdentityStoreTest : public testing::Test { pool_owner_->pool()->Shutdown(); } + void RunUntilIdle() { + RunAllPendingInMessageLoop(BrowserThread::DB); + RunAllPendingInMessageLoop(BrowserThread::IO); + pool_owner_->pool()->FlushForTesting(); + base::RunLoop().RunUntilIdle(); + } + + base::Closure RequestIdentityAndRunUtilIdle(const std::string& origin, + const std::string& identity_name, + const std::string& common_name, + bool* completed, + std::string* certificate, + std::string* private_key) { + base::Closure cancel_callback = webrtc_identity_store_->RequestIdentity( + GURL(origin), + identity_name, + common_name, + base::Bind(&OnRequestCompleted, completed, certificate, private_key)); + EXPECT_FALSE(cancel_callback.is_null()); + RunUntilIdle(); + return cancel_callback; + } + protected: TestBrowserThreadBundle browser_thread_bundle_; scoped_ptr<base::SequencedWorkerPoolOwner> pool_owner_; - scoped_ptr<WebRTCIdentityStore> webrtc_identity_store_; + scoped_refptr<WebRTCIdentityStore> webrtc_identity_store_; }; -static void OnRequestCompleted(bool* completed, - int error, - const std::string& certificate, - const std::string& private_key) { - ASSERT_EQ(net::OK, error); - ASSERT_NE("", certificate); - ASSERT_NE("", private_key); - *completed = true; -} - TEST_F(WebRTCIdentityStoreTest, RequestIdentity) { bool completed = false; + std::string dummy; base::Closure cancel_callback = - webrtc_identity_store_->RequestIdentity( - GURL("http://google.com"), - "a", - "b", - base::Bind(&OnRequestCompleted, &completed)); - ASSERT_FALSE(cancel_callback.is_null()); - pool_owner_->pool()->FlushForTesting(); - base::RunLoop().RunUntilIdle(); + RequestIdentityAndRunUtilIdle(kFakeOrigin, + kFakeIdentityName1, + kFakeCommonName1, + &completed, + &dummy, + &dummy); EXPECT_TRUE(completed); } TEST_F(WebRTCIdentityStoreTest, CancelRequest) { bool completed = false; - base::Closure cancel_callback = - webrtc_identity_store_->RequestIdentity( - GURL("http://google.com"), - "a", - "b", - base::Bind(&OnRequestCompleted, &completed)); + std::string dummy; + base::Closure cancel_callback = webrtc_identity_store_->RequestIdentity( + GURL(kFakeOrigin), + kFakeIdentityName1, + kFakeCommonName1, + base::Bind(&OnRequestCompleted, &completed, &dummy, &dummy)); ASSERT_FALSE(cancel_callback.is_null()); cancel_callback.Run(); - pool_owner_->pool()->FlushForTesting(); - base::RunLoop().RunUntilIdle(); + + RunUntilIdle(); EXPECT_FALSE(completed); } -TEST_F(WebRTCIdentityStoreTest, MultipleRequests) { +TEST_F(WebRTCIdentityStoreTest, ConcurrentUniqueRequests) { bool completed_1 = false; bool completed_2 = false; - base::Closure cancel_callback_1 = - webrtc_identity_store_->RequestIdentity( - GURL("http://foo.com"), - "a", - "b", - base::Bind(&OnRequestCompleted, &completed_1)); + std::string dummy; + base::Closure cancel_callback_1 = webrtc_identity_store_->RequestIdentity( + GURL(kFakeOrigin), + kFakeIdentityName1, + kFakeCommonName1, + base::Bind(&OnRequestCompleted, &completed_1, &dummy, &dummy)); ASSERT_FALSE(cancel_callback_1.is_null()); + base::Closure cancel_callback_2 = webrtc_identity_store_->RequestIdentity( + GURL(kFakeOrigin), + kFakeIdentityName2, + kFakeCommonName1, + base::Bind(&OnRequestCompleted, &completed_2, &dummy, &dummy)); + ASSERT_FALSE(cancel_callback_2.is_null()); + + RunUntilIdle(); + EXPECT_TRUE(completed_1); + EXPECT_TRUE(completed_2); +} + +TEST_F(WebRTCIdentityStoreTest, DifferentCommonNameReturnNewIdentity) { + bool completed_1 = false; + bool completed_2 = false; + std::string cert_1, cert_2, key_1, key_2; + + base::Closure cancel_callback_1 = + RequestIdentityAndRunUtilIdle(kFakeOrigin, + kFakeIdentityName1, + kFakeCommonName1, + &completed_1, + &cert_1, + &key_1); + base::Closure cancel_callback_2 = - webrtc_identity_store_->RequestIdentity( - GURL("http://bar.com"), - "a", - "b", - base::Bind(&OnRequestCompleted, &completed_2)); + RequestIdentityAndRunUtilIdle(kFakeOrigin, + kFakeIdentityName1, + kFakeCommonName2, + &completed_2, + &cert_2, + &key_2); + + EXPECT_TRUE(completed_1); + EXPECT_TRUE(completed_2); + EXPECT_NE(cert_1, cert_2); + EXPECT_NE(key_1, key_2); +} + +TEST_F(WebRTCIdentityStoreTest, SerialIdenticalRequests) { + bool completed_1 = false; + bool completed_2 = false; + std::string cert_1, cert_2, key_1, key_2; + + base::Closure cancel_callback_1 = + RequestIdentityAndRunUtilIdle(kFakeOrigin, + kFakeIdentityName1, + kFakeCommonName1, + &completed_1, + &cert_1, + &key_1); + + base::Closure cancel_callback_2 = + RequestIdentityAndRunUtilIdle(kFakeOrigin, + kFakeIdentityName1, + kFakeCommonName1, + &completed_2, + &cert_2, + &key_2); + + EXPECT_TRUE(completed_1); + EXPECT_TRUE(completed_2); + EXPECT_EQ(cert_1, cert_2); + EXPECT_EQ(key_1, key_2); +} + +TEST_F(WebRTCIdentityStoreTest, ConcurrentIdenticalRequestsJoined) { + bool completed_1 = false; + bool completed_2 = false; + std::string cert_1, cert_2, key_1, key_2; + + base::Closure cancel_callback_1 = webrtc_identity_store_->RequestIdentity( + GURL(kFakeOrigin), + kFakeIdentityName1, + kFakeCommonName1, + base::Bind(&OnRequestCompleted, &completed_1, &cert_1, &key_1)); + ASSERT_FALSE(cancel_callback_1.is_null()); + + base::Closure cancel_callback_2 = webrtc_identity_store_->RequestIdentity( + GURL(kFakeOrigin), + kFakeIdentityName1, + kFakeCommonName1, + base::Bind(&OnRequestCompleted, &completed_2, &cert_2, &key_2)); + ASSERT_FALSE(cancel_callback_2.is_null()); + + RunUntilIdle(); + EXPECT_TRUE(completed_1); + EXPECT_TRUE(completed_2); + EXPECT_EQ(cert_1, cert_2); + EXPECT_EQ(key_1, key_2); +} + +TEST_F(WebRTCIdentityStoreTest, CancelOneOfIdenticalRequests) { + bool completed_1 = false; + bool completed_2 = false; + std::string cert_1, cert_2, key_1, key_2; + + base::Closure cancel_callback_1 = webrtc_identity_store_->RequestIdentity( + GURL(kFakeOrigin), + kFakeIdentityName1, + kFakeCommonName1, + base::Bind(&OnRequestCompleted, &completed_1, &cert_1, &key_1)); + ASSERT_FALSE(cancel_callback_1.is_null()); + + base::Closure cancel_callback_2 = webrtc_identity_store_->RequestIdentity( + GURL(kFakeOrigin), + kFakeIdentityName1, + kFakeCommonName1, + base::Bind(&OnRequestCompleted, &completed_2, &cert_2, &key_2)); ASSERT_FALSE(cancel_callback_2.is_null()); - pool_owner_->pool()->FlushForTesting(); - base::RunLoop().RunUntilIdle(); + cancel_callback_1.Run(); + + RunUntilIdle(); + EXPECT_FALSE(completed_1); + EXPECT_TRUE(completed_2); +} + +TEST_F(WebRTCIdentityStoreTest, DeleteDataAndGenerateNewIdentity) { + bool completed_1 = false; + bool completed_2 = false; + std::string cert_1, cert_2, key_1, key_2; + + // Generate the first identity. + base::Closure cancel_callback_1 = + RequestIdentityAndRunUtilIdle(kFakeOrigin, + kFakeIdentityName1, + kFakeCommonName1, + &completed_1, + &cert_1, + &key_1); + + // Clear the data and the second request should return a new identity. + webrtc_identity_store_->DeleteBetween( + base::Time(), base::Time::Now(), base::Bind(&base::DoNothing)); + RunUntilIdle(); + + base::Closure cancel_callback_2 = + RequestIdentityAndRunUtilIdle(kFakeOrigin, + kFakeIdentityName1, + kFakeCommonName1, + &completed_2, + &cert_2, + &key_2); + EXPECT_TRUE(completed_1); EXPECT_TRUE(completed_2); + EXPECT_NE(cert_1, cert_2); + EXPECT_NE(key_1, key_2); } + #endif } // namespace content diff --git a/content/browser/renderer_host/media/webrtc_identity_service_host.cc b/content/browser/renderer_host/media/webrtc_identity_service_host.cc index cce3c74..0230b26 100644 --- a/content/browser/renderer_host/media/webrtc_identity_service_host.cc +++ b/content/browser/renderer_host/media/webrtc_identity_service_host.cc @@ -6,6 +6,7 @@ #include "base/bind.h" #include "base/callback_helpers.h" +#include "content/browser/child_process_security_policy_impl.h" #include "content/browser/media/webrtc_identity_store.h" #include "content/common/media/webrtc_identity_messages.h" #include "net/base/net_errors.h" @@ -13,8 +14,10 @@ namespace content { WebRTCIdentityServiceHost::WebRTCIdentityServiceHost( + int renderer_process_id, WebRTCIdentityStore* identity_store) - : identity_store_(identity_store) {} + : renderer_process_id_(renderer_process_id), + identity_store_(identity_store) {} WebRTCIdentityServiceHost::~WebRTCIdentityServiceHost() { if (!cancel_callback_.is_null()) @@ -42,6 +45,15 @@ void WebRTCIdentityServiceHost::OnRequestIdentity( SendErrorMessage(net::ERR_INSUFFICIENT_RESOURCES); return; } + + ChildProcessSecurityPolicyImpl* policy = + ChildProcessSecurityPolicyImpl::GetInstance(); + if (!policy->CanAccessCookiesForOrigin(renderer_process_id_, origin)) { + DLOG(WARNING) << "Request rejected because origin access is denied."; + SendErrorMessage(net::ERR_ACCESS_DENIED); + return; + } + cancel_callback_ = identity_store_->RequestIdentity( origin, identity_name, diff --git a/content/browser/renderer_host/media/webrtc_identity_service_host.h b/content/browser/renderer_host/media/webrtc_identity_service_host.h index 4c53056..3676223 100644 --- a/content/browser/renderer_host/media/webrtc_identity_service_host.h +++ b/content/browser/renderer_host/media/webrtc_identity_service_host.h @@ -26,7 +26,8 @@ class WebRTCIdentityStore; // ERR_INSUFFICIENT_RESOURCES will be sent back to the renderer. class CONTENT_EXPORT WebRTCIdentityServiceHost : public BrowserMessageFilter { public: - explicit WebRTCIdentityServiceHost(WebRTCIdentityStore* identity_store); + explicit WebRTCIdentityServiceHost(int renderer_process_id, + WebRTCIdentityStore* identity_store); protected: virtual ~WebRTCIdentityServiceHost(); @@ -50,6 +51,7 @@ class CONTENT_EXPORT WebRTCIdentityServiceHost : public BrowserMessageFilter { void SendErrorMessage(int error); + int renderer_process_id_; base::Closure cancel_callback_; WebRTCIdentityStore* identity_store_; diff --git a/content/browser/renderer_host/media/webrtc_identity_service_host_unittest.cc b/content/browser/renderer_host/media/webrtc_identity_service_host_unittest.cc index 058e15be..341378d 100644 --- a/content/browser/renderer_host/media/webrtc_identity_service_host_unittest.cc +++ b/content/browser/renderer_host/media/webrtc_identity_service_host_unittest.cc @@ -4,9 +4,11 @@ #include <deque> +#include "content/browser/child_process_security_policy_impl.h" #include "content/browser/media/webrtc_identity_store.h" #include "content/browser/renderer_host/media/webrtc_identity_service_host.h" #include "content/common/media/webrtc_identity_messages.h" +#include "content/public/test/test_browser_thread_bundle.h" #include "ipc/ipc_message.h" #include "net/base/net_errors.h" #include "testing/gtest/include/gtest/gtest.h" @@ -21,9 +23,12 @@ static const char FAKE_COMMON_NAME[] = "fake common name"; static const char FAKE_CERTIFICATE[] = "fake cert"; static const char FAKE_PRIVATE_KEY[] = "fake private key"; static const int FAKE_ERROR = 100; +static const int FAKE_RENDERER_ID = 10; class MockWebRTCIdentityStore : public WebRTCIdentityStore { public: + MockWebRTCIdentityStore() : WebRTCIdentityStore(base::FilePath(), NULL) {} + virtual base::Closure RequestIdentity( const GURL& origin, const std::string& identity_name, @@ -46,6 +51,8 @@ class MockWebRTCIdentityStore : public WebRTCIdentityStore { } private: + virtual ~MockWebRTCIdentityStore() {} + void OnCancel() { callback_.Reset(); } CompletionCallback callback_; @@ -54,7 +61,11 @@ class MockWebRTCIdentityStore : public WebRTCIdentityStore { class WebRTCIdentityServiceHostForTest : public WebRTCIdentityServiceHost { public: explicit WebRTCIdentityServiceHostForTest(WebRTCIdentityStore* identity_store) - : WebRTCIdentityServiceHost(identity_store) {} + : WebRTCIdentityServiceHost(FAKE_RENDERER_ID, identity_store) { + ChildProcessSecurityPolicyImpl* policy = + ChildProcessSecurityPolicyImpl::GetInstance(); + policy->Add(FAKE_RENDERER_ID); + } virtual bool Send(IPC::Message* message) OVERRIDE { messages_.push_back(*message); @@ -75,7 +86,11 @@ class WebRTCIdentityServiceHostForTest : public WebRTCIdentityServiceHost { void ClearMessages() { messages_.clear(); } private: - virtual ~WebRTCIdentityServiceHostForTest() {} + virtual ~WebRTCIdentityServiceHostForTest() { + ChildProcessSecurityPolicyImpl* policy = + ChildProcessSecurityPolicyImpl::GetInstance(); + policy->Remove(FAKE_RENDERER_ID); + } std::deque<IPC::Message> messages_; }; @@ -83,7 +98,8 @@ class WebRTCIdentityServiceHostForTest : public WebRTCIdentityServiceHost { class WebRTCIdentityServiceHostTest : public ::testing::Test { public: WebRTCIdentityServiceHostTest() - : store_(new MockWebRTCIdentityStore()), + : browser_thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP), + store_(new MockWebRTCIdentityStore()), host_(new WebRTCIdentityServiceHostForTest(store_.get())) {} void SendRequestToHost() { @@ -124,7 +140,8 @@ class WebRTCIdentityServiceHostTest : public ::testing::Test { } protected: - scoped_ptr<MockWebRTCIdentityStore> store_; + TestBrowserThreadBundle browser_thread_bundle_; + scoped_refptr<MockWebRTCIdentityStore> store_; scoped_refptr<WebRTCIdentityServiceHostForTest> host_; }; @@ -158,4 +175,13 @@ TEST_F(WebRTCIdentityServiceHostTest, TestOnRequestFailed) { VerifyRequestFailedMessage(net::ERR_KEY_GENERATION_FAILED); } +TEST_F(WebRTCIdentityServiceHostTest, TestOriginAccessDenied) { + ChildProcessSecurityPolicyImpl* policy = + ChildProcessSecurityPolicyImpl::GetInstance(); + policy->Remove(FAKE_RENDERER_ID); + + SendRequestToHost(); + VerifyRequestFailedMessage(net::ERR_ACCESS_DENIED); +} + } // namespace content diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc index d3ba06e..3ed4860 100644 --- a/content/browser/renderer_host/render_process_host_impl.cc +++ b/content/browser/renderer_host/render_process_host_impl.cc @@ -652,7 +652,7 @@ void RenderProcessHostImpl::CreateMessageFilters() { channel_->AddFilter(gpu_message_filter_); #if defined(ENABLE_WEBRTC) channel_->AddFilter(new WebRTCIdentityServiceHost( - storage_partition_impl_->GetWebRTCIdentityStore())); + GetID(), storage_partition_impl_->GetWebRTCIdentityStore())); peer_connection_tracker_host_ = new PeerConnectionTrackerHost(GetID()); channel_->AddFilter(peer_connection_tracker_host_.get()); channel_->AddFilter(new MediaStreamDispatcherHost( diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc index 1775b5f..361b4d1 100644 --- a/content/browser/storage_partition_impl.cc +++ b/content/browser/storage_partition_impl.cc @@ -252,6 +252,7 @@ struct StoragePartitionImpl::DataDeletionHelper { net::URLRequestContextGetter* rq_context, DOMStorageContextWrapper* dom_storage_context, quota::QuotaManager* quota_manager, + WebRTCIdentityStore* webrtc_identity_store, const base::Time begin, const base::Time end); @@ -284,7 +285,7 @@ StoragePartitionImpl::StoragePartitionImpl( webkit_database::DatabaseTracker* database_tracker, DOMStorageContextWrapper* dom_storage_context, IndexedDBContextImpl* indexed_db_context, - scoped_ptr<WebRTCIdentityStore> webrtc_identity_store) + WebRTCIdentityStore* webrtc_identity_store) : partition_path_(partition_path), quota_manager_(quota_manager), appcache_service_(appcache_service), @@ -292,7 +293,7 @@ StoragePartitionImpl::StoragePartitionImpl( database_tracker_(database_tracker), dom_storage_context_(dom_storage_context), indexed_db_context_(indexed_db_context), - webrtc_identity_store_(webrtc_identity_store.Pass()) {} + webrtc_identity_store_(webrtc_identity_store) {} StoragePartitionImpl::~StoragePartitionImpl() { // These message loop checks are just to avoid leaks in unittests. @@ -367,8 +368,8 @@ StoragePartitionImpl* StoragePartitionImpl::Create( scoped_refptr<ChromeAppCacheService> appcache_service = new ChromeAppCacheService(quota_manager->proxy()); - scoped_ptr<WebRTCIdentityStore> webrtc_identity_store( - new WebRTCIdentityStore()); + scoped_refptr<WebRTCIdentityStore> webrtc_identity_store( + new WebRTCIdentityStore(path, context->GetSpecialStoragePolicy())); return new StoragePartitionImpl(partition_path, quota_manager.get(), @@ -377,7 +378,7 @@ StoragePartitionImpl* StoragePartitionImpl::Create( database_tracker.get(), dom_storage_context.get(), indexed_db_context.get(), - webrtc_identity_store.Pass()); + webrtc_identity_store.get()); } base::FilePath StoragePartitionImpl::GetPath() { @@ -431,7 +432,8 @@ void StoragePartitionImpl::ClearDataImpl( // DataDeletionHelper::DecrementTaskCountOnUI(). helper->ClearDataOnUIThread( remove_mask, quota_storage_remove_mask, remove_origin, - GetPath(), rq_context, dom_storage_context_, quota_manager_, begin, end); + GetPath(), rq_context, dom_storage_context_, quota_manager_, + webrtc_identity_store_, begin, end); } void StoragePartitionImpl:: @@ -546,6 +548,7 @@ void StoragePartitionImpl::DataDeletionHelper::ClearDataOnUIThread( net::URLRequestContextGetter* rq_context, DOMStorageContextWrapper* dom_storage_context, quota::QuotaManager* quota_manager, + WebRTCIdentityStore* webrtc_identity_store, const base::Time begin, const base::Time end) { DCHECK_NE(remove_mask, 0u); @@ -602,6 +605,18 @@ void StoragePartitionImpl::DataDeletionHelper::ClearDataOnUIThread( path, begin, end, decrement_callback)); } + if (remove_mask & REMOVE_DATA_MASK_WEBRTC_IDENTITY) { + IncrementTaskCountOnUI(); + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(&WebRTCIdentityStore::DeleteBetween, + webrtc_identity_store, + begin, + end, + decrement_callback)); + } + DecrementTaskCountOnUI(); } diff --git a/content/browser/storage_partition_impl.h b/content/browser/storage_partition_impl.h index 8665a74..e59347c 100644 --- a/content/browser/storage_partition_impl.h +++ b/content/browser/storage_partition_impl.h @@ -77,7 +77,7 @@ class StoragePartitionImpl : public StoragePartition { webkit_database::DatabaseTracker* database_tracker, DOMStorageContextWrapper* dom_storage_context, IndexedDBContextImpl* indexed_db_context, - scoped_ptr<WebRTCIdentityStore> webrtc_identity_store); + WebRTCIdentityStore* webrtc_identity_store); void ClearDataImpl(uint32 remove_mask, uint32 quota_storage_remove_mask, @@ -112,7 +112,7 @@ class StoragePartitionImpl : public StoragePartition { scoped_refptr<webkit_database::DatabaseTracker> database_tracker_; scoped_refptr<DOMStorageContextWrapper> dom_storage_context_; scoped_refptr<IndexedDBContextImpl> indexed_db_context_; - scoped_ptr<WebRTCIdentityStore> webrtc_identity_store_; + scoped_refptr<WebRTCIdentityStore> webrtc_identity_store_; DISALLOW_COPY_AND_ASSIGN(StoragePartitionImpl); }; diff --git a/content/browser/storage_partition_impl_unittest.cc b/content/browser/storage_partition_impl_unittest.cc index 2207837..a8b47bc 100644 --- a/content/browser/storage_partition_impl_unittest.cc +++ b/content/browser/storage_partition_impl_unittest.cc @@ -109,14 +109,8 @@ TEST_F(StoragePartitionShaderClearTest, ClearShaderCache) { EXPECT_EQ(1u, Size()); TestClosureCallback clear_cb; - StoragePartitionImpl sp(cache_path(), - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - scoped_ptr<WebRTCIdentityStore>()); + StoragePartitionImpl sp( + cache_path(), NULL, NULL, NULL, NULL, NULL, NULL, NULL); base::MessageLoop::current()->PostTask( FROM_HERE, base::Bind(&ClearData, &sp, clear_cb.callback())); clear_cb.WaitForResult(); diff --git a/content/content_browser.gypi b/content/content_browser.gypi index b3751ac..0fc5e68 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -707,6 +707,8 @@ 'browser/media/media_internals_ui.h', 'browser/media/webrtc_identity_store.cc', 'browser/media/webrtc_identity_store.h', + 'browser/media/webrtc_identity_store_backend.cc', + 'browser/media/webrtc_identity_store_backend.h', 'browser/media/webrtc_internals.cc', 'browser/media/webrtc_internals.h', 'browser/media/webrtc_internals_message_handler.cc', diff --git a/content/public/browser/storage_partition.h b/content/public/browser/storage_partition.h index f69feaa..b30ca42 100644 --- a/content/public/browser/storage_partition.h +++ b/content/public/browser/storage_partition.h @@ -64,7 +64,7 @@ class StoragePartition { REMOVE_DATA_MASK_LOCAL_STORAGE = 1 << 4, REMOVE_DATA_MASK_SHADER_CACHE = 1 << 5, REMOVE_DATA_MASK_WEBSQL = 1 << 6, - + REMOVE_DATA_MASK_WEBRTC_IDENTITY = 1 << 7, REMOVE_DATA_MASK_ALL = -1 }; |