sdwdate ported from Kicksecure/Whonix for lainOS. https://lainos.net
  • Python 84%
  • Shell 12.3%
  • C 3.6%
Find a file
2026-07-11 11:59:53 -07:00
pkg added log file creation 2026-07-10 13:17:05 -07:00
src added sdwdate 2026-07-10 08:43:13 -07:00
30_default.conf added sdwdate 2026-07-10 08:43:13 -07:00
99-lainos-sdwdate-rtc.rules Fix hardware clock sync: replace leaprun-based hwclock sync (privleap dependency, not used on LainOS) with direct hwclock --systohc call; add udev rule granting sdwdate group access to /dev/rtc0; grant CAP_SYS_TIME to hwclock (RTC_SET_TIME ioctl requires it beyond file permissions) 2026-07-11 10:21:59 -07:00
__init__.py added sdwdate 2026-07-10 08:43:13 -07:00
config.py added sdwdate 2026-07-10 08:43:13 -07:00
lainos-minimum-unixtime-show added sdwdate 2026-07-10 08:43:13 -07:00
lainos-onion-time-pre-script added sdwdate 2026-07-10 08:43:13 -07:00
lainos-sdwdate.initd Fix checkpath invocation (checkpath has no -g flag; use -o root:sdwdate instead) for /etc/adjtime group access, needed by hwclock --systohc after a real time correction 2026-07-11 11:59:53 -07:00
PKGBUILD Fix hardware clock sync: replace leaprun-based hwclock sync (privleap dependency, not used on LainOS) with direct hwclock --systohc call; add udev rule granting sdwdate group access to /dev/rtc0; grant CAP_SYS_TIME to hwclock (RTC_SET_TIME ioctl requires it beyond file permissions) 2026-07-11 10:21:59 -07:00
proxy_settings.py added sdwdate 2026-07-10 08:43:13 -07:00
README.md Update README.md 2026-07-10 18:22:40 +02:00
remote_times.py added sdwdate 2026-07-10 08:43:13 -07:00
sanitize_string_lib.py added sdwdate 2026-07-10 08:43:13 -07:00
sclockadj.c added sdwdate 2026-07-10 08:43:13 -07:00
sdwdate added sdwdate 2026-07-10 08:43:13 -07:00
sdwdate-1.0.0-1-x86_64.pkg.tar.zst added sdwdate 2026-07-10 08:43:13 -07:00
sdwdate-1.0.0-1-x86_64.pkg.tar.zst.sig added log file creation 2026-07-10 13:17:05 -07:00
sdwdate-1.0.0-2-x86_64.pkg.tar.zst added log file creation 2026-07-10 13:17:05 -07:00
sdwdate-1.0.0-3-x86_64.pkg.tar.zst added log file creation 2026-07-10 13:17:05 -07:00
sdwdate-debug-1.0.0-1-x86_64.pkg.tar.zst added sdwdate 2026-07-10 08:43:13 -07:00
sdwdate-debug-1.0.0-2-x86_64.pkg.tar.zst added log file creation 2026-07-10 13:17:05 -07:00
sdwdate-debug-1.0.0-3-x86_64.pkg.tar.zst added log file creation 2026-07-10 13:17:05 -07:00
sdwdate-libexec added sdwdate 2026-07-10 08:43:13 -07:00
sdwdate.install Add sdwdate user to tor group (in both post_install and start_pre() for Calamares-install reliability), fixing Tor Consensus Time Sanity Check AttributeError caused by inability to read Tor's control-port auth cookie 2026-07-11 11:30:11 -07:00
sdwdate.py Fix hardware clock sync: replace leaprun-based hwclock sync (privleap dependency, not used on LainOS) with direct hwclock --systohc call; add udev rule granting sdwdate group access to /dev/rtc0; grant CAP_SYS_TIME to hwclock (RTC_SET_TIME ioctl requires it beyond file permissions) 2026-07-11 10:21:59 -07:00
timesanitycheck.py added sdwdate 2026-07-10 08:43:13 -07:00
translations.py added sdwdate 2026-07-10 08:43:13 -07:00
url_to_unixtime added sdwdate 2026-07-10 08:43:13 -07:00

sdwdate (lainOS layer 02 port)

Secure Distributed Web Date ~ Tor-based, fingerprint-resistant time synchronization.

A LainOS Layer 02 / OpenRC port of Kicksecure's sdwdate, the time-sync daemon used by Whonix and Kicksecure. Instead of plaintext, unauthenticated NTP, sdwdate fetches time from multiple independent Tor onion services, cross-validates against Tor's own network consensus data, and applies any needed correction gradually rather than jumping the clock.


Why not just use NTP?

Plaintext NTP has two properties that matter for anonymity/privacy work:

  • It's observable. Anyone on your network path (ISP, local network operator) can see that your machine is checking the time, and roughly when ~ a timing signal that can help correlate your activity across sessions or identities.
  • It's unauthenticated. A malicious or compromised NTP server (or a network attacker spoofing UDP) can simply lie to you about the time, with nothing to cross-check against.

sdwdate fetches from several independent onion services at once, over Tor, and only accepts the result if it agrees with Tor's own consensus valid-after/valid-until window ~ a single lying source gets caught rather than trusted.


What's different from upstream

This is a genuine port, not a straight repackage. lainOS is OpenRC-based (not systemd) and doas-based (not sudo/privleap), and does not use AppArmor. Several upstream components don't apply here and were deliberately not ported; a few small pieces were missing entirely from a first pass and had to be reimplemented from scratch once actual runtime testing surfaced them.

