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