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}