跳转到内容
搜索文档

使用 Roughtime

最后更新 查看 MarkdownAgent 设置

有多种方式可以使用 Roughtime 来保持您的时钟同步。这些示例配方使用了 Cloudflare 的 Go 包,该包基于 Google 的 Go 客户端

该协议也在 C++RustJava 中得到了实现。

客户端配置

客户端配置由一个格式化为 JSON 对象的命名 Roughtime 服务器列表组成。例如:

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

它包含每个服务器的 根公钥 (root public key)。当服务器启动时,它会生成一个 在线 (online) 公钥/私钥对。根私钥用于为在线公钥创建 委托 (delegation),而在线私钥用于对响应进行签名。

委托在 Web 上起着与传统 X.509 证书 相同的作用。客户端首先使用根公钥来验证委托,然后使用在线公钥来验证响应。

因为响应是 可审计的 (auditable),该协议使得每个客户端都能够对提供准确时间负责。

配置还编码了服务器使用的签名算法类型(目前仅支持 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

桌面警报

使用 Roughtime 的一种更通用的方法是创建桌面警报,在您的时钟发生偏差时警告您。

在 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 {
    // 错误处理...
  }
}

有关完整的运行示例,请查看我们的 GitHub(已在 Ubuntu 18.04 上进行测试)。您可以将此程序作为 cron 作业运行,以定期检查您的时钟是否已同步。

使用多个来源

对 Roughtime 使用多个来源非常容易(且强烈推荐):

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

第一个参数是一序列的服务器,其余参数与 roughtime.Get() 中的相同。这会按顺序查询序列 servers 中的每个服务器。输出 res 是一个与 servers 长度相同的切片。

每个元素代表对该服务器查询的结果。如果查询成功,则结果包含服务器的时间。如果失败,则结果包含发生的错误。要计算您的时钟与有效响应之间的中位数差值:

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 {
  // 错误处理...
}

变量 chain 是一个结构体,其中包含 results 中第一次成功的查询。它有一个字段 chain.Next 指向下一个成功的查询。Verify() 的输入参数允许您将先前的结果作为起点来验证该链。例如,如果 chain.Verify(nil) 有效,那么 chain.Next.Verify(chain.Roughtime) 也将有效。

详细输出模式

可以让 roughtime.Do() 在执行查询时输出有用的信息。为此,请调用 roughtime.SetLogger() 来设置日志记录器。例如:

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

这篇文档对您有帮助吗?