From f2b51f6a62e660f100e01aa2206028f149367c80 Mon Sep 17 00:00:00 2001 From: timedout Date: Sat, 21 Mar 2026 16:46:52 +0000 Subject: [PATCH 1/5] Clarify how multiple signatures should be handled during verification Signed-off-by: timedout --- content/server-server-api.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/content/server-server-api.md b/content/server-server-api.md index 8bc0d28b..eb66d9a4 100644 --- a/content/server-server-api.md +++ b/content/server-server-api.md @@ -1484,10 +1484,9 @@ the Policy Server for a signature too. When a server receives an event over federation from another server, the receiving server should check the hashes and signatures on that event. -First the signature is checked. The event is redacted following the -[redaction -algorithm](/client-server-api#redactions), and -the resultant object is checked for a signature from the originating +First the signatures are checked. The event is redacted following the +[redaction algorithm](/client-server-api#redactions), and +the resultant object is checked for signatures from the originating server, following the algorithm described in [Checking for a signature](/appendices#checking-for-a-signature). Note that this step should succeed whether we have been sent the full event or a @@ -1503,7 +1502,7 @@ The signatures expected on an event are: Other room versions do not track the `event_id` over federation and therefore do not need a signature from those servers. -If the signature is found to be valid, the expected content hash is +If all signatures from known keys are found to be valid, the expected content hash is calculated as described below. The content hash in the `hashes` property of the received event is base64-decoded, and the two are compared for equality. From e3744b1deea3fd001185af24acc1b466da9d23bb Mon Sep 17 00:00:00 2001 From: timedout Date: Sat, 21 Mar 2026 16:51:17 +0000 Subject: [PATCH 2/5] Add changelog entry Signed-off-by: timedout --- changelogs/server_server/newsfragments/2341.clarification | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelogs/server_server/newsfragments/2341.clarification diff --git a/changelogs/server_server/newsfragments/2341.clarification b/changelogs/server_server/newsfragments/2341.clarification new file mode 100644 index 00000000..868c353e --- /dev/null +++ b/changelogs/server_server/newsfragments/2341.clarification @@ -0,0 +1 @@ +Clarify how multiple signatures should be handled during signature verification. Contributed by @nexy7574. From d3fcc122eb25a4cd7912b2fbd5b01c8c9a2fa452 Mon Sep 17 00:00:00 2001 From: timedout Date: Sun, 29 Mar 2026 12:15:56 +0100 Subject: [PATCH 3/5] Explicitly state unknown and expired keys are ignored --- content/server-server-api.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/content/server-server-api.md b/content/server-server-api.md index eb66d9a4..baf8f5c0 100644 --- a/content/server-server-api.md +++ b/content/server-server-api.md @@ -1502,6 +1502,12 @@ The signatures expected on an event are: Other room versions do not track the `event_id` over federation and therefore do not need a signature from those servers. +Only signatures from known server keys are validated here, any unknown keys are ignored. +In particular, the [policy server key](#validating-policy-server-signatures) is not +expected to be published and therefore should be skipped at this stage. +Additionally, any keys that are known to have expired prior to the event's +`origin_server_ts` are ignored. + If all signatures from known keys are found to be valid, the expected content hash is calculated as described below. The content hash in the `hashes` property of the received event is base64-decoded, and the two are compared for From 795cc3fce937ffd5f89e4cca330af89b8be5cdd0 Mon Sep 17 00:00:00 2001 From: timedout Date: Sat, 11 Apr 2026 20:09:44 +0100 Subject: [PATCH 4/5] Update content/server-server-api.md Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> --- content/server-server-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/server-server-api.md b/content/server-server-api.md index baf8f5c0..4f0cca19 100644 --- a/content/server-server-api.md +++ b/content/server-server-api.md @@ -1508,7 +1508,7 @@ expected to be published and therefore should be skipped at this stage. Additionally, any keys that are known to have expired prior to the event's `origin_server_ts` are ignored. -If all signatures from known keys are found to be valid, the expected content hash is +If all signatures from known unexpired keys from the originating server(s) are found to be valid, the expected content hash is calculated as described below. The content hash in the `hashes` property of the received event is base64-decoded, and the two are compared for equality. From c175953e808869bc24a32f36a328341e3fddce4c Mon Sep 17 00:00:00 2001 From: timedout Date: Mon, 13 Apr 2026 03:25:46 +0100 Subject: [PATCH 5/5] Clarify which signatures are expected on an event (feedback) Signed-off-by: timedout --- content/server-server-api.md | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/content/server-server-api.md b/content/server-server-api.md index 4f0cca19..a94e066a 100644 --- a/content/server-server-api.md +++ b/content/server-server-api.md @@ -1492,38 +1492,36 @@ signature](/appendices#checking-for-a-signature). Note that this step should succeed whether we have been sent the full event or a redacted copy. -The signatures expected on an event are: +Unless the event is a 3rd party invite, only the signature(s) from the +originating server (the server the `sender` belongs to) are required for +verification. If a signature is from an unknown or expired key, it is skipped. -- The `sender`'s server, unless the invite was created as a result of - 3rd party invite. The sender must already match the 3rd party - invite, and the server which actually sends the event may be a - different server. -- For room versions 1 and 2, the server which created the `event_id`. - Other room versions do not track the `event_id` over federation and - therefore do not need a signature from those servers. +If the event is a 3rd party invite, the sender must already match the 3rd party +invite, and the server which actually sends the event may be a different +server. -Only signatures from known server keys are validated here, any unknown keys are ignored. +Only signatures from known server keys are validated here. Any unknown keys are ignored. In particular, the [policy server key](#validating-policy-server-signatures) is not expected to be published and therefore should be skipped at this stage. Additionally, any keys that are known to have expired prior to the event's `origin_server_ts` are ignored. -If all signatures from known unexpired keys from the originating server(s) are found to be valid, the expected content hash is -calculated as described below. The content hash in the `hashes` property -of the received event is base64-decoded, and the two are compared for -equality. - -If the hash check fails, then it is assumed that this is because we have -only been given a redacted version of the event. To enforce this, the -receiving server should use the redacted copy it calculated rather than -the full copy it received. - {{% boxes/note %}} {{% added-in v="1.18" %}} Events sent in rooms with [Policy Servers](#policy-servers) have [additional](#validating-policy-server-signatures) signature validation requirements. {{% /boxes/note %}} +If all signatures from known unexpired keys from the originating server(s) are +found to be valid, the expected content hash is calculated as described below. +The content hash in the `hashes` property of the received event is base64-decoded, +and the two are compared for equality. + +If the hash check fails, then it is assumed that this is because we have +only been given a redacted version of the event. To enforce this, the +receiving server should use the redacted copy it calculated rather than +the full copy it received. + ### Calculating the reference hash for an event The *reference hash* of an event covers the essential fields of an