Brillo: 通过tlsdated service更新系统时间

在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
  1. ssl协议支持sslv23, sslv3, tlsv1, 但从brillo-m9-dev/external/boringssl/src/include/openssl/ssl.h上看,最新的版本已经到tlsv1.2, 从实际的测试来看服务器使用tlsv1.2协议也能正常处理。
  2. 支持http, socks4, socks5代理

发表评论

电子邮件地址不会被公开。 必填项已用*标注