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"
21 namespace local_discovery
{
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
,
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
))
79 for (const base::Value
* value
: *list_of_string
) {
80 std::string string_value
;
81 if (value
->GetAsString(&string_value
) && string_value
== expected_value
)
89 class PrivetV3Session::FetcherDelegate
: public PrivetURLFetcher::Delegate
{
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
,
112 void ReplyAndDestroyItself(Result result
, const base::DictionaryValue
& value
);
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
)
129 auth_token_(auth_token
),
131 weak_ptr_factory_(this) {
134 PrivetV3Session::FetcherDelegate::~FetcherDelegate() {
137 std::string
PrivetV3Session::FetcherDelegate::GetAuthToken() {
141 void PrivetV3Session::FetcherDelegate::OnNeedPrivetToken(
142 PrivetURLFetcher
* fetcher
,
143 const PrivetURLFetcher::TokenCallback
& callback
) {
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
,
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(
169 net::URLFetcher::RequestType request_type
,
171 DCHECK(!url_fetcher_
);
173 session_
->client_
->CreateURLFetcher(url
, request_type
, this).Pass();
174 url_fetcher_
->V3Mode();
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(
188 const base::DictionaryValue
& value
) {
190 if (!callback_
.is_null()) {
191 callback_
.Run(result
, value
);
194 base::ThreadTaskRunnerHandle::Get()->PostTask(
195 FROM_HERE
, base::Bind(&PrivetV3Session::DeleteFetcher
, session_
,
196 base::Unretained(this)));
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() {
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
,
230 const base::DictionaryValue
& response
) {
231 if (result
!= Result::STATUS_SUCCESS
)
232 return callback
.Run(result
, response
);
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
,
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
,
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()),
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
)),
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
,
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
;
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
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
) {
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
);
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
);
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
);
431 fetchers_
.push_back(fetcher
);
432 return fetcher
->CreateURLFetcher(CreatePrivetURL(api
), request_type
,
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())
444 base::DictionaryValue input
;
445 input
.SetString(kPrivetV3KeySessionId
, session_id_
);
446 StartPostRequest(kPrivetV3PairingCancelPath
, input
, MessageCallback());
449 } // namespace local_discovery