Add ICU message format support
[chromium-blink-merge.git] / chrome / browser / local_discovery / privetv3_session.cc
blob2f70be59a6f7002c6adcdabad3e09aec5343c36d
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/location.h"
10 #include "base/logging.h"
11 #include "base/single_thread_task_runner.h"
12 #include "base/thread_task_runner_handle.h"
13 #include "chrome/browser/local_discovery/privet_constants.h"
14 #include "chrome/browser/local_discovery/privet_http.h"
15 #include "chrome/browser/local_discovery/privet_url_fetcher.h"
16 #include "chrome/common/cloud_print/cloud_print_constants.h"
17 #include "crypto/hmac.h"
18 #include "crypto/p224_spake.h"
19 #include "url/gurl.h"
21 namespace local_discovery {
23 namespace {
25 const char kPrivetV3AuthAnonymous[] = "Privet anonymous";
26 const char kPrivetV3CryptoP224Spake2[] = "p224_spake2";
27 const char kPrivetV3Auto[] = "auto";
29 const char kPrivetV3InfoKeyAuth[] = "authentication";
30 const char kPrivetV3InfoKeyVersion[] = "version";
31 const char kPrivetV3InfoVersion[] = "3.0";
33 const char kPrivetV3KeyAccessToken[] = "accessToken";
34 const char kPrivetV3KeyAuthCode[] = "authCode";
35 const char kPrivetV3KeyCertFingerprint[] = "certFingerprint";
36 const char kPrivetV3KeyCertSignature[] = "certSignature";
37 const char kPrivetV3KeyClientCommitment[] = "clientCommitment";
38 const char kPrivetV3KeyCrypto[] = "crypto";
39 const char kPrivetV3KeyDeviceCommitment[] = "deviceCommitment";
40 const char kPrivetV3KeyMode[] = "mode";
41 const char kPrivetV3KeyPairing[] = "pairing";
42 const char kPrivetV3KeyRequestedScope[] = "requestedScope";
43 const char kPrivetV3KeyScope[] = "scope";
44 const char kPrivetV3KeySessionId[] = "sessionId";
45 const char kPrivetV3KeyTokenType[] = "tokenType";
47 const char kPrivetV3PairingStartPath[] = "/privet/v3/pairing/start";
48 const char kPrivetV3PairingConfirmPath[] = "/privet/v3/pairing/confirm";
49 const char kPrivetV3PairingCancelPath[] = "/privet/v3/pairing/cancel";
50 const char kPrivetV3AuthPath[] = "/privet/v3/auth";
52 const char kUrlPlaceHolder[] = "http://host/";
54 const int kUrlFetcherTimeoutSec = 30;
56 GURL CreatePrivetURL(const std::string& path) {
57 GURL url(kUrlPlaceHolder);
58 GURL::Replacements replacements;
59 replacements.SetPathStr(path);
60 return url.ReplaceComponents(replacements);
63 using PairingType = PrivetV3Session::PairingType;
65 bool GetDecodedString(const base::DictionaryValue& response,
66 const std::string& key,
67 std::string* value) {
68 std::string base64;
69 return response.GetString(key, &base64) && base::Base64Decode(base64, value);
72 bool ContainsString(const base::DictionaryValue& dictionary,
73 const std::string& key,
74 const std::string& expected_value) {
75 const base::ListValue* list_of_string = nullptr;
76 if (!dictionary.GetList(key, &list_of_string))
77 return false;
79 for (const base::Value* value : *list_of_string) {
80 std::string string_value;
81 if (value->GetAsString(&string_value) && string_value == expected_value)
82 return true;
84 return false;
87 } // namespace
89 class PrivetV3Session::FetcherDelegate : public PrivetURLFetcher::Delegate {
90 public:
91 FetcherDelegate(const base::WeakPtr<PrivetV3Session>& session,
92 const std::string& auth_token,
93 const PrivetV3Session::MessageCallback& callback);
94 ~FetcherDelegate() override;
96 // PrivetURLFetcher::Delegate methods.
97 std::string GetAuthToken() override;
98 void OnNeedPrivetToken(
99 PrivetURLFetcher* fetcher,
100 const PrivetURLFetcher::TokenCallback& callback) override;
101 void OnError(PrivetURLFetcher* fetcher,
102 PrivetURLFetcher::ErrorType error) override;
103 void OnParsedJson(PrivetURLFetcher* fetcher,
104 const base::DictionaryValue& value,
105 bool has_error) override;
107 PrivetURLFetcher* CreateURLFetcher(const GURL& url,
108 net::URLFetcher::RequestType request_type,
109 bool orphaned);
111 private:
112 void ReplyAndDestroyItself(Result result, const base::DictionaryValue& value);
113 void OnTimeout();
115 scoped_ptr<PrivetURLFetcher> url_fetcher_;
117 base::WeakPtr<PrivetV3Session> session_;
118 std::string auth_token_;
119 MessageCallback callback_;
121 base::WeakPtrFactory<FetcherDelegate> weak_ptr_factory_;
124 PrivetV3Session::FetcherDelegate::FetcherDelegate(
125 const base::WeakPtr<PrivetV3Session>& session,
126 const std::string& auth_token,
127 const PrivetV3Session::MessageCallback& callback)
128 : session_(session),
129 auth_token_(auth_token),
130 callback_(callback),
131 weak_ptr_factory_(this) {
134 PrivetV3Session::FetcherDelegate::~FetcherDelegate() {
137 std::string PrivetV3Session::FetcherDelegate::GetAuthToken() {
138 return auth_token_;
141 void PrivetV3Session::FetcherDelegate::OnNeedPrivetToken(
142 PrivetURLFetcher* fetcher,
143 const PrivetURLFetcher::TokenCallback& callback) {
144 NOTREACHED();
145 OnError(fetcher, PrivetURLFetcher::URL_FETCH_ERROR);
148 void PrivetV3Session::FetcherDelegate::OnError(
149 PrivetURLFetcher* fetcher,
150 PrivetURLFetcher::ErrorType error) {
151 LOG(ERROR) << "PrivetURLFetcher url: " << fetcher->url()
152 << ", error: " << error
153 << ", response code: " << fetcher->response_code();
154 ReplyAndDestroyItself(Result::STATUS_CONNECTIONERROR,
155 base::DictionaryValue());
158 void PrivetV3Session::FetcherDelegate::OnParsedJson(
159 PrivetURLFetcher* fetcher,
160 const base::DictionaryValue& value,
161 bool has_error) {
162 LOG_IF(ERROR, has_error) << "Response: " << value;
163 ReplyAndDestroyItself(
164 has_error ? Result::STATUS_DEVICEERROR : Result::STATUS_SUCCESS, value);
167 PrivetURLFetcher* PrivetV3Session::FetcherDelegate::CreateURLFetcher(
168 const GURL& url,
169 net::URLFetcher::RequestType request_type,
170 bool orphaned) {
171 DCHECK(!url_fetcher_);
172 url_fetcher_ =
173 session_->client_->CreateURLFetcher(url, request_type, this).Pass();
174 url_fetcher_->V3Mode();
176 auto timeout_task =
177 orphaned ? base::Bind(&FetcherDelegate::OnTimeout, base::Owned(this))
178 : base::Bind(&FetcherDelegate::OnTimeout,
179 weak_ptr_factory_.GetWeakPtr());
180 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
181 FROM_HERE, timeout_task,
182 base::TimeDelta::FromSeconds(kUrlFetcherTimeoutSec));
183 return url_fetcher_.get();
186 void PrivetV3Session::FetcherDelegate::ReplyAndDestroyItself(
187 Result result,
188 const base::DictionaryValue& value) {
189 if (session_) {
190 if (!callback_.is_null()) {
191 callback_.Run(result, value);
192 callback_.Reset();
194 base::ThreadTaskRunnerHandle::Get()->PostTask(
195 FROM_HERE, base::Bind(&PrivetV3Session::DeleteFetcher, session_,
196 base::Unretained(this)));
197 session_.reset();
201 void PrivetV3Session::FetcherDelegate::OnTimeout() {
202 LOG(ERROR) << "PrivetURLFetcher timeout, url: " << url_fetcher_->url();
203 ReplyAndDestroyItself(Result::STATUS_CONNECTIONERROR,
204 base::DictionaryValue());
207 PrivetV3Session::PrivetV3Session(scoped_ptr<PrivetHTTPClient> client)
208 : client_(client.Pass()), weak_ptr_factory_(this) {
211 PrivetV3Session::~PrivetV3Session() {
212 Cancel();
215 void PrivetV3Session::Init(const InitCallback& callback) {
216 DCHECK(fetchers_.empty());
217 DCHECK(fingerprint_.empty());
218 DCHECK(session_id_.empty());
219 DCHECK(privet_auth_token_.empty());
221 privet_auth_token_ = kPrivetV3AuthAnonymous;
223 StartGetRequest(kPrivetInfoPath,
224 base::Bind(&PrivetV3Session::OnInfoDone,
225 weak_ptr_factory_.GetWeakPtr(), callback));
228 void PrivetV3Session::OnInfoDone(const InitCallback& callback,
229 Result result,
230 const base::DictionaryValue& response) {
231 if (result != Result::STATUS_SUCCESS)
232 return callback.Run(result, response);
234 std::string version;
235 if (!response.GetString(kPrivetV3InfoKeyVersion, &version) ||
236 version != kPrivetV3InfoVersion) {
237 LOG(ERROR) << "Response: " << response;
238 return callback.Run(Result::STATUS_SESSIONERROR, response);
241 const base::DictionaryValue* authentication = nullptr;
242 const base::ListValue* pairing = nullptr;
243 if (!response.GetDictionary(kPrivetV3InfoKeyAuth, &authentication) ||
244 !authentication->GetList(kPrivetV3KeyPairing, &pairing)) {
245 LOG(ERROR) << "Response: " << response;
246 return callback.Run(Result::STATUS_SESSIONERROR, response);
249 // The only supported crypto.
250 if (!ContainsString(*authentication, kPrivetV3KeyCrypto,
251 kPrivetV3CryptoP224Spake2) ||
252 !ContainsString(*authentication, kPrivetV3KeyMode, kPrivetV3KeyPairing)) {
253 LOG(ERROR) << "Response: " << response;
254 return callback.Run(Result::STATUS_SESSIONERROR, response);
257 callback.Run(Result::STATUS_SUCCESS, response);
260 void PrivetV3Session::StartPairing(PairingType pairing_type,
261 const ResultCallback& callback) {
262 base::DictionaryValue input;
263 input.SetString(kPrivetV3KeyPairing,
264 extensions::api::gcd_private::ToString(pairing_type));
265 input.SetString(kPrivetV3KeyCrypto, kPrivetV3CryptoP224Spake2);
267 StartPostRequest(kPrivetV3PairingStartPath, input,
268 base::Bind(&PrivetV3Session::OnPairingStartDone,
269 weak_ptr_factory_.GetWeakPtr(), callback));
272 void PrivetV3Session::OnPairingStartDone(
273 const ResultCallback& callback,
274 Result result,
275 const base::DictionaryValue& response) {
276 if (result != Result::STATUS_SUCCESS)
277 return callback.Run(result);
279 if (!response.GetString(kPrivetV3KeySessionId, &session_id_) ||
280 !GetDecodedString(response, kPrivetV3KeyDeviceCommitment, &commitment_)) {
281 LOG(ERROR) << "Response: " << response;
282 return callback.Run(Result::STATUS_SESSIONERROR);
285 return callback.Run(Result::STATUS_SUCCESS);
288 void PrivetV3Session::ConfirmCode(const std::string& code,
289 const ResultCallback& callback) {
290 if (session_id_.empty()) {
291 LOG(ERROR) << "Pairing is not started";
292 return callback.Run(Result::STATUS_SESSIONERROR);
295 spake_.reset(new crypto::P224EncryptedKeyExchange(
296 crypto::P224EncryptedKeyExchange::kPeerTypeClient, code));
298 base::DictionaryValue input;
299 input.SetString(kPrivetV3KeySessionId, session_id_);
301 std::string client_commitment;
302 base::Base64Encode(spake_->GetNextMessage(), &client_commitment);
303 input.SetString(kPrivetV3KeyClientCommitment, client_commitment);
305 // Call ProcessMessage after GetNextMessage().
306 crypto::P224EncryptedKeyExchange::Result result =
307 spake_->ProcessMessage(commitment_);
308 DCHECK_EQ(result, crypto::P224EncryptedKeyExchange::kResultPending);
310 StartPostRequest(kPrivetV3PairingConfirmPath, input,
311 base::Bind(&PrivetV3Session::OnPairingConfirmDone,
312 weak_ptr_factory_.GetWeakPtr(), callback));
315 void PrivetV3Session::OnPairingConfirmDone(
316 const ResultCallback& callback,
317 Result result,
318 const base::DictionaryValue& response) {
319 if (result != Result::STATUS_SUCCESS)
320 return callback.Run(result);
322 std::string fingerprint;
323 std::string signature;
324 if (!GetDecodedString(response, kPrivetV3KeyCertFingerprint, &fingerprint) ||
325 !GetDecodedString(response, kPrivetV3KeyCertSignature, &signature)) {
326 LOG(ERROR) << "Response: " << response;
327 return callback.Run(Result::STATUS_SESSIONERROR);
330 crypto::HMAC hmac(crypto::HMAC::SHA256);
331 // Key will be verified below, using HMAC.
332 const std::string& key = spake_->GetUnverifiedKey();
333 if (!hmac.Init(reinterpret_cast<const unsigned char*>(key.c_str()),
334 key.size()) ||
335 !hmac.Verify(fingerprint, signature)) {
336 LOG(ERROR) << "Response: " << response;
337 return callback.Run(Result::STATUS_SESSIONERROR);
340 std::string auth_code(hmac.DigestLength(), ' ');
341 if (!hmac.Sign(session_id_,
342 reinterpret_cast<unsigned char*>(string_as_array(&auth_code)),
343 auth_code.size())) {
344 LOG(FATAL) << "Signing failed";
345 return callback.Run(Result::STATUS_SESSIONERROR);
347 // From now this is expected certificate.
348 fingerprint_ = fingerprint;
350 std::string auth_code_base64;
351 base::Base64Encode(auth_code, &auth_code_base64);
353 base::DictionaryValue input;
354 input.SetString(kPrivetV3KeyAuthCode, auth_code_base64);
355 input.SetString(kPrivetV3KeyMode, kPrivetV3KeyPairing);
356 input.SetString(kPrivetV3KeyRequestedScope, kPrivetV3Auto);
358 // Now we can use SendMessage with certificate validation.
359 SendMessage(kPrivetV3AuthPath, input,
360 base::Bind(&PrivetV3Session::OnAuthenticateDone,
361 weak_ptr_factory_.GetWeakPtr(), callback));
364 void PrivetV3Session::OnAuthenticateDone(
365 const ResultCallback& callback,
366 Result result,
367 const base::DictionaryValue& response) {
368 if (result != Result::STATUS_SUCCESS)
369 return callback.Run(result);
371 std::string access_token;
372 std::string token_type;
373 std::string scope;
374 if (!response.GetString(kPrivetV3KeyAccessToken, &access_token) ||
375 !response.GetString(kPrivetV3KeyTokenType, &token_type) ||
376 !response.GetString(kPrivetV3KeyScope, &scope)) {
377 LOG(ERROR) << "Response: " << response;
378 return callback.Run(Result::STATUS_SESSIONERROR);
381 privet_auth_token_ = token_type + " " + access_token;
383 return callback.Run(Result::STATUS_SUCCESS);
386 void PrivetV3Session::SendMessage(const std::string& api,
387 const base::DictionaryValue& input,
388 const MessageCallback& callback) {
389 // TODO(vitalybuka): Implement validating HTTPS certificate using
390 // fingerprint_.
391 if (fingerprint_.empty()) {
392 LOG(ERROR) << "Session is not paired";
393 return callback.Run(Result::STATUS_SESSIONERROR, base::DictionaryValue());
396 StartPostRequest(api, input, callback);
399 void PrivetV3Session::RunCallback(const base::Closure& callback) {
400 callback.Run();
403 void PrivetV3Session::StartGetRequest(const std::string& api,
404 const MessageCallback& callback) {
405 CreateFetcher(api, net::URLFetcher::RequestType::GET, callback)->Start();
408 void PrivetV3Session::StartPostRequest(const std::string& api,
409 const base::DictionaryValue& input,
410 const MessageCallback& callback) {
411 if (!on_post_data_.is_null())
412 on_post_data_.Run(input);
413 std::string json;
414 base::JSONWriter::WriteWithOptions(
415 input, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json);
416 PrivetURLFetcher* fetcher =
417 CreateFetcher(api, net::URLFetcher::RequestType::POST, callback);
418 fetcher->SetUploadData(cloud_print::kContentTypeJSON, json);
419 fetcher->Start();
422 PrivetURLFetcher* PrivetV3Session::CreateFetcher(
423 const std::string& api,
424 net::URLFetcher::RequestType request_type,
425 const MessageCallback& callback) {
426 // Don't abort cancel requests after session object is destroyed.
427 const bool orphaned = (api == kPrivetV3PairingCancelPath);
428 FetcherDelegate* fetcher = new FetcherDelegate(weak_ptr_factory_.GetWeakPtr(),
429 privet_auth_token_, callback);
430 if (!orphaned)
431 fetchers_.push_back(fetcher);
432 return fetcher->CreateURLFetcher(CreatePrivetURL(api), request_type,
433 orphaned);
436 void PrivetV3Session::DeleteFetcher(const FetcherDelegate* fetcher) {
437 fetchers_.erase(std::find(fetchers_.begin(), fetchers_.end(), fetcher));
440 void PrivetV3Session::Cancel() {
441 // Cancel started unconfirmed sessions.
442 if (session_id_.empty() || !fingerprint_.empty())
443 return;
444 base::DictionaryValue input;
445 input.SetString(kPrivetV3KeySessionId, session_id_);
446 StartPostRequest(kPrivetV3PairingCancelPath, input, MessageCallback());
449 } // namespace local_discovery