Skip to main content

matrix_sdk_crypto/store/
types.rs

1// Copyright 2020 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Data types for persistent storage.
16//!
17//! This module defines the data structures used by the crypto store to
18//! represent objects that are persisted in the database.
19
20use std::{
21    collections::{BTreeMap, HashMap, HashSet},
22    time::Duration,
23};
24
25use ruma::{MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedRoomId, OwnedUserId};
26use serde::{Deserialize, Serialize};
27use vodozemac::{Curve25519PublicKey, base64_encode};
28use zeroize::{Zeroize, ZeroizeOnDrop};
29
30use super::{DehydrationError, GossipRequest};
31use crate::{
32    Account, Device, DeviceData, GossippedSecret, Session, UserIdentity, UserIdentityData,
33    olm::{
34        InboundGroupSession, OlmMessageHash, OutboundGroupSession, PrivateCrossSigningIdentity,
35        SenderData,
36    },
37    types::{
38        EventEncryptionAlgorithm,
39        events::{
40            room_key_bundle::RoomKeyBundleContent,
41            room_key_withheld::{RoomKeyWithheldContent, RoomKeyWithheldEvent},
42        },
43    },
44};
45
46/// Aggregated changes to be saved in the database.
47///
48/// This is an update version of `Changes` that will replace it as #2624
49/// progresses.
50// If you ever add a field here, make sure to update `Changes::is_empty` too.
51#[derive(Default, Debug)]
52#[allow(missing_docs)]
53pub struct PendingChanges {
54    pub account: Option<Account>,
55}
56
57impl PendingChanges {
58    /// Are there any changes stored or is this an empty `Changes` struct?
59    pub fn is_empty(&self) -> bool {
60        self.account.is_none()
61    }
62}
63
64/// Aggregated changes to be saved in the database.
65// If you ever add a field here, make sure to update `Changes::is_empty` too.
66#[derive(Default, Debug)]
67#[allow(missing_docs)]
68pub struct Changes {
69    pub private_identity: Option<PrivateCrossSigningIdentity>,
70    pub backup_version: Option<String>,
71    pub backup_decryption_key: Option<BackupDecryptionKey>,
72    pub dehydrated_device_pickle_key: Option<DehydratedDeviceKey>,
73    pub sessions: Vec<Session>,
74    pub message_hashes: Vec<OlmMessageHash>,
75    pub inbound_group_sessions: Vec<InboundGroupSession>,
76    pub outbound_group_sessions: Vec<OutboundGroupSession>,
77    pub key_requests: Vec<GossipRequest>,
78    pub identities: IdentityChanges,
79    pub devices: DeviceChanges,
80    /// Stores when a `m.room_key.withheld` is received
81    pub withheld_session_info: BTreeMap<OwnedRoomId, BTreeMap<String, RoomKeyWithheldEntry>>,
82    pub room_settings: HashMap<OwnedRoomId, RoomSettings>,
83    pub secrets: Vec<GossippedSecret>,
84    pub next_batch_token: Option<String>,
85
86    /// Historical room key history bundles that we have received and should
87    /// store.
88    pub received_room_key_bundles: Vec<StoredRoomKeyBundleData>,
89
90    /// The set of rooms for which we have requested all room keys from the
91    /// backup in advance of constructing a room key bundle.
92    pub room_key_backups_fully_downloaded: HashSet<OwnedRoomId>,
93
94    /// Updates to the list of rooms where we have recently accepted invites
95    /// (and should now accept a key bundle, if one arrives soon).
96    pub rooms_pending_key_bundle: HashMap<OwnedRoomId, Option<RoomPendingKeyBundleDetails>>,
97}
98
99/// Information about an [MSC4268] room key bundle.
100///
101/// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
102#[derive(Clone, Debug, Serialize, Deserialize)]
103pub struct StoredRoomKeyBundleData {
104    /// The user that sent us this data.
105    pub sender_user: OwnedUserId,
106
107    /// The [`Curve25519PublicKey`] of the device that sent us this data.
108    pub sender_key: Curve25519PublicKey,
109
110    /// Information about the sender of this data and how much we trust that
111    /// information.
112    pub sender_data: SenderData,
113
114    /// The room key bundle data itself.
115    pub bundle_data: RoomKeyBundleContent,
116}
117
118/// A user for which we are tracking the list of devices.
119#[derive(Clone, Debug, Serialize, Deserialize)]
120pub struct TrackedUser {
121    /// The user ID of the user.
122    pub user_id: OwnedUserId,
123    /// The outdate/dirty flag of the user, remembers if the list of devices for
124    /// the user is considered to be out of date. If the list of devices is
125    /// out of date, a `/keys/query` request should be sent out for this
126    /// user.
127    pub dirty: bool,
128}
129
130impl Changes {
131    /// Are there any changes stored or is this an empty `Changes` struct?
132    pub fn is_empty(&self) -> bool {
133        self.private_identity.is_none()
134            && self.backup_version.is_none()
135            && self.backup_decryption_key.is_none()
136            && self.dehydrated_device_pickle_key.is_none()
137            && self.sessions.is_empty()
138            && self.message_hashes.is_empty()
139            && self.inbound_group_sessions.is_empty()
140            && self.outbound_group_sessions.is_empty()
141            && self.key_requests.is_empty()
142            && self.identities.is_empty()
143            && self.devices.is_empty()
144            && self.withheld_session_info.is_empty()
145            && self.room_settings.is_empty()
146            && self.secrets.is_empty()
147            && self.next_batch_token.is_none()
148            && self.received_room_key_bundles.is_empty()
149    }
150}
151
152/// This struct is used to remember whether an identity has undergone a change
153/// or remains the same as the one we already know about.
154///
155/// When the homeserver informs us of a potential change in a user's identity or
156/// device during a `/sync` response, it triggers a `/keys/query` request from
157/// our side. In response to this query, the server provides a comprehensive
158/// snapshot of all the user's devices and identities.
159///
160/// Our responsibility is to discern whether a device or identity is new,
161/// changed, or unchanged.
162#[derive(Debug, Clone, Default)]
163#[allow(missing_docs)]
164pub struct IdentityChanges {
165    pub new: Vec<UserIdentityData>,
166    pub changed: Vec<UserIdentityData>,
167    pub unchanged: Vec<UserIdentityData>,
168}
169
170impl IdentityChanges {
171    pub(super) fn is_empty(&self) -> bool {
172        self.new.is_empty() && self.changed.is_empty()
173    }
174
175    /// Convert the vectors contained in the [`IdentityChanges`] into
176    /// three maps from user id to user identity (new, updated, unchanged).
177    pub(super) fn into_maps(
178        self,
179    ) -> (
180        BTreeMap<OwnedUserId, UserIdentityData>,
181        BTreeMap<OwnedUserId, UserIdentityData>,
182        BTreeMap<OwnedUserId, UserIdentityData>,
183    ) {
184        let new: BTreeMap<_, _> = self
185            .new
186            .into_iter()
187            .map(|identity| (identity.user_id().to_owned(), identity))
188            .collect();
189
190        let changed: BTreeMap<_, _> = self
191            .changed
192            .into_iter()
193            .map(|identity| (identity.user_id().to_owned(), identity))
194            .collect();
195
196        let unchanged: BTreeMap<_, _> = self
197            .unchanged
198            .into_iter()
199            .map(|identity| (identity.user_id().to_owned(), identity))
200            .collect();
201
202        (new, changed, unchanged)
203    }
204}
205
206#[derive(Debug, Clone, Default)]
207#[allow(missing_docs)]
208pub struct DeviceChanges {
209    pub new: Vec<DeviceData>,
210    pub changed: Vec<DeviceData>,
211    pub deleted: Vec<DeviceData>,
212}
213
214/// Updates about [`Device`]s which got received over the `/keys/query`
215/// endpoint.
216#[derive(Clone, Debug, Default)]
217pub struct DeviceUpdates {
218    /// The list of newly discovered devices.
219    ///
220    /// A device being in this list does not necessarily mean that the device
221    /// was just created, it just means that it's the first time we're
222    /// seeing this device.
223    pub new: BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, Device>>,
224    /// The list of changed devices.
225    pub changed: BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, Device>>,
226}
227
228/// Updates about [`UserIdentity`]s which got received over the `/keys/query`
229/// endpoint.
230#[derive(Clone, Debug, Default)]
231pub struct IdentityUpdates {
232    /// The list of newly discovered user identities .
233    ///
234    /// A identity being in this list does not necessarily mean that the
235    /// identity was just created, it just means that it's the first time
236    /// we're seeing this identity.
237    pub new: BTreeMap<OwnedUserId, UserIdentity>,
238    /// The list of changed identities.
239    pub changed: BTreeMap<OwnedUserId, UserIdentity>,
240    /// The list of unchanged identities.
241    pub unchanged: BTreeMap<OwnedUserId, UserIdentity>,
242}
243
244/// The private part of a backup key.
245///
246/// The private part of the key is not used on a regular basis. Rather, it is
247/// used only when we need to *recover* the backup.
248///
249/// Typically, this private key is itself encrypted and stored in server-side
250/// secret storage (SSSS), whence it can be retrieved when it is needed for a
251/// recovery operation. Alternatively, the key can be "gossiped" between devices
252/// via "secret sharing".
253#[derive(Clone, Zeroize, ZeroizeOnDrop, Deserialize, Serialize)]
254#[serde(transparent)]
255pub struct BackupDecryptionKey {
256    pub(crate) inner: Box<[u8; BackupDecryptionKey::KEY_SIZE]>,
257}
258
259impl BackupDecryptionKey {
260    /// The number of bytes the decryption key will hold.
261    pub const KEY_SIZE: usize = 32;
262
263    /// Create a new random decryption key.
264    pub fn new() -> Result<Self, rand::Error> {
265        let mut rng = rand::thread_rng();
266
267        let mut key = Box::new([0u8; Self::KEY_SIZE]);
268        rand::Fill::try_fill(key.as_mut_slice(), &mut rng)?;
269
270        Ok(Self { inner: key })
271    }
272
273    /// Export the [`BackupDecryptionKey`] as a base64 encoded string.
274    pub fn to_base64(&self) -> String {
275        base64_encode(self.inner.as_slice())
276    }
277}
278
279#[cfg(not(tarpaulin_include))]
280impl std::fmt::Debug for BackupDecryptionKey {
281    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
282        f.debug_tuple("BackupDecryptionKey").field(&"...").finish()
283    }
284}
285
286/// The pickle key used to safely store the dehydrated device pickle.
287///
288/// This input key material will be expanded using HKDF into an AES key, MAC
289/// key, and an initialization vector (IV).
290#[derive(Clone, Zeroize, ZeroizeOnDrop, Deserialize, Serialize)]
291#[serde(transparent)]
292pub struct DehydratedDeviceKey {
293    pub(crate) inner: Box<[u8; DehydratedDeviceKey::KEY_SIZE]>,
294}
295
296impl DehydratedDeviceKey {
297    /// The number of bytes the encryption key will hold.
298    pub const KEY_SIZE: usize = 32;
299
300    /// Generates a new random pickle key.
301    pub fn new() -> Result<Self, rand::Error> {
302        let mut rng = rand::thread_rng();
303
304        let mut key = Box::new([0u8; Self::KEY_SIZE]);
305        rand::Fill::try_fill(key.as_mut_slice(), &mut rng)?;
306
307        Ok(Self { inner: key })
308    }
309
310    /// Creates a new dehydration pickle key from the given slice.
311    ///
312    /// Fail if the slice length is not 32.
313    pub fn from_slice(slice: &[u8]) -> Result<Self, DehydrationError> {
314        if slice.len() == 32 {
315            let mut key = Box::new([0u8; 32]);
316            key.copy_from_slice(slice);
317            Ok(DehydratedDeviceKey { inner: key })
318        } else {
319            Err(DehydrationError::PickleKeyLength(slice.len()))
320        }
321    }
322
323    /// Creates a dehydration pickle key from the given bytes.
324    pub fn from_bytes(raw_key: &[u8; 32]) -> Self {
325        let mut inner = Box::new([0u8; Self::KEY_SIZE]);
326        inner.copy_from_slice(raw_key);
327
328        Self { inner }
329    }
330
331    /// Export the [`DehydratedDeviceKey`] as a base64 encoded string.
332    pub fn to_base64(&self) -> String {
333        base64_encode(self.inner.as_slice())
334    }
335}
336
337impl From<&[u8; 32]> for DehydratedDeviceKey {
338    fn from(value: &[u8; 32]) -> Self {
339        DehydratedDeviceKey { inner: Box::new(*value) }
340    }
341}
342
343impl From<DehydratedDeviceKey> for Vec<u8> {
344    fn from(key: DehydratedDeviceKey) -> Self {
345        key.inner.to_vec()
346    }
347}
348
349#[cfg(not(tarpaulin_include))]
350impl std::fmt::Debug for DehydratedDeviceKey {
351    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
352        f.debug_tuple("DehydratedDeviceKey").field(&"...").finish()
353    }
354}
355
356impl DeviceChanges {
357    /// Merge the given `DeviceChanges` into this instance of `DeviceChanges`.
358    pub fn extend(&mut self, other: DeviceChanges) {
359        self.new.extend(other.new);
360        self.changed.extend(other.changed);
361        self.deleted.extend(other.deleted);
362    }
363
364    /// Are there any changes is this an empty [`DeviceChanges`] struct?
365    pub fn is_empty(&self) -> bool {
366        self.new.is_empty() && self.changed.is_empty() && self.deleted.is_empty()
367    }
368}
369
370/// Struct holding info about how many room keys the store has.
371#[derive(Debug, Clone, Default)]
372pub struct RoomKeyCounts {
373    /// The total number of room keys the store has.
374    pub total: usize,
375    /// The number of backed up room keys the store has.
376    pub backed_up: usize,
377}
378
379/// Stored versions of the backup keys.
380#[derive(Default, Clone, Debug)]
381pub struct BackupKeys {
382    /// The key used to decrypt backed up room keys.
383    pub decryption_key: Option<BackupDecryptionKey>,
384    /// The version that we are using for backups.
385    pub backup_version: Option<String>,
386}
387
388/// A struct containing private cross signing keys that can be backed up or
389/// uploaded to the secret store.
390#[derive(Default, Zeroize, ZeroizeOnDrop)]
391pub struct CrossSigningKeyExport {
392    /// The seed of the master key encoded as unpadded base64.
393    pub master_key: Option<String>,
394    /// The seed of the self signing key encoded as unpadded base64.
395    pub self_signing_key: Option<String>,
396    /// The seed of the user signing key encoded as unpadded base64.
397    pub user_signing_key: Option<String>,
398}
399
400#[cfg(not(tarpaulin_include))]
401impl std::fmt::Debug for CrossSigningKeyExport {
402    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
403        f.debug_struct("CrossSigningKeyExport")
404            .field("master_key", &self.master_key.is_some())
405            .field("self_signing_key", &self.self_signing_key.is_some())
406            .field("user_signing_key", &self.user_signing_key.is_some())
407            .finish_non_exhaustive()
408    }
409}
410
411/// Result type telling us if a `/keys/query` response was expected for a given
412/// user.
413#[derive(Clone, Copy, Debug, PartialEq, Eq)]
414pub(crate) enum UserKeyQueryResult {
415    WasPending,
416    WasNotPending,
417
418    /// A query was pending, but we gave up waiting
419    TimeoutExpired,
420}
421
422/// Room encryption settings which are modified by state events or user options
423#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
424pub struct RoomSettings {
425    /// The encryption algorithm that should be used in the room.
426    pub algorithm: EventEncryptionAlgorithm,
427
428    /// Whether state event encryption is enabled.
429    #[cfg(feature = "experimental-encrypted-state-events")]
430    #[serde(default)]
431    pub encrypt_state_events: bool,
432
433    /// Should untrusted devices receive the room key, or should they be
434    /// excluded from the conversation.
435    pub only_allow_trusted_devices: bool,
436
437    /// The maximum time an encryption session should be used for, before it is
438    /// rotated.
439    pub session_rotation_period: Option<Duration>,
440
441    /// The maximum number of messages an encryption session should be used for,
442    /// before it is rotated.
443    pub session_rotation_period_messages: Option<usize>,
444}
445
446impl Default for RoomSettings {
447    fn default() -> Self {
448        Self {
449            algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2,
450            #[cfg(feature = "experimental-encrypted-state-events")]
451            encrypt_state_events: false,
452            only_allow_trusted_devices: false,
453            session_rotation_period: None,
454            session_rotation_period_messages: None,
455        }
456    }
457}
458
459/// Information on a room key that has been received or imported.
460#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
461pub struct RoomKeyInfo {
462    /// The [messaging algorithm] that this key is used for. Will be one of the
463    /// `m.megolm.*` algorithms.
464    ///
465    /// [messaging algorithm]: https://spec.matrix.org/v1.6/client-server-api/#messaging-algorithms
466    pub algorithm: EventEncryptionAlgorithm,
467
468    /// The room where the key is used.
469    pub room_id: OwnedRoomId,
470
471    /// The Curve25519 key of the device which initiated the session originally.
472    pub sender_key: Curve25519PublicKey,
473
474    /// The ID of the session that the key is for.
475    pub session_id: String,
476}
477
478impl From<&InboundGroupSession> for RoomKeyInfo {
479    fn from(group_session: &InboundGroupSession) -> Self {
480        RoomKeyInfo {
481            algorithm: group_session.algorithm().clone(),
482            room_id: group_session.room_id().to_owned(),
483            sender_key: group_session.sender_key(),
484            session_id: group_session.session_id().to_owned(),
485        }
486    }
487}
488
489/// Information on a room key that has been withheld
490#[derive(Clone, Debug, Deserialize, Serialize)]
491pub struct RoomKeyWithheldInfo {
492    /// The room where the key is used.
493    pub room_id: OwnedRoomId,
494
495    /// The ID of the session that the key is for.
496    pub session_id: String,
497
498    /// The withheld entry from a `m.room_key.withheld` event or [MSC4268] room
499    /// key bundle.
500    ///
501    /// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
502    pub withheld_event: RoomKeyWithheldEntry,
503}
504
505/// Represents an entry for a withheld room key event, which can be either a
506/// to-device event or a bundle entry.
507#[derive(Clone, Debug, Serialize, Deserialize)]
508pub struct RoomKeyWithheldEntry {
509    /// The user ID responsible for this entry, either from a
510    /// `m.room_key.withheld` to-device event or an [MSC4268] room key bundle.
511    ///
512    /// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
513    pub sender: OwnedUserId,
514    /// The content of the entry, which provides details about the reason the
515    /// key was withheld.
516    pub content: RoomKeyWithheldContent,
517}
518
519impl From<RoomKeyWithheldEvent> for RoomKeyWithheldEntry {
520    fn from(value: RoomKeyWithheldEvent) -> Self {
521        Self { sender: value.sender, content: value.content }
522    }
523}
524
525/// Information about a received historic room key bundle.
526///
527/// This struct contains information needed to uniquely identify a room key
528/// bundle. Only a single bundle per sender for a given room is persisted at a
529/// time.
530///
531/// It is used to notify listeners about received room key bundles.
532#[derive(Debug, Clone)]
533pub struct RoomKeyBundleInfo {
534    /// The user ID of the person that sent us the historic room key bundle.
535    pub sender: OwnedUserId,
536
537    /// The [`Curve25519PublicKey`] of the device that sent us this data.
538    pub sender_key: Curve25519PublicKey,
539
540    /// The ID of the room the bundle should be used in.
541    pub room_id: OwnedRoomId,
542}
543
544impl From<&StoredRoomKeyBundleData> for RoomKeyBundleInfo {
545    fn from(value: &StoredRoomKeyBundleData) -> Self {
546        let StoredRoomKeyBundleData { sender_user, sender_data: _, bundle_data, sender_key } =
547            value;
548        let sender_key = *sender_key;
549
550        Self { sender: sender_user.clone(), room_id: bundle_data.room_id.clone(), sender_key }
551    }
552}
553
554/// A struct remembering details of a room that we recently joined following an
555/// invite (and so should accept a room key bundle if we receive one).
556#[derive(Debug, Clone, Serialize, Deserialize)]
557pub struct RoomPendingKeyBundleDetails {
558    /// The ID of the room associated with this entry, used to track and
559    /// enumerate all such rooms during the startup process. This enables the
560    /// client to resume importing after a crash.
561    pub room_id: OwnedRoomId,
562
563    /// A timestamp remembering when we observed the user accepting an invite
564    /// using this client.
565    pub invite_accepted_at: MilliSecondsSinceUnixEpoch,
566
567    /// The user ID of the person that invited us.
568    pub inviter: OwnedUserId,
569}