Event signing-key validity is judged at the event's origin_server_ts¶
- Status: accepted
- Date: 2026-09-28
Technical Story: finding "Key validity against origin_server_ts" recorded in
docs/todos/capability-gaps.md on the 0.12.13 branch. The option below was
chosen in writing by the user on 2026-09-28.
Context and Problem Statement¶
federation::authorize_federation_pdu took a now_ts argument and rejected a
PDU when the current time was past the sender key's valid_until_ts, in every
room version. The backfill path passed 0, which skipped the check entirely.
The spec says something different:
- server-server-api.md,
valid_until_ts: "This field MUST be ignored in room versions 1, 2, 3, and 4." - rooms/v5.md, "Signing key validity period" (unchanged through v12):
"servers MUST enforce the
valid_until_tsproperty from a key request is at least as large as theorigin_server_tsfor the event being validated." - server-server-api.md, receipt checks: "any keys that are known to have
expired prior to the event's
origin_server_tsare ignored." - rooms/v5.md: "Servers MUST use the lesser of
valid_until_tsand 7 days into the future when determining if a key is valid."
So the old check rejected events the spec accepts (v1-v4 with a now-expired key, and any v5+ event older than its key's expiry), never rejected an event sent after its key had expired, and backfill did no validity check at all. No 7-day cap was applied anywhere.
Considered Options¶
- Follow the spec exactly: no current-time check; v5+ require
valid_until_ts >= origin_server_ts; v1-v4 ignorevalid_until_ts; cap a cached key at fetch time + 7 days. - The spec rule plus the old current-time rejection, as an extra restriction.
- Keep the current-time rejection and add the
origin_server_tsrule only where nothing was checked (backfill).
Decision Outcome¶
Chosen option: "Follow the spec exactly".
RoomVersionPolicy::ignores_key_validityis true for v1-v4 only. It defaults to false, so a version that forgets to set it enforces the check.authorize_federation_pduhas nonow_tsparameter. For a version that enforces validity it rejects whenvalid_until_ts < origin_server_ts, includingvalid_until_ts == 0(no known validity), and rejects a PDU with no integerorigin_server_ts. Every caller (/send, the membership and invite endpoints, backfill, and main's re-verification of worker relays) gets the same rule.cache_remote_server_keysrequires the fetch time and storesmin(valid_until_ts, fetched_at + 7 days). The resolver refetches when a cached key reaches that time, so a key is never trusted for more than seven days without being fetched again.
The 7-day cap is applied at fetch time, not at check time ("7 days into the future" from each check). The fetch-time cap is never later than the check-time reading, and it is the one that forces a refetch, which is what lets a key owner withdraw a key.
Positive Consequences¶
- Old events signed with a since-rotated key verify, as the spec requires; events signed after their key expired are refused, including on backfill.
Negative Consequences¶
- The removed current-time check also guarded the resolver's stale-cache fallback (a key served when the remote cannot be reached). That guard is now the 7-day cap: a stale key still verifies events sent before its capped expiry, and nothing sent after it.
origin_server_tsis chosen by the sender, so a server holding an expired key can backdate events. The spec accepts this; the capped expiry bounds it.
Pros and Cons of the Options¶
Follow the spec exactly¶
- Good, because it is what the spec requires, in every room version.
- Bad, because a stale cached key is usable for events up to its capped expiry.
The spec rule plus the current-time rejection¶
- Good, because a stale cached key is never used.
- Bad, because it rejects v1-v4 events on a field the spec says MUST be ignored, and rejects valid historical events whose key has since expired.
Keep the current-time rejection, add the rule to backfill¶
- Good, because it changes the least.
- Bad, because it keeps both spec violations and applies different rules on different paths.