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