ruma-signatures 0.22.0

Digital signatures according to the Matrix specification.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
//! Verification of digital signatures.

use std::collections::{BTreeMap, BTreeSet};

use ruma_common::{
    AnyKeyName, CanonicalJsonObject, CanonicalJsonValue, IdParseError, OwnedEventId,
    OwnedServerName, SigningKeyAlgorithm, SigningKeyId, UserId,
    canonical_json::{
        CanonicalJsonFieldError, CanonicalJsonObjectExt, CanonicalJsonType, RedactingSerializer,
    },
    room_version_rules::{RoomVersionRules, SignaturesRules},
    serde::{Base64, base64::Standard},
};
use ruma_events::{
    StaticEventContent,
    room::policy::{POLICY_SERVER_ED25519_SIGNING_KEY_ID, RoomPolicyEventContent},
};

#[cfg(test)]
mod tests;

use crate::{
    JsonError, VerificationError, content_hash, ed25519::Ed25519Verifier,
    sign::FIELDS_TO_REMOVE_FOR_SIGNING,
};

/// Verifies that the signed event contains all the required valid signatures.
///
/// Some room versions may require signatures from multiple homeservers, so this function takes a
/// map from servers to sets of public keys. Signatures are verified for each required homeserver.
/// All known public keys for a homeserver should be provided. The first one found on the given
/// event will be used.
///
/// If the `Ok` variant is returned by this function, it will contain a [`Verified`] value which
/// distinguishes an event with valid signatures and a matching content hash with an event with
/// only valid signatures. See the documentation for [`Verified`] for details.
///
/// # Parameters
///
/// * `public_key_map`: A map from server name to a map from key identifier to public signing key.
///   [`required_server_signatures_to_verify_event()`] can be called to get the list of servers that
///   must appear in this map. If any of those servers is missing, this function will return a
///   [`VerificationError::NoPublicKeysForEntity`] error.
/// * `object`: The JSON object of the event that was signed.
/// * `room_version`: The version of the event's room.
///
/// # Examples
///
/// ```rust
/// # use std::collections::BTreeMap;
/// # use ruma_common::RoomVersionId;
/// # use ruma_common::serde::Base64;
/// # use ruma_signatures::{verify_event, Verified};
/// #
/// const PUBLIC_KEY: &[u8] = b"XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI";
///
/// // Deserialize an event from JSON.
/// let object = serde_json::from_str(
///     r#"{
///         "auth_events": [],
///         "content": {},
///         "depth": 3,
///         "hashes": {
///             "sha256": "5jM4wQpv6lnBo7CLIghJuHdW+s2CMBJPUOGOC89ncos"
///         },
///         "origin": "domain",
///         "origin_server_ts": 1000000,
///         "prev_events": [],
///         "room_id": "!x:domain",
///         "sender": "@a:domain",
///         "signatures": {
///             "domain": {
///                 "ed25519:1": "KxwGjPSDEtvnFgU00fwFz+l6d2pJM6XBIaMEn81SXPTRl16AqLAYqfIReFGZlHi5KLjAWbOoMszkwsQma+lYAg"
///             }
///         },
///         "type": "X",
///         "unsigned": {
///             "age_ts": 1000000
///         }
///     }"#
/// ).unwrap();
///
/// // Create the `PublicKeyMap` that will inform `verify_json` which signatures to verify.
/// let mut public_key_set = BTreeMap::new();
/// public_key_set.insert("ed25519:1".into(), Base64::parse(PUBLIC_KEY.to_owned()).unwrap());
/// let mut public_key_map = BTreeMap::new();
/// public_key_map.insert("domain".into(), public_key_set);
///
/// // Get the redaction rules for the version of the current room.
/// let rules =
///     RoomVersionId::V6.rules().expect("The rules should be known for a supported room version");
///
/// // Verify at least one signature for each entity in `public_key_map`.
/// let verification_result = verify_event(&public_key_map, &object, &rules);
/// assert!(verification_result.is_ok());
/// assert_eq!(verification_result.unwrap(), Verified::All);
/// ```
pub fn verify_event(
    public_key_map: &PublicKeyMap,
    object: &CanonicalJsonObject,
    rules: &RoomVersionRules,
) -> Result<Verified, VerificationError> {
    let hashes = object.get_as_required_object("hashes", "hashes")?;
    let hash = hashes.get_as_required_string("sha256", "hashes.sha256")?;
    let signature_map = object.get_as_required_object("signatures", "signatures")?;

    let servers_to_check = required_server_signatures_to_verify_event(object, &rules.signatures)?;
    let canonical_json = RedactingSerializer::new()
        .rules(&rules.redaction)
        .custom_redacted_root_fields(FIELDS_TO_REMOVE_FOR_SIGNING)
        .serialize(object)?;

    for entity_id in servers_to_check {
        verify_canonical_json_for_entity(
            entity_id.as_str(),
            public_key_map,
            signature_map,
            canonical_json.as_bytes(),
        )?;
    }

    let calculated_hash = content_hash(object)?;

    if let Ok(hash) = Base64::<Standard>::parse(hash)
        && hash.as_bytes() == calculated_hash.as_bytes()
    {
        return Ok(Verified::All);
    }

    Ok(Verified::Signatures)
}

