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