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"
19 namespace local_discovery
{
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
);
62 class EnumToStringMap
{
64 static std::string
FindNameById(T id
) {
65 for (const Element
& m
: kMap
) {
75 static bool FindIdByName(const std::string
& name
, T
* id
) {
76 for (const Element
& m
: kMap
) {
77 if (m
.name
&& m
.name
== name
) {
88 const char* const name
;
90 static const Element kMap
[];
93 using PairingType
= PrivetV3Session::PairingType
;
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
) {
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
))
128 for (const base::Value
* value
: *list_of_string
) {
129 std::string string_value
;
130 if (value
->GetAsString(&string_value
) && string_value
== expected_value
)
138 class PrivetV3Session::FetcherDelegate
: public PrivetURLFetcher::Delegate
{
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
,
161 void ReplyAndDestroyItself(Result result
, const base::DictionaryValue
& value
);
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
)
178 auth_token_(auth_token
),
180 weak_ptr_factory_(this) {
183 PrivetV3Session::FetcherDelegate::~FetcherDelegate() {
186 std::string
PrivetV3Session::FetcherDelegate::GetAuthToken() {
190 void PrivetV3Session::FetcherDelegate::OnNeedPrivetToken(
191 PrivetURLFetcher
* fetcher
,
192 const PrivetURLFetcher::TokenCallback
& callback
) {
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
,
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(
218 net::URLFetcher::RequestType request_type
,
220 DCHECK(!url_fetcher_
);
222 session_
->client_
->CreateURLFetcher(url
, request_type
, this).Pass();
223 url_fetcher_
->V3Mode();
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(
237 const base::DictionaryValue
& value
) {
239 if (!callback_
.is_null()) {
240 callback_
.Run(result
, value
);
243 base::MessageLoop::current()->PostTask(
244 FROM_HERE
, base::Bind(&PrivetV3Session::DeleteFetcher
, session_
,
245 base::Unretained(this)));
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() {
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
,
279 const base::DictionaryValue
& response
) {
280 std::vector
<PairingType
> pairing_types
;
281 if (result
!= Result::STATUS_SUCCESS
)
282 return callback
.Run(result
, pairing_types
);
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
,
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
,
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()),
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
)),
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
,
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
;
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
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
) {
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
);
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
);
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
);
490 fetchers_
.push_back(fetcher
);
491 return fetcher
->CreateURLFetcher(CreatePrivetURL(api
), request_type
,
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())
503 base::DictionaryValue input
;
504 input
.SetString(kPrivetV3KeySessionId
, session_id_
);
505 StartPostRequest(kPrivetV3PairingCancelPath
, input
, MessageCallback());
508 } // namespace local_discovery