Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

Roughtime を使う

最終更新 Markdown で表示Agent セットアップ

時計を同期するために Roughtime を使う方法はいくつかあります。このレシピでは、Google の Go クライアント を基にした Cloudflare の Go パッケージ を使います。

プロトコルは C++RustJava でも実装されています。

クライアント設定

クライアント設定は、名前付き Roughtime サーバーの一覧で、JSON オブジェクトとして記述します。例:

{
  "servers": [
    {
      "name": "Cloudflare-Roughtime-2",
      "publicKeyType": "ed25519",
      "publicKey": "0GD7c3yP8xEc4Zl2zeuN2SlLvDVVocjsPSL8/Rl/7zg=",
      "addresses": [
        {
          "protocol": "udp",
          "address": "roughtime.cloudflare.com:2003"
        }
      ]
    }
  ]
}

各サーバーの ルート公開鍵 が含まれます。サーバー起動時に オンライン の公開鍵 / 秘密鍵ペアを生成します。ルート秘密鍵はオンライン公開鍵の 委任 を作るために使い、オンライン秘密鍵は応答の署名に使います。

委任は、Web 上の従来の X.509 証明書 と同じ役割です。クライアントはまずルート公開鍵で委任を検証し、次にオンライン公開鍵で応答を検証します。

応答は 監査可能 です。そのため、プロトコルは各クライアントに、正確な時刻を提供する説明責任を持たせます。

設定には、サーバーが使う署名アルゴリズムの種類も含まれます(現在サポートしているのは Ed25519 のみです)。最後に、サービスへ到達できるアドレスの一覧と、到達に使うトランスポートプロトコル(現在サポートしているのは UDP のみ)が含まれます。

TLS

最初の例として、単一の Roughtime サーバーで TLS クライアントまたはサーバーを同期する方法がわかりやすいです。自前の時計と Roughtime サーバーの時計の差を計算します。

最初の手順は、設定ファイルの読み込みです(github.com/cloudflare/roughtime をインポートしてください)。

servers, skipped, err := roughtime.LoadConfig("roughtime.config")

この例では、変数 servers は入力ファイルから解析した有効なサーバー設定の一覧です。変数 skipped はスキップしたサーバー数です。たとえば、署名アルゴリズムやトランスポートプロトコルが未対応の場合にスキップされます。

次に、システム時刻を取得し、一覧の最初のサーバーを問い合わせます。

t0 := time.Now()
rt, err := roughtime.Get(&servers[0], attempts, timeout, nil)

これでサーバーへリクエストを送り、応答を検証します。変数 rt の型は *roughtime.Roughtime で、問い合わせ結果を表します。入力は次のとおりです。

  1. サーバーの設定。
  2. サーバーへ接続する試行回数。
  3. 各接続試行の待機時間。
  4. 省略可能な *roughtime.Roughtime。前回の問い合わせ結果です。

最後のパラメーターを渡すと、リクエストの nonce 生成に使います(詳細は後述します)。

crypto/tls パッケージでは、証明書やセッションチケットなどの検証に使う現在時刻の コールバックを指定 できます。このコールバックは次のように計算できます。

t1, radius := rt.Now()
delta := t1.Sub(t0.Now())
now := func() time.Time {
  return time.Now().Add(delta)
}

変数 t1 はサーバーが報告した時刻、radius はサーバーの不確かさ半径です。

完全な動作例は GitHub を参照してください。

デスクトップ通知

より一般的な使い方として、時計がずれているときに警告するデスクトップ通知を作れます。

Ubuntu GNU/Linux では、次のような処理ができます。

skew := time.Duration(math.Abs(float64(delta)))
if skew > 10*time.Second {
  summary := "Check your clock!"
  body := fmt.Sprintf("%s says it's off by %v.", servers[0].Name, skew)
  cmd := exec.Command("notify-send", "-i", "clock", summary, body)
  if err := cmd.Run(); err != nil {
    // error handling ...
  }
}

完全な動作例は GitHub を参照してください(Ubuntu 18.04 でテスト済み)。このプログラムを cron ジョブとして実行し、時計が同期しているかを定期的に確認します。

複数のソースを使う

Roughtime で複数のソースを使うのは簡単です(強く推奨します)。

t0 := time.Now()
res := roughtime.Do(servers, attempts, timeout, nil)

第 1 パラメーターはサーバーの列、残りのパラメーターは roughtime.Get() と同じです。列 servers の各サーバーを順に問い合わせます。出力 resservers と同じ長さのスライスです。

各要素は、そのサーバーへの問い合わせ結果です。成功した場合、結果にはサーバーの時刻が含まれます。失敗した場合、発生したエラーが含まれます。自前の時計と有効な応答の差の中央値を計算するには:

thresh := 10 * time.Second
delta, err := roughtime.MedianDeltaWithRadiusThresh(res, t0, thresh)

不確かさ半径が 10 秒を超える応答は除外します。有効な応答がなければエラーが返ります。

ソースを監査する

関数 roughtime.Do() は有効な応答をつなぎます。各 nonce は、直前に成功した問い合わせのサーバー応答から生成します。ブログ で詳しく説明しているとおり、問い合わせをこのように連結すると、問い合わせがこの順で行われたことの暗号的証明になります。結果がこの性質を持つことを検証するには、次のようにします。

chain := roughtime.NewChain(results)
ok, err := chain.Verify(nil)
if err != nil || !ok {
  // error handling ...
}

変数 chain は、results 内の最初の成功した問い合わせを含む構造です。フィールド chain.Next が、次の成功した問い合わせを指します。Verify() の入力パラメーターで、チェーン検証の起点として前回の結果を使えます。たとえば chain.Verify(nil) が有効なら、chain.Next.Verify(chain.Roughtime) も有効です。

詳細出力

roughtime.Do() は、問い合わせ実行中に有用な情報を出力できます。そのためには roughtime.SetLogger() でロガーを設定します。例:

roughtime.SetLogger(log.New(os.Stdout, "", 0))

役に立ちましたか?