How the reader obtains wall-clock time, how that meets Aliro's time-based
credential checks, and the fixes this repo carries. The on-air protocol is
protocol-research.md.
File references use paths inside the fetched workspace (workspace/); upstream
line numbers are as of the pins in west.yml / bootstrap.sh.
- The wall clock on the Zephyr CHIP port is RAM-only:
SetClock_RealTime()stores an offset against the monotonic clock (gBootRealTime,modules/lib/matter/src/platform/Zephyr/SystemTimeSupport.cpp). Any reset, warm or cold, erases it. - Only two things set it: the Matter Time Synchronization cluster's
SetUTCTimecommand and a configured trusted time source. Apple Home sendsSetUTCTimeexactly once, at commissioning (observed in a re-pair capture, 2026-07-17). It configures noTrustedTimeSourceand noDefaultNTP, so the clock is never set again. The device emitsTimeFailureat every boot. - Matter's Last Known Good Time (LKGT) is persisted in the fabric table, but
written only at NOC install (certificate notBefore) and inside the cluster's
UpdateUTCTime(src/app/clusters/time-synchronization-server/time-synchronization-cluster.cpp:132). Nothing refreshes it, so it stays frozen at the commissioning timestamp, roughly 30 s before the first Access Document is minted. - The Nordic add-on's
GetCurrentTime()(subsys/aliro/time_utils/src/time_utils_chip.cpp) returns the wall clock when valid and otherwise silently substitutes LKGT, turning "time unknown" into a confidently wrong answer.GetCurrentUnixTime()has no fallback and fails.
Access Documents are minted on demand with validFrom set to the phone's
current time and validUntil effectively unbounded (year 4001). So
VerifyValidityPeriod
(applications/matter-aliro-door-lock-app/src/aliro/interface_impl/access_document.cpp),
which runs after the signature is verified, degenerates to: the reader clock
must not be behind the phone clock.
Failure chain after any reboot, without the fixes below:
- RAM clock is gone;
GetCurrentTime()falls back to LKGT. - LKGT predates every document ever minted.
- Every fresh document is rejected as not-yet-valid
(
Current time is outside the Access Document validity period). - Step-up fails closed, permanently, until the lock is re-commissioned.
- Document time ratchet (
CONFIG_DOOR_LOCK_TIME_CONCEPT_RATCHET, default y): when a signature-verified document'svalidFromlies ahead of the reader clock, ratchet the system clock forward tovalidFrom(never backward) instead of failing. Heals from issuer-signed material with no infrastructure. Trade-off: an issuer that signs a future-dated document can advance the clock early, weakening not-before enforcement. - Wall-clock persistence (
CONFIG_DOOR_LOCK_TIME_CONCEPT_PERSIST, default y): a 60 s poll persists the wall clock through the app'sKeyValueStorage(settings keydl/UT) once per hour, immediately when the clock first becomes valid, and after any backward correction; atAliroInitthe system clock is seeded from the stored value if (and only if) it is invalid. Restored time understates real time by at most one hour plus the power-off duration. It deliberately does not refresh Matter's fabric LKGT: LKGT feeds CASE certificate-validity checking, and a ratchet-poisoned time must not be persisted into that path. - Interplay: persist alone fixes the common case (reboot after long uptime); the ratchet alone covers documents minted while the lock was powered off. Because of persist, a ratchet advance survives reboot, as the ratchet Kconfig help states.
The Aliro BLE advertisement embeds an expiry timestamp of lock unix time + CONFIG_DOOR_LOCK_ULTRAWIDELOCK_BLE_SERVICE_DYNAMIC_TAG_EXPIRY_DURATION_S (default
900 s) when the wall clock is valid (ultrawidelock_service.cpp,
PrepareAdvertisingDataLocked), and "expiry unavailable" when it is not.
iPhones accept the unavailable form but silently ignore an advertisement whose
expiry lies in their past. A clock that is valid but behind real time by
more than the window kills unlock-on-approach for every phone, while NFC and
Matter keep working. Both time fixes above create that state (the restored
clock understates real time by the accumulated power-off duration; a ratcheted
clock understates it by the presented document's age), so
apps/nrf5340dk-lock/overlays/ultrawidelock-cred.conf disabled the Dynamic Tag
while no real time source existed. With the Matter-timesync seeding merged (the
clock steps up on sync) the overlay is back to =y, bench-gated on the seeded
clock. Stock firmware never hits this: its clock is either fresh (before the
first reboot) or invalid (after), never valid-but-stale. The ESP32 port derives
the same tag from its live clock (SNTP-fed; ultrawidelock_ble.c +
ultrawidelock_advtag.c), re-derives at half the 900 s window, immediately on a
time step, and falls back to the "expiry unavailable" form when the clock is not
yet valid.
Real network time is the only fix that makes expiry and schedule enforcement
accurate, but plain SNTP is unauthenticated: the only input to the trust chain
an attacker on the home network or DNS path could control (backward spoof:
unlock denial; forward spoof: weakened not-before without issuer keys). It needs
the border router to offer a routable path (IPv6 NTP or NAT64/DNS64) and must be
fail-open for offline homes. If built later: clamp to
never-before-firmware-build-time, cap backward steps, run async with backoff,
and rank it below SetUTCTime.
- Commission, wait ~1 min after the clock becomes valid (first persist write),
reboot: boot log shows
Wall clock seeded from persisted value, and the first step-up passes with no ratchet warning. - Power off for a while, mint a fresh document (unlock from a second
same-account iPhone), power on: first step-up logs
system time ratcheted forwardand unlocks.
The temporary DBG overlay lines in apps/nrf5340dk-lock/overlays/ultrawidelock-cred.conf
(DOOR_LOCK_APP_LOG_LEVEL_DBG, DOOR_LOCK_ULTRAWIDELOCK_TIME_UTILS_LOG_LEVEL_DBG)
surface the Current time / validFrom / validUntil values during these tests
and should be reverted afterwards.