NTS: Authenticated Time at Meta

NTS: Authenticated Time at Meta

NTS:Meta 的认证时间服务

By Oleg Obleukhov Meta’s public time service now speaks NTS (Network Time Security, RFC 8915) at nts.meta.com. Packets are authenticated, so a device can verify the time came from us and was not modified on the way. 由 Oleg Obleukhov 撰写。Meta 的公共时间服务现已支持 NTS(网络时间安全协议,RFC 8915),地址为 nts.meta.com。数据包经过认证,因此设备可以验证时间确实来自我们,且在传输过程中未被篡改。

Our NTS servers hold no per-client state. Cookie keys are derived, not stored and not replicated. We’ve open sourced everything, including the protocol, server, client through Meta’s Time library on GitHub. We’re also encouraging everyone, particularly those who maintain an NTP client on Android or iOS, to join us in enabling NTS support. 我们的 NTS 服务器不保存任何客户端状态。Cookie 密钥是实时推导出来的,既不存储也不进行复制。我们已经开源了所有内容,包括协议、服务器和客户端,均可通过 GitHub 上的 Meta Time 库获取。我们也鼓励所有人,特别是那些维护 Android 或 iOS NTP 客户端的开发者,加入我们,共同启用 NTS 支持。

We’ve previously written about building a more accurate time service at Meta scale – migrating from ntpd to chrony, going from 10 milliseconds to 100 microseconds, and opening time.meta.com to everyone. That work made time precise. This work makes it verifiable. 我们之前曾撰写过关于在 Meta 规模下构建更精确时间服务的文章——从 ntpd 迁移到 chrony,将精度从 10 毫秒提升至 100 微秒,并向所有人开放 time.meta.com。那项工作实现了时间的“精确”,而这项工作则实现了时间的“可验证”。

Why NTS? NTP has been unauthenticated since 1985, like a lot of the internet’s foundational protocols. But it is also the one every certificate, token, and signature is checked against. The client sends 48 bytes, something sends 48 bytes back, and the client believes it. There is no signature and no identity. 为什么要使用 NTS?自 1985 年以来,NTP 一直处于未认证状态,就像互联网的许多基础协议一样。但它同时也是每一个证书、令牌和签名进行校验时所依赖的基准。客户端发送 48 字节,对方返回 48 字节,客户端就对此深信不疑。这其中没有任何签名,也没有任何身份验证。

It is not alone in that. Plenty of the internet’s foundational protocols were designed for a network where nobody was assumed to be hostile, and the industry has been retrofitting them ever since. What makes time different is that it is not just another protocol to secure. It is the input every other security decision is measured against. 这种情况并非个例。互联网的许多基础协议在设计时,都假设网络环境是友好的,而业界此后一直在对其进行修补。时间协议的特殊之处在于,它不仅仅是另一个需要保护的协议,它是衡量所有其他安全决策的输入基准。

For most of NTP’s life that did not matter much, because a clock being a few seconds off was an operational annoyance. Today it is load-bearing: 在 NTP 诞生的大部分时间里,这并不重要,因为时钟偏差几秒钟通常只是操作上的小麻烦。但今天,它已成为关键的支撑:

  • Certificate validation: notBefore and notAfter are compared against the local clock. Wrong clock, wrong answer.
  • 证书验证: notBefore 和 notAfter 字段会与本地时钟进行比对。时钟错误,结果就会错误。
  • Token and credential expiry: “Valid for 15 minutes” is a claim about a clock.
  • 令牌和凭据过期: “有效期 15 分钟”本质上是对时钟的依赖。
  • Replay windows: Rejecting a request older than N seconds requires knowing what time it is.
  • 重放窗口: 拒绝超过 N 秒的请求,前提是必须知道当前准确时间。
  • Log correlation and ordering: Two hosts that disagree produce a timeline that never happened.
  • 日志关联与排序: 如果两台主机的时间不一致,生成的事件时间线将与事实不符。

And the check itself is circular. You can’t do X.509 notBefore/notAfter validation without knowing the current time. Effectively you’d have to disable cert time validation, or implement one of the suggested work-arounds. There are two common workarounds. Both are bad. 而且这种校验本身是循环依赖的。如果不了解当前时间,就无法进行 X.509 的 notBefore/notAfter 验证。实际上,你必须禁用证书时间验证,或者实施某种建议的变通方案。目前有两种常见的变通方案,但都很糟糕。

You can skip validation until the clock is set, which leaves a device unprotected exactly when it is most exposed. Or you can build in-slack: A certificate issued at 12:00:00 is rejected by a client whose clock reads 11:59:30, so issuers backdate notBefore by a few minutes and validators quietly widen the window at their end. Both sides are guessing at how wrong the other one is. 你可以选择在时钟校准前跳过验证,但这会让设备在最脆弱的时候失去保护。或者你可以设置“余量”(in-slack):如果证书在 12:00:00 签发,而客户端时钟显示 11:59:30,客户端会拒绝该证书。因此,签发者会将 notBefore 时间提前几分钟,而验证者则在本地悄悄放宽窗口。双方都在猜测对方的时钟到底偏差了多少。

That was survivable when a certificate lasted 398 days. CA/Browser Forum ballot SC-081v3 changed that. The cap on newly issued publicly trusted TLS certificates dropped to 200 days in March 2026, falls to 100 days in March 2027, and reaches 47 days in March 2029 — with domain validation reuse dropping to 10 days at the same point. 当证书有效期长达 398 天时,这种做法尚可维持。但 CA/Browser 论坛的 SC-081v3 提案改变了这一点。新签发的公共信任 TLS 证书有效期上限在 2026 年 3 月降至 200 天,2027 年 3 月降至 100 天,并于 2029 年 3 月降至 47 天——同时域名验证的复用期也将缩短至 10 天。

