For Developers‎ > ‎Design Documents‎ > ‎

Sane time

"...because any sane person knows what day it is!" -- Raz

Background

There are cases in Chrome where it is important to have an accurate time source. Although most modern operating systems now automatically synchronize computer clocks to some external time source, some computers may still have a skewed clock for various reasons: time syncing may not be configured correctly, the user may have manually set the clock erroneously, etc. Thus, it is desirable for Chrome itself to keep track of time from an external, trusted source and its offset from the time on the local computer.

Time Sources discusses a similar problem for Chrome OS devices.

Design

We desire that the current real time (in UTC) be known to within a few minutes.  Assuming that the time on any Google server is accurate, and that we can communicate to Google machines securely, we call any time that we receive from a Google server over a secure channel "sane time" (because it's just accurate enough to be considered sane).  Since TLS handshakes include client/server time, and since HTTP responses typically include a date header, we can piggyback on existing connections to Google servers.  Given that we make various requests to Google servers regularly (safe browsing in particular updates every 45 minutes), we can keep sane time updated regularly.

Retrieving sane time from TLS handshakes has some advantages: sending timestamps is mandated as part of the spec and implemented by the most popular libraries; it is also a simple 32-bit UNIX timestamp and thus requires minimal parsing/conversion code; finally, the RTT for a handshake should be quite low.  However, there is a proliferation of SSL libraries used by Chrome (NSS, OpenSSL, Secure Transport, SChannel) as well as various versions of SSL/TLS.  Therefore, making the code changes may take some time as it involves multiple third-party libraries, and even then we may not be able to get the timestamp for some libraries.  Also, False Start introduces a complication: we may finish the connection before the handshake is actually finished, but we can't trust the timestamps until the handshake is finished, and so we have to keep track of that separately from the connection state.

Retrieving sane time from the date header in HTTP (over SSL/TLS) responses is easier, as all the HTTP code is in Chromium.  However, it requires date parsing, it's not always guaranteed to be present, (see section 13.2.3 of RFC 2616) and depending on the actual request, the RTT may be high.  Also, we must be careful to only consider fresh (i.e., non-cached) HTTP requests.

htpdate is a utility to set the current time from an HTTP header, and tlsdate is a utility to set the current time from a TLS handshake.

What about NTP?

NTP is a protocol built expressly for time synchronization.  But relying on it in Chrome has some disadvantages:

  • It requires maintaining NTP client code in Chrome, which is a higher burden than the solution outlined here, both in code size and developer time.
  • It involves sending a lot of traffic to some set of NTP servers.  The solution outlined here involves no additional network traffic.
Another possibility is to rely on the local time if it's already running an NTP client.  This is also problematic, as it involves writing code to detect running NTP clients and verifying that it's sanely configured.

Local clock changes

In Chrome, there are two clocks: a wall clock (base::Time) and a non-decreasing clock (base::TimeTicks).  The wall clock is what changes when the local time is changed, so we cannot reliably use it to compare against the server time unless we can detect when it changes (currently not possible).  Furthermore, we have no way of detecting when it changes when Chrome isn't running!

The non-decreasing clock, which is usually a tick count since the computer has started, is unaffected when the local time is changed.  However, it becomes meaningless across restarts of Chrome (since the computer may be rebooted between restarts).  Furthermore, it may stop when the computer is put to sleep, so even though it is intended for measure durations, it gives inaccurate results if the computer was asleep for the measured time interval.

Chrome can detect when the computer goes to sleep and wakes, but only on some platforms.

Code changes

The meta-bug is 146090.

We define a ServerTimeInfo struct that represents time information received from a server:

struct ServerTimeInfo {
  enum SourceType { UNKNOWN, TLS, TLS_FALSE_START, HTTP, HTTPS, FROM_DISK, ... };
  string server;
  SourceType source_type;
  Time server_time;
  TimeTicks start_ticks, end_ticks;
}

server is the hostname of the server from which the time was received.  source_type is the type of the time source -- TLS, HTTPS, etc.  Most of the time we care about time infos from *.google.com and with source type TLS or HTTPS, but sometimes we may still use time infos that are slightly less trusted, e.g. FROM_DISK, if we have no other source.  server_time is the actual timestamp received from the server. start_ticks is the client time when the request to the server was made, and end_ticks is the client time when the response was received. server_time corresponds to some time in [start_ticks, end_ticks], so ideally the latter interval should be as small as possible.  We use ticks since it's from a monotonic clock (i.e., unrelated to the computer clock, which the user may change).  However, this means that we can't persist ServerTimeInfo structs to disk directly, as ticks are reset on computer start.  But one can serialize a ServerTimeInfo by converting start_ticks/end_ticks to Time objects (using the current values of Time::now() and TimeTicks::now()) and then serializing those along with server_time. However, since the local clock may change between serialization and deserialization, the TrustLevel of the deserialized ServerTimeInfo object should be at most UNVERIFIED.

One can approximate the current server time given a ServerTimeInfo:

// Assume sti.server_time corresponds to the time midway between start_ticks and end_ticks.
TimeTicks midpoint = sti.start_ticks + (sti.end_ticks - sti.start_ticks) / 2;
Time server_now = sti.server_time + (TimeTicks::now() - midpoint);

Here's a rough outline of what needs to be changed to maintain a clock based on sane time:

  1. Modify SSLInfo to have a ServerTimeInfo member.
  2. Modify the implementations of SSLClientSocket to fill in the ServerTimeInfo in SSLInfo from the TLS handshake when possible. (This may take a while, so it can be done in parallel with the following steps.)
  3. Modify URLRequest and related classes to create a ServerTimeInfo from the Date response header (if it exists) and request/response timestamps (they're already being calculated).
  4. Expose methods to get/create the TLS/HTTP ServerTimeInfos from URLRequest/URLFetcher.
  5. Create a SaneTimeTracker class that consumes ServerTimeInfos and maintains the best known one.  It can have a policy of which ones it'll accept.  For example, it should only accept ones from TLS connections to google.com servers, it should keep the one from the last hour with the smallest tick interval, etc.
  6. Add a SaneTimeTracker to URLRequestContext and hook it up to any created connections.
  7. Add a way to propagate ServerTimeInfos from the "main" SaneTimeTracker to other threads/objects (perhaps similar to NetworkChangeNotifier).
  8. Add periodic serialization to a pref, and deserialize on startup.
  9. Add code to detect when the system time changes.  Serialize when that happens.
  10. Add code to detect when the system goes to sleep and wakes.  Treat this the same way as a restart of Chrome.
Comments