Skip to main content

tuwunel_service/server_keys/
verify.rs

1//! Event-ID derivation and federation signature verification.
2//!
3//! Verification resolves required signing keys through the server-key service,
4//! applies room-version signature rules, and optionally adds derived event IDs
5//! to newer event formats.
6
7use ruma::{
8	CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, RoomVersionId,
9	signatures::{Verified, verify_event},
10};
11use serde_json::value::RawValue as RawJsonValue;
12use tuwunel_core::{
13	Err, Result, implement,
14	matrix::{event::gen_event_id_canonical_json, room_version},
15};
16
17/// Derives an event ID, runs event verification, and returns canonical JSON.
18///
19/// Missing verification keys may be fetched. Newer room versions receive the
20/// derived `event_id` after any successful [`Verified`] result, including the
21/// signatures-only classification produced for a content-hash mismatch.
22#[implement(super::Service)]
23pub async fn validate_and_add_event_id(
24	&self,
25	pdu: &RawJsonValue,
26	room_version_id: &RoomVersionId,
27) -> Result<(OwnedEventId, CanonicalJsonObject)> {
28	let (event_id, mut value) = gen_event_id_canonical_json(pdu, room_version_id)?;
29
30	if let Err(e) = self
31		.verify_event(&value, Some(room_version_id))
32		.await
33	{
34		return Err!(BadServerResponse(debug_error!(
35			"Event {event_id} failed verification: {e:?}"
36		)));
37	}
38
39	// For v3+ rooms we add the event_id, but for v1/v2 rooms it's already present.
40	if !room_version::rules(room_version_id)?
41		.event_format
42		.require_event_id
43	{
44		value.insert("event_id".into(), CanonicalJsonValue::String(event_id.as_str().into()));
45	}
46
47	Ok((event_id, value))
48}
49
50/// Derives and checks an event using only keys already in local storage.
51///
52/// The method rejects the event before verification when any required key is
53/// absent. Any successful [`Verified`] classification is accepted, including a
54/// signatures-only result after a content-hash mismatch.
55#[implement(super::Service)]
56pub async fn validate_and_add_event_id_no_fetch(
57	&self,
58	pdu: &RawJsonValue,
59	room_version_id: &RoomVersionId,
60) -> Result<(OwnedEventId, CanonicalJsonObject)> {
61	let (event_id, mut value) = gen_event_id_canonical_json(pdu, room_version_id)?;
62	let room_version_rules = room_version::rules(room_version_id)?;
63
64	if !self
65		.required_keys_exist(&value, &room_version_rules)
66		.await
67	{
68		return Err!(BadServerResponse(debug_warn!(
69			"Event {event_id} cannot be verified: missing keys."
70		)));
71	}
72
73	if let Err(e) = self
74		.verify_event(&value, Some(room_version_id))
75		.await
76	{
77		return Err!(BadServerResponse(debug_error!(
78			"Event {event_id} failed verification: {e:?}"
79		)));
80	}
81
82	// For v3+ rooms we add the event_id, but for v1/v2 rooms it's already present.
83	if !room_version_rules.event_format.require_event_id {
84		value.insert("event_id".into(), CanonicalJsonValue::String(event_id.as_str().into()));
85	}
86
87	Ok((event_id, value))
88}
89
90/// Verifies an event and returns ruma's verification classification.
91///
92/// Required keys are loaded or fetched through [`Self::get_event_keys`]. When
93/// no room version is supplied, version 11 rules are used. Callers must inspect
94/// [`Verified`] to distinguish complete verification from signatures only.
95#[implement(super::Service)]
96pub async fn verify_event(
97	&self,
98	event: &CanonicalJsonObject,
99	room_version_id: Option<&RoomVersionId>,
100) -> Result<Verified> {
101	let room_version_id = room_version_id.unwrap_or(&RoomVersionId::V11);
102	let room_version_rules = room_version::rules(room_version_id)?;
103
104	let event_keys = self
105		.get_event_keys(event, &room_version_rules)
106		.await?;
107
108	verify_event(&event_keys, event, &room_version_rules).map_err(Into::into)
109}
110
111/// Verifies signatures on a canonical JSON object.
112///
113/// Unlike [`Self::verify_event`], this does not verify an event content hash.
114/// Version 11 signature rules are used when no room version is supplied.
115#[implement(super::Service)]
116pub async fn verify_json(
117	&self,
118	event: &CanonicalJsonObject,
119	room_version_id: Option<&RoomVersionId>,
120) -> Result {
121	let room_version_id = room_version_id.unwrap_or(&RoomVersionId::V11);
122	let room_version_rules = room_version::rules(room_version_id)?;
123
124	let event_keys = self
125		.get_event_keys(event, &room_version_rules)
126		.await?;
127
128	ruma::signatures::verify_json(&event_keys, event).map_err(Into::into)
129}