/// Verify that the given event has a valid signature from the given policy server.
///
/// If the event is an `m.room.policy` event with an empty `state_key` string, this function
/// succeeds without checking the signature.
///
/// For other cases, this returns an error if the signature is missing or invalid.
///
/// # Parameters
///
/// * `room_policy`: The `content` of the `m.room.policy` event in the current state of the room. If
///   there is no `m.room.policy` event in the state of the room or it is invalid, it is assumed
///   that the room has no policy server so this function should not be called to check for the
///   policy server signature.
/// * `object`: The JSON object of the event that was signed.
/// * `rules`: The rules of the version of the event's room.
pub fn verify_policy_server_signature(
    room_policy: &RoomPolicyEventContent,
    object: &CanonicalJsonObject,
    rules: &RoomVersionRules,
) -> Result<(), VerificationError> {
    let event_type = object.get_as_required_string("type", "type")?;

    if event_type == RoomPolicyEventContent::TYPE
        && object
            .get_as_required_string("state_key", "state_key")
            .is_ok_and(|state_key| state_key.is_empty())
    {
        // Don't check the policy server signature.
        return Ok(());
    }

    let signature_map = object.get_as_required_object("signatures", "signatures")?;
    let canonical_json = RedactingSerializer::new()
        .rules(&rules.redaction)
        .custom_redacted_root_fields(FIELDS_TO_REMOVE_FOR_SIGNING)
        .serialize(object)?;

    verify_canonical_json_for_entity(
        room_policy.via.as_str(),
        room_policy,
        signature_map,
        canonical_json.as_bytes(),
    )
}

/// Uses a set of public keys to verify a signed JSON object.
///
/// Signatures using an unsupported algorithm are ignored, but each entity must have at least one
/// signature from a supported algorithm.
///
/// Unlike `content_hash` and `reference_hash`, this function does not report an error if the
/// canonical JSON is larger than 65535 bytes; this function may be used for requests that are
/// larger than just one PDU's maximum size.
///
/// # Parameters
///
/// * `public_key_map`: A map from entity identifiers to a map from key identifiers to public keys.
///   Generally, entity identifiers are server names — the host/IP/port of a homeserver (e.g.
///   `example.com`) for which a signature must be verified. Key identifiers for each server (e.g.
///   `ed25519:1`) then map to their respective public keys.
/// * `object`: The JSON object that was signed.
///
/// # Errors
///
/// Returns an error if verification fails.
///
/// # Examples
///
/// ```rust
/// use std::collections::BTreeMap;
///
/// use ruma_common::serde::Base64;
///
/// const PUBLIC_KEY: &[u8] = b"XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI";
///
/// // Deserialize the signed JSON.
/// let object = serde_json::from_str(
///     r#"{
///         "signatures": {
///             "domain": {
///                 "ed25519:1": "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"
///             }
///         }
///     }"#
/// ).unwrap();
///
/// // Create the `PublicKeyMap` that will inform `verify_json` which signatures to verify.
/// let mut public_key_set = BTreeMap::new();
/// public_key_set.insert("ed25519:1".into(), Base64::parse(PUBLIC_KEY.to_owned()).unwrap());
/// let mut public_key_map = BTreeMap::new();
/// public_key_map.insert("domain".into(), public_key_set);
///
/// // Verify at least one signature for each entity in `public_key_map`.
/// assert!(ruma_signatures::verify_json(&public_key_map, &object).is_ok());
/// ```
pub fn verify_json(
    public_key_map: &PublicKeyMap,
    object: &CanonicalJsonObject,
) -> Result<(), VerificationError> {
    let signature_map = object.get_as_required_object("signatures", "signatures")?;
    let canonical_json = to_canonical_json_string_for_signing(object)?;

    for entity_id in signature_map.keys() {
        verify_canonical_json_for_entity(
            entity_id,
            public_key_map,
            signature_map,
            canonical_json.as_bytes(),
        )?;
    }

    Ok(())
}

