Roughtimeの使用
Roughtimeを使用して時計を同期させる方法はいくつかあります。これらのレシピは、GoogleのGoクライアント ↗に基づいたCloudflareのGoパッケージ ↗を使用しています。
このプロトコルは、C++ ↗、Rust ↗、およびJava ↗でも実装されています。
クライアント設定は、JSONオブジェクトとしてフォーマットされた名前付きRoughtimeサーバーのリストで構成されています。例えば:
{ "servers": [ { "name": "Cloudflare-Roughtime-2", "publicKeyType": "ed25519", "publicKey": "0GD7c3yP8xEc4Zl2zeuN2SlLvDVVocjsPSL8/Rl/7zg=", "addresses": [ { "protocol": "udp", "address": "roughtime.cloudflare.com:2003" } ] } ]}これには各サーバーのルート公開鍵が含まれています。サーバーが起動すると、オンライン公開/秘密鍵ペアを生成します。ルート秘密鍵はオンライン公開鍵の委任を作成するために使用され、オンライン秘密鍵は応答に署名するために使用されます。
委任は、ウェブ上の従来のX.509証明書 ↗と同じ機能を果たします。クライアントは最初にルート公開鍵を使用して委任を検証し、その後オンライン公開鍵を使用して応答を検証します。
応答が監査可能であるため、プロトコルは各クライアントに正確な時間を提供する責任を持たせます。
設定には、サーバーによって使用される署名アルゴリズムのタイプ(現在はEd25519 ↗のみがサポートされています)もエンコードされています。最後に、設定にはサービスにアクセスできるアドレスのリストと、それらに到達するために使用するトランスポートプロトコル(現在はUDPのみがサポートされています)が含まれています。
良い出発例は、単一の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型で、クエリの結果を表します。入力は次のとおりです:
- サーバーの設定。
- サーバーにダイヤルする試行回数。
- 各ダイヤル試行の待機時間。
- オプションの
*roughtime.Roughtime、以前のクエリの結果。
最後のパラメータが提供されると、それはリクエストのノンスを生成するために使用されます(この後で詳しく説明します)。
crypto/tlsパッケージは、ユーザーが証明書、セッショントークンなどを検証する際に使用する現在の時間のコールバックを指定 ↗できるようにします。このコールバックは次のように計算できます:
t1, radius := rt.Now()delta := t1.Sub(t0.Now())now := func() time.Time { return time.Now().Add(delta)}変数t1はサーバーによって報告された時間で、radiusはサーバーの不確実性半径です。
完全な動作例については、私たちのGitHub ↗をチェックしてください。
Roughtimeを使用するより一般的な方法は、時計がずれていると警告するデスクトップアラートを作成することです。
Ubuntu GNU/Linuxでは、次のようにできます:
skew := time.Duration(math.Abs(float64(delta)))if skew > 10*time.Second { summary := "時計を確認してください!" body := fmt.Sprintf("%sは%vだけずれていると言っています。", servers[0].Name, skew) cmd := exec.Command("notify-send", "-i", "clock", summary, body) if err := cmd.Run(); err != nil { // エラーハンドリング ... }}完全な動作例については、私たちのGitHub ↗をチェックしてください(Ubuntu 18.04でテスト済み)。このプログラムをcronジョブとして実行し、定期的に時計が同期しているか確認します。
Roughtimeのために複数のソースを使用するのは簡単です(非常に推奨されます):
t0 := time.Now()res := roughtime.Do(servers, attempts, timeout, nil)最初のパラメータはサーバーのシーケンスで、残りのパラメータはroughtime.Get()と同じです。これにより、serversのシーケンス内の各サーバーに順番にクエリが送信されます。出力resはserversと同じ長さのスライスです。
各要素はサーバーへのクエリの結果を表します。クエリが成功した場合、結果にはサーバーの時間が含まれます。失敗した場合、結果には発生したエラーが含まれます。あなたの時計と有効な応答との中央値の差を計算するには:
thresh := 10 * time.Seconddelta, err := roughtime.MedianDeltaWithRadiusThresh(res, t0, thresh)これにより、不確実性半径が10秒を超える応答が拒否されます。有効な応答がなかった場合、エラーが返されます。
関数roughtime.Do()は、有効な応答を連鎖させ、最後の成功したクエリのサーバーの応答を使用して各ノンスを生成します。私たちがブログ ↗で詳しく説明しているように、この方法でクエリをリンクさせることで、クエリが順番に行われたことの暗号的証明が得られます。結果がこの特性を持っていることを確認するには、次のようにします:
chain := roughtime.NewChain(results)ok, err := chain.Verify(nil)if err != nil || !ok { // エラーハンドリング ...}変数chainは、results内の最初の成功したクエリを含む構造体です。chain.Nextというフィールドがあり、次の成功したクエリを指します。Verify()への入力パラメータは、チェーンの検証のための出発点として以前の結果を使用することを可能にします。例えば、chain.Verify(nil)が有効であれば、chain.Next.Verify(chain.Roughtime)も有効になります。
roughtime.Do()がクエリを実行する際に有用な情報を出力することも可能です。そのためには、roughtime.SetLogger()を呼び出してロガーを設定します。例えば:
roughtime.SetLogger(log.New(os.Stdout, "", 0))