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>;