Skip to content

META 2026-10-06

Read original ↗

NTS: Authenticated Time at Meta

Summary

Meta's public time service now speaks NTS (Network Time Security, RFC 8915) at nts.meta.com, making NTP packets authenticated so a client can verify the time came from Meta and was not modified in flight. The post's central architectural contribution is statelessness: the NTS servers hold no per-client state — the cookies that carry each client's session keys are sealed with a key derived, not stored or replicated, from a shared master secret and the current Unix epoch day. This turns what the obvious design would make a distributed-state problem (a replicated key ring, a session table) into a pure derivation that every server computes identically, so "adding a server is adding a server." Meta frames the work as the authenticity complement to its earlier precision work (ntpd → chrony, 10 ms → 100 µs): "That work made time precise. This work makes it verifiable." Everything — protocol, server, client — is open-sourced in Meta's Time library, and the post is partly a call to action for mobile-OS vendors (Android, iOS) to implement NTS, since those stacks still set billions of clocks over unauthenticated UDP.

Why time needs authentication

NTP has been unauthenticated since 1985: the client sends 48 bytes, something sends 48 bytes back, and the client believes it — no signature, no identity. For most of NTP's life a clock being a few seconds off was an operational annoyance, but today time is the input every other security decision is measured against:

  • Certificate validation — notBefore/notAfter are compared against the local clock.
  • Token / credential expiry — "valid for 15 minutes" is a claim about a clock.
  • Replay windows — rejecting a request older than N seconds requires knowing the time.
  • Log correlation / ordering — two hosts that disagree produce a timeline that never happened.

The check is circular: you can't do X.509 time validation without knowing the current time. The two common workarounds are both bad — skip validation until the clock is set (leaves a device unprotected exactly when most exposed), or build in slack (issuers backdate notBefore, validators widen the window — "both sides are guessing at how wrong the other one is"). This was survivable at 398-day certificates; CA/Browser Forum ballot SC-081v3 drops the cap on newly issued publicly-trusted TLS certs to 200 days (Mar 2026) → 100 days (Mar 2027) → 47 days (Mar 2029), with domain-validation reuse dropping to 10 days. At 47 days, renewal is monthly and unattended (usually ACME) — and "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."

Key takeaways

  1. Two-phase protocol, and separating the phases is what makes it deployable. Phase 1 — NTS-KE (Key Establishment): TLS 1.3 over TCP/4460, negotiated with ALPN ntske/1; client and server agree on an AEAD, derive two directional session keys (C2S and S2C) straight from the TLS session via the RFC 5705 exporter, server issues eight cookies, tells the client which NTP server to actually use (server-negotiation record), and closes. Phase 2 — Authenticated NTP: standard NTPv4 over UDP/123 to the advertised server with NTS extension fields; this happens once, not per packet. (Source: this article)

  2. Stateless cookies via a time-derived seal key — the headline design. A cookie is server state handed to the client for safekeeping; it holds the session keys, sealed with AES-SIV so only the server can open it. Rather than replicate a key ring to every server (which "turns time service into a distributed state problem"), each server derives the sealing key: key = HKDF-SHA256(master, salt = BE32(day), info = "fbnts-cookie-seal-v1"), where day is whole 24-hour periods since the Unix epoch — no timezone, no DST, every host computes the same integer for the same instant. That integer is the cookie's key ID, travels in the clear, and lets a server open a cookie it has never seen, sealed by a host it has never talked to. Meta accepts two days back and one forward to cover skew around a rotation boundary. Result: "no key ring, no replication, no session table, no shared state to fall out of sync, and nothing for an attacker to exhaust." (Source: this article)

  3. Three AEADs negotiated, client preference wins. AES-SIV-CMAC-256 (ID 15, which RFC 8915 makes mandatory-to-implement and is what an unknown client most likely asks for), AES-SIV-CMAC-512 (17), and AES-128-GCM-SIV (30). The chronyc authdata output shows Type 30 (AES-128-GCM-SIV) negotiated with KLen 128-bit session keys. (Source: this article)

  4. Downgrade / replay defenses are built into the response shape. A forged packet fails verification and is dropped — Meta does 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. Because replacement cookies ride inside the response authenticator (encrypted) rather than in the clear, an on-path observer "sees a different opaque blob every exchange and cannot follow a client between networks." Spending each cookie once is the client's half of the bargain — the server is stateless and doesn't track them. (Source: this article)

  5. pool, not server, for independent sources. One line of chrony config — pool nts.meta.com nts iburst maxsources 5. nts.meta.com terminates key exchange only; during the handshake it advertises an NTP responder (steering the client to time.meta.com), so "the host you configure is not the host you end up talking to." pool asks for maxsources independent associations, each running its own KE over its own TLS session, each ending with a distinct C2S/S2C key pair — so NTP can outvote a bad source. Five sources come up in about 20 minutes as each member completes its own KE in turn. (Source: this article)

  6. The KE/NTP split falls directly out of the stateless-cookie design. The KE server and the NTP responders it hands you off to "are different machines in different places, and they never exchange anything" — because the seal key is derived identically everywhere, no cookie minted by the KE server needs to be shared with the responder that later opens it. (Source: this article)

  7. The plain NTP path is untouched and free. The responder parses NTS extension fields only when present, so unauthenticated clients "hit the same code as before at the same cost." (Source: this article)

  8. What NTS does not fix (explicit limits). Delay attacks — an on-path attacker who holds your packets shifts your clock by up to half the delay introduced, and no cryptography detects it ("authentication tells you who wrote a packet, not how long it spent in flight"); mitigations are multiple sources, sanity limits, bounded steps. Bootstrapping — NTS-KE runs over TLS, which wants a working clock (the same circular problem); a device with a dead RTC must get roughly correct before its first handshake. Wrong servers — NTS authenticates the path, not the answer; a correctly-authenticated but wrong server hands you the wrong time with a perfect signature (quorum, sanity checks, monitoring still required). Monotonicity — authenticated time is still wall-clock time, which moves backward at leap seconds/corrections; Cloudflare's 2016 leap-second incident (a negative round-trip panicked Go's rand.Int63n()) is the cautionary tale — use a monotonic clock for elapsed time. (Source: this article)

