- Python 84%
- Shell 12.3%
- C 3.6%
| pkg | ||
| src | ||
| 30_default.conf | ||
| 99-lainos-sdwdate-rtc.rules | ||
| __init__.py | ||
| config.py | ||
| lainos-minimum-unixtime-show | ||
| lainos-onion-time-pre-script | ||
| lainos-sdwdate.initd | ||
| PKGBUILD | ||
| proxy_settings.py | ||
| README.md | ||
| remote_times.py | ||
| sanitize_string_lib.py | ||
| sclockadj.c | ||
| sdwdate | ||
| sdwdate-1.0.0-1-x86_64.pkg.tar.zst | ||
| sdwdate-1.0.0-1-x86_64.pkg.tar.zst.sig | ||
| sdwdate-1.0.0-2-x86_64.pkg.tar.zst | ||
| sdwdate-1.0.0-3-x86_64.pkg.tar.zst | ||
| sdwdate-debug-1.0.0-1-x86_64.pkg.tar.zst | ||
| sdwdate-debug-1.0.0-2-x86_64.pkg.tar.zst | ||
| sdwdate-debug-1.0.0-3-x86_64.pkg.tar.zst | ||
| sdwdate-libexec | ||
| sdwdate.install | ||
| sdwdate.py | ||
| timesanitycheck.py | ||
| translations.py | ||
| url_to_unixtime | ||
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'stimesanitycheckpackage. 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'sonion-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 cleanexit 0is 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-scriptspackage 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 underusr/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