/// Check a signed JSON object using the given public key and signature, all provided as bytes.
///
/// This is a low-level function. In general you will want to use [`verify_event()`] or
/// [`verify_json()`].
///
/// # Parameters
///
/// * `algorithm`: The algorithm used for the signature. Currently this method only supports the
///   ed25519 algorithm.
/// * `public_key`: The raw bytes of the public key used to sign the JSON.
/// * `signature`: The raw bytes of the signature.
/// * `canonical_json`: The signed canonical JSON bytes. Can be obtained by calling
///   [`to_canonical_json_string_for_signing()`].
///
/// # Errors
///
/// Returns an error if verification fails.
pub fn verify_canonical_json_bytes(
    algorithm: &SigningKeyAlgorithm,
    public_key: &[u8],
    signature: &[u8],
    canonical_json: &[u8],
) -> Result<(), VerificationError> {
    let verifier =
        verifier_from_algorithm(algorithm).ok_or(VerificationError::UnsupportedAlgorithm)?;

    verify_canonical_json_with(&verifier, public_key, signature, canonical_json)
}

/// Serialize the given JSON object to prepare it for [signing].
///
/// This serializes the object to [canonical JSON] form without the `signatures` and `unsigned`
/// fields.
///
/// # Parameters
///
/// * `object`: The JSON object to convert.
///
/// # Examples
///
/// ```
/// use ruma_signatures::to_canonical_json_string_for_signing;
///
/// let input = r#"{
///     "本": 2,
///     "æ—¥": 1
/// }"#;
///
/// let object = serde_json::from_str(input)?;
/// let canonical = to_canonical_json_string_for_signing(&object)?;
///
/// assert_eq!(canonical, r#"{"日":1,"本":2}"#);
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// [signing]: https://spec.matrix.org/v1.19/appendices/#signing-details
/// [canonical JSON]: https://spec.matrix.org/v1.19/appendices/#canonical-json
pub fn to_canonical_json_string_for_signing(
    object: &CanonicalJsonObject,
) -> Result<String, JsonError> {
    Ok(RedactingSerializer::new()
        .custom_redacted_root_fields(FIELDS_TO_REMOVE_FOR_SIGNING)
        .serialize(object)?)
}

/// Uses a set of public keys to verify signed canonical JSON bytes for a given entity.
///
/// Implements the algorithm described in the spec for [checking signatures].
///
/// # Parameters
///
/// * `entity_id`: The entity to check the signatures for.
/// * `fetch_public_keys`: A type to get the public signing keys of servers by key ID.
/// * `signature_map`: The map of signatures from the signed JSON object.
/// * `canonical_json`: The signed canonical JSON bytes. Can be obtained by calling
///   [`to_canonical_json_string_for_signing()`].
///
/// # Errors
///
/// Returns an error if verification fails.
///
/// [checking signatures]: https://spec.matrix.org/v1.19/appendices/#checking-for-a-signature
fn verify_canonical_json_for_entity(
    entity_id: &str,
    fetch_public_keys: &impl FetchEntityPublicSigningKey,
    signature_map: &CanonicalJsonObject,
    canonical_json: &[u8],
) -> Result<(), VerificationError> {
    let signature_set = signature_map
        .get_as_object(entity_id, format!("signatures.{entity_id}"))?
        .ok_or_else(|| VerificationError::NoSignaturesForEntity(entity_id.to_owned()))?;

    let mut checked = false;
    for (key_id, signature) in signature_set {
        // If the key is not in the map of public keys, ignore.
        let Some(public_key) = fetch_public_keys.public_signing_key(entity_id, key_id)? else {
            continue;
        };

        // If we cannot parse the key ID, ignore.
        let Ok(parsed_key_id) = <&SigningKeyId<AnyKeyName>>::try_from(key_id.as_str()) else {
            continue;
        };

        // If the signature uses an unknown algorithm, ignore.
        let Some(verifier) = verifier_from_algorithm(&parsed_key_id.algorithm()) else {
            continue;
        };

        let CanonicalJsonValue::String(signature) = signature else {
            return Err(CanonicalJsonFieldError::InvalidType {
                path: format!("signatures.{entity_id}.{key_id}"),
                expected: CanonicalJsonType::String,
                found: signature.json_type(),
            }
            .into());
        };

        let signature = Base64::<Standard>::parse(signature).map_err(|error| {
            VerificationError::InvalidBase64Signature {
                path: format!("signatures.{entity_id}.{key_id}"),
                source: error,
            }
        })?;

        verify_canonical_json_with(&verifier, public_key, signature.as_bytes(), canonical_json)?;
        checked = true;
    }

    if !checked {
        return Err(VerificationError::NoSupportedSignatureForEntity(entity_id.to_owned()));
    }

    Ok(())
}

