在Android原生系统中,系统时间是通过NTP client service进行更新的。而Brillo系统中没有NTP client service,系统时间的更新是通过tlsdated service进行更新的。与NTP相比,通tlsdated service更新时间的好处是不用建立专门的NTP server, 任何一个https web server都可以作为一个时间服务器,给Brillo系统提供网络时间。
- 时钟(Clock)
在linux系统中包含两种类型的时钟:
一个是RTC(Real Time Clock)或者是Hardware clock, 是一个专门的硬件电路用来保持当前的时间。一般来说,PC或者嵌入式系统都有这么一个设备,保证系统在下一次开机时,时间能够被正确读取。RTC读取与更新是通过hwclock实现:
$ sudo hwclock -r 2016年02月24日 星期三 21时17分36秒 -0.793276 seconds
对于RPi 2B(树莓派)来说,由于系统中没有RTC模块,系统的时钟需要由网络提供,如在Raspbian系统中通过NTP service获取。
另一种为System Clock, 如果细分的话,包启3种类型的时间:
a. “wall” clock, 也就是我们通过date命令获取到的时间;
b. uptime, 从系统开机开始计时的时间,不包含系统睡眠的时间;
c. elapsed realtime, 从系统开机开始计时的时间,包启系统睡眠的时间;
而tlsdated在这里主要是去更新”wall” clock: clock_settime(CLOCK_READTIME),当然tlsdated也可以更新RTC。
$ man clock_settime
CLOCK_REALTIME
System-wide clock that measures real (i.e., wall-clock) time. Setting this clock requires appropriate privileges. This clock is affected by discontinuous jumps in the system time (e.g., if the system administrator manually changes the clock), and by the incremental adjustments performed by adjtime(3) and NTP.
- 原理
请看external/tlsdate/README:
tlsdate: secure parasitic rdate replacement
tlsdate sets the local clock by securely connecting with TLS to remote servers and extracting the remote time out of the secure handshake. Unlike ntpdate, tlsdate uses TCP, for instance connecting to a remote HTTPS or TLS enabled service, and provides some protection against adversaries that try to feed you malicious time information.
tlsdate是在建立TLS/SSL连接时,在handshake阶段,获取服务器的时间。服务器提供TLS/SSL服务时,需要有证书,证书是有时效性的,服务器要保证系统时间是准确的。如我们用openssl去建立连接的时候:
$ openssl s_client -connect github.com:443 CONNECTED(00000003) ... --- SSL handshake has read 3233 bytes and written 421 bytes --- New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES128-GCM-SHA256 Server public key is 2048 bit Secure Renegotiation IS supported Compression: NONE Expansion: NONE SSL-Session: Protocol : TLSv1.2 Cipher : ECDHE-RSA-AES128-GCM-SHA256 Session-ID: D2BAC00450FD9282EA0E8DDEE0190D5A937D99A8980904D25DF2FFD22892CB8C Session-ID-ctx: Master-Key: AF8B27CE338007F6EA343F98B63D49F13294F6EAAC67C712A0BC291470C35870E7CF33AB206F2F4AF919D2D4190B396F Key-Arg : None PSK identity: None PSK identity hint: None SRP username: None Start Time: 1456321458 Timeout : 300 (sec) Verify return code: 20 (unable to get local issuer certificate) --- closed
除了得到服务器的时间之外,我们还可以知道服务器使用的是TLSv1.2的协议。
- 代码
相关的代码在brillo-m9-dev/external/tlsdate下面,编译时会生成下面三个模块:
-> 1. system/bin/tlsdated:
a. 接收更新系统时间的请求:
-src/events/check_continuity.c:时间连续性检查,默认每4分钟执行一次
- src/dbus.c: 通过D-Bus通知更新系统时间(org.torproject.tlsdate::SetTime())
- src/platform-cros.c:handle_service_change(), handle_manager_change(), handle_suspend_done(), handle_dbus_change()
- src/events/route_up.c:在netlink 接收到new route时。
b. 提供当前的系统时间,接收更新系统时间的请求,执行system/bin/tlsdate去获取当前系统时间。
-> 2. system/bin/tlsdate:
代码brillo-m9-dev/external/tlsdate/src/tlsdated.c:
int main (int argc, char **argv) { // ... execlp (TLSDATE_HELPER, "tlsdate", host, port, protocol, (ca_racket ? "racket" : "unchecked"), (verbose ? "verbose" : "quiet"), ca_cert_container, (setclock ? "setclock" : "dont-set-clock"), (showtime ? (showtime == 2 ? "showtime=raw" : "showtime") : "no-showtime"), (timewarp ? "timewarp" : "no-fun"), (leap ? "leapaway" : "holdfast"), (proxy ? proxy : "none"), (http ? "http" : "tls"), NULL); perror ("Failed to run tlsdate-helper"); return 1; }
处理由system/bin/tlsdated传过来参数,之后再执行system/bin/tlsdate-helper来获取服务器时间。有点多余,直接执行system/bin/tlsdate-helper不行吗?
-> 3. system/bin/tlsdate-helper
让SSL之间的交互运行在不同的进程(drop privilege), 通过mmap从子进程中获取时间:
int main(int argc, char **argv) { // ... // We cast the mmap value to remove this error when compiling with g++: // src/tlsdate-helper.c: In function ‘int main(int, char**)’: // src/tlsdate-helper.c:822:41: error: invalid conversion from ‘void*’ to ‘uint32_t time_map = (uint32_t *) mmap (NULL, sizeof (uint32_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); // ... /* Run SSL interaction in separate process (and not as 'root') */ ssl_child = fork (); if (-1 == ssl_child) die ("fork failed: %s", strerror (errno)); if (0 == ssl_child) { drop_privs_to (UNPRIV_USER, UNPRIV_GROUP, NULL); run_ssl (time_map, leap, http); (void) munmap (time_map, sizeof (uint32_t)); _exit (0); } // ... }
Brillo系统使用的是external/boringssl (openssl),TLS handshake time callback,从sssl->s3->server_random中获取服务器时间:
void
openssl_time_callback (const SSL* ssl, int where, int ret)
{
if (where == SSL_CB_CONNECT_LOOP &&
(ssl->state == SSL3_ST_CR_SRVR_HELLO_A || ssl->state == SSL3_ST_CR_SRVR_HELLO_B))
{
// XXX TODO: If we want to trust the remote system for time,
// can we just read that time out of the remote system and if the
// cert verifies, decide that the time is reasonable?
// Such a process seems to indicate that a once valid cert would be
// forever valid - we stopgap that by ensuring it isn't less than
// the latest compiled_time and isn't above max_reasonable_time...
// XXX TODO: Solve eternal question about the Chicken and the Egg...
uint32_t compiled_time = RECENT_COMPILE_DATE;
uint32_t max_reasonable_time = MAX_REASONABLE_TIME;
uint32_t server_time;
verb("V: freezing time for x509 verification");
memcpy(&server_time, ssl->s3->server_random, sizeof(uint32_t));
if (compiled_time < ntohl(server_time)
&&
ntohl(server_time) < max_reasonable_time)
{
verb("V: remote peer provided: %d, preferred over compile time: %d",
ntohl(server_time), compiled_time);
verb("V: freezing time with X509_VERIFY_PARAM_set_time");
X509_VERIFY_PARAM_set_time(ssl->ctx->cert_store->param,
(time_t) ntohl(server_time) + 86400);
} else {
die("V: the remote server is a false ticker! server: %d compile: %d",
ntohl(server_time), compiled_time);
}
}
}
NOTE:
这里需要找一个靠谱的服务器,external/tlsdate/CHANGELOG:
Update default host to google.com – www.ptb.de randomized timestamps
更新系统时间的命令为:
$ tlsdate google.com 443 tlsv1 racket quiet /system/etc/security/cacerts dont-set-clock showtime=raw nofun leapaway none tls
当host为google.com时(由于google.com无法访问):
02-24 14:04:22.503 1010 1010 D tlsdate : V: RECENT_COMPILE_DATE is 1456322471.000000 02-24 14:04:22.503 1010 1010 D tlsdate : V: we'll do the time warp another time - we're not setting clock 02-24 14:04:22.504 1010 1010 D tlsdate : V: attemping to drop administrator privileges 02-24 14:04:22.505 1010 1010 D tlsdate : V: time is currently 1456322662.504999000 02-24 14:04:22.505 1010 1010 D tlsdate : V: time is greater than RECENT_COMPILE_DATE 02-24 14:04:22.598 1016 1016 D tlsdate : V: using TLSv1_client_method() 02-24 14:04:22.638 1016 1016 D tlsdate : V: Using OpenSSL for SSL 02-24 14:04:22.712 1016 1016 D tlsdate : V: opening socket to google.com:443 02-24 14:04:22.979 992 992 D tlsdated: [event:handle_child_death] tlsdate reaped => pid:1010 uid:1039 status:1 code:1
将host改为github.com:
02-24 14:15:53.206 1055 1055 D tlsdate : V: RECENT_COMPILE_DATE is 1456322471.000000 02-24 14:15:53.206 1055 1055 D tlsdate : V: we'll do the time warp another time - we're not setting clock 02-24 14:15:53.207 1055 1055 D tlsdate : V: attemping to drop administrator privileges 02-24 14:15:53.208 1055 1055 D tlsdate : V: time is currently 1456323353.208433000 02-24 14:15:53.209 1055 1055 D tlsdate : V: time is greater than RECENT_COMPILE_DATE 02-24 14:15:53.214 1056 1056 D tlsdate : V: using TLSv1_client_method() 02-24 14:15:53.219 1056 1056 D tlsdate : V: Using OpenSSL for SSL 02-24 14:15:53.221 1056 1056 D tlsdate : V: opening socket to github.com:443 02-24 14:15:55.452 1056 1056 D tlsdate : V: freezing time for x509 verification 02-24 14:15:55.453 1056 1056 D tlsdate : V: remote peer provided: 1456323355, preferred over compile time: 1456322471 02-24 14:15:55.453 1056 1056 D tlsdate : V: freezing time with X509_VERIFY_PARAM_set_time 02-24 14:15:55.962 1056 1056 D tlsdate : V: In TLS response, T=1456323355 02-24 14:15:55.964 1056 1056 D tlsdate : V: certificate verification passed 02-24 14:15:55.965 1056 1056 D tlsdate : V: commonName matched: github.com 02-24 14:15:55.966 1056 1056 I tlsdate : V: found non subjectAltName extension 02-24 14:15:55.967 1056 1056 I tlsdate : V: found non subjectAltName extension 02-24 14:15:55.968 1056 1056 D tlsdate : V: subjectAltName matched: github.com, type: DNS 02-24 14:15:55.968 1056 1056 D tlsdate : V: hostname verification passed 02-24 14:15:55.969 1056 1056 I tlsdate : V: public key is ready for inspection 02-24 14:15:55.969 1056 1056 D tlsdate : V: key type: EVP_PKEY_RSA 02-24 14:15:55.970 1056 1056 D tlsdate : V: keybits: 2048 02-24 14:15:55.973 1056 1056 I tlsdate : V: key length appears safe 02-24 14:15:55.984 1055 1055 D tlsdate : V: server time 1456323355 (difference is about -2 s) was fetched in 2774 ms 02-24 14:15:55.986 1055 1055 D tlsdate : V: the TLS handshake took more than 2000 msecs - consider using a different server or run it again 02-24 14:15:55.006 990 990 I tlsdated: [event:handle_time_setter] time set from the network (1456323355) 02-24 14:15:55.035 990 990 D tlsdated: [event:handle_child_death] tlsdate reaped => pid:1055 uid:1039 status:0 code:1
当获取到服务器时间之后,会再去检查证书(certificate)、主机名(host name)有没有问题。
- 关于system/bin/tlsdate-helper
- ssl协议支持sslv23, sslv3, tlsv1, 但从brillo-m9-dev/external/boringssl/src/include/openssl/ssl.h上看,最新的版本已经到tlsv1.2, 从实际的测试来看服务器使用tlsv1.2协议也能正常处理。
- 支持http, socks4, socks5代理