2017年10月12日

ブロードバンドタワー國武です。

最近、NTPの実装比較をした記事に目が止まりました。

Comparison of NTP implementations
https://chrony.tuxfamily.org/comparison.html

chrony の紹介ページなので、ある程度のバイアスが掛かっているのを割り引いたとしても、chrony は頑張ってるなーという印象を受けます。この他にも、ntpd のクライアントコマンドの ntpqコマンドとは異なり、同期を取っている NTPサーバの IPv6アドレスが丸められず表示できたりと、個人的には chrony を使うことが増えてきました。

で、時刻同期繋がりで Roughtime を思い出しました。Roughtime は、Googleの方が丁度1年ほど前に公開したプロジェクトです。下記サイトで紹介されたことで、一時的に話題になりました。

Roughtime (19 Sep 2016)
https://www.imperialviolet.org/2016/09/19/roughtime.html

このサイトによれば、TLSや Kerberos、DNSSECなどのセキュリティプロトコルは、しばしば正確な時刻同期が前提としていますが、認証がないNTPサーバとの同期をしています。多くはman-in-the-middle攻撃が可能な状態だと思われます。また、絶対的にNTPはNTPサーバを信頼するモデルなので、その対策の手立てとしてクライアント側で検証可能なものがあればいいのではないか?その一つの提案が Roughtime というプロトコルになるようです。
Roughtimeでは、クライアントから nonce(使い捨て乱数データ)を時刻サーバに投げます。時刻サーバでは、この nonce に時刻データを付与して、署名します。さらにクライアントはこの結果のハッシュを取るなどして、これを nonce として別の時刻サーバに投げます。同じようにまたサーバから時刻情報と署名を付けて返されるわけですが、当然最初の時刻データよりも新しい時刻データが返ることが期待されます。もし先祖返りしてしまったら、どこかの時刻サーバが嘘をついたことの1つの証拠となります。このチェーンを複数のサーバで実施することで、概ね正しいと思われる時刻データが得られるだろうというのが基本的な考えのようです。

ただ、この Roughtime は 10秒以内の時刻同期で良いと言った割り切りがあります。PTP(Precision Time Protocol)のような高精度の時刻同期を代替するようなものではありません。証明書の期限が切れた、切れなかったを確認するのに、10秒ズレてたからといって大きな問題にはならないよね、ぐらいの緩い感じがなかなかおもしろいです。

さて、前置きはこれぐらいにして実際に使ってみましょう。

Roughtimeを使ってみる

Roughtime の実証コードは

https://roughtime.googlesource.com/roughtime/

からDownloadできます。

C++版と Go言語版がありますが、機能が豊富でコンパイルが簡単な Go言語版で紹介させて頂きます。

Go でのコンパイル

今回は CentOS7でコンパイルしてみます。

$ sudo yum install epel-release
$ sudo yum update
$ sudo yum install golang
$ mkdir ~/go
$ export GOPATH=~/go
$ export PATH=$PATH:$GOPATH/bin

コンパイル方法は

https://roughtime.googlesource.com/roughtime/+/HEAD/BUILDING.md

に書いてありますが、少しわかりづらいでしょうか。下記のようにすると、client も server もコンパイル可能です。

$ go get golang.org/x/crypto/ed25519
$ cd ~/go/src
$ git clone https://roughtime.googlesource.com/roughtime roughtime.googlesource.com
$ cd roughtime.googlesource.com/go/client
$ go build
$ ../server
$ go build

サーバを動かしてみる

まず鍵を作ります。

$ ./server -generate-key
Private key: a880c2cee8faa45431909b7f3c78fcc285397a4938c4be7a4c1b06af8e33d7fbadb2a631fa264eb6a72047a99d3ced1211e29bb911831f12469a8152fb6e61ce

{
  "servers": [
    {
      "name": "FIXME",
      "publicKeyType": "ed25519",
      "publicKey": "rbKmMfomTranIEepnTztEhHim7kRgx8SRpqBUvtuYc4=",
      "addresses": [
        {
          "protocol": "udp",
          "address": "FIXME"
        }
      ]
    }
  ]
}

Private key とされている文字列を ‘priv’ というファイル名で保存します。そして server を起動してみましょう。

./server
2016/09/21 08:17:18 Processing requests on port 5333

のように、port指定無しだと、5333番で Listen するようになります。

client を動かす。

次は client です。roughtime-servers.json の “servers” のセクションに、上記で出力された結果を追記します。

加工前

{
  "created": "2016-08-30T23:09:00Z",
  "expires": "2017-03-01T00:00:00Z",
  "servers": [
    {
      "name": "Google",
      "publicKeyType": "ed25519",
      "publicKey": "etPaaIxcBMY1oUeGpwvPMCJMwlRVNxv51KK/tktoJTQ=",
      "addresses": [
        {
          "protocol": "udp",
          "address": "roughtime.sandbox.google.com:2002"
        }
      ]
    }
  ]
}

加工後

{
  "created": "2016-08-30T23:09:00Z",
  "expires": "2017-03-01T00:00:00Z",
  "servers": [
    {
      "name": "Google",
      "publicKeyType": "ed25519",
      "publicKey": "etPaaIxcBMY1oUeGpwvPMCJMwlRVNxv51KK/tktoJTQ=",
      "addresses": [
        {
          "protocol": "udp",
          "address": "roughtime.sandbox.google.com:2002"
        }
      ]
    },
    {
      "name": "FIXME",
      "publicKeyType": "ed25519",
      "publicKey": "rbKmMfomTranIEepnTztEhHim7kRgx8SRpqBUvtuYc4=",
      "addresses": [
        {
          "protocol": "udp",
          "address": "FIXME"
        }
      ]
    }
  ]
}

この時、FIXME となっている箇所の “name” と “address” を修正しておきます。あとは

$ go/client/client --servers-file=roughtime-servers.json
Quorum set to 2 servers because not enough valid servers were found to meet the default (3)!
Failed to get 2 servers to agree on the time.

のように起動できます。が、エラーが出てますね。仕組み上、最低でも3台必要ですし、時刻がずれていると、同意が取れなかったと失敗します。3台以上で動かすと

$ go/client/client
Failed to get 3 servers to agree on the time.

上記は、3サーバで時刻の同意が取れてない例になります。同意が取れれば

$ go/client/client
real-time delta: -510.163µs

と表示されます。ただ、現状公知?の Roughtime のサーバリストは特に無く、google からは試験的に1台提供されているだけです。ecosystem で成長するといいなぁ……というスタンス。まだ始まったばかりですね。

最近では、別の方が Rust版の Roughtimeサーバを実装されたようです。

Rustによる Roughtime 実装
https://github.com/int08h/roughenough

時刻同期は重要なプロトコルでありながら、地道な開発が必要なものです。RoughtimeのMLは流量があまりなく、盛り上がっているとはいい難いようですが、定期的に watch しておきたいと考えています。

本ブログの情報につきましては、自社の検証に基づいた結果からの情報提供であり、
品質保証を目的としたものではございません。

投稿者: Koichi KUNITAKE

XenServerやLinux周りを触ってます。IPv6はボチボチ……