At 47 days, manual certificate management stops being viable at any scale. Renewal moves to roughly monthly, and it happens unattended — usually over ACME. An automated renewal loop on a host with a skewed clock does not raise a ticket. It reissues, or it fails, on a schedule nobody is watching. 在 47 天的有效期下,任何规模的手动证书管理都将不再可行。续期将变为大约每月一次,且通常通过 ACME 协议自动完成,无需人工干预。如果主机时钟偏差导致自动续期循环失败,系统不会发出警报,它只会按照无人监控的计划不断尝试重签或直接失败。

How NTS Works

NTS 的工作原理

NTS works in phases, and separating them is what makes it deployable. NTS 分阶段工作,这种分离设计使其具备了可部署性。

Phase 1 – Key Establishment (NTS-KE): TLS 1.3 over TCP/4460, negotiated with ALPN ntske/1. The client and server agree on an AEAD algorithm, then derive two directional session keys (C2S and S2C) straight from the TLS session using the RFC 5705 exporter. The server issues eight cookies, tells the client which NTP server to actually use via a server negotiation record, and closes the connection. This happens once, not per packet. 第一阶段 – 密钥建立 (NTS-KE): 基于 TCP/4460 端口的 TLS 1.3,通过 ALPN ntske/1 进行协商。客户端和服务器商定一种 AEAD 算法,然后使用 RFC 5705 导出器直接从 TLS 会话中推导出两个方向的会话密钥(C2S 和 S2C)。服务器颁发八个 Cookie,通过服务器协商记录告知客户端实际应使用的 NTP 服务器,然后关闭连接。此过程仅发生一次,而非每个数据包都进行。

We negotiate three AEADs: AES-SIV-CMAC-256 (ID 15), which RFC 8915 makes mandatory to implement and is what an unknown client is most likely to ask for; AES-SIV-CMAC-512 (17); and AES-128-GCM-SIV (30). Client preference wins. 我们协商三种 AEAD 算法:RFC 8915 强制要求实现的 AES-SIV-CMAC-256(ID 15,未知客户端最可能请求的算法)、AES-SIV-CMAC-512(17)以及 AES-128-GCM-SIV(30)。客户端的偏好优先。

Phase 2 – Authenticated NTP: Standard NTPv4 over UDP/123 to the advertised server, with NTS extension fields. A request carries a unique identifier, a cookie, and an authenticator; the authenticator covers everything before it, but encrypts nothing. The server opens the cookie to recover the session keys, verifies that authenticator, and replies with the unique identifier echoed back plus an authenticator of its own – and that one does carry a payload: the fresh cookies, encrypted inside it. 第二阶段 – 认证 NTP: 通过 UDP/123 端口向指定的服务器发送标准 NTPv4 请求,并携带 NTS 扩展字段。请求包含唯一标识符、Cookie 和认证器;认证器覆盖其之前的所有内容,但不进行加密。服务器解开 Cookie 以恢复会话密钥,验证认证器,并回复回显的唯一标识符以及服务器自己的认证器——该认证器确实携带了有效载荷:加密在其中的新 Cookie。

A forged packet fails verification and is dropped. We do not send an NTS NAK, so a failure is indistinguishable from packet loss and cannot be used to push a client into a fresh key exchange. A replayed response carries the wrong unique identifier and matches no outstanding request. 伪造的数据包因验证失败而被丢弃。我们不发送 NTS NAK,因此验证失败与数据包丢失无法区分,攻击者无法借此迫使客户端进行新的密钥交换。重放的响应因携带错误的唯一标识符,无法匹配任何待处理的请求。

Because the replacement cookies ride inside the response authenticator rather than in the clear, an observer sees a different opaque blob every exchange and cannot follow a client between networks. Spending each cookie once is the client’s side of that bargain – the server is stateless and does not track them. 由于替换用的 Cookie 封装在响应认证器内部而非明文传输,观察者在每次交换中看到的都是不同的不透明数据块,无法在不同网络间追踪客户端。作为交换,客户端需确保每个 Cookie 只使用一次——服务器是无状态的,不会对其进行追踪。

Cookies Without State

A cookie is a server state handed to the client for safekeeping. It holds the session keys, sealed with AES-SIV so only the server can open it. The obvious implementation is a key ring replicated to every server, which turns time service into a distributed state problem. We do not replicate keys. Each server derives the sealing key from a shared master secret and the current day: Cookie 是服务器交给客户端代为保管的状态信息。它包含会话密钥,并使用 AES-SIV 加密,确保只有服务器能解开。最直观的实现方式是将密钥环复制到每台服务器,但这会将时间服务变成一个分布式状态问题。我们不复制密钥。每台服务器都根据共享的主密钥和当前日期推导出密封密钥:

key = HKDF-SHA256(master, salt = BE32(day), info = “fbnts-cookie-seal-v1”)

Since the Unix epoch, day is measured as whole 24-hour periods, not a calendar day. So there is no timezone and no DST, and every host computes the same integer for the same instant. That integer is the cookie’s key ID and travels in the clear. A server that has been handed a cookie it has never seen or sealed by a host it has never talked to, re-derives the key and opens it. We accept two days back and one. 自 Unix 纪元以来,日期以完整的 24 小时周期计算,而非日历日。因此不存在时区和夏令时问题,每台主机在同一时刻计算出的整数都是相同的。该整数即为 Cookie 的密钥 ID,以明文传输。如果服务器收到一个从未见过或由从未通信过的主机密封的 Cookie,它会重新推导出密钥并将其解开。我们接受过去两天和未来一天的密钥。