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