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) { |