| // 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.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <map> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/logging.h" |
| #include "base/macros.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" |
| #include "net/cert/x509_util.h" |
| #include "url/gurl.h" |
| |
| 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| which expires after |
| // |validity_period| and returns the result in |result|. |
| static void GenerateIdentityWorker(const std::string& common_name, |
| base::TimeDelta validity_period, |
| WebRTCIdentityRequestResult* result) { |
| result->error = net::OK; |
| int serial_number = base::RandInt(0, std::numeric_limits<int>::max()); |
| |
| scoped_ptr<crypto::RSAPrivateKey> key; |
| base::Time now = base::Time::Now(); |
| bool success = net::x509_util::CreateKeyAndSelfSignedCert( |
| "CN=" + common_name, |
| serial_number, |
| now, |
| now + validity_period, |
| &key, |
| &result->certificate); |
| |
| if (!success) { |
| DLOG(ERROR) << "Unable to create x509 cert for client"; |
| result->error = net::ERR_SELF_SIGNED_CERT_GENERATION_FAILED; |
| return; |
| } |
| |
| std::vector<uint8_t> private_key_info; |
| if (!key->ExportPrivateKey(&private_key_info)) { |
| DLOG(ERROR) << "Unable to export private key"; |
| result->error = net::ERR_PRIVATE_KEY_EXPORT_FAILED; |
| return; |
| } |
| |
| result->private_key = |
| 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-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 GURL& origin, |
| const std::string& identity_name, |
| const std::string& common_name, |
| bool enable_cache) |
| : origin_(origin), |
| identity_name_(identity_name), |
| common_name_(common_name), |
| enable_cache_(enable_cache) {} |
| |
| void Cancel(WebRTCIdentityRequestHandle* handle) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (callbacks_.find(handle) == callbacks_.end()) |
| return; |
| callbacks_.erase(handle); |
| } |
| |
| bool enable_cache() const { return enable_cache_; } |
| |
| private: |
| friend class WebRTCIdentityStore; |
| |
| 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_CURRENTLY_ON(BrowserThread::IO); |
| for (CallbackMap::iterator it = callbacks_.begin(); it != callbacks_.end(); |
| ++it) |
| it->second.Run(result.error, result.certificate, result.private_key); |
| delete this; |
| } |
| |
| GURL origin_; |
| std::string identity_name_; |
| std::string common_name_; |
| typedef std::map<WebRTCIdentityRequestHandle*, |
| WebRTCIdentityStore::CompletionCallback> CallbackMap; |
| CallbackMap callbacks_; |
| bool enable_cache_; |
| }; |
| |
| // The class represents an identity request which calls back to the external |
| // client when the request completes. |
| // Its lifetime is tied with the Callback held by the corresponding |
| // WebRTCIdentityRequest. |
| class WebRTCIdentityRequestHandle { |
| public: |
| WebRTCIdentityRequestHandle( |
| WebRTCIdentityStore* store, |
| const WebRTCIdentityStore::CompletionCallback& callback) |
| : request_(NULL), callback_(callback) {} |
| |
| private: |
| friend class WebRTCIdentityStore; |
| |
| // Cancel the request. Does nothing if the request finished or was already |
| // cancelled. |
| void Cancel() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (!request_) |
| return; |
| |
| callback_.Reset(); |
| WebRTCIdentityRequest* request = request_; |
| request_ = NULL; |
| // "this" will be deleted after the following call, because "this" is |
| // owned by the Callback held by |request|. |
| request->Cancel(this); |
| } |
| |
| void OnRequestStarted(WebRTCIdentityRequest* request) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(request); |
| request_ = request; |
| } |
| |
| void OnRequestComplete(int error, |
| const std::string& certificate, |
| const std::string& private_key) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(request_); |
| request_ = NULL; |
| base::ResetAndReturn(&callback_).Run(error, certificate, private_key); |
| } |
| |
| WebRTCIdentityRequest* request_; |
| WebRTCIdentityStore::CompletionCallback callback_; |
| |
| DISALLOW_COPY_AND_ASSIGN(WebRTCIdentityRequestHandle); |
| }; |
| |
| WebRTCIdentityStore::WebRTCIdentityStore(const base::FilePath& path, |
| storage::SpecialStoragePolicy* policy) |
| : validity_period_(base::TimeDelta::FromDays(30)), |
| task_runner_(base::WorkerPool::GetTaskRunner(true)), |
| backend_(new WebRTCIdentityStoreBackend(path, policy, validity_period_)) { |
| } |
| |
| WebRTCIdentityStore::~WebRTCIdentityStore() { backend_->Close(); } |
| |
| base::Closure WebRTCIdentityStore::RequestIdentity( |
| const GURL& origin, |
| const std::string& identity_name, |
| const std::string& common_name, |
| const CompletionCallback& callback, |
| bool enable_cache) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| WebRTCIdentityRequest* request = |
| FindRequest(origin, identity_name, common_name); |
| // 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, |
| enable_cache); |
| // In either case, |request| will delete itself after the result is posted. |
| if (enable_cache) { |
| if (!backend_->FindIdentity( |
| origin, identity_name, common_name, |
| base::Bind(&WebRTCIdentityStore::BackendFindCallback, this, |
| request))) { |
| // Bail out if the backend failed to start the task. |
| delete request; |
| return base::Closure(); |
| } |
| } else { |
| GenerateNewIdentity(request); |
| } |
| in_flight_requests_.push_back(request); |
| } |
| |
| WebRTCIdentityRequestHandle* handle = |
| new WebRTCIdentityRequestHandle(this, callback); |
| |
| 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_CURRENTLY_ON(BrowserThread::IO); |
| backend_->DeleteBetween(delete_begin, delete_end, callback); |
| } |
| |
| void WebRTCIdentityStore::SetValidityPeriodForTesting( |
| base::TimeDelta validity_period) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| validity_period_ = validity_period; |
| backend_->SetValidityPeriodForTesting(validity_period); |
| } |
| |
| void WebRTCIdentityStore::SetTaskRunnerForTesting( |
| const scoped_refptr<base::TaskRunner>& task_runner) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| task_runner_ = task_runner; |
| } |
| |
| void WebRTCIdentityStore::BackendFindCallback(WebRTCIdentityRequest* request, |
| int error, |
| const std::string& certificate, |
| const std::string& private_key) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (error == net::OK) { |
| DVLOG(2) << "Identity found in DB."; |
| WebRTCIdentityRequestResult result(error, certificate, private_key); |
| PostRequestResult(request, result); |
| return; |
| } |
| GenerateNewIdentity(request); |
| } |
| |
| void WebRTCIdentityStore::GenerateIdentityCallback( |
| WebRTCIdentityRequest* request, |
| WebRTCIdentityRequestResult* result) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (result->error == net::OK && request->enable_cache()) { |
| 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_CURRENTLY_ON(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; |
| } |
| |
| void WebRTCIdentityStore::GenerateNewIdentity(WebRTCIdentityRequest* request) { |
| WebRTCIdentityRequestResult* result = |
| new WebRTCIdentityRequestResult(0, "", ""); |
| if (!task_runner_->PostTaskAndReply( |
| FROM_HERE, base::Bind(&GenerateIdentityWorker, request->common_name_, |
| validity_period_, 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); |
| } |
| } |
| |
| } // namespace content |