| 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 bd5f4ca678b97c43dd9d128de3c5543ec2125f98..10e0c38dec79fb47fa2d26ac3445d0407dbfc0dd 100644
|
| --- a/chrome/browser/local_discovery/privetv3_session.cc
|
| +++ b/chrome/browser/local_discovery/privetv3_session.cc
|
| @@ -4,6 +4,7 @@
|
|
|
| #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,17 +12,34 @@
|
| #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 {
|
|
|
| namespace {
|
|
|
| -const char kUrlPlaceHolder[] = "http://host/";
|
| +const char kPrivetV3AuthAnonymous[] = "Privet anonymous";
|
| +const char kPrivetV3CryptoP224Spake2[] = "p224_spake2";
|
|
|
| -const char kStubPrivetCode[] = "1234";
|
| +const char kPrivetV3InfoKeyAuth[] = "authentication";
|
| +const char kPrivetV3InfoKeyVersion[] = "version";
|
| +const char kPrivetV3InfoVersion[] = "3.0";
|
|
|
| -const char kPrivetV3AuthAnonymous[] = "Privet anonymous";
|
| +const char kPrivetV3KeyCertFingerprint[] = "certFingerprint";
|
| +const char kPrivetV3KeyCertSignature[] = "certSignature";
|
| +const char kPrivetV3KeyClientCommitment[] = "clientCommitment";
|
| +const char kPrivetV3KeyCrypto[] = "crypto";
|
| +const char kPrivetV3KeyDeviceCommitment[] = "deviceCommitment";
|
| +const char kPrivetV3KeyMode[] = "mode";
|
| +const char kPrivetV3KeyPairing[] = "pairing";
|
| +const char kPrivetV3KeySessionId[] = "sessionId";
|
| +
|
| +const char kPrivetV3PairingConfirmPath[] = "/privet/v3/pairing/confirm";
|
| +const char kPrivetV3PairingStartPath[] = "/privet/v3/pairing/start";
|
| +
|
| +const char kUrlPlaceHolder[] = "http://host/";
|
|
|
| GURL CreatePrivetURL(const std::string& path) {
|
| GURL url(kUrlPlaceHolder);
|
| @@ -30,6 +48,79 @@ GURL CreatePrivetURL(const std::string& path) {
|
| return url.ReplaceComponents(replacements);
|
| }
|
|
|
| +template <typename T>
|
| +class EnumToStringMap {
|
| + public:
|
| + static std::string FindNameById(T id) {
|
| + for (const Element& 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 Element& m : kMap) {
|
| + if (m.name && m.name == name) {
|
| + *id = m.id;
|
| + return true;
|
| + }
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + private:
|
| + struct Element {
|
| + const T id;
|
| + const char* const name;
|
| + };
|
| + static const Element kMap[];
|
| +};
|
| +
|
| +using PairingType = PrivetV3Session::PairingType;
|
| +
|
| +template <>
|
| +const EnumToStringMap<PrivetV3Session::PairingType>::Element
|
| + EnumToStringMap<PrivetV3Session::PairingType>::kMap[] = {
|
| + {PairingType::PAIRING_TYPE_PINCODE, "pinCode"},
|
| + {PairingType::PAIRING_TYPE_EMBEDDEDCODE, "embeddedCode"},
|
| +};
|
| +
|
| +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);
|
| +}
|
| +
|
| +bool ContainsString(const base::DictionaryValue& dictionary,
|
| + const std::string& key,
|
| + const std::string& expected_value) {
|
| + const base::ListValue* list_of_string = nullptr;
|
| + if (!dictionary.GetList(key, &list_of_string))
|
| + return false;
|
| +
|
| + for (const base::Value* value : *list_of_string) {
|
| + std::string string_value;
|
| + if (value->GetAsString(&string_value) && string_value == expected_value)
|
| + return true;
|
| + }
|
| + return false;
|
| +}
|
| +
|
| } // namespace
|
|
|
| class PrivetV3Session::FetcherDelegate : public PrivetURLFetcher::Delegate {
|
| @@ -50,11 +141,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_;
|
| @@ -101,6 +195,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_,
|
| @@ -108,71 +212,183 @@ 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() {
|
| }
|
|
|
| void PrivetV3Session::Init(const InitCallback& callback) {
|
| + DCHECK(fetchers_.empty());
|
| + DCHECK(fingerprint_.empty());
|
| + DCHECK(session_id_.empty());
|
| + DCHECK(privet_auth_token_.empty());
|
| +
|
| 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));
|
| + 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);
|
| +
|
| + std::string version;
|
| + if (!response.GetString(kPrivetV3InfoKeyVersion, &version) ||
|
| + version != kPrivetV3InfoVersion) {
|
| + return callback.Run(Result::STATUS_SESSIONERROR, pairing_types);
|
| + }
|
| +
|
| + const base::DictionaryValue* authentication = nullptr;
|
| + const base::ListValue* pairing = nullptr;
|
| + if (!response.GetDictionary(kPrivetV3InfoKeyAuth, &authentication) ||
|
| + !authentication->GetList(kPrivetV3KeyPairing, &pairing)) {
|
| + return callback.Run(Result::STATUS_SESSIONERROR, pairing_types);
|
| + }
|
| +
|
| + // The only supported crypto.
|
| + if (!ContainsString(*authentication, kPrivetV3KeyCrypto,
|
| + kPrivetV3CryptoP224Spake2) ||
|
| + !ContainsString(*authentication, kPrivetV3KeyMode, kPrivetV3KeyPairing)) {
|
| + 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);
|
| + if (session_id_.empty())
|
| + return callback.Run(Result::STATUS_SESSIONERROR);
|
| +
|
| + spake_.reset(new crypto::P224EncryptedKeyExchange(
|
| + crypto::P224EncryptedKeyExchange::kPeerTypeClient, code));
|
| +
|
| + base::DictionaryValue input;
|
| + input.SetString(kPrivetV3KeySessionId, session_id_);
|
| +
|
| + std::string client_commitment;
|
| + base::Base64Encode(spake_->GetNextMessage(), &client_commitment);
|
| + input.SetString(kPrivetV3KeyClientCommitment, client_commitment);
|
| +
|
| + // Call ProcessMessage after GetNextMessage().
|
| + 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);
|
| +}
|
| +
|
| +void PrivetV3Session::RunCallback(const base::Closure& callback) {
|
| + callback.Run();
|
| +}
|
|
|
| - scoped_ptr<PrivetURLFetcher> url_fetcher(client_->CreateURLFetcher(
|
| - CreatePrivetURL(api), net::URLFetcher::POST, fetcher_delegate));
|
| +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) {
|
| + if (!on_post_data_.is_null())
|
| + on_post_data_.Run(input);
|
| 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();
|
| + PrivetURLFetcher* fetcher =
|
| + CreateFetcher(api, net::URLFetcher::RequestType::POST, callback);
|
| + 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) {
|
|
|