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