diff options
Diffstat (limited to 'content/browser/media/webrtc/webrtc_identity_store.cc')
-rw-r--r-- | content/browser/media/webrtc/webrtc_identity_store.cc | 325 |
1 files changed, 325 insertions, 0 deletions
diff --git a/content/browser/media/webrtc/webrtc_identity_store.cc b/content/browser/media/webrtc/webrtc_identity_store.cc new file mode 100644 index 0000000..a5b91ad --- /dev/null +++ b/content/browser/media/webrtc/webrtc_identity_store.cc @@ -0,0 +1,325 @@ +// 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/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/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 |