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::{CanonicalJsonFieldError, CanonicalJsonObjectExt, 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)?;
99
100    let hashes = object.get_as_required_object("hashes", "hashes")?;
101    let hash = hashes.get_as_required_string("sha256", "hashes.sha256")?;
102    let signature_map = object.get_as_required_object("signatures", "signatures")?;
103
104    let servers_to_check = required_server_signatures_to_verify_event(object, &rules.signatures)?;
105    let canonical_json = to_canonical_json_string_for_signing(&redacted)?;
106
107    for entity_id in servers_to_check {
108        verify_canonical_json_for_entity(
109            entity_id.as_str(),
110            public_key_map,
111            signature_map,
112            canonical_json.as_bytes(),
113        )?;
114    }
115
116    let calculated_hash = content_hash(object)?;
117
118    if let Ok(hash) = Base64::<Standard>::parse(hash)
119        && hash.as_bytes() == calculated_hash.as_bytes()
120    {
121        return Ok(Verified::All);
122    }
123
124    Ok(Verified::Signatures)
125}
126
127/// Uses a set of public keys to verify a signed JSON object.
128///
129/// Signatures using an unsupported algorithm are ignored, but each entity must have at least one
130/// signature from a supported algorithm.
131///
132/// Unlike `content_hash` and `reference_hash`, this function does not report an error if the
133/// canonical JSON is larger than 65535 bytes; this function may be used for requests that are
134/// larger than just one PDU's maximum size.
135///
136/// # Parameters
137///
138/// * `public_key_map`: A map from entity identifiers to a map from key identifiers to public keys.
139///   Generally, entity identifiers are server names — the host/IP/port of a homeserver (e.g.
140///   `example.com`) for which a signature must be verified. Key identifiers for each server (e.g.
141///   `ed25519:1`) then map to their respective public keys.
142/// * `object`: The JSON object that was signed.
143///
144/// # Errors
145///
146/// Returns an error if verification fails.
147///
148/// # Examples
149///
150/// ```rust
151/// use std::collections::BTreeMap;
152///
153/// use ruma_common::serde::Base64;
154///
155/// const PUBLIC_KEY: &[u8] = b"XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI";
156///
157/// // Deserialize the signed JSON.
158/// let object = serde_json::from_str(
159///     r#"{
160///         "signatures": {
161///             "domain": {
162///                 "ed25519:1": "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"
163///             }
164///         }
165///     }"#
166/// ).unwrap();
167///
168/// // Create the `PublicKeyMap` that will inform `verify_json` which signatures to verify.
169/// let mut public_key_set = BTreeMap::new();
170/// public_key_set.insert("ed25519:1".into(), Base64::parse(PUBLIC_KEY.to_owned()).unwrap());
171/// let mut public_key_map = BTreeMap::new();
172/// public_key_map.insert("domain".into(), public_key_set);
173///
174/// // Verify at least one signature for each entity in `public_key_map`.
175/// assert!(ruma_signatures::verify_json(&public_key_map, &object).is_ok());
176/// ```
177pub fn verify_json(
178    public_key_map: &PublicKeyMap,
179    object: &CanonicalJsonObject,
180) -> Result<(), VerificationError> {
181    let signature_map = object.get_as_required_object("signatures", "signatures")?;
182    let canonical_json = to_canonical_json_string_for_signing(object)?;
183
184    for entity_id in signature_map.keys() {
185        verify_canonical_json_for_entity(
186            entity_id,
187            public_key_map,
188            signature_map,
189            canonical_json.as_bytes(),
190        )?;
191    }
192
193    Ok(())
194}
195
196/// Check a signed JSON object using the given public key and signature, all provided as bytes.
197///
198/// This is a low-level function. In general you will want to use [`verify_event()`] or
199/// [`verify_json()`].
200///
201/// # Parameters
202///
203/// * `algorithm`: The algorithm used for the signature. Currently this method only supports the
204///   ed25519 algorithm.
205/// * `public_key`: The raw bytes of the public key used to sign the JSON.
206/// * `signature`: The raw bytes of the signature.
207/// * `canonical_json`: The signed canonical JSON bytes. Can be obtained by calling
208///   [`to_canonical_json_string_for_signing()`].
209///
210/// # Errors
211///
212/// Returns an error if verification fails.
213pub fn verify_canonical_json_bytes(
214    algorithm: &SigningKeyAlgorithm,
215    public_key: &[u8],
216    signature: &[u8],
217    canonical_json: &[u8],
218) -> Result<(), VerificationError> {
219    let verifier =
220        verifier_from_algorithm(algorithm).ok_or(VerificationError::UnsupportedAlgorithm)?;
221
222    verify_canonical_json_with(&verifier, public_key, signature, canonical_json)
223}
224
225/// Serialize the given JSON object to prepare it for [signing].
226///
227/// This serializes the object to [canonical JSON] form without the `signatures` and `unsigned`
228/// fields.
229///
230/// # Parameters
231///
232/// * `object`: The JSON object to convert.
233///
234/// # Examples
235///
236/// ```
237/// use ruma_signatures::to_canonical_json_string_for_signing;
238///
239/// let input = r#"{
240///     "本": 2,
241///     "日": 1
242/// }"#;
243///
244/// let object = serde_json::from_str(input)?;
245/// let canonical = to_canonical_json_string_for_signing(&object)?;
246///
247/// assert_eq!(canonical, r#"{"日":1,"本":2}"#);
248/// # Ok::<(), Box<dyn std::error::Error>>(())
249/// ```
250///
251/// [signing]: https://spec.matrix.org/v1.18/appendices/#signing-details
252/// [canonical JSON]: https://spec.matrix.org/v1.18/appendices/#canonical-json
253pub fn to_canonical_json_string_for_signing(
254    object: &CanonicalJsonObject,
255) -> Result<String, JsonError> {
256    to_canonical_json_string_with_fields_to_remove(object, FIELDS_TO_REMOVE_FOR_SIGNING)
257}
258
259/// Serialize the given JSON object to the canonical JSON form without the given fields.
260pub(crate) fn to_canonical_json_string_with_fields_to_remove(
261    object: &CanonicalJsonObject,
262    fields: &[&str],
263) -> Result<String, JsonError> {
264    let mut owned_object = object.clone();
265
266    for field in fields {
267        owned_object.remove(*field);
268    }
269
270    to_json_string(&owned_object).map_err(Into::into)
271}
272
273/// Uses a set of public keys to verify signed canonical JSON bytes for a given entity.
274///
275/// Implements the algorithm described in the spec for [checking signatures].
276///
277/// # Parameters
278///
279/// * `entity_id`: The entity to check the signatures for.
280/// * `public_key_map`: A map from entity identifiers to a map from key identifiers to public keys.
281/// * `signature_map`: The map of signatures from the signed JSON object.
282/// * `canonical_json`: The signed canonical JSON bytes. Can be obtained by calling
283///   [`to_canonical_json_string_for_signing()`].
284///
285/// # Errors
286///
287/// Returns an error if verification fails.
288///
289/// [checking signatures]: https://spec.matrix.org/v1.18/appendices/#checking-for-a-signature
290fn verify_canonical_json_for_entity(
291    entity_id: &str,
292    public_key_map: &PublicKeyMap,
293    signature_map: &CanonicalJsonObject,
294    canonical_json: &[u8],
295) -> Result<(), VerificationError> {
296    let signature_set = signature_map
297        .get_as_object(entity_id, format!("signatures.{entity_id}"))?
298        .ok_or_else(|| VerificationError::NoSignaturesForEntity(entity_id.to_owned()))?;
299
300    let public_keys = public_key_map
301        .get(entity_id)
302        .ok_or_else(|| VerificationError::NoPublicKeysForEntity(entity_id.to_owned()))?;
303
304    let mut checked = false;
305    for (key_id, signature) in signature_set {
306        // If the key is not in the map of public keys, ignore.
307        let Some(public_key) = public_keys.get(key_id) else {
308            continue;
309        };
310
311        // If we cannot parse the key ID, ignore.
312        let Ok(parsed_key_id) = <&SigningKeyId<AnyKeyName>>::try_from(key_id.as_str()) else {
313            continue;
314        };
315
316        // If the signature uses an unknown algorithm, ignore.
317        let Some(verifier) = verifier_from_algorithm(&parsed_key_id.algorithm()) else {
318            continue;
319        };
320
321        let CanonicalJsonValue::String(signature) = signature else {
322            return Err(CanonicalJsonFieldError::InvalidType {
323                path: format!("signatures.{entity_id}.{key_id}"),
324                expected: CanonicalJsonType::String,
325                found: signature.json_type(),
326            }
327            .into());
328        };
329
330        let signature = Base64::<Standard>::parse(signature).map_err(|error| {
331            VerificationError::InvalidBase64Signature {
332                path: format!("signatures.{entity_id}.{key_id}"),
333                source: error,
334            }
335        })?;
336
337        verify_canonical_json_with(
338            &verifier,
339            public_key.as_bytes(),
340            signature.as_bytes(),
341            canonical_json,
342        )?;
343        checked = true;
344    }
345
346    if !checked {
347        return Err(VerificationError::NoSupportedSignatureForEntity(entity_id.to_owned()));
348    }
349
350    Ok(())
351}
352
353/// Uses a public key to verify signed canonical JSON bytes.
354///
355/// # Parameters
356///
357/// * `verifier`: A [`Verifier`] appropriate for the digital signature algorithm that was used.
358/// * `public_key`: The raw bytes of the public key used to sign the JSON.
359/// * `signature`: The raw bytes of the signature.
360/// * `canonical_json`: The signed canonical JSON bytes. Can be obtained by calling
361///   [`to_canonical_json_string_for_signing()`].
362///
363/// # Errors
364///
365/// Returns an error if verification fails.
366fn verify_canonical_json_with<V>(
367    verifier: &V,
368    public_key: &[u8],
369    signature: &[u8],
370    canonical_json: &[u8],
371) -> Result<(), VerificationError>
372where
373    V: Verifier,
374{
375    verifier.verify_json(public_key, signature, canonical_json).map_err(Into::into)
376}
377
378/// Get the list of servers whose signature must be checked to verify the given event.
379///
380/// Applies the rules for [validating signatures on received events] for populating the list:
381///
382/// - Add the server of the `sender`, except if it's an invite event that results from a third-party
383///   invite.
384/// - For room versions 1 and 2, add the server of the `event_id`.
385/// - For room versions that support restricted join rules, if it's a join event with a
386///   `join_authorised_via_users_server`, add the server of that user.
387///
388/// [validating signatures on received events]: https://spec.matrix.org/v1.18/server-server-api/#validating-hashes-and-signatures-on-received-events
389pub fn required_server_signatures_to_verify_event(
390    object: &CanonicalJsonObject,
391    rules: &SignaturesRules,
392) -> Result<BTreeSet<OwnedServerName>, VerificationError> {
393    let mut servers_to_check = BTreeSet::new();
394
395    if !is_invite_via_third_party_id(object)? {
396        let sender = object.get_as_required_string("sender", "sender")?;
397        let user_id = <&UserId>::try_from(sender).map_err(|source| {
398            VerificationError::ParseIdentifier { identifier_type: "user ID", source }
399        })?;
400
401        servers_to_check.insert(user_id.server_name().to_owned());
402    }
403
404    if rules.check_event_id_server {
405        let raw_event_id = object.get_as_required_string("event_id", "event_id")?;
406        let event_id: OwnedEventId = raw_event_id.parse().map_err(|source| {
407            VerificationError::ParseIdentifier { identifier_type: "event ID", source }
408        })?;
409
410        let server_name = event_id.server_name().map(ToOwned::to_owned).ok_or_else(|| {
411            VerificationError::ParseIdentifier {
412                identifier_type: "event ID",
413                source: IdParseError::InvalidServerName,
414            }
415        })?;
416
417        servers_to_check.insert(server_name);
418    }
419
420    if rules.check_join_authorised_via_users_server
421        && let Some(authorized_user) = object
422            .get("content")
423            .and_then(|c| c.as_object())
424            .map(|c| {
425                c.get_as_string(
426                    "join_authorised_via_users_server",
427                    "content.join_authorised_via_users_server",
428                )
429            })
430            .transpose()?
431            .flatten()
432    {
433        let authorized_user = <&UserId>::try_from(authorized_user).map_err(|source| {
434            VerificationError::ParseIdentifier { identifier_type: "user ID", source }
435        })?;
436
437        servers_to_check.insert(authorized_user.server_name().to_owned());
438    }
439
440    Ok(servers_to_check)
441}
442
443/// Whether the given event is an `m.room.member` invite that was created as the result of a
444/// third-party invite.
445///
446/// Returns an error if the object has not the expected format of an `m.room.member` event.
447fn is_invite_via_third_party_id(object: &CanonicalJsonObject) -> Result<bool, JsonError> {
448    let event_type = object.get_as_required_string("type", "type")?;
449
450    if event_type != "m.room.member" {
451        return Ok(false);
452    }
453
454    let content = object.get_as_required_object("content", "content")?;
455    let membership = content.get_as_required_string("membership", "content.membership")?;
456
457    if membership != "invite" {
458        return Ok(false);
459    }
460
461    Ok(content.get_as_object("third_party_invite", "content.third_party_invite")?.is_some())
462}
463
464/// A digital signature verifier.
465pub(crate) trait Verifier {
466    /// The error type returned by the verifier.
467    type Error: std::error::Error + Into<VerificationError>;
468
469    /// Use a public key to verify a signature against the JSON object that was signed.
470    ///
471    /// # Parameters
472    ///
473    /// * `public_key`: The raw bytes of the public key of the key pair used to sign the message.
474    /// * `signature`: The raw bytes of the signature to verify.
475    /// * `message`: The raw bytes of the message that was signed.
476    ///
477    /// # Errors
478    ///
479    /// Returns an error if verification fails.
480    fn verify_json(
481        &self,
482        public_key: &[u8],
483        signature: &[u8],
484        message: &[u8],
485    ) -> Result<(), Self::Error>;
486}
487
488/// Get the verifier for the given algorithm, if it is supported.
489fn verifier_from_algorithm(algorithm: &SigningKeyAlgorithm) -> Option<impl Verifier + use<>> {
490    match algorithm {
491        SigningKeyAlgorithm::Ed25519 => Some(Ed25519Verifier),
492        _ => None,
493    }
494}
495
496/// A value returned when an event is successfully verified.
497///
498/// Event verification involves verifying both signatures and a content hash. It is possible for
499/// the signatures on an event to be valid, but for the hash to be different than the one
500/// calculated during verification. This is not necessarily an error condition, as it may indicate
501/// that the event has been redacted. In this case, receiving homeservers should store a redacted
502/// version of the event.
503#[derive(Clone, Debug, Hash, PartialEq, Eq)]
504#[allow(clippy::exhaustive_enums)]
505pub enum Verified {
506    /// All signatures are valid and the content hashes match.
507    All,
508
509    /// All signatures are valid but the content hashes don't match.
510    ///
511    /// This may indicate a redacted event.
512    Signatures,
513}
514
515/// A map from entity names to sets of public keys for that entity.
516///
517/// An entity is generally a homeserver, e.g. `example.com`.
518pub type PublicKeyMap = BTreeMap<String, PublicKeySet>;
519
520/// A set of public keys for a single homeserver.
521///
522/// This is represented as a map from key ID to base64-encoded signature.
523pub type PublicKeySet = BTreeMap<String, Base64>;