/// Uses a public key to verify signed canonical JSON bytes.
///
/// # Parameters
///
/// * `verifier`: A [`Verifier`] appropriate for the digital signature algorithm that was used.
/// * `public_key`: The raw bytes of the public key used to sign the JSON.
/// * `signature`: The raw bytes of the signature.
/// * `canonical_json`: The signed canonical JSON bytes. Can be obtained by calling
///   [`to_canonical_json_string_for_signing()`].
///
/// # Errors
///
/// Returns an error if verification fails.
fn verify_canonical_json_with<V>(
    verifier: &V,
    public_key: &[u8],
    signature: &[u8],
    canonical_json: &[u8],
) -> Result<(), VerificationError>
where
    V: Verifier,
{
    verifier.verify_json(public_key, signature, canonical_json).map_err(Into::into)
}

/// Get the list of servers whose signature must be checked to verify the given event.
///
/// Applies the rules for [validating signatures on received events] for populating the list:
///
/// - Add the server of the `sender`, except if it's an invite event that results from a third-party
///   invite.
/// - For room versions 1 and 2, add the server of the `event_id`.
/// - For room versions that support restricted join rules, if it's a join event with a
///   `join_authorised_via_users_server`, add the server of that user.
///
/// [validating signatures on received events]: https://spec.matrix.org/v1.19/server-server-api/#validating-hashes-and-signatures-on-received-events
pub fn required_server_signatures_to_verify_event(
    object: &CanonicalJsonObject,
    rules: &SignaturesRules,
) -> Result<BTreeSet<OwnedServerName>, VerificationError> {
    let mut servers_to_check = BTreeSet::new();

    if !is_invite_via_third_party_id(object)? {
        let sender = object.get_as_required_string("sender", "sender")?;
        let user_id = <&UserId>::try_from(sender).map_err(|source| {
            VerificationError::ParseIdentifier { identifier_type: "user ID", source }
        })?;

        servers_to_check.insert(user_id.server_name().to_owned());
    }

    if rules.check_event_id_server {
        let raw_event_id = object.get_as_required_string("event_id", "event_id")?;
        let event_id: OwnedEventId = raw_event_id.parse().map_err(|source| {
            VerificationError::ParseIdentifier { identifier_type: "event ID", source }
        })?;

        let server_name = event_id.server_name().map(ToOwned::to_owned).ok_or_else(|| {
            VerificationError::ParseIdentifier {
                identifier_type: "event ID",
                source: IdParseError::InvalidServerName,
            }
        })?;

        servers_to_check.insert(server_name);
    }

    if rules.check_join_authorised_via_users_server
        && let Some(authorized_user) = object
            .get("content")
            .and_then(|c| c.as_object())
            .map(|c| {
                c.get_as_string(
                    "join_authorised_via_users_server",
                    "content.join_authorised_via_users_server",
                )
            })
            .transpose()?
            .flatten()
    {
        let authorized_user = <&UserId>::try_from(authorized_user).map_err(|source| {
            VerificationError::ParseIdentifier { identifier_type: "user ID", source }
        })?;

        servers_to_check.insert(authorized_user.server_name().to_owned());
    }

    Ok(servers_to_check)
}

