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