Ported as-is from upstream (GPLv3, sclockadj.c compiled with the full upstream hardened flag set):

  • Core Python daemon (sdwdate.py, config.py, proxy_settings.py, remote_times.py, timesanitycheck.py)
  • sclockadj ~ the C helper that applies clock corrections gradually (5ms steps with 1-second pauses) instead of jumping, so Tor doesn't get confused by a sudden time change
  • Default time-source pool config (etc/sdwdate.d/30_default.conf)
  • url_to_unixtime ~ per-source time fetcher

Written from scratch for lainOS (not vendored from Kicksecure ~ each file documents why in its own header):

  • sanitize_string / guimessages.translations ~ minimal reimplementations of two small, cosmetic Kicksecure helper packages (log-sanitization and i18n string passthrough). Not security- or logic-critical; sdwdate's actual clock-sync behavior does not depend on them beyond calling their interface.
  • lainos-minimum-unixtime-show ~ reimplementation of the anti-replay-protection floor-timestamp mechanism normally provided by Kicksecure's timesanitycheck package. Reads a build-time floor, the last-known-good sync timestamp, and optional user override files, and reports the maximum (most restrictive) value, matching the interface sdwdate expects.
  • lainos-onion-time-pre-script ~ no-op replacement for upstream's onion-time-pre-script, which signals a companion privleap/AppArmor-dependent mechanism to request a Tor restart. LainOS doesn't use privleap or AppArmor confinement here, so there's nothing to signal; a clean exit 0 is the correct behavior for sdwdate's preparation pre-flight check in that case.
  • lainos-sdwdate.initd ~ OpenRC init script translated from upstream's systemd unit (see below).

Deliberately not ported:

  • AppArmor profiles (lainOS does not use MAC; see protocol7-core-security-analysis.md's Known Gaps for the project's general position on this)
  • privleap integration (only used for one soft ordering directive upstream; not called by the daemon's actual logic)
  • Qubes-specific conditionals and the helper-scripts package dependency
  • sdwdate-start-anondate-set-file-watcher ~ the Tor-restart-request file-watcher mechanism this replaces. Deferred; not required for core clock-sync functionality.
  • config-test, onion-tester, and other dev-only test scripts under usr/share/sdwdate/

OpenRC translation notes

Upstream's sdwdate.service grants AmbientCapabilities=CAP_SYS_TIME so the daemon can change the clock while running as an unprivileged sdwdate user, rather than as root. OpenRC has no direct equivalent to systemd's ambient capabilities, so this port uses setcap cap_sys_time+ep on the sdwdate and sclockadj binaries directly, applied in the init script's start_pre(). This achieves the same practical effect: the daemon runs unprivileged, with only the one specific capability it actually needs.

Upstream's SystemCallFilter (a substantial seccomp whitelist) has no OpenRC translation in this port. This is a real, acknowledged gap relative to upstream's hardening ~ worth revisiting as a follow-up, using the same seccomp-wrapper pattern already used for lainos-notifyd/lainos-dbus-bridge in protocol7-core.

Type=notify (systemd's readiness-tracking mechanism via sd_notify) has no exact OpenRC equivalent either. The daemon still sends real sd_notify messages (READY=1, STATUS=..., WATCHDOG=1) via python-sdnotify; these are absorbed and logged by LainOS's own lainos-notifyd, the same daemon that handles this protocol for the rest of the system.


Installation

doas pacman -S sdwdate

This creates a dedicated sdwdate system user (home directory /run/sdwdate, shell /usr/bin/false), the required runtime directories, and installs the OpenRC service.

sdwdate and chrony should not run at the same time ~ both will try to set the clock, and there's no coordination between them. Rather than a hard package conflict, LainOS ships a toggle script (see lainos-utils) to switch between them deliberately:

lainos-sdwdate enable    # stop chrony, start Tor + sdwdate
lainos-sdwdate disable   # stop sdwdate, start chrony (default)
lainos-sdwdate status    # show which is currently active

chrony remains the lainOS default. sdwdate has no fallback time source ~ if Tor is blocked, slow to bootstrap, or otherwise unreachable, sdwdate cannot sync time at all, and your clock will simply not be corrected until Tor is available again. The OpenRC init script's need tor / after tor dependency means sdwdate won't even attempt to start if Tor itself fails to start, so this fails visibly (both services show as failed in rc-status) rather than silently. Whether to run sdwdate as your primary time sync, or only turn it on situationally (e.g., alongside other Tor-based activity), is a real tradeoff worth making deliberately rather than defaulting into.


Verifying it's working

doas tail -f /var/log/sdwdate.log

A healthy sync cycle looks like: preparation succeeds, several onion sources are contacted concurrently, each reports Tor Consensus Time Sanity Check: sane, a median time difference is computed across all successful sources, and (if a correction is needed) sclockadj is launched in the background to apply it gradually. The daemon then sleeps for a while (config-dependent, roughly 2+ hours by default) before its next check.

A small correction (a few seconds) on a system whose clock is already accurate is the expected, healthy result ~ sdwdate's job is verifying accuracy via an anonymized, cross-validated channel, not producing a dramatic clock change. The security benefit is in how the check happens (over Tor, cross-validated against consensus data, gradually applied), not in how much the clock moves.


License

GPLv3, matching upstream. lainOS-native replacement files (see above) are original work under the same license for consistency with the rest of the package.

Credits

  • Kicksecure / Whonix ~ original sdwdate design and implementation
  • lainOS layer 02 port: Grayson Giles