diff options
author | jiayl@chromium.org <jiayl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-08-08 22:03:53 +0000 |
---|---|---|
committer | jiayl@chromium.org <jiayl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-08-08 22:03:53 +0000 |
commit | ec346e097fdc2b32dbf7305179182f43393b5be1 (patch) | |
tree | 7464385f6bbc638b3b6b6b1a59385db0b270e6c6 | |
parent | f06dc19bf8afa46c8a74d94ab2c35cb4118de883 (diff) | |
download | chromium_src-ec346e097fdc2b32dbf7305179182f43393b5be1.zip chromium_src-ec346e097fdc2b32dbf7305179182f43393b5be1.tar.gz chromium_src-ec346e097fdc2b32dbf7305179182f43393b5be1.tar.bz2 |
Adds a SqlLite backend to WebRTCIdentityStore.
WebRTCIdentityStore maintains a list of inflight requests. If a new request is identical (i.e. same origin, identity_name, common_name) to a inflight request, the new one is joined into the existing one, so that it will not make duplicated requests to the backend or generate dupicated identities.
If the backend does not find an existing identity, WebRTCIdentityStore generates a new one and adds it to the backend.
The backend borrows most of the design from SQLiteServerBoundCertStore::Backend. It loads the database into memory on the first find request. Add or delete database operations are batched to commit every 30 seconds or every 512 operations.
BUG=
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=216264
Review URL: https://chromiumcodereview.appspot.com/21453002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@216448 0039d316-1c4b-4281-b951-d872f2087c98
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 }; |