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