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