| Index: components/network_time/network_time_tracker.cc
|
| diff --git a/components/network_time/network_time_tracker.cc b/components/network_time/network_time_tracker.cc
|
| index 73f66ddfbd710706e5b75cb8379013414e49f570..1dbfd820fa2d0bfb0b8cf38131c01aca887eaeba 100644
|
| --- a/components/network_time/network_time_tracker.cc
|
| +++ b/components/network_time/network_time_tracker.cc
|
| @@ -5,21 +5,35 @@
|
| #include "components/network_time/network_time_tracker.h"
|
|
|
| #include <stdint.h>
|
| +#include <string>
|
| #include <utility>
|
|
|
| +#include "base/feature_list.h"
|
| #include "base/i18n/time_formatting.h"
|
| +#include "base/json/json_reader.h"
|
| #include "base/logging.h"
|
| +#include "base/message_loop/message_loop.h"
|
| #include "base/strings/utf_string_conversions.h"
|
| #include "base/time/tick_clock.h"
|
| #include "build/build_config.h"
|
| +#include "components/client_update_protocol/ecdsa.h"
|
| #include "components/network_time/network_time_pref_names.h"
|
| #include "components/prefs/pref_registry_simple.h"
|
| #include "components/prefs/pref_service.h"
|
| +#include "net/base/load_flags.h"
|
| +#include "net/base/net_errors.h"
|
| +#include "net/http/http_response_headers.h"
|
| +#include "net/url_request/url_fetcher.h"
|
| +#include "net/url_request/url_fetcher_response_writer.h"
|
| +#include "net/url_request/url_request_context_getter.h"
|
|
|
| namespace network_time {
|
|
|
| namespace {
|
|
|
| +// Minimum number of minutes between time queries.
|
| +const uint32_t kMinimumQueryDelayMinutes = 60;
|
| +
|
| // Number of time measurements performed in a given network time calculation.
|
| const uint32_t kNumTimeMeasurements = 7;
|
|
|
| @@ -41,6 +55,61 @@ const char kPrefUncertainty[] = "uncertainty";
|
| // Name of a pref that stores the network time via |ToJsTime|.
|
| const char kPrefNetworkTime[] = "network";
|
|
|
| +// Time server's maximum allowable clock skew, in seconds. (This is a property
|
| +// of the time server that we happen to know. It's unlikely that it would ever
|
| +// be that badly wrong, but all the same it's included here to document the very
|
| +// rough nature of the time service provided by this class.)
|
| +const uint32_t kTimeServerMaxSkewSeconds = 10;
|
| +
|
| +const char kTimeServiceURL[] = "http://clients2.google.com/time/1/current";
|
| +
|
| +// Finch feature that enables network time service querying.
|
| +const base::Feature kNetworkTimeServiceQuerying{
|
| + "NetworkTimeServiceQuerying", base::FEATURE_DISABLED_BY_DEFAULT};
|
| +
|
| +// This is an ECDSA prime256v1 named-curve key.
|
| +const int kKeyVersion = 1;
|
| +const uint8_t kKeyPubBytes[] = {
|
| + 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02,
|
| + 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03,
|
| + 0x42, 0x00, 0x04, 0xeb, 0xd8, 0xad, 0x0b, 0x8f, 0x75, 0xe8, 0x84, 0x36,
|
| + 0x23, 0x48, 0x14, 0x24, 0xd3, 0x93, 0x42, 0x25, 0x43, 0xc1, 0xde, 0x36,
|
| + 0x29, 0xc6, 0x95, 0xca, 0xeb, 0x28, 0x85, 0xff, 0x09, 0xdc, 0x08, 0xec,
|
| + 0x45, 0x74, 0x6e, 0x4b, 0xc3, 0xa5, 0xfd, 0x8a, 0x2f, 0x02, 0xa0, 0x4b,
|
| + 0xc3, 0xc6, 0xa4, 0x7b, 0xa4, 0x41, 0xfc, 0xa7, 0x02, 0x54, 0xab, 0xe3,
|
| + 0xe4, 0xb1, 0x00, 0xf5, 0xd5, 0x09, 0x11};
|
| +
|
| +std::string GetServerProof(const net::URLFetcher* source) {
|
| + const net::HttpResponseHeaders* response_headers =
|
| + source->GetResponseHeaders();
|
| + if (!response_headers) {
|
| + return std::string();
|
| + }
|
| + std::string proof;
|
| + return response_headers->EnumerateHeader(nullptr, "x-cup-server-proof",
|
| + &proof)
|
| + ? proof
|
| + : std::string();
|
| +}
|
| +
|
| +// Limits the amount of data that will be buffered from the server's response.
|
| +class SizeLimitingStringWriter : public net::URLFetcherStringWriter {
|
| + public:
|
| + explicit SizeLimitingStringWriter(size_t limit) : limit_(limit) {}
|
| +
|
| + int Write(net::IOBuffer* buffer,
|
| + int num_bytes,
|
| + const net::CompletionCallback& callback) override {
|
| + if (data().length() + num_bytes > limit_) {
|
| + return net::ERR_FILE_TOO_BIG;
|
| + }
|
| + return net::URLFetcherStringWriter::Write(buffer, num_bytes, callback);
|
| + }
|
| +
|
| + private:
|
| + size_t limit_;
|
| +};
|
| +
|
| } // namespace
|
|
|
| // static
|
| @@ -52,8 +121,13 @@ void NetworkTimeTracker::RegisterPrefs(PrefRegistrySimple* registry) {
|
| NetworkTimeTracker::NetworkTimeTracker(
|
| std::unique_ptr<base::Clock> clock,
|
| std::unique_ptr<base::TickClock> tick_clock,
|
| - PrefService* pref_service)
|
| - : clock_(std::move(clock)),
|
| + PrefService* pref_service,
|
| + scoped_refptr<net::URLRequestContextGetter> getter)
|
| + : server_url_(kTimeServiceURL),
|
| + max_response_size_(1024),
|
| + getter_(std::move(getter)),
|
| + loop_(nullptr),
|
| + clock_(std::move(clock)),
|
| tick_clock_(std::move(tick_clock)),
|
| pref_service_(pref_service) {
|
| const base::DictionaryValue* time_mapping =
|
| @@ -83,6 +157,13 @@ NetworkTimeTracker::NetworkTimeTracker(
|
| pref_service_->ClearPref(prefs::kNetworkTimeMapping);
|
| network_time_at_last_measurement_ = base::Time(); // Reset.
|
| }
|
| +
|
| + base::StringPiece public_key = {reinterpret_cast<const char*>(kKeyPubBytes),
|
| + sizeof(kKeyPubBytes)};
|
| + query_signer_ =
|
| + client_update_protocol::Ecdsa::Create(kKeyVersion, public_key);
|
| +
|
| + QueueTimeQuery(base::TimeDelta::FromMinutes(kMinimumQueryDelayMinutes));
|
| }
|
|
|
| NetworkTimeTracker::~NetworkTimeTracker() {
|
| @@ -132,6 +213,40 @@ void NetworkTimeTracker::UpdateNetworkTime(base::Time network_time,
|
| time_mapping.SetDouble(kPrefNetworkTime,
|
| network_time_at_last_measurement_.ToJsTime());
|
| pref_service_->Set(prefs::kNetworkTimeMapping, time_mapping);
|
| +
|
| + // Calls to update the network time can (as of this writing) come from various
|
| + // sources, e.g. organically from Omaha update checks. In that even, we may
|
| + // as well delay the next time server query. If |UpdateNetworkTime| is ever
|
| + // made into a private method, this can be removed.
|
| + query_timer_.Reset();
|
| +}
|
| +
|
| +void NetworkTimeTracker::SetTimeServerURLForTesting(const GURL& url) {
|
| + server_url_ = url;
|
| +}
|
| +
|
| +void NetworkTimeTracker::SetMaxResponseSizeForTesting(size_t limit) {
|
| + max_response_size_ = limit;
|
| +}
|
| +
|
| +void NetworkTimeTracker::SetPublicKeyForTesting(const base::StringPiece& key) {
|
| + query_signer_ = client_update_protocol::Ecdsa::Create(kKeyVersion, key);
|
| +}
|
| +
|
| +bool NetworkTimeTracker::QueryTimeServiceForTesting() {
|
| + QueryTimeService();
|
| + loop_ = base::MessageLoop::current(); // Gets Quit on completion.
|
| + return time_fetcher_ != nullptr;
|
| +}
|
| +
|
| +void NetworkTimeTracker::WaitForFetchForTesting(uint32_t nonce) {
|
| + query_signer_->OverrideNonceForTesting(kKeyVersion, nonce);
|
| + base::MessageLoop::current()->Run();
|
| +}
|
| +
|
| +base::TimeDelta NetworkTimeTracker::GetTimerDelayForTesting() const {
|
| + DCHECK(query_timer_.IsRunning());
|
| + return query_timer_.GetCurrentDelay();
|
| }
|
|
|
| bool NetworkTimeTracker::GetNetworkTime(base::Time* network_time,
|
| @@ -167,4 +282,117 @@ bool NetworkTimeTracker::GetNetworkTime(base::Time* network_time,
|
| return true;
|
| }
|
|
|
| +void NetworkTimeTracker::QueryTimeService() {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| +
|
| + // Do not query the time service if not enabled via Finch.
|
| + if (!base::FeatureList::IsEnabled(kNetworkTimeServiceQuerying)) {
|
| + return;
|
| + }
|
| +
|
| + // If GetNetworkTime() returns true, the NetworkTimeTracker thinks it is in
|
| + // sync, so there is no need to query.
|
| + base::Time network_time;
|
| + if (GetNetworkTime(&network_time, nullptr)) {
|
| + return;
|
| + }
|
| +
|
| + std::string query_string;
|
| + query_signer_->SignRequest(nullptr, &query_string);
|
| + GURL url = server_url_;
|
| + GURL::Replacements replacements;
|
| + replacements.SetQueryStr(query_string);
|
| + url = url.ReplaceComponents(replacements);
|
| +
|
| + // This cancels any outstanding fetch.
|
| + time_fetcher_ = net::URLFetcher::Create(url, net::URLFetcher::GET, this);
|
| + if (!time_fetcher_) {
|
| + DVLOG(1) << "tried to make fetch happen; failed";
|
| + return;
|
| + }
|
| + time_fetcher_->SaveResponseWithWriter(
|
| + std::unique_ptr<net::URLFetcherResponseWriter>(
|
| + new SizeLimitingStringWriter(max_response_size_)));
|
| + DCHECK(getter_);
|
| + time_fetcher_->SetRequestContext(getter_.get());
|
| + // Not expecting any cookies, but just in case.
|
| + time_fetcher_->SetLoadFlags(net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE |
|
| + net::LOAD_DO_NOT_SAVE_COOKIES |
|
| + net::LOAD_DO_NOT_SEND_COOKIES |
|
| + net::LOAD_DO_NOT_SEND_AUTH_DATA);
|
| + time_fetcher_->Start();
|
| + fetch_started_ = tick_clock_->NowTicks();
|
| +}
|
| +
|
| +bool NetworkTimeTracker::UpdateTimeFromResponse() {
|
| + if (time_fetcher_->GetStatus().status() != net::URLRequestStatus::SUCCESS &&
|
| + time_fetcher_->GetResponseCode() != 200) {
|
| + DVLOG(1) << "fetch failed, status=" << time_fetcher_->GetStatus().status()
|
| + << ",code=" << time_fetcher_->GetResponseCode();
|
| + return false;
|
| + }
|
| +
|
| + std::string response_body;
|
| + if (!time_fetcher_->GetResponseAsString(&response_body)) {
|
| + DVLOG(1) << "failed to get response";
|
| + return false;
|
| + }
|
| + DCHECK(query_signer_);
|
| + if (!query_signer_->ValidateResponse(response_body,
|
| + GetServerProof(time_fetcher_.get()))) {
|
| + DVLOG(1) << "invalid signature";
|
| + return false;
|
| + }
|
| + response_body = response_body.substr(5); // Skips leading )]}'\n
|
| + std::unique_ptr<base::Value> value = base::JSONReader::Read(response_body);
|
| + if (!value) {
|
| + DVLOG(1) << "bad JSON";
|
| + return false;
|
| + }
|
| + const base::DictionaryValue* dict;
|
| + if (!value->GetAsDictionary(&dict)) {
|
| + DVLOG(1) << "not a dictionary";
|
| + return false;
|
| + }
|
| + double current_time_millis;
|
| + if (!dict->GetDouble("current_time_millis", ¤t_time_millis)) {
|
| + DVLOG(1) << "no current_time_millis";
|
| + return false;
|
| + }
|
| + // There is a "server_nonce" key here too, but it serves no purpose other than
|
| + // to make the server's response unpredictable.
|
| + base::Time current_time = base::Time::FromJsTime(current_time_millis);
|
| + base::TimeDelta resolution =
|
| + base::TimeDelta::FromMilliseconds(1) +
|
| + base::TimeDelta::FromSeconds(kTimeServerMaxSkewSeconds);
|
| + base::TimeDelta latency = tick_clock_->NowTicks() - fetch_started_;
|
| + UpdateNetworkTime(current_time, resolution, latency, tick_clock_->NowTicks());
|
| + return true;
|
| +}
|
| +
|
| +void NetworkTimeTracker::OnURLFetchComplete(const net::URLFetcher* source) {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + DCHECK(time_fetcher_);
|
| + DCHECK_EQ(source, time_fetcher_.get());
|
| +
|
| + if (!UpdateTimeFromResponse()) { // On error, back off.
|
| + DCHECK(query_timer_.IsRunning());
|
| + base::TimeDelta delay = query_timer_.GetCurrentDelay();
|
| + if (delay < base::TimeDelta::FromDays(2)) {
|
| + delay *= 2;
|
| + }
|
| + QueueTimeQuery(delay);
|
| + }
|
| + time_fetcher_.reset();
|
| + if (loop_ != nullptr) {
|
| + loop_->QuitWhenIdle();
|
| + loop_ = nullptr;
|
| + }
|
| +}
|
| +
|
| +void NetworkTimeTracker::QueueTimeQuery(base::TimeDelta delay) {
|
| + query_timer_.Start(FROM_HERE, delay, this,
|
| + &NetworkTimeTracker::QueryTimeService);
|
| +}
|
| +
|
| } // namespace network_time
|
|
|