Slightly annoying fact: clocks are normally synced with a protocol (NTP - Network Time Protocol) that provides zero cryptographic guarantees. On the surface this is totally fine, but when we start composing security-sensitive protocols (think certificate expiration checks, audit logs or time-bound credentials), we need to turn a blind eye…

Not today! let’s build a simple cryptographic time reference to set the House Standard Time in proper cryptographic fashion. This reference feeds this limited edition, mission-critical clock sitting in my home office:

The time reference itself (the time server) is this beauty mess of wires, currently living behind a couch. It has a small cryptographic processor, a GPS and a raspberry pi. This reference propagates the House Standard Time via WiFi.

Protocol #

The protocol we’ll be using for clock synchronization is roughtime. This is a pretty new protocol introduced in 2016 by Adam Langley (Google). It’s pretty barebones, and simple enough you can write a fully functional client in an evening —so much fun.

roughtime is essentially a challenge-response protocol. The client sends a random 32-byte challenge to the server, which replies with a signed message containing the client challenge and the current time. (There’s some machinery to make this efficient, like packing requests in a Merkle tree, but this isn’t important now.)

Since you’re probably familiar with NTP, here are the key differences:

  Roughtime NTP
Target time precision Coarse: ~seconds. Fine for human consumption, certificate expiration Excellent. Sub-second
Security model Excellent. Architecture tolerates a few actively malicious servers, no SPOF. Network is considered untrusted: packets are cryptographically protected Bad. Network is assumed trusted (there are some extensions for adding a secure transport layer)
Protocol maturity Very new, although very simple and solid Golden standard
Software/libraries availability Bad. DIY land, mostly one-off efforts that get abandoned. Mostly targeting beefy machines, almost nothing for embedded targets Excellent, very mature. Reference implementation has been maintained for 20 years, multiple implementations
Software/libraries quality Good. Mostly written in memory safe languages Vary a lot
Server ecosystem maturity Very bad. Highly concentrated, few players. Main developer (Google) seems to have abandoned development. Cloudflare runs servers. Google is still offering this without any availability guarantees Very good. There are global pools of NTP servers.
Who uses it? Current deployments ??? probably a few nerds, unclear Virtually everywhere. time.apple.com, etc
Client code complexity apart from crypto, easy very easy if high precision isn’t required, high otherwise. Reference implementation is extremely complex 100k LoC of C
Suitable for embedded? Suboptimal. The public-key signature scheme is ed25519, which is notoriously RAM-hungry, plus needs SHA512 code yes
History, context, motivation Introduced in 2016 by Adam Langley (Google) to solve Google needs. Little public involvement after initial release Introduced around 1985 by Dave L. Mills, who continued improving it on a life-long effort
Algorithms Marzullo, ed25519, Merkle trees Marzullo, phase-locked loop

Server architecture #

Time to build! We partition the time server into two clearly distinct blocks:

  • trusted domain: comprised of a GPS module plus a small RTOS-based microcontroller. Here we implement the following functionality: parsing GPS module NMEA data, key storage, generate cryptographic signatures
  • untrusted domain: a raspberry pi running a golang binary implementing the following functionality: talk to network peers over UDP, construct requests to the trusted domain for signing bundling multiple requests (Merkle tree construction)

Why? The whole point of partitioning this way is to minimize attack surface and compact all the security-critical functionality in a small block. This partition is in a sense optimal: the trusted domain does the minimum amount of work needed, minimizing the trusted codebase. All networking code is offloaded to the raspberry (none of which is security critical), all security-sensitive is in the trusted domain. No secrets live in the raspberry. The worst possible impact of a compromised untrusted domain is availability.

The interface between those two blocks looks essentially like this (less important fields omitted):

typedef struct signing_request_t {
  uint8_t merkle_root[32]; // (merkle-)hash of all client nonces
};

typedef struct signing_response_t {
  uint64_t midpoint; // time at the moment of signing
  uint8_t signature[64]; // essentially, signature over merkle_root + midpoint
};

Note that data in signing_request can be 100% attacker controlled — this is fine and actually a scenario we accept. This means the raspi can be 100% popped (the “only” consequence being reduced availability). Time is not chosen by the untrusted domain, the time reference (the GNSS) is directly fed into the trusted domain. The communication between those two domains is a simple serial line, with no framer (yolo), and fixed size packets (difficult, but not impossible, to screw up). The raspi cannot reflash the microcontroller; a human has to plug a programmer to the microcontroller.