Chromium Code Reviews| 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) { |