ruma_signatures/functions.rs
1//! Functions for signing and verifying JSON and events.
2
3use std::{
4 borrow::Cow,
5 collections::{BTreeMap, BTreeSet},
6 mem,
7};
8
9use base64::{alphabet, Engine};
10use ruma_common::{
11 canonical_json::{redact, JsonType},
12 serde::{base64::Standard, Base64},
13 AnyKeyName, CanonicalJsonObject, CanonicalJsonValue, OwnedEventId, OwnedServerName,
14 RoomVersionId, SigningKeyAlgorithm, SigningKeyId, UserId,
15};
16use serde_json::to_string as to_json_string;
17use sha2::{digest::Digest, Sha256};
18
19#[cfg(test)]
20mod tests;
21
22use crate::{
23 keys::{KeyPair, PublicKeyMap},
24 verification::{verifier_from_algorithm, Verified, Verifier},
25 Error, JsonError, ParseError, VerificationError,
26};
27
28const MAX_PDU_BYTES: usize = 65_535;
29
30/// The fields to remove from a JSON object when converting JSON into the "canonical" form.
31static CANONICAL_JSON_FIELDS_TO_REMOVE: &[&str] = &["signatures", "unsigned"];
32
33/// The fields to remove from a JSON object when creating a content hash of an event.
34static CONTENT_HASH_FIELDS_TO_REMOVE: &[&str] = &["hashes", "signatures", "unsigned"];
35
36/// The fields to remove from a JSON object when creating a reference hash of an event.
37static REFERENCE_HASH_FIELDS_TO_REMOVE: &[&str] = &["signatures", "unsigned"];
38
39/// Signs an arbitrary JSON object and adds the signature to an object under the key `signatures`.
40///
41/// If `signatures` is already present, the new signature will be appended to the existing ones.
42///
43/// # Parameters
44///
45/// * `entity_id`: The identifier of the entity creating the signature. Generally this means a
46/// homeserver, e.g. `example.com`.
47/// * `key_pair`: A cryptographic key pair used to sign the JSON.
48/// * `object`: A JSON object to sign according and append a signature to.
49///
50/// # Errors
51///
52/// Returns an error if:
53///
54/// * `object` contains a field called `signatures` that is not a JSON object.
55///
56/// # Examples
57///
58/// A homeserver signs JSON with a key pair:
59///
60/// ```rust
61/// # use ruma_common::serde::base64::Base64;
62/// #
63/// const PKCS8: &str = "\
64/// MFECAQEwBQYDK2VwBCIEINjozvdfbsGEt6DD+7Uf4PiJ/YvTNXV2mIPc/\
65/// tA0T+6tgSEA3TPraTczVkDPTRaX4K+AfUuyx7Mzq1UafTXypnl0t2k\
66/// ";
67///
68/// let document: Base64 = Base64::parse(PKCS8).unwrap();
69///
70/// // Create an Ed25519 key pair.
71/// let key_pair = ruma_signatures::Ed25519KeyPair::from_der(
72/// document.as_bytes(),
73/// "1".into(), // The "version" of the key.
74/// )
75/// .unwrap();
76///
77/// // Deserialize some JSON.
78/// let mut value = serde_json::from_str("{}").unwrap();
79///
80/// // Sign the JSON with the key pair.
81/// assert!(ruma_signatures::sign_json("domain", &key_pair, &mut value).is_ok());
82/// ```
83///
84/// This will modify the JSON from an empty object to a structure like this:
85///
86/// ```json
87/// {
88/// "signatures": {
89/// "domain": {
90/// "ed25519:1": "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"
91/// }
92/// }
93/// }
94/// ```
95pub fn sign_json<K>(
96 entity_id: &str,
97 key_pair: &K,
98 object: &mut CanonicalJsonObject,
99) -> Result<(), Error>
100where
101 K: KeyPair,
102{
103 let (signatures_key, mut signature_map) = match object.remove_entry("signatures") {
104 Some((key, CanonicalJsonValue::Object(signatures))) => (Cow::Owned(key), signatures),
105 Some(_) => return Err(JsonError::not_of_type("signatures", JsonType::Object)),
106 None => (Cow::Borrowed("signatures"), BTreeMap::new()),
107 };
108
109 let maybe_unsigned_entry = object.remove_entry("unsigned");
110
111 // Get the canonical JSON string.
112 let json = to_json_string(object).map_err(JsonError::Serde)?;
113
114 // Sign the canonical JSON string.
115 let signature = key_pair.sign(json.as_bytes());
116
117 // Insert the new signature in the map we pulled out (or created) previously.
118 let signature_set = signature_map
119 .entry(entity_id.to_owned())
120 .or_insert_with(|| CanonicalJsonValue::Object(BTreeMap::new()));
121
122 let signature_set = match signature_set {
123 CanonicalJsonValue::Object(obj) => obj,
124 _ => return Err(JsonError::not_multiples_of_type("signatures", JsonType::Object)),
125 };
126
127 signature_set.insert(signature.id(), CanonicalJsonValue::String(signature.base64()));
128
129 // Put `signatures` and `unsigned` back in.
130 object.insert(signatures_key.into(), CanonicalJsonValue::Object(signature_map));
131
132 if let Some((k, v)) = maybe_unsigned_entry {
133 object.insert(k, v);
134 }
135
136 Ok(())
137}
138
139/// Converts an event into the [canonical] string form.
140///
141/// [canonical]: https://spec.matrix.org/latest/appendices/#canonical-json
142///
143/// # Parameters
144///
145/// * `object`: The JSON object to convert.
146///
147/// # Examples
148///
149/// ```rust
150/// let input = r#"{
151/// "本": 2,
152/// "日": 1
153/// }"#;
154///
155/// let object = serde_json::from_str(input).unwrap();
156/// let canonical = ruma_signatures::canonical_json(&object).unwrap();
157///
158/// assert_eq!(canonical, r#"{"日":1,"本":2}"#);
159/// ```
160pub fn canonical_json(object: &CanonicalJsonObject) -> Result<String, Error> {
161 canonical_json_with_fields_to_remove(object, CANONICAL_JSON_FIELDS_TO_REMOVE)
162}
163
164/// Uses a set of public keys to verify a signed JSON object.
165///
166/// Signatures using an unsupported algorithm are ignored, but each entity must have at least one
167/// signature from a supported algorithm.
168///
169/// Unlike `content_hash` and `reference_hash`, this function does not report an error if the
170/// canonical JSON is larger than 65535 bytes; this function may be used for requests that are
171/// larger than just one PDU's maximum size.
172///
173/// # Parameters
174///
175/// * `public_key_map`: A map from entity identifiers to a map from key identifiers to public keys.
176/// Generally, entity identifiers are server names — the host/IP/port of a homeserver (e.g.
177/// `example.com`) for which a signature must be verified. Key identifiers for each server (e.g.
178/// `ed25519:1`) then map to their respective public keys.
179/// * `object`: The JSON object that was signed.
180///
181/// # Errors
182///
183/// Returns an error if verification fails.
184///
185/// # Examples
186///
187/// ```rust
188/// use std::collections::BTreeMap;
189///
190/// use ruma_common::serde::Base64;
191///
192/// const PUBLIC_KEY: &[u8] = b"XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI";
193///
194/// // Deserialize the signed JSON.
195/// let object = serde_json::from_str(
196/// r#"{
197/// "signatures": {
198/// "domain": {
199/// "ed25519:1": "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"
200/// }
201/// }
202/// }"#
203/// ).unwrap();
204///
205/// // Create the `PublicKeyMap` that will inform `verify_json` which signatures to verify.
206/// let mut public_key_set = BTreeMap::new();
207/// public_key_set.insert("ed25519:1".into(), Base64::parse(PUBLIC_KEY.to_owned()).unwrap());
208/// let mut public_key_map = BTreeMap::new();
209/// public_key_map.insert("domain".into(), public_key_set);
210///
211/// // Verify at least one signature for each entity in `public_key_map`.
212/// assert!(ruma_signatures::verify_json(&public_key_map, &object).is_ok());
213/// ```
214pub fn verify_json(
215 public_key_map: &PublicKeyMap,
216 object: &CanonicalJsonObject,
217) -> Result<(), Error> {
218 let signature_map = match object.get("signatures") {
219 Some(CanonicalJsonValue::Object(signatures)) => signatures,
220 Some(_) => return Err(JsonError::not_of_type("signatures", JsonType::Object)),
221 None => return Err(JsonError::field_missing_from_object("signatures")),
222 };
223
224 let canonical_json = canonical_json(object)?;
225
226 for entity_id in signature_map.keys() {
227 verify_canonical_json_for_entity(
228 entity_id,
229 public_key_map,
230 signature_map,
231 canonical_json.as_bytes(),
232 )?;
233 }
234
235 Ok(())
236}
237
238/// Uses a set of public keys to verify signed canonical JSON bytes for a given entity.
239///
240/// Implements the algorithm described in the spec for [checking signatures].
241///
242/// # Parameters
243///
244/// * `entity_id`: The entity to check the signatures for.
245/// * `public_key_map`: A map from entity identifiers to a map from key identifiers to public keys.
246/// * `signature_map`: The map of signatures from the signed JSON object.
247/// * `canonical_json`: The signed canonical JSON bytes. Can be obtained by calling
248/// [`canonical_json()`].
249///
250/// # Errors
251///
252/// Returns an error if verification fails.
253///
254/// [checking signatures]: https://spec.matrix.org/latest/appendices/#checking-for-a-signature
255fn verify_canonical_json_for_entity(
256 entity_id: &str,
257 public_key_map: &PublicKeyMap,
258 signature_map: &CanonicalJsonObject,
259 canonical_json: &[u8],
260) -> Result<(), Error> {
261 let signature_set = match signature_map.get(entity_id) {
262 Some(CanonicalJsonValue::Object(set)) => set,
263 Some(_) => {
264 return Err(JsonError::not_multiples_of_type("signature sets", JsonType::Object))
265 }
266 None => return Err(VerificationError::NoSignaturesForEntity(entity_id.to_owned()).into()),
267 };
268
269 let public_keys = public_key_map
270 .get(entity_id)
271 .ok_or_else(|| VerificationError::NoPublicKeysForEntity(entity_id.to_owned()))?;
272
273 let mut checked = false;
274 for (key_id, signature) in signature_set {
275 // If we cannot parse the key ID, ignore.
276 let Ok(parsed_key_id) = <&SigningKeyId<AnyKeyName>>::try_from(key_id.as_str()) else {
277 continue;
278 };
279
280 // If the signature uses an unknown algorithm, ignore.
281 let Some(verifier) = verifier_from_algorithm(&parsed_key_id.algorithm()) else {
282 continue;
283 };
284
285 let public_key = match public_keys.get(key_id) {
286 Some(public_key) => public_key,
287 None => {
288 return Err(VerificationError::PublicKeyNotFound {
289 entity: entity_id.to_owned(),
290 key_id: key_id.clone(),
291 }
292 .into())
293 }
294 };
295
296 let signature = match signature {
297 CanonicalJsonValue::String(signature) => signature,
298 _ => return Err(JsonError::not_of_type("signature", JsonType::String)),
299 };
300
301 let signature = Base64::<Standard>::parse(signature)
302 .map_err(|e| ParseError::base64("signature", signature, e))?;
303
304 verify_canonical_json_with(
305 &verifier,
306 public_key.as_bytes(),
307 signature.as_bytes(),
308 canonical_json,
309 )?;
310 checked = true;
311 }
312
313 if !checked {
314 return Err(VerificationError::NoSupportedSignatureForEntity(entity_id.to_owned()).into());
315 }
316
317 Ok(())
318}
319
320/// Check a signed JSON object using the given public key and signature, all provided as bytes.
321///
322/// This is a low-level function. In general you will want to use [`verify_event()`] or
323/// [`verify_json()`].
324///
325/// # Parameters
326///
327/// * `algorithm`: The algorithm used for the signature. Currently this method only supports the
328/// ed25519 algorithm.
329/// * `public_key`: The raw bytes of the public key used to sign the JSON.
330/// * `signature`: The raw bytes of the signature.
331/// * `canonical_json`: The signed canonical JSON bytes. Can be obtained by calling
332/// [`canonical_json()`].
333///
334/// # Errors
335///
336/// Returns an error if verification fails.
337pub fn verify_canonical_json_bytes(
338 algorithm: &SigningKeyAlgorithm,
339 public_key: &[u8],
340 signature: &[u8],
341 canonical_json: &[u8],
342) -> Result<(), Error> {
343 let verifier =
344 verifier_from_algorithm(algorithm).ok_or(VerificationError::UnsupportedAlgorithm)?;
345
346 verify_canonical_json_with(&verifier, public_key, signature, canonical_json)
347}
348
349/// Uses a public key to verify signed canonical JSON bytes.
350///
351/// # Parameters
352///
353/// * `verifier`: A [`Verifier`] appropriate for the digital signature algorithm that was used.
354/// * `public_key`: The raw bytes of the public key used to sign the JSON.
355/// * `signature`: The raw bytes of the signature.
356/// * `canonical_json`: The signed canonical JSON bytes. Can be obtained by calling
357/// [`canonical_json()`].
358///
359/// # Errors
360///
361/// Returns an error if verification fails.
362fn verify_canonical_json_with<V>(
363 verifier: &V,
364 public_key: &[u8],
365 signature: &[u8],
366 canonical_json: &[u8],
367) -> Result<(), Error>
368where
369 V: Verifier,
370{
371 verifier.verify_json(public_key, signature, canonical_json)
372}
373
374/// Creates a *content hash* for an event.
375///
376/// The content hash of an event covers the complete event including the unredacted contents. It is
377/// used during federation and is described in the Matrix server-server specification.
378///
379/// # Parameters
380///
381/// * `object`: A JSON object to generate a content hash for.
382///
383/// # Errors
384///
385/// Returns an error if the event is too large.
386pub fn content_hash(object: &CanonicalJsonObject) -> Result<Base64<Standard, [u8; 32]>, Error> {
387 let json = canonical_json_with_fields_to_remove(object, CONTENT_HASH_FIELDS_TO_REMOVE)?;
388 if json.len() > MAX_PDU_BYTES {
389 return Err(Error::PduSize);
390 }
391
392 let hash = Sha256::digest(json.as_bytes());
393
394 Ok(Base64::new(hash.into()))
395}
396
397/// Creates a *reference hash* for an event.
398///
399/// The reference hash of an event covers the essential fields of an event, including content
400/// hashes.
401///
402/// Returns the hash as a base64-encoded string, without padding. The correct character set is used
403/// depending on the room version:
404///
405/// * For room versions 1 and 2, the standard character set is used for sending the reference hash
406/// of the `auth_events` and `prev_events`.
407/// * For room version 3, the standard character set is used for using the reference hash as the
408/// event ID.
409/// * For newer versions, the URL-safe character set is used for using the reference hash as the
410/// event ID.
411///
412/// # Parameters
413///
414/// * `object`: A JSON object to generate a reference hash for.
415/// * `room_version`: The version of the event's room.
416///
417/// # Errors
418///
419/// Returns an error if the event is too large or redaction fails.
420pub fn reference_hash(
421 object: &CanonicalJsonObject,
422 room_version: &RoomVersionId,
423) -> Result<String, Error> {
424 let redacted_value = redact(object.clone(), room_version, None)?;
425
426 let json =
427 canonical_json_with_fields_to_remove(&redacted_value, REFERENCE_HASH_FIELDS_TO_REMOVE)?;
428 if json.len() > MAX_PDU_BYTES {
429 return Err(Error::PduSize);
430 }
431
432 let hash = Sha256::digest(json.as_bytes());
433
434 let base64_alphabet = match room_version {
435 RoomVersionId::V1 | RoomVersionId::V2 | RoomVersionId::V3 => alphabet::STANDARD,
436 // Room versions higher than version 3 are url safe base64 encoded
437 _ => alphabet::URL_SAFE,
438 };
439 let base64_engine = base64::engine::GeneralPurpose::new(
440 &base64_alphabet,
441 base64::engine::general_purpose::NO_PAD,
442 );
443
444 Ok(base64_engine.encode(hash))
445}
446
447/// Hashes and signs an event and adds the hash and signature to objects under the keys `hashes` and
448/// `signatures`, respectively.
449///
450/// If `hashes` and/or `signatures` are already present, the new data will be appended to the
451/// existing data.
452///
453/// # Parameters
454///
455/// * `entity_id`: The identifier of the entity creating the signature. Generally this means a
456/// homeserver, e.g. "example.com".
457/// * `key_pair`: A cryptographic key pair used to sign the event.
458/// * `object`: A JSON object to be hashed and signed according to the Matrix specification.
459/// * `room_version`: The version of the event's room.
460///
461/// # Errors
462///
463/// Returns an error if:
464///
465/// * `object` contains a field called `content` that is not a JSON object.
466/// * `object` contains a field called `hashes` that is not a JSON object.
467/// * `object` contains a field called `signatures` that is not a JSON object.
468/// * `object` is missing the `type` field or the field is not a JSON string.
469///
470/// # Examples
471///
472/// ```rust
473/// # use ruma_common::{RoomVersionId, serde::base64::Base64};
474/// # use ruma_signatures::{hash_and_sign_event, Ed25519KeyPair};
475/// #
476/// const PKCS8: &str = "\
477/// MFECAQEwBQYDK2VwBCIEINjozvdfbsGEt6DD+7Uf4PiJ/YvTNXV2mIPc/\
478/// tA0T+6tgSEA3TPraTczVkDPTRaX4K+AfUuyx7Mzq1UafTXypnl0t2k\
479/// ";
480///
481/// let document: Base64 = Base64::parse(PKCS8).unwrap();
482///
483/// // Create an Ed25519 key pair.
484/// let key_pair = Ed25519KeyPair::from_der(
485/// document.as_bytes(),
486/// "1".into(), // The "version" of the key.
487/// )
488/// .unwrap();
489///
490/// // Deserialize an event from JSON.
491/// let mut object = serde_json::from_str(
492/// r#"{
493/// "room_id": "!x:domain",
494/// "sender": "@a:domain",
495/// "origin": "domain",
496/// "origin_server_ts": 1000000,
497/// "signatures": {},
498/// "hashes": {},
499/// "type": "X",
500/// "content": {},
501/// "prev_events": [],
502/// "auth_events": [],
503/// "depth": 3,
504/// "unsigned": {
505/// "age_ts": 1000000
506/// }
507/// }"#,
508/// )
509/// .unwrap();
510///
511/// // Hash and sign the JSON with the key pair.
512/// assert!(hash_and_sign_event("domain", &key_pair, &mut object, &RoomVersionId::V1).is_ok());
513/// ```
514///
515/// This will modify the JSON from the structure shown to a structure like this:
516///
517/// ```json
518/// {
519/// "auth_events": [],
520/// "content": {},
521/// "depth": 3,
522/// "hashes": {
523/// "sha256": "5jM4wQpv6lnBo7CLIghJuHdW+s2CMBJPUOGOC89ncos"
524/// },
525/// "origin": "domain",
526/// "origin_server_ts": 1000000,
527/// "prev_events": [],
528/// "room_id": "!x:domain",
529/// "sender": "@a:domain",
530/// "signatures": {
531/// "domain": {
532/// "ed25519:1": "KxwGjPSDEtvnFgU00fwFz+l6d2pJM6XBIaMEn81SXPTRl16AqLAYqfIReFGZlHi5KLjAWbOoMszkwsQma+lYAg"
533/// }
534/// },
535/// "type": "X",
536/// "unsigned": {
537/// "age_ts": 1000000
538/// }
539/// }
540/// ```
541///
542/// Notice the addition of `hashes` and `signatures`.
543pub fn hash_and_sign_event<K>(
544 entity_id: &str,
545 key_pair: &K,
546 object: &mut CanonicalJsonObject,
547 room_version: &RoomVersionId,
548) -> Result<(), Error>
549where
550 K: KeyPair,
551{
552 let hash = content_hash(object)?;
553
554 let hashes_value = object
555 .entry("hashes".to_owned())
556 .or_insert_with(|| CanonicalJsonValue::Object(BTreeMap::new()));
557
558 match hashes_value {
559 CanonicalJsonValue::Object(hashes) => {
560 hashes.insert("sha256".into(), CanonicalJsonValue::String(hash.encode()))
561 }
562 _ => return Err(JsonError::not_of_type("hashes", JsonType::Object)),
563 };
564
565 let mut redacted = redact(object.clone(), room_version, None)?;
566
567 sign_json(entity_id, key_pair, &mut redacted)?;
568
569 object.insert("signatures".into(), mem::take(redacted.get_mut("signatures").unwrap()));
570
571 Ok(())
572}
573
574/// Verifies that the signed event contains all the required valid signatures.
575///
576/// Some room versions may require signatures from multiple homeservers, so this function takes a
577/// map from servers to sets of public keys. Signatures are verified for each required homeserver.
578/// All known public keys for a homeserver should be provided. The first one found on the given
579/// event will be used.
580///
581/// If the `Ok` variant is returned by this function, it will contain a [`Verified`] value which
582/// distinguishes an event with valid signatures and a matching content hash with an event with
583/// only valid signatures. See the documentation for [`Verified`] for details.
584///
585/// # Parameters
586///
587/// * `public_key_map`: A map from entity identifiers to a map from key identifiers to public keys.
588/// Generally, entity identifiers are server names—the host/IP/port of a homeserver (e.g.
589/// "example.com") for which a signature must be verified. Key identifiers for each server (e.g.
590/// "ed25519:1") then map to their respective public keys.
591/// * `object`: The JSON object of the event that was signed.
592/// * `room_version`: The version of the event's room.
593///
594/// # Examples
595///
596/// ```rust
597/// # use std::collections::BTreeMap;
598/// # use ruma_common::RoomVersionId;
599/// # use ruma_common::serde::Base64;
600/// # use ruma_signatures::{verify_event, Verified};
601/// #
602/// const PUBLIC_KEY: &[u8] = b"XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI";
603///
604/// // Deserialize an event from JSON.
605/// let object = serde_json::from_str(
606/// r#"{
607/// "auth_events": [],
608/// "content": {},
609/// "depth": 3,
610/// "hashes": {
611/// "sha256": "5jM4wQpv6lnBo7CLIghJuHdW+s2CMBJPUOGOC89ncos"
612/// },
613/// "origin": "domain",
614/// "origin_server_ts": 1000000,
615/// "prev_events": [],
616/// "room_id": "!x:domain",
617/// "sender": "@a:domain",
618/// "signatures": {
619/// "domain": {
620/// "ed25519:1": "KxwGjPSDEtvnFgU00fwFz+l6d2pJM6XBIaMEn81SXPTRl16AqLAYqfIReFGZlHi5KLjAWbOoMszkwsQma+lYAg"
621/// }
622/// },
623/// "type": "X",
624/// "unsigned": {
625/// "age_ts": 1000000
626/// }
627/// }"#
628/// ).unwrap();
629///
630/// // Create the `PublicKeyMap` that will inform `verify_json` which signatures to verify.
631/// let mut public_key_set = BTreeMap::new();
632/// public_key_set.insert("ed25519:1".into(), Base64::parse(PUBLIC_KEY.to_owned()).unwrap());
633/// let mut public_key_map = BTreeMap::new();
634/// public_key_map.insert("domain".into(), public_key_set);
635///
636/// // Verify at least one signature for each entity in `public_key_map`.
637/// let verification_result = verify_event(&public_key_map, &object, &RoomVersionId::V6);
638/// assert!(verification_result.is_ok());
639/// assert_eq!(verification_result.unwrap(), Verified::All);
640/// ```
641pub fn verify_event(
642 public_key_map: &PublicKeyMap,
643 object: &CanonicalJsonObject,
644 room_version: &RoomVersionId,
645) -> Result<Verified, Error> {
646 let redacted = redact(object.clone(), room_version, None)?;
647
648 let hash = match object.get("hashes") {
649 Some(hashes_value) => match hashes_value {
650 CanonicalJsonValue::Object(hashes) => match hashes.get("sha256") {
651 Some(hash_value) => match hash_value {
652 CanonicalJsonValue::String(hash) => hash,
653 _ => return Err(JsonError::not_of_type("sha256 hash", JsonType::String)),
654 },
655 None => return Err(JsonError::not_of_type("hashes", JsonType::Object)),
656 },
657 _ => return Err(JsonError::field_missing_from_object("sha256")),
658 },
659 None => return Err(JsonError::field_missing_from_object("hashes")),
660 };
661
662 let signature_map = match object.get("signatures") {
663 Some(CanonicalJsonValue::Object(signatures)) => signatures,
664 Some(_) => return Err(JsonError::not_of_type("signatures", JsonType::Object)),
665 None => return Err(JsonError::field_missing_from_object("signatures")),
666 };
667
668 let servers_to_check = servers_to_check_signatures(object, room_version)?;
669 let canonical_json = canonical_json(&redacted)?;
670
671 for entity_id in servers_to_check {
672 verify_canonical_json_for_entity(
673 entity_id.as_str(),
674 public_key_map,
675 signature_map,
676 canonical_json.as_bytes(),
677 )?;
678 }
679
680 let calculated_hash = content_hash(object)?;
681
682 if let Ok(hash) = Base64::<Standard>::parse(hash) {
683 if hash.as_bytes() == calculated_hash.as_bytes() {
684 return Ok(Verified::All);
685 }
686 }
687
688 Ok(Verified::Signatures)
689}
690
691/// Internal implementation detail of the canonical JSON algorithm.
692///
693/// Allows customization of the fields that will be removed before serializing.
694fn canonical_json_with_fields_to_remove(
695 object: &CanonicalJsonObject,
696 fields: &[&str],
697) -> Result<String, Error> {
698 let mut owned_object = object.clone();
699
700 for field in fields {
701 owned_object.remove(*field);
702 }
703
704 to_json_string(&owned_object).map_err(|e| Error::Json(e.into()))
705}
706
707/// Extracts the server names to check signatures for given event.
708///
709/// Respects the rules for [validating signatures on received events] for populating the result:
710///
711/// - Add the server of the sender, except if it's an invite event that results from a third-party
712/// invite.
713/// - For room versions 1 and 2, add the server of the `event_id`.
714/// - For room versions that support restricted join rules, if it's a join event with a
715/// `join_authorised_via_users_server`, add the server of that user.
716///
717/// [validating signatures on received events]: https://spec.matrix.org/latest/server-server-api/#validating-hashes-and-signatures-on-received-events
718fn servers_to_check_signatures(
719 object: &CanonicalJsonObject,
720 room_version: &RoomVersionId,
721) -> Result<BTreeSet<OwnedServerName>, Error> {
722 let mut servers_to_check = BTreeSet::new();
723
724 if !is_invite_via_third_party_id(object)? {
725 match object.get("sender") {
726 Some(CanonicalJsonValue::String(raw_sender)) => {
727 let user_id = <&UserId>::try_from(raw_sender.as_str())
728 .map_err(|e| Error::from(ParseError::UserId(e)))?;
729
730 servers_to_check.insert(user_id.server_name().to_owned());
731 }
732 _ => return Err(JsonError::not_of_type("sender", JsonType::String)),
733 };
734 }
735
736 match room_version {
737 RoomVersionId::V1 | RoomVersionId::V2 => match object.get("event_id") {
738 Some(CanonicalJsonValue::String(raw_event_id)) => {
739 let event_id: OwnedEventId =
740 raw_event_id.parse().map_err(|e| Error::from(ParseError::EventId(e)))?;
741
742 let server_name = event_id
743 .server_name()
744 .ok_or_else(|| {
745 ParseError::from_event_id_by_room_version(&event_id, room_version)
746 })?
747 .to_owned();
748
749 servers_to_check.insert(server_name);
750 }
751 _ => {
752 return Err(JsonError::field_missing_from_object("event_id"));
753 }
754 },
755 RoomVersionId::V3
756 | RoomVersionId::V4
757 | RoomVersionId::V5
758 | RoomVersionId::V6
759 | RoomVersionId::V7 => {}
760 // TODO: And for all future versions that have join_authorised_via_users_server
761 RoomVersionId::V8 | RoomVersionId::V9 | RoomVersionId::V10 | RoomVersionId::V11 => {
762 if let Some(authorized_user) = object
763 .get("content")
764 .and_then(|c| c.as_object())
765 .and_then(|c| c.get("join_authorised_via_users_server"))
766 {
767 let authorized_user = authorized_user.as_str().ok_or_else(|| {
768 JsonError::not_of_type("join_authorised_via_users_server", JsonType::String)
769 })?;
770 let authorized_user = <&UserId>::try_from(authorized_user)
771 .map_err(|e| Error::from(ParseError::UserId(e)))?;
772
773 servers_to_check.insert(authorized_user.server_name().to_owned());
774 }
775 }
776 _ => unimplemented!(),
777 }
778
779 Ok(servers_to_check)
780}
781
782/// Whether the given event is an `m.room.member` invite that was created as the result of a
783/// third-party invite.
784///
785/// Returns an error if the object has not the expected format of an `m.room.member` event.
786fn is_invite_via_third_party_id(object: &CanonicalJsonObject) -> Result<bool, Error> {
787 let Some(CanonicalJsonValue::String(raw_type)) = object.get("type") else {
788 return Err(JsonError::not_of_type("type", JsonType::String));
789 };
790
791 if raw_type != "m.room.member" {
792 return Ok(false);
793 }
794
795 let Some(CanonicalJsonValue::Object(content)) = object.get("content") else {
796 return Err(JsonError::not_of_type("content", JsonType::Object));
797 };
798
799 let Some(CanonicalJsonValue::String(membership)) = content.get("membership") else {
800 return Err(JsonError::not_of_type("membership", JsonType::String));
801 };
802
803 if membership != "invite" {
804 return Ok(false);
805 }
806
807 match content.get("third_party_invite") {
808 Some(CanonicalJsonValue::Object(_)) => Ok(true),
809 None => Ok(false),
810 _ => Err(JsonError::not_of_type("third_party_invite", JsonType::Object)),
811 }
812}