Skip to main content

matrix_sdk_crypto/session_manager/group_sessions/
share_strategy.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 std::{
16    collections::{BTreeMap, BTreeSet, HashMap},
17    default::Default,
18};
19
20use itertools::{Either, Itertools};
21use matrix_sdk_common::deserialized_responses::WithheldCode;
22use ruma::{DeviceId, OwnedDeviceId, OwnedUserId, UserId};
23use serde::{Deserialize, Serialize};
24use tracing::{debug, instrument, trace};
25
26use super::OutboundGroupSession;
27#[cfg(feature = "experimental-x509-identity-verification")]
28use crate::x509::X509Verifier;
29#[cfg(doc)]
30use crate::{Device, UserIdentity};
31use crate::{
32    DeviceData, EncryptionSettings, LocalTrust, OlmError, OwnUserIdentityData, UserIdentityData,
33    error::{OlmResult, SessionRecipientCollectionError},
34    olm::ShareInfo,
35    store::Store,
36};
37
38/// Strategy to collect the devices that should receive room keys for the
39/// current discussion.
40#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
41#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
42#[serde(from = "CollectStrategyDeserializationHelper")]
43pub enum CollectStrategy {
44    /// Share with all (unblacklisted) devices.
45    ///
46    /// Not recommended, per the guidance of [MSC4153].
47    ///
48    /// (Used by Element X and Element Web in the legacy, non-"exclude insecure
49    /// devices" mode.)
50    ///
51    /// [MSC4153]: https://github.com/matrix-org/matrix-doc/pull/4153
52    #[default]
53    AllDevices,
54
55    /// Share with all devices, except errors for _verified_ users cause sharing
56    /// to fail with an error.
57    ///
58    /// In this strategy, if a verified user has an unsigned device, key sharing
59    /// will fail with a
60    /// [`SessionRecipientCollectionError::VerifiedUserHasUnsignedDevice`]. If a
61    /// verified user has replaced their identity, key sharing will fail with a
62    /// [`SessionRecipientCollectionError::VerifiedUserChangedIdentity`].
63    ///
64    /// Otherwise, keys are shared with unsigned devices as normal.
65    ///
66    /// Once the problematic devices are blacklisted or whitelisted the caller
67    /// can retry to share a second time.
68    ///
69    /// Not recommended, per the guidance of [MSC4153].
70    ///
71    /// [MSC4153]: https://github.com/matrix-org/matrix-doc/pull/4153
72    ErrorOnVerifiedUserProblem,
73
74    /// Share based on identity. Only distribute to devices signed by their
75    /// owner. If a user has no published identity he will not receive any room
76    /// keys.
77    ///
78    /// This is the recommended strategy: it is compliant with the guidance of
79    /// [MSC4153].
80    ///
81    /// (Used by Element Web and Element X in the "exclude insecure devices"
82    /// mode.)
83    ///
84    /// [MSC4153]: https://github.com/matrix-org/matrix-doc/pull/4153
85    IdentityBasedStrategy,
86
87    /// Only share keys with devices that we "trust". A device is trusted if any
88    /// of the following is true:
89    ///     - It was manually marked as trusted.
90    ///     - It was marked as verified via interactive verification.
91    ///     - It is signed by its owner identity, and this identity has been
92    ///       trusted via interactive verification.
93    ///     - It is the current own device of the user.
94    ///
95    /// This strategy is compliant with [MSC4153], but is probably too strict
96    /// for normal use.
97    ///
98    /// (Used by Element Web when "only send messages to verified users" is
99    /// enabled.)
100    ///
101    /// [MSC4153]: https://github.com/matrix-org/matrix-doc/pull/4153
102    OnlyTrustedDevices,
103}
104
105impl CollectStrategy {
106    /// Creates an identity based strategy
107    pub const fn new_identity_based() -> Self {
108        CollectStrategy::IdentityBasedStrategy
109    }
110}
111
112/// Deserialization helper for [`CollectStrategy`].
113#[derive(Deserialize)]
114enum CollectStrategyDeserializationHelper {
115    /// `AllDevices`, `ErrorOnVerifiedUserProblem` and `OnlyTrustedDevices` used
116    /// to be implemented as a single strategy with flags.
117    DeviceBasedStrategy {
118        #[serde(default)]
119        error_on_verified_user_problem: bool,
120
121        #[serde(default)]
122        only_allow_trusted_devices: bool,
123    },
124
125    AllDevices,
126    ErrorOnVerifiedUserProblem,
127    IdentityBasedStrategy,
128    OnlyTrustedDevices,
129}
130
131impl From<CollectStrategyDeserializationHelper> for CollectStrategy {
132    fn from(value: CollectStrategyDeserializationHelper) -> Self {
133        use CollectStrategyDeserializationHelper::*;
134
135        match value {
136            DeviceBasedStrategy {
137                only_allow_trusted_devices: true,
138                error_on_verified_user_problem: _,
139            } => CollectStrategy::OnlyTrustedDevices,
140            DeviceBasedStrategy {
141                only_allow_trusted_devices: false,
142                error_on_verified_user_problem: true,
143            } => CollectStrategy::ErrorOnVerifiedUserProblem,
144            DeviceBasedStrategy {
145                only_allow_trusted_devices: false,
146                error_on_verified_user_problem: false,
147            } => CollectStrategy::AllDevices,
148
149            AllDevices => CollectStrategy::AllDevices,
150            ErrorOnVerifiedUserProblem => CollectStrategy::ErrorOnVerifiedUserProblem,
151            IdentityBasedStrategy => CollectStrategy::IdentityBasedStrategy,
152            OnlyTrustedDevices => CollectStrategy::OnlyTrustedDevices,
153        }
154    }
155}
156
157/// Returned by `collect_session_recipients`.
158///
159/// Information indicating whether the session needs to be rotated
160/// (`should_rotate`) and the list of users/devices that should receive
161/// (`devices`) or not the session, including withheld reason
162/// `withheld_devices`.
163#[derive(Debug, Default)]
164pub(crate) struct CollectRecipientsResult {
165    /// If true the outbound group session should be rotated
166    pub should_rotate: bool,
167    /// The map of user|device that should receive the session
168    pub devices: BTreeMap<OwnedUserId, Vec<DeviceData>>,
169    /// The map of user|device that won't receive the key with the withheld
170    /// code.
171    pub withheld_devices: Vec<(DeviceData, WithheldCode)>,
172}
173
174/// Given a list of user and an outbound session, return the list of users and
175/// their devices that this session should be shared with.
176///
177/// Returns information indicating whether the session needs to be rotated and
178/// the list of users/devices that should receive or not the session (with
179/// withheld reason).
180#[instrument(skip_all)]
181pub(crate) async fn collect_session_recipients(
182    store: &Store,
183    users: impl Iterator<Item = &UserId>,
184    settings: &EncryptionSettings,
185    outbound: &OutboundGroupSession,
186) -> OlmResult<CollectRecipientsResult> {
187    let mut result = collect_recipients_for_share_strategy(
188        store,
189        users,
190        &settings.sharing_strategy,
191        Some(outbound),
192    )
193    .await?;
194
195    // To protect the room history we need to rotate the session if either:
196    //
197    // 1. Any user left the room.
198    // 2. Any of the users' devices got deleted or blacklisted.
199    // 3. The history visibility changed.
200    // 4. The encryption algorithm changed.
201    //
202    // `result.should_rotate` is true if the first or second in that list is
203    // true; we now need to check for the other two.
204    let device_removed = result.should_rotate;
205
206    let visibility_changed = outbound.settings().history_visibility != settings.history_visibility;
207    let algorithm_changed = outbound.settings().algorithm != settings.algorithm;
208
209    result.should_rotate = device_removed || visibility_changed || algorithm_changed;
210
211    if result.should_rotate {
212        debug!(
213            device_removed,
214            visibility_changed, algorithm_changed, "Rotating room key to protect room history",
215        );
216    }
217
218    Ok(result)
219}
220
221/// Given a list of users and a [`CollectStrategy`], return the list of devices
222/// that cryptographic keys should be shared with, or that withheld notices
223/// should be sent to.
224///
225/// If an existing [`OutboundGroupSession`] is provided, will also check the
226/// list of devices that the session has been _previously_ shared with, and if
227/// that list is too broad, returns a flag indicating that the session should be
228/// rotated (e.g., because a device has been deleted or a user has left the
229/// chat).
230pub(crate) async fn collect_recipients_for_share_strategy(
231    store: &Store,
232    users: impl Iterator<Item = &UserId>,
233    share_strategy: &CollectStrategy,
234    outbound: Option<&OutboundGroupSession>,
235) -> OlmResult<CollectRecipientsResult> {
236    let users: BTreeSet<&UserId> = users.collect();
237    trace!(?users, ?share_strategy, "Calculating group session recipients");
238
239    let mut result = CollectRecipientsResult::default();
240    let mut verified_users_with_new_identities: Vec<OwnedUserId> = Default::default();
241
242    // If we have an outbound session, check if a user is missing from the set
243    // of users that should get the session but is in the set of users that
244    // received the session.
245    if let Some(outbound) = outbound {
246        let view = outbound.sharing_view();
247        let users_shared_with = view.shared_with_users().collect::<BTreeSet<_>>();
248        let left_users = users_shared_with.difference(&users).collect::<BTreeSet<_>>();
249        if !left_users.is_empty() {
250            trace!(?left_users, "Some users have left the chat: session must be rotated");
251            result.should_rotate = true;
252        }
253    }
254
255    let own_identity = store.get_user_identity(store.user_id()).await?.and_then(|i| i.into_own());
256
257    // Get the recipient and withheld devices, based on the collection strategy.
258    match share_strategy {
259        CollectStrategy::AllDevices => {
260            for user_id in users {
261                trace!(?user_id, "CollectStrategy::AllDevices: Considering recipient devices",);
262                let user_devices = store.get_device_data_for_user_filtered(user_id).await?;
263                let device_owner_identity = store.get_user_identity(user_id).await?;
264
265                let recipient_devices = split_devices_for_user_for_all_devices_strategy(
266                    user_devices,
267                    &own_identity,
268                    &device_owner_identity,
269                    #[cfg(feature = "experimental-x509-identity-verification")]
270                    store.x509_verifier(),
271                );
272                update_recipients_for_user(&mut result, outbound, user_id, recipient_devices);
273            }
274        }
275        CollectStrategy::ErrorOnVerifiedUserProblem => {
276            let mut unsigned_devices_of_verified_users: BTreeMap<OwnedUserId, Vec<OwnedDeviceId>> =
277                Default::default();
278
279            for user_id in users {
280                trace!(
281                    ?user_id,
282                    "CollectStrategy::ErrorOnVerifiedUserProblem: Considering recipient devices"
283                );
284                let user_devices = store.get_device_data_for_user_filtered(user_id).await?;
285
286                let device_owner_identity = store.get_user_identity(user_id).await?;
287
288                if has_identity_verification_violation(
289                    own_identity.as_ref(),
290                    device_owner_identity.as_ref(),
291                    #[cfg(feature = "experimental-x509-identity-verification")]
292                    store.x509_verifier(),
293                ) {
294                    verified_users_with_new_identities.push(user_id.to_owned());
295                    // No point considering the individual devices of this user.
296                    continue;
297                }
298
299                let recipient_devices =
300                    split_devices_for_user_for_error_on_verified_user_problem_strategy(
301                        user_devices,
302                        &own_identity,
303                        &device_owner_identity,
304                        #[cfg(feature = "experimental-x509-identity-verification")]
305                        store.x509_verifier(),
306                    );
307
308                match recipient_devices {
309                    ErrorOnVerifiedUserProblemResult::UnsignedDevicesOfVerifiedUser(devices) => {
310                        unsigned_devices_of_verified_users.insert(user_id.to_owned(), devices);
311                    }
312                    ErrorOnVerifiedUserProblemResult::Devices(recipient_devices) => {
313                        update_recipients_for_user(
314                            &mut result,
315                            outbound,
316                            user_id,
317                            recipient_devices,
318                        );
319                    }
320                }
321            }
322
323            // If `error_on_verified_user_problem` is set, then
324            // `unsigned_devices_of_verified_users` may be populated. If so, we
325            // need to bail out with an error.
326            if !unsigned_devices_of_verified_users.is_empty() {
327                return Err(OlmError::SessionRecipientCollectionError(
328                    SessionRecipientCollectionError::VerifiedUserHasUnsignedDevice(
329                        unsigned_devices_of_verified_users,
330                    ),
331                ));
332            }
333        }
334        CollectStrategy::IdentityBasedStrategy => {
335            // We require our own cross-signing to be properly set up for the
336            // identity-based strategy, so return an error if it isn't.
337            match &own_identity {
338                None => {
339                    return Err(OlmError::SessionRecipientCollectionError(
340                        SessionRecipientCollectionError::CrossSigningNotSetup,
341                    ));
342                }
343                Some(identity) if !identity.is_verified() => {
344                    return Err(OlmError::SessionRecipientCollectionError(
345                        SessionRecipientCollectionError::SendingFromUnverifiedDevice,
346                    ));
347                }
348                Some(_) => (),
349            }
350
351            for user_id in users {
352                trace!(
353                    ?user_id,
354                    "CollectStrategy::IdentityBasedStrategy: Considering recipient devices"
355                );
356                let user_devices = store.get_device_data_for_user_filtered(user_id).await?;
357
358                let device_owner_identity = store.get_user_identity(user_id).await?;
359
360                if has_identity_verification_violation(
361                    own_identity.as_ref(),
362                    device_owner_identity.as_ref(),
363                    #[cfg(feature = "experimental-x509-identity-verification")]
364                    store.x509_verifier(),
365                ) {
366                    verified_users_with_new_identities.push(user_id.to_owned());
367                    // No point considering the individual devices of this user.
368                    continue;
369                }
370
371                let recipient_devices = split_devices_for_user_for_identity_based_strategy(
372                    user_devices,
373                    &device_owner_identity,
374                );
375
376                update_recipients_for_user(&mut result, outbound, user_id, recipient_devices);
377            }
378        }
379
380        CollectStrategy::OnlyTrustedDevices => {
381            for user_id in users {
382                trace!(
383                    ?user_id,
384                    "CollectStrategy::OnlyTrustedDevices: Considering recipient devices"
385                );
386                let user_devices = store.get_device_data_for_user_filtered(user_id).await?;
387                let device_owner_identity = store.get_user_identity(user_id).await?;
388
389                let recipient_devices = split_devices_for_user_for_only_trusted_devices(
390                    user_devices,
391                    &own_identity,
392                    &device_owner_identity,
393                    #[cfg(feature = "experimental-x509-identity-verification")]
394                    store.x509_verifier(),
395                );
396
397                update_recipients_for_user(&mut result, outbound, user_id, recipient_devices);
398            }
399        }
400    }
401
402    // We may have encountered previously-verified users who have changed their
403    // identities. If so, we bail out with an error.
404    if !verified_users_with_new_identities.is_empty() {
405        return Err(OlmError::SessionRecipientCollectionError(
406            SessionRecipientCollectionError::VerifiedUserChangedIdentity(
407                verified_users_with_new_identities,
408            ),
409        ));
410    }
411
412    trace!(result.should_rotate, "Done calculating group session recipients");
413
414    Ok(result)
415}
416
417/// Update this [`CollectRecipientsResult`] with the device list for a specific
418/// user.
419fn update_recipients_for_user(
420    recipients: &mut CollectRecipientsResult,
421    outbound: Option<&OutboundGroupSession>,
422    user_id: &UserId,
423    recipient_devices: RecipientDevicesForUser,
424) {
425    // If we haven't already concluded that the session should be rotated for
426    // other reasons, we also need to check whether any of the devices in the
427    // session got deleted or blacklisted in the meantime. If so, we should also
428    // rotate the session.
429    if let Some(outbound) = outbound
430        && !recipients.should_rotate
431    {
432        recipients.should_rotate =
433            is_session_overshared_for_user(outbound, user_id, &recipient_devices.allowed_devices)
434    }
435
436    recipients
437        .devices
438        .entry(user_id.to_owned())
439        .or_default()
440        .extend(recipient_devices.allowed_devices);
441    recipients.withheld_devices.extend(recipient_devices.denied_devices_with_code);
442}
443
444/// Check if the session has been shared with a device belonging to the given
445/// user, that is no longer in the pool of devices that should participate in
446/// the discussion.
447///
448/// # Arguments
449///
450/// - `outbound_session` - the outbound group session to check for oversharing.
451/// - `user_id` - the ID of the user we are checking the devices for.
452/// - `recipient_devices` - the list of devices belonging to `user_id` that we
453///   expect to share the session with.
454///
455/// # Returns
456///
457/// `true` if the session has been shared with any devices belonging to
458/// `user_id` that are not in `recipient_devices`. Otherwise, `false`.
459fn is_session_overshared_for_user(
460    outbound_session: &OutboundGroupSession,
461    user_id: &UserId,
462    recipient_devices: &[DeviceData],
463) -> bool {
464    // Device IDs that should receive this session
465    let recipient_device_ids: BTreeSet<&DeviceId> =
466        recipient_devices.iter().map(|d| d.device_id()).collect();
467
468    let view = outbound_session.sharing_view();
469    let newly_deleted_or_blacklisted: BTreeSet<&DeviceId> = view
470        .iter_shares(Some(user_id), None)
471        .filter_map(|(_user_id, device_id, info)| {
472            // If a devices who we've shared the session with before is not in
473            // the list of devices that should receive the session, we need to
474            // rotate. We also collect all of those device IDs to log them out.
475            if matches!(info, ShareInfo::Shared(_)) && !recipient_device_ids.contains(device_id) {
476                Some(device_id)
477            } else {
478                None
479            }
480        })
481        .collect();
482
483    let should_rotate = !newly_deleted_or_blacklisted.is_empty();
484    if should_rotate {
485        debug!(
486            "Rotating a room key due to these devices being deleted/blacklisted {:?}",
487            newly_deleted_or_blacklisted,
488        );
489    }
490    should_rotate
491}
492
493#[cfg(feature = "experimental-send-custom-to-device")]
494/// Partition the devices based on the given collect strategy
495pub(crate) async fn split_devices_for_share_strategy(
496    store: &Store,
497    devices: Vec<DeviceData>,
498    share_strategy: CollectStrategy,
499) -> OlmResult<(Vec<DeviceData>, Vec<(DeviceData, WithheldCode)>)> {
500    let own_identity = store.get_user_identity(store.user_id()).await?.and_then(|i| i.into_own());
501
502    let mut verified_users_with_new_identities: BTreeSet<OwnedUserId> = Default::default();
503
504    let mut allowed_devices: Vec<DeviceData> = Default::default();
505    let mut blocked_devices: Vec<(DeviceData, WithheldCode)> = Default::default();
506
507    let mut user_identities_cache: BTreeMap<OwnedUserId, Option<UserIdentityData>> =
508        Default::default();
509    let mut get_user_identity = async move |user_id| -> OlmResult<_> {
510        match user_identities_cache.get(user_id) {
511            Some(user_identity) => Ok(user_identity.clone()),
512            None => {
513                let user_identity = store.get_user_identity(user_id).await?;
514                user_identities_cache.insert(user_id.to_owned(), user_identity.clone());
515                Ok(user_identity)
516            }
517        }
518    };
519
520    match share_strategy {
521        CollectStrategy::AllDevices => {
522            for device in devices.iter() {
523                let user_id = device.user_id();
524                let device_owner_identity = get_user_identity(user_id).await?;
525
526                if let Some(withheld_code) = withheld_code_for_device_for_all_devices_strategy(
527                    device,
528                    &own_identity,
529                    &device_owner_identity,
530                    #[cfg(feature = "experimental-x509-identity-verification")]
531                    store.x509_verifier(),
532                ) {
533                    blocked_devices.push((device.clone(), withheld_code));
534                } else {
535                    allowed_devices.push(device.clone());
536                }
537            }
538        }
539
540        CollectStrategy::ErrorOnVerifiedUserProblem => {
541            // We throw an error if any user has a verification violation. So we
542            // loop through all the devices given, and check if the associated
543            // user has a verification violation. If so, we add the device to
544            // `unsigned_devices_of_verified_users`, which will be returned with
545            // the error.
546            let mut unsigned_devices_of_verified_users: BTreeMap<OwnedUserId, Vec<OwnedDeviceId>> =
547                Default::default();
548            let mut add_device_to_unsigned_devices_map = |user_id: &UserId, device: &DeviceData| {
549                let device_id = device.device_id().to_owned();
550                if let Some(devices) = unsigned_devices_of_verified_users.get_mut(user_id) {
551                    devices.push(device_id);
552                } else {
553                    unsigned_devices_of_verified_users.insert(user_id.to_owned(), vec![device_id]);
554                }
555            };
556
557            for device in devices.iter() {
558                let user_id = device.user_id();
559                let device_owner_identity = get_user_identity(user_id).await?;
560
561                if has_identity_verification_violation(
562                    own_identity.as_ref(),
563                    device_owner_identity.as_ref(),
564                    #[cfg(feature = "experimental-x509-identity-verification")]
565                    store.x509_verifier(),
566                ) {
567                    verified_users_with_new_identities.insert(user_id.to_owned());
568                } else {
569                    match handle_device_for_user_for_error_on_verified_user_problem_strategy(
570                        device,
571                        own_identity.as_ref(),
572                        device_owner_identity.as_ref(),
573                        #[cfg(feature = "experimental-x509-identity-verification")]
574                        store.x509_verifier(),
575                    ) {
576                        ErrorOnVerifiedUserProblemDeviceDecision::Ok => {
577                            allowed_devices.push(device.clone())
578                        }
579                        ErrorOnVerifiedUserProblemDeviceDecision::Withhold(code) => {
580                            blocked_devices.push((device.clone(), code))
581                        }
582                        ErrorOnVerifiedUserProblemDeviceDecision::UnsignedOfVerified => {
583                            add_device_to_unsigned_devices_map(user_id, device);
584                        }
585                    }
586                }
587            }
588
589            if !unsigned_devices_of_verified_users.is_empty() {
590                return Err(OlmError::SessionRecipientCollectionError(
591                    SessionRecipientCollectionError::VerifiedUserHasUnsignedDevice(
592                        unsigned_devices_of_verified_users,
593                    ),
594                ));
595            }
596        }
597
598        CollectStrategy::IdentityBasedStrategy => {
599            // We require our own cross-signing to be properly set up for the
600            // identity-based strategy, so return an error if it isn't.
601            match &own_identity {
602                None => {
603                    return Err(OlmError::SessionRecipientCollectionError(
604                        SessionRecipientCollectionError::CrossSigningNotSetup,
605                    ));
606                }
607                Some(identity) if !identity.is_verified() => {
608                    return Err(OlmError::SessionRecipientCollectionError(
609                        SessionRecipientCollectionError::SendingFromUnverifiedDevice,
610                    ));
611                }
612                Some(_) => (),
613            }
614
615            for device in devices.iter() {
616                let user_id = device.user_id();
617                let device_owner_identity = get_user_identity(user_id).await?;
618
619                if has_identity_verification_violation(
620                    own_identity.as_ref(),
621                    device_owner_identity.as_ref(),
622                    #[cfg(feature = "experimental-x509-identity-verification")]
623                    store.x509_verifier(),
624                ) {
625                    verified_users_with_new_identities.insert(user_id.to_owned());
626                } else if let Some(device_owner_identity) = device_owner_identity {
627                    if let Some(withheld_code) =
628                        withheld_code_for_device_with_owner_for_identity_based_strategy(
629                            device,
630                            &device_owner_identity,
631                        )
632                    {
633                        blocked_devices.push((device.clone(), withheld_code));
634                    } else {
635                        allowed_devices.push(device.clone());
636                    }
637                } else {
638                    // Device owner has no identity, so the device is considered
639                    // to be unverified
640                    blocked_devices.push((device.clone(), WithheldCode::Unverified));
641                }
642            }
643        }
644
645        CollectStrategy::OnlyTrustedDevices => {
646            for device in devices.iter() {
647                let user_id = device.user_id();
648                let device_owner_identity = get_user_identity(user_id).await?;
649
650                if let Some(withheld_code) =
651                    withheld_code_for_device_for_only_trusted_devices_strategy(
652                        device,
653                        &own_identity,
654                        &device_owner_identity,
655                        #[cfg(feature = "experimental-x509-identity-verification")]
656                        store.x509_verifier(),
657                    )
658                {
659                    blocked_devices.push((device.clone(), withheld_code));
660                } else {
661                    allowed_devices.push(device.clone());
662                }
663            }
664        }
665    }
666
667    if !verified_users_with_new_identities.is_empty() {
668        return Err(OlmError::SessionRecipientCollectionError(
669            SessionRecipientCollectionError::VerifiedUserChangedIdentity(
670                verified_users_with_new_identities.into_iter().collect(),
671            ),
672        ));
673    }
674
675    Ok((allowed_devices, blocked_devices))
676}
677
678pub(crate) async fn withheld_code_for_device_for_share_strategy(
679    device: &DeviceData,
680    share_strategy: CollectStrategy,
681    own_identity: &Option<OwnUserIdentityData>,
682    device_owner_identity: &Option<UserIdentityData>,
683    #[cfg(feature = "experimental-x509-identity-verification")] x509_verifier: Option<
684        &X509Verifier,
685    >,
686) -> OlmResult<Option<WithheldCode>> {
687    match share_strategy {
688        CollectStrategy::AllDevices => Ok(withheld_code_for_device_for_all_devices_strategy(
689            device,
690            own_identity,
691            device_owner_identity,
692            #[cfg(feature = "experimental-x509-identity-verification")]
693            x509_verifier,
694        )),
695        CollectStrategy::ErrorOnVerifiedUserProblem => {
696            if has_identity_verification_violation(
697                own_identity.as_ref(),
698                device_owner_identity.as_ref(),
699                #[cfg(feature = "experimental-x509-identity-verification")]
700                x509_verifier,
701            ) {
702                return Err(OlmError::SessionRecipientCollectionError(
703                    SessionRecipientCollectionError::VerifiedUserChangedIdentity(vec![
704                        device.user_id().to_owned(),
705                    ]),
706                ));
707            }
708            match handle_device_for_user_for_error_on_verified_user_problem_strategy(
709                device,
710                own_identity.as_ref(),
711                device_owner_identity.as_ref(),
712                #[cfg(feature = "experimental-x509-identity-verification")]
713                x509_verifier,
714            ) {
715                ErrorOnVerifiedUserProblemDeviceDecision::Ok => Ok(None),
716                ErrorOnVerifiedUserProblemDeviceDecision::Withhold(code) => Ok(Some(code)),
717                ErrorOnVerifiedUserProblemDeviceDecision::UnsignedOfVerified => {
718                    Err(OlmError::SessionRecipientCollectionError(
719                        SessionRecipientCollectionError::VerifiedUserHasUnsignedDevice(
720                            BTreeMap::from([(
721                                device.user_id().to_owned(),
722                                vec![device.device_id().to_owned()],
723                            )]),
724                        ),
725                    ))
726                }
727            }
728        }
729        CollectStrategy::IdentityBasedStrategy => {
730            // We require our own cross-signing to be properly set up for the
731            // identity-based strategy, so return false if it isn't.
732            match &own_identity {
733                None => {
734                    return Err(OlmError::SessionRecipientCollectionError(
735                        SessionRecipientCollectionError::CrossSigningNotSetup,
736                    ));
737                }
738                Some(identity) if !identity.is_verified() => {
739                    return Err(OlmError::SessionRecipientCollectionError(
740                        SessionRecipientCollectionError::SendingFromUnverifiedDevice,
741                    ));
742                }
743                Some(_) => (),
744            }
745
746            if has_identity_verification_violation(
747                own_identity.as_ref(),
748                device_owner_identity.as_ref(),
749                #[cfg(feature = "experimental-x509-identity-verification")]
750                x509_verifier,
751            ) {
752                Err(OlmError::SessionRecipientCollectionError(
753                    SessionRecipientCollectionError::VerifiedUserChangedIdentity(vec![
754                        device.user_id().to_owned(),
755                    ]),
756                ))
757            } else if let Some(device_owner_identity) = device_owner_identity {
758                Ok(withheld_code_for_device_with_owner_for_identity_based_strategy(
759                    device,
760                    device_owner_identity,
761                ))
762            } else {
763                // Device owner has no identity, so the device is considered to
764                // be unverified
765                Ok(Some(WithheldCode::Unverified))
766            }
767        }
768        CollectStrategy::OnlyTrustedDevices => {
769            Ok(withheld_code_for_device_for_only_trusted_devices_strategy(
770                device,
771                own_identity,
772                device_owner_identity,
773                #[cfg(feature = "experimental-x509-identity-verification")]
774                x509_verifier,
775            ))
776        }
777    }
778}
779
780/// Result type for [`split_devices_for_user_for_all_devices_strategy`],
781/// [`split_devices_for_user_for_error_on_verified_user_problem_strategy`],
782/// [`split_devices_for_user_for_identity_based_strategy`],
783/// [`split_devices_for_user_for_only_trusted_devices`].
784///
785/// A partitioning of the devices for a given user.
786#[derive(Default)]
787struct RecipientDevicesForUser {
788    /// Devices that should receive the room key.
789    allowed_devices: Vec<DeviceData>,
790    /// Devices that should receive a withheld code.
791    denied_devices_with_code: Vec<(DeviceData, WithheldCode)>,
792}
793
794/// Result type for
795/// [`split_devices_for_user_for_error_on_verified_user_problem_strategy`].
796enum ErrorOnVerifiedUserProblemResult {
797    /// We found devices that should cause the transmission to fail, due to
798    /// being an unsigned device belonging to a verified user. Only populated
799    /// when `error_on_verified_user_problem` is set.
800    UnsignedDevicesOfVerifiedUser(Vec<OwnedDeviceId>),
801
802    /// There were no unsigned devices of verified users.
803    Devices(RecipientDevicesForUser),
804}
805
806/// Partition the list of a user's devices according to whether they should
807/// receive the key, for [`CollectStrategy::AllDevices`].
808fn split_devices_for_user_for_all_devices_strategy(
809    user_devices: HashMap<OwnedDeviceId, DeviceData>,
810    own_identity: &Option<OwnUserIdentityData>,
811    device_owner_identity: &Option<UserIdentityData>,
812    #[cfg(feature = "experimental-x509-identity-verification")] x509_verifier: Option<
813        &X509Verifier,
814    >,
815) -> RecipientDevicesForUser {
816    let (left, right) = user_devices.into_values().partition_map(|d| {
817        if let Some(withheld_code) = withheld_code_for_device_for_all_devices_strategy(
818            &d,
819            own_identity,
820            device_owner_identity,
821            #[cfg(feature = "experimental-x509-identity-verification")]
822            x509_verifier,
823        ) {
824            Either::Right((d, withheld_code))
825        } else {
826            Either::Left(d)
827        }
828    });
829
830    RecipientDevicesForUser { allowed_devices: left, denied_devices_with_code: right }
831}
832
833/// Determine whether we should withhold encrypted messages from the given
834/// device, for [`CollectStrategy::AllDevices`], and if so, what withheld code
835/// to send.
836fn withheld_code_for_device_for_all_devices_strategy(
837    device_data: &DeviceData,
838    own_identity: &Option<OwnUserIdentityData>,
839    device_owner_identity: &Option<UserIdentityData>,
840    #[cfg(feature = "experimental-x509-identity-verification")] x509_verifier: Option<
841        &X509Verifier,
842    >,
843) -> Option<WithheldCode> {
844    if device_data.is_blacklisted() {
845        Some(WithheldCode::Blacklisted)
846    } else if device_data.is_dehydrated()
847        && should_withhold_to_dehydrated_device(
848            device_data,
849            own_identity.as_ref(),
850            device_owner_identity.as_ref(),
851            #[cfg(feature = "experimental-x509-identity-verification")]
852            x509_verifier,
853        )
854    {
855        Some(WithheldCode::Unverified)
856    } else {
857        None
858    }
859}
860
861/// Helper for [`split_devices_for_user_for_all_devices_strategy`].
862///
863/// Given a dehydrated device `device`, decide if we should withhold the room
864/// key from it.
865///
866/// Dehydrated devices must be signed by their owners (whether or not we have
867/// verified the owner), and, if we previously verified the owner, they must be
868/// verified still (i.e., they must not have a verification violation).
869fn should_withhold_to_dehydrated_device(
870    device: &DeviceData,
871    own_identity: Option<&OwnUserIdentityData>,
872    device_owner_identity: Option<&UserIdentityData>,
873    #[cfg(feature = "experimental-x509-identity-verification")] x509_verifier: Option<
874        &X509Verifier,
875    >,
876) -> bool {
877    device_owner_identity.is_none_or(|owner_id| {
878        // Dehydrated devices must be signed by their owners
879        !device.is_cross_signed_by_owner(owner_id) ||
880
881        // If the user has changed identity since we verified them, withhold the message
882        (owner_id.was_previously_verified() && !is_user_verified(
883            own_identity,
884            owner_id,
885            #[cfg(feature = "experimental-x509-identity-verification")]
886            x509_verifier,
887        ))
888    })
889}
890
891/// Partition the list of a user's devices according to whether they should
892/// receive the key, for [`CollectStrategy::ErrorOnVerifiedUserProblem`].
893///
894/// This function returns one of two values:
895///
896/// - A list of the devices that should cause the transmission to fail due to
897///   being unsigned. In this case, we don't bother to return the rest of the
898///   devices, because we assume transmission will fail.
899///
900/// - Otherwise, returns a [`RecipientDevicesForUser`] which lists, separately,
901///   the devices that should receive the room key, and those that should
902///   receive a withheld code.
903fn split_devices_for_user_for_error_on_verified_user_problem_strategy(
904    user_devices: HashMap<OwnedDeviceId, DeviceData>,
905    own_identity: &Option<OwnUserIdentityData>,
906    device_owner_identity: &Option<UserIdentityData>,
907    #[cfg(feature = "experimental-x509-identity-verification")] x509_verifier: Option<
908        &X509Verifier,
909    >,
910) -> ErrorOnVerifiedUserProblemResult {
911    let mut recipient_devices = RecipientDevicesForUser::default();
912
913    // We construct unsigned_devices_of_verified_users lazily, because chances
914    // are we won't need it.
915    let mut unsigned_devices_of_verified_users: Option<Vec<OwnedDeviceId>> = None;
916
917    for d in user_devices.into_values() {
918        match handle_device_for_user_for_error_on_verified_user_problem_strategy(
919            &d,
920            own_identity.as_ref(),
921            device_owner_identity.as_ref(),
922            #[cfg(feature = "experimental-x509-identity-verification")]
923            x509_verifier,
924        ) {
925            ErrorOnVerifiedUserProblemDeviceDecision::Ok => {
926                recipient_devices.allowed_devices.push(d)
927            }
928            ErrorOnVerifiedUserProblemDeviceDecision::Withhold(code) => {
929                recipient_devices.denied_devices_with_code.push((d, code))
930            }
931            ErrorOnVerifiedUserProblemDeviceDecision::UnsignedOfVerified => {
932                unsigned_devices_of_verified_users
933                    .get_or_insert_with(Vec::default)
934                    .push(d.device_id().to_owned())
935            }
936        }
937    }
938
939    if let Some(devices) = unsigned_devices_of_verified_users {
940        ErrorOnVerifiedUserProblemResult::UnsignedDevicesOfVerifiedUser(devices)
941    } else {
942        ErrorOnVerifiedUserProblemResult::Devices(recipient_devices)
943    }
944}
945
946/// Result type for
947/// [`handle_device_for_user_for_error_on_verified_user_problem_strategy`].
948enum ErrorOnVerifiedUserProblemDeviceDecision {
949    Ok,
950    Withhold(WithheldCode),
951    UnsignedOfVerified,
952}
953
954fn handle_device_for_user_for_error_on_verified_user_problem_strategy(
955    device: &DeviceData,
956    own_identity: Option<&OwnUserIdentityData>,
957    device_owner_identity: Option<&UserIdentityData>,
958    #[cfg(feature = "experimental-x509-identity-verification")] x509_verifier: Option<
959        &X509Verifier,
960    >,
961) -> ErrorOnVerifiedUserProblemDeviceDecision {
962    if device.is_blacklisted() {
963        ErrorOnVerifiedUserProblemDeviceDecision::Withhold(WithheldCode::Blacklisted)
964    } else if device.local_trust_state() == LocalTrust::Ignored {
965        // Ignore the trust state of that device and share
966        ErrorOnVerifiedUserProblemDeviceDecision::Ok
967    } else if is_unsigned_device_of_verified_user(
968        own_identity,
969        device_owner_identity,
970        device,
971        #[cfg(feature = "experimental-x509-identity-verification")]
972        x509_verifier,
973    ) {
974        ErrorOnVerifiedUserProblemDeviceDecision::UnsignedOfVerified
975    } else if device.is_dehydrated()
976        && device_owner_identity.is_none_or(|owner_id| {
977            // Dehydrated devices must be signed by their owners, whether or not
978            // that owner is verified
979            !device.is_cross_signed_by_owner(owner_id)
980        })
981    {
982        ErrorOnVerifiedUserProblemDeviceDecision::Withhold(WithheldCode::Unverified)
983    } else {
984        ErrorOnVerifiedUserProblemDeviceDecision::Ok
985    }
986}
987
988fn split_devices_for_user_for_identity_based_strategy(
989    user_devices: HashMap<OwnedDeviceId, DeviceData>,
990    device_owner_identity: &Option<UserIdentityData>,
991) -> RecipientDevicesForUser {
992    match device_owner_identity {
993        None => {
994            // withheld all the users devices, we need to have an identity for
995            // this distribution mode
996            RecipientDevicesForUser {
997                allowed_devices: Vec::default(),
998                denied_devices_with_code: user_devices
999                    .into_values()
1000                    .map(|d| (d, WithheldCode::Unverified))
1001                    .collect(),
1002            }
1003        }
1004        Some(device_owner_identity) => {
1005            // Only accept devices signed by the current identity
1006            let (recipients, withheld_recipients): (
1007                Vec<DeviceData>,
1008                Vec<(DeviceData, WithheldCode)>,
1009            ) = user_devices.into_values().partition_map(|d| {
1010                if let Some(withheld_code) =
1011                    withheld_code_for_device_with_owner_for_identity_based_strategy(
1012                        &d,
1013                        device_owner_identity,
1014                    )
1015                {
1016                    Either::Right((d, withheld_code))
1017                } else {
1018                    Either::Left(d)
1019                }
1020            });
1021            RecipientDevicesForUser {
1022                allowed_devices: recipients,
1023                denied_devices_with_code: withheld_recipients,
1024            }
1025        }
1026    }
1027}
1028
1029/// Determine whether we should withhold encrypted messages from the given
1030/// device, for [`CollectStrategy::IdentityBased`], and if so, what withheld
1031/// code to send.
1032fn withheld_code_for_device_with_owner_for_identity_based_strategy(
1033    device_data: &DeviceData,
1034    device_owner_identity: &UserIdentityData,
1035) -> Option<WithheldCode> {
1036    if device_data.is_cross_signed_by_owner(device_owner_identity) {
1037        None
1038    } else {
1039        Some(WithheldCode::Unverified)
1040    }
1041}
1042
1043/// Partition the list of a user's devices according to whether they should
1044/// receive the key, for [`CollectStrategy::OnlyTrustedDevices`].
1045fn split_devices_for_user_for_only_trusted_devices(
1046    user_devices: HashMap<OwnedDeviceId, DeviceData>,
1047    own_identity: &Option<OwnUserIdentityData>,
1048    device_owner_identity: &Option<UserIdentityData>,
1049    #[cfg(feature = "experimental-x509-identity-verification")] x509_verifier: Option<
1050        &X509Verifier,
1051    >,
1052) -> RecipientDevicesForUser {
1053    let (left, right) = user_devices.into_values().partition_map(|d| {
1054        if let Some(withheld_code) = withheld_code_for_device_for_only_trusted_devices_strategy(
1055            &d,
1056            own_identity,
1057            device_owner_identity,
1058            #[cfg(feature = "experimental-x509-identity-verification")]
1059            x509_verifier,
1060        ) {
1061            Either::Right((d, withheld_code))
1062        } else {
1063            Either::Left(d)
1064        }
1065    });
1066    RecipientDevicesForUser { allowed_devices: left, denied_devices_with_code: right }
1067}
1068
1069/// Determine whether we should withhold encrypted messages from the given
1070/// device, for [`CollectStrategy::OnlyTrustedDevices`], and if so, what
1071/// withheld code to send.
1072fn withheld_code_for_device_for_only_trusted_devices_strategy(
1073    device_data: &DeviceData,
1074    own_identity: &Option<OwnUserIdentityData>,
1075    device_owner_identity: &Option<UserIdentityData>,
1076    #[cfg(feature = "experimental-x509-identity-verification")] x509_verifier: Option<
1077        &X509Verifier,
1078    >,
1079) -> Option<WithheldCode> {
1080    match (
1081        device_data.local_trust_state(),
1082        device_data.is_cross_signing_trusted(
1083            own_identity,
1084            device_owner_identity,
1085            #[cfg(feature = "experimental-x509-identity-verification")]
1086            x509_verifier,
1087        ),
1088    ) {
1089        (LocalTrust::BlackListed, _) => Some(WithheldCode::Blacklisted),
1090        (LocalTrust::Ignored | LocalTrust::Verified, _) => None,
1091        (LocalTrust::Unset, false) => Some(WithheldCode::Unverified),
1092        (LocalTrust::Unset, true) => None,
1093    }
1094}
1095
1096fn is_unsigned_device_of_verified_user(
1097    own_identity: Option<&OwnUserIdentityData>,
1098    device_owner_identity: Option<&UserIdentityData>,
1099    device_data: &DeviceData,
1100    #[cfg(feature = "experimental-x509-identity-verification")] x509_verifier: Option<
1101        &X509Verifier,
1102    >,
1103) -> bool {
1104    device_owner_identity.is_some_and(|device_owner_identity| {
1105        is_user_verified(
1106            own_identity,
1107            device_owner_identity,
1108            #[cfg(feature = "experimental-x509-identity-verification")]
1109            x509_verifier,
1110        ) && !device_data.is_cross_signed_by_owner(device_owner_identity)
1111    })
1112}
1113
1114/// Check if the user was previously verified, but they have now changed their
1115/// identity so that they are no longer verified.
1116///
1117/// This is much the same as [`UserIdentity::has_verification_violation`], but
1118/// works with a low-level [`UserIdentityData`] rather than higher-level
1119/// [`UserIdentity`].
1120fn has_identity_verification_violation(
1121    own_identity: Option<&OwnUserIdentityData>,
1122    device_owner_identity: Option<&UserIdentityData>,
1123    #[cfg(feature = "experimental-x509-identity-verification")] x509_verifier: Option<
1124        &X509Verifier,
1125    >,
1126) -> bool {
1127    device_owner_identity.is_some_and(|device_owner_identity| {
1128        device_owner_identity.was_previously_verified()
1129            && !is_user_verified(
1130                own_identity,
1131                device_owner_identity,
1132                #[cfg(feature = "experimental-x509-identity-verification")]
1133                x509_verifier,
1134            )
1135    })
1136}
1137
1138fn is_user_verified(
1139    own_identity: Option<&OwnUserIdentityData>,
1140    user_identity: &UserIdentityData,
1141    #[cfg(feature = "experimental-x509-identity-verification")] x509_verifier: Option<
1142        &X509Verifier,
1143    >,
1144) -> bool {
1145    match user_identity {
1146        UserIdentityData::Own(own_identity) => own_identity.is_verified(),
1147        UserIdentityData::Other(other_identity) => other_identity.is_verified(
1148            own_identity,
1149            #[cfg(feature = "experimental-x509-identity-verification")]
1150            x509_verifier,
1151        ),
1152    }
1153}
1154
1155#[cfg(test)]
1156mod tests {
1157    use std::{collections::BTreeMap, iter, ops::Deref, sync::Arc};
1158
1159    use assert_matches::assert_matches;
1160    use insta::{assert_snapshot, with_settings};
1161    use matrix_sdk_common::deserialized_responses::WithheldCode;
1162    use matrix_sdk_test::{
1163        async_test, test_json,
1164        test_json::keys_query_sets::{
1165            IdentityChangeDataSet, KeyDistributionTestData, MaloIdentityChangeDataSet,
1166            VerificationViolationTestData,
1167        },
1168    };
1169    use ruma::{
1170        DeviceId, TransactionId, UserId, device_id,
1171        events::{dummy::ToDeviceDummyEventContent, room::history_visibility::HistoryVisibility},
1172        room_id,
1173    };
1174    use serde_json::json;
1175    use strass::assert_let;
1176
1177    #[cfg(feature = "experimental-send-custom-to-device")]
1178    use super::split_devices_for_share_strategy;
1179    use crate::{
1180        CrossSigningKeyExport, DeviceData, EncryptionSettings, LocalTrust, OlmError, OlmMachine,
1181        error::SessionRecipientCollectionError,
1182        olm::{OutboundGroupSession, ShareInfo},
1183        session_manager::{
1184            CollectStrategy,
1185            group_sessions::share_strategy::{
1186                collect_session_recipients, withheld_code_for_device_for_share_strategy,
1187            },
1188        },
1189        store::caches::SequenceNumber,
1190        testing::simulate_key_query_response_for_verification,
1191        types::requests::ToDeviceRequest,
1192    };
1193
1194    /// Returns an `OlmMachine` set up for the test user in
1195    /// [`KeyDistributionTestData`], with cross-signing set up and the private
1196    /// cross-signing keys imported.
1197    async fn test_machine() -> OlmMachine {
1198        use KeyDistributionTestData as DataSet;
1199
1200        // Create the local user (`@me`), and import the public identity keys
1201        let machine = OlmMachine::new(DataSet::me_id(), DataSet::me_device_id()).await;
1202        let keys_query = DataSet::me_keys_query_response();
1203        machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
1204
1205        // Also import the private cross signing keys
1206        machine
1207            .import_cross_signing_keys(CrossSigningKeyExport {
1208                master_key: DataSet::MASTER_KEY_PRIVATE_EXPORT.to_owned().into(),
1209                self_signing_key: DataSet::SELF_SIGNING_KEY_PRIVATE_EXPORT.to_owned().into(),
1210                user_signing_key: DataSet::USER_SIGNING_KEY_PRIVATE_EXPORT.to_owned().into(),
1211            })
1212            .await
1213            .unwrap();
1214
1215        machine
1216    }
1217
1218    /// Get the `DeviceData` struct for the given user's device.
1219    async fn get_device_data(
1220        machine: &OlmMachine,
1221        user_id: &UserId,
1222        device_id: &DeviceId,
1223    ) -> DeviceData {
1224        machine.get_device(user_id, device_id, None).await.unwrap().unwrap().deref().clone()
1225    }
1226
1227    async fn get_own_identity_data(
1228        machine: &OlmMachine,
1229        user_id: &UserId,
1230    ) -> Option<crate::OwnUserIdentityData> {
1231        machine
1232            .get_identity(user_id, None)
1233            .await
1234            .unwrap()
1235            .and_then(|i| i.own())
1236            .map(|i| i.deref().clone())
1237    }
1238
1239    async fn get_user_identity_data(
1240        machine: &OlmMachine,
1241        user_id: &UserId,
1242    ) -> Option<crate::UserIdentityData> {
1243        use crate::{UserIdentity, identities::user::UserIdentityData};
1244        machine.get_identity(user_id, None).await.unwrap().map(|i| match i {
1245            UserIdentity::Own(i) => UserIdentityData::Own(i.deref().clone()),
1246            UserIdentity::Other(i) => UserIdentityData::Other(i.deref().clone()),
1247        })
1248    }
1249
1250    /// Import device data for `@dan`, `@dave`, and `@good`, as referenced in
1251    /// [`KeyDistributionTestData`], into the given OlmMachine
1252    async fn import_known_users_to_test_machine(machine: &OlmMachine) {
1253        let keys_query = KeyDistributionTestData::dan_keys_query_response();
1254        let txn_id = TransactionId::new();
1255        machine.mark_request_as_sent(&txn_id, &keys_query).await.unwrap();
1256
1257        let txn_id_dave = TransactionId::new();
1258        let keys_query_dave = KeyDistributionTestData::dave_keys_query_response();
1259        machine.mark_request_as_sent(&txn_id_dave, &keys_query_dave).await.unwrap();
1260
1261        let txn_id_good = TransactionId::new();
1262        let keys_query_good = KeyDistributionTestData::good_keys_query_response();
1263        machine.mark_request_as_sent(&txn_id_good, &keys_query_good).await.unwrap();
1264    }
1265
1266    /// Assert that [`CollectStrategy::AllDevices`] retains the same
1267    /// serialization format.
1268    #[test]
1269    #[cfg(not(feature = "experimental-encrypted-state-events"))]
1270    fn test_serialize_device_based_strategy() {
1271        let encryption_settings = all_devices_strategy_settings();
1272        let serialized = serde_json::to_string(&encryption_settings).unwrap();
1273        with_settings!({prepend_module_to_snapshot => false}, {
1274            assert_snapshot!(serialized)
1275        });
1276    }
1277
1278    /// Assert that [`CollectStrategy::AllDevices`] retains the same
1279    /// serialization format, even when experimental encrypted state events are
1280    /// enabled.
1281    #[test]
1282    #[cfg(feature = "experimental-encrypted-state-events")]
1283    fn test_serialize_strategy_with_encrypted_state() {
1284        let encryption_settings = all_devices_strategy_settings();
1285        let serialized = serde_json::to_string(&encryption_settings).unwrap();
1286        with_settings!({prepend_module_to_snapshot => false}, {
1287            assert_snapshot!(serialized)
1288        });
1289    }
1290
1291    /// [`CollectStrategy::AllDevices`] used to be known as
1292    /// `DeviceBasedStrategy`. Check we can still deserialize the old
1293    /// representation.
1294    #[test]
1295    fn test_deserialize_old_device_based_strategy() {
1296        let settings: EncryptionSettings = serde_json::from_value(json!({
1297            "algorithm": "m.megolm.v1.aes-sha2",
1298            "rotation_period":{"secs":604800,"nanos":0},
1299            "rotation_period_msgs":100,
1300            "history_visibility":"shared",
1301            "sharing_strategy":{"DeviceBasedStrategy":{"only_allow_trusted_devices":false,"error_on_verified_user_problem":false}},
1302        })).unwrap();
1303        assert_matches!(settings.sharing_strategy, CollectStrategy::AllDevices);
1304    }
1305
1306    /// [`CollectStrategy::ErrorOnVerifiedUserProblem`] used to be represented
1307    /// as a variant on the former `DeviceBasedStrategy`. Check we can still
1308    /// deserialize the old representation.
1309    #[test]
1310    fn test_deserialize_old_error_on_verified_user_problem() {
1311        let settings: EncryptionSettings = serde_json::from_value(json!({
1312            "algorithm": "m.megolm.v1.aes-sha2",
1313            "rotation_period":{"secs":604800,"nanos":0},
1314            "rotation_period_msgs":100,
1315            "history_visibility":"shared",
1316            "sharing_strategy":{"DeviceBasedStrategy":{"only_allow_trusted_devices":false,"error_on_verified_user_problem":true}},
1317        })).unwrap();
1318        assert_matches!(settings.sharing_strategy, CollectStrategy::ErrorOnVerifiedUserProblem);
1319    }
1320
1321    /// [`CollectStrategy::OnlyTrustedDevices`] used to be represented as a
1322    /// variant on the former `DeviceBasedStrategy`. Check we can still
1323    /// deserialize the old representation.
1324    #[test]
1325    fn test_deserialize_old_only_trusted_devices_strategy() {
1326        let settings: EncryptionSettings = serde_json::from_value(json!({
1327            "algorithm": "m.megolm.v1.aes-sha2",
1328            "rotation_period":{"secs":604800,"nanos":0},
1329            "rotation_period_msgs":100,
1330            "history_visibility":"shared",
1331            "sharing_strategy":{"DeviceBasedStrategy":{"only_allow_trusted_devices":true,"error_on_verified_user_problem":false}},
1332        })).unwrap();
1333        assert_matches!(settings.sharing_strategy, CollectStrategy::OnlyTrustedDevices);
1334    }
1335
1336    #[async_test]
1337    async fn test_share_with_per_device_strategy_to_all() {
1338        let machine = test_machine().await;
1339        import_known_users_to_test_machine(&machine).await;
1340
1341        let encryption_settings = all_devices_strategy_settings();
1342
1343        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
1344
1345        let share_result = collect_session_recipients(
1346            machine.store(),
1347            vec![
1348                KeyDistributionTestData::dan_id(),
1349                KeyDistributionTestData::dave_id(),
1350                KeyDistributionTestData::good_id(),
1351            ]
1352            .into_iter(),
1353            &encryption_settings,
1354            &group_session,
1355        )
1356        .await
1357        .unwrap();
1358
1359        assert!(!share_result.should_rotate);
1360
1361        let dan_devices_shared =
1362            share_result.devices.get(KeyDistributionTestData::dan_id()).unwrap();
1363        let dave_devices_shared =
1364            share_result.devices.get(KeyDistributionTestData::dave_id()).unwrap();
1365        let good_devices_shared =
1366            share_result.devices.get(KeyDistributionTestData::good_id()).unwrap();
1367
1368        // With this strategy the room key would be distributed to all devices
1369        assert_eq!(dan_devices_shared.len(), 2);
1370        assert_eq!(dave_devices_shared.len(), 1);
1371        assert_eq!(good_devices_shared.len(), 2);
1372
1373        #[cfg(feature = "experimental-send-custom-to-device")]
1374        {
1375            // construct the list of all devices from the result of
1376            // collect_session_recipients, because that gives us the devices as
1377            // `DeviceData`
1378            let mut all_devices = dan_devices_shared.clone();
1379            all_devices.append(&mut dave_devices_shared.clone());
1380            all_devices.append(&mut good_devices_shared.clone());
1381
1382            let (shared_devices, withheld_devices) = split_devices_for_share_strategy(
1383                machine.store(),
1384                all_devices,
1385                CollectStrategy::AllDevices,
1386            )
1387            .await
1388            .unwrap();
1389
1390            assert_eq!(shared_devices.len(), 5);
1391            assert_eq!(withheld_devices.len(), 0);
1392        }
1393
1394        let own_identity_data =
1395            get_own_identity_data(&machine, KeyDistributionTestData::me_id()).await;
1396        let dan_identity_data =
1397            get_user_identity_data(&machine, KeyDistributionTestData::dan_id()).await;
1398
1399        assert_eq!(
1400            withheld_code_for_device_for_share_strategy(
1401                &get_device_data(
1402                    &machine,
1403                    KeyDistributionTestData::dan_id(),
1404                    KeyDistributionTestData::dan_signed_device_id()
1405                )
1406                .await,
1407                CollectStrategy::AllDevices,
1408                &own_identity_data,
1409                &dan_identity_data,
1410                #[cfg(feature = "experimental-x509-identity-verification")]
1411                None,
1412            )
1413            .await
1414            .unwrap(),
1415            None,
1416        );
1417    }
1418
1419    #[async_test]
1420    async fn test_share_with_only_trusted_strategy() {
1421        let machine = test_machine().await;
1422        import_known_users_to_test_machine(&machine).await;
1423
1424        let encryption_settings = EncryptionSettings {
1425            sharing_strategy: CollectStrategy::OnlyTrustedDevices,
1426            ..Default::default()
1427        };
1428
1429        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
1430
1431        let share_result = collect_session_recipients(
1432            machine.store(),
1433            vec![
1434                KeyDistributionTestData::dan_id(),
1435                KeyDistributionTestData::dave_id(),
1436                KeyDistributionTestData::good_id(),
1437            ]
1438            .into_iter(),
1439            &encryption_settings,
1440            &group_session,
1441        )
1442        .await
1443        .unwrap();
1444
1445        assert!(!share_result.should_rotate);
1446
1447        let dave_devices_shared = share_result.devices.get(KeyDistributionTestData::dave_id());
1448        let good_devices_shared = share_result.devices.get(KeyDistributionTestData::good_id());
1449        // dave and good wouldn't receive any key
1450        assert!(dave_devices_shared.unwrap().is_empty());
1451        assert!(good_devices_shared.unwrap().is_empty());
1452
1453        // dan is verified by me and has one of his devices self signed, so
1454        // should get the key
1455        let dan_devices_shared =
1456            share_result.devices.get(KeyDistributionTestData::dan_id()).unwrap();
1457
1458        assert_eq!(dan_devices_shared.len(), 1);
1459        let dan_device_that_will_get_the_key = &dan_devices_shared[0];
1460        assert_eq!(
1461            dan_device_that_will_get_the_key.device_id().as_str(),
1462            KeyDistributionTestData::dan_signed_device_id()
1463        );
1464
1465        // Check withhelds for others
1466        let (_, code) = share_result
1467            .withheld_devices
1468            .iter()
1469            .find(|(d, _)| d.device_id() == KeyDistributionTestData::dan_unsigned_device_id())
1470            .expect("This dan's device should receive a withheld code");
1471
1472        assert_eq!(code, &WithheldCode::Unverified);
1473
1474        let (_, code) = share_result
1475            .withheld_devices
1476            .iter()
1477            .find(|(d, _)| d.device_id() == KeyDistributionTestData::dave_device_id())
1478            .expect("This daves's device should receive a withheld code");
1479
1480        assert_eq!(code, &WithheldCode::Unverified);
1481
1482        #[cfg(feature = "experimental-send-custom-to-device")]
1483        {
1484            let all_devices: Vec<DeviceData> = vec![
1485                get_device_data(
1486                    &machine,
1487                    KeyDistributionTestData::dan_id(),
1488                    KeyDistributionTestData::dan_unsigned_device_id(),
1489                )
1490                .await,
1491                get_device_data(
1492                    &machine,
1493                    KeyDistributionTestData::dan_id(),
1494                    KeyDistributionTestData::dan_signed_device_id(),
1495                )
1496                .await,
1497                get_device_data(
1498                    &machine,
1499                    KeyDistributionTestData::dave_id(),
1500                    KeyDistributionTestData::dave_device_id(),
1501                )
1502                .await,
1503                get_device_data(
1504                    &machine,
1505                    KeyDistributionTestData::good_id(),
1506                    KeyDistributionTestData::good_device_1_id(),
1507                )
1508                .await,
1509                get_device_data(
1510                    &machine,
1511                    KeyDistributionTestData::good_id(),
1512                    KeyDistributionTestData::good_device_2_id(),
1513                )
1514                .await,
1515            ];
1516
1517            let (shared_devices, withheld_devices) = split_devices_for_share_strategy(
1518                machine.store(),
1519                all_devices,
1520                CollectStrategy::OnlyTrustedDevices,
1521            )
1522            .await
1523            .unwrap();
1524
1525            assert_eq!(shared_devices.len(), 1);
1526            assert_eq!(
1527                shared_devices[0].device_id().as_str(),
1528                KeyDistributionTestData::dan_signed_device_id()
1529            );
1530
1531            assert_eq!(withheld_devices.len(), 4);
1532            assert_eq!(
1533                withheld_devices[0].0.device_id().as_str(),
1534                KeyDistributionTestData::dan_unsigned_device_id()
1535            );
1536            assert_eq!(withheld_devices[0].1, WithheldCode::Unverified);
1537            assert_eq!(
1538                withheld_devices[1].0.device_id().as_str(),
1539                KeyDistributionTestData::dave_device_id()
1540            );
1541            assert_eq!(withheld_devices[1].1, WithheldCode::Unverified);
1542        }
1543
1544        let own_identity_data =
1545            get_own_identity_data(&machine, KeyDistributionTestData::me_id()).await;
1546        let dan_identity_data =
1547            get_user_identity_data(&machine, KeyDistributionTestData::dan_id()).await;
1548        let dave_identity_data =
1549            get_user_identity_data(&machine, KeyDistributionTestData::dave_id()).await;
1550
1551        assert_eq!(
1552            withheld_code_for_device_for_share_strategy(
1553                &get_device_data(
1554                    &machine,
1555                    KeyDistributionTestData::dan_id(),
1556                    KeyDistributionTestData::dan_signed_device_id()
1557                )
1558                .await,
1559                CollectStrategy::OnlyTrustedDevices,
1560                &own_identity_data,
1561                &dan_identity_data,
1562                #[cfg(feature = "experimental-x509-identity-verification")]
1563                None,
1564            )
1565            .await
1566            .unwrap(),
1567            None,
1568        );
1569        assert_eq!(
1570            withheld_code_for_device_for_share_strategy(
1571                &get_device_data(
1572                    &machine,
1573                    KeyDistributionTestData::dan_id(),
1574                    KeyDistributionTestData::dan_unsigned_device_id()
1575                )
1576                .await,
1577                CollectStrategy::OnlyTrustedDevices,
1578                &own_identity_data,
1579                &dan_identity_data,
1580                #[cfg(feature = "experimental-x509-identity-verification")]
1581                None,
1582            )
1583            .await
1584            .unwrap(),
1585            Some(WithheldCode::Unverified),
1586        );
1587        assert_eq!(
1588            withheld_code_for_device_for_share_strategy(
1589                &get_device_data(
1590                    &machine,
1591                    KeyDistributionTestData::dave_id(),
1592                    KeyDistributionTestData::dave_device_id()
1593                )
1594                .await,
1595                CollectStrategy::OnlyTrustedDevices,
1596                &own_identity_data,
1597                &dave_identity_data,
1598                #[cfg(feature = "experimental-x509-identity-verification")]
1599                None,
1600            )
1601            .await
1602            .unwrap(),
1603            Some(WithheldCode::Unverified),
1604        );
1605    }
1606
1607    /// Test that [`collect_session_recipients`] returns an error if there are
1608    /// unsigned devices belonging to verified users, when
1609    /// `error_on_verified_user_problem` is set.
1610    #[async_test]
1611    async fn test_error_on_unsigned_of_verified_users() {
1612        use VerificationViolationTestData as DataSet;
1613
1614        // We start with Bob, who is verified and has one unsigned device.
1615        let machine = unsigned_of_verified_setup().await;
1616
1617        // Add Carol, also verified with one unsigned device.
1618        let carol_keys = DataSet::carol_keys_query_response_signed();
1619        machine.mark_request_as_sent(&TransactionId::new(), &carol_keys).await.unwrap();
1620
1621        // Double-check the state of Carol.
1622        let carol_identity =
1623            machine.get_identity(DataSet::carol_id(), None).await.unwrap().unwrap();
1624        assert!(carol_identity.other().unwrap().is_verified());
1625
1626        let carol_unsigned_device = machine
1627            .get_device(DataSet::carol_id(), DataSet::carol_unsigned_device_id(), None)
1628            .await
1629            .unwrap()
1630            .unwrap();
1631        assert!(!carol_unsigned_device.is_verified());
1632
1633        // Sharing an OutboundGroupSession should fail.
1634        let encryption_settings = error_on_verification_problem_encryption_settings();
1635        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
1636        let share_result = collect_session_recipients(
1637            machine.store(),
1638            vec![DataSet::bob_id(), DataSet::carol_id()].into_iter(),
1639            &encryption_settings,
1640            &group_session,
1641        )
1642        .await;
1643
1644        assert_let!(
1645            Err(OlmError::SessionRecipientCollectionError(
1646                SessionRecipientCollectionError::VerifiedUserHasUnsignedDevice(unverified_devices)
1647            )) = share_result
1648        );
1649
1650        // Check the list of devices in the error.
1651        assert_eq!(
1652            unverified_devices,
1653            BTreeMap::from([
1654                (DataSet::bob_id().to_owned(), vec![DataSet::bob_device_2_id().to_owned()]),
1655                (
1656                    DataSet::carol_id().to_owned(),
1657                    vec![DataSet::carol_unsigned_device_id().to_owned()]
1658                ),
1659            ])
1660        );
1661
1662        #[cfg(feature = "experimental-send-custom-to-device")]
1663        {
1664            let all_devices = vec![
1665                get_device_data(&machine, DataSet::bob_id(), DataSet::bob_device_1_id()).await,
1666                get_device_data(&machine, DataSet::bob_id(), DataSet::bob_device_2_id()).await,
1667                get_device_data(&machine, DataSet::carol_id(), DataSet::carol_signed_device_id())
1668                    .await,
1669                get_device_data(&machine, DataSet::carol_id(), DataSet::carol_unsigned_device_id())
1670                    .await,
1671            ];
1672
1673            let split_result = split_devices_for_share_strategy(
1674                machine.store(),
1675                all_devices,
1676                CollectStrategy::ErrorOnVerifiedUserProblem,
1677            )
1678            .await;
1679
1680            assert_let!(
1681                Err(OlmError::SessionRecipientCollectionError(
1682                    SessionRecipientCollectionError::VerifiedUserHasUnsignedDevice(
1683                        unverified_devices
1684                    )
1685                )) = split_result
1686            );
1687
1688            // Check the list of devices in the error.
1689            assert_eq!(
1690                unverified_devices,
1691                BTreeMap::from([
1692                    (DataSet::bob_id().to_owned(), vec![DataSet::bob_device_2_id().to_owned()]),
1693                    (
1694                        DataSet::carol_id().to_owned(),
1695                        vec![DataSet::carol_unsigned_device_id().to_owned()]
1696                    ),
1697                ])
1698            );
1699        }
1700
1701        let own_identity_data = get_own_identity_data(&machine, DataSet::own_id()).await;
1702        let carol_identity_data = get_user_identity_data(&machine, DataSet::carol_id()).await;
1703
1704        assert_eq!(
1705            withheld_code_for_device_for_share_strategy(
1706                &get_device_data(&machine, DataSet::carol_id(), DataSet::carol_signed_device_id())
1707                    .await,
1708                CollectStrategy::ErrorOnVerifiedUserProblem,
1709                &own_identity_data,
1710                &carol_identity_data,
1711                #[cfg(feature = "experimental-x509-identity-verification")]
1712                None,
1713            )
1714            .await
1715            .unwrap(),
1716            None,
1717        );
1718        assert_let!(
1719            Err(OlmError::SessionRecipientCollectionError(
1720                SessionRecipientCollectionError::VerifiedUserHasUnsignedDevice(_)
1721            )) = withheld_code_for_device_for_share_strategy(
1722                &get_device_data(
1723                    &machine,
1724                    DataSet::carol_id(),
1725                    DataSet::carol_unsigned_device_id()
1726                )
1727                .await,
1728                CollectStrategy::ErrorOnVerifiedUserProblem,
1729                &own_identity_data,
1730                &carol_identity_data,
1731                #[cfg(feature = "experimental-x509-identity-verification")]
1732                None,
1733            )
1734            .await
1735        );
1736    }
1737
1738    /// Test that we can resolve errors from `error_on_verified_user_problem` by
1739    /// whitelisting the device.
1740    #[async_test]
1741    async fn test_error_on_unsigned_of_verified_resolve_by_whitelisting() {
1742        use VerificationViolationTestData as DataSet;
1743
1744        let machine = unsigned_of_verified_setup().await;
1745
1746        // Whitelist the unsigned device
1747        machine
1748            .get_device(DataSet::bob_id(), DataSet::bob_device_2_id(), None)
1749            .await
1750            .unwrap()
1751            .unwrap()
1752            .set_local_trust(LocalTrust::Ignored)
1753            .await
1754            .unwrap();
1755
1756        let encryption_settings = error_on_verification_problem_encryption_settings();
1757        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
1758
1759        // We should be able to share a key, and it should include the unsigned
1760        // device.
1761        let share_result = collect_session_recipients(
1762            machine.store(),
1763            iter::once(DataSet::bob_id()),
1764            &encryption_settings,
1765            &group_session,
1766        )
1767        .await
1768        .unwrap();
1769
1770        assert_eq!(2, share_result.devices.get(DataSet::bob_id()).unwrap().len());
1771        assert_eq!(0, share_result.withheld_devices.len());
1772
1773        #[cfg(feature = "experimental-send-custom-to-device")]
1774        {
1775            let all_devices = vec![
1776                get_device_data(&machine, DataSet::bob_id(), DataSet::bob_device_1_id()).await,
1777                get_device_data(&machine, DataSet::bob_id(), DataSet::bob_device_2_id()).await,
1778            ];
1779
1780            let (shared_devices, withheld_devices) = split_devices_for_share_strategy(
1781                machine.store(),
1782                all_devices,
1783                CollectStrategy::ErrorOnVerifiedUserProblem,
1784            )
1785            .await
1786            .unwrap();
1787
1788            assert_eq!(shared_devices.len(), 2);
1789            assert_eq!(withheld_devices.len(), 0);
1790        }
1791
1792        let own_identity_data = get_own_identity_data(&machine, DataSet::own_id()).await;
1793        let bob_identity_data = get_user_identity_data(&machine, DataSet::bob_id()).await;
1794
1795        assert_eq!(
1796            withheld_code_for_device_for_share_strategy(
1797                &get_device_data(&machine, DataSet::bob_id(), DataSet::bob_device_2_id()).await,
1798                CollectStrategy::ErrorOnVerifiedUserProblem,
1799                &own_identity_data,
1800                &bob_identity_data,
1801                #[cfg(feature = "experimental-x509-identity-verification")]
1802                None,
1803            )
1804            .await
1805            .unwrap(),
1806            None,
1807        );
1808    }
1809
1810    /// Test that we can resolve errors from `error_on_verified_user_problem` by
1811    /// blacklisting the device.
1812    #[async_test]
1813    async fn test_error_on_unsigned_of_verified_resolve_by_blacklisting() {
1814        use VerificationViolationTestData as DataSet;
1815
1816        let machine = unsigned_of_verified_setup().await;
1817
1818        // Blacklist the unsigned device
1819        machine
1820            .get_device(DataSet::bob_id(), DataSet::bob_device_2_id(), None)
1821            .await
1822            .unwrap()
1823            .unwrap()
1824            .set_local_trust(LocalTrust::BlackListed)
1825            .await
1826            .unwrap();
1827
1828        let encryption_settings = error_on_verification_problem_encryption_settings();
1829        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
1830
1831        // We should be able to share a key, and it should exclude the unsigned
1832        // device.
1833        let share_result = collect_session_recipients(
1834            machine.store(),
1835            iter::once(DataSet::bob_id()),
1836            &encryption_settings,
1837            &group_session,
1838        )
1839        .await
1840        .unwrap();
1841
1842        assert_eq!(1, share_result.devices.get(DataSet::bob_id()).unwrap().len());
1843        let withheld_list: Vec<_> = share_result
1844            .withheld_devices
1845            .iter()
1846            .map(|(d, code)| (d.device_id().to_owned(), code.clone()))
1847            .collect();
1848        assert_eq!(
1849            withheld_list,
1850            vec![(DataSet::bob_device_2_id().to_owned(), WithheldCode::Blacklisted)]
1851        );
1852
1853        let bob_device_2 =
1854            get_device_data(&machine, DataSet::bob_id(), DataSet::bob_device_2_id()).await;
1855        #[cfg(feature = "experimental-send-custom-to-device")]
1856        {
1857            let bob_device_1 =
1858                get_device_data(&machine, DataSet::bob_id(), DataSet::bob_device_1_id()).await;
1859            let all_devices = vec![bob_device_1.clone(), bob_device_2.clone()];
1860
1861            let (shared_devices, withheld_devices) = split_devices_for_share_strategy(
1862                machine.store(),
1863                all_devices,
1864                CollectStrategy::ErrorOnVerifiedUserProblem,
1865            )
1866            .await
1867            .unwrap();
1868
1869            assert_eq!(shared_devices, vec![bob_device_1.clone()]);
1870            assert_eq!(withheld_devices, vec![(bob_device_2.clone(), WithheldCode::Blacklisted)]);
1871        }
1872
1873        let own_identity_data = get_own_identity_data(&machine, DataSet::own_id()).await;
1874        let bob_identity_data = get_user_identity_data(&machine, DataSet::bob_id()).await;
1875
1876        assert_eq!(
1877            withheld_code_for_device_for_share_strategy(
1878                &bob_device_2,
1879                CollectStrategy::ErrorOnVerifiedUserProblem,
1880                &own_identity_data,
1881                &bob_identity_data,
1882                #[cfg(feature = "experimental-x509-identity-verification")]
1883                None,
1884            )
1885            .await
1886            .unwrap(),
1887            Some(WithheldCode::Blacklisted),
1888        );
1889    }
1890
1891    /// Test that [`collect_session_recipients`] returns an error when
1892    /// `error_on_verified_user_problem` is set, if our own identity is verified
1893    /// and we have unsigned devices.
1894    #[async_test]
1895    async fn test_error_on_unsigned_of_verified_owner_is_us() {
1896        use VerificationViolationTestData as DataSet;
1897
1898        let machine = unsigned_of_verified_setup().await;
1899
1900        // Add a couple of devices to Alice's account
1901        let mut own_keys = DataSet::own_keys_query_response_1().clone();
1902        own_keys.device_keys.insert(
1903            DataSet::own_id().to_owned(),
1904            BTreeMap::from([
1905                DataSet::own_signed_device_keys(),
1906                DataSet::own_unsigned_device_keys(),
1907            ]),
1908        );
1909        machine.mark_request_as_sent(&TransactionId::new(), &own_keys).await.unwrap();
1910
1911        let encryption_settings = error_on_verification_problem_encryption_settings();
1912        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
1913        let share_result = collect_session_recipients(
1914            machine.store(),
1915            iter::once(DataSet::own_id()),
1916            &encryption_settings,
1917            &group_session,
1918        )
1919        .await;
1920
1921        assert_let!(
1922            Err(OlmError::SessionRecipientCollectionError(
1923                SessionRecipientCollectionError::VerifiedUserHasUnsignedDevice(unverified_devices)
1924            )) = share_result
1925        );
1926
1927        // Check the list of devices in the error.
1928        assert_eq!(
1929            unverified_devices,
1930            BTreeMap::from([(
1931                DataSet::own_id().to_owned(),
1932                vec![DataSet::own_unsigned_device_id()]
1933            ),])
1934        );
1935
1936        #[cfg(feature = "experimental-send-custom-to-device")]
1937        {
1938            let all_devices = vec![
1939                get_device_data(&machine, DataSet::own_id(), &DataSet::own_signed_device_id())
1940                    .await,
1941                get_device_data(&machine, DataSet::own_id(), &DataSet::own_unsigned_device_id())
1942                    .await,
1943            ];
1944
1945            let split_result = split_devices_for_share_strategy(
1946                machine.store(),
1947                all_devices,
1948                CollectStrategy::ErrorOnVerifiedUserProblem,
1949            )
1950            .await;
1951            assert_let!(
1952                Err(OlmError::SessionRecipientCollectionError(
1953                    SessionRecipientCollectionError::VerifiedUserHasUnsignedDevice(
1954                        unverified_devices
1955                    )
1956                )) = split_result
1957            );
1958
1959            // Check the list of devices in the error.
1960            assert_eq!(
1961                unverified_devices,
1962                BTreeMap::from([(
1963                    DataSet::own_id().to_owned(),
1964                    vec![DataSet::own_unsigned_device_id()]
1965                ),])
1966            );
1967        }
1968
1969        let own_identity_data = get_own_identity_data(&machine, DataSet::own_id()).await;
1970        let own_user_identity_data = get_user_identity_data(&machine, DataSet::own_id()).await;
1971
1972        assert_let!(
1973            Err(OlmError::SessionRecipientCollectionError(
1974                SessionRecipientCollectionError::VerifiedUserHasUnsignedDevice(_)
1975            )) = withheld_code_for_device_for_share_strategy(
1976                &get_device_data(&machine, DataSet::own_id(), &DataSet::own_unsigned_device_id())
1977                    .await,
1978                CollectStrategy::ErrorOnVerifiedUserProblem,
1979                &own_identity_data,
1980                &own_user_identity_data,
1981                #[cfg(feature = "experimental-x509-identity-verification")]
1982                None,
1983            )
1984            .await
1985        );
1986    }
1987
1988    /// Test that an unsigned device of an unverified user doesn't cause an
1989    /// error.
1990    #[async_test]
1991    async fn test_should_not_error_on_unsigned_of_unverified() {
1992        use VerificationViolationTestData as DataSet;
1993
1994        let machine = OlmMachine::new(DataSet::own_id(), device_id!("LOCAL")).await;
1995
1996        // Tell the OlmMachine about our own public keys.
1997        let own_keys = DataSet::own_keys_query_response_1();
1998        machine.mark_request_as_sent(&TransactionId::new(), &own_keys).await.unwrap();
1999
2000        // Import the secret parts of our own cross-signing keys.
2001        machine
2002            .import_cross_signing_keys(CrossSigningKeyExport {
2003                master_key: DataSet::MASTER_KEY_PRIVATE_EXPORT.to_owned().into(),
2004                self_signing_key: DataSet::SELF_SIGNING_KEY_PRIVATE_EXPORT.to_owned().into(),
2005                user_signing_key: DataSet::USER_SIGNING_KEY_PRIVATE_EXPORT.to_owned().into(),
2006            })
2007            .await
2008            .unwrap();
2009
2010        // This time our own identity is trusted but is not signing bob.
2011        let bob_keys = DataSet::bob_keys_query_response_rotated();
2012        machine.mark_request_as_sent(&TransactionId::new(), &bob_keys).await.unwrap();
2013
2014        // Double-check the state of Bob: he should be unverified, and should
2015        // have an unsigned device.
2016        let bob_identity = machine.get_identity(DataSet::bob_id(), None).await.unwrap().unwrap();
2017        assert!(!bob_identity.other().unwrap().is_verified());
2018
2019        let bob_unsigned_device = machine
2020            .get_device(DataSet::bob_id(), DataSet::bob_device_1_id(), None)
2021            .await
2022            .unwrap()
2023            .unwrap();
2024        assert!(!bob_unsigned_device.is_cross_signed_by_owner());
2025
2026        let encryption_settings = error_on_verification_problem_encryption_settings();
2027        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
2028        collect_session_recipients(
2029            machine.store(),
2030            iter::once(DataSet::bob_id()),
2031            &encryption_settings,
2032            &group_session,
2033        )
2034        .await
2035        .unwrap();
2036
2037        #[cfg(feature = "experimental-send-custom-to-device")]
2038        {
2039            let all_devices = vec![
2040                get_device_data(&machine, DataSet::bob_id(), DataSet::bob_device_1_id()).await,
2041                get_device_data(&machine, DataSet::bob_id(), DataSet::bob_device_2_id()).await,
2042            ];
2043
2044            let (shared_devices, withheld_devices) = split_devices_for_share_strategy(
2045                machine.store(),
2046                all_devices,
2047                CollectStrategy::ErrorOnVerifiedUserProblem,
2048            )
2049            .await
2050            .unwrap();
2051
2052            assert_eq!(shared_devices.len(), 2);
2053            assert_eq!(withheld_devices.len(), 0);
2054        }
2055
2056        let own_identity_data = get_own_identity_data(&machine, DataSet::own_id()).await;
2057        let bob_identity_data = get_user_identity_data(&machine, DataSet::bob_id()).await;
2058
2059        assert_eq!(
2060            withheld_code_for_device_for_share_strategy(
2061                &get_device_data(&machine, DataSet::bob_id(), DataSet::bob_device_2_id()).await,
2062                CollectStrategy::ErrorOnVerifiedUserProblem,
2063                &own_identity_data,
2064                &bob_identity_data,
2065                #[cfg(feature = "experimental-x509-identity-verification")]
2066                None,
2067            )
2068            .await
2069            .unwrap(),
2070            None,
2071        );
2072    }
2073
2074    /// Test that an unsigned device of a signed user doesn't cause an error,
2075    /// when we have not verified our own identity.
2076    #[async_test]
2077    async fn test_should_not_error_on_unsigned_of_signed_but_unverified() {
2078        use VerificationViolationTestData as DataSet;
2079
2080        let machine = OlmMachine::new(DataSet::own_id(), device_id!("LOCAL")).await;
2081
2082        // Tell the OlmMachine about our own public keys.
2083        let keys_query = DataSet::own_keys_query_response_1();
2084        machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
2085
2086        // ... and those of Bob.
2087        let keys_query = DataSet::bob_keys_query_response_signed();
2088        machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
2089
2090        // Double-check the state of Bob: his identity should be signed but
2091        // unverified, and he should have an unsigned device.
2092        let bob_identity =
2093            machine.get_identity(DataSet::bob_id(), None).await.unwrap().unwrap().other().unwrap();
2094        assert!(
2095            bob_identity.own_identity.as_ref().unwrap().is_identity_signed(&bob_identity.inner)
2096        );
2097        assert!(!bob_identity.is_verified());
2098
2099        let bob_unsigned_device = machine
2100            .get_device(DataSet::bob_id(), DataSet::bob_device_2_id(), None)
2101            .await
2102            .unwrap()
2103            .unwrap();
2104        assert!(!bob_unsigned_device.is_cross_signed_by_owner());
2105
2106        // Share a session, and ensure that it doesn't error.
2107        let encryption_settings = error_on_verification_problem_encryption_settings();
2108        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
2109        collect_session_recipients(
2110            machine.store(),
2111            iter::once(DataSet::bob_id()),
2112            &encryption_settings,
2113            &group_session,
2114        )
2115        .await
2116        .unwrap();
2117
2118        #[cfg(feature = "experimental-send-custom-to-device")]
2119        {
2120            let all_devices = vec![
2121                get_device_data(&machine, DataSet::bob_id(), DataSet::bob_device_1_id()).await,
2122                get_device_data(&machine, DataSet::bob_id(), DataSet::bob_device_2_id()).await,
2123            ];
2124
2125            let (shared_devices, withheld_devices) = split_devices_for_share_strategy(
2126                machine.store(),
2127                all_devices,
2128                CollectStrategy::ErrorOnVerifiedUserProblem,
2129            )
2130            .await
2131            .unwrap();
2132
2133            assert_eq!(shared_devices.len(), 2);
2134            assert_eq!(withheld_devices.len(), 0);
2135        }
2136
2137        let own_identity_data = get_own_identity_data(&machine, DataSet::own_id()).await;
2138        let bob_identity_data = get_user_identity_data(&machine, DataSet::bob_id()).await;
2139
2140        assert_eq!(
2141            withheld_code_for_device_for_share_strategy(
2142                &get_device_data(&machine, DataSet::bob_id(), DataSet::bob_device_2_id()).await,
2143                CollectStrategy::ErrorOnVerifiedUserProblem,
2144                &own_identity_data,
2145                &bob_identity_data,
2146                #[cfg(feature = "experimental-x509-identity-verification")]
2147                None,
2148            )
2149            .await
2150            .unwrap(),
2151            None,
2152        );
2153    }
2154
2155    /// Test that a verified user changing their identity causes an error in
2156    /// `collect_session_recipients`, and that it can be resolved by withdrawing
2157    /// verification
2158    #[async_test]
2159    async fn test_verified_user_changed_identity() {
2160        use test_json::keys_query_sets::VerificationViolationTestData as DataSet;
2161
2162        // We start with Bob, who is verified and has one unsigned device. We
2163        // have also verified our own identity.
2164        let machine = unsigned_of_verified_setup().await;
2165
2166        // Bob then rotates his identity
2167        let bob_keys = DataSet::bob_keys_query_response_rotated();
2168        machine.mark_request_as_sent(&TransactionId::new(), &bob_keys).await.unwrap();
2169
2170        // Double-check the state of Bob
2171        let bob_identity = machine.get_identity(DataSet::bob_id(), None).await.unwrap().unwrap();
2172        assert!(bob_identity.has_verification_violation());
2173
2174        // Sharing an OutboundGroupSession should fail.
2175        let encryption_settings = error_on_verification_problem_encryption_settings();
2176        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
2177        let share_result = collect_session_recipients(
2178            machine.store(),
2179            iter::once(DataSet::bob_id()),
2180            &encryption_settings,
2181            &group_session,
2182        )
2183        .await;
2184
2185        assert_let!(
2186            Err(OlmError::SessionRecipientCollectionError(
2187                SessionRecipientCollectionError::VerifiedUserChangedIdentity(violating_users)
2188            )) = share_result
2189        );
2190        assert_eq!(violating_users, vec![DataSet::bob_id()]);
2191
2192        #[cfg(feature = "experimental-send-custom-to-device")]
2193        {
2194            let all_devices = vec![
2195                get_device_data(&machine, DataSet::bob_id(), DataSet::bob_device_1_id()).await,
2196                get_device_data(&machine, DataSet::bob_id(), DataSet::bob_device_2_id()).await,
2197            ];
2198
2199            let split_result = split_devices_for_share_strategy(
2200                machine.store(),
2201                all_devices,
2202                CollectStrategy::ErrorOnVerifiedUserProblem,
2203            )
2204            .await;
2205            assert_let!(
2206                Err(OlmError::SessionRecipientCollectionError(
2207                    SessionRecipientCollectionError::VerifiedUserChangedIdentity(violating_users)
2208                )) = split_result
2209            );
2210            assert_eq!(violating_users, vec![DataSet::bob_id()]);
2211        }
2212
2213        let own_identity_data = get_own_identity_data(&machine, DataSet::own_id()).await;
2214        let bob_identity_data = get_user_identity_data(&machine, DataSet::bob_id()).await;
2215
2216        assert_let!(
2217            Err(OlmError::SessionRecipientCollectionError(
2218                SessionRecipientCollectionError::VerifiedUserChangedIdentity(_)
2219            )) = withheld_code_for_device_for_share_strategy(
2220                &get_device_data(&machine, DataSet::bob_id(), DataSet::bob_device_1_id()).await,
2221                CollectStrategy::ErrorOnVerifiedUserProblem,
2222                &own_identity_data,
2223                &bob_identity_data,
2224                #[cfg(feature = "experimental-x509-identity-verification")]
2225                None,
2226            )
2227            .await
2228        );
2229
2230        // Resolve by calling withdraw_verification
2231        bob_identity.withdraw_verification().await.unwrap();
2232
2233        collect_session_recipients(
2234            machine.store(),
2235            iter::once(DataSet::bob_id()),
2236            &encryption_settings,
2237            &group_session,
2238        )
2239        .await
2240        .unwrap();
2241
2242        #[cfg(feature = "experimental-send-custom-to-device")]
2243        {
2244            let all_devices = vec![
2245                get_device_data(&machine, DataSet::bob_id(), DataSet::bob_device_1_id()).await,
2246                get_device_data(&machine, DataSet::bob_id(), DataSet::bob_device_2_id()).await,
2247            ];
2248
2249            split_devices_for_share_strategy(
2250                machine.store(),
2251                all_devices,
2252                CollectStrategy::ErrorOnVerifiedUserProblem,
2253            )
2254            .await
2255            .unwrap();
2256        }
2257
2258        let own_identity_data = get_own_identity_data(&machine, DataSet::own_id()).await;
2259        let bob_identity_data = get_user_identity_data(&machine, DataSet::bob_id()).await;
2260
2261        assert_eq!(
2262            withheld_code_for_device_for_share_strategy(
2263                &get_device_data(&machine, DataSet::bob_id(), DataSet::bob_device_1_id()).await,
2264                CollectStrategy::ErrorOnVerifiedUserProblem,
2265                &own_identity_data,
2266                &bob_identity_data,
2267                #[cfg(feature = "experimental-x509-identity-verification")]
2268                None,
2269            )
2270            .await
2271            .unwrap(),
2272            None,
2273        );
2274    }
2275
2276    /// Test that our own identity being changed causes an error in
2277    /// `collect_session_recipients`, and that it can be resolved by withdrawing
2278    /// verification
2279    #[async_test]
2280    async fn test_own_verified_identity_changed() {
2281        use test_json::keys_query_sets::VerificationViolationTestData as DataSet;
2282
2283        // We start with a verified identity.
2284        let machine = unsigned_of_verified_setup().await;
2285        let own_identity = machine.get_identity(DataSet::own_id(), None).await.unwrap().unwrap();
2286        assert!(own_identity.own().unwrap().is_verified());
2287
2288        // Another device rotates our own identity.
2289        let own_keys = DataSet::own_keys_query_response_2();
2290        machine.mark_request_as_sent(&TransactionId::new(), &own_keys).await.unwrap();
2291
2292        let own_identity = machine.get_identity(DataSet::own_id(), None).await.unwrap().unwrap();
2293        assert!(!own_identity.is_verified());
2294
2295        // Sharing an OutboundGroupSession should fail.
2296        let encryption_settings = error_on_verification_problem_encryption_settings();
2297        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
2298        let share_result = collect_session_recipients(
2299            machine.store(),
2300            iter::once(DataSet::own_id()),
2301            &encryption_settings,
2302            &group_session,
2303        )
2304        .await;
2305
2306        assert_let!(
2307            Err(OlmError::SessionRecipientCollectionError(
2308                SessionRecipientCollectionError::VerifiedUserChangedIdentity(violating_users)
2309            )) = share_result
2310        );
2311        assert_eq!(violating_users, vec![DataSet::own_id()]);
2312
2313        #[cfg(feature = "experimental-send-custom-to-device")]
2314        {
2315            let all_devices: Vec<DeviceData> =
2316                vec![get_device_data(&machine, DataSet::own_id(), machine.device_id()).await];
2317
2318            let split_result = split_devices_for_share_strategy(
2319                machine.store(),
2320                all_devices,
2321                CollectStrategy::ErrorOnVerifiedUserProblem,
2322            )
2323            .await;
2324
2325            assert_let!(
2326                Err(OlmError::SessionRecipientCollectionError(
2327                    SessionRecipientCollectionError::VerifiedUserChangedIdentity(violating_users)
2328                )) = split_result
2329            );
2330            assert_eq!(violating_users, vec![DataSet::own_id()]);
2331        }
2332
2333        let own_identity_data = get_own_identity_data(&machine, DataSet::own_id()).await;
2334        let own_user_identity_data = get_user_identity_data(&machine, DataSet::own_id()).await;
2335
2336        assert_let!(
2337            Err(OlmError::SessionRecipientCollectionError(
2338                SessionRecipientCollectionError::VerifiedUserChangedIdentity(_)
2339            )) = withheld_code_for_device_for_share_strategy(
2340                &get_device_data(&machine, DataSet::own_id(), machine.device_id()).await,
2341                CollectStrategy::ErrorOnVerifiedUserProblem,
2342                &own_identity_data,
2343                &own_user_identity_data,
2344                #[cfg(feature = "experimental-x509-identity-verification")]
2345                None,
2346            )
2347            .await
2348        );
2349
2350        // Resolve by calling withdraw_verification
2351        own_identity.withdraw_verification().await.unwrap();
2352
2353        collect_session_recipients(
2354            machine.store(),
2355            iter::once(DataSet::own_id()),
2356            &encryption_settings,
2357            &group_session,
2358        )
2359        .await
2360        .unwrap();
2361
2362        #[cfg(feature = "experimental-send-custom-to-device")]
2363        {
2364            let all_devices: Vec<DeviceData> =
2365                vec![get_device_data(&machine, DataSet::own_id(), machine.device_id()).await];
2366
2367            split_devices_for_share_strategy(
2368                machine.store(),
2369                all_devices,
2370                CollectStrategy::ErrorOnVerifiedUserProblem,
2371            )
2372            .await
2373            .unwrap();
2374        }
2375
2376        let own_identity_data = get_own_identity_data(&machine, DataSet::own_id()).await;
2377        let own_user_identity_data = get_user_identity_data(&machine, DataSet::own_id()).await;
2378
2379        withheld_code_for_device_for_share_strategy(
2380            &get_device_data(&machine, DataSet::own_id(), machine.device_id()).await,
2381            CollectStrategy::ErrorOnVerifiedUserProblem,
2382            &own_identity_data,
2383            &own_user_identity_data,
2384            #[cfg(feature = "experimental-x509-identity-verification")]
2385            None,
2386        )
2387        .await
2388        .unwrap();
2389    }
2390
2391    /// A set of tests for the behaviour of [`collect_session_recipients`] with
2392    /// a dehydrated device
2393    mod dehydrated_device {
2394        use std::{collections::HashSet, iter};
2395
2396        use insta::{allow_duplicates, assert_json_snapshot, with_settings};
2397        use matrix_sdk_common::deserialized_responses::WithheldCode;
2398        use matrix_sdk_test::{
2399            async_test, ruma_response_to_json,
2400            test_json::keys_query_sets::{
2401                KeyDistributionTestData, KeyQueryResponseTemplate,
2402                KeyQueryResponseTemplateDeviceOptions,
2403            },
2404        };
2405        use ruma::{DeviceId, TransactionId, UserId, device_id, user_id};
2406        use vodozemac::{Curve25519PublicKey, Ed25519SecretKey};
2407
2408        use super::{
2409            all_devices_strategy_settings, create_test_outbound_group_session,
2410            error_on_verification_problem_encryption_settings, identity_based_strategy_settings,
2411            test_machine,
2412        };
2413        use crate::{
2414            EncryptionSettings, OlmMachine,
2415            session_manager::group_sessions::{
2416                CollectRecipientsResult, share_strategy::collect_session_recipients,
2417            },
2418        };
2419
2420        #[async_test]
2421        async fn test_all_devices_strategy_should_share_with_verified_dehydrated_device() {
2422            should_share_with_verified_dehydrated_device(&all_devices_strategy_settings()).await
2423        }
2424
2425        #[async_test]
2426        async fn test_error_on_verification_problem_strategy_should_share_with_verified_dehydrated_device()
2427         {
2428            should_share_with_verified_dehydrated_device(
2429                &error_on_verification_problem_encryption_settings(),
2430            )
2431            .await
2432        }
2433
2434        #[async_test]
2435        async fn test_identity_based_strategy_should_share_with_verified_dehydrated_device() {
2436            should_share_with_verified_dehydrated_device(&identity_based_strategy_settings()).await
2437        }
2438
2439        /// Common helper for
2440        /// [`test_all_devices_strategy_should_share_with_verified_dehydrated_device`],
2441        /// [`test_error_on_verification_problem_strategy_should_share_with_verified_dehydrated_device`]
2442        /// and
2443        /// [`test_identity_based_strategy_should_share_with_verified_dehydrated_device`].
2444        async fn should_share_with_verified_dehydrated_device(
2445            encryption_settings: &EncryptionSettings,
2446        ) {
2447            let machine = test_machine().await;
2448
2449            // Bob is a user with cross-signing, who has a single (verified)
2450            // dehydrated device.
2451            let bob_user_id = user_id!("@bob:localhost");
2452            let bob_dehydrated_device_id = device_id!("DEHYDRATED_DEVICE");
2453            let keys_query = key_query_response_template_with_cross_signing(bob_user_id)
2454                .with_dehydrated_device(bob_dehydrated_device_id, true)
2455                .build_response();
2456            allow_duplicates! {
2457                with_settings!({ sort_maps => true, prepend_module_to_snapshot => false }, {
2458                    assert_json_snapshot!(ruma_response_to_json(keys_query.clone()));
2459                });
2460            }
2461            machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
2462
2463            // When we collect the recipients ...
2464            let recips = share_test_session_and_collect_recipients(
2465                &machine,
2466                bob_user_id,
2467                encryption_settings,
2468            )
2469            .await;
2470
2471            // ... then the dehydrated device should be included
2472            assert_shared_with(recips, bob_user_id, [bob_dehydrated_device_id].into());
2473        }
2474
2475        #[async_test]
2476        async fn test_all_devices_strategy_should_not_share_with_unverified_dehydrated_device() {
2477            should_not_share_with_unverified_dehydrated_device(&all_devices_strategy_settings())
2478                .await
2479        }
2480
2481        #[async_test]
2482        async fn test_error_on_verification_problem_strategy_should_not_share_with_unverified_dehydrated_device()
2483         {
2484            should_not_share_with_unverified_dehydrated_device(
2485                &error_on_verification_problem_encryption_settings(),
2486            )
2487            .await
2488        }
2489
2490        #[async_test]
2491        async fn test_identity_based_strategy_should_not_share_with_unverified_dehydrated_device() {
2492            should_not_share_with_unverified_dehydrated_device(&identity_based_strategy_settings())
2493                .await
2494        }
2495
2496        /// Common helper for
2497        /// [`test_all_devices_strategy_should_not_share_with_unverified_dehydrated_device`],
2498        /// [`test_error_on_verification_problem_strategy_should_not_share_with_unverified_dehydrated_device`]
2499        /// and
2500        /// [`test_identity_based_strategy_should_not_share_with_unverified_dehydrated_device`].
2501        async fn should_not_share_with_unverified_dehydrated_device(
2502            encryption_settings: &EncryptionSettings,
2503        ) {
2504            let machine = test_machine().await;
2505
2506            // Bob is a user with cross-signing, who has a single (unverified)
2507            // dehydrated device.
2508            let bob_user_id = user_id!("@bob:localhost");
2509            let bob_dehydrated_device_id = device_id!("DEHYDRATED_DEVICE");
2510            let keys_query = key_query_response_template_with_cross_signing(bob_user_id)
2511                .with_dehydrated_device(bob_dehydrated_device_id, false)
2512                .build_response();
2513            allow_duplicates! {
2514                with_settings!({ sort_maps => true, prepend_module_to_snapshot => false }, {
2515                    assert_json_snapshot!(ruma_response_to_json(keys_query.clone()));
2516                });
2517            }
2518            machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
2519
2520            // When we collect the recipients ...
2521            let recips = share_test_session_and_collect_recipients(
2522                &machine,
2523                bob_user_id,
2524                encryption_settings,
2525            )
2526            .await;
2527
2528            // ... it shouldn't be shared with anyone, and there should be a
2529            // withheld message for the dehydrated device.
2530            assert_withheld_to(recips, bob_user_id, bob_dehydrated_device_id);
2531        }
2532
2533        #[async_test]
2534        async fn test_all_devices_strategy_should_share_with_verified_device_of_pin_violation_user()
2535        {
2536            should_share_with_verified_device_of_pin_violation_user(
2537                &all_devices_strategy_settings(),
2538            )
2539            .await
2540        }
2541
2542        #[async_test]
2543        async fn test_error_on_verification_problem_strategy_should_share_with_verified_device_of_pin_violation_user()
2544         {
2545            should_share_with_verified_device_of_pin_violation_user(
2546                &error_on_verification_problem_encryption_settings(),
2547            )
2548            .await
2549        }
2550
2551        #[async_test]
2552        async fn test_identity_based_strategy_should_share_with_verified_device_of_pin_violation_user()
2553         {
2554            should_share_with_verified_device_of_pin_violation_user(
2555                &identity_based_strategy_settings(),
2556            )
2557            .await
2558        }
2559
2560        /// Common helper for
2561        /// [`test_all_devices_strategy_should_share_with_verified_device_of_pin_violation_user`],
2562        /// [`test_error_on_verification_problem_strategy_should_share_with_verified_device_of_pin_violation_user`]
2563        /// and
2564        /// [`test_identity_based_strategy_should_share_with_verified_device_of_pin_violation_user`].
2565        async fn should_share_with_verified_device_of_pin_violation_user(
2566            encryption_settings: &EncryptionSettings,
2567        ) {
2568            let machine = test_machine().await;
2569
2570            // Bob starts out with one identity
2571            let bob_user_id = user_id!("@bob:localhost");
2572            let keys_query =
2573                key_query_response_template_with_cross_signing(bob_user_id).build_response();
2574            machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
2575
2576            // He then changes identity, and adds a dehydrated device (signed
2577            // with his new identity)
2578            let bob_dehydrated_device_id = device_id!("DEHYDRATED_DEVICE");
2579            let keys_query = key_query_response_template_with_changed_cross_signing(bob_user_id)
2580                .with_dehydrated_device(bob_dehydrated_device_id, true)
2581                .build_response();
2582            allow_duplicates! {
2583                with_settings!({ sort_maps => true, prepend_module_to_snapshot => false }, {
2584                    assert_json_snapshot!(ruma_response_to_json(keys_query.clone()));
2585                });
2586            }
2587            machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
2588
2589            // When we collect the recipients ...
2590            let recips = share_test_session_and_collect_recipients(
2591                &machine,
2592                bob_user_id,
2593                encryption_settings,
2594            )
2595            .await;
2596
2597            // ... then the dehydrated device should be included
2598            assert_shared_with(recips, bob_user_id, [bob_dehydrated_device_id].into());
2599        }
2600
2601        #[async_test]
2602        async fn test_all_devices_strategy_should_not_share_with_dehydrated_device_of_verification_violation_user()
2603         {
2604            should_not_share_with_dehydrated_device_of_verification_violation_user(
2605                &all_devices_strategy_settings(),
2606            )
2607            .await
2608        }
2609
2610        /// Helper function for
2611        /// [`test_all_devices_strategy_should_not_share_with_dehydrated_device_of_verification_violation_user`].
2612        async fn should_not_share_with_dehydrated_device_of_verification_violation_user(
2613            encryption_settings: &EncryptionSettings,
2614        ) {
2615            let bob_user_id = user_id!("@bob:localhost");
2616            let bob_dehydrated_device_id = device_id!("DEHYDRATED_DEVICE");
2617            let machine = prepare_machine_with_dehydrated_device_of_verification_violation_user(
2618                bob_user_id,
2619                bob_dehydrated_device_id,
2620            )
2621            .await;
2622
2623            // When we collect the recipients ...
2624            let recips = share_test_session_and_collect_recipients(
2625                &machine,
2626                bob_user_id,
2627                encryption_settings,
2628            )
2629            .await;
2630
2631            // ... it shouldn't be shared with anyone, and there should be a
2632            // withheld message for the dehydrated device.
2633            assert_withheld_to(recips, bob_user_id, bob_dehydrated_device_id);
2634        }
2635
2636        #[async_test]
2637        async fn test_error_on_verification_problem_strategy_should_give_error_for_dehydrated_device_of_verification_violation_user()
2638         {
2639            should_give_error_for_dehydrated_device_of_verification_violation_user(
2640                &error_on_verification_problem_encryption_settings(),
2641            )
2642            .await
2643        }
2644
2645        #[async_test]
2646        async fn test_identity_based_strategy_should_give_error_for_dehydrated_device_of_verification_violation_user()
2647         {
2648            // This hits the same codepath as
2649            // `test_share_identity_strategy_report_verification_violation`, but
2650            // we test dehydrated devices here specifically, for completeness.
2651            should_give_error_for_dehydrated_device_of_verification_violation_user(
2652                &identity_based_strategy_settings(),
2653            )
2654            .await
2655        }
2656
2657        /// Common helper for
2658        /// [`test_error_on_verification_problem_strategy_should_give_error_for_dehydrated_device_of_verification_violation_user`]
2659        /// and
2660        /// [`test_identity_based_strategy_should_give_error_for_dehydrated_device_of_verification_violation_user`].
2661        async fn should_give_error_for_dehydrated_device_of_verification_violation_user(
2662            encryption_settings: &EncryptionSettings,
2663        ) {
2664            let bob_user_id = user_id!("@bob:localhost");
2665            let bob_dehydrated_device_id = device_id!("DEHYDRATED_DEVICE");
2666            let machine = prepare_machine_with_dehydrated_device_of_verification_violation_user(
2667                bob_user_id,
2668                bob_dehydrated_device_id,
2669            )
2670            .await;
2671
2672            let group_session = create_test_outbound_group_session(&machine, encryption_settings);
2673            let share_result = collect_session_recipients(
2674                machine.store(),
2675                iter::once(bob_user_id),
2676                encryption_settings,
2677                &group_session,
2678            )
2679            .await;
2680
2681            // The key share should fail with an error indicating that
2682            // recipients were previously verified.
2683            assert_matches::assert_matches!(
2684                share_result,
2685                Err(crate::OlmError::SessionRecipientCollectionError(
2686                    crate::SessionRecipientCollectionError::VerifiedUserChangedIdentity(_)
2687                ))
2688            );
2689        }
2690
2691        /// Prepare an OlmMachine which knows about a user `bob_user_id`, who
2692        /// has recently changed identity, and then added a new dehydrated
2693        /// device `bob_dehydrated_device_id`.
2694        async fn prepare_machine_with_dehydrated_device_of_verification_violation_user(
2695            bob_user_id: &UserId,
2696            bob_dehydrated_device_id: &DeviceId,
2697        ) -> OlmMachine {
2698            let machine = test_machine().await;
2699
2700            // Bob starts out with one identity, which we have verified
2701            let keys_query = key_query_response_template_with_cross_signing(bob_user_id)
2702                .with_user_verification_signature(
2703                    KeyDistributionTestData::me_id(),
2704                    &KeyDistributionTestData::me_private_user_signing_key(),
2705                )
2706                .build_response();
2707            machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
2708
2709            // He then changes identity, and adds a dehydrated device (signed
2710            // with his new identity)
2711            let keys_query = key_query_response_template_with_changed_cross_signing(bob_user_id)
2712                .with_dehydrated_device(bob_dehydrated_device_id, true)
2713                .build_response();
2714            allow_duplicates! {
2715                with_settings!({ sort_maps => true, prepend_module_to_snapshot => false }, {
2716                    assert_json_snapshot!(ruma_response_to_json(keys_query.clone()));
2717                });
2718            }
2719            machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
2720
2721            machine
2722        }
2723
2724        /// Create a test megolm session and prepare to share it with the given
2725        /// users, using the given sharing strategy.
2726        async fn share_test_session_and_collect_recipients(
2727            machine: &OlmMachine,
2728            target_user_id: &UserId,
2729            encryption_settings: &EncryptionSettings,
2730        ) -> CollectRecipientsResult {
2731            let group_session = create_test_outbound_group_session(machine, encryption_settings);
2732            collect_session_recipients(
2733                machine.store(),
2734                iter::once(target_user_id),
2735                encryption_settings,
2736                &group_session,
2737            )
2738            .await
2739            .unwrap()
2740        }
2741
2742        /// Assert that the session is shared with the given devices, and that
2743        /// there are no "withheld" messages
2744        fn assert_shared_with(
2745            recips: CollectRecipientsResult,
2746            user_id: &UserId,
2747            device_ids: HashSet<&DeviceId>,
2748        ) {
2749            let bob_devices_shared: HashSet<_> = recips
2750                .devices
2751                .get(user_id)
2752                .unwrap_or_else(|| panic!("session not shared with {user_id}"))
2753                .iter()
2754                .map(|d| d.device_id())
2755                .collect();
2756            assert_eq!(bob_devices_shared, device_ids);
2757
2758            assert!(recips.withheld_devices.is_empty(), "Unexpected withheld messages");
2759        }
2760
2761        /// Assert that the session is not shared with any devices, and that
2762        /// there is a withheld code for the given device.
2763        fn assert_withheld_to(
2764            recips: CollectRecipientsResult,
2765            bob_user_id: &UserId,
2766            bob_dehydrated_device_id: &DeviceId,
2767        ) {
2768            // The share list should be empty
2769            for (user, device_list) in recips.devices {
2770                assert_eq!(device_list.len(), 0, "session unexpectedly shared with {user}");
2771            }
2772
2773            // ... and there should be one withheld message
2774            assert_eq!(recips.withheld_devices.len(), 1);
2775            assert_eq!(recips.withheld_devices[0].0.user_id(), bob_user_id);
2776            assert_eq!(recips.withheld_devices[0].0.device_id(), bob_dehydrated_device_id);
2777            assert_eq!(recips.withheld_devices[0].1, WithheldCode::Unverified);
2778        }
2779
2780        /// Start a [`KeysQueryResponseTemplate`] for the given user, with
2781        /// cross-signing keys.
2782        fn key_query_response_template_with_cross_signing(
2783            user_id: &UserId,
2784        ) -> KeyQueryResponseTemplate {
2785            KeyQueryResponseTemplate::new(user_id.to_owned()).with_cross_signing_keys(
2786                Ed25519SecretKey::from_slice(b"master12master12master12master12"),
2787                Ed25519SecretKey::from_slice(b"self1234self1234self1234self1234"),
2788                Ed25519SecretKey::from_slice(b"user1234user1234user1234user1234"),
2789            )
2790        }
2791
2792        /// Start a [`KeysQueryResponseTemplate`] for the given user, with
2793        /// _different_ cross signing key to
2794        /// [`key_query_response_template_with_cross_signing`].
2795        fn key_query_response_template_with_changed_cross_signing(
2796            bob_user_id: &UserId,
2797        ) -> KeyQueryResponseTemplate {
2798            KeyQueryResponseTemplate::new(bob_user_id.to_owned()).with_cross_signing_keys(
2799                Ed25519SecretKey::from_slice(b"newmaster__newmaster__newmaster_"),
2800                Ed25519SecretKey::from_slice(b"self1234self1234self1234self1234"),
2801                Ed25519SecretKey::from_slice(b"user1234user1234user1234user1234"),
2802            )
2803        }
2804
2805        trait KeyQueryResponseTemplateExt {
2806            fn with_dehydrated_device(
2807                self,
2808                device_id: &DeviceId,
2809                verified: bool,
2810            ) -> KeyQueryResponseTemplate;
2811        }
2812
2813        impl KeyQueryResponseTemplateExt for KeyQueryResponseTemplate {
2814            /// Add a dehydrated device to the KeyQueryResponseTemplate
2815            fn with_dehydrated_device(
2816                self,
2817                device_id: &DeviceId,
2818                verified: bool,
2819            ) -> KeyQueryResponseTemplate {
2820                self.with_device(
2821                    device_id,
2822                    &Curve25519PublicKey::from(b"curvepubcurvepubcurvepubcurvepub".to_owned()),
2823                    &Ed25519SecretKey::from_slice(b"device12device12device12device12"),
2824                    KeyQueryResponseTemplateDeviceOptions::new()
2825                        .dehydrated(true)
2826                        .verified(verified),
2827                )
2828            }
2829        }
2830    }
2831
2832    #[async_test]
2833    async fn test_share_with_identity_strategy() {
2834        let machine = test_machine().await;
2835        import_known_users_to_test_machine(&machine).await;
2836
2837        let encryption_settings = identity_based_strategy_settings();
2838
2839        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
2840
2841        let share_result = collect_session_recipients(
2842            machine.store(),
2843            vec![
2844                KeyDistributionTestData::dan_id(),
2845                KeyDistributionTestData::dave_id(),
2846                KeyDistributionTestData::good_id(),
2847            ]
2848            .into_iter(),
2849            &encryption_settings,
2850            &group_session,
2851        )
2852        .await
2853        .unwrap();
2854
2855        assert!(!share_result.should_rotate);
2856
2857        let dave_devices_shared = share_result.devices.get(KeyDistributionTestData::dave_id());
2858        let good_devices_shared = share_result.devices.get(KeyDistributionTestData::good_id());
2859        // dave has no published identity so will not receive the key
2860        assert!(dave_devices_shared.unwrap().is_empty());
2861
2862        // @good has properly signed his devices, he should get the keys
2863        assert_eq!(good_devices_shared.unwrap().len(), 2);
2864
2865        // dan has one of his devices self signed, so should get the key
2866        let dan_devices_shared =
2867            share_result.devices.get(KeyDistributionTestData::dan_id()).unwrap();
2868
2869        assert_eq!(dan_devices_shared.len(), 1);
2870        let dan_device_that_will_get_the_key = &dan_devices_shared[0];
2871        assert_eq!(
2872            dan_device_that_will_get_the_key.device_id().as_str(),
2873            KeyDistributionTestData::dan_signed_device_id()
2874        );
2875
2876        // Check withhelds for others
2877        let (_, code) = share_result
2878            .withheld_devices
2879            .iter()
2880            .find(|(d, _)| d.device_id() == KeyDistributionTestData::dan_unsigned_device_id())
2881            .expect("This dan's device should receive a withheld code");
2882
2883        assert_eq!(code, &WithheldCode::Unverified);
2884
2885        // Check withhelds for others
2886        let (_, code) = share_result
2887            .withheld_devices
2888            .iter()
2889            .find(|(d, _)| d.device_id() == KeyDistributionTestData::dave_device_id())
2890            .expect("This dave device should receive a withheld code");
2891
2892        assert_eq!(code, &WithheldCode::Unverified);
2893    }
2894
2895    /// Test key sharing with the identity-based strategy with different states
2896    /// of our own verification.
2897    #[async_test]
2898    async fn test_share_identity_strategy_no_cross_signing() {
2899        // Starting off, we have not yet set up our own cross-signing, so
2900        // sharing with the identity-based strategy should fail.
2901        let machine: OlmMachine = OlmMachine::new(
2902            KeyDistributionTestData::me_id(),
2903            KeyDistributionTestData::me_device_id(),
2904        )
2905        .await;
2906
2907        let keys_query = KeyDistributionTestData::dan_keys_query_response();
2908        machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
2909
2910        let fake_room_id = room_id!("!roomid:localhost");
2911
2912        let encryption_settings = identity_based_strategy_settings();
2913
2914        let request_result = machine
2915            .share_room_key(
2916                fake_room_id,
2917                iter::once(KeyDistributionTestData::dan_id()),
2918                encryption_settings.clone(),
2919            )
2920            .await;
2921
2922        assert_matches!(
2923            request_result,
2924            Err(OlmError::SessionRecipientCollectionError(
2925                SessionRecipientCollectionError::CrossSigningNotSetup
2926            ))
2927        );
2928
2929        // We now get our public cross-signing keys, but we don't trust them
2930        // yet. In this case, sharing the keys should still fail since our own
2931        // device is still unverified.
2932        let keys_query = KeyDistributionTestData::me_keys_query_response();
2933        machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
2934
2935        let request_result = machine
2936            .share_room_key(
2937                fake_room_id,
2938                iter::once(KeyDistributionTestData::dan_id()),
2939                encryption_settings.clone(),
2940            )
2941            .await;
2942
2943        assert_matches!(
2944            request_result,
2945            Err(OlmError::SessionRecipientCollectionError(
2946                SessionRecipientCollectionError::SendingFromUnverifiedDevice
2947            ))
2948        );
2949
2950        // Finally, after we trust our own cross-signing keys, key sharing
2951        // should succeed.
2952        machine
2953            .import_cross_signing_keys(CrossSigningKeyExport {
2954                master_key: KeyDistributionTestData::MASTER_KEY_PRIVATE_EXPORT.to_owned().into(),
2955                self_signing_key: KeyDistributionTestData::SELF_SIGNING_KEY_PRIVATE_EXPORT
2956                    .to_owned()
2957                    .into(),
2958                user_signing_key: KeyDistributionTestData::USER_SIGNING_KEY_PRIVATE_EXPORT
2959                    .to_owned()
2960                    .into(),
2961            })
2962            .await
2963            .unwrap();
2964
2965        let requests = machine
2966            .share_room_key(
2967                fake_room_id,
2968                iter::once(KeyDistributionTestData::dan_id()),
2969                encryption_settings.clone(),
2970            )
2971            .await
2972            .unwrap();
2973
2974        // Dan has two devices, but only one is cross-signed, so there should
2975        // only be one key share.
2976        assert_eq!(requests.len(), 1);
2977    }
2978
2979    /// Test that identity-based key sharing gives an error when a verified user
2980    /// changes their identity, and that the key can be shared when the identity
2981    /// change is resolved.
2982    #[async_test]
2983    async fn test_share_identity_strategy_report_verification_violation() {
2984        let machine: OlmMachine = OlmMachine::new(
2985            KeyDistributionTestData::me_id(),
2986            KeyDistributionTestData::me_device_id(),
2987        )
2988        .await;
2989
2990        machine.bootstrap_cross_signing(false).await.unwrap();
2991
2992        // We will try sending a key to two different users.
2993        let user1 = IdentityChangeDataSet::user_id();
2994        let user2 = MaloIdentityChangeDataSet::user_id();
2995
2996        // We first get both users' initial device and identity keys.
2997        let keys_query = IdentityChangeDataSet::key_query_with_identity_a();
2998        machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
2999
3000        let keys_query = MaloIdentityChangeDataSet::initial_key_query();
3001        machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
3002
3003        // And then we get both user' changed identity keys. We simulate a
3004        // verification violation by marking both users as having been
3005        // previously verified, in which case the key sharing should fail.
3006        let keys_query = IdentityChangeDataSet::key_query_with_identity_b();
3007        machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
3008        machine
3009            .get_identity(user1, None)
3010            .await
3011            .unwrap()
3012            .unwrap()
3013            .other()
3014            .unwrap()
3015            .mark_as_previously_verified()
3016            .await
3017            .unwrap();
3018
3019        let keys_query = MaloIdentityChangeDataSet::updated_key_query();
3020        machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
3021        machine
3022            .get_identity(user2, None)
3023            .await
3024            .unwrap()
3025            .unwrap()
3026            .other()
3027            .unwrap()
3028            .mark_as_previously_verified()
3029            .await
3030            .unwrap();
3031
3032        let fake_room_id = room_id!("!roomid:localhost");
3033
3034        // We share the key using the identity-based strategy.
3035        let encryption_settings = identity_based_strategy_settings();
3036
3037        let request_result = machine
3038            .share_room_key(
3039                fake_room_id,
3040                vec![user1, user2].into_iter(),
3041                encryption_settings.clone(),
3042            )
3043            .await;
3044
3045        // The key share should fail with an error indicating that recipients
3046        // were previously verified.
3047        assert_let!(
3048            Err(OlmError::SessionRecipientCollectionError(
3049                SessionRecipientCollectionError::VerifiedUserChangedIdentity(affected_users)
3050            )) = request_result
3051        );
3052        // Both our recipients should be in `affected_users`.
3053        assert_eq!(2, affected_users.len());
3054
3055        // We resolve this for user1 by withdrawing their verification.
3056        machine
3057            .get_identity(user1, None)
3058            .await
3059            .unwrap()
3060            .unwrap()
3061            .withdraw_verification()
3062            .await
3063            .unwrap();
3064
3065        // We resolve this for user2 by re-verifying.
3066        let verification_request = machine
3067            .get_identity(user2, None)
3068            .await
3069            .unwrap()
3070            .unwrap()
3071            .other()
3072            .unwrap()
3073            .verify()
3074            .await
3075            .unwrap();
3076
3077        let master_key =
3078            &machine.get_identity(user2, None).await.unwrap().unwrap().other().unwrap().master_key;
3079
3080        let my_identity = machine
3081            .get_identity(KeyDistributionTestData::me_id(), None)
3082            .await
3083            .expect("Should not fail to find own identity")
3084            .expect("Our own identity should not be missing")
3085            .own()
3086            .expect("Our own identity should be of type Own");
3087
3088        let msk = json!({ user2: serde_json::to_value(master_key).expect("Should not fail to serialize")});
3089        let ssk =
3090            serde_json::to_value(&MaloIdentityChangeDataSet::updated_key_query().self_signing_keys)
3091                .expect("Should not fail to serialize");
3092
3093        let kq_response = simulate_key_query_response_for_verification(
3094            verification_request,
3095            my_identity,
3096            KeyDistributionTestData::me_id(),
3097            user2,
3098            msk,
3099            ssk,
3100        );
3101
3102        machine
3103            .mark_request_as_sent(
3104                &TransactionId::new(),
3105                crate::types::requests::AnyIncomingResponse::KeysQuery(&kq_response),
3106            )
3107            .await
3108            .unwrap();
3109
3110        assert!(machine.get_identity(user2, None).await.unwrap().unwrap().is_verified());
3111
3112        // And now the key share should succeed.
3113        machine
3114            .share_room_key(
3115                fake_room_id,
3116                vec![user1, user2].into_iter(),
3117                encryption_settings.clone(),
3118            )
3119            .await
3120            .unwrap();
3121    }
3122
3123    #[async_test]
3124    async fn test_should_rotate_based_on_visibility() {
3125        let machine = test_machine().await;
3126        import_known_users_to_test_machine(&machine).await;
3127
3128        let strategy = CollectStrategy::AllDevices;
3129
3130        let encryption_settings = EncryptionSettings {
3131            sharing_strategy: strategy.clone(),
3132            history_visibility: HistoryVisibility::Invited,
3133            ..Default::default()
3134        };
3135
3136        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
3137
3138        let _ = collect_session_recipients(
3139            machine.store(),
3140            vec![KeyDistributionTestData::dan_id()].into_iter(),
3141            &encryption_settings,
3142            &group_session,
3143        )
3144        .await
3145        .unwrap();
3146
3147        // Try to share again with updated history visibility
3148        let encryption_settings = EncryptionSettings {
3149            sharing_strategy: strategy.clone(),
3150            history_visibility: HistoryVisibility::Shared,
3151            ..Default::default()
3152        };
3153
3154        let share_result = collect_session_recipients(
3155            machine.store(),
3156            vec![KeyDistributionTestData::dan_id()].into_iter(),
3157            &encryption_settings,
3158            &group_session,
3159        )
3160        .await
3161        .unwrap();
3162
3163        assert!(share_result.should_rotate);
3164    }
3165
3166    /// Test that the session is rotated when a device is removed from the
3167    /// recipients. In that case we simulate that dan has logged out one of his
3168    /// devices.
3169    #[async_test]
3170    async fn test_should_rotate_based_on_device_excluded() {
3171        let machine = test_machine().await;
3172        import_known_users_to_test_machine(&machine).await;
3173
3174        let encryption_settings = all_devices_strategy_settings();
3175        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
3176        let sender_key = machine.identity_keys().curve25519;
3177
3178        group_session
3179            .mark_shared_with(
3180                KeyDistributionTestData::dan_id(),
3181                KeyDistributionTestData::dan_signed_device_id(),
3182                sender_key,
3183            )
3184            .await;
3185        group_session
3186            .mark_shared_with(
3187                KeyDistributionTestData::dan_id(),
3188                KeyDistributionTestData::dan_unsigned_device_id(),
3189                sender_key,
3190            )
3191            .await;
3192
3193        // Try to share again after dan has removed one of his devices
3194        let keys_query = KeyDistributionTestData::dan_keys_query_response_device_loggedout();
3195        machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
3196
3197        // share again
3198        let share_result = collect_session_recipients(
3199            machine.store(),
3200            vec![KeyDistributionTestData::dan_id()].into_iter(),
3201            &encryption_settings,
3202            &group_session,
3203        )
3204        .await
3205        .unwrap();
3206
3207        assert!(share_result.should_rotate);
3208    }
3209
3210    /// Test that the session is rotated if a devices has a pending to-device
3211    /// request that would share the keys with it.
3212    #[async_test]
3213    async fn test_should_rotate_based_on_device_with_pending_request_excluded() {
3214        let machine = test_machine().await;
3215        import_known_users_to_test_machine(&machine).await;
3216
3217        let encryption_settings = all_devices_strategy_settings();
3218        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
3219        let sender_key = machine.identity_keys().curve25519;
3220
3221        let dan_user = KeyDistributionTestData::dan_id();
3222        let dan_dev1 = KeyDistributionTestData::dan_signed_device_id();
3223        let dan_dev2 = KeyDistributionTestData::dan_unsigned_device_id();
3224
3225        // Share the session with device 1
3226        group_session.mark_shared_with(dan_user, dan_dev1, sender_key).await;
3227
3228        {
3229            // Add a pending request to share with device 2
3230            let share_infos = BTreeMap::from([(
3231                dan_user.to_owned(),
3232                BTreeMap::from([(
3233                    dan_dev2.to_owned(),
3234                    ShareInfo::new_shared(sender_key, 0, SequenceNumber::default()),
3235                )]),
3236            )]);
3237
3238            let txid = TransactionId::new();
3239            let req = Arc::new(ToDeviceRequest::for_recipients(
3240                dan_user,
3241                vec![dan_dev2.to_owned()],
3242                &ruma::events::AnyToDeviceEventContent::Dummy(ToDeviceDummyEventContent),
3243                txid.clone(),
3244            ));
3245            group_session.add_request(txid, req, share_infos);
3246        }
3247
3248        // Remove device 2
3249        let keys_query = KeyDistributionTestData::dan_keys_query_response_device_loggedout();
3250        machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
3251
3252        // Share again
3253        let share_result = collect_session_recipients(
3254            machine.store(),
3255            vec![KeyDistributionTestData::dan_id()].into_iter(),
3256            &encryption_settings,
3257            &group_session,
3258        )
3259        .await
3260        .unwrap();
3261
3262        assert!(share_result.should_rotate);
3263    }
3264
3265    /// Test that the session is not rotated if a devices is removed but was
3266    /// already withheld from receiving the session.
3267    #[async_test]
3268    async fn test_should_not_rotate_if_keys_were_withheld() {
3269        let machine = test_machine().await;
3270        import_known_users_to_test_machine(&machine).await;
3271
3272        let encryption_settings = all_devices_strategy_settings();
3273        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
3274        let fake_room_id = group_session.room_id();
3275
3276        // Because we don't have Olm sessions initialized, this will contain
3277        // withheld requests for both of Dan's devices
3278        let requests = machine
3279            .share_room_key(
3280                fake_room_id,
3281                vec![KeyDistributionTestData::dan_id()].into_iter(),
3282                encryption_settings.clone(),
3283            )
3284            .await
3285            .unwrap();
3286
3287        for r in requests {
3288            machine
3289                .inner
3290                .group_session_manager
3291                .mark_request_as_sent(r.as_ref().txn_id.as_ref())
3292                .await
3293                .unwrap();
3294        }
3295
3296        // Try to share again after dan has removed one of his devices
3297        let keys_query = KeyDistributionTestData::dan_keys_query_response_device_loggedout();
3298        machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
3299
3300        // share again
3301        let share_result = collect_session_recipients(
3302            machine.store(),
3303            vec![KeyDistributionTestData::dan_id()].into_iter(),
3304            &encryption_settings,
3305            &group_session,
3306        )
3307        .await
3308        .unwrap();
3309
3310        assert!(!share_result.should_rotate);
3311    }
3312
3313    /// Common setup for tests which require a verified user to have unsigned
3314    /// devices.
3315    ///
3316    /// Returns an `OlmMachine` which is properly configured with trusted
3317    /// cross-signing keys. Also imports a set of keys for Bob
3318    /// ([`VerificationViolationTestData::bob_id`]), where Bob is verified and
3319    /// has 2 devices, one signed and the other not.
3320    async fn unsigned_of_verified_setup() -> OlmMachine {
3321        use test_json::keys_query_sets::VerificationViolationTestData as DataSet;
3322
3323        let machine = OlmMachine::new(DataSet::own_id(), device_id!("LOCAL")).await;
3324
3325        // Tell the OlmMachine about our own public keys.
3326        let own_keys = DataSet::own_keys_query_response_1();
3327        machine.mark_request_as_sent(&TransactionId::new(), &own_keys).await.unwrap();
3328
3329        // Import the secret parts of our own cross-signing keys.
3330        machine
3331            .import_cross_signing_keys(CrossSigningKeyExport {
3332                master_key: DataSet::MASTER_KEY_PRIVATE_EXPORT.to_owned().into(),
3333                self_signing_key: DataSet::SELF_SIGNING_KEY_PRIVATE_EXPORT.to_owned().into(),
3334                user_signing_key: DataSet::USER_SIGNING_KEY_PRIVATE_EXPORT.to_owned().into(),
3335            })
3336            .await
3337            .unwrap();
3338
3339        // Tell the OlmMachine about Bob's keys.
3340        let bob_keys = DataSet::bob_keys_query_response_signed();
3341        machine.mark_request_as_sent(&TransactionId::new(), &bob_keys).await.unwrap();
3342
3343        // Double-check the state of Bob: he should be verified, and should have
3344        // one signed and one unsigned device.
3345        let bob_identity = machine.get_identity(DataSet::bob_id(), None).await.unwrap().unwrap();
3346        assert!(bob_identity.other().unwrap().is_verified());
3347
3348        let bob_signed_device = machine
3349            .get_device(DataSet::bob_id(), DataSet::bob_device_1_id(), None)
3350            .await
3351            .unwrap()
3352            .unwrap();
3353        assert!(bob_signed_device.is_verified());
3354        assert!(bob_signed_device.device_owner_identity.is_some());
3355
3356        let bob_unsigned_device = machine
3357            .get_device(DataSet::bob_id(), DataSet::bob_device_2_id(), None)
3358            .await
3359            .unwrap()
3360            .unwrap();
3361        assert!(!bob_unsigned_device.is_verified());
3362
3363        machine
3364    }
3365
3366    /// [`EncryptionSettings`] with [`CollectStrategy::AllDevices`]
3367    fn all_devices_strategy_settings() -> EncryptionSettings {
3368        EncryptionSettings { sharing_strategy: CollectStrategy::AllDevices, ..Default::default() }
3369    }
3370
3371    /// [`EncryptionSettings`] with
3372    /// [`CollectStrategy::ErrorOnVerifiedUserProblem`]
3373    fn error_on_verification_problem_encryption_settings() -> EncryptionSettings {
3374        EncryptionSettings {
3375            sharing_strategy: CollectStrategy::ErrorOnVerifiedUserProblem,
3376            ..Default::default()
3377        }
3378    }
3379
3380    /// [`EncryptionSettings`] with [`CollectStrategy::IdentityBasedStrategy`]
3381    fn identity_based_strategy_settings() -> EncryptionSettings {
3382        EncryptionSettings {
3383            sharing_strategy: CollectStrategy::IdentityBasedStrategy,
3384            ..Default::default()
3385        }
3386    }
3387
3388    /// Create an [`OutboundGroupSession`], backed by the given olm machine,
3389    /// without sharing it.
3390    fn create_test_outbound_group_session(
3391        machine: &OlmMachine,
3392        encryption_settings: &EncryptionSettings,
3393    ) -> OutboundGroupSession {
3394        OutboundGroupSession::new(
3395            machine.device_id().into(),
3396            Arc::new(machine.identity_keys()),
3397            room_id!("!roomid:localhost"),
3398            encryption_settings.clone(),
3399        )
3400        .expect("creating an outbound group session should not fail")
3401    }
3402}