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 {
WebRTCIdentityRequestResultcontent::WebRTCIdentityRequestResult24 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|.
GenerateIdentityWorker(const std::string & common_name,base::TimeDelta validity_period,WebRTCIdentityRequestResult * 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:
WebRTCIdentityRequest(const GURL & origin,const std::string & identity_name,const std::string & common_name)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
Cancel(WebRTCIdentityRequestHandle * handle)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
AddCallback(WebRTCIdentityRequestHandle * handle,const WebRTCIdentityStore::CompletionCallback & callback)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.
Post(const WebRTCIdentityRequestResult & result)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:
WebRTCIdentityRequestHandle(WebRTCIdentityStore * store,const WebRTCIdentityStore::CompletionCallback & callback)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.
Cancel()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
OnRequestStarted(WebRTCIdentityRequest * request)151 void OnRequestStarted(WebRTCIdentityRequest* request) {
152 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
153 DCHECK(request);
154 request_ = request;
155 }
156
OnRequestComplete(int error,const std::string & certificate,const std::string & private_key)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
WebRTCIdentityStore(const base::FilePath & path,quota::SpecialStoragePolicy * policy)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
~WebRTCIdentityStore()180 WebRTCIdentityStore::~WebRTCIdentityStore() { backend_->Close(); }
181
RequestIdentity(const GURL & origin,const std::string & identity_name,const std::string & common_name,const CompletionCallback & callback)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
DeleteBetween(base::Time delete_begin,base::Time delete_end,const base::Closure & callback)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
SetValidityPeriodForTesting(base::TimeDelta validity_period)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
SetTaskRunnerForTesting(const scoped_refptr<base::TaskRunner> & task_runner)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
BackendFindCallback(WebRTCIdentityRequest * request,int error,const std::string & certificate,const std::string & private_key)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
GenerateIdentityCallback(WebRTCIdentityRequest * request,WebRTCIdentityRequestResult * result)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
PostRequestResult(WebRTCIdentityRequest * request,const WebRTCIdentityRequestResult & result)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.
FindRequest(const GURL & origin,const std::string & identity_name,const std::string & common_name)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