Chromium Code Reviews| 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..ad02929f5cf82b81a1d16fa28c84a87096cc7900 100644 |
| --- a/components/network_time/network_time_tracker.cc |
| +++ b/components/network_time/network_time_tracker.cc |
| @@ -8,18 +8,27 @@ |
| #include <utility> |
| #include "base/i18n/time_formatting.h" |
| +#include "base/json/json_reader.h" |
| #include "base/logging.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/http/http_response_headers.h" |
| +#include "net/url_request/url_fetcher.h" |
| +#include "url/gurl.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 +50,36 @@ 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. |
| +const uint32_t kTimeServerMaxSkewSeconds = 10; |
| + |
| +const char kTimeServiceURL[] = "http://clients2.google.com/time/1/current"; |
|
mmenke
2016/04/28 15:17:09
I thought there was a plan to move most google dom
mab
2016/04/29 19:42:07
I'll have to defer to waffles on the HSTS question
waffles
2016/04/29 19:51:22
mmenke: Please contact me (internally) with any ne
mmenke
2016/04/29 19:59:44
You're probably more knowledgeable here than I am.
|
| + |
| +// 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(); |
| +} |
| + |
| } // namespace |
| // static |
| @@ -52,8 +91,10 @@ 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) |
| + : getter_(getter), |
| + clock_(std::move(clock)), |
| tick_clock_(std::move(tick_clock)), |
| pref_service_(pref_service) { |
| const base::DictionaryValue* time_mapping = |
| @@ -83,12 +124,109 @@ 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)}; |
| + |
| + if (getter_) { |
| + query_signer_ = |
| + client_update_protocol::Ecdsa::Create(kKeyVersion, public_key); |
| + base::TimeDelta period = |
| + base::TimeDelta::FromMinutes(kMinimumQueryDelayMinutes); |
| + query_timer_.Start(FROM_HERE, period, this, |
| + &NetworkTimeTracker::QueryTimeService); |
| + } |
| } |
| NetworkTimeTracker::~NetworkTimeTracker() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| } |
| +void NetworkTimeTracker::QueryTimeService() { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + |
| + // 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(kTimeServiceURL); |
| + 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_->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(); |
| +} |
| + |
| +void NetworkTimeTracker::OnURLFetchComplete(const net::URLFetcher* source) { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + DCHECK(source); |
| + if (source->GetStatus().status() != net::URLRequestStatus::SUCCESS && |
| + source->GetResponseCode() != 200) { |
| + DVLOG(1) << "fetch failed, status=" << source->GetStatus().status() |
| + << ",code=" << source->GetResponseCode(); |
| + return; |
| + } |
| + |
| + std::string response_body; |
| + if (!source->GetResponseAsString(&response_body)) { |
| + DVLOG(1) << "failed to get response"; |
| + return; |
| + } |
| + DCHECK(query_signer_); |
| + if (!query_signer_->ValidateResponse(response_body, GetServerProof(source))) { |
| + DVLOG(1) << "invalid signature"; |
| + return; |
| + } |
| + response_body = response_body.substr(5); // Skips leading )]}'\n |
| + base::JSONReader reader; |
| + std::unique_ptr<base::Value> value = reader.Read(response_body); |
| + if (!value) { |
| + DVLOG(1) << "bad JSON"; |
| + return; |
| + } |
| + const base::DictionaryValue* dict; |
| + if (!value->GetAsDictionary(&dict)) { |
| + DVLOG(1) << "not a dictionary"; |
| + return; |
| + } |
| + double current_time_millis; |
| + if (!dict->GetDouble("current_time_millis", ¤t_time_millis)) { |
| + DVLOG(1) << "no current_time_millis"; |
| + return; |
| + } |
| + // 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); |
| + // The extra 10 seconds comes from a property of the time server that we |
| + // happen to know, which is its maximum allowable clock skew. 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. |
| + 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()); |
| +} |
| + |
| void NetworkTimeTracker::UpdateNetworkTime(base::Time network_time, |
| base::TimeDelta resolution, |
| base::TimeDelta latency, |
| @@ -132,6 +270,10 @@ void NetworkTimeTracker::UpdateNetworkTime(base::Time network_time, |
| time_mapping.SetDouble(kPrefNetworkTime, |
| network_time_at_last_measurement_.ToJsTime()); |
| pref_service_->Set(prefs::kNetworkTimeMapping, time_mapping); |
| + |
| + if (getter_) { |
| + query_timer_.Reset(); |
| + } |
| } |
| bool NetworkTimeTracker::GetNetworkTime(base::Time* network_time, |