Add ENABLE_MEDIA_ROUTER define to builds other than Android and iOS.
[chromium-blink-merge.git] / chrome / browser / local_discovery / privetv3_session.cc
blobf5c46e2986680542f86399ecd0909429bde78d00
1 // Copyright 2014 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.
5 #include "chrome/browser/local_discovery/privetv3_session.h"
7 #include "base/base64.h"
8 #include "base/json/json_writer.h"
9 #include "base/logging.h"
10 #include "base/message_loop/message_loop.h"
11 #include "chrome/browser/local_discovery/privet_constants.h"
12 #include "chrome/browser/local_discovery/privet_http.h"
13 #include "chrome/browser/local_discovery/privet_url_fetcher.h"
14 #include "chrome/common/cloud_print/cloud_print_constants.h"
15 #include "crypto/hmac.h"
16 #include "crypto/p224_spake.h"
17 #include "url/gurl.h"
19 namespace local_discovery {
21 namespace {
23 const char kPrivetV3AuthAnonymous[] = "Privet anonymous";
24 const char kPrivetV3CryptoP224Spake2[] = "p224_spake2";
25 const char kPrivetV3Auto[] = "auto";
27 const char kPrivetV3InfoKeyAuth[] = "authentication";
28 const char kPrivetV3InfoKeyVersion[] = "version";
29 const char kPrivetV3InfoVersion[] = "3.0";
31 const char kPrivetV3KeyAccessToken[] = "accessToken";
32 const char kPrivetV3KeyAuthCode[] = "authCode";
33 const char kPrivetV3KeyCertFingerprint[] = "certFingerprint";
34 const char kPrivetV3KeyCertSignature[] = "certSignature";
35 const char kPrivetV3KeyClientCommitment[] = "clientCommitment";
36 const char kPrivetV3KeyCrypto[] = "crypto";
37 const char kPrivetV3KeyDeviceCommitment[] = "deviceCommitment";
38 const char kPrivetV3KeyMode[] = "mode";
39 const char kPrivetV3KeyPairing[] = "pairing";
40 const char kPrivetV3KeyRequestedScope[] = "requestedScope";
41 const char kPrivetV3KeyScope[] = "scope";
42 const char kPrivetV3KeySessionId[] = "sessionId";
43 const char kPrivetV3KeyTokenType[] = "tokenType";
45 const char kPrivetV3PairingStartPath[] = "/privet/v3/pairing/start";
46 const char kPrivetV3PairingConfirmPath[] = "/privet/v3/pairing/confirm";
47 const char kPrivetV3PairingCancelPath[] = "/privet/v3/pairing/cancel";
48 const char kPrivetV3AuthPath[] = "/privet/v3/auth";
50 const char kUrlPlaceHolder[] = "http://host/";
52 const int kUrlFetcherTimeoutSec = 30;
54 GURL CreatePrivetURL(const std::string& path) {
55 GURL url(kUrlPlaceHolder);
56 GURL::Replacements replacements;
57 replacements.SetPathStr(path);
58 return url.ReplaceComponents(replacements);
61 template <typename T>
62 class EnumToStringMap {
63 public:
64 static std::string FindNameById(T id) {
65 for (const Element& m : kMap) {
66 if (m.id == id) {
67 DCHECK(m.name);
68 return m.name;
71 NOTREACHED();
72 return std::string();
75 static bool FindIdByName(const std::string& name, T* id) {
76 for (const Element& m : kMap) {
77 if (m.name && m.name == name) {
78 *id = m.id;
79 return true;
82 return false;
85 private:
86 struct Element {
87 const T id;
88 const char* const name;
90 static const Element kMap[];
93 using PairingType = PrivetV3Session::PairingType;
95 template <>
96 const EnumToStringMap<PrivetV3Session::PairingType>::Element
97 EnumToStringMap<PrivetV3Session::PairingType>::kMap[] = {
98 {PairingType::PAIRING_TYPE_PINCODE, "pinCode"},
99 {PairingType::PAIRING_TYPE_EMBEDDEDCODE, "embeddedCode"},
100 {PairingType::PAIRING_TYPE_ULTRASOUND32, "ultrasound32"},
101 {PairingType::PAIRING_TYPE_AUDIBLE32, "audible32"},
104 template <typename T>
105 std::string EnumToString(T id) {
106 return EnumToStringMap<T>::FindNameById(id);
109 template <typename T>
110 bool StringToEnum(const std::string& name, T* id) {
111 return EnumToStringMap<T>::FindIdByName(name, id);
114 bool GetDecodedString(const base::DictionaryValue& response,
115 const std::string& key,
116 std::string* value) {
117 std::string base64;
118 return response.GetString(key, &base64) && base::Base64Decode(base64, value);
121 bool ContainsString(const base::DictionaryValue& dictionary,
122 const std::string& key,
123 const std::string& expected_value) {
124 const base::ListValue* list_of_string = nullptr;
125 if (!dictionary.GetList(key, &list_of_string))
126 return false;
128 for (const base::Value* value : *list_of_string) {
129 std::string string_value;
130 if (value->GetAsString(&string_value) && string_value == expected_value)
131 return true;
133 return false;
136 } // namespace
138 class PrivetV3Session::FetcherDelegate : public PrivetURLFetcher::Delegate {
139 public:
140 FetcherDelegate(const base::WeakPtr<PrivetV3Session>& session,
141 const std::string& auth_token,
142 const PrivetV3Session::MessageCallback& callback);
143 ~FetcherDelegate() override;
145 // PrivetURLFetcher::Delegate methods.
146 std::string GetAuthToken() override;
147 void OnNeedPrivetToken(
148 PrivetURLFetcher* fetcher,
149 const PrivetURLFetcher::TokenCallback& callback) override;
150 void OnError(PrivetURLFetcher* fetcher,
151 PrivetURLFetcher::ErrorType error) override;
152 void OnParsedJson(PrivetURLFetcher* fetcher,
153 const base::DictionaryValue& value,
154 bool has_error) override;
156 PrivetURLFetcher* CreateURLFetcher(const GURL& url,
157 net::URLFetcher::RequestType request_type,
158 bool orphaned);
160 private:
161 void ReplyAndDestroyItself(Result result, const base::DictionaryValue& value);
162 void OnTimeout();
164 scoped_ptr<PrivetURLFetcher> url_fetcher_;
166 base::WeakPtr<PrivetV3Session> session_;
167 std::string auth_token_;
168 MessageCallback callback_;
170 base::WeakPtrFactory<FetcherDelegate> weak_ptr_factory_;
173 PrivetV3Session::FetcherDelegate::FetcherDelegate(
174 const base::WeakPtr<PrivetV3Session>& session,
175 const std::string& auth_token,
176 const PrivetV3Session::MessageCallback& callback)
177 : session_(session),
178 auth_token_(auth_token),
179 callback_(callback),
180 weak_ptr_factory_(this) {
183 PrivetV3Session::FetcherDelegate::~FetcherDelegate() {
186 std::string PrivetV3Session::FetcherDelegate::GetAuthToken() {
187 return auth_token_;
190 void PrivetV3Session::FetcherDelegate::OnNeedPrivetToken(
191 PrivetURLFetcher* fetcher,
192 const PrivetURLFetcher::TokenCallback& callback) {
193 NOTREACHED();
194 OnError(fetcher, PrivetURLFetcher::URL_FETCH_ERROR);
197 void PrivetV3Session::FetcherDelegate::OnError(
198 PrivetURLFetcher* fetcher,
199 PrivetURLFetcher::ErrorType error) {
200 LOG(ERROR) << "PrivetURLFetcher url: " << fetcher->url()
201 << ", error: " << error
202 << ", response code: " << fetcher->response_code();
203 ReplyAndDestroyItself(Result::STATUS_CONNECTIONERROR,
204 base::DictionaryValue());
207 void PrivetV3Session::FetcherDelegate::OnParsedJson(
208 PrivetURLFetcher* fetcher,
209 const base::DictionaryValue& value,
210 bool has_error) {
211 LOG_IF(ERROR, has_error) << "Response: " << value;
212 ReplyAndDestroyItself(
213 has_error ? Result::STATUS_DEVICEERROR : Result::STATUS_SUCCESS, value);
216 PrivetURLFetcher* PrivetV3Session::FetcherDelegate::CreateURLFetcher(
217 const GURL& url,
218 net::URLFetcher::RequestType request_type,
219 bool orphaned) {
220 DCHECK(!url_fetcher_);
221 url_fetcher_ =
222 session_->client_->CreateURLFetcher(url, request_type, this).Pass();
223 url_fetcher_->V3Mode();
225 auto timeout_task =
226 orphaned ? base::Bind(&FetcherDelegate::OnTimeout, base::Owned(this))
227 : base::Bind(&FetcherDelegate::OnTimeout,
228 weak_ptr_factory_.GetWeakPtr());
229 base::MessageLoop::current()->PostDelayedTask(
230 FROM_HERE, timeout_task,
231 base::TimeDelta::FromSeconds(kUrlFetcherTimeoutSec));
232 return url_fetcher_.get();
235 void PrivetV3Session::FetcherDelegate::ReplyAndDestroyItself(
236 Result result,
237 const base::DictionaryValue& value) {
238 if (session_) {
239 if (!callback_.is_null()) {
240 callback_.Run(result, value);
241 callback_.Reset();
243 base::MessageLoop::current()->PostTask(
244 FROM_HERE, base::Bind(&PrivetV3Session::DeleteFetcher, session_,
245 base::Unretained(this)));
246 session_.reset();
250 void PrivetV3Session::FetcherDelegate::OnTimeout() {
251 LOG(ERROR) << "PrivetURLFetcher timeout, url: " << url_fetcher_->url();
252 ReplyAndDestroyItself(Result::STATUS_CONNECTIONERROR,
253 base::DictionaryValue());
256 PrivetV3Session::PrivetV3Session(scoped_ptr<PrivetHTTPClient> client)
257 : client_(client.Pass()), weak_ptr_factory_(this) {
260 PrivetV3Session::~PrivetV3Session() {
261 Cancel();
264 void PrivetV3Session::Init(const InitCallback& callback) {
265 DCHECK(fetchers_.empty());
266 DCHECK(fingerprint_.empty());
267 DCHECK(session_id_.empty());
268 DCHECK(privet_auth_token_.empty());
270 privet_auth_token_ = kPrivetV3AuthAnonymous;
272 StartGetRequest(kPrivetInfoPath,
273 base::Bind(&PrivetV3Session::OnInfoDone,
274 weak_ptr_factory_.GetWeakPtr(), callback));
277 void PrivetV3Session::OnInfoDone(const InitCallback& callback,
278 Result result,
279 const base::DictionaryValue& response) {
280 std::vector<PairingType> pairing_types;
281 if (result != Result::STATUS_SUCCESS)
282 return callback.Run(result, pairing_types);
284 std::string version;
285 if (!response.GetString(kPrivetV3InfoKeyVersion, &version) ||
286 version != kPrivetV3InfoVersion) {
287 LOG(ERROR) << "Response: " << response;
288 return callback.Run(Result::STATUS_SESSIONERROR, pairing_types);
291 const base::DictionaryValue* authentication = nullptr;
292 const base::ListValue* pairing = nullptr;
293 if (!response.GetDictionary(kPrivetV3InfoKeyAuth, &authentication) ||
294 !authentication->GetList(kPrivetV3KeyPairing, &pairing)) {
295 LOG(ERROR) << "Response: " << response;
296 return callback.Run(Result::STATUS_SESSIONERROR, pairing_types);
299 // The only supported crypto.
300 if (!ContainsString(*authentication, kPrivetV3KeyCrypto,
301 kPrivetV3CryptoP224Spake2) ||
302 !ContainsString(*authentication, kPrivetV3KeyMode, kPrivetV3KeyPairing)) {
303 LOG(ERROR) << "Response: " << response;
304 return callback.Run(Result::STATUS_SESSIONERROR, pairing_types);
307 for (const base::Value* value : *pairing) {
308 std::string pairing_string;
309 PairingType pairing_type;
310 if (!value->GetAsString(&pairing_string) ||
311 !StringToEnum(pairing_string, &pairing_type)) {
312 continue; // Skip unknown pairing.
314 pairing_types.push_back(pairing_type);
317 callback.Run(Result::STATUS_SUCCESS, pairing_types);
320 void PrivetV3Session::StartPairing(PairingType pairing_type,
321 const ResultCallback& callback) {
322 base::DictionaryValue input;
323 input.SetString(kPrivetV3KeyPairing, EnumToString(pairing_type));
324 input.SetString(kPrivetV3KeyCrypto, kPrivetV3CryptoP224Spake2);
326 StartPostRequest(kPrivetV3PairingStartPath, input,
327 base::Bind(&PrivetV3Session::OnPairingStartDone,
328 weak_ptr_factory_.GetWeakPtr(), callback));
331 void PrivetV3Session::OnPairingStartDone(
332 const ResultCallback& callback,
333 Result result,
334 const base::DictionaryValue& response) {
335 if (result != Result::STATUS_SUCCESS)
336 return callback.Run(result);
338 if (!response.GetString(kPrivetV3KeySessionId, &session_id_) ||
339 !GetDecodedString(response, kPrivetV3KeyDeviceCommitment, &commitment_)) {
340 LOG(ERROR) << "Response: " << response;
341 return callback.Run(Result::STATUS_SESSIONERROR);
344 return callback.Run(Result::STATUS_SUCCESS);
347 void PrivetV3Session::ConfirmCode(const std::string& code,
348 const ResultCallback& callback) {
349 if (session_id_.empty()) {
350 LOG(ERROR) << "Pairing is not started";
351 return callback.Run(Result::STATUS_SESSIONERROR);
354 spake_.reset(new crypto::P224EncryptedKeyExchange(
355 crypto::P224EncryptedKeyExchange::kPeerTypeClient, code));
357 base::DictionaryValue input;
358 input.SetString(kPrivetV3KeySessionId, session_id_);
360 std::string client_commitment;
361 base::Base64Encode(spake_->GetNextMessage(), &client_commitment);
362 input.SetString(kPrivetV3KeyClientCommitment, client_commitment);
364 // Call ProcessMessage after GetNextMessage().
365 crypto::P224EncryptedKeyExchange::Result result =
366 spake_->ProcessMessage(commitment_);
367 DCHECK_EQ(result, crypto::P224EncryptedKeyExchange::kResultPending);
369 StartPostRequest(kPrivetV3PairingConfirmPath, input,
370 base::Bind(&PrivetV3Session::OnPairingConfirmDone,
371 weak_ptr_factory_.GetWeakPtr(), callback));
374 void PrivetV3Session::OnPairingConfirmDone(
375 const ResultCallback& callback,
376 Result result,
377 const base::DictionaryValue& response) {
378 if (result != Result::STATUS_SUCCESS)
379 return callback.Run(result);
381 std::string fingerprint;
382 std::string signature;
383 if (!GetDecodedString(response, kPrivetV3KeyCertFingerprint, &fingerprint) ||
384 !GetDecodedString(response, kPrivetV3KeyCertSignature, &signature)) {
385 LOG(ERROR) << "Response: " << response;
386 return callback.Run(Result::STATUS_SESSIONERROR);
389 crypto::HMAC hmac(crypto::HMAC::SHA256);
390 // Key will be verified below, using HMAC.
391 const std::string& key = spake_->GetUnverifiedKey();
392 if (!hmac.Init(reinterpret_cast<const unsigned char*>(key.c_str()),
393 key.size()) ||
394 !hmac.Verify(fingerprint, signature)) {
395 LOG(ERROR) << "Response: " << response;
396 return callback.Run(Result::STATUS_SESSIONERROR);
399 std::string auth_code(hmac.DigestLength(), ' ');
400 if (!hmac.Sign(session_id_,
401 reinterpret_cast<unsigned char*>(string_as_array(&auth_code)),
402 auth_code.size())) {
403 LOG(FATAL) << "Signing failed";
404 return callback.Run(Result::STATUS_SESSIONERROR);
406 // From now this is expected certificate.
407 fingerprint_ = fingerprint;
409 std::string auth_code_base64;
410 base::Base64Encode(auth_code, &auth_code_base64);
412 base::DictionaryValue input;
413 input.SetString(kPrivetV3KeyAuthCode, auth_code_base64);
414 input.SetString(kPrivetV3KeyMode, kPrivetV3KeyPairing);
415 input.SetString(kPrivetV3KeyRequestedScope, kPrivetV3Auto);
417 // Now we can use SendMessage with certificate validation.
418 SendMessage(kPrivetV3AuthPath, input,
419 base::Bind(&PrivetV3Session::OnAuthenticateDone,
420 weak_ptr_factory_.GetWeakPtr(), callback));
423 void PrivetV3Session::OnAuthenticateDone(
424 const ResultCallback& callback,
425 Result result,
426 const base::DictionaryValue& response) {
427 if (result != Result::STATUS_SUCCESS)
428 return callback.Run(result);
430 std::string access_token;
431 std::string token_type;
432 std::string scope;
433 if (!response.GetString(kPrivetV3KeyAccessToken, &access_token) ||
434 !response.GetString(kPrivetV3KeyTokenType, &token_type) ||
435 !response.GetString(kPrivetV3KeyScope, &scope)) {
436 LOG(ERROR) << "Response: " << response;
437 return callback.Run(Result::STATUS_SESSIONERROR);
440 privet_auth_token_ = token_type + " " + access_token;
442 return callback.Run(Result::STATUS_SUCCESS);
445 void PrivetV3Session::SendMessage(const std::string& api,
446 const base::DictionaryValue& input,
447 const MessageCallback& callback) {
448 // TODO(vitalybuka): Implement validating HTTPS certificate using
449 // fingerprint_.
450 if (fingerprint_.empty()) {
451 LOG(ERROR) << "Session is not paired";
452 return callback.Run(Result::STATUS_SESSIONERROR, base::DictionaryValue());
455 StartPostRequest(api, input, callback);
458 void PrivetV3Session::RunCallback(const base::Closure& callback) {
459 callback.Run();
462 void PrivetV3Session::StartGetRequest(const std::string& api,
463 const MessageCallback& callback) {
464 CreateFetcher(api, net::URLFetcher::RequestType::GET, callback)->Start();
467 void PrivetV3Session::StartPostRequest(const std::string& api,
468 const base::DictionaryValue& input,
469 const MessageCallback& callback) {
470 if (!on_post_data_.is_null())
471 on_post_data_.Run(input);
472 std::string json;
473 base::JSONWriter::WriteWithOptions(
474 &input, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json);
475 PrivetURLFetcher* fetcher =
476 CreateFetcher(api, net::URLFetcher::RequestType::POST, callback);
477 fetcher->SetUploadData(cloud_print::kContentTypeJSON, json);
478 fetcher->Start();
481 PrivetURLFetcher* PrivetV3Session::CreateFetcher(
482 const std::string& api,
483 net::URLFetcher::RequestType request_type,
484 const MessageCallback& callback) {
485 // Don't abort cancel requests after session object is destroyed.
486 const bool orphaned = (api == kPrivetV3PairingCancelPath);
487 FetcherDelegate* fetcher = new FetcherDelegate(weak_ptr_factory_.GetWeakPtr(),
488 privet_auth_token_, callback);
489 if (!orphaned)
490 fetchers_.push_back(fetcher);
491 return fetcher->CreateURLFetcher(CreatePrivetURL(api), request_type,
492 orphaned);
495 void PrivetV3Session::DeleteFetcher(const FetcherDelegate* fetcher) {
496 fetchers_.erase(std::find(fetchers_.begin(), fetchers_.end(), fetcher));
499 void PrivetV3Session::Cancel() {
500 // Cancel started unconfirmed sessions.
501 if (session_id_.empty() || !fingerprint_.empty())
502 return;
503 base::DictionaryValue input;
504 input.SetString(kPrivetV3KeySessionId, session_id_);
505 StartPostRequest(kPrivetV3PairingCancelPath, input, MessageCallback());
508 } // namespace local_discovery