Home | History | Annotate | Download | only in media
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "content/browser/media/webrtc_identity_store.h"
      6 
      7 #include <map>
      8 
      9 #include "base/bind.h"
     10 #include "base/callback_helpers.h"
     11 #include "base/logging.h"
     12 #include "base/rand_util.h"
     13 #include "base/threading/worker_pool.h"
     14 #include "content/browser/media/webrtc_identity_store_backend.h"
     15 #include "content/public/browser/browser_thread.h"
     16 #include "crypto/rsa_private_key.h"
     17 #include "net/base/net_errors.h"
     18 #include "net/cert/x509_util.h"
     19 #include "url/gurl.h"
     20 
     21 namespace content {
     22 
     23 struct WebRTCIdentityRequestResult {
     24   WebRTCIdentityRequestResult(int error,
     25                               const std::string& certificate,
     26                               const std::string& private_key)
     27       : error(error), certificate(certificate), private_key(private_key) {}
     28 
     29   int error;
     30   std::string certificate;
     31   std::string private_key;
     32 };
     33 
     34 // Generates a new identity using |common_name| which expires after
     35 // |validity_period| and returns the result in |result|.
     36 static void GenerateIdentityWorker(const std::string& common_name,
     37                                    base::TimeDelta validity_period,
     38                                    WebRTCIdentityRequestResult* result) {
     39   result->error = net::OK;
     40   int serial_number = base::RandInt(0, std::numeric_limits<int>::max());
     41 
     42   scoped_ptr<crypto::RSAPrivateKey> key;
     43   base::Time now = base::Time::Now();
     44   bool success = net::x509_util::CreateKeyAndSelfSignedCert(
     45       "CN=" + common_name,
     46       serial_number,
     47       now,
     48       now + validity_period,
     49       &key,
     50       &result->certificate);
     51 
     52   if (!success) {
     53     DLOG(ERROR) << "Unable to create x509 cert for client";
     54     result->error = net::ERR_SELF_SIGNED_CERT_GENERATION_FAILED;
     55     return;
     56   }
     57 
     58   std::vector<uint8> private_key_info;
     59   if (!key->ExportPrivateKey(&private_key_info)) {
     60     DLOG(ERROR) << "Unable to export private key";
     61     result->error = net::ERR_PRIVATE_KEY_EXPORT_FAILED;
     62     return;
     63   }
     64 
     65   result->private_key =
     66       std::string(private_key_info.begin(), private_key_info.end());
     67 }
     68 
     69 class WebRTCIdentityRequestHandle;
     70 
     71 // The class represents an identity request internal to WebRTCIdentityStore.
     72 // It has a one-to-many mapping to the external version of the request,
     73 // WebRTCIdentityRequestHandle, i.e. multiple identical external requests are
     74 // combined into one internal request.
     75 // It's deleted automatically when the request is completed.
     76 class WebRTCIdentityRequest {
     77  public:
     78   WebRTCIdentityRequest(const GURL& origin,
     79                         const std::string& identity_name,
     80                         const std::string& common_name)
     81       : origin_(origin),
     82         identity_name_(identity_name),
     83         common_name_(common_name) {}
     84 
     85   void Cancel(WebRTCIdentityRequestHandle* handle) {
     86     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
     87     if (callbacks_.find(handle) == callbacks_.end())
     88       return;
     89     callbacks_.erase(handle);
     90   }
     91 
     92  private:
     93   friend class WebRTCIdentityStore;
     94 
     95   void AddCallback(WebRTCIdentityRequestHandle* handle,
     96                    const WebRTCIdentityStore::CompletionCallback& callback) {
     97     DCHECK(callbacks_.find(handle) == callbacks_.end());
     98     callbacks_[handle] = callback;
     99   }
    100 
    101   // This method deletes "this" and no one should access it after the request
    102   // completes.
    103   // We do not use base::Owned to tie its lifetime to the callback for
    104   // WebRTCIdentityStoreBackend::FindIdentity, because it needs to live longer
    105   // than that if the identity does not exist in DB.
    106   void Post(const WebRTCIdentityRequestResult& result) {
    107     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    108     for (CallbackMap::iterator it = callbacks_.begin(); it != callbacks_.end();
    109          ++it)
    110       it->second.Run(result.error, result.certificate, result.private_key);
    111     delete this;
    112   }
    113 
    114   GURL origin_;
    115   std::string identity_name_;
    116   std::string common_name_;
    117   typedef std::map<WebRTCIdentityRequestHandle*,
    118                    WebRTCIdentityStore::CompletionCallback> CallbackMap;
    119   CallbackMap callbacks_;
    120 };
    121 
    122 // The class represents an identity request which calls back to the external
    123 // client when the request completes.
    124 // Its lifetime is tied with the Callback held by the corresponding
    125 // WebRTCIdentityRequest.
    126 class WebRTCIdentityRequestHandle {
    127  public:
    128   WebRTCIdentityRequestHandle(
    129       WebRTCIdentityStore* store,
    130       const WebRTCIdentityStore::CompletionCallback& callback)
    131       : store_(store), request_(NULL), callback_(callback) {}
    132 
    133  private:
    134   friend class WebRTCIdentityStore;
    135 
    136   // Cancel the request.  Does nothing if the request finished or was already
    137   // cancelled.
    138   void Cancel() {
    139     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    140     if (!request_)
    141       return;
    142 
    143     callback_.Reset();
    144     WebRTCIdentityRequest* request = request_;
    145     request_ = NULL;
    146     // "this" will be deleted after the following call, because "this" is
    147     // owned by the Callback held by |request|.
    148     request->Cancel(this);
    149   }
    150 
    151   void OnRequestStarted(WebRTCIdentityRequest* request) {
    152     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    153     DCHECK(request);
    154     request_ = request;
    155   }
    156 
    157   void OnRequestComplete(int error,
    158                          const std::string& certificate,
    159                          const std::string& private_key) {
    160     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    161     DCHECK(request_);
    162     request_ = NULL;
    163     base::ResetAndReturn(&callback_).Run(error, certificate, private_key);
    164   }
    165 
    166   WebRTCIdentityStore* store_;
    167   WebRTCIdentityRequest* request_;
    168   WebRTCIdentityStore::CompletionCallback callback_;
    169 
    170   DISALLOW_COPY_AND_ASSIGN(WebRTCIdentityRequestHandle);
    171 };
    172 
    173 WebRTCIdentityStore::WebRTCIdentityStore(const base::FilePath& path,
    174                                          quota::SpecialStoragePolicy* policy)
    175     : validity_period_(base::TimeDelta::FromDays(30)),
    176       task_runner_(base::WorkerPool::GetTaskRunner(true)),
    177       backend_(new WebRTCIdentityStoreBackend(path, policy, validity_period_)) {
    178   }
    179 
    180 WebRTCIdentityStore::~WebRTCIdentityStore() { backend_->Close(); }
    181 
    182 base::Closure WebRTCIdentityStore::RequestIdentity(
    183     const GURL& origin,
    184     const std::string& identity_name,
    185     const std::string& common_name,
    186     const CompletionCallback& callback) {
    187   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    188   WebRTCIdentityRequest* request =
    189       FindRequest(origin, identity_name, common_name);
    190   // If there is no identical request in flight, create a new one, queue it,
    191   // and make the backend request.
    192   if (!request) {
    193     request = new WebRTCIdentityRequest(origin, identity_name, common_name);
    194     // |request| will delete itself after the result is posted.
    195     if (!backend_->FindIdentity(
    196             origin,
    197             identity_name,
    198             common_name,
    199             base::Bind(
    200                 &WebRTCIdentityStore::BackendFindCallback, this, request))) {
    201       // Bail out if the backend failed to start the task.
    202       delete request;
    203       return base::Closure();
    204     }
    205     in_flight_requests_.push_back(request);
    206   }
    207 
    208   WebRTCIdentityRequestHandle* handle =
    209     new WebRTCIdentityRequestHandle(this, callback);
    210 
    211   request->AddCallback(
    212       handle,
    213       base::Bind(&WebRTCIdentityRequestHandle::OnRequestComplete,
    214                  base::Owned(handle)));
    215   handle->OnRequestStarted(request);
    216   return base::Bind(&WebRTCIdentityRequestHandle::Cancel,
    217                     base::Unretained(handle));
    218 }
    219 
    220 void WebRTCIdentityStore::DeleteBetween(base::Time delete_begin,
    221                                         base::Time delete_end,
    222                                         const base::Closure& callback) {
    223   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    224   backend_->DeleteBetween(delete_begin, delete_end, callback);
    225 }
    226 
    227 void WebRTCIdentityStore::SetValidityPeriodForTesting(
    228     base::TimeDelta validity_period) {
    229   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    230   validity_period_ = validity_period;
    231   backend_->SetValidityPeriodForTesting(validity_period);
    232 }
    233 
    234 void WebRTCIdentityStore::SetTaskRunnerForTesting(
    235     const scoped_refptr<base::TaskRunner>& task_runner) {
    236   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    237   task_runner_ = task_runner;
    238 }
    239 
    240 void WebRTCIdentityStore::BackendFindCallback(WebRTCIdentityRequest* request,
    241                                               int error,
    242                                               const std::string& certificate,
    243                                               const std::string& private_key) {
    244   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    245   if (error == net::OK) {
    246     DVLOG(2) << "Identity found in DB.";
    247     WebRTCIdentityRequestResult result(error, certificate, private_key);
    248     PostRequestResult(request, result);
    249     return;
    250   }
    251   // Generate a new identity if not found in the DB.
    252   WebRTCIdentityRequestResult* result =
    253       new WebRTCIdentityRequestResult(0, "", "");
    254   if (!task_runner_->PostTaskAndReply(
    255            FROM_HERE,
    256            base::Bind(&GenerateIdentityWorker,
    257                       request->common_name_,
    258                       validity_period_,
    259                       result),
    260            base::Bind(&WebRTCIdentityStore::GenerateIdentityCallback,
    261                       this,
    262                       request,
    263                       base::Owned(result)))) {
    264     // Completes the request with error if failed to post the task.
    265     WebRTCIdentityRequestResult result(net::ERR_UNEXPECTED, "", "");
    266     PostRequestResult(request, result);
    267   }
    268 }
    269 
    270 void WebRTCIdentityStore::GenerateIdentityCallback(
    271     WebRTCIdentityRequest* request,
    272     WebRTCIdentityRequestResult* result) {
    273   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    274   if (result->error == net::OK) {
    275     DVLOG(2) << "New identity generated and added to the backend.";
    276     backend_->AddIdentity(request->origin_,
    277                           request->identity_name_,
    278                           request->common_name_,
    279                           result->certificate,
    280                           result->private_key);
    281   }
    282   PostRequestResult(request, *result);
    283 }
    284 
    285 void WebRTCIdentityStore::PostRequestResult(
    286     WebRTCIdentityRequest* request,
    287     const WebRTCIdentityRequestResult& result) {
    288   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    289   // Removes the in flight request from the queue.
    290   for (size_t i = 0; i < in_flight_requests_.size(); ++i) {
    291     if (in_flight_requests_[i] == request) {
    292       in_flight_requests_.erase(in_flight_requests_.begin() + i);
    293       break;
    294     }
    295   }
    296   // |request| will be deleted after this call.
    297   request->Post(result);
    298 }
    299 
    300 // Find an identical request from the in flight requests.
    301 WebRTCIdentityRequest* WebRTCIdentityStore::FindRequest(
    302     const GURL& origin,
    303     const std::string& identity_name,
    304     const std::string& common_name) {
    305   for (size_t i = 0; i < in_flight_requests_.size(); ++i) {
    306     if (in_flight_requests_[i]->origin_ == origin &&
    307         in_flight_requests_[i]->identity_name_ == identity_name &&
    308         in_flight_requests_[i]->common_name_ == common_name) {
    309       return in_flight_requests_[i];
    310     }
    311   }
    312   return NULL;
    313 }
    314 
    315 }  // namespace content
    316