OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/local_discovery/privetv3_session.h" | 5 #include "chrome/browser/local_discovery/privetv3_session.h" |
6 | 6 |
| 7 #include "base/base64.h" |
7 #include "base/json/json_writer.h" | 8 #include "base/json/json_writer.h" |
8 #include "base/logging.h" | 9 #include "base/logging.h" |
9 #include "base/message_loop/message_loop.h" | 10 #include "base/message_loop/message_loop.h" |
10 #include "chrome/browser/local_discovery/privet_constants.h" | 11 #include "chrome/browser/local_discovery/privet_constants.h" |
11 #include "chrome/browser/local_discovery/privet_http.h" | 12 #include "chrome/browser/local_discovery/privet_http.h" |
12 #include "chrome/browser/local_discovery/privet_url_fetcher.h" | 13 #include "chrome/browser/local_discovery/privet_url_fetcher.h" |
13 #include "chrome/common/cloud_print/cloud_print_constants.h" | 14 #include "chrome/common/cloud_print/cloud_print_constants.h" |
| 15 #include "crypto/hmac.h" |
| 16 #include "crypto/p224_spake.h" |
14 #include "url/gurl.h" | 17 #include "url/gurl.h" |
15 | 18 |
16 namespace local_discovery { | 19 namespace local_discovery { |
17 | 20 |
18 namespace { | 21 namespace { |
19 | 22 |
| 23 const char kPrivetV3AuthAnonymous[] = "Privet anonymous"; |
| 24 const char kPrivetV3CryptoP224Spake2[] = "p224_spake2"; |
| 25 |
| 26 const char kPrivetV3InfoKeyAuth[] = "authentication"; |
| 27 const char kPrivetV3InfoKeyVersion[] = "version"; |
| 28 const char kPrivetV3InfoVersion[] = "3.0"; |
| 29 |
| 30 const char kPrivetV3KeyCertFingerprint[] = "certFingerprint"; |
| 31 const char kPrivetV3KeyCertSignature[] = "certSignature"; |
| 32 const char kPrivetV3KeyClientCommitment[] = "clientCommitment"; |
| 33 const char kPrivetV3KeyCrypto[] = "crypto"; |
| 34 const char kPrivetV3KeyDeviceCommitment[] = "deviceCommitment"; |
| 35 const char kPrivetV3KeyMode[] = "mode"; |
| 36 const char kPrivetV3KeyPairing[] = "pairing"; |
| 37 const char kPrivetV3KeySessionId[] = "sessionId"; |
| 38 |
| 39 const char kPrivetV3PairingConfirmPath[] = "/privet/v3/pairing/confirm"; |
| 40 const char kPrivetV3PairingStartPath[] = "/privet/v3/pairing/start"; |
| 41 |
20 const char kUrlPlaceHolder[] = "http://host/"; | 42 const char kUrlPlaceHolder[] = "http://host/"; |
21 | 43 |
22 const char kStubPrivetCode[] = "1234"; | |
23 | |
24 const char kPrivetV3AuthAnonymous[] = "Privet anonymous"; | |
25 | |
26 GURL CreatePrivetURL(const std::string& path) { | 44 GURL CreatePrivetURL(const std::string& path) { |
27 GURL url(kUrlPlaceHolder); | 45 GURL url(kUrlPlaceHolder); |
28 GURL::Replacements replacements; | 46 GURL::Replacements replacements; |
29 replacements.SetPathStr(path); | 47 replacements.SetPathStr(path); |
30 return url.ReplaceComponents(replacements); | 48 return url.ReplaceComponents(replacements); |
31 } | 49 } |
32 | 50 |
| 51 template <typename T> |
| 52 class EnumToStringMap { |
| 53 public: |
| 54 static std::string FindNameById(T id) { |
| 55 for (const Element& m : kMap) { |
| 56 if (m.id == id) { |
| 57 DCHECK(m.name); |
| 58 return m.name; |
| 59 } |
| 60 } |
| 61 NOTREACHED(); |
| 62 return std::string(); |
| 63 } |
| 64 |
| 65 static bool FindIdByName(const std::string& name, T* id) { |
| 66 for (const Element& m : kMap) { |
| 67 if (m.name && m.name == name) { |
| 68 *id = m.id; |
| 69 return true; |
| 70 } |
| 71 } |
| 72 return false; |
| 73 } |
| 74 |
| 75 private: |
| 76 struct Element { |
| 77 const T id; |
| 78 const char* const name; |
| 79 }; |
| 80 static const Element kMap[]; |
| 81 }; |
| 82 |
| 83 using PairingType = PrivetV3Session::PairingType; |
| 84 |
| 85 template <> |
| 86 const EnumToStringMap<PrivetV3Session::PairingType>::Element |
| 87 EnumToStringMap<PrivetV3Session::PairingType>::kMap[] = { |
| 88 {PairingType::PAIRING_TYPE_PINCODE, "pinCode"}, |
| 89 {PairingType::PAIRING_TYPE_EMBEDDEDCODE, "embeddedCode"}, |
| 90 }; |
| 91 |
| 92 template <typename T> |
| 93 std::string EnumToString(T id) { |
| 94 return EnumToStringMap<T>::FindNameById(id); |
| 95 } |
| 96 |
| 97 template <typename T> |
| 98 bool StringToEnum(const std::string& name, T* id) { |
| 99 return EnumToStringMap<T>::FindIdByName(name, id); |
| 100 } |
| 101 |
| 102 bool GetDecodedString(const base::DictionaryValue& response, |
| 103 const std::string& key, |
| 104 std::string* value) { |
| 105 std::string base64; |
| 106 return response.GetString(key, &base64) && base::Base64Decode(base64, value); |
| 107 } |
| 108 |
| 109 bool ContainsString(const base::DictionaryValue& dictionary, |
| 110 const std::string& key, |
| 111 const std::string& expected_value) { |
| 112 const base::ListValue* list_of_string = nullptr; |
| 113 if (!dictionary.GetList(key, &list_of_string)) |
| 114 return false; |
| 115 |
| 116 for (const base::Value* value : *list_of_string) { |
| 117 std::string string_value; |
| 118 if (value->GetAsString(&string_value) && string_value == expected_value) |
| 119 return true; |
| 120 } |
| 121 return false; |
| 122 } |
| 123 |
33 } // namespace | 124 } // namespace |
34 | 125 |
35 class PrivetV3Session::FetcherDelegate : public PrivetURLFetcher::Delegate { | 126 class PrivetV3Session::FetcherDelegate : public PrivetURLFetcher::Delegate { |
36 public: | 127 public: |
37 FetcherDelegate(const base::WeakPtr<PrivetV3Session>& session, | 128 FetcherDelegate(const base::WeakPtr<PrivetV3Session>& session, |
38 const std::string& auth_token, | 129 const std::string& auth_token, |
39 const PrivetV3Session::MessageCallback& callback); | 130 const PrivetV3Session::MessageCallback& callback); |
40 ~FetcherDelegate() override; | 131 ~FetcherDelegate() override; |
41 | 132 |
42 // PrivetURLFetcher::Delegate methods. | 133 // PrivetURLFetcher::Delegate methods. |
43 std::string GetAuthToken() override; | 134 std::string GetAuthToken() override; |
44 void OnNeedPrivetToken( | 135 void OnNeedPrivetToken( |
45 PrivetURLFetcher* fetcher, | 136 PrivetURLFetcher* fetcher, |
46 const PrivetURLFetcher::TokenCallback& callback) override; | 137 const PrivetURLFetcher::TokenCallback& callback) override; |
47 void OnError(PrivetURLFetcher* fetcher, | 138 void OnError(PrivetURLFetcher* fetcher, |
48 PrivetURLFetcher::ErrorType error) override; | 139 PrivetURLFetcher::ErrorType error) override; |
49 void OnParsedJson(PrivetURLFetcher* fetcher, | 140 void OnParsedJson(PrivetURLFetcher* fetcher, |
50 const base::DictionaryValue& value, | 141 const base::DictionaryValue& value, |
51 bool has_error) override; | 142 bool has_error) override; |
52 | 143 |
| 144 PrivetURLFetcher* CreateURLFetcher(const GURL& url, |
| 145 net::URLFetcher::RequestType request_type); |
| 146 |
53 private: | 147 private: |
54 friend class PrivetV3Session; | |
55 void DeleteThis(); | 148 void DeleteThis(); |
56 | 149 |
57 scoped_ptr<PrivetURLFetcher> url_fetcher_; | 150 scoped_ptr<PrivetURLFetcher> url_fetcher_; |
| 151 |
58 base::WeakPtr<PrivetV3Session> session_; | 152 base::WeakPtr<PrivetV3Session> session_; |
59 std::string auth_token_; | 153 std::string auth_token_; |
60 MessageCallback callback_; | 154 MessageCallback callback_; |
61 }; | 155 }; |
62 | 156 |
63 PrivetV3Session::FetcherDelegate::FetcherDelegate( | 157 PrivetV3Session::FetcherDelegate::FetcherDelegate( |
64 const base::WeakPtr<PrivetV3Session>& session, | 158 const base::WeakPtr<PrivetV3Session>& session, |
65 const std::string& auth_token, | 159 const std::string& auth_token, |
66 const PrivetV3Session::MessageCallback& callback) | 160 const PrivetV3Session::MessageCallback& callback) |
67 : session_(session), auth_token_(auth_token), callback_(callback) { | 161 : session_(session), auth_token_(auth_token), callback_(callback) { |
(...skipping 26 matching lines...) Expand all Loading... |
94 PrivetURLFetcher* fetcher, | 188 PrivetURLFetcher* fetcher, |
95 const base::DictionaryValue& value, | 189 const base::DictionaryValue& value, |
96 bool has_error) { | 190 bool has_error) { |
97 if (session_) { | 191 if (session_) { |
98 DeleteThis(); | 192 DeleteThis(); |
99 callback_.Run( | 193 callback_.Run( |
100 has_error ? Result::STATUS_DEVICEERROR : Result::STATUS_SUCCESS, value); | 194 has_error ? Result::STATUS_DEVICEERROR : Result::STATUS_SUCCESS, value); |
101 } | 195 } |
102 } | 196 } |
103 | 197 |
| 198 PrivetURLFetcher* PrivetV3Session::FetcherDelegate::CreateURLFetcher( |
| 199 const GURL& url, |
| 200 net::URLFetcher::RequestType request_type) { |
| 201 DCHECK(!url_fetcher_); |
| 202 url_fetcher_ = |
| 203 session_->client_->CreateURLFetcher(url, request_type, this).Pass(); |
| 204 url_fetcher_->V3Mode(); |
| 205 return url_fetcher_.get(); |
| 206 } |
| 207 |
104 void PrivetV3Session::FetcherDelegate::DeleteThis() { | 208 void PrivetV3Session::FetcherDelegate::DeleteThis() { |
105 base::MessageLoop::current()->PostTask( | 209 base::MessageLoop::current()->PostTask( |
106 FROM_HERE, base::Bind(&PrivetV3Session::DeleteFetcher, session_, | 210 FROM_HERE, base::Bind(&PrivetV3Session::DeleteFetcher, session_, |
107 base::Unretained(this))); | 211 base::Unretained(this))); |
108 } | 212 } |
109 | 213 |
110 PrivetV3Session::PrivetV3Session(scoped_ptr<PrivetHTTPClient> client) | 214 PrivetV3Session::PrivetV3Session(scoped_ptr<PrivetHTTPClient> client) |
111 : client_(client.Pass()), code_confirmed_(false), weak_ptr_factory_(this) { | 215 : client_(client.Pass()), weak_ptr_factory_(this) { |
112 } | 216 } |
113 | 217 |
114 PrivetV3Session::~PrivetV3Session() { | 218 PrivetV3Session::~PrivetV3Session() { |
115 } | 219 } |
116 | 220 |
117 void PrivetV3Session::Init(const InitCallback& callback) { | 221 void PrivetV3Session::Init(const InitCallback& callback) { |
| 222 DCHECK(fetchers_.empty()); |
| 223 DCHECK(fingerprint_.empty()); |
| 224 DCHECK(session_id_.empty()); |
| 225 DCHECK(privet_auth_token_.empty()); |
| 226 |
118 privet_auth_token_ = kPrivetV3AuthAnonymous; | 227 privet_auth_token_ = kPrivetV3AuthAnonymous; |
119 | 228 |
120 // TODO: call /info. | 229 StartGetRequest(kPrivetInfoPath, |
121 base::MessageLoop::current()->PostDelayedTask( | 230 base::Bind(&PrivetV3Session::OnInfoDone, |
122 FROM_HERE, | 231 weak_ptr_factory_.GetWeakPtr(), callback)); |
123 base::Bind(&PrivetV3Session::RunCallback, weak_ptr_factory_.GetWeakPtr(), | 232 } |
124 base::Bind(callback, Result::STATUS_SUCCESS, | 233 |
125 std::vector<PairingType>( | 234 void PrivetV3Session::OnInfoDone(const InitCallback& callback, |
126 1, PairingType::PAIRING_TYPE_EMBEDDEDCODE))), | 235 Result result, |
127 base::TimeDelta::FromSeconds(1)); | 236 const base::DictionaryValue& response) { |
| 237 std::vector<PairingType> pairing_types; |
| 238 if (result != Result::STATUS_SUCCESS) |
| 239 return callback.Run(result, pairing_types); |
| 240 |
| 241 std::string version; |
| 242 if (!response.GetString(kPrivetV3InfoKeyVersion, &version) || |
| 243 version != kPrivetV3InfoVersion) { |
| 244 return callback.Run(Result::STATUS_SESSIONERROR, pairing_types); |
| 245 } |
| 246 |
| 247 const base::DictionaryValue* authentication = nullptr; |
| 248 const base::ListValue* pairing = nullptr; |
| 249 if (!response.GetDictionary(kPrivetV3InfoKeyAuth, &authentication) || |
| 250 !authentication->GetList(kPrivetV3KeyPairing, &pairing)) { |
| 251 return callback.Run(Result::STATUS_SESSIONERROR, pairing_types); |
| 252 } |
| 253 |
| 254 // The only supported crypto. |
| 255 if (!ContainsString(*authentication, kPrivetV3KeyCrypto, |
| 256 kPrivetV3CryptoP224Spake2) || |
| 257 !ContainsString(*authentication, kPrivetV3KeyMode, kPrivetV3KeyPairing)) { |
| 258 return callback.Run(Result::STATUS_SESSIONERROR, pairing_types); |
| 259 } |
| 260 |
| 261 for (const base::Value* value : *pairing) { |
| 262 std::string pairing_string; |
| 263 PairingType pairing_type; |
| 264 if (!value->GetAsString(&pairing_string) || |
| 265 !StringToEnum(pairing_string, &pairing_type)) { |
| 266 continue; // Skip unknown pairing. |
| 267 } |
| 268 pairing_types.push_back(pairing_type); |
| 269 } |
| 270 |
| 271 callback.Run(Result::STATUS_SUCCESS, pairing_types); |
128 } | 272 } |
129 | 273 |
130 void PrivetV3Session::StartPairing(PairingType pairing_type, | 274 void PrivetV3Session::StartPairing(PairingType pairing_type, |
131 const ResultCallback& callback) { | 275 const ResultCallback& callback) { |
132 // TODO: call /privet/v3/pairing/start. | 276 base::DictionaryValue input; |
133 base::MessageLoop::current()->PostDelayedTask( | 277 input.SetString(kPrivetV3KeyPairing, EnumToString(pairing_type)); |
134 FROM_HERE, | 278 input.SetString(kPrivetV3KeyCrypto, kPrivetV3CryptoP224Spake2); |
135 base::Bind(&PrivetV3Session::RunCallback, weak_ptr_factory_.GetWeakPtr(), | 279 |
136 base::Bind(callback, Result::STATUS_SUCCESS)), | 280 StartPostRequest(kPrivetV3PairingStartPath, input, |
137 base::TimeDelta::FromSeconds(1)); | 281 base::Bind(&PrivetV3Session::OnPairingStartDone, |
| 282 weak_ptr_factory_.GetWeakPtr(), callback)); |
| 283 } |
| 284 |
| 285 void PrivetV3Session::OnPairingStartDone( |
| 286 const ResultCallback& callback, |
| 287 Result result, |
| 288 const base::DictionaryValue& response) { |
| 289 if (result != Result::STATUS_SUCCESS) |
| 290 return callback.Run(result); |
| 291 |
| 292 if (!response.GetString(kPrivetV3KeySessionId, &session_id_) || |
| 293 !GetDecodedString(response, kPrivetV3KeyDeviceCommitment, &commitment_)) { |
| 294 return callback.Run(Result::STATUS_SESSIONERROR); |
| 295 } |
| 296 |
| 297 return callback.Run(Result::STATUS_SUCCESS); |
138 } | 298 } |
139 | 299 |
140 void PrivetV3Session::ConfirmCode(const std::string& code, | 300 void PrivetV3Session::ConfirmCode(const std::string& code, |
141 const ResultCallback& callback) { | 301 const ResultCallback& callback) { |
142 // TODO: call /privet/v3/pairing/confirm. | 302 if (session_id_.empty()) |
143 if (code == kStubPrivetCode) { | 303 return callback.Run(Result::STATUS_SESSIONERROR); |
144 code_confirmed_ = true; | 304 |
145 callback.Run(Result::STATUS_SUCCESS); | 305 spake_.reset(new crypto::P224EncryptedKeyExchange( |
146 } else { | 306 crypto::P224EncryptedKeyExchange::kPeerTypeClient, code)); |
147 callback.Run(Result::STATUS_BADPAIRINGCODEERROR); | 307 |
| 308 base::DictionaryValue input; |
| 309 input.SetString(kPrivetV3KeySessionId, session_id_); |
| 310 |
| 311 std::string client_commitment; |
| 312 base::Base64Encode(spake_->GetNextMessage(), &client_commitment); |
| 313 input.SetString(kPrivetV3KeyClientCommitment, client_commitment); |
| 314 |
| 315 // Call ProcessMessage after GetNextMessage(). |
| 316 crypto::P224EncryptedKeyExchange::Result result = |
| 317 spake_->ProcessMessage(commitment_); |
| 318 DCHECK_EQ(result, crypto::P224EncryptedKeyExchange::kResultPending); |
| 319 |
| 320 StartPostRequest(kPrivetV3PairingConfirmPath, input, |
| 321 base::Bind(&PrivetV3Session::OnPairingConfirmDone, |
| 322 weak_ptr_factory_.GetWeakPtr(), callback)); |
| 323 } |
| 324 |
| 325 void PrivetV3Session::OnPairingConfirmDone( |
| 326 const ResultCallback& callback, |
| 327 Result result, |
| 328 const base::DictionaryValue& response) { |
| 329 if (result != Result::STATUS_SUCCESS) |
| 330 return callback.Run(result); |
| 331 |
| 332 std::string fingerprint; |
| 333 std::string signature; |
| 334 if (!GetDecodedString(response, kPrivetV3KeyCertFingerprint, &fingerprint) || |
| 335 !GetDecodedString(response, kPrivetV3KeyCertSignature, &signature)) { |
| 336 return callback.Run(Result::STATUS_SESSIONERROR); |
148 } | 337 } |
| 338 |
| 339 crypto::HMAC hmac(crypto::HMAC::SHA256); |
| 340 // Key will be verified below, using HMAC. |
| 341 const std::string& key = spake_->GetUnverifiedKey(); |
| 342 if (!hmac.Init(reinterpret_cast<const unsigned char*>(key.c_str()), |
| 343 key.size()) || |
| 344 !hmac.Verify(fingerprint, signature)) { |
| 345 return callback.Run(Result::STATUS_SESSIONERROR); |
| 346 } |
| 347 |
| 348 return callback.Run(Result::STATUS_SUCCESS); |
149 } | 349 } |
150 | 350 |
151 void PrivetV3Session::SendMessage(const std::string& api, | 351 void PrivetV3Session::SendMessage(const std::string& api, |
152 const base::DictionaryValue& input, | 352 const base::DictionaryValue& input, |
153 const MessageCallback& callback) { | 353 const MessageCallback& callback) { |
154 if (!code_confirmed_) | 354 // TODO(vitalybuka): Implement validating HTTPS certificate using |
| 355 // fingerprint_. |
| 356 if (fingerprint_.empty()) |
155 return callback.Run(Result::STATUS_SESSIONERROR, base::DictionaryValue()); | 357 return callback.Run(Result::STATUS_SESSIONERROR, base::DictionaryValue()); |
156 | 358 |
157 FetcherDelegate* fetcher_delegate(new FetcherDelegate( | 359 StartPostRequest(api, input, callback); |
158 weak_ptr_factory_.GetWeakPtr(), privet_auth_token_, callback)); | |
159 fetchers_.push_back(fetcher_delegate); | |
160 | |
161 scoped_ptr<PrivetURLFetcher> url_fetcher(client_->CreateURLFetcher( | |
162 CreatePrivetURL(api), net::URLFetcher::POST, fetcher_delegate)); | |
163 | |
164 std::string json; | |
165 base::JSONWriter::WriteWithOptions( | |
166 &input, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json); | |
167 url_fetcher->SetUploadData(cloud_print::kContentTypeJSON, json); | |
168 | |
169 fetcher_delegate->url_fetcher_ = url_fetcher.Pass(); | |
170 fetcher_delegate->url_fetcher_->V3Mode(); | |
171 fetcher_delegate->url_fetcher_->Start(); | |
172 } | 360 } |
173 | 361 |
174 void PrivetV3Session::RunCallback(const base::Closure& callback) { | 362 void PrivetV3Session::RunCallback(const base::Closure& callback) { |
175 callback.Run(); | 363 callback.Run(); |
176 } | 364 } |
177 | 365 |
| 366 void PrivetV3Session::StartGetRequest(const std::string& api, |
| 367 const MessageCallback& callback) { |
| 368 CreateFetcher(api, net::URLFetcher::RequestType::GET, callback)->Start(); |
| 369 } |
| 370 |
| 371 void PrivetV3Session::StartPostRequest(const std::string& api, |
| 372 const base::DictionaryValue& input, |
| 373 const MessageCallback& callback) { |
| 374 if (!on_post_data_.is_null()) |
| 375 on_post_data_.Run(input); |
| 376 std::string json; |
| 377 base::JSONWriter::WriteWithOptions( |
| 378 &input, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json); |
| 379 PrivetURLFetcher* fetcher = |
| 380 CreateFetcher(api, net::URLFetcher::RequestType::POST, callback); |
| 381 fetcher->SetUploadData(cloud_print::kContentTypeJSON, json); |
| 382 fetcher->Start(); |
| 383 } |
| 384 |
| 385 PrivetURLFetcher* PrivetV3Session::CreateFetcher( |
| 386 const std::string& api, |
| 387 net::URLFetcher::RequestType request_type, |
| 388 const MessageCallback& callback) { |
| 389 fetchers_.push_back(new FetcherDelegate(weak_ptr_factory_.GetWeakPtr(), |
| 390 privet_auth_token_, callback)); |
| 391 return fetchers_.back()->CreateURLFetcher(CreatePrivetURL(api), request_type); |
| 392 } |
| 393 |
178 void PrivetV3Session::DeleteFetcher(const FetcherDelegate* fetcher) { | 394 void PrivetV3Session::DeleteFetcher(const FetcherDelegate* fetcher) { |
179 fetchers_.erase(std::find(fetchers_.begin(), fetchers_.end(), fetcher)); | 395 fetchers_.erase(std::find(fetchers_.begin(), fetchers_.end(), fetcher)); |
180 } | 396 } |
181 | 397 |
182 } // namespace local_discovery | 398 } // namespace local_discovery |
OLD | NEW |