atagen

matrix-rust-sdk

public
Star0
Watch0
ssh://brewery@pls.lobotomise.me:33/atagen/matrix-rust-sdk.git
crates/matrix-sdk-crypto/src/lib.rsRust1137 lines43.1 KB
1
// Copyright 2020 The Matrix.org Foundation C.I.C.
2
// Copyright 2024 Damir Jelić
3
//
4
// Licensed under the Apache License, Version 2.0 (the "License");
5
// you may not use this file except in compliance with the License.
6
// You may obtain a copy of the License at
7
//
8
//     http://www.apache.org/licenses/LICENSE-2.0
9
//
10
// Unless required by applicable law or agreed to in writing, software
11
// distributed under the License is distributed on an "AS IS" BASIS,
12
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
// See the License for the specific language governing permissions and
14
// limitations under the License.
15
16
#![doc = include_str!("../README.md")]
17
#![cfg_attr(docsrs, feature(doc_cfg))]
18
#![warn(missing_docs, missing_debug_implementations)]
19
#![cfg_attr(target_family = "wasm", allow(clippy::arc_with_non_send_sync))]
20
21
pub mod backups;
22
mod ciphers;
23
pub mod dehydrated_devices;
24
mod error;
25
mod file_encryption;
26
mod gossiping;
27
mod identities;
28
mod machine;
29
pub mod olm;
30
pub mod secret_storage;
31
mod session_manager;
32
pub mod store;
33
pub mod types;
34
mod utilities;
35
mod verification;
36
37
#[cfg(any(test, feature = "testing"))]
38
/// Testing facilities and helpers for crypto tests
39
pub mod testing {
40
    pub use crate::identities::{
41
        device::testing::get_device,
42
        user::testing::{
43
            get_other_identity, get_own_identity, simulate_key_query_response_for_verification,
44
        },
45
    };
46
}
47
48
use std::collections::{BTreeMap, BTreeSet};
49
50
pub use identities::room_identity_state::{
51
    IdentityState, IdentityStatusChange, RoomIdentityChange, RoomIdentityProvider,
52
    RoomIdentityState,
53
};
54
use ruma::OwnedRoomId;
55
56
/// Return type for the room key importing.
57
#[derive(Debug, Clone, PartialEq, Eq)]
58
pub struct RoomKeyImportResult {
59
    /// The number of room keys that were imported.
60
    pub imported_count: usize,
61
    /// The total number of room keys that were found in the export.
62
    pub total_count: usize,
63
    /// The map of keys that were imported.
64
    ///
65
    /// It's a map from room id to a map of the sender key to a set of session
66
    /// ids.
67
    pub keys: BTreeMap<OwnedRoomId, BTreeMap<String, BTreeSet<String>>>,
68
}
69
70
impl RoomKeyImportResult {
71
    pub(crate) fn new(
72
        imported_count: usize,
73
        total_count: usize,
74
        keys: BTreeMap<OwnedRoomId, BTreeMap<String, BTreeSet<String>>>,
75
    ) -> Self {
76
        Self { imported_count, total_count, keys }
77
    }
78
}
79
80
pub use error::{
81
    EventError, MegolmError, OlmError, SessionCreationError, SessionRecipientCollectionError,
82
    SetRoomSettingsError, SignatureError,
83
};
84
pub use file_encryption::{
85
    AttachmentDecryptor, AttachmentEncryptor, DecryptorError, KeyExportError, MediaEncryptionInfo,
86
    decrypt_room_key_export, encrypt_room_key_export,
87
};
88
pub use gossiping::{GossipRequest, GossippedSecret};
89
pub use identities::{
90
    Device, DeviceData, LocalTrust, OtherUserIdentity, OtherUserIdentityData, OwnUserIdentity,
91
    OwnUserIdentityData, UserDevices, UserIdentity, UserIdentityData,
92
};
93
pub use machine::{CrossSigningBootstrapRequests, EncryptionSyncChanges, OlmMachine};
94
use matrix_sdk_common::deserialized_responses::{DecryptedRoomEvent, UnableToDecryptInfo};
95
#[cfg(feature = "qrcode")]
96
pub use matrix_sdk_qrcode;
97
pub use olm::{Account, CrossSigningStatus, EncryptionSettings, Session};
98
use serde::{Deserialize, Serialize};
99
pub use session_manager::CollectStrategy;
100
pub use store::{
101
    CryptoStoreError, SecretImportError, SecretInfo,
102
    types::{CrossSigningKeyExport, TrackedUser},
103
};
104
pub use verification::{
105
    AcceptSettings, AcceptedProtocols, CancelInfo, Emoji, EmojiShortAuthString, Sas, SasState,
106
    Verification, VerificationRequest, VerificationRequestState, format_emojis,
107
};
108
#[cfg(feature = "qrcode")]
109
pub use verification::{QrVerification, QrVerificationState, ScanError};
110
#[doc(no_inline)]
111
pub use vodozemac;
112
113
/// The version of the matrix-sdk-cypto crate being used
114
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
115
116
#[cfg(test)]
117
matrix_sdk_test_utils::init_tracing_for_tests!();
118
119
#[cfg(feature = "uniffi")]
120
uniffi::setup_scaffolding!();
121
122
/// The trust level in the sender's device that is required to decrypt an
123
/// event.
124
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
125
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
126
pub enum TrustRequirement {
127
    /// Decrypt events from everyone regardless of trust.
128
    ///
129
    /// Not recommended, per the guidance of [MSC4153].
130
    ///
131
    /// [MSC4153]: https://github.com/matrix-org/matrix-doc/pull/4153
132
    Untrusted,
133
134
    /// Only decrypt events from cross-signed devices or legacy sessions (Megolm
135
    /// sessions created before we started collecting trust information).
136
    CrossSignedOrLegacy,
137
138
    /// Only decrypt events from cross-signed devices.
139
    CrossSigned,
140
}
141
142
/// Settings for decrypting messages
143
#[derive(Clone, Debug, Deserialize, Serialize)]
144
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
145
pub struct DecryptionSettings {
146
    /// The trust level in the sender's device that is required to decrypt the
147
    /// event. If the sender's device is not sufficiently trusted,
148
    /// [`MegolmError::SenderIdentityNotTrusted`] will be returned.
149
    pub sender_device_trust_requirement: TrustRequirement,
150
}
151
152
/// The result of an attempt to decrypt a room event: either a successful
153
/// decryption, or information on a failure.
154
#[derive(Clone, Debug, Serialize, Deserialize)]
155
pub enum RoomEventDecryptionResult {
156
    /// A successfully-decrypted encrypted event.
157
    Decrypted(DecryptedRoomEvent),
158
159
    /// We were unable to decrypt the event
160
    UnableToDecrypt(UnableToDecryptInfo),
161
}
162
163
#[cfg_attr(doc, aquamarine::aquamarine)]
164
/// A step by step guide that explains how to include [end-to-end-encryption]
165
/// support in a [Matrix] client library.
166
///
167
/// This crate implements a [sans-network-io](https://sans-io.readthedocs.io/)
168
/// state machine that allows you to add [end-to-end-encryption] support to a
169
/// [Matrix] client library.
170
///
171
/// This guide aims to provide a comprehensive understanding of end-to-end
172
/// encryption in Matrix without any prior knowledge requirements. However, it
173
/// is recommended that the reader has a basic understanding of Matrix and its
174
/// [client-server specification] for a more informed and efficient learning
175
/// experience.
176
///
177
/// The [introductory](#introduction) section provides a simplified explanation
178
/// of end-to-end encryption and its implementation in Matrix for those who may
179
/// not have prior knowledge. If you already have a solid understanding of
180
/// end-to-end encryption, including the [Olm] and [Megolm] protocols, you may
181
/// choose to skip directly to the [Getting Started](#getting-started) section.
182
///
183
/// # Table of Contents
184
/// 1. [Introduction](#introduction)
185
/// 2. [Getting started](#getting-started)
186
/// 3. [Decrypting room events](#decryption)
187
/// 4. [Encrypting room events](#encryption)
188
/// 5. [Interactively verifying devices and user identities](#verification)
189
///
190
/// # Introduction
191
///
192
/// Welcome to the first part of this guide, where we will introduce the
193
/// fundamental concepts of end-to-end encryption and its implementation in
194
/// Matrix.
195
///
196
/// This section will provide a clear and concise overview of what
197
/// end-to-end encryption is and why it is important for secure communication.
198
/// You will also learn about how Matrix uses end-to-end encryption to protect
199
/// the privacy and security of its users' communications. Whether you are new
200
/// to the topic or simply want to improve your understanding, this section will
201
/// serve as a solid foundation for the rest of the guide.
202
///
203
/// Let's dive in!
204
///
205
/// ## Notation
206
///
207
/// ## End-to-end-encryption
208
///
209
/// End-to-end encryption (E2EE) is a method of secure communication where only
210
/// the communicating devices, also known as "the ends," can read the data being
211
/// transmitted. This means that the data is encrypted on one device, and can
212
/// only be decrypted on the other device. The server is used only as a
213
/// transport mechanism to deliver messages between devices.
214
///
215
/// The following chart displays how communication between two clients using a
216
/// server in the middle usually works.
217
///
218
/// ```mermaid
219
/// flowchart LR
220
///     alice[Alice]
221
///     bob[Bob]
222
///     subgraph Homeserver
223
///         direction LR
224
///         outbox[Alice outbox]
225
///         inbox[Bob inbox]
226
///         outbox -. unencrypted .-> inbox
227
///     end
228
///
229
///     alice -- encrypted --> outbox
230
///     inbox -- encrypted --> bob
231
/// ```
232
///
233
/// The next chart, instead, displays how the same flow is happening in a
234
/// end-to-end-encrypted world.
235
///
236
/// ```mermaid
237
/// flowchart LR
238
///     alice[Alice]
239
///     bob[Bob]
240
///     subgraph Homeserver
241
///         direction LR
242
///         outbox[Alice outbox]
243
///         inbox[Bob inbox]
244
///         outbox == encrypted ==> inbox
245
///     end
246
///
247
///     alice == encrypted ==> outbox
248
///     inbox == encrypted ==> bob
249
/// ```
250
///
251
/// Note that the path from the outbox to the inbox is now encrypted as well.
252
///
253
/// Alice and Bob have created a secure communication channel
254
/// through which they can exchange messages confidentially, without the risk of
255
/// the server accessing the contents of their messages.
256
///
257
/// ## Publishing cryptographic identities of devices
258
///
259
/// If Alice and Bob want to establish a secure channel over which they can
260
/// exchange messages, they first need learn about each others cryptographic
261
/// identities. This is achieved by using the homeserver as a public key
262
/// directory.
263
///
264
/// A public key directory is used to store and distribute public keys of users
265
/// in an end-to-end encrypted system. The basic idea behind a public key
266
/// directory is that it allows users to easily discover and download the public
267
/// keys of other users with whom they wish to establish an end-to-end encrypted
268
/// communication.
269
///
270
/// Each user generates a pair of public and private keys. The user then uploads
271
/// their public key to the public key directory. Other users can then search
272
/// the directory to find the public key of the user they wish to communicate
273
/// with, and download it to their own device.
274
///
275
/// ```mermaid
276
/// flowchart LR
277
///     alice[Alice]
278
///     subgraph homeserver[Homeserver]
279
///         direction LR
280
///         directory[(Public key directory)]
281
///     end
282
///     bob[Bob]
283
///
284
///     alice -- upload keys --> directory
285
///     directory -- download keys --> bob
286
/// ```
287
///
288
/// Once a user has the other user's public key, they can use it to establish an
289
/// end-to-end encrypted channel using a [key-agreement protocol].
290
///
291
/// ## Using the Triple Diffie-Hellman key-agreement protocol
292
///
293
/// In the triple Diffie-Hellman key agreement protocol (3DH in short), each
294
/// user generates a long-term identity key pair and a set of one-time prekeys.
295
/// When two users want to establish a shared secret key, they exchange their
296
/// public identity keys and one of their prekeys. These public keys are then
297
/// used in a [Diffie-Hellman] key exchange to compute a shared secret key.
298
///
299
/// The use of one-time prekeys ensures that the shared secret key is different
300
/// for each session, even if the same identity keys are used.
301
///
302
/// ```mermaid
303
/// flowchart LR
304
/// subgraph alice_keys[Alice Keys]
305
///     direction TB
306
///     alice_key[Alice's identity key]
307
///     alice_base_key[Alice's one-time key]
308
/// end
309
///
310
/// subgraph bob_keys[Bob Keys]
311
///     direction TB
312
///     bob_key[Bob's identity key]
313
///     bob_one_time[Bob's one-time key]
314
/// end
315
///
316
/// alice_key <--> bob_one_time
317
/// alice_base_key <--> bob_one_time
318
/// alice_base_key <--> bob_key
319
/// ```
320
///
321
/// Similar to [X3DH] (Extended Triple Diffie-Hellman) key agreement protocol
322
///
323
/// ## Speeding up encryption for large groups
324
///
325
/// In the previous section we learned how to utilize a key agreement protocol
326
/// to establish secure 1-to-1 encrypted communication channels. These channels
327
/// allow us to encrypt a message for each device separately.
328
///
329
/// One critical property of these channels is that, if you want to send a
330
/// message to a group of devices, we'll need to encrypt the message for each
331
/// device individually.
332
///
333
/// TODO Explain how megolm fits into this
334
///
335
/// # Getting started
336
///
337
/// Before we start writing any code, let us get familiar with the basic
338
/// principle upon which this library is built.
339
///
340
/// The central piece of the library is the [`OlmMachine`] which acts as a state
341
/// machine which consumes data that gets received from the homeserver and
342
/// outputs data which should be sent to the homeserver.
343
///
344
/// ## Push/pull mechanism
345
///
346
/// The [`OlmMachine`] at the heart of it acts as a state machine that operates
347
/// in a push/pull manner. HTTP responses which were received from the
348
/// homeserver get forwarded into the [`OlmMachine`] and in turn the internal
349
/// state gets updated which produces HTTP requests that need to be sent to the
350
/// homeserver.
351
///
352
/// In a manner, we're pulling data from the server, we update our internal
353
/// state based on the data and in turn push data back to the server.
354
///
355
/// ```mermaid
356
/// flowchart LR
357
///     homeserver[Homeserver]
358
///     client[OlmMachine]
359
///
360
///     homeserver -- pull --> client
361
///     client -- push --> homeserver
362
/// ```
363
///
364
/// ## Initializing the state machine
365
///
366
/// ```
367
/// use anyhow::Result;
368
/// use matrix_sdk_crypto::OlmMachine;
369
/// use ruma::user_id;
370
///
371
/// # #[tokio::main]
372
/// # async fn main() -> Result<()> {
373
/// let user_id = user_id!("@alice:localhost");
374
/// let device_id = "DEVICEID".into();
375
///
376
/// let machine = OlmMachine::new(user_id, device_id).await;
377
/// # Ok(())
378
/// # }
379
/// ```
380
///
381
/// This will create a [`OlmMachine`] that does not persist any data TODO
382
/// ```ignore
383
/// use anyhow::Result;
384
/// use matrix_sdk_crypto::OlmMachine;
385
/// use matrix_sdk_sqlite::SqliteCryptoStore;
386
/// use ruma::user_id;
387
///
388
/// # #[tokio::main]
389
/// # async fn main() -> Result<()> {
390
/// let user_id = user_id!("@alice:localhost");
391
/// let device_id = "DEVICEID".into();
392
///
393
/// let store = SqliteCryptoStore::open("/home/example/matrix-client/", None).await?;
394
///
395
/// let machine = OlmMachine::with_store(user_id, device_id, store).await;
396
/// # Ok(())
397
/// # }
398
/// ```
399
///
400
/// # Decryption
401
///
402
/// In the world of encrypted communication, it is common to start with the
403
/// encryption step when implementing a protocol. However, in the case of adding
404
/// end-to-end encryption support to a Matrix client library, a simpler approach
405
/// is to first focus on the decryption process. This is because there are
406
/// already Matrix clients in existence that support encryption, which means
407
/// that our client library can simply receive encrypted messages and then
408
/// decrypt them.
409
///
410
/// In this section, we will guide you through the minimal steps
411
/// necessary to get the decryption process up and running using the
412
/// matrix-sdk-crypto Rust crate. By the end of this section you should have a
413
/// Matrix client that is able to decrypt room events that other clients have
414
/// sent.
415
///
416
/// To enable decryption the following three steps are needed:
417
///
418
/// 1. [The cryptographic identity of your device needs to be published to the
419
///    homeserver](#uploading-identity-and-one-time-keys).
420
/// 2. [Decryption keys coming in from other devices need to be processed and
421
///    stored](#receiving-room-keys-and-related-changes).
422
/// 3. [Individual messages need to be decrypted](#decrypting-room-events).
423
///
424
/// The simplified flowchart
425
/// ```mermaid
426
/// graph TD
427
///     sync[Sync with the homeserver]
428
///     receive_changes[Push E2EE related changes into the state machine]
429
///     send_outgoing_requests[Send all outgoing requests to the homeserver]
430
///     decrypt[Process the rest of the sync]
431
///
432
///     sync --> receive_changes;
433
///     receive_changes --> send_outgoing_requests;
434
///     send_outgoing_requests --> decrypt;
435
///     decrypt -- repeat --> sync;
436
/// ```
437
///
438
/// ## Uploading identity and one-time keys.
439
///
440
/// To enable end-to-end encryption in a Matrix client, the first step is to
441
/// announce the support for it to other users in the network. This is done by
442
/// publishing the client's long-term device keys and a set of one-time prekeys
443
/// to the Matrix homeserver. The homeserver then makes this information
444
/// available to other devices in the network.
445
///
446
/// The long-term device keys and one-time prekeys allow other devices to
447
/// encrypt messages specifically for your device.
448
///
449
/// To achieve this, you will need to extract any requests that need to be sent
450
/// to the homeserver from the [`OlmMachine`] and send them to the homeserver.
451
/// The following snippet showcases how to achieve this using the
452
/// [`OlmMachine::outgoing_requests()`] method:
453
///
454
/// ```no_run
455
/// # use std::collections::BTreeMap;
456
/// # use ruma::api::client::keys::upload_keys::v3::Response;
457
/// # use anyhow::Result;
458
/// # use matrix_sdk_crypto::{OlmMachine, types::requests::OutgoingRequest};
459
/// # async fn send_request(request: OutgoingRequest) -> Result<Response> {
460
/// #     let response = unimplemented!();
461
/// #     Ok(response)
462
/// # }
463
/// # #[tokio::main]
464
/// # async fn main() -> Result<()> {
465
/// # let machine: OlmMachine = unimplemented!();
466
/// // Get all the outgoing requests.
467
/// let outgoing_requests = machine.outgoing_requests().await?;
468
///
469
/// // Send each request to the server and push the response into the state machine.
470
/// // You can safely send these requests out in parallel.
471
/// for request in outgoing_requests {
472
///     let request_id = request.request_id();
473
///     // Send the request to the server and await a response.
474
///     let response = send_request(request).await?;
475
///     // Push the response into the state machine.
476
///     machine.mark_request_as_sent(&request_id, &response).await?;
477
/// }
478
/// # Ok(())
479
/// # }
480
/// ```
481
///
482
/// #### 🔒 Locking rule
483
///
484
/// It's important to note that the outgoing requests method in the
485
/// [`OlmMachine`], while thread-safe, may return the same request multiple
486
/// times if it is called multiple times before the request has been marked as
487
/// sent. To prevent this issue, it is advisable to encapsulate the outgoing
488
/// request handling logic into a separate helper method and protect it from
489
/// being called multiple times concurrently using a lock.
490
///
491
/// This helps to ensure that the request is only handled once and prevents
492
/// multiple identical requests from being sent.
493
///
494
/// Additionally, if an error occurs while sending a request using the
495
/// [`OlmMachine::outgoing_requests()`] method, the request will be
496
/// naturally retried the next time the method is called.
497
///
498
/// A more complete example, which uses a helper method, might look like this:
499
/// ```no_run
500
/// # use std::collections::BTreeMap;
501
/// # use ruma::api::client::keys::upload_keys::v3::Response;
502
/// # use anyhow::Result;
503
/// # use matrix_sdk_crypto::{OlmMachine, types::requests::OutgoingRequest};
504
/// # async fn send_request(request: &OutgoingRequest) -> Result<Response> {
505
/// #     let response = unimplemented!();
506
/// #     Ok(response)
507
/// # }
508
/// # #[tokio::main]
509
/// # async fn main() -> Result<()> {
510
/// struct Client {
511
///     outgoing_requests_lock: tokio::sync::Mutex<()>,
512
///     olm_machine: OlmMachine,
513
/// }
514
///
515
/// async fn process_outgoing_requests(client: &Client) -> Result<()> {
516
///     // Let's acquire a lock so we know that we don't send out the same request out multiple
517
///     // times.
518
///     let guard = client.outgoing_requests_lock.lock().await;
519
///
520
///     for request in client.olm_machine.outgoing_requests().await? {
521
///         let request_id = request.request_id();
522
///
523
///         match send_request(&request).await {
524
///             Ok(response) => {
525
///                 client.olm_machine.mark_request_as_sent(&request_id, &response).await?;
526
///             }
527
///             Err(error) => {
528
///                 // It's OK to ignore transient HTTP errors since requests will be retried.
529
///                 eprintln!(
530
///                     "Error while sending out a end-to-end encryption \
531
///                     related request: {error:?}"
532
///                 );
533
///             }
534
///         }
535
///     }
536
///
537
///     Ok(())
538
/// }
539
/// # Ok(())
540
/// # }
541
/// ```
542
///
543
/// Once we have the helper method that processes our outgoing requests we can
544
/// structure our sync method as follows:
545
///
546
/// ```no_run
547
/// # use anyhow::Result;
548
/// # use matrix_sdk_crypto::OlmMachine;
549
/// # #[tokio::main]
550
/// # async fn main() -> Result<()> {
551
/// # struct Client {
552
/// #     outgoing_requests_lock: tokio::sync::Mutex<()>,
553
/// #     olm_machine: OlmMachine,
554
/// # }
555
/// # async fn process_outgoing_requests(client: &Client) -> Result<()> {
556
/// #    unimplemented!();
557
/// # }
558
/// # async fn send_out_sync_request(client: &Client) -> Result<()> {
559
/// #    unimplemented!();
560
/// # }
561
/// async fn sync(client: &Client) -> Result<()> {
562
///     // This is happening at the top of the method so we advertise our
563
///     // end-to-end encryption capabilities as soon as possible.
564
///     process_outgoing_requests(client).await?;
565
///
566
///     // We can sync with the homeserver now.
567
///     let response = send_out_sync_request(client).await?;
568
///
569
///     // Process the sync response here.
570
///
571
///     Ok(())
572
/// }
573
/// # Ok(())
574
/// # }
575
/// ```
576
///
577
/// ## Receiving room keys and related changes
578
///
579
/// The next step in our implementation is to forward messages that were sent
580
/// directly to the client's device, and state updates about the one-time
581
/// prekeys, to the [`OlmMachine`]. This is achieved using
582
/// the [`OlmMachine::receive_sync_changes()`] method.
583
///
584
/// The method performs two tasks:
585
///
586
/// 1. It processes and, if necessary, decrypts each [to-device] event that was
587
///    pushed into it, and returns the decrypted events. The original events are
588
///    replaced with their decrypted versions.
589
///
590
/// 2. It produces internal state changes that may trigger the creation of new
591
///    outgoing requests. For example, if the server informs the client that its
592
///    one-time prekeys have been depleted, the OlmMachine will create an
593
///    outgoing request to replenish them.
594
///
595
/// Our updated sync method now looks like this:
596
///
597
/// ```no_run
598
/// # use anyhow::Result;
599
/// # use matrix_sdk_crypto::{
600
///     DecryptionSettings, EncryptionSyncChanges, OlmMachine, TrustRequirement
601
/// };
602
/// # use ruma::api::client::sync::sync_events::v3::Response;
603
/// # #[tokio::main]
604
/// # async fn main() -> Result<()> {
605
/// # struct Client {
606
/// #     outgoing_requests_lock: tokio::sync::Mutex<()>,
607
/// #     olm_machine: OlmMachine,
608
/// # }
609
/// # async fn process_outgoing_requests(client: &Client) -> Result<()> {
610
/// #    unimplemented!();
611
/// # }
612
/// # async fn send_out_sync_request(client: &Client) -> Result<Response> {
613
/// #    unimplemented!();
614
/// # }
615
/// async fn sync(client: &Client) -> Result<()> {
616
///     process_outgoing_requests(client).await?;
617
///
618
///     let response = send_out_sync_request(client).await?;
619
///
620
///     let sync_changes = EncryptionSyncChanges {
621
///         to_device_events: response.to_device.events,
622
///         changed_devices: &response.device_lists,
623
///         one_time_keys_counts: &response.device_one_time_keys_count,
624
///         unused_fallback_keys: response.device_unused_fallback_key_types.as_deref(),
625
///         next_batch_token: Some(response.next_batch),
626
///     };
627
///
628
///     let decryption_settings = DecryptionSettings {
629
///         sender_device_trust_requirement: TrustRequirement::Untrusted
630
///     };
631
///
632
///     // Push the sync changes into the OlmMachine, make sure that this is
633
///     // happening before the `next_batch` token of the sync is persisted.
634
///     let to_device_events = client
635
///         .olm_machine
636
///         .receive_sync_changes(sync_changes, &decryption_settings)
637
///         .await?;
638
///
639
///     // Send the outgoing requests out that the sync changes produced.
640
///     process_outgoing_requests(client).await?;
641
///
642
///     // Process the rest of the sync response here.
643
///
644
///     Ok(())
645
/// }
646
/// # Ok(())
647
/// # }
648
/// ```
649
///
650
/// It is important to note that the names of the fields in the response shown
651
/// in the example match the names of the fields specified in the [sync]
652
/// response specification.
653
///
654
/// It is critical to note that due to the ephemeral nature of to-device
655
/// events[[1]], it is important to process these events before persisting the
656
/// `next_batch` sync token. This is because if the `next_batch` sync token is
657
/// persisted before processing the to-device events, some messages might be
658
/// lost, leading to decryption failures.
659
///
660
/// ## Decrypting room events
661
///
662
/// The final step in the decryption process is to decrypt the room events that
663
/// are received from the server. To do this, the encrypted events must be
664
/// passed to the [`OlmMachine`], which will use the keys that were previously
665
/// exchanged between devices to decrypt the events. The decrypted events can
666
/// then be processed and displayed to the user in the Matrix client.
667
///
668
/// Room message [events] can be decrypted using the
669
/// [`OlmMachine::decrypt_room_event()`] method:
670
///
671
/// ```no_run
672
/// # use std::collections::BTreeMap;
673
/// # use anyhow::Result;
674
/// # use matrix_sdk_crypto::{OlmMachine, DecryptionSettings, TrustRequirement};
675
/// # #[tokio::main]
676
/// # async fn main() -> Result<()> {
677
/// # let encrypted = unimplemented!();
678
/// # let room_id = unimplemented!();
679
/// # let machine: OlmMachine = unimplemented!();
680
/// # let settings = DecryptionSettings { sender_device_trust_requirement: TrustRequirement::Untrusted };
681
/// // Decrypt your room events now.
682
/// let decrypted = machine
683
///     .decrypt_room_event(encrypted, room_id, &settings)
684
///     .await?;
685
/// # Ok(())
686
/// # }
687
/// ```
688
/// It's worth mentioning that the [`OlmMachine::decrypt_room_event()`] method
689
/// is designed to be thread-safe and can be safely called concurrently. This
690
/// means that room message [events] can be processed in parallel, improving the
691
/// overall efficiency of the end-to-end encryption implementation.
692
///
693
/// By allowing room message [events] to be processed concurrently, the client's
694
/// implementation can take full advantage of the capabilities of modern
695
/// hardware and achieve better performance, especially when dealing with a
696
/// large number of messages at once.
697
///
698
/// # Encryption
699
///
700
/// In this section of the guide, we will focus on enabling the encryption of
701
/// messages in our Matrix client library. Up until this point, we have been
702
/// discussing the process of decrypting messages that have been encrypted by
703
/// other devices. Now, we will shift our focus to the process of encrypting
704
/// messages on the client side, so that they can be securely transmitted over
705
/// the Matrix network to other devices.
706
///
707
/// This section will guide you through the steps required to set up the
708
/// encryption process, including establishing the necessary sessions and
709
/// encrypting messages using the Megolm group session. The specific steps are
710
/// outlined below:
711
///
712
/// 1. [Cryptographic devices of other users need to be
713
///    discovered](#tracking-users)
714
///
715
/// 2. [Secure channels between the devices need to be
716
///    established](#establishing-end-to-end-encrypted-channels)
717
///
718
/// 3. [A room key needs to be exchanged with the group](#exchanging-room-keys)
719
///
720
/// 4. [Individual messages need to be encrypted using the room
721
///    key](#encrypting-room-events)
722
///
723
/// The process for enabling encryption in a two-device scenario is also
724
/// depicted in the following sequence diagram:
725
///
726
/// ```mermaid
727
/// sequenceDiagram
728
/// actor Alice
729
/// participant Homeserver
730
/// actor Bob
731
///
732
/// Alice->>Homeserver: Download Bob's one-time prekey
733
/// Homeserver->>Alice: Bob's one-time prekey
734
/// Alice->>Alice: Encrypt the room key
735
/// Alice->>Homeserver: Send the room key to each of Bob's devices
736
/// Homeserver->>Bob: Deliver the room key
737
/// Alice->>Alice: Encrypt the message
738
/// Alice->>Homeserver: Send the encrypted message
739
/// Homeserver->>Bob: Deliver the encrypted message
740
/// ```
741
///
742
/// In the following subsections, we will provide a step-by-step guide on how to
743
/// enable the encryption of messages using the OlmMachine. We will outline the
744
/// specific method calls and usage patterns that are required to establish the
745
/// necessary sessions, encrypt messages, and send them over the Matrix network.
746
///
747
/// ## Tracking users
748
///
749
/// The first step in the process of encrypting a message and sending it to a
750
/// device is to discover the devices that the recipient user has. This can be
751
/// achieved by sending a request to the homeserver to retrieve a list of the
752
/// recipient's device keys. The response to this request will include the
753
/// device keys for all of the devices that belong to the recipient, as well as
754
/// information about their current status and whether or not they support
755
/// end-to-end encryption.
756
///
757
/// The process for discovering and keeping track of devices for a user is
758
/// outlined in the Matrix specification in the "[Tracking the device list for a
759
/// user]" section.
760
///
761
/// A simplified sequence diagram of the process can also be found below.
762
///
763
/// ```mermaid
764
/// sequenceDiagram
765
/// actor Alice
766
/// participant Homeserver
767
///
768
/// Alice->>Homeserver: Sync with the homeserver
769
/// Homeserver->>Alice: Users whose device list has changed
770
/// Alice->>Alice: Mark user's devicel list as outdated
771
/// Alice->>Homeserver: Ask the server for the new device list of all the outdated users
772
/// Alice->>Alice: Update the local device list and mark the users as up-to-date
773
/// ```
774
///
775
/// The OlmMachine refers to users whose devices we are tracking as "tracked
776
/// users" and utilizes the [`OlmMachine::update_tracked_users()`] method to
777
/// start considering users to be tracked. Keeping the above diagram in mind, we
778
/// can now update our sync method as follows:
779
///
780
/// ```no_run
781
/// # use anyhow::Result;
782
/// # use std::ops::Deref;
783
/// # use matrix_sdk_crypto::{
784
/// #     DecryptionSettings, EncryptionSyncChanges, OlmMachine, TrustRequirement
785
/// # };
786
/// # use ruma::api::client::sync::sync_events::v3::{Response, State, JoinedRoom};
787
/// # use ruma::{OwnedUserId, serde::Raw, events::AnySyncStateEvent};
788
/// # #[tokio::main]
789
/// # async fn main() -> Result<()> {
790
/// # struct Client {
791
/// #     outgoing_requests_lock: tokio::sync::Mutex<()>,
792
/// #     olm_machine: OlmMachine,
793
/// # }
794
/// # async fn process_outgoing_requests(client: &Client) -> Result<()> {
795
/// #    unimplemented!();
796
/// # }
797
/// # async fn send_out_sync_request(client: &Client) -> Result<Response> {
798
/// #    unimplemented!();
799
/// # }
800
/// # fn is_member_event_of_a_joined_user(event: &Raw<AnySyncStateEvent>) -> bool {
801
/// #     true
802
/// # }
803
/// # fn get_user_id(event: &Raw<AnySyncStateEvent>) -> OwnedUserId {
804
/// #     unimplemented!();
805
/// # }
806
/// # fn is_room_encrypted(room: &JoinedRoom) -> bool {
807
/// #     true
808
/// # }
809
/// async fn sync(client: &Client) -> Result<()> {
810
///     process_outgoing_requests(client).await?;
811
///
812
///     let response = send_out_sync_request(client).await?;
813
///
814
///     let sync_changes = EncryptionSyncChanges {
815
///         to_device_events: response.to_device.events,
816
///         changed_devices: &response.device_lists,
817
///         one_time_keys_counts: &response.device_one_time_keys_count,
818
///         unused_fallback_keys: response.device_unused_fallback_key_types.as_deref(),
819
///         next_batch_token: Some(response.next_batch),
820
///     };
821
///
822
///     let decryption_settings = DecryptionSettings {
823
///         sender_device_trust_requirement: TrustRequirement::Untrusted
824
///     };
825
///
826
///     // Push the sync changes into the OlmMachine, make sure that this is
827
///     // happening before the `next_batch` token of the sync is persisted.
828
///     let to_device_events = client
829
///         .olm_machine
830
///         .receive_sync_changes(sync_changes, &decryption_settings)
831
///         .await?;
832
///
833
///     // Send the outgoing requests out that the sync changes produced.
834
///     process_outgoing_requests(client).await?;
835
///
836
///     // Collect all the joined and invited users of our end-to-end encrypted rooms here.
837
///     let mut users = Vec::new();
838
///
839
///     for (_, room) in &response.rooms.join {
840
///         // For simplicity reasons we're only looking at the state field of a joined room, but
841
///         // the events in the timeline are important as well.
842
///         if let State::Before(state) = &room.state {
843
///            for event in &state.events {
844
///                 if is_member_event_of_a_joined_user(event) && is_room_encrypted(room) {
845
///                     let user_id = get_user_id(event);
846
///                     users.push(user_id);
847
///                 }
848
///             }
849
///         }
850
///     }
851
///
852
///     // Mark all the users that we consider to be in a end-to-end encrypted room with us to be
853
///     // tracked. We need to know about all the devices each user has so we can later encrypt
854
///     // messages for each of their devices.
855
///     client.olm_machine.update_tracked_users(users.iter().map(Deref::deref)).await?;
856
///
857
///     // Process the rest of the sync response here.
858
///
859
///     Ok(())
860
/// }
861
/// # Ok(())
862
/// # }
863
/// ```
864
///
865
/// Now that we have discovered the devices of the users we'd like to
866
/// communicate with in an end-to-end encrypted manner, we can start considering
867
/// encrypting messages for those devices. This concludes the sync processing
868
/// method, we are now ready to move on to the next section, which will explain
869
/// how to begin the encryption process.
870
///
871
/// ## Establishing end-to-end encrypted channels
872
///
873
/// In the [Triple
874
/// Diffie-Hellman](#using-the-triple-diffie-hellman-key-agreement-protocol)
875
/// section, we described the need for two Curve25519 keys from the recipient
876
/// device to establish a 1-to-1 secure channel: the long-term identity key of a
877
/// device and a one-time prekey. In the previous section, we started tracking
878
/// the device keys, including the long-term identity key that we need. The next
879
/// step is to download the one-time prekey on an on-demand basis and establish
880
/// the 1-to-1 secure channel.
881
///
882
/// To accomplish this, we can use the [`OlmMachine::get_missing_sessions()`]
883
/// method in bulk, which will claim the one-time prekey for all the devices of
884
/// a user that we're not already sharing a 1-to-1 encrypted channel with.
885
///
886
/// #### 🔒 Locking rule
887
///
888
/// As with the [`OlmMachine::outgoing_requests()`] method, it is necessary to
889
/// protect this method with a lock, otherwise we will be creating more 1-to-1
890
/// encrypted channels than necessary.
891
///
892
/// ```no_run
893
/// # use std::collections::{BTreeMap, HashSet};
894
/// # use std::ops::Deref;
895
/// # use anyhow::Result;
896
/// # use ruma::UserId;
897
/// # use ruma::api::client::keys::claim_keys::v3::{Response, Request};
898
/// # use matrix_sdk_crypto::OlmMachine;
899
/// # async fn send_request(request: &Request) -> Result<Response> {
900
/// #     let response = unimplemented!();
901
/// #     Ok(response)
902
/// # }
903
/// # #[tokio::main]
904
/// # async fn main() -> Result<()> {
905
/// # let users: HashSet<&UserId> = HashSet::new();
906
/// # let machine: OlmMachine = unimplemented!();
907
/// // Mark all the users that are part of an encrypted room as tracked
908
/// if let Some((request_id, request)) =
909
///     machine.get_missing_sessions(users.iter().map(Deref::deref)).await?
910
/// {
911
///     let response = send_request(&request).await?;
912
///     machine.mark_request_as_sent(&request_id, &response).await?;
913
/// }
914
/// # Ok(())
915
/// # }
916
/// ```
917
///
918
/// With the ability to exchange messages directly with devices, we can now
919
/// start sharing room keys over the 1-to-1 encrypted channel.
920
///
921
/// ## Exchanging room keys
922
///
923
/// To exchange a room key with our group, we will once again take a bulk
924
/// approach. The [`OlmMachine::share_room_key()`] method is used to accomplish
925
/// this step. This method will create a new room key, if necessary, and encrypt
926
/// it for each device belonging to the users provided as an argument. It will
927
/// then output an array of sendToDevice requests that we must send to the
928
/// server, and mark the requests as sent.
929
///
930
/// #### 🔒 Locking rule
931
///
932
/// Like some of the previous methods, OlmMachine::share_room_key() needs to be
933
/// protected by a lock to prevent the possibility of creating and sending
934
/// multiple room keys simultaneously for the same group. The lock can be
935
/// implemented on a per-room basis, which allows for parallel room key
936
/// exchanges across different rooms.
937
///
938
/// ```no_run
939
/// # use std::collections::{BTreeMap, HashSet};
940
/// # use std::ops::Deref;
941
/// # use anyhow::Result;
942
/// # use ruma::UserId;
943
/// # use ruma::api::client::keys::claim_keys::v3::{Response, Request};
944
/// # use matrix_sdk_crypto::{OlmMachine, types::requests::ToDeviceRequest, EncryptionSettings};
945
/// # async fn send_request(request: &ToDeviceRequest) -> Result<Response> {
946
/// #     let response = unimplemented!();
947
/// #     Ok(response)
948
/// # }
949
/// # #[tokio::main]
950
/// # async fn main() -> Result<()> {
951
/// # let users: HashSet<&UserId> = HashSet::new();
952
/// # let room_id = unimplemented!();
953
/// # let settings = EncryptionSettings::default();
954
/// # let machine: OlmMachine = unimplemented!();
955
/// // Let's share a room key with our group.
956
/// let requests = machine.share_room_key(
957
///     room_id,
958
///     users.iter().map(Deref::deref),
959
///     EncryptionSettings::default(),
960
/// ).await?;
961
///
962
/// // Make sure each request is sent out
963
/// for request in requests {
964
///     let request_id = &request.txn_id;
965
///     let response = send_request(&request).await?;
966
///     machine.mark_request_as_sent(&request_id, &response).await?;
967
/// }
968
/// # Ok(())
969
/// # }
970
/// ```
971
///
972
/// In order to ensure that room keys are rotated and exchanged when needed, the
973
/// [`OlmMachine::share_room_key()`] method should be called before sending
974
/// each room message in an end-to-end encrypted room. If a room key has
975
/// already been exchanged, the method becomes a no-op.
976
///
977
/// ## Encrypting room events
978
///
979
/// After the room key has been successfully shared, a plaintext can be
980
/// encrypted.
981
///
982
/// ```no_run
983
/// # use anyhow::Result;
984
/// # use matrix_sdk_crypto::{DecryptionSettings, OlmMachine, TrustRequirement};
985
/// # use ruma::events::{AnyMessageLikeEventContent, room::message::RoomMessageEventContent};
986
/// # #[tokio::main]
987
/// # async fn main() -> Result<()> {
988
/// # let room_id = unimplemented!();
989
/// # let event = unimplemented!();
990
/// # let machine: OlmMachine = unimplemented!();
991
/// # let settings = DecryptionSettings { sender_device_trust_requirement: TrustRequirement::Untrusted };
992
/// let content = AnyMessageLikeEventContent::RoomMessage(RoomMessageEventContent::text_plain("It's a secret to everybody."));
993
/// let encrypted_content = machine.encrypt_room_event(room_id, content).await?;
994
/// # Ok(())
995
/// # }
996
/// ```
997
///
998
/// ## Appendix: Combining the session creation and room key exchange
999
///
1000
/// The steps from the previous three sections should combined into a single
1001
/// method that is used to send messages.
1002
///
1003
/// ```no_run
1004
/// # use std::collections::{BTreeMap, HashSet};
1005
/// # use std::ops::Deref;
1006
/// # use anyhow::Result;
1007
/// # use serde_json::json;
1008
/// # use ruma::{UserId, RoomId, serde::Raw};
1009
/// # use ruma::api::client::keys::claim_keys::v3::{Response, Request};
1010
/// # use matrix_sdk_crypto::{EncryptionSettings, OlmMachine, types::requests::ToDeviceRequest};
1011
/// # use tokio::sync::MutexGuard;
1012
/// # async fn send_request(request: &Request) -> Result<Response> {
1013
/// #     let response = unimplemented!();
1014
/// #     Ok(response)
1015
/// # }
1016
/// # async fn send_to_device_request(request: &ToDeviceRequest) -> Result<Response> {
1017
/// #     let response = unimplemented!();
1018
/// #     Ok(response)
1019
/// # }
1020
/// # async fn acquire_per_room_lock(room_id: &RoomId) -> MutexGuard<()> {
1021
/// #     unimplemented!();
1022
/// # }
1023
/// # async fn get_joined_members(room_id: &RoomId) -> Vec<&UserId> {
1024
/// #    unimplemented!();
1025
/// # }
1026
/// # fn is_room_encrypted(room_id: &RoomId) -> bool {
1027
/// #     true
1028
/// # }
1029
/// # #[tokio::main]
1030
/// # async fn main() -> Result<()> {
1031
/// # let users: HashSet<&UserId> = HashSet::new();
1032
/// # let machine: OlmMachine = unimplemented!();
1033
/// struct Client {
1034
///     session_establishment_lock: tokio::sync::Mutex<()>,
1035
///     olm_machine: OlmMachine,
1036
/// }
1037
///
1038
/// async fn establish_sessions(client: &Client, users: &[&UserId]) -> Result<()> {
1039
///     if let Some((request_id, request)) =
1040
///         client.olm_machine.get_missing_sessions(users.iter().map(Deref::deref)).await?
1041
///     {
1042
///         let response = send_request(&request).await?;
1043
///         client.olm_machine.mark_request_as_sent(&request_id, &response).await?;
1044
///     }
1045
///
1046
///     Ok(())
1047
/// }
1048
///
1049
/// async fn share_room_key(machine: &OlmMachine, room_id: &RoomId, users: &[&UserId]) -> Result<()> {
1050
///     let _lock = acquire_per_room_lock(room_id).await;
1051
///
1052
///     let requests = machine.share_room_key(
1053
///             room_id,
1054
///             users.iter().map(Deref::deref),
1055
///             EncryptionSettings::default(),
1056
///     ).await?;
1057
///
1058
///     // Make sure each request is sent out
1059
///     for request in requests {
1060
///         let request_id = &request.txn_id;
1061
///         let response = send_to_device_request(&request).await?;
1062
///         machine.mark_request_as_sent(&request_id, &response).await?;
1063
///     }
1064
///
1065
///     Ok(())
1066
/// }
1067
///
1068
/// async fn send_message(client: &Client, room_id: &RoomId, message: &str) -> Result<()> {
1069
///     let mut content = json!({
1070
///         "body": message,
1071
///             "msgtype": "m.text",
1072
///     });
1073
///
1074
///     if is_room_encrypted(room_id) {
1075
///         let content = Raw::new(&json!({
1076
///             "body": message,
1077
///             "msgtype": "m.text",
1078
///         }))?.cast_unchecked();
1079
///
1080
///         let users = get_joined_members(room_id).await;
1081
///
1082
///         establish_sessions(client, &users).await?;
1083
///         share_room_key(&client.olm_machine, room_id, &users).await?;
1084
///
1085
///         let encrypted = client
1086
///             .olm_machine
1087
///             .encrypt_room_event_raw(room_id, "m.room.message", &content)
1088
///             .await?;
1089
///     }
1090
///
1091
///     Ok(())
1092
/// }
1093
/// # Ok(())
1094
/// # }
1095
/// ```
1096
///
1097
/// TODO
1098
///
1099
/// [Matrix]: https://matrix.org/
1100
/// [Olm]: https://gitlab.matrix.org/matrix-org/olm/-/blob/master/docs/olm.md
1101
/// [Diffie-Hellman]: https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange
1102
/// [Megolm]: https://gitlab.matrix.org/matrix-org/olm/blob/master/docs/megolm.md
1103
/// [end-to-end-encryption]: https://en.wikipedia.org/wiki/End-to-end_encryption
1104
/// [homeserver]: https://spec.matrix.org/unstable/#architecture
1105
/// [key-agreement protocol]: https://en.wikipedia.org/wiki/Key-agreement_protocol
1106
/// [client-server specification]: https://spec.matrix.org/latest/client-server-api
1107
/// [forward secrecy]: https://en.wikipedia.org/wiki/Forward_secrecy
1108
/// [replay attacks]: https://en.wikipedia.org/wiki/Replay_attack
1109
/// [Tracking the device list for a user]: https://spec.matrix.org/unstable/client-server-api/#tracking-the-device-list-for-a-user
1110
/// [X3DH]: https://signal.org/docs/specifications/x3dh/
1111
/// [to-device]: https://spec.matrix.org/unstable/client-server-api/#send-to-device-messaging
1112
/// [sync]: https://spec.matrix.org/unstable/client-server-api/#get_matrixclientv3sync
1113
/// [events]: https://spec.matrix.org/unstable/client-server-api/#events
1114
///
1115
/// [1]: https://spec.matrix.org/unstable/client-server-api/#server-behaviour-4
1116
pub mod tutorial {}
1117
1118
#[cfg(test)]
1119
mod test {
1120
    use insta::assert_json_snapshot;
1121
1122
    use crate::{DecryptionSettings, TrustRequirement};
1123
1124
    #[test]
1125
    fn snapshot_trust_requirement() {
1126
        assert_json_snapshot!(TrustRequirement::Untrusted);
1127
        assert_json_snapshot!(TrustRequirement::CrossSignedOrLegacy);
1128
        assert_json_snapshot!(TrustRequirement::CrossSigned);
1129
    }
1130
1131
    #[test]
1132
    fn snapshot_decryption_settings() {
1133
        assert_json_snapshot!(DecryptionSettings {
1134
            sender_device_trust_requirement: TrustRequirement::Untrusted,
1135
        });
1136
    }
1137
}