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