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