Skip to main content

matrix_sdk_crypto/
lib.rs

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