Index: chrome/browser/local_discovery/privetv3_session.cc |
diff --git a/chrome/browser/local_discovery/privetv3_session.cc b/chrome/browser/local_discovery/privetv3_session.cc |
index b90152414028b879aaaa6920c44171744d1de298..e308d397dbd5c872b3471dbdc1d86a33a24c22e0 100644 |
--- a/chrome/browser/local_discovery/privetv3_session.cc |
+++ b/chrome/browser/local_discovery/privetv3_session.cc |
@@ -2,8 +2,14 @@ |
// Use of this source code is governed by a BSD-style license that can be |
// found in the LICENSE file. |
+#if defined(_WIN32) && defined(GetMessage) |
+// Method in p224_spake conflicts with macro on Windows. |
+#undef GetMessage |
+#endif |
+ |
#include "chrome/browser/local_discovery/privetv3_session.h" |
+#include "base/base64.h" |
#include "base/json/json_writer.h" |
#include "base/logging.h" |
#include "base/message_loop/message_loop.h" |
@@ -11,6 +17,8 @@ |
#include "chrome/browser/local_discovery/privet_http.h" |
#include "chrome/browser/local_discovery/privet_url_fetcher.h" |
#include "chrome/common/cloud_print/cloud_print_constants.h" |
+#include "crypto/hmac.h" |
+#include "crypto/p224_spake.h" |
#include "url/gurl.h" |
namespace local_discovery { |
@@ -22,6 +30,23 @@ const char kUrlPlaceHolder[] = "http://host/"; |
const char kStubPrivetCode[] = "1234"; |
char kPrivetV3AuthAnonymous[] = "Privet anonymous"; |
+char kPrivetV3CryptoP224Spake2[] = "p224_spake2"; |
+ |
+char kPrivetV3InfoKeyAuth[] = "authentication"; |
+char kPrivetV3InfoKeyPairing[] = "authentication"; |
+char kPrivetV3InfoKeyHttpsPort[] = "endpoints.httpsPort"; |
+ |
+char kPrivetV3KeyPairing[] = "pairing"; |
+char kPrivetV3KeyCrypto[] = "crypto"; |
+char kPrivetV3KeySessionId[] = "sessionId"; |
+char kPrivetV3KeyDeviceCommitment[] = "deviceCommitment"; |
+char kPrivetV3KeyClientCommitment[] = "clientCommitment"; |
+char kPrivetV3KeyCertFingerprint[] = "certFingerprint"; |
+char kPrivetV3KeyCertSignature[] = "certSignature"; |
+ |
+char kPrivetV3PairingStartPath[] = "/privet/v3/pairing/start"; |
+char kPrivetV3PairingConfirmPath[] = "/privet/v3/pairing/confirm"; |
Aleksey Shlyapnikov
2015/01/26 21:47:45
Make them all const char.
Vitaly Buka (NO REVIEWS)
2015/01/27 00:16:33
Done.
|
+ |
GURL CreatePrivetURL(const std::string& path) { |
GURL url(kUrlPlaceHolder); |
GURL::Replacements replacements; |
@@ -29,6 +54,68 @@ GURL CreatePrivetURL(const std::string& path) { |
return url.ReplaceComponents(replacements); |
} |
+template <typename T> |
+class EnumToStringMap { |
+ public: |
+ static std::string FindNameById(T id) { |
+ for (const Map& m : kMap) { |
+ if (m.id == id) { |
+ DCHECK(m.name); |
+ return m.name; |
+ } |
+ } |
+ NOTREACHED(); |
+ return std::string(); |
+ } |
+ |
+ static bool FindIdByName(const std::string& name, T* id) { |
+ for (const Map& m : kMap) { |
+ if (m.name && m.name == name) { |
+ *id = m.id; |
+ return true; |
+ } |
+ } |
+ return false; |
+ } |
+ |
+ private: |
+ struct Map { |
Aleksey Shlyapnikov
2015/01/26 21:47:45
Element
Vitaly Buka (NO REVIEWS)
2015/01/27 00:16:33
Done.
|
+ const T id; |
+ const char* const name; |
+ }; |
+ static const Map kMap[]; |
+}; |
+ |
+using PairingType = PrivetV3Session::PairingType; |
+ |
+template <> |
+const EnumToStringMap<PrivetV3Session::PairingType>::Map |
+ EnumToStringMap<PrivetV3Session::PairingType>::kMap[] = { |
+ {PairingType::PAIRING_TYPE_PINCODE, "pinCode"}, |
+ {PairingType::PAIRING_TYPE_EMBEDDEDCODE, "embeddedCode"}, |
+ {PairingType::PAIRING_TYPE_ULTRASOUNDDSSSBROADCASTER, |
+ "ultrasoundDsssBroadcaster"}, |
+ {PairingType::PAIRING_TYPE_AUDIBLEDTMFBROADCASTER, |
+ "audibleDtmfBroadcaster"}, |
+}; |
+ |
+template <typename T> |
+std::string EnumToString(T id) { |
+ return EnumToStringMap<T>::FindNameById(id); |
+} |
+ |
+template <typename T> |
+bool StringToEnum(const std::string& name, T* id) { |
+ return EnumToStringMap<T>::FindIdByName(name, id); |
+} |
+ |
+bool GetDecodedString(const base::DictionaryValue& response, |
+ const std::string& key, |
+ std::string* value) { |
+ std::string base64; |
+ return response.GetString(key, &base64) && base::Base64Decode(base64, value); |
+} |
+ |
} // namespace |
class PrivetV3Session::FetcherDelegate : public PrivetURLFetcher::Delegate { |
@@ -49,11 +136,14 @@ class PrivetV3Session::FetcherDelegate : public PrivetURLFetcher::Delegate { |
const base::DictionaryValue& value, |
bool has_error) override; |
+ PrivetURLFetcher* CreateURLFetcher(const GURL& url, |
+ net::URLFetcher::RequestType request_type); |
+ |
private: |
- friend class PrivetV3Session; |
void DeleteThis(); |
scoped_ptr<PrivetURLFetcher> url_fetcher_; |
+ |
base::WeakPtr<PrivetV3Session> session_; |
std::string auth_token_; |
MessageCallback callback_; |
@@ -100,6 +190,16 @@ void PrivetV3Session::FetcherDelegate::OnParsedJson( |
} |
} |
+PrivetURLFetcher* PrivetV3Session::FetcherDelegate::CreateURLFetcher( |
+ const GURL& url, |
+ net::URLFetcher::RequestType request_type) { |
+ DCHECK(!url_fetcher_); |
+ url_fetcher_ = |
+ session_->client_->CreateURLFetcher(url, request_type, this).Pass(); |
+ url_fetcher_->V3Mode(); |
+ return url_fetcher_.get(); |
+} |
+ |
void PrivetV3Session::FetcherDelegate::DeleteThis() { |
base::MessageLoop::current()->PostTask( |
FROM_HERE, base::Bind(&PrivetV3Session::DeleteFetcher, session_, |
@@ -107,7 +207,7 @@ void PrivetV3Session::FetcherDelegate::DeleteThis() { |
} |
PrivetV3Session::PrivetV3Session(scoped_ptr<PrivetHTTPClient> client) |
- : client_(client.Pass()), code_confirmed_(false), weak_ptr_factory_(this) { |
+ : client_(client.Pass()), weak_ptr_factory_(this) { |
} |
PrivetV3Session::~PrivetV3Session() { |
@@ -116,62 +216,172 @@ PrivetV3Session::~PrivetV3Session() { |
void PrivetV3Session::Init(const InitCallback& callback) { |
privet_auth_token_ = kPrivetV3AuthAnonymous; |
- // TODO: call /info. |
- base::MessageLoop::current()->PostDelayedTask( |
- FROM_HERE, |
- base::Bind(&PrivetV3Session::RunCallback, weak_ptr_factory_.GetWeakPtr(), |
- base::Bind(callback, Result::STATUS_SUCCESS, |
- std::vector<PairingType>( |
- 1, PairingType::PAIRING_TYPE_EMBEDDEDCODE))), |
- base::TimeDelta::FromSeconds(1)); |
+ DCHECK(fetchers_.empty()); |
+ |
+ privet_auth_token_ = kPrivetV3AuthAnonymous; |
+ |
+ StartGetRequest(kPrivetInfoPath, |
+ base::Bind(&PrivetV3Session::OnInfoDone, |
+ weak_ptr_factory_.GetWeakPtr(), callback)); |
+} |
+ |
+void PrivetV3Session::OnInfoDone(const InitCallback& callback, |
+ Result result, |
+ const base::DictionaryValue& response) { |
+ std::vector<PairingType> pairing_types; |
+ if (result != Result::STATUS_SUCCESS) |
+ return callback.Run(result, pairing_types); |
+ |
+ const base::DictionaryValue* authentication = nullptr; |
+ const base::ListValue* pairing = nullptr; |
+ const base::ListValue* crypto = nullptr; |
+ if (!response.GetDictionary(kPrivetV3InfoKeyAuth, &authentication) || |
+ !authentication->GetList(kPrivetV3KeyPairing, &pairing) || |
+ !authentication->GetList(kPrivetV3KeyCrypto, &crypto)) { |
+ return callback.Run(Result::STATUS_SESSIONERROR, pairing_types); |
+ } |
+ |
+ bool has_compartible_crypto = false; |
Aleksey Shlyapnikov
2015/01/26 21:47:45
has_compatible_crypto
Vitaly Buka (NO REVIEWS)
2015/01/27 00:16:33
Done.
|
+ for (const base::Value* value : *crypto) { |
+ std::string crypto_string; |
+ // The only supported crypto. |
+ if (value->GetAsString(&crypto_string) && |
+ crypto_string == kPrivetV3CryptoP224Spake2) { |
+ has_compartible_crypto = true; |
+ break; |
+ } |
+ } |
+ |
+ if (!has_compartible_crypto) |
+ return callback.Run(Result::STATUS_SESSIONERROR, pairing_types); |
+ |
+ for (const base::Value* value : *pairing) { |
+ std::string pairing_string; |
+ PairingType pairing_type; |
+ if (!value->GetAsString(&pairing_string) || |
+ !StringToEnum(pairing_string, &pairing_type)) { |
+ continue; // Skip unknown pairing. |
+ } |
+ pairing_types.push_back(pairing_type); |
+ } |
+ |
+ callback.Run(Result::STATUS_SUCCESS, pairing_types); |
} |
void PrivetV3Session::StartPairing(PairingType pairing_type, |
const ResultCallback& callback) { |
- // TODO: call /privet/v3/pairing/start. |
- base::MessageLoop::current()->PostDelayedTask( |
- FROM_HERE, |
- base::Bind(&PrivetV3Session::RunCallback, weak_ptr_factory_.GetWeakPtr(), |
- base::Bind(callback, Result::STATUS_SUCCESS)), |
- base::TimeDelta::FromSeconds(1)); |
+ base::DictionaryValue input; |
+ input.SetString(kPrivetV3KeyPairing, EnumToString(pairing_type)); |
+ input.SetString(kPrivetV3KeyCrypto, kPrivetV3CryptoP224Spake2); |
+ |
+ StartPostRequest(kPrivetV3PairingStartPath, input, |
+ base::Bind(&PrivetV3Session::OnPairingStartDone, |
+ weak_ptr_factory_.GetWeakPtr(), callback)); |
+} |
+ |
+void PrivetV3Session::OnPairingStartDone( |
+ const ResultCallback& callback, |
+ Result result, |
+ const base::DictionaryValue& response) { |
+ if (result != Result::STATUS_SUCCESS) |
+ return callback.Run(result); |
+ |
+ if (!response.GetString(kPrivetV3KeySessionId, &session_id_) || |
+ !GetDecodedString(response, kPrivetV3KeyDeviceCommitment, &commitment_)) { |
+ return callback.Run(Result::STATUS_SESSIONERROR); |
+ } |
+ |
+ return callback.Run(Result::STATUS_SUCCESS); |
} |
void PrivetV3Session::ConfirmCode(const std::string& code, |
const ResultCallback& callback) { |
- // TODO: call /privet/v3/pairing/confirm. |
- if (code == kStubPrivetCode) { |
- code_confirmed_ = true; |
- callback.Run(Result::STATUS_SUCCESS); |
- } else { |
- callback.Run(Result::STATUS_BADPAIRINGCODEERROR); |
+ spake_.reset(new crypto::P224EncryptedKeyExchange( |
+ crypto::P224EncryptedKeyExchange::kPeerTypeClient, code)); |
+ |
+ base::DictionaryValue input; |
+ input.SetString(kPrivetV3KeySessionId, session_id_); |
+ |
+ std::string client_commitment; |
+ base::Base64Encode(spake_->GetMessage(), &client_commitment); |
+ input.SetString(kPrivetV3KeyClientCommitment, client_commitment); |
+ |
+ // Call ProcessMessage after GetMessage(). |
+ crypto::P224EncryptedKeyExchange::Result result = |
+ spake_->ProcessMessage(commitment_); |
+ DCHECK_EQ(result, crypto::P224EncryptedKeyExchange::kResultPending); |
+ |
+ StartPostRequest(kPrivetV3PairingConfirmPath, input, |
+ base::Bind(&PrivetV3Session::OnPairingConfirmDone, |
+ weak_ptr_factory_.GetWeakPtr(), callback)); |
+} |
+ |
+void PrivetV3Session::OnPairingConfirmDone( |
+ const ResultCallback& callback, |
+ Result result, |
+ const base::DictionaryValue& response) { |
+ if (result != Result::STATUS_SUCCESS) |
+ return callback.Run(result); |
+ |
+ std::string fingerprint; |
+ std::string signature; |
+ if (!GetDecodedString(response, kPrivetV3KeyCertFingerprint, &fingerprint) || |
+ !GetDecodedString(response, kPrivetV3KeyCertSignature, &signature)) { |
+ return callback.Run(Result::STATUS_SESSIONERROR); |
+ } |
+ |
+ crypto::HMAC hmac(crypto::HMAC::SHA256); |
+ // Key will be verified below, using HMAC. |
+ const std::string& key = spake_->GetUnverifiedKey(); |
+ if (!hmac.Init(reinterpret_cast<const unsigned char*>(key.c_str()), |
+ key.size()) || |
+ !hmac.Verify(fingerprint, signature)) { |
+ return callback.Run(Result::STATUS_SESSIONERROR); |
} |
+ |
+ return callback.Run(Result::STATUS_SUCCESS); |
} |
void PrivetV3Session::SendMessage(const std::string& api, |
const base::DictionaryValue& input, |
const MessageCallback& callback) { |
- if (!code_confirmed_) |
+ // TODO(vitalybuka): Implement validating HTTPS certificate using |
+ // fingerprint_. |
+ if (fingerprint_.empty()) |
return callback.Run(Result::STATUS_SESSIONERROR, base::DictionaryValue()); |
- FetcherDelegate* fetcher_delegate(new FetcherDelegate( |
- weak_ptr_factory_.GetWeakPtr(), privet_auth_token_, callback)); |
- fetchers_.push_back(fetcher_delegate); |
+ StartPostRequest(api, input, callback); |
+} |
- scoped_ptr<PrivetURLFetcher> url_fetcher(client_->CreateURLFetcher( |
- CreatePrivetURL(api), net::URLFetcher::POST, fetcher_delegate)); |
+void PrivetV3Session::RunCallback(const base::Closure& callback) { |
+ callback.Run(); |
+} |
+ |
+void PrivetV3Session::StartGetRequest(const std::string& api, |
+ const MessageCallback& callback) { |
+ CreateFetcher(api, net::URLFetcher::RequestType::GET, callback)->Start(); |
+} |
+ |
+void PrivetV3Session::StartPostRequest(const std::string& api, |
+ const base::DictionaryValue& input, |
+ const MessageCallback& callback) { |
+ PrivetURLFetcher* fetcher = |
+ CreateFetcher(api, net::URLFetcher::RequestType::POST, callback); |
std::string json; |
base::JSONWriter::WriteWithOptions( |
&input, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json); |
- url_fetcher->SetUploadData(cloud_print::kContentTypeJSON, json); |
- |
- fetcher_delegate->url_fetcher_ = url_fetcher.Pass(); |
- fetcher_delegate->url_fetcher_->V3Mode(); |
- fetcher_delegate->url_fetcher_->Start(); |
+ fetcher->SetUploadData(cloud_print::kContentTypeJSON, json); |
+ fetcher->Start(); |
} |
-void PrivetV3Session::RunCallback(const base::Closure& callback) { |
- callback.Run(); |
+PrivetURLFetcher* PrivetV3Session::CreateFetcher( |
+ const std::string& api, |
+ net::URLFetcher::RequestType request_type, |
+ const MessageCallback& callback) { |
+ fetchers_.push_back(new FetcherDelegate(weak_ptr_factory_.GetWeakPtr(), |
+ privet_auth_token_, callback)); |
+ return fetchers_.back()->CreateURLFetcher(CreatePrivetURL(api), request_type); |
} |
void PrivetV3Session::DeleteFetcher(const FetcherDelegate* fetcher) { |