Skip to main content

matrix_sdk_crypto/types/events/
utd_cause.rs

1// Copyright 2024 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
15use matrix_sdk_common::deserialized_responses::{
16    UnableToDecryptInfo, UnableToDecryptReason, VerificationLevel, WithheldCode,
17};
18use ruma::{MilliSecondsSinceUnixEpoch, events::AnySyncTimelineEvent, serde::Raw};
19use serde::Deserialize;
20
21/// Our best guess at the reason why an event can't be decrypted.
22#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)]
23#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
24pub enum UtdCause {
25    /// We don't have an explanation for why this UTD happened - it is probably
26    /// a bug, or a network split between the two homeservers.
27    ///
28    /// For example:
29    ///
30    /// - the keys for this event are missing, but a key storage backup exists
31    ///   and is working, so we should be able to find the keys in the backup.
32    ///
33    /// - the keys for this event are missing, and a key storage backup exists
34    ///   on the server, but that backup is not working on this client even
35    ///   though this device is verified.
36    #[default]
37    Unknown = 0,
38
39    /// We are missing the keys for this event, and the event was sent when we
40    /// were not a member of the room (or invited).
41    SentBeforeWeJoined = 1,
42
43    /// The message was sent by a user identity we have not verified, but the
44    /// user was previously verified.
45    VerificationViolation = 2,
46
47    /// The [`crate::TrustRequirement`] requires that the sending device be
48    /// signed by its owner, and it was not.
49    UnsignedDevice = 3,
50
51    /// The [`crate::TrustRequirement`] requires that the sending device be
52    /// signed by its owner, and we were unable to securely find the device.
53    ///
54    /// This could be because the device has since been deleted, because we
55    /// haven't yet downloaded it from the server, or because the session data
56    /// was obtained from an insecure source (imported from a file, obtained
57    /// from a legacy (asymmetric) backup, unsafe key forward, etc.)
58    UnknownDevice = 4,
59
60    /// We are missing the keys for this event, but it is a "device-historical"
61    /// message and there is no key storage backup on the server, presumably
62    /// because the user has turned it off.
63    ///
64    /// Device-historical means that the message was sent before the current
65    /// device existed (but the current user was probably a member of the room
66    /// at the time the message was sent). Not to be confused with pre-join or
67    /// pre-invite messages (see [`UtdCause::SentBeforeWeJoined`] for that).
68    ///
69    /// Expected message to user: "History is not available on this device".
70    HistoricalMessageAndBackupIsDisabled = 5,
71
72    /// The keys for this event are intentionally withheld.
73    ///
74    /// The sender has refused to share the key because our device does not meet
75    /// the sender's security requirements.
76    WithheldForUnverifiedOrInsecureDevice = 6,
77
78    /// The keys for this event are missing, likely because the sender was
79    /// unable to share them (e.g., failure to establish an Olm 1:1 channel).
80    /// Alternatively, the sender may have deliberately excluded this device by
81    /// cherry-picking and blocking it, in which case, no action can be taken on
82    /// our side.
83    WithheldBySender = 7,
84
85    /// We are missing the keys for this event, but it is a "device-historical"
86    /// message, and even though a key storage backup does exist, we can't use
87    /// it because our device is unverified.
88    ///
89    /// Device-historical means that the message was sent before the current
90    /// device existed (but the current user was probably a member of the room
91    /// at the time the message was sent). Not to be confused with pre-join or
92    /// pre-invite messages (see [`UtdCause::SentBeforeWeJoined`] for that).
93    ///
94    /// Expected message to user: "You need to verify this device".
95    HistoricalMessageAndDeviceIsUnverified = 8,
96}
97
98/// MSC4115 membership info in the unsigned area.
99#[derive(Deserialize)]
100struct UnsignedWithMembership {
101    #[serde(alias = "io.element.msc4115.membership")]
102    membership: Membership,
103}
104
105/// MSC4115 contents of the membership property
106#[derive(Deserialize)]
107#[serde(rename_all = "lowercase")]
108enum Membership {
109    Leave,
110    Invite,
111    Join,
112}
113
114/// Contextual crypto information used by [`UtdCause::determine`] to properly
115/// identify an Unable-To-Decrypt cause in addition to the
116/// [`UnableToDecryptInfo`] and raw event info.
117#[derive(Debug, Clone, Copy)]
118pub struct CryptoContextInfo {
119    /// The current device creation timestamp, used as a heuristic to determine
120    /// if an event is device-historical or not (sent before the current device
121    /// existed).
122    pub device_creation_ts: MilliSecondsSinceUnixEpoch,
123
124    /// True if this device is secure because it has been verified by us
125    pub this_device_is_verified: bool,
126
127    /// True if key storage exists on the server, even if we are unable to use
128    /// it
129    pub backup_exists_on_server: bool,
130
131    /// True if key storage is correctly set up and can be used by the current
132    /// client to download and decrypt message keys.
133    pub is_backup_configured: bool,
134}
135
136impl UtdCause {
137    /// Decide the cause of this UTD, based on the evidence we have.
138    pub fn determine(
139        raw_event: &Raw<AnySyncTimelineEvent>,
140        crypto_context_info: CryptoContextInfo,
141        unable_to_decrypt_info: &UnableToDecryptInfo,
142    ) -> Self {
143        use UnableToDecryptReason::*;
144        match &unable_to_decrypt_info.reason {
145            MissingMegolmSession { withheld_code: Some(WithheldCode::Unverified) } => {
146                UtdCause::WithheldForUnverifiedOrInsecureDevice
147            }
148
149            MissingMegolmSession { withheld_code: Some(WithheldCode::Blacklisted) }
150            | MissingMegolmSession { withheld_code: Some(WithheldCode::Unauthorised) }
151            | MissingMegolmSession { withheld_code: Some(WithheldCode::Unavailable) }
152            | MissingMegolmSession { withheld_code: Some(WithheldCode::NoOlm) }
153            | MissingMegolmSession { withheld_code: Some(WithheldCode::_Custom(_)) } => {
154                UtdCause::WithheldBySender
155            }
156
157            MissingMegolmSession { withheld_code: None }
158            | MissingMegolmSession { withheld_code: Some(WithheldCode::HistoryNotShared) }
159            | UnknownMegolmMessageIndex => {
160                // Look in the unsigned area for a `membership` field.
161                if let Some(unsigned) =
162                    raw_event.get_field::<UnsignedWithMembership>("unsigned").ok().flatten()
163                    && let Membership::Leave = unsigned.membership
164                {
165                    // We were not a member - this is the cause of the UTD
166                    return UtdCause::SentBeforeWeJoined;
167                }
168
169                if let Ok(timeline_event) = raw_event.deserialize()
170                    && timeline_event.origin_server_ts() < crypto_context_info.device_creation_ts
171                {
172                    // This event was sent before this device existed, so it is
173                    // "historical"
174                    return UtdCause::determine_historical(crypto_context_info);
175                }
176
177                UtdCause::Unknown
178            }
179
180            SenderIdentityNotTrusted(VerificationLevel::VerificationViolation) => {
181                UtdCause::VerificationViolation
182            }
183
184            SenderIdentityNotTrusted(VerificationLevel::UnsignedDevice) => UtdCause::UnsignedDevice,
185
186            SenderIdentityNotTrusted(VerificationLevel::None(_)) => UtdCause::UnknownDevice,
187
188            _ => UtdCause::Unknown,
189        }
190    }
191
192    /**
193     * Below is the flow chart we follow for deciding whether historical
194     * UTDs are expected. This function starts at position `B`.
195     *
196     * ```text
197     * A: Is the message newer than the device?
198     *   No -> B
199     *   Yes - Normal UTD error
200     *
201     * B: Is there a backup on the server?
202     *   No -> History is not available on this device
203     *   Yes -> C
204     *
205     * C: Is backup working on this device?
206     *   No -> D
207     *   Yes -> Normal UTD error
208     *
209     * D: Is this device verified?
210     *   No -> You need to verify this device
211     *   Yes -> Normal UTD error
212     * ```
213     */
214    fn determine_historical(crypto_context_info: CryptoContextInfo) -> UtdCause {
215        let backup_disabled = !crypto_context_info.backup_exists_on_server;
216        let backup_failing = !crypto_context_info.is_backup_configured;
217        let unverified = !crypto_context_info.this_device_is_verified;
218
219        if backup_disabled {
220            UtdCause::HistoricalMessageAndBackupIsDisabled
221        } else if backup_failing && unverified {
222            UtdCause::HistoricalMessageAndDeviceIsUnverified
223        } else {
224            // We didn't get the key from key storage backup, but we think we
225            // should have, because either:
226            //
227            // - backup is working (so why didn't we get it?), or
228            // - backup is not working for an unknown reason (because the device
229            //   is verified, and that is the only reason we check).
230            //
231            // In either case, we shrug and give an `Unknown` cause.
232            UtdCause::Unknown
233        }
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use matrix_sdk_common::deserialized_responses::{
240        DeviceLinkProblem, UnableToDecryptInfo, UnableToDecryptReason, VerificationLevel,
241        WithheldCode,
242    };
243    use ruma::{MilliSecondsSinceUnixEpoch, events::AnySyncTimelineEvent, serde::Raw};
244    use serde_json::{json, value::to_raw_value};
245
246    use crate::types::events::{UtdCause, utd_cause::CryptoContextInfo};
247
248    const EVENT_TIME: usize = 5555;
249    const BEFORE_EVENT_TIME: usize = 1111;
250    const AFTER_EVENT_TIME: usize = 9999;
251
252    #[test]
253    fn test_if_there_is_no_membership_info_we_guess_unknown() {
254        // If our JSON contains no membership info, then we guess the UTD is
255        // unknown.
256        assert_eq!(
257            UtdCause::determine(&raw_event(json!({})), device_old(), &missing_megolm_session()),
258            UtdCause::Unknown
259        );
260    }
261
262    #[test]
263    fn test_if_membership_info_cant_be_parsed_we_guess_unknown() {
264        // If our JSON contains a membership property but not the JSON we
265        // expected, then we guess the UTD is unknown.
266        assert_eq!(
267            UtdCause::determine(
268                &raw_event(json!({ "unsigned": { "membership": 3 } })),
269                device_old(),
270                &missing_megolm_session()
271            ),
272            UtdCause::Unknown
273        );
274    }
275
276    #[test]
277    fn test_if_membership_is_invite_we_guess_unknown() {
278        // If membership=invite then we expected to be sent the keys so the
279        // cause of the UTD is unknown.
280        assert_eq!(
281            UtdCause::determine(
282                &raw_event(json!({ "unsigned": { "membership": "invite" } }),),
283                device_old(),
284                &missing_megolm_session()
285            ),
286            UtdCause::Unknown
287        );
288    }
289
290    #[test]
291    fn test_if_membership_is_join_we_guess_unknown() {
292        // If membership=join then we expected to be sent the keys so the cause
293        // of the UTD is unknown.
294        assert_eq!(
295            UtdCause::determine(
296                &raw_event(json!({ "unsigned": { "membership": "join" } })),
297                device_old(),
298                &missing_megolm_session()
299            ),
300            UtdCause::Unknown
301        );
302    }
303
304    #[test]
305    fn test_if_membership_is_leave_we_guess_membership() {
306        // If membership=leave then we have an explanation for why we can't
307        // decrypt, until we have MSC3061.
308        assert_eq!(
309            UtdCause::determine(
310                &raw_event(json!({ "unsigned": { "membership": "leave" } })),
311                device_old(),
312                &missing_megolm_session()
313            ),
314            UtdCause::SentBeforeWeJoined
315        );
316    }
317
318    #[test]
319    fn test_if_membership_is_leave_and_session_not_shared_we_guess_membership() {
320        // Even under MSC4268 (history sharing), the session may have been
321        // withheld. We use the same UTD cause.
322        assert_eq!(
323            UtdCause::determine(
324                &raw_event(json!({ "unsigned": { "membership": "leave" } })),
325                device_old(),
326                &UnableToDecryptInfo {
327                    session_id: None,
328                    reason: UnableToDecryptReason::MissingMegolmSession {
329                        withheld_code: Some(WithheldCode::HistoryNotShared)
330                    },
331                }
332            ),
333            UtdCause::SentBeforeWeJoined
334        );
335    }
336
337    #[test]
338    fn test_if_reason_is_not_missing_key_we_guess_unknown_even_if_membership_is_leave() {
339        // If the UnableToDecryptReason is other than MissingMegolmSession or
340        // UnknownMegolmMessageIndex, we do not know the reason for the failure
341        // even if membership=leave.
342        assert_eq!(
343            UtdCause::determine(
344                &raw_event(json!({ "unsigned": { "membership": "leave" } })),
345                device_old(),
346                &malformed_encrypted_event()
347            ),
348            UtdCause::Unknown
349        );
350    }
351
352    #[test]
353    fn test_if_unstable_prefix_membership_is_leave_we_guess_membership() {
354        // Before MSC4115 is merged, we support the unstable prefix too.
355        assert_eq!(
356            UtdCause::determine(
357                &raw_event(json!({ "unsigned": { "io.element.msc4115.membership": "leave" } })),
358                device_old(),
359                &missing_megolm_session()
360            ),
361            UtdCause::SentBeforeWeJoined
362        );
363    }
364
365    #[test]
366    fn test_verification_violation_is_passed_through() {
367        assert_eq!(
368            UtdCause::determine(&raw_event(json!({})), device_old(), &verification_violation()),
369            UtdCause::VerificationViolation
370        );
371    }
372
373    #[test]
374    fn test_unsigned_device_is_passed_through() {
375        assert_eq!(
376            UtdCause::determine(&raw_event(json!({})), device_old(), &unsigned_device()),
377            UtdCause::UnsignedDevice
378        );
379    }
380
381    #[test]
382    fn test_unknown_device_is_passed_through() {
383        assert_eq!(
384            UtdCause::determine(&raw_event(json!({})), device_old(), &missing_device()),
385            UtdCause::UnknownDevice
386        );
387    }
388
389    #[test]
390    fn test_old_devices_dont_cause_historical_utds() {
391        // Message key is missing.
392        let info = missing_megolm_session();
393
394        // The device is old.
395        let context = device_old();
396
397        // So we have no explanation for this UTD.
398        assert_eq!(UtdCause::determine(&utd_event(), context, &info), UtdCause::Unknown);
399
400        // Same for unknown megolm message index
401        let info = unknown_megolm_message_index();
402        assert_eq!(UtdCause::determine(&utd_event(), context, &info), UtdCause::Unknown);
403    }
404
405    #[test]
406    fn test_if_backup_is_disabled_historical_utd_is_expected() {
407        // Message key is missing.
408        let info = missing_megolm_session();
409
410        // The device is new.
411        let mut context = device_new();
412
413        // There is no key storage backup on the server.
414        context.backup_exists_on_server = false;
415
416        // So this UTD is expected, and the solution (for future messages!) is
417        // to turn on key storage backups.
418        assert_eq!(
419            UtdCause::determine(&utd_event(), context, &info),
420            UtdCause::HistoricalMessageAndBackupIsDisabled
421        );
422
423        // Same for unknown megolm message index
424        let info = unknown_megolm_message_index();
425        assert_eq!(
426            UtdCause::determine(&utd_event(), context, &info),
427            UtdCause::HistoricalMessageAndBackupIsDisabled
428        );
429    }
430
431    #[test]
432    fn test_malformed_events_are_never_expected_utds() {
433        // The event was malformed.
434        let info = malformed_encrypted_event();
435
436        // The device is new.
437        let mut context = device_new();
438
439        // There is no key storage backup on the server.
440        context.backup_exists_on_server = false;
441
442        // So this could be expected historical like the previous test, but
443        // because the encrypted event is malformed, that takes precedence, and
444        // it's unexpected.
445        assert_eq!(UtdCause::determine(&utd_event(), context, &info), UtdCause::Unknown);
446
447        // Same for decryption failures
448        let info = megolm_decryption_failure();
449        assert_eq!(UtdCause::determine(&utd_event(), context, &info), UtdCause::Unknown);
450    }
451
452    #[test]
453    fn test_new_devices_with_nonworking_backups_because_unverified_cause_expected_utds() {
454        // Message key is missing.
455        let info = missing_megolm_session();
456
457        // The device is new.
458        let mut context = device_new();
459
460        // There is a key storage backup on the server.
461        context.backup_exists_on_server = true;
462
463        // The key storage backup is not working,
464        context.is_backup_configured = false;
465
466        // probably because... Our device is not verified.
467        context.this_device_is_verified = false;
468
469        // So this UTD is expected, and the solution is (hopefully) to verify.
470        assert_eq!(
471            UtdCause::determine(&utd_event(), context, &info),
472            UtdCause::HistoricalMessageAndDeviceIsUnverified
473        );
474
475        // Same for unknown megolm message index
476        let info = unknown_megolm_message_index();
477        assert_eq!(
478            UtdCause::determine(&utd_event(), context, &info),
479            UtdCause::HistoricalMessageAndDeviceIsUnverified
480        );
481    }
482
483    #[test]
484    fn test_if_backup_is_working_then_historical_utd_is_unexpected() {
485        // Message key is missing.
486        let info = missing_megolm_session();
487
488        // The device is new.
489        let mut context = device_new();
490
491        // There is a key storage backup on the server.
492        context.backup_exists_on_server = true;
493
494        // The key storage backup is working.
495        context.is_backup_configured = true;
496
497        // So this UTD is unexpected since we should be able to fetch the key
498        // from storage.
499        assert_eq!(UtdCause::determine(&utd_event(), context, &info), UtdCause::Unknown);
500
501        // Same for unknown megolm message index
502        let info = unknown_megolm_message_index();
503        assert_eq!(UtdCause::determine(&utd_event(), context, &info), UtdCause::Unknown);
504    }
505
506    #[test]
507    fn test_if_backup_is_not_working_even_though_verified_then_historical_utd_is_unexpected() {
508        // Message key is missing.
509        let info = missing_megolm_session();
510
511        // The device is new.
512        let mut context = device_new();
513
514        // There is a key storage backup on the server.
515        context.backup_exists_on_server = true;
516
517        // The key storage backup is working.
518        context.is_backup_configured = false;
519
520        // even though... Our device is verified.
521        context.this_device_is_verified = true;
522
523        // So this UTD is unexpected since we can't explain why our backup is
524        // not working.
525        //
526        // TODO: it might be nice to tell the user that our backup is not
527        // working! Currently we don't distinguish between Unknown cases, since
528        // we want to make sure they are all reported as unexpected UTDs.
529        assert_eq!(UtdCause::determine(&utd_event(), context, &info), UtdCause::Unknown);
530
531        // Same for unknown megolm message index
532        let info = unknown_megolm_message_index();
533        assert_eq!(UtdCause::determine(&utd_event(), context, &info), UtdCause::Unknown);
534    }
535
536    fn utd_event() -> Raw<AnySyncTimelineEvent> {
537        raw_event(json!({
538            "type": "m.room.encrypted",
539            "event_id": "$0",
540            // the values don't matter much but the expected fields should be there.
541            "content": {
542                "algorithm": "m.megolm.v1.aes-sha2",
543                "ciphertext": "FOO",
544                "sender_key": "SENDERKEYSENDERKEY",
545                "device_id": "ABCDEFGH",
546                "session_id": "A0",
547            },
548            "sender": "@bob:localhost",
549            "origin_server_ts": EVENT_TIME,
550            "unsigned": { "membership": "join" }
551        }))
552    }
553
554    fn raw_event(value: serde_json::Value) -> Raw<AnySyncTimelineEvent> {
555        Raw::from_json(to_raw_value(&value).unwrap())
556    }
557
558    fn device_old() -> CryptoContextInfo {
559        CryptoContextInfo {
560            device_creation_ts: MilliSecondsSinceUnixEpoch((BEFORE_EVENT_TIME).try_into().unwrap()),
561            this_device_is_verified: false,
562            is_backup_configured: false,
563            backup_exists_on_server: false,
564        }
565    }
566
567    fn device_new() -> CryptoContextInfo {
568        CryptoContextInfo {
569            device_creation_ts: MilliSecondsSinceUnixEpoch((AFTER_EVENT_TIME).try_into().unwrap()),
570            this_device_is_verified: false,
571            is_backup_configured: false,
572            backup_exists_on_server: false,
573        }
574    }
575
576    fn missing_megolm_session() -> UnableToDecryptInfo {
577        UnableToDecryptInfo {
578            session_id: None,
579            reason: UnableToDecryptReason::MissingMegolmSession { withheld_code: None },
580        }
581    }
582
583    fn malformed_encrypted_event() -> UnableToDecryptInfo {
584        UnableToDecryptInfo {
585            session_id: None,
586            reason: UnableToDecryptReason::MalformedEncryptedEvent,
587        }
588    }
589
590    fn unknown_megolm_message_index() -> UnableToDecryptInfo {
591        UnableToDecryptInfo {
592            session_id: None,
593            reason: UnableToDecryptReason::UnknownMegolmMessageIndex,
594        }
595    }
596
597    fn megolm_decryption_failure() -> UnableToDecryptInfo {
598        UnableToDecryptInfo {
599            session_id: None,
600            reason: UnableToDecryptReason::MegolmDecryptionFailure,
601        }
602    }
603
604    fn verification_violation() -> UnableToDecryptInfo {
605        UnableToDecryptInfo {
606            session_id: None,
607            reason: UnableToDecryptReason::SenderIdentityNotTrusted(
608                VerificationLevel::VerificationViolation,
609            ),
610        }
611    }
612
613    fn unsigned_device() -> UnableToDecryptInfo {
614        UnableToDecryptInfo {
615            session_id: None,
616            reason: UnableToDecryptReason::SenderIdentityNotTrusted(
617                VerificationLevel::UnsignedDevice,
618            ),
619        }
620    }
621
622    fn missing_device() -> UnableToDecryptInfo {
623        UnableToDecryptInfo {
624            session_id: None,
625            reason: UnableToDecryptReason::SenderIdentityNotTrusted(VerificationLevel::None(
626                DeviceLinkProblem::MissingDevice,
627            )),
628        }
629    }
630}