diff options
author | sorin <sorin@chromium.org> | 2016-02-17 10:45:17 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2016-02-17 18:46:51 +0000 |
commit | 1bc5eff904e6e20191a0abc2ab7a4c75c31dcc1f (patch) | |
tree | 3982cbb00d3e68792199112696315313f3b3560f | |
parent | 4ae838b9c5d4ad2b0751c63a5ccd6bda1065989e (diff) | |
download | chromium_src-1bc5eff904e6e20191a0abc2ab7a4c75c31dcc1f.zip chromium_src-1bc5eff904e6e20191a0abc2ab7a4c75c31dcc1f.tar.gz chromium_src-1bc5eff904e6e20191a0abc2ab7a4c75c31dcc1f.tar.bz2 |
Implement CUP signing in UpdateClient.
BUG=583027
Review URL: https://codereview.chromium.org/1685323002
Cr-Commit-Position: refs/heads/master@{#375930}
23 files changed, 371 insertions, 138 deletions
diff --git a/chrome/browser/component_updater/chrome_component_updater_configurator.cc b/chrome/browser/component_updater/chrome_component_updater_configurator.cc index 5f60ccf..fcc40d5 100644 --- a/chrome/browser/component_updater/chrome_component_updater_configurator.cc +++ b/chrome/browser/component_updater/chrome_component_updater_configurator.cc @@ -50,6 +50,7 @@ class ChromeConfigurator : public update_client::Configurator { const override; bool DeltasEnabled() const override; bool UseBackgroundDownloader() const override; + bool UseCupSigning() const override; scoped_refptr<base::SequencedTaskRunner> GetSequencedTaskRunner() const override; @@ -144,6 +145,10 @@ bool ChromeConfigurator::UseBackgroundDownloader() const { return configurator_impl_.UseBackgroundDownloader(); } +bool ChromeConfigurator::UseCupSigning() const { + return configurator_impl_.UseCupSigning(); +} + // Returns a task runner to run blocking tasks. The task runner continues to run // after the browser shuts down, until the OS terminates the process. This // imposes certain requirements for the code using the task runner, such as diff --git a/chrome/browser/extensions/updater/chrome_update_client_config.cc b/chrome/browser/extensions/updater/chrome_update_client_config.cc index 3ae3dca..7b81c2d 100644 --- a/chrome/browser/extensions/updater/chrome_update_client_config.cc +++ b/chrome/browser/extensions/updater/chrome_update_client_config.cc @@ -87,6 +87,10 @@ bool ChromeUpdateClientConfig::UseBackgroundDownloader() const { return impl_.UseBackgroundDownloader(); } +bool ChromeUpdateClientConfig::UseCupSigning() const { + return false; +} + ChromeUpdateClientConfig::~ChromeUpdateClientConfig() {} } // namespace extensions diff --git a/chrome/browser/extensions/updater/chrome_update_client_config.h b/chrome/browser/extensions/updater/chrome_update_client_config.h index 7e6b020..3596335 100644 --- a/chrome/browser/extensions/updater/chrome_update_client_config.h +++ b/chrome/browser/extensions/updater/chrome_update_client_config.h @@ -41,6 +41,7 @@ class ChromeUpdateClientConfig : public UpdateClientConfig { const override; bool DeltasEnabled() const override; bool UseBackgroundDownloader() const override; + bool UseCupSigning() const override; protected: friend class base::RefCountedThreadSafe<ChromeUpdateClientConfig>; diff --git a/components/component_updater/configurator_impl.cc b/components/component_updater/configurator_impl.cc index 35c1793..6372118 100644 --- a/components/component_updater/configurator_impl.cc +++ b/components/component_updater/configurator_impl.cc @@ -178,4 +178,8 @@ bool ConfiguratorImpl::UseBackgroundDownloader() const { return background_downloads_enabled_; } +bool ConfiguratorImpl::UseCupSigning() const { + return true; +} + } // namespace component_updater diff --git a/components/component_updater/configurator_impl.h b/components/component_updater/configurator_impl.h index 09170d4..d5f99cf 100644 --- a/components/component_updater/configurator_impl.h +++ b/components/component_updater/configurator_impl.h @@ -81,6 +81,9 @@ class ConfiguratorImpl { // non on-demand components. bool UseBackgroundDownloader() const; + // True if signing of update checks is enabled. + bool UseCupSigning() const; + private: net::URLRequestContextGetter* url_request_getter_; std::string extra_info_; diff --git a/components/update_client/action_update_check.cc b/components/update_client/action_update_check.cc index 5c411c4..9e31eb6 100644 --- a/components/update_client/action_update_check.cc +++ b/components/update_client/action_update_check.cc @@ -96,18 +96,14 @@ void ActionUpdateCheck::Run(UpdateContext* update_context, Callback callback) { } void ActionUpdateCheck::UpdateCheckComplete( - const GURL& original_url, int error, - const std::string& error_message, const UpdateResponse::Results& results) { DCHECK(thread_checker_.CalledOnValidThread()); - VLOG(1) << "Update check completed from: " << original_url.spec(); - if (!error) OnUpdateCheckSucceeded(results); else - OnUpdateCheckFailed(error, error_message); + OnUpdateCheckFailed(error); } void ActionUpdateCheck::OnUpdateCheckSucceeded( @@ -198,8 +194,7 @@ void ActionUpdateCheck::OnUpdateCheckSucceeded( UpdateCrx(); } -void ActionUpdateCheck::OnUpdateCheckFailed(int error, - const std::string& error_message) { +void ActionUpdateCheck::OnUpdateCheckFailed(int error) { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(error); diff --git a/components/update_client/action_update_check.h b/components/update_client/action_update_check.h index b5f1d24..7576d64 100644 --- a/components/update_client/action_update_check.h +++ b/components/update_client/action_update_check.h @@ -36,13 +36,10 @@ class ActionUpdateCheck : public Action, private ActionImpl { void Run(UpdateContext* update_context, Callback callback) override; private: - void UpdateCheckComplete(const GURL& original_url, - int error, - const std::string& error_message, - const UpdateResponse::Results& results); + void UpdateCheckComplete(int error, const UpdateResponse::Results& results); void OnUpdateCheckSucceeded(const UpdateResponse::Results& results); - void OnUpdateCheckFailed(int error, const std::string& error_message); + void OnUpdateCheckFailed(int error); scoped_ptr<UpdateChecker> update_checker_; const base::Version browser_version_; diff --git a/components/update_client/client_update_protocol_ecdsa.cc b/components/update_client/client_update_protocol_ecdsa.cc index 499ece7..8479ceb 100644 --- a/components/update_client/client_update_protocol_ecdsa.cc +++ b/components/update_client/client_update_protocol_ecdsa.cc @@ -5,6 +5,7 @@ #include "components/update_client/client_update_protocol_ecdsa.h" #include "base/logging.h" +#include "base/macros.h" #include "base/memory/scoped_ptr.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_piece.h" @@ -14,6 +15,8 @@ #include "crypto/sha2.h" #include "crypto/signature_verifier.h" +namespace update_client { + namespace { // This is the algorithm ID for ECDSA with SHA-256. Parameters are ABSENT. @@ -46,7 +49,7 @@ std::vector<uint8_t> SHA256HashVec(const std::vector<uint8_t>& vec) { reinterpret_cast<const char*>(&vec.front()), vec.size())); } -bool ParseETagHeader(const base::StringPiece& etag_header_value, +bool ParseETagHeader(const base::StringPiece& etag_header_value_in, std::vector<uint8_t>* ecdsa_signature_out, std::vector<uint8_t>* request_hash_out) { DCHECK(ecdsa_signature_out); @@ -55,6 +58,20 @@ bool ParseETagHeader(const base::StringPiece& etag_header_value, // The ETag value is a UTF-8 string, formatted as "S:H", where: // * S is the ECDSA signature in DER-encoded ASN.1 form, converted to hex. // * H is the SHA-256 hash of the observed request body, standard hex format. + // A Weak ETag is formatted as W/"S:H". This function treats it the same as a + // strong ETag. + base::StringPiece etag_header_value(etag_header_value_in); + + // Remove the weak prefix, then remove the begin and the end quotes. + const char kWeakETagPrefix[] = "W/"; + if (etag_header_value.starts_with(kWeakETagPrefix)) + etag_header_value.remove_prefix(arraysize(kWeakETagPrefix) - 1); + if (etag_header_value.size() >= 2 && etag_header_value.starts_with("\"") && + etag_header_value.ends_with("\"")) { + etag_header_value.remove_prefix(1); + etag_header_value.remove_suffix(1); + } + const base::StringPiece::size_type delim_pos = etag_header_value.find(':'); if (delim_pos == base::StringPiece::npos || delim_pos == 0 || delim_pos == etag_header_value.size() - 1) @@ -188,3 +205,5 @@ bool ClientUpdateProtocolEcdsa::ValidateResponse( static_cast<int>(signed_message_hash.size())); return verifier.VerifyFinal(); } + +} // namespace update_client diff --git a/components/update_client/client_update_protocol_ecdsa.h b/components/update_client/client_update_protocol_ecdsa.h index 6796626..2459c8c 100644 --- a/components/update_client/client_update_protocol_ecdsa.h +++ b/components/update_client/client_update_protocol_ecdsa.h @@ -13,6 +13,8 @@ #include "base/memory/scoped_ptr.h" #include "base/strings/string_piece.h" +namespace update_client { + // Client Update Protocol v2, or CUP-ECDSA, is used by Google Update (Omaha) // servers to ensure freshness and authenticity of update checks over HTTP, // without the overhead of HTTPS -- namely, no PKI, no guarantee of privacy, @@ -81,4 +83,6 @@ class ClientUpdateProtocolEcdsa { DISALLOW_IMPLICIT_CONSTRUCTORS(ClientUpdateProtocolEcdsa); }; +} // namespace update_client + #endif // COMPONENTS_UPDATE_CLIENT_CLIENT_UPDATE_PROTOCOL_ECDSA_H_ diff --git a/components/update_client/client_update_protocol_ecdsa_unittest.cc b/components/update_client/client_update_protocol_ecdsa_unittest.cc index 506aad2..eab1f35 100644 --- a/components/update_client/client_update_protocol_ecdsa_unittest.cc +++ b/components/update_client/client_update_protocol_ecdsa_unittest.cc @@ -16,6 +16,8 @@ #include "crypto/secure_util.h" #include "testing/gtest/include/gtest/gtest.h" +namespace update_client { + namespace { std::string GetPublicKeyForTesting() { @@ -292,3 +294,5 @@ TEST_F(CupEcdsaTest, ValidateResponse_TestSigning) { "bf022100dd7d41d467be2af98d9116b0c7ba09740d54578c02a02f74da5f089834be3403" ":2727bc2b3c33feb6800a830f4055901dd87d65a84184c5fbeb3f816db0a243f5")); } + +} // namespace update_client diff --git a/components/update_client/configurator.h b/components/update_client/configurator.h index 5d6a16a..c8101dd 100644 --- a/components/update_client/configurator.h +++ b/components/update_client/configurator.h @@ -98,6 +98,9 @@ class Configurator : public base::RefCountedThreadSafe<Configurator> { // non on-demand components. virtual bool UseBackgroundDownloader() const = 0; + // True if signing of update checks is enabled. + virtual bool UseCupSigning() const = 0; + // Gets a task runner to a blocking pool of threads suitable for worker jobs. virtual scoped_refptr<base::SequencedTaskRunner> GetSequencedTaskRunner() const = 0; diff --git a/components/update_client/ping_manager.cc b/components/update_client/ping_manager.cc index 17bcdd6..1918814 100644 --- a/components/update_client/ping_manager.cc +++ b/components/update_client/ping_manager.cc @@ -179,7 +179,7 @@ class PingSender { bool SendPing(const CrxUpdateItem* item); private: - void OnRequestSenderComplete(const net::URLFetcher* source); + void OnRequestSenderComplete(int error, const std::string& response); const scoped_refptr<Configurator> config_; scoped_ptr<RequestSender> request_sender_; @@ -195,7 +195,8 @@ PingSender::~PingSender() { DCHECK(thread_checker_.CalledOnValidThread()); } -void PingSender::OnRequestSenderComplete(const net::URLFetcher* source) { +void PingSender::OnRequestSenderComplete(int error, + const std::string& response) { DCHECK(thread_checker_.CalledOnValidThread()); delete this; } @@ -211,7 +212,7 @@ bool PingSender::SendPing(const CrxUpdateItem* item) { request_sender_.reset(new RequestSender(config_)); request_sender_->Send( - BuildPing(*config_, item), urls, + false, BuildPing(*config_, item), urls, base::Bind(&PingSender::OnRequestSenderComplete, base::Unretained(this))); return true; } diff --git a/components/update_client/request_sender.cc b/components/update_client/request_sender.cc index 1a1ebc3..b2296a2 100644 --- a/components/update_client/request_sender.cc +++ b/components/update_client/request_sender.cc @@ -4,41 +4,68 @@ #include "components/update_client/request_sender.h" +#include "base/base64.h" #include "base/bind.h" #include "base/bind_helpers.h" #include "base/location.h" #include "base/logging.h" #include "base/single_thread_task_runner.h" +#include "base/strings/stringprintf.h" #include "base/thread_task_runner_handle.h" +#include "components/update_client/client_update_protocol_ecdsa.h" #include "components/update_client/configurator.h" #include "components/update_client/utils.h" +#include "net/http/http_response_headers.h" #include "net/url_request/url_fetcher.h" namespace update_client { +namespace { + +// This is an ECDSA prime256v1 named-curve key. +const int kKeyVersion = 5; +const char kKeyPubBytesBase64[] = + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEB+Yi+3SdJKCyFJmm+suW3CyXygvVsbDbPnJgoC" + "X4GeTtoL8Q/WjPx7CGtXOL1Xjbx0VPPN3DrvqZSL/oXy9hVw=="; + +} // namespace + +// This value is chosen not to conflict with network errors defined by +// net/base/net_error_list.h. The callers don't have to handle this error in +// any meaningful way, but this value may be reported in UMA stats, therefore +// avoiding collisions with known network errors is desirable. +int RequestSender::kErrorResponseNotTrusted = -10000; + RequestSender::RequestSender(const scoped_refptr<Configurator>& config) - : config_(config) {} + : config_(config), use_signing_(false) {} RequestSender::~RequestSender() { DCHECK(thread_checker_.CalledOnValidThread()); } -void RequestSender::Send(const std::string& request_string, +void RequestSender::Send(bool use_signing, + const std::string& request_body, const std::vector<GURL>& urls, const RequestSenderCallback& request_sender_callback) { DCHECK(thread_checker_.CalledOnValidThread()); - if (urls.empty()) { - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::Bind(request_sender_callback, nullptr)); - return; - } - request_string_ = request_string; + use_signing_ = use_signing; + request_body_ = request_body; urls_ = urls; request_sender_callback_ = request_sender_callback; + if (urls_.empty()) { + return HandleSendError(-1); + } + cur_url_ = urls_.begin(); + if (use_signing_) { + public_key_ = GetKey(kKeyPubBytesBase64); + if (public_key_.empty()) + return HandleSendError(-1); + } + SendInternal(); } @@ -47,17 +74,48 @@ void RequestSender::SendInternal() { DCHECK(cur_url_->is_valid()); DCHECK(thread_checker_.CalledOnValidThread()); - url_fetcher_ = SendProtocolRequest(*cur_url_, request_string_, this, - config_->RequestContext()); + GURL url(*cur_url_); + + if (use_signing_) { + DCHECK(!public_key_.empty()); + signer_ = ClientUpdateProtocolEcdsa::Create(kKeyVersion, public_key_); + std::string request_query_string; + signer_->SignRequest(request_body_, &request_query_string); + + url = BuildUpdateUrl(url, request_query_string); + } + + url_fetcher_ = + SendProtocolRequest(url, request_body_, this, config_->RequestContext()); + if (!url_fetcher_.get()) + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&RequestSender::SendInternalComplete, base::Unretained(this), + -1, std::string(), std::string())); } -void RequestSender::OnURLFetchComplete(const net::URLFetcher* source) { - DCHECK(thread_checker_.CalledOnValidThread()); - if (GetFetchError(*source) == 0) { - request_sender_callback_.Run(source); - return; +void RequestSender::SendInternalComplete(int error, + const std::string& response_body, + const std::string& response_etag) { + if (!error) { + if (!use_signing_) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(request_sender_callback_, 0, response_body)); + return; + } + + DCHECK(use_signing_); + DCHECK(signer_.get()); + if (signer_->ValidateResponse(response_body, response_etag)) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(request_sender_callback_, 0, response_body)); + return; + } + + error = kErrorResponseNotTrusted; } + DCHECK(error); if (++cur_url_ != urls_.end() && base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, @@ -65,8 +123,58 @@ void RequestSender::OnURLFetchComplete(const net::URLFetcher* source) { return; } + HandleSendError(error); +} + +void RequestSender::OnURLFetchComplete(const net::URLFetcher* source) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(source); + + VLOG(1) << "request completed from url: " << source->GetOriginalURL().spec(); + + const int fetch_error(GetFetchError(*source)); + std::string response_body; + CHECK(source->GetResponseAsString(&response_body)); + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&RequestSender::SendInternalComplete, base::Unretained(this), + fetch_error, response_body, GetServerETag(source))); +} + +void RequestSender::HandleSendError(int error) { base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::Bind(request_sender_callback_, source)); + FROM_HERE, base::Bind(request_sender_callback_, error, std::string())); +} + +std::string RequestSender::GetKey(const char* key_bytes_base64) { + std::string result; + return base::Base64Decode(std::string(key_bytes_base64), &result) + ? result + : std::string(); +} + +GURL RequestSender::BuildUpdateUrl(const GURL& url, + const std::string& query_params) { + const std::string query_string( + url.has_query() ? base::StringPrintf("%s&%s", url.query().c_str(), + query_params.c_str()) + : query_params); + GURL::Replacements replacements; + replacements.SetQueryStr(query_string); + + return url.ReplaceComponents(replacements); +} + +std::string RequestSender::GetServerETag(const net::URLFetcher* source) { + const auto response_headers(source->GetResponseHeaders()); + if (!response_headers) + return std::string(); + + std::string etag; + return response_headers->EnumerateHeader(nullptr, "ETag", &etag) + ? etag + : std::string(); } } // namespace update_client diff --git a/components/update_client/request_sender.h b/components/update_client/request_sender.h index b6ec23b..71b41ee 100644 --- a/components/update_client/request_sender.h +++ b/components/update_client/request_sender.h @@ -22,39 +22,69 @@ class URLFetcher; namespace update_client { +class ClientUpdateProtocolEcdsa; class Configurator; // Sends a request to one of the urls provided. The class implements a chain // of responsibility design pattern, where the urls are tried in the order they // are specified, until the request to one of them succeeds or all have failed. +// CUP signing is optional. class RequestSender : public net::URLFetcherDelegate { public: - // The |source| refers to the fetcher object used to make the request. This - // parameter can be NULL in some error cases. - typedef base::Callback<void(const net::URLFetcher* source)> - RequestSenderCallback; + // If |error| is 0, then the response is provided in the |response| parameter. + using RequestSenderCallback = + base::Callback<void(int error, const std::string& response)>; + + static int kErrorResponseNotTrusted; explicit RequestSender(const scoped_refptr<Configurator>& config); ~RequestSender() override; - void Send(const std::string& request_string, + // |use_signing| enables CUP signing of protocol messages exchanged using + // this class. + void Send(bool use_signing, + const std::string& request_body, const std::vector<GURL>& urls, const RequestSenderCallback& request_sender_callback); private: - void SendInternal(); + // Combines the |url| and |query_params| parameters. + static GURL BuildUpdateUrl(const GURL& url, const std::string& query_params); + + // Decodes and returns the public key used by CUP. + static std::string GetKey(const char* key_bytes_base64); + + // Returns the Etag of the server response or an empty string if the + // Etag is not available. + static std::string GetServerETag(const net::URLFetcher* source); // Overrides for URLFetcherDelegate. void OnURLFetchComplete(const net::URLFetcher* source) override; + // Implements the error handling and url fallback mechanism. + void SendInternal(); + + // Called when SendInternal complets. |response_body|and |response_etag| + // contain the body and the etag associated with the HTTP response. + void SendInternalComplete(int error, + const std::string& response_body, + const std::string& response_etag); + + // Helper function to handle a non-continuable error in Send. + void HandleSendError(int error); + + base::ThreadChecker thread_checker_; + const scoped_refptr<Configurator> config_; + bool use_signing_; std::vector<GURL> urls_; - std::vector<GURL>::const_iterator cur_url_; - scoped_ptr<net::URLFetcher> url_fetcher_; - std::string request_string_; + std::string request_body_; RequestSenderCallback request_sender_callback_; - base::ThreadChecker thread_checker_; + std::string public_key_; + std::vector<GURL>::const_iterator cur_url_; + scoped_ptr<net::URLFetcher> url_fetcher_; + scoped_ptr<ClientUpdateProtocolEcdsa> signer_; DISALLOW_COPY_AND_ASSIGN(RequestSender); }; diff --git a/components/update_client/request_sender_unittest.cc b/components/update_client/request_sender_unittest.cc index 527eb26..5132fd9 100644 --- a/components/update_client/request_sender_unittest.cc +++ b/components/update_client/request_sender_unittest.cc @@ -6,6 +6,7 @@ #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/run_loop.h" +#include "base/strings/string_util.h" #include "base/thread_task_runner_handle.h" #include "components/update_client/request_sender.h" #include "components/update_client/test_configurator.h" @@ -22,6 +23,17 @@ const char kUrl2[] = "https://localhost2/path2"; const char kUrlPath1[] = "path1"; const char kUrlPath2[] = "path2"; +// TODO(sorin): refactor as a utility function for unit tests. +base::FilePath test_file(const char* file) { + base::FilePath path; + PathService::Get(base::DIR_SOURCE_ROOT, &path); + return path.AppendASCII("components") + .AppendASCII("test") + .AppendASCII("data") + .AppendASCII("update_client") + .AppendASCII(file); +} + } // namespace class RequestSenderTest : public testing::Test { @@ -33,7 +45,7 @@ class RequestSenderTest : public testing::Test { void SetUp() override; void TearDown() override; - void RequestSenderComplete(const net::URLFetcher* source); + void RequestSenderComplete(int error, const std::string& response); protected: void Quit(); @@ -47,7 +59,8 @@ class RequestSenderTest : public testing::Test { URLRequestPostInterceptor* post_interceptor_1_; // Owned by the factory. URLRequestPostInterceptor* post_interceptor_2_; // Owned by the factory. - const net::URLFetcher* url_fetcher_source_; + int error_; + std::string response_; private: base::MessageLoopForIO loop_; @@ -57,13 +70,9 @@ class RequestSenderTest : public testing::Test { }; RequestSenderTest::RequestSenderTest() - : post_interceptor_1_(nullptr), - post_interceptor_2_(nullptr), - url_fetcher_source_(nullptr) { -} + : post_interceptor_1_(nullptr), post_interceptor_2_(nullptr), error_(0) {} -RequestSenderTest::~RequestSenderTest() { -} +RequestSenderTest::~RequestSenderTest() {} void RequestSenderTest::SetUp() { config_ = new TestConfigurator(base::ThreadTaskRunnerHandle::Get(), @@ -114,21 +123,25 @@ void RequestSenderTest::Quit() { quit_closure_.Run(); } -void RequestSenderTest::RequestSenderComplete(const net::URLFetcher* source) { - url_fetcher_source_ = source; +void RequestSenderTest::RequestSenderComplete(int error, + const std::string& response) { + error_ = error; + response_ = response; + Quit(); } // Tests that when a request to the first url succeeds, the subsequent urls are // not tried. TEST_F(RequestSenderTest, RequestSendSuccess) { - EXPECT_TRUE(post_interceptor_1_->ExpectRequest(new PartialMatch("test"))); + EXPECT_TRUE(post_interceptor_1_->ExpectRequest( + new PartialMatch("test"), test_file("updatecheck_reply_1.xml"))); std::vector<GURL> urls; urls.push_back(GURL(kUrl1)); urls.push_back(GURL(kUrl2)); request_sender_.reset(new RequestSender(config_)); - request_sender_->Send("test", urls, + request_sender_->Send(false, "test", urls, base::Bind(&RequestSenderTest::RequestSenderComplete, base::Unretained(this))); RunThreads(); @@ -138,9 +151,15 @@ TEST_F(RequestSenderTest, RequestSendSuccess) { EXPECT_EQ(1, post_interceptor_1_->GetCount()) << post_interceptor_1_->GetRequestsAsString(); + // Sanity check the request. EXPECT_STREQ("test", post_interceptor_1_->GetRequests()[0].c_str()); - EXPECT_EQ(GURL(kUrl1), url_fetcher_source_->GetOriginalURL()); - EXPECT_EQ(200, url_fetcher_source_->GetResponseCode()); + + // Check the response post conditions. + EXPECT_EQ(0, error_); + EXPECT_TRUE(base::StartsWith(response_, + "<?xml version='1.0' encoding='UTF-8'?>", + base::CompareCase::SENSITIVE)); + EXPECT_EQ(443ul, response_.size()); } // Tests that the request succeeds using the second url after the first url @@ -154,7 +173,7 @@ TEST_F(RequestSenderTest, RequestSendSuccessWithFallback) { urls.push_back(GURL(kUrl1)); urls.push_back(GURL(kUrl2)); request_sender_.reset(new RequestSender(config_)); - request_sender_->Send("test", urls, + request_sender_->Send(false, "test", urls, base::Bind(&RequestSenderTest::RequestSenderComplete, base::Unretained(this))); RunThreads(); @@ -170,8 +189,7 @@ TEST_F(RequestSenderTest, RequestSendSuccessWithFallback) { EXPECT_STREQ("test", post_interceptor_1_->GetRequests()[0].c_str()); EXPECT_STREQ("test", post_interceptor_2_->GetRequests()[0].c_str()); - EXPECT_EQ(GURL(kUrl2), url_fetcher_source_->GetOriginalURL()); - EXPECT_EQ(200, url_fetcher_source_->GetResponseCode()); + EXPECT_EQ(0, error_); } // Tests that the request fails when both urls have failed. @@ -185,7 +203,7 @@ TEST_F(RequestSenderTest, RequestSendFailed) { urls.push_back(GURL(kUrl1)); urls.push_back(GURL(kUrl2)); request_sender_.reset(new RequestSender(config_)); - request_sender_->Send("test", urls, + request_sender_->Send(false, "test", urls, base::Bind(&RequestSenderTest::RequestSenderComplete, base::Unretained(this))); RunThreads(); @@ -201,20 +219,42 @@ TEST_F(RequestSenderTest, RequestSendFailed) { EXPECT_STREQ("test", post_interceptor_1_->GetRequests()[0].c_str()); EXPECT_STREQ("test", post_interceptor_2_->GetRequests()[0].c_str()); - EXPECT_EQ(GURL(kUrl2), url_fetcher_source_->GetOriginalURL()); - EXPECT_EQ(403, url_fetcher_source_->GetResponseCode()); + EXPECT_EQ(403, error_); } // Tests that the request fails when no urls are provided. TEST_F(RequestSenderTest, RequestSendFailedNoUrls) { std::vector<GURL> urls; request_sender_.reset(new RequestSender(config_)); - request_sender_->Send("test", urls, + request_sender_->Send(false, "test", urls, + base::Bind(&RequestSenderTest::RequestSenderComplete, + base::Unretained(this))); + RunThreads(); + + EXPECT_EQ(-1, error_); +} + +// Tests that a CUP request fails if the response is not signed. +TEST_F(RequestSenderTest, RequestSendCupError) { + EXPECT_TRUE(post_interceptor_1_->ExpectRequest( + new PartialMatch("test"), test_file("updatecheck_reply_1.xml"))); + + std::vector<GURL> urls; + urls.push_back(GURL(kUrl1)); + request_sender_.reset(new RequestSender(config_)); + request_sender_->Send(true, "test", urls, base::Bind(&RequestSenderTest::RequestSenderComplete, base::Unretained(this))); RunThreads(); - EXPECT_EQ(nullptr, url_fetcher_source_); + EXPECT_EQ(1, post_interceptor_1_->GetHitCount()) + << post_interceptor_1_->GetRequestsAsString(); + EXPECT_EQ(1, post_interceptor_1_->GetCount()) + << post_interceptor_1_->GetRequestsAsString(); + + EXPECT_STREQ("test", post_interceptor_1_->GetRequests()[0].c_str()); + EXPECT_EQ(RequestSender::kErrorResponseNotTrusted, error_); + EXPECT_TRUE(response_.empty()); } } // namespace update_client diff --git a/components/update_client/test_configurator.cc b/components/update_client/test_configurator.cc index 1e4e76e..f082bb8 100644 --- a/components/update_client/test_configurator.cc +++ b/components/update_client/test_configurator.cc @@ -27,8 +27,8 @@ TestConfigurator::TestConfigurator( : worker_task_runner_(worker_task_runner), initial_time_(0), ondemand_time_(0), - context_(new net::TestURLRequestContextGetter(network_task_runner)) { -} + use_cup_signing_(false), + context_(new net::TestURLRequestContextGetter(network_task_runner)) {} TestConfigurator::~TestConfigurator() { } @@ -103,6 +103,10 @@ bool TestConfigurator::UseBackgroundDownloader() const { return false; } +bool TestConfigurator::UseCupSigning() const { + return use_cup_signing_; +} + void TestConfigurator::SetOnDemandTime(int seconds) { ondemand_time_ = seconds; } @@ -111,6 +115,10 @@ void TestConfigurator::SetInitialDelay(int seconds) { initial_time_ = seconds; } +void TestConfigurator::SetUseCupSigning(bool use_cup_signing) { + use_cup_signing_ = use_cup_signing; +} + void TestConfigurator::SetDownloadPreference( const std::string& download_preference) { download_preference_ = download_preference; diff --git a/components/update_client/test_configurator.h b/components/update_client/test_configurator.h index 2aa5ff2..e4c72f2a 100644 --- a/components/update_client/test_configurator.h +++ b/components/update_client/test_configurator.h @@ -73,12 +73,14 @@ class TestConfigurator : public Configurator { scoped_refptr<OutOfProcessPatcher> CreateOutOfProcessPatcher() const override; bool DeltasEnabled() const override; bool UseBackgroundDownloader() const override; + bool UseCupSigning() const override; scoped_refptr<base::SequencedTaskRunner> GetSequencedTaskRunner() const override; void SetOnDemandTime(int seconds); void SetInitialDelay(int seconds); void SetDownloadPreference(const std::string& download_preference); + void SetUseCupSigning(bool use_cup_signing); private: friend class base::RefCountedThreadSafe<TestConfigurator>; @@ -90,6 +92,7 @@ class TestConfigurator : public Configurator { int initial_time_; int ondemand_time_; std::string download_preference_; + bool use_cup_signing_; scoped_refptr<net::TestURLRequestContextGetter> context_; diff --git a/components/update_client/update_checker.cc b/components/update_client/update_checker.cc index 16a4a95..dd851d9 100644 --- a/components/update_client/update_checker.cc +++ b/components/update_client/update_checker.cc @@ -22,7 +22,6 @@ #include "components/update_client/crx_update_item.h" #include "components/update_client/request_sender.h" #include "components/update_client/utils.h" -#include "net/url_request/url_fetcher.h" #include "url/gurl.h" namespace update_client { @@ -86,14 +85,13 @@ class UpdateCheckerImpl : public UpdateChecker { const UpdateCheckCallback& update_check_callback) override; private: - void OnRequestSenderComplete(const net::URLFetcher* source); + void OnRequestSenderComplete(int error, const std::string& response); + base::ThreadChecker thread_checker_; const scoped_refptr<Configurator> config_; UpdateCheckCallback update_check_callback_; scoped_ptr<RequestSender> request_sender_; - base::ThreadChecker thread_checker_; - DISALLOW_COPY_AND_ASSIGN(UpdateCheckerImpl); }; @@ -119,6 +117,7 @@ bool UpdateCheckerImpl::CheckForUpdates( request_sender_.reset(new RequestSender(config_)); request_sender_->Send( + config_->UseCupSigning(), BuildUpdateCheckRequest(*config_, items_to_check, additional_attributes), config_->UpdateUrl(), base::Bind(&UpdateCheckerImpl::OnRequestSenderComplete, @@ -126,42 +125,26 @@ bool UpdateCheckerImpl::CheckForUpdates( return true; } -void UpdateCheckerImpl::OnRequestSenderComplete(const net::URLFetcher* source) { +void UpdateCheckerImpl::OnRequestSenderComplete(int error, + const std::string& response) { DCHECK(thread_checker_.CalledOnValidThread()); - GURL original_url; - int error = 0; - std::string error_message; - UpdateResponse update_response; - - if (source) { - original_url = source->GetOriginalURL(); - VLOG(1) << "Update check request went to: " << original_url.spec(); - if (FetchSuccess(*source)) { - std::string xml; - source->GetResponseAsString(&xml); - if (!update_response.Parse(xml)) { - error = -1; - error_message = update_response.errors(); - } - } else { - error = GetFetchError(*source); - error_message.assign("network error"); + if (!error) { + UpdateResponse update_response; + if (update_response.Parse(response)) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(update_check_callback_, error, update_response.results())); + return; } - } else { - error = -1; - error_message = "no fetcher"; - } - if (error) { - VLOG(1) << "Update request failed: " << error_message; + error = -1; + VLOG(1) << "Parse failed " << update_response.errors(); } - request_sender_.reset(); - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::Bind(update_check_callback_, original_url, error, - error_message, update_response.results())); + FROM_HERE, + base::Bind(update_check_callback_, error, UpdateResponse::Results())); } } // namespace diff --git a/components/update_client/update_checker.h b/components/update_client/update_checker.h index 076abe9..1de7f0c 100644 --- a/components/update_client/update_checker.h +++ b/components/update_client/update_checker.h @@ -17,10 +17,6 @@ class GURL; -namespace net { -class URLRequestContextGetter; -} - namespace update_client { class Configurator; @@ -29,10 +25,7 @@ struct CrxUpdateItem; class UpdateChecker { public: using UpdateCheckCallback = - base::Callback<void(const GURL& original_url, - int error, - const std::string& error_message, - const UpdateResponse::Results& results)>; + base::Callback<void(int error, const UpdateResponse::Results& results)>; using Factory = scoped_ptr<UpdateChecker> (*)(const scoped_refptr<Configurator>& config); diff --git a/components/update_client/update_checker_unittest.cc b/components/update_client/update_checker_unittest.cc index 0a799c8..85457b9 100644 --- a/components/update_client/update_checker_unittest.cc +++ b/components/update_client/update_checker_unittest.cc @@ -47,10 +47,7 @@ class UpdateCheckerTest : public testing::Test { void SetUp() override; void TearDown() override; - void UpdateCheckComplete(const GURL& original_url, - int error, - const std::string& error_message, - const UpdateResponse::Results& results); + void UpdateCheckComplete(int error, const UpdateResponse::Results& results); protected: void Quit(); @@ -66,9 +63,7 @@ class UpdateCheckerTest : public testing::Test { scoped_ptr<InterceptorFactory> interceptor_factory_; URLRequestPostInterceptor* post_interceptor_; // Owned by the factory. - GURL original_url_; int error_; - std::string error_message_; UpdateResponse::Results results_; private: @@ -95,7 +90,6 @@ void UpdateCheckerTest::SetUp() { update_checker_.reset(); error_ = 0; - error_message_.clear(); results_ = UpdateResponse::Results(); } @@ -134,13 +128,9 @@ void UpdateCheckerTest::Quit() { } void UpdateCheckerTest::UpdateCheckComplete( - const GURL& original_url, int error, - const std::string& error_message, const UpdateResponse::Results& results) { - original_url_ = original_url; error_ = error; - error_message_ = error_message; results_ = results; Quit(); } @@ -199,10 +189,7 @@ TEST_F(UpdateCheckerTest, UpdateCheckSuccess) { post_interceptor_->GetRequests()[0].find("<hw physmemory=")); // Sanity check the arguments of the callback after parsing. - ASSERT_FALSE(config_->UpdateUrl().empty()); - EXPECT_EQ(config_->UpdateUrl().front(), original_url_); EXPECT_EQ(0, error_); - EXPECT_TRUE(error_message_.empty()); EXPECT_EQ(1ul, results_.list.size()); EXPECT_STREQ("jebgalgnebhfojomionfpkfelancnnkf", results_.list[0].extension_id.c_str()); @@ -223,7 +210,6 @@ TEST_F(UpdateCheckerTest, UpdateCheckError) { update_checker_->CheckForUpdates( items_to_check, "", base::Bind(&UpdateCheckerTest::UpdateCheckComplete, base::Unretained(this))); - RunThreads(); EXPECT_EQ(1, post_interceptor_->GetHitCount()) @@ -231,10 +217,7 @@ TEST_F(UpdateCheckerTest, UpdateCheckError) { EXPECT_EQ(1, post_interceptor_->GetCount()) << post_interceptor_->GetRequestsAsString(); - ASSERT_FALSE(config_->UpdateUrl().empty()); - EXPECT_EQ(config_->UpdateUrl().front(), original_url_); EXPECT_EQ(403, error_); - EXPECT_STREQ("network error", error_message_.c_str()); EXPECT_EQ(0ul, results_.list.size()); } @@ -262,4 +245,41 @@ TEST_F(UpdateCheckerTest, UpdateCheckDownloadPreference) { post_interceptor_->GetRequests()[0].find(" dlpref=\"cacheable\"")); } +// This test is checking that an update check signed with CUP fails, since there +// is currently no entity that can respond with a valid signed response. +// A proper CUP test requires network mocks, which are not available now. +TEST_F(UpdateCheckerTest, UpdateCheckCupError) { + EXPECT_TRUE(post_interceptor_->ExpectRequest( + new PartialMatch("updatecheck"), test_file("updatecheck_reply_1.xml"))); + + config_->SetUseCupSigning(true); + update_checker_ = UpdateChecker::Create(config_); + + CrxUpdateItem item(BuildCrxUpdateItem()); + std::vector<CrxUpdateItem*> items_to_check; + items_to_check.push_back(&item); + + update_checker_->CheckForUpdates( + items_to_check, "", base::Bind(&UpdateCheckerTest::UpdateCheckComplete, + base::Unretained(this))); + + RunThreads(); + + EXPECT_EQ(1, post_interceptor_->GetHitCount()) + << post_interceptor_->GetRequestsAsString(); + ASSERT_EQ(1, post_interceptor_->GetCount()) + << post_interceptor_->GetRequestsAsString(); + + // Sanity check the request. + EXPECT_NE( + string::npos, + post_interceptor_->GetRequests()[0].find( + "app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" version=\"0.9\">" + "<updatecheck /><packages><package fp=\"fp1\"/></packages></app>")); + + // Expect an error since the response is not trusted. + EXPECT_EQ(-10000, error_); + EXPECT_EQ(0ul, results_.list.size()); +} + } // namespace update_client diff --git a/components/update_client/update_client_unittest.cc b/components/update_client/update_client_unittest.cc index 8a245ad..a0bee0f 100644 --- a/components/update_client/update_client_unittest.cc +++ b/components/update_client/update_client_unittest.cc @@ -226,8 +226,8 @@ TEST_F(UpdateClientTest, OneCrxNoUpdate) { const std::string& additional_attributes, const UpdateCheckCallback& update_check_callback) override { base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::Bind(update_check_callback, GURL(), 0, "", - UpdateResponse::Results())); + FROM_HERE, + base::Bind(update_check_callback, 0, UpdateResponse::Results())); return true; } }; @@ -368,7 +368,7 @@ TEST_F(UpdateClientTest, TwoCrxUpdateNoUpdate) { results.list.push_back(result); base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::Bind(update_check_callback, GURL(), 0, "", results)); + FROM_HERE, base::Bind(update_check_callback, 0, results)); return true; } }; @@ -580,7 +580,7 @@ TEST_F(UpdateClientTest, TwoCrxUpdate) { results.list.push_back(result2); base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::Bind(update_check_callback, GURL(), 0, "", results)); + FROM_HERE, base::Bind(update_check_callback, 0, results)); return true; } }; @@ -827,7 +827,7 @@ TEST_F(UpdateClientTest, TwoCrxUpdateDownloadTimeout) { results.list.push_back(result2); base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::Bind(update_check_callback, GURL(), 0, "", results)); + FROM_HERE, base::Bind(update_check_callback, 0, results)); return true; } }; @@ -1099,7 +1099,7 @@ TEST_F(UpdateClientTest, OneCrxDiffUpdate) { } base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::Bind(update_check_callback, GURL(), 0, "", results)); + FROM_HERE, base::Bind(update_check_callback, 0, results)); return true; } }; @@ -1340,7 +1340,7 @@ TEST_F(UpdateClientTest, OneCrxInstallError) { results.list.push_back(result); base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::Bind(update_check_callback, GURL(), 0, "", results)); + FROM_HERE, base::Bind(update_check_callback, 0, results)); return true; } }; @@ -1573,7 +1573,7 @@ TEST_F(UpdateClientTest, OneCrxDiffUpdateFailsFullUpdateSucceeds) { } base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::Bind(update_check_callback, GURL(), 0, "", results)); + FROM_HERE, base::Bind(update_check_callback, 0, results)); return true; } }; @@ -1777,8 +1777,8 @@ TEST_F(UpdateClientTest, OneCrxNoUpdateQueuedCall) { const std::string& additional_attributes, const UpdateCheckCallback& update_check_callback) override { base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::Bind(update_check_callback, GURL(), 0, "", - UpdateResponse::Results())); + FROM_HERE, + base::Bind(update_check_callback, 0, UpdateResponse::Results())); return true; } }; @@ -1911,7 +1911,7 @@ TEST_F(UpdateClientTest, OneCrxInstall) { results.list.push_back(result); base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::Bind(update_check_callback, GURL(), 0, "", results)); + FROM_HERE, base::Bind(update_check_callback, 0, results)); return true; } }; @@ -2062,8 +2062,8 @@ TEST_F(UpdateClientTest, ConcurrentInstallSameCRX) { const std::string& additional_attributes, const UpdateCheckCallback& update_check_callback) override { base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::Bind(update_check_callback, GURL(), 0, "", - UpdateResponse::Results())); + FROM_HERE, + base::Bind(update_check_callback, 0, UpdateResponse::Results())); return true; } }; diff --git a/components/update_client/utils.cc b/components/update_client/utils.cc index 75b0d00..a6ec277 100644 --- a/components/update_client/utils.cc +++ b/components/update_client/utils.cc @@ -133,6 +133,8 @@ scoped_ptr<net::URLFetcher> SendProtocolRequest( net::URLRequestContextGetter* url_request_context_getter) { scoped_ptr<net::URLFetcher> url_fetcher = net::URLFetcher::Create( 0, url, net::URLFetcher::POST, url_fetcher_delegate); + if (!url_fetcher.get()) + return url_fetcher; url_fetcher->SetUploadData("application/xml", protocol_request); url_fetcher->SetRequestContext(url_request_context_getter); diff --git a/ios/chrome/browser/component_updater/ios_component_updater_configurator.cc b/ios/chrome/browser/component_updater/ios_component_updater_configurator.cc index 1c1025c..ea16f56 100644 --- a/ios/chrome/browser/component_updater/ios_component_updater_configurator.cc +++ b/ios/chrome/browser/component_updater/ios_component_updater_configurator.cc @@ -5,6 +5,7 @@ #include "ios/chrome/browser/component_updater/ios_component_updater_configurator.h" #include <string> +#include <vector> #include "base/threading/sequenced_worker_pool.h" #include "base/version.h" @@ -42,6 +43,7 @@ class IOSConfigurator : public update_client::Configurator { const override; bool DeltasEnabled() const override; bool UseBackgroundDownloader() const override; + bool UseCupSigning() const override; scoped_refptr<base::SequencedTaskRunner> GetSequencedTaskRunner() const override; @@ -127,6 +129,10 @@ bool IOSConfigurator::UseBackgroundDownloader() const { return configurator_impl_.UseBackgroundDownloader(); } +bool IOSConfigurator::UseCupSigning() const { + return configurator_impl_.UseCupSigning(); +} + scoped_refptr<base::SequencedTaskRunner> IOSConfigurator::GetSequencedTaskRunner() const { return web::WebThread::GetBlockingPool() |