/// Whether the given event is an `m.room.member` invite that was created as the result of a
/// third-party invite.
///
/// Returns an error if the object has not the expected format of an `m.room.member` event.
fn is_invite_via_third_party_id(object: &CanonicalJsonObject) -> Result<bool, JsonError> {
    let event_type = object.get_as_required_string("type", "type")?;

    if event_type != "m.room.member" {
        return Ok(false);
    }

    let content = object.get_as_required_object("content", "content")?;
    let membership = content.get_as_required_string("membership", "content.membership")?;

    if membership != "invite" {
        return Ok(false);
    }

    Ok(content.get_as_object("third_party_invite", "content.third_party_invite")?.is_some())
}

/// A digital signature verifier.
pub(crate) trait Verifier {
    /// The error type returned by the verifier.
    type Error: std::error::Error + Into<VerificationError>;

    /// Use a public key to verify a signature against the JSON object that was signed.
    ///
    /// # Parameters
    ///
    /// * `public_key`: The raw bytes of the public key of the key pair used to sign the message.
    /// * `signature`: The raw bytes of the signature to verify.
    /// * `message`: The raw bytes of the message that was signed.
    ///
    /// # Errors
    ///
    /// Returns an error if verification fails.
    fn verify_json(
        &self,
        public_key: &[u8],
        signature: &[u8],
        message: &[u8],
    ) -> Result<(), Self::Error>;
}

/// Get the verifier for the given algorithm, if it is supported.
fn verifier_from_algorithm(algorithm: &SigningKeyAlgorithm) -> Option<impl Verifier + use<>> {
    match algorithm {
        SigningKeyAlgorithm::Ed25519 => Some(Ed25519Verifier),
        _ => None,
    }
}

/// A value returned when an event is successfully verified.
///
/// Event verification involves verifying both signatures and a content hash. It is possible for
/// the signatures on an event to be valid, but for the hash to be different than the one
/// calculated during verification. This is not necessarily an error condition, as it may indicate
/// that the event has been redacted. In this case, receiving homeservers should store a redacted
/// version of the event.
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
#[allow(clippy::exhaustive_enums)]
pub enum Verified {
    /// All signatures are valid and the content hashes match.
    All,

    /// All signatures are valid but the content hashes don't match.
    ///
    /// This may indicate a redacted event.
    Signatures,
}

/// A map from entity names to sets of public keys for that entity.
///
/// An entity is generally a homeserver, e.g. `example.com`.
pub type PublicKeyMap = BTreeMap<String, PublicKeySet>;

/// A set of public keys for a single homeserver.
///
/// This is represented as a map from key ID to base64-encoded signature.
pub type PublicKeySet = BTreeMap<String, Base64>;

/// A trait implemented by types that allow to get the public signing keys for a given entity.
trait FetchEntityPublicSigningKey {
    /// Get the bytes of the public signing key with the given ID for the given entity.
    fn public_signing_key(
        &self,
        entity: &str,
        key_id: &str,
    ) -> Result<Option<&[u8]>, VerificationError>;
}

impl FetchEntityPublicSigningKey for PublicKeyMap {
    fn public_signing_key(
        &self,
        entity: &str,
        key_id: &str,
    ) -> Result<Option<&[u8]>, VerificationError> {
        Ok(self
            .get(entity)
            .ok_or_else(|| VerificationError::NoPublicKeysForEntity(entity.to_owned()))?
            .get(key_id)
            .map(Base64::as_bytes))
    }
}

impl FetchEntityPublicSigningKey for RoomPolicyEventContent {
    fn public_signing_key(
        &self,
        entity: &str,
        key_id: &str,
    ) -> Result<Option<&[u8]>, VerificationError> {
        if entity != self.via {
            return Err(VerificationError::NoPublicKeysForEntity(entity.to_owned()));
        }

        if key_id != POLICY_SERVER_ED25519_SIGNING_KEY_ID {
            return Ok(None);
        }

        Ok(self.public_keys.get(&SigningKeyAlgorithm::Ed25519).map(Base64::as_bytes))
    }
}