Skip to main content

ruma_signatures/
verify.rs

1//! Verification of digital signatures.
2
3use std::collections::{BTreeMap, BTreeSet};
4
5use ruma_common::{
6    AnyKeyName, CanonicalJsonObject, CanonicalJsonValue, IdParseError, OwnedEventId,
7    OwnedServerName, SigningKeyAlgorithm, SigningKeyId, UserId,
8    canonical_json::{CanonicalJsonType, redact},
9    room_version_rules::{RoomVersionRules, SignaturesRules},
10    serde::{Base64, base64::Standard},
11};
12use serde_json::to_string as to_json_string;
13
14#[cfg(test)]
15mod tests;
16
17use crate::{
18    JsonError, VerificationError, content_hash, ed25519::Ed25519Verifier,
19    sign::FIELDS_TO_REMOVE_FOR_SIGNING,
20};
21
22/// Verifies that the signed event contains all the required valid signatures.
23///
24/// Some room versions may require signatures from multiple homeservers, so this function takes a
25/// map from servers to sets of public keys. Signatures are verified for each required homeserver.
26/// All known public keys for a homeserver should be provided. The first one found on the given
27/// event will be used.
28///
29/// If the `Ok` variant is returned by this function, it will contain a [`Verified`] value which
30/// distinguishes an event with valid signatures and a matching content hash with an event with
31/// only valid signatures. See the documentation for [`Verified`] for details.
32///
33/// # Parameters
34///
35/// * `public_key_map`: A map from server name to a map from key identifier to public signing key.
36///   [`required_server_signatures_to_verify_event()`] can be called to get the list of servers that
37///   must appear in this map. If any of those servers is missing, this function will return a
38///   [`VerificationError::NoPublicKeysForEntity`] error.
39/// * `object`: The JSON object of the event that was signed.
40/// * `room_version`: The version of the event's room.
41///
42/// # Examples
43///
44/// ```rust
45/// # use std::collections::BTreeMap;
46/// # use ruma_common::RoomVersionId;
47/// # use ruma_common::serde::Base64;
48/// # use ruma_signatures::{verify_event, Verified};
49/// #
50/// const PUBLIC_KEY: &[u8] = b"XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI";
51///
52/// // Deserialize an event from JSON.
53/// let object = serde_json::from_str(
54///     r#"{
55///         "auth_events": [],
56///         "content": {},
57///         "depth": 3,
58///         "hashes": {
59///             "sha256": "5jM4wQpv6lnBo7CLIghJuHdW+s2CMBJPUOGOC89ncos"
60///         },
61///         "origin": "domain",
62///         "origin_server_ts": 1000000,
63///         "prev_events": [],
64///         "room_id": "!x:domain",
65///         "sender": "@a:domain",
66///         "signatures": {
67///             "domain": {
68///                 "ed25519:1": "KxwGjPSDEtvnFgU00fwFz+l6d2pJM6XBIaMEn81SXPTRl16AqLAYqfIReFGZlHi5KLjAWbOoMszkwsQma+lYAg"
69///             }
70///         },
71///         "type": "X",
72///         "unsigned": {
73///             "age_ts": 1000000
74///         }
75///     }"#
76/// ).unwrap();
77///
78/// // Create the `PublicKeyMap` that will inform `verify_json` which signatures to verify.
79/// let mut public_key_set = BTreeMap::new();
80/// public_key_set.insert("ed25519:1".into(), Base64::parse(PUBLIC_KEY.to_owned()).unwrap());
81/// let mut public_key_map = BTreeMap::new();
82/// public_key_map.insert("domain".into(), public_key_set);
83///
84/// // Get the redaction rules for the version of the current room.
85/// let rules =
86///     RoomVersionId::V6.rules().expect("The rules should be known for a supported room version");
87///
88/// // Verify at least one signature for each entity in `public_key_map`.
89/// let verification_result = verify_event(&public_key_map, &object, &rules);
90/// assert!(verification_result.is_ok());
91/// assert_eq!(verification_result.unwrap(), Verified::All);
92/// ```
93pub fn verify_event(
94    public_key_map: &PublicKeyMap,
95    object: &CanonicalJsonObject,
96    rules: &RoomVersionRules,
97) -> Result<Verified, VerificationError> {
98    let redacted = redact(object.clone(), &rules.redaction, None).map_err(JsonError::from)?;
99
100    let hashes = match object.get("hashes") {
101        Some(CanonicalJsonValue::Object(hashes)) => hashes,
102        Some(value) => {
103            return Err(JsonError::InvalidType {
104                path: "hashes".to_owned(),
105                expected: CanonicalJsonType::Object,
106                found: value.json_type(),
107            }
108            .into());
109        }
110        None => return Err(JsonError::MissingField { path: "hashes".to_owned() }.into()),
111    };
112
113    let hash = match hashes.get("sha256") {
114        Some(CanonicalJsonValue::String(hash)) => hash,
115        Some(value) => {
116            return Err(JsonError::InvalidType {
117                path: "hashes.sha256".to_owned(),
118                expected: CanonicalJsonType::String,
119                found: value.json_type(),
120            }
121            .into());
122        }
123        None => return Err(JsonError::MissingField { path: "hashes.sha256".to_owned() }.into()),
124    };
125
126    let signature_map = match object.get("signatures") {
127        Some(CanonicalJsonValue::Object(signatures)) => signatures,
128        Some(value) => {
129            return Err(JsonError::InvalidType {
130                path: "signatures".to_owned(),
131                expected: CanonicalJsonType::Object,
132                found: value.json_type(),
133            }
134            .into());
135        }
136        None => return Err(JsonError::MissingField { path: "signatures".to_owned() }.into()),
137    };
138
139    let servers_to_check = required_server_signatures_to_verify_event(object, &rules.signatures)?;
140    let canonical_json = to_canonical_json_string_for_signing(&redacted)?;
141
142    for entity_id in servers_to_check {
143        verify_canonical_json_for_entity(
144            entity_id.as_str(),
145            public_key_map,
146            signature_map,
147            canonical_json.as_bytes(),
148        )?;
149    }
150
151    let calculated_hash = content_hash(object)?;
152
153    if let Ok(hash) = Base64::<Standard>::parse(hash)
154        && hash.as_bytes() == calculated_hash.as_bytes()
155    {
156        return Ok(Verified::All);
157    }
158
159    Ok(Verified::Signatures)
160}
161
162/// Uses a set of public keys to verify a signed JSON object.
163///
164/// Signatures using an unsupported algorithm are ignored, but each entity must have at least one
165/// signature from a supported algorithm.
166///
167/// Unlike `content_hash` and `reference_hash`, this function does not report an error if the
168/// canonical JSON is larger than 65535 bytes; this function may be used for requests that are
169/// larger than just one PDU's maximum size.
170///
171/// # Parameters
172///
173/// * `public_key_map`: A map from entity identifiers to a map from key identifiers to public keys.
174///   Generally, entity identifiers are server names — the host/IP/port of a homeserver (e.g.
175///   `example.com`) for which a signature must be verified. Key identifiers for each server (e.g.
176///   `ed25519:1`) then map to their respective public keys.
177/// * `object`: The JSON object that was signed.
178///
179/// # Errors
180///
181/// Returns an error if verification fails.
182///
183/// # Examples
184///
185/// ```rust
186/// use std::collections::BTreeMap;
187///
188/// use ruma_common::serde::Base64;
189///
190/// const PUBLIC_KEY: &[u8] = b"XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI";
191///
192/// // Deserialize the signed JSON.
193/// let object = serde_json::from_str(
194///     r#"{
195///         "signatures": {
196///             "domain": {
197///                 "ed25519:1": "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"
198///             }
199///         }
200///     }"#
201/// ).unwrap();
202///
203/// // Create the `PublicKeyMap` that will inform `verify_json` which signatures to verify.
204/// let mut public_key_set = BTreeMap::new();
205/// public_key_set.insert("ed25519:1".into(), Base64::parse(PUBLIC_KEY.to_owned()).unwrap());
206/// let mut public_key_map = BTreeMap::new();
207/// public_key_map.insert("domain".into(), public_key_set);
208///
209/// // Verify at least one signature for each entity in `public_key_map`.
210/// assert!(ruma_signatures::verify_json(&public_key_map, &object).is_ok());
211/// ```
212pub fn verify_json(
213    public_key_map: &PublicKeyMap,
214    object: &CanonicalJsonObject,
215) -> Result<(), VerificationError> {
216    let signature_map = match object.get("signatures") {
217        Some(CanonicalJsonValue::Object(signatures)) => signatures,
218        Some(value) => {
219            return Err(JsonError::InvalidType {
220                path: "signatures".to_owned(),
221                expected: CanonicalJsonType::Object,
222                found: value.json_type(),
223            }
224            .into());
225        }
226        None => return Err(JsonError::MissingField { path: "signatures".to_owned() }.into()),
227    };
228
229    let canonical_json = to_canonical_json_string_for_signing(object)?;
230
231    for entity_id in signature_map.keys() {
232        verify_canonical_json_for_entity(
233            entity_id,
234            public_key_map,
235            signature_map,
236            canonical_json.as_bytes(),
237        )?;
238    }
239
240    Ok(())
241}
242
243/// Check a signed JSON object using the given public key and signature, all provided as bytes.
244///
245/// This is a low-level function. In general you will want to use [`verify_event()`] or
246/// [`verify_json()`].
247///
248/// # Parameters
249///
250/// * `algorithm`: The algorithm used for the signature. Currently this method only supports the
251///   ed25519 algorithm.
252/// * `public_key`: The raw bytes of the public key used to sign the JSON.
253/// * `signature`: The raw bytes of the signature.
254/// * `canonical_json`: The signed canonical JSON bytes. Can be obtained by calling
255///   [`to_canonical_json_string_for_signing()`].
256///
257/// # Errors
258///
259/// Returns an error if verification fails.
260pub fn verify_canonical_json_bytes(
261    algorithm: &SigningKeyAlgorithm,
262    public_key: &[u8],
263    signature: &[u8],
264    canonical_json: &[u8],
265) -> Result<(), VerificationError> {
266    let verifier =
267        verifier_from_algorithm(algorithm).ok_or(VerificationError::UnsupportedAlgorithm)?;
268
269    verify_canonical_json_with(&verifier, public_key, signature, canonical_json)
270}
271
272/// Serialize the given JSON object to prepare it for [signing].
273///
274/// This serializes the object to [canonical JSON] form without the `signatures` and `unsigned`
275/// fields.
276///
277/// # Parameters
278///
279/// * `object`: The JSON object to convert.
280///
281/// # Examples
282///
283/// ```
284/// use ruma_signatures::to_canonical_json_string_for_signing;
285///
286/// let input = r#"{
287///     "本": 2,
288///     "日": 1
289/// }"#;
290///
291/// let object = serde_json::from_str(input)?;
292/// let canonical = to_canonical_json_string_for_signing(&object)?;
293///
294/// assert_eq!(canonical, r#"{"日":1,"本":2}"#);
295/// # Ok::<(), Box<dyn std::error::Error>>(())
296/// ```
297///
298/// [signing]: https://spec.matrix.org/v1.18/appendices/#signing-details
299/// [canonical JSON]: https://spec.matrix.org/v1.18/appendices/#canonical-json
300pub fn to_canonical_json_string_for_signing(
301    object: &CanonicalJsonObject,
302) -> Result<String, JsonError> {
303    to_canonical_json_string_with_fields_to_remove(object, FIELDS_TO_REMOVE_FOR_SIGNING)
304}
305
306/// Serialize the given JSON object to the canonical JSON form without the given fields.
307pub(crate) fn to_canonical_json_string_with_fields_to_remove(
308    object: &CanonicalJsonObject,
309    fields: &[&str],
310) -> Result<String, JsonError> {
311    let mut owned_object = object.clone();
312
313    for field in fields {
314        owned_object.remove(*field);
315    }
316
317    to_json_string(&owned_object).map_err(Into::into)
318}
319
320/// Uses a set of public keys to verify signed canonical JSON bytes for a given entity.
321///
322/// Implements the algorithm described in the spec for [checking signatures].
323///
324/// # Parameters
325///
326/// * `entity_id`: The entity to check the signatures for.
327/// * `public_key_map`: A map from entity identifiers to a map from key identifiers to public keys.
328/// * `signature_map`: The map of signatures from the signed JSON object.
329/// * `canonical_json`: The signed canonical JSON bytes. Can be obtained by calling
330///   [`to_canonical_json_string_for_signing()`].
331///
332/// # Errors
333///
334/// Returns an error if verification fails.
335///
336/// [checking signatures]: https://spec.matrix.org/v1.18/appendices/#checking-for-a-signature
337fn verify_canonical_json_for_entity(
338    entity_id: &str,
339    public_key_map: &PublicKeyMap,
340    signature_map: &CanonicalJsonObject,
341    canonical_json: &[u8],
342) -> Result<(), VerificationError> {
343    let signature_set = match signature_map.get(entity_id) {
344        Some(CanonicalJsonValue::Object(set)) => set,
345        Some(value) => {
346            return Err(JsonError::InvalidType {
347                path: format!("signatures.{entity_id}"),
348                expected: CanonicalJsonType::Object,
349                found: value.json_type(),
350            }
351            .into());
352        }
353        None => return Err(VerificationError::NoSignaturesForEntity(entity_id.to_owned())),
354    };
355
356    let public_keys = public_key_map
357        .get(entity_id)
358        .ok_or_else(|| VerificationError::NoPublicKeysForEntity(entity_id.to_owned()))?;
359
360    let mut checked = false;
361    for (key_id, signature) in signature_set {
362        // If the key is not in the map of public keys, ignore.
363        let Some(public_key) = public_keys.get(key_id) else {
364            continue;
365        };
366
367        // If we cannot parse the key ID, ignore.
368        let Ok(parsed_key_id) = <&SigningKeyId<AnyKeyName>>::try_from(key_id.as_str()) else {
369            continue;
370        };
371
372        // If the signature uses an unknown algorithm, ignore.
373        let Some(verifier) = verifier_from_algorithm(&parsed_key_id.algorithm()) else {
374            continue;
375        };
376
377        let CanonicalJsonValue::String(signature) = signature else {
378            return Err(JsonError::InvalidType {
379                path: format!("signatures.{entity_id}.{key_id}"),
380                expected: CanonicalJsonType::String,
381                found: signature.json_type(),
382            }
383            .into());
384        };
385
386        let signature = Base64::<Standard>::parse(signature).map_err(|error| {
387            VerificationError::InvalidBase64Signature {
388                path: format!("signatures.{entity_id}.{key_id}"),
389                source: error,
390            }
391        })?;
392
393        verify_canonical_json_with(
394            &verifier,
395            public_key.as_bytes(),
396            signature.as_bytes(),
397            canonical_json,
398        )?;
399        checked = true;
400    }
401
402    if !checked {
403        return Err(VerificationError::NoSupportedSignatureForEntity(entity_id.to_owned()));
404    }
405
406    Ok(())
407}
408
409/// Uses a public key to verify signed canonical JSON bytes.
410///
411/// # Parameters
412///
413/// * `verifier`: A [`Verifier`] appropriate for the digital signature algorithm that was used.
414/// * `public_key`: The raw bytes of the public key used to sign the JSON.
415/// * `signature`: The raw bytes of the signature.
416/// * `canonical_json`: The signed canonical JSON bytes. Can be obtained by calling
417///   [`to_canonical_json_string_for_signing()`].
418///
419/// # Errors
420///
421/// Returns an error if verification fails.
422fn verify_canonical_json_with<V>(
423    verifier: &V,
424    public_key: &[u8],
425    signature: &[u8],
426    canonical_json: &[u8],
427) -> Result<(), VerificationError>
428where
429    V: Verifier,
430{
431    verifier.verify_json(public_key, signature, canonical_json).map_err(Into::into)
432}
433
434/// Get the list of servers whose signature must be checked to verify the given event.
435///
436/// Applies the rules for [validating signatures on received events] for populating the list:
437///
438/// - Add the server of the `sender`, except if it's an invite event that results from a third-party
439///   invite.
440/// - For room versions 1 and 2, add the server of the `event_id`.
441/// - For room versions that support restricted join rules, if it's a join event with a
442///   `join_authorised_via_users_server`, add the server of that user.
443///
444/// [validating signatures on received events]: https://spec.matrix.org/v1.18/server-server-api/#validating-hashes-and-signatures-on-received-events
445pub fn required_server_signatures_to_verify_event(
446    object: &CanonicalJsonObject,
447    rules: &SignaturesRules,
448) -> Result<BTreeSet<OwnedServerName>, VerificationError> {
449    let mut servers_to_check = BTreeSet::new();
450
451    if !is_invite_via_third_party_id(object)? {
452        match object.get("sender") {
453            Some(CanonicalJsonValue::String(raw_sender)) => {
454                let user_id = <&UserId>::try_from(raw_sender.as_str()).map_err(|source| {
455                    VerificationError::ParseIdentifier { identifier_type: "user ID", source }
456                })?;
457
458                servers_to_check.insert(user_id.server_name().to_owned());
459            }
460            Some(value) => {
461                return Err(JsonError::InvalidType {
462                    path: "sender".to_owned(),
463                    expected: CanonicalJsonType::String,
464                    found: value.json_type(),
465                }
466                .into());
467            }
468            _ => return Err(JsonError::MissingField { path: "sender".to_owned() }.into()),
469        }
470    }
471
472    if rules.check_event_id_server {
473        match object.get("event_id") {
474            Some(CanonicalJsonValue::String(raw_event_id)) => {
475                let event_id: OwnedEventId = raw_event_id.parse().map_err(|source| {
476                    VerificationError::ParseIdentifier { identifier_type: "event ID", source }
477                })?;
478
479                let server_name =
480                    event_id.server_name().map(ToOwned::to_owned).ok_or_else(|| {
481                        VerificationError::ParseIdentifier {
482                            identifier_type: "event ID",
483                            source: IdParseError::InvalidServerName,
484                        }
485                    })?;
486
487                servers_to_check.insert(server_name);
488            }
489            Some(value) => {
490                return Err(JsonError::InvalidType {
491                    path: "event_id".to_owned(),
492                    expected: CanonicalJsonType::String,
493                    found: value.json_type(),
494                }
495                .into());
496            }
497            _ => {
498                return Err(JsonError::MissingField { path: "event_id".to_owned() }.into());
499            }
500        }
501    }
502
503    if rules.check_join_authorised_via_users_server
504        && let Some(authorized_user) = object
505            .get("content")
506            .and_then(|c| c.as_object())
507            .and_then(|c| c.get("join_authorised_via_users_server"))
508    {
509        let authorized_user = authorized_user.as_str().ok_or_else(|| JsonError::InvalidType {
510            path: "content.join_authorised_via_users_server".to_owned(),
511            expected: CanonicalJsonType::String,
512            found: authorized_user.json_type(),
513        })?;
514        let authorized_user = <&UserId>::try_from(authorized_user).map_err(|source| {
515            VerificationError::ParseIdentifier { identifier_type: "user ID", source }
516        })?;
517
518        servers_to_check.insert(authorized_user.server_name().to_owned());
519    }
520
521    Ok(servers_to_check)
522}
523
524/// Whether the given event is an `m.room.member` invite that was created as the result of a
525/// third-party invite.
526///
527/// Returns an error if the object has not the expected format of an `m.room.member` event.
528fn is_invite_via_third_party_id(object: &CanonicalJsonObject) -> Result<bool, JsonError> {
529    let raw_type = match object.get("type") {
530        Some(CanonicalJsonValue::String(raw_type)) => raw_type,
531        Some(value) => {
532            return Err(JsonError::InvalidType {
533                path: "type".to_owned(),
534                expected: CanonicalJsonType::String,
535                found: value.json_type(),
536            });
537        }
538        None => return Err(JsonError::MissingField { path: "type".to_owned() }),
539    };
540
541    if raw_type != "m.room.member" {
542        return Ok(false);
543    }
544
545    let content = match object.get("content") {
546        Some(CanonicalJsonValue::Object(content)) => content,
547        Some(value) => {
548            return Err(JsonError::InvalidType {
549                path: "content".to_owned(),
550                expected: CanonicalJsonType::Object,
551                found: value.json_type(),
552            });
553        }
554        None => return Err(JsonError::MissingField { path: "content".to_owned() }),
555    };
556
557    let membership = match content.get("membership") {
558        Some(CanonicalJsonValue::String(membership)) => membership,
559        Some(value) => {
560            return Err(JsonError::InvalidType {
561                path: "content.membership".to_owned(),
562                expected: CanonicalJsonType::String,
563                found: value.json_type(),
564            });
565        }
566        None => {
567            return Err(JsonError::MissingField { path: "content.membership".to_owned() });
568        }
569    };
570
571    if membership != "invite" {
572        return Ok(false);
573    }
574
575    match content.get("third_party_invite") {
576        Some(CanonicalJsonValue::Object(_)) => Ok(true),
577        Some(value) => Err(JsonError::InvalidType {
578            path: "content.third_party_invite".to_owned(),
579            expected: CanonicalJsonType::Object,
580            found: value.json_type(),
581        }),
582        None => Ok(false),
583    }
584}
585
586/// A digital signature verifier.
587pub(crate) trait Verifier {
588    /// The error type returned by the verifier.
589    type Error: std::error::Error + Into<VerificationError>;
590
591    /// Use a public key to verify a signature against the JSON object that was signed.
592    ///
593    /// # Parameters
594    ///
595    /// * `public_key`: The raw bytes of the public key of the key pair used to sign the message.
596    /// * `signature`: The raw bytes of the signature to verify.
597    /// * `message`: The raw bytes of the message that was signed.
598    ///
599    /// # Errors
600    ///
601    /// Returns an error if verification fails.
602    fn verify_json(
603        &self,
604        public_key: &[u8],
605        signature: &[u8],
606        message: &[u8],
607    ) -> Result<(), Self::Error>;
608}
609
610/// Get the verifier for the given algorithm, if it is supported.
611fn verifier_from_algorithm(algorithm: &SigningKeyAlgorithm) -> Option<impl Verifier + use<>> {
612    match algorithm {
613        SigningKeyAlgorithm::Ed25519 => Some(Ed25519Verifier),
614        _ => None,
615    }
616}
617
618/// A value returned when an event is successfully verified.
619///
620/// Event verification involves verifying both signatures and a content hash. It is possible for
621/// the signatures on an event to be valid, but for the hash to be different than the one
622/// calculated during verification. This is not necessarily an error condition, as it may indicate
623/// that the event has been redacted. In this case, receiving homeservers should store a redacted
624/// version of the event.
625#[derive(Clone, Debug, Hash, PartialEq, Eq)]
626#[allow(clippy::exhaustive_enums)]
627pub enum Verified {
628    /// All signatures are valid and the content hashes match.
629    All,
630
631    /// All signatures are valid but the content hashes don't match.
632    ///
633    /// This may indicate a redacted event.
634    Signatures,
635}
636
637/// A map from entity names to sets of public keys for that entity.
638///
639/// An entity is generally a homeserver, e.g. `example.com`.
640pub type PublicKeyMap = BTreeMap<String, PublicKeySet>;
641
642/// A set of public keys for a single homeserver.
643///
644/// This is represented as a map from key ID to base64-encoded signature.
645pub type PublicKeySet = BTreeMap<String, Base64>;