Operational numbers / concrete details

  • NTS-KE: TLS 1.3, TCP port 4460, ALPN ntske/1; 8 cookies issued per handshake; happens once, not per packet.
  • Authenticated NTP: NTPv4, UDP port 123.
  • AEADs: AES-SIV-CMAC-256 (15, MTI), AES-SIV-CMAC-512 (17), AES-128-GCM-SIV (30). chronyc authdata sample: Type 30, KLen 128, CLen 68 (36-byte envelope around two 16-byte keys), Cook 8, NAK 0.
  • Seal-key derivation: HKDF-SHA256(master, salt = BE32(day), info = "fbnts-cookie-seal-v1"); day = whole 24-hour periods since Unix epoch; key-ID window = 2 days back, 1 day forward.
  • Pool bring-up: maxsources 5 → ~20 minutes for five independent authenticated sources.
  • The 2037 failure mode: NTP's 32-bit seconds field wraps in February 2036; a client stranded past that boundary "computes its correction into the wrong era," every cert is long-expired so TLS fails and it can't even reach its own update service — recovery means a factory reset or a service-center trip. "Multiply that by a fleet and the cost of a wrong integer becomes a logistics problem."
  • Adoption: RFC 8915 published Sept 2020; Cloudflare launched time.cloudflare.com with NTS 15 months earlier (June 2019) against the then-draft. Public NTS server list is "a few dozen entries" (PTB, Netnod, time.nl). Server-side: chrony and ntpsec implement NTS. Client-side gap is mobile: Android platform time-sync is plain SNTP over UDP/123 (no NTS); Apple devices sync via timed against time.apple.com with no known NTS support.

Caveats

  • Precision numbers (the 10 ms → 100 µs improvement) belong to the earlier NTP-service post; this post is about authenticity, not precision, and discloses no fresh latency/accuracy distributions, fleet sizes, QPS, or server counts for the NTS deployment.
  • The master-secret distribution/rotation mechanism (how master reaches every KE server, how often it rotates, where it's held) is not disclosed — only the per-day derived seal key is described.
  • No numbers on NTS-KE handshake rate, cookie-refresh cadence under steady state, or the cost of the TLS 1.3 KE tier vs the UDP responder tier.
  • "Two days back, one forward" is stated as the key-ID acceptance window but the rationale for the asymmetry (and behaviour exactly at a rotation boundary) is only sketched.
  • Delay-attack, bootstrapping, wrong-server, and monotonicity limits are named as open problems with standard mitigations, not solved here.

Source

Last updated · 854 distilled / 2,448 read