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