"...because any sane person knows what day it is!" -- Raz BackgroundThere 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. DesignWe 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:
Local clock changesIn 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 changesThe 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:
|
