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;
27use crate::{
28    error::{OlmResult, SessionRecipientCollectionError},
29    olm::ShareInfo,
30    store::Store,
31    DeviceData, EncryptionSettings, LocalTrust, OlmError, OwnUserIdentityData, UserIdentityData,
32};
33#[cfg(doc)]
34use crate::{Device, UserIdentity};
35
36/// Strategy to collect the devices that should receive room keys for the
37/// current discussion.
38#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
39#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
40#[serde(from = "CollectStrategyDeserializationHelper")]
41pub enum CollectStrategy {
42    /// Share with all (unblacklisted) devices.
43    #[default]
44    AllDevices,
45
46    /// Share with all devices, except errors for *verified* users cause sharing
47    /// to fail with an error.
48    ///
49    /// In this strategy, if a verified user has an unsigned device,
50    /// key sharing will fail with a
51    /// [`SessionRecipientCollectionError::VerifiedUserHasUnsignedDevice`].
52    /// If a verified user has replaced their identity, key
53    /// sharing will fail with a
54    /// [`SessionRecipientCollectionError::VerifiedUserChangedIdentity`].
55    ///
56    /// Otherwise, keys are shared with unsigned devices as normal.
57    ///
58    /// Once the problematic devices are blacklisted or whitelisted the
59    /// caller can retry to share a second time.
60    ErrorOnVerifiedUserProblem,
61
62    /// Share based on identity. Only distribute to devices signed by their
63    /// owner. If a user has no published identity he will not receive
64    /// any room keys.
65    IdentityBasedStrategy,
66
67    /// Only share keys with devices that we "trust". A device is trusted if any
68    /// of the following is true:
69    ///     - It was manually marked as trusted.
70    ///     - It was marked as verified via interactive verification.
71    ///     - It is signed by its owner identity, and this identity has been
72    ///       trusted via interactive verification.
73    ///     - It is the current own device of the user.
74    OnlyTrustedDevices,
75}
76
77impl CollectStrategy {
78    /// Creates an identity based strategy
79    pub const fn new_identity_based() -> Self {
80        CollectStrategy::IdentityBasedStrategy
81    }
82}
83
84/// Deserialization helper for [`CollectStrategy`].
85#[derive(Deserialize)]
86enum CollectStrategyDeserializationHelper {
87    /// `AllDevices`, `ErrorOnVerifiedUserProblem` and `OnlyTrustedDevices` used
88    /// to be implemented as a single strategy with flags.
89    DeviceBasedStrategy {
90        #[serde(default)]
91        error_on_verified_user_problem: bool,
92
93        #[serde(default)]
94        only_allow_trusted_devices: bool,
95    },
96
97    AllDevices,
98    ErrorOnVerifiedUserProblem,
99    IdentityBasedStrategy,
100    OnlyTrustedDevices,
101}
102
103impl From<CollectStrategyDeserializationHelper> for CollectStrategy {
104    fn from(value: CollectStrategyDeserializationHelper) -> Self {
105        use CollectStrategyDeserializationHelper::*;
106
107        match value {
108            DeviceBasedStrategy {
109                only_allow_trusted_devices: true,
110                error_on_verified_user_problem: _,
111            } => CollectStrategy::OnlyTrustedDevices,
112            DeviceBasedStrategy {
113                only_allow_trusted_devices: false,
114                error_on_verified_user_problem: true,
115            } => CollectStrategy::ErrorOnVerifiedUserProblem,
116            DeviceBasedStrategy {
117                only_allow_trusted_devices: false,
118                error_on_verified_user_problem: false,
119            } => CollectStrategy::AllDevices,
120
121            AllDevices => CollectStrategy::AllDevices,
122            ErrorOnVerifiedUserProblem => CollectStrategy::ErrorOnVerifiedUserProblem,
123            IdentityBasedStrategy => CollectStrategy::IdentityBasedStrategy,
124            OnlyTrustedDevices => CollectStrategy::OnlyTrustedDevices,
125        }
126    }
127}
128
129/// Returned by `collect_session_recipients`.
130///
131/// Information indicating whether the session needs to be rotated
132/// (`should_rotate`) and the list of users/devices that should receive
133/// (`devices`) or not the session,  including withheld reason
134/// `withheld_devices`.
135#[derive(Debug, Default)]
136pub(crate) struct CollectRecipientsResult {
137    /// If true the outbound group session should be rotated
138    pub should_rotate: bool,
139    /// The map of user|device that should receive the session
140    pub devices: BTreeMap<OwnedUserId, Vec<DeviceData>>,
141    /// The map of user|device that won't receive the key with the withheld
142    /// code.
143    pub withheld_devices: Vec<(DeviceData, WithheldCode)>,
144}
145
146/// Given a list of user and an outbound session, return the list of users
147/// and their devices that this session should be shared with.
148///
149/// Returns information indicating whether the session needs to be rotated
150/// and the list of users/devices that should receive or not the session
151/// (with withheld reason).
152#[instrument(skip_all)]
153pub(crate) async fn collect_session_recipients(
154    store: &Store,
155    users: impl Iterator<Item = &UserId>,
156    settings: &EncryptionSettings,
157    outbound: &OutboundGroupSession,
158) -> OlmResult<CollectRecipientsResult> {
159    let mut result = collect_recipients_for_share_strategy(
160        store,
161        users,
162        &settings.sharing_strategy,
163        Some(outbound),
164    )
165    .await?;
166
167    // To protect the room history we need to rotate the session if either:
168    //
169    // 1. Any user left the room.
170    // 2. Any of the users' devices got deleted or blacklisted.
171    // 3. The history visibility changed.
172    // 4. The encryption algorithm changed.
173    //
174    // `result.should_rotate` is true if the first or second in that list is true;
175    // we now need to check for the other two.
176    let device_removed = result.should_rotate;
177
178    let visibility_changed = outbound.settings().history_visibility != settings.history_visibility;
179    let algorithm_changed = outbound.settings().algorithm != settings.algorithm;
180
181    result.should_rotate = device_removed || visibility_changed || algorithm_changed;
182
183    if result.should_rotate {
184        debug!(
185            device_removed,
186            visibility_changed, algorithm_changed, "Rotating room key to protect room history",
187        );
188    }
189
190    Ok(result)
191}
192
193/// Given a list of users and a [`CollectStrategy`], return the list of devices
194/// that cryptographic keys should be shared with, or that withheld notices
195/// should be sent to.
196///
197/// If an existing [`OutboundGroupSession`] is provided, will also check the
198/// list of devices that the session has been *previously* shared with, and
199/// if that list is too broad, returns a flag indicating that the session should
200/// be rotated (e.g., because a device has been deleted or a user has left the
201/// chat).
202pub(crate) async fn collect_recipients_for_share_strategy(
203    store: &Store,
204    users: impl Iterator<Item = &UserId>,
205    share_strategy: &CollectStrategy,
206    outbound: Option<&OutboundGroupSession>,
207) -> OlmResult<CollectRecipientsResult> {
208    let users: BTreeSet<&UserId> = users.collect();
209    trace!(?users, ?share_strategy, "Calculating group session recipients");
210
211    let mut result = CollectRecipientsResult::default();
212    let mut verified_users_with_new_identities: Vec<OwnedUserId> = Default::default();
213
214    // If we have an outbound session, check if a user is missing from the set of
215    // users that should get the session but is in the set of users that
216    // received the session.
217    if let Some(outbound) = outbound {
218        let view = outbound.sharing_view();
219        let users_shared_with = view.shared_with_users().collect::<BTreeSet<_>>();
220        let left_users = users_shared_with.difference(&users).collect::<BTreeSet<_>>();
221        if !left_users.is_empty() {
222            trace!(?left_users, "Some users have left the chat: session must be rotated");
223            result.should_rotate = true;
224        }
225    }
226
227    let own_identity = store.get_user_identity(store.user_id()).await?.and_then(|i| i.into_own());
228
229    // Get the recipient and withheld devices, based on the collection strategy.
230    match share_strategy {
231        CollectStrategy::AllDevices => {
232            for user_id in users {
233                trace!(?user_id, "CollectStrategy::AllDevices: Considering recipient devices",);
234                let user_devices = store.get_device_data_for_user_filtered(user_id).await?;
235                let device_owner_identity = store.get_user_identity(user_id).await?;
236
237                let recipient_devices = split_devices_for_user_for_all_devices_strategy(
238                    user_devices,
239                    &own_identity,
240                    &device_owner_identity,
241                );
242                update_recipients_for_user(&mut result, outbound, user_id, recipient_devices);
243            }
244        }
245        CollectStrategy::ErrorOnVerifiedUserProblem => {
246            let mut unsigned_devices_of_verified_users: BTreeMap<OwnedUserId, Vec<OwnedDeviceId>> =
247                Default::default();
248
249            for user_id in users {
250                trace!(
251                    ?user_id,
252                    "CollectStrategy::ErrorOnVerifiedUserProblem: Considering recipient devices"
253                );
254                let user_devices = store.get_device_data_for_user_filtered(user_id).await?;
255
256                let device_owner_identity = store.get_user_identity(user_id).await?;
257
258                if has_identity_verification_violation(
259                    own_identity.as_ref(),
260                    device_owner_identity.as_ref(),
261                ) {
262                    verified_users_with_new_identities.push(user_id.to_owned());
263                    // No point considering the individual devices of this user.
264                    continue;
265                }
266
267                let recipient_devices =
268                    split_devices_for_user_for_error_on_verified_user_problem_strategy(
269                        user_devices,
270                        &own_identity,
271                        &device_owner_identity,
272                    );
273
274                match recipient_devices {
275                    ErrorOnVerifiedUserProblemResult::UnsignedDevicesOfVerifiedUser(devices) => {
276                        unsigned_devices_of_verified_users.insert(user_id.to_owned(), devices);
277                    }
278                    ErrorOnVerifiedUserProblemResult::Devices(recipient_devices) => {
279                        update_recipients_for_user(
280                            &mut result,
281                            outbound,
282                            user_id,
283                            recipient_devices,
284                        );
285                    }
286                }
287            }
288
289            // If `error_on_verified_user_problem` is set, then
290            // `unsigned_devices_of_verified_users` may be populated. If so, we need to bail
291            // out with an error.
292            if !unsigned_devices_of_verified_users.is_empty() {
293                return Err(OlmError::SessionRecipientCollectionError(
294                    SessionRecipientCollectionError::VerifiedUserHasUnsignedDevice(
295                        unsigned_devices_of_verified_users,
296                    ),
297                ));
298            }
299        }
300        CollectStrategy::IdentityBasedStrategy => {
301            // We require our own cross-signing to be properly set up for the
302            // identity-based strategy, so return an error if it isn't.
303            match &own_identity {
304                None => {
305                    return Err(OlmError::SessionRecipientCollectionError(
306                        SessionRecipientCollectionError::CrossSigningNotSetup,
307                    ))
308                }
309                Some(identity) if !identity.is_verified() => {
310                    return Err(OlmError::SessionRecipientCollectionError(
311                        SessionRecipientCollectionError::SendingFromUnverifiedDevice,
312                    ))
313                }
314                Some(_) => (),
315            }
316
317            for user_id in users {
318                trace!(
319                    ?user_id,
320                    "CollectStrategy::IdentityBasedStrategy: Considering recipient devices"
321                );
322                let user_devices = store.get_device_data_for_user_filtered(user_id).await?;
323
324                let device_owner_identity = store.get_user_identity(user_id).await?;
325
326                if has_identity_verification_violation(
327                    own_identity.as_ref(),
328                    device_owner_identity.as_ref(),
329                ) {
330                    verified_users_with_new_identities.push(user_id.to_owned());
331                    // No point considering the individual devices of this user.
332                    continue;
333                }
334
335                let recipient_devices = split_devices_for_user_for_identity_based_strategy(
336                    user_devices,
337                    &device_owner_identity,
338                );
339
340                update_recipients_for_user(&mut result, outbound, user_id, recipient_devices);
341            }
342        }
343
344        CollectStrategy::OnlyTrustedDevices => {
345            for user_id in users {
346                trace!(
347                    ?user_id,
348                    "CollectStrategy::OnlyTrustedDevices: Considering recipient devices"
349                );
350                let user_devices = store.get_device_data_for_user_filtered(user_id).await?;
351                let device_owner_identity = store.get_user_identity(user_id).await?;
352
353                let recipient_devices = split_devices_for_user_for_only_trusted_devices(
354                    user_devices,
355                    &own_identity,
356                    &device_owner_identity,
357                );
358
359                update_recipients_for_user(&mut result, outbound, user_id, recipient_devices);
360            }
361        }
362    }
363
364    // We may have encountered previously-verified users who have changed their
365    // identities. If so, we bail out with an error.
366    if !verified_users_with_new_identities.is_empty() {
367        return Err(OlmError::SessionRecipientCollectionError(
368            SessionRecipientCollectionError::VerifiedUserChangedIdentity(
369                verified_users_with_new_identities,
370            ),
371        ));
372    }
373
374    trace!(result.should_rotate, "Done calculating group session recipients");
375
376    Ok(result)
377}
378
379/// Update this [`CollectRecipientsResult`] with the device list for a specific
380/// user.
381fn update_recipients_for_user(
382    recipients: &mut CollectRecipientsResult,
383    outbound: Option<&OutboundGroupSession>,
384    user_id: &UserId,
385    recipient_devices: RecipientDevicesForUser,
386) {
387    // If we haven't already concluded that the session should be
388    // rotated for other reasons, we also need to check whether any
389    // of the devices in the session got deleted or blacklisted in the
390    // meantime. If so, we should also rotate the session.
391    if let Some(outbound) = outbound {
392        if !recipients.should_rotate {
393            recipients.should_rotate = is_session_overshared_for_user(
394                outbound,
395                user_id,
396                &recipient_devices.allowed_devices,
397            )
398        }
399    }
400
401    recipients
402        .devices
403        .entry(user_id.to_owned())
404        .or_default()
405        .extend(recipient_devices.allowed_devices);
406    recipients.withheld_devices.extend(recipient_devices.denied_devices_with_code);
407}
408
409/// Check if the session has been shared with a device belonging to the given
410/// user, that is no longer in the pool of devices that should participate in
411/// the discussion.
412///
413/// # Arguments
414///
415/// * `outbound_session` - the outbound group session to check for oversharing.
416/// * `user_id` - the ID of the user we are checking the devices for.
417/// * `recipient_devices` - the list of devices belonging to `user_id` that we
418///   expect to share the session with.
419///
420/// # Returns
421///
422/// `true` if the session has been shared with any devices belonging to
423/// `user_id` that are not in `recipient_devices`. Otherwise, `false`.
424fn is_session_overshared_for_user(
425    outbound_session: &OutboundGroupSession,
426    user_id: &UserId,
427    recipient_devices: &[DeviceData],
428) -> bool {
429    // Device IDs that should receive this session
430    let recipient_device_ids: BTreeSet<&DeviceId> =
431        recipient_devices.iter().map(|d| d.device_id()).collect();
432
433    let view = outbound_session.sharing_view();
434    let newly_deleted_or_blacklisted: BTreeSet<&DeviceId> = view
435        .iter_shares(Some(user_id), None)
436        .filter_map(|(_user_id, device_id, info)| {
437            // If a devices who we've shared the session with before is not in the
438            // list of devices that should receive the session, we need to rotate.
439            // We also collect all of those device IDs to log them out.
440            if matches!(info, ShareInfo::Shared(_)) && !recipient_device_ids.contains(device_id) {
441                Some(device_id)
442            } else {
443                None
444            }
445        })
446        .collect();
447
448    let should_rotate = !newly_deleted_or_blacklisted.is_empty();
449    if should_rotate {
450        debug!(
451            "Rotating a room key due to these devices being deleted/blacklisted {:?}",
452            newly_deleted_or_blacklisted,
453        );
454    }
455    should_rotate
456}
457
458/// Result type for [`split_devices_for_user_for_all_devices_strategy`],
459/// [`split_devices_for_user_for_error_on_verified_user_problem_strategy`],
460/// [`split_devices_for_user_for_identity_based_strategy`],
461/// [`split_devices_for_user_for_only_trusted_devices`].
462///
463/// A partitioning of the devices for a given user.
464#[derive(Default)]
465struct RecipientDevicesForUser {
466    /// Devices that should receive the room key.
467    allowed_devices: Vec<DeviceData>,
468    /// Devices that should receive a withheld code.
469    denied_devices_with_code: Vec<(DeviceData, WithheldCode)>,
470}
471
472/// Result type for
473/// [`split_devices_for_user_for_error_on_verified_user_problem_strategy`].
474enum ErrorOnVerifiedUserProblemResult {
475    /// We found devices that should cause the transmission to fail, due to
476    /// being an unsigned device belonging to a verified user. Only
477    /// populated when `error_on_verified_user_problem` is set.
478    UnsignedDevicesOfVerifiedUser(Vec<OwnedDeviceId>),
479
480    /// There were no unsigned devices of verified users.
481    Devices(RecipientDevicesForUser),
482}
483
484/// Partition the list of a user's devices according to whether they should
485/// receive the key, for [`CollectStrategy::AllDevices`].
486fn split_devices_for_user_for_all_devices_strategy(
487    user_devices: HashMap<OwnedDeviceId, DeviceData>,
488    own_identity: &Option<OwnUserIdentityData>,
489    device_owner_identity: &Option<UserIdentityData>,
490) -> RecipientDevicesForUser {
491    let (left, right) = user_devices.into_values().partition_map(|d| {
492        if d.is_blacklisted() {
493            Either::Right((d, WithheldCode::Blacklisted))
494        } else if d.is_dehydrated()
495            && should_withhold_to_dehydrated_device(
496                &d,
497                own_identity.as_ref(),
498                device_owner_identity.as_ref(),
499            )
500        {
501            Either::Right((d, WithheldCode::Unverified))
502        } else {
503            Either::Left(d)
504        }
505    });
506
507    RecipientDevicesForUser { allowed_devices: left, denied_devices_with_code: right }
508}
509
510/// Helper for [`split_devices_for_user_for_all_devices_strategy`].
511///
512/// Given a dehydrated device `device`, decide if we should withhold the room
513/// key from it.
514///
515/// Dehydrated devices must be signed by their owners (whether or not we have
516/// verified the owner), and, if we previously verified the owner, they must be
517/// verified still (i.e., they must not have a verification violation).
518fn should_withhold_to_dehydrated_device(
519    device: &DeviceData,
520    own_identity: Option<&OwnUserIdentityData>,
521    device_owner_identity: Option<&UserIdentityData>,
522) -> bool {
523    device_owner_identity.is_none_or(|owner_id| {
524        // Dehydrated devices must be signed by their owners
525        !device.is_cross_signed_by_owner(owner_id) ||
526
527        // If the user has changed identity since we verified them, withhold the message
528        (owner_id.was_previously_verified() && !is_user_verified(own_identity, owner_id))
529    })
530}
531
532/// Partition the list of a user's devices according to whether they should
533/// receive the key, for [`CollectStrategy::ErrorOnVerifiedUserProblem`].
534///
535/// This function returns one of two values:
536///
537/// * A list of the devices that should cause the transmission to fail due to
538///   being unsigned. In this case, we don't bother to return the rest of the
539///   devices, because we assume transmission will fail.
540///
541/// * Otherwise, returns a [`RecipientDevicesForUser`] which lists, separately,
542///   the devices that should receive the room key, and those that should
543///   receive a withheld code.
544fn split_devices_for_user_for_error_on_verified_user_problem_strategy(
545    user_devices: HashMap<OwnedDeviceId, DeviceData>,
546    own_identity: &Option<OwnUserIdentityData>,
547    device_owner_identity: &Option<UserIdentityData>,
548) -> ErrorOnVerifiedUserProblemResult {
549    let mut recipient_devices = RecipientDevicesForUser::default();
550
551    // We construct unsigned_devices_of_verified_users lazily, because chances are
552    // we won't need it.
553    let mut unsigned_devices_of_verified_users: Option<Vec<OwnedDeviceId>> = None;
554
555    for d in user_devices.into_values() {
556        match handle_device_for_user_for_error_on_verified_user_problem_strategy(
557            &d,
558            own_identity.as_ref(),
559            device_owner_identity.as_ref(),
560        ) {
561            ErrorOnVerifiedUserProblemDeviceDecision::Ok => {
562                recipient_devices.allowed_devices.push(d)
563            }
564            ErrorOnVerifiedUserProblemDeviceDecision::Withhold(code) => {
565                recipient_devices.denied_devices_with_code.push((d, code))
566            }
567            ErrorOnVerifiedUserProblemDeviceDecision::UnsignedOfVerified => {
568                unsigned_devices_of_verified_users
569                    .get_or_insert_with(Vec::default)
570                    .push(d.device_id().to_owned())
571            }
572        }
573    }
574
575    if let Some(devices) = unsigned_devices_of_verified_users {
576        ErrorOnVerifiedUserProblemResult::UnsignedDevicesOfVerifiedUser(devices)
577    } else {
578        ErrorOnVerifiedUserProblemResult::Devices(recipient_devices)
579    }
580}
581
582/// Result type for
583/// [`handle_device_for_user_for_error_on_verified_user_problem_strategy`].
584enum ErrorOnVerifiedUserProblemDeviceDecision {
585    Ok,
586    Withhold(WithheldCode),
587    UnsignedOfVerified,
588}
589
590fn handle_device_for_user_for_error_on_verified_user_problem_strategy(
591    device: &DeviceData,
592    own_identity: Option<&OwnUserIdentityData>,
593    device_owner_identity: Option<&UserIdentityData>,
594) -> ErrorOnVerifiedUserProblemDeviceDecision {
595    if device.is_blacklisted() {
596        ErrorOnVerifiedUserProblemDeviceDecision::Withhold(WithheldCode::Blacklisted)
597    } else if device.local_trust_state() == LocalTrust::Ignored {
598        // Ignore the trust state of that device and share
599        ErrorOnVerifiedUserProblemDeviceDecision::Ok
600    } else if is_unsigned_device_of_verified_user(own_identity, device_owner_identity, device) {
601        ErrorOnVerifiedUserProblemDeviceDecision::UnsignedOfVerified
602    } else if device.is_dehydrated()
603        && device_owner_identity.is_none_or(|owner_id| {
604            // Dehydrated devices must be signed by their owners, whether or not that
605            // owner is verified
606            !device.is_cross_signed_by_owner(owner_id)
607        })
608    {
609        ErrorOnVerifiedUserProblemDeviceDecision::Withhold(WithheldCode::Unverified)
610    } else {
611        ErrorOnVerifiedUserProblemDeviceDecision::Ok
612    }
613}
614
615fn split_devices_for_user_for_identity_based_strategy(
616    user_devices: HashMap<OwnedDeviceId, DeviceData>,
617    device_owner_identity: &Option<UserIdentityData>,
618) -> RecipientDevicesForUser {
619    match device_owner_identity {
620        None => {
621            // withheld all the users devices, we need to have an identity for this
622            // distribution mode
623            RecipientDevicesForUser {
624                allowed_devices: Vec::default(),
625                denied_devices_with_code: user_devices
626                    .into_values()
627                    .map(|d| (d, WithheldCode::Unverified))
628                    .collect(),
629            }
630        }
631        Some(device_owner_identity) => {
632            // Only accept devices signed by the current identity
633            let (recipients, withheld_recipients): (
634                Vec<DeviceData>,
635                Vec<(DeviceData, WithheldCode)>,
636            ) = user_devices.into_values().partition_map(|d| {
637                if d.is_cross_signed_by_owner(device_owner_identity) {
638                    Either::Left(d)
639                } else {
640                    Either::Right((d, WithheldCode::Unverified))
641                }
642            });
643            RecipientDevicesForUser {
644                allowed_devices: recipients,
645                denied_devices_with_code: withheld_recipients,
646            }
647        }
648    }
649}
650
651/// Partition the list of a user's devices according to whether they should
652/// receive the key, for [`CollectStrategy::OnlyTrustedDevices`].
653fn split_devices_for_user_for_only_trusted_devices(
654    user_devices: HashMap<OwnedDeviceId, DeviceData>,
655    own_identity: &Option<OwnUserIdentityData>,
656    device_owner_identity: &Option<UserIdentityData>,
657) -> RecipientDevicesForUser {
658    let (left, right) = user_devices.into_values().partition_map(|d| {
659        match (
660            d.local_trust_state(),
661            d.is_cross_signing_trusted(own_identity, device_owner_identity),
662        ) {
663            (LocalTrust::BlackListed, _) => Either::Right((d, WithheldCode::Blacklisted)),
664            (LocalTrust::Ignored | LocalTrust::Verified, _) => Either::Left(d),
665            (LocalTrust::Unset, false) => Either::Right((d, WithheldCode::Unverified)),
666            (LocalTrust::Unset, true) => Either::Left(d),
667        }
668    });
669    RecipientDevicesForUser { allowed_devices: left, denied_devices_with_code: right }
670}
671
672fn is_unsigned_device_of_verified_user(
673    own_identity: Option<&OwnUserIdentityData>,
674    device_owner_identity: Option<&UserIdentityData>,
675    device_data: &DeviceData,
676) -> bool {
677    device_owner_identity.is_some_and(|device_owner_identity| {
678        is_user_verified(own_identity, device_owner_identity)
679            && !device_data.is_cross_signed_by_owner(device_owner_identity)
680    })
681}
682
683/// Check if the user was previously verified, but they have now changed their
684/// identity so that they are no longer verified.
685///
686/// This is much the same as [`UserIdentity::has_verification_violation`], but
687/// works with a low-level [`UserIdentityData`] rather than higher-level
688/// [`UserIdentity`].
689fn has_identity_verification_violation(
690    own_identity: Option<&OwnUserIdentityData>,
691    device_owner_identity: Option<&UserIdentityData>,
692) -> bool {
693    device_owner_identity.is_some_and(|device_owner_identity| {
694        device_owner_identity.was_previously_verified()
695            && !is_user_verified(own_identity, device_owner_identity)
696    })
697}
698
699fn is_user_verified(
700    own_identity: Option<&OwnUserIdentityData>,
701    user_identity: &UserIdentityData,
702) -> bool {
703    match user_identity {
704        UserIdentityData::Own(own_identity) => own_identity.is_verified(),
705        UserIdentityData::Other(other_identity) => {
706            own_identity.is_some_and(|oi| oi.is_identity_verified(other_identity))
707        }
708    }
709}
710
711#[cfg(test)]
712mod tests {
713    use std::{collections::BTreeMap, iter, sync::Arc};
714
715    use assert_matches::assert_matches;
716    use assert_matches2::assert_let;
717    use insta::{assert_snapshot, with_settings};
718    use matrix_sdk_common::deserialized_responses::WithheldCode;
719    use matrix_sdk_test::{
720        async_test, test_json,
721        test_json::keys_query_sets::{
722            IdentityChangeDataSet, KeyDistributionTestData, MaloIdentityChangeDataSet,
723            VerificationViolationTestData,
724        },
725    };
726    use ruma::{
727        device_id,
728        events::{dummy::ToDeviceDummyEventContent, room::history_visibility::HistoryVisibility},
729        room_id, TransactionId,
730    };
731    use serde_json::json;
732
733    use crate::{
734        error::SessionRecipientCollectionError,
735        olm::{OutboundGroupSession, ShareInfo},
736        session_manager::{
737            group_sessions::share_strategy::collect_session_recipients, CollectStrategy,
738        },
739        store::caches::SequenceNumber,
740        testing::simulate_key_query_response_for_verification,
741        types::requests::ToDeviceRequest,
742        CrossSigningKeyExport, EncryptionSettings, LocalTrust, OlmError, OlmMachine,
743    };
744
745    /// Returns an `OlmMachine` set up for the test user in
746    /// [`KeyDistributionTestData`], with cross-signing set up and the
747    /// private cross-signing keys imported.
748    async fn test_machine() -> OlmMachine {
749        use KeyDistributionTestData as DataSet;
750
751        // Create the local user (`@me`), and import the public identity keys
752        let machine = OlmMachine::new(DataSet::me_id(), DataSet::me_device_id()).await;
753        let keys_query = DataSet::me_keys_query_response();
754        machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
755
756        // Also import the private cross signing keys
757        machine
758            .import_cross_signing_keys(CrossSigningKeyExport {
759                master_key: DataSet::MASTER_KEY_PRIVATE_EXPORT.to_owned().into(),
760                self_signing_key: DataSet::SELF_SIGNING_KEY_PRIVATE_EXPORT.to_owned().into(),
761                user_signing_key: DataSet::USER_SIGNING_KEY_PRIVATE_EXPORT.to_owned().into(),
762            })
763            .await
764            .unwrap();
765
766        machine
767    }
768
769    /// Import device data for `@dan`, `@dave`, and `@good`, as referenced in
770    /// [`KeyDistributionTestData`], into the given OlmMachine
771    async fn import_known_users_to_test_machine(machine: &OlmMachine) {
772        let keys_query = KeyDistributionTestData::dan_keys_query_response();
773        let txn_id = TransactionId::new();
774        machine.mark_request_as_sent(&txn_id, &keys_query).await.unwrap();
775
776        let txn_id_dave = TransactionId::new();
777        let keys_query_dave = KeyDistributionTestData::dave_keys_query_response();
778        machine.mark_request_as_sent(&txn_id_dave, &keys_query_dave).await.unwrap();
779
780        let txn_id_good = TransactionId::new();
781        let keys_query_good = KeyDistributionTestData::good_keys_query_response();
782        machine.mark_request_as_sent(&txn_id_good, &keys_query_good).await.unwrap();
783    }
784
785    /// Assert that [`CollectStrategy::AllDevices`] retains the same
786    /// serialization format.
787    #[test]
788    fn test_serialize_device_based_strategy() {
789        let encryption_settings = all_devices_strategy_settings();
790        let serialized = serde_json::to_string(&encryption_settings).unwrap();
791        with_settings!({prepend_module_to_snapshot => false}, {
792            assert_snapshot!(serialized)
793        });
794    }
795
796    /// [`CollectStrategy::AllDevices`] used to be known as
797    /// `DeviceBasedStrategy`. Check we can still deserialize the old
798    /// representation.
799    #[test]
800    fn test_deserialize_old_device_based_strategy() {
801        let settings: EncryptionSettings = serde_json::from_value(json!({
802            "algorithm": "m.megolm.v1.aes-sha2",
803            "rotation_period":{"secs":604800,"nanos":0},
804            "rotation_period_msgs":100,
805            "history_visibility":"shared",
806            "sharing_strategy":{"DeviceBasedStrategy":{"only_allow_trusted_devices":false,"error_on_verified_user_problem":false}},
807        })).unwrap();
808        assert_matches!(settings.sharing_strategy, CollectStrategy::AllDevices);
809    }
810
811    /// [`CollectStrategy::ErrorOnVerifiedUserProblem`] used to be represented
812    /// as a variant on the former `DeviceBasedStrategy`. Check we can still
813    /// deserialize the old representation.
814    #[test]
815    fn test_deserialize_old_error_on_verified_user_problem() {
816        let settings: EncryptionSettings = serde_json::from_value(json!({
817            "algorithm": "m.megolm.v1.aes-sha2",
818            "rotation_period":{"secs":604800,"nanos":0},
819            "rotation_period_msgs":100,
820            "history_visibility":"shared",
821            "sharing_strategy":{"DeviceBasedStrategy":{"only_allow_trusted_devices":false,"error_on_verified_user_problem":true}},
822        })).unwrap();
823        assert_matches!(settings.sharing_strategy, CollectStrategy::ErrorOnVerifiedUserProblem);
824    }
825
826    /// [`CollectStrategy::OnlyTrustedDevices`] used to be represented as a
827    /// variant on the former `DeviceBasedStrategy`. Check we can still
828    /// deserialize the old representation.
829    #[test]
830    fn test_deserialize_old_only_trusted_devices_strategy() {
831        let settings: EncryptionSettings = serde_json::from_value(json!({
832            "algorithm": "m.megolm.v1.aes-sha2",
833            "rotation_period":{"secs":604800,"nanos":0},
834            "rotation_period_msgs":100,
835            "history_visibility":"shared",
836            "sharing_strategy":{"DeviceBasedStrategy":{"only_allow_trusted_devices":true,"error_on_verified_user_problem":false}},
837        })).unwrap();
838        assert_matches!(settings.sharing_strategy, CollectStrategy::OnlyTrustedDevices);
839    }
840
841    #[async_test]
842    async fn test_share_with_per_device_strategy_to_all() {
843        let machine = test_machine().await;
844        import_known_users_to_test_machine(&machine).await;
845
846        let encryption_settings = all_devices_strategy_settings();
847
848        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
849
850        let share_result = collect_session_recipients(
851            machine.store(),
852            vec![
853                KeyDistributionTestData::dan_id(),
854                KeyDistributionTestData::dave_id(),
855                KeyDistributionTestData::good_id(),
856            ]
857            .into_iter(),
858            &encryption_settings,
859            &group_session,
860        )
861        .await
862        .unwrap();
863
864        assert!(!share_result.should_rotate);
865
866        let dan_devices_shared =
867            share_result.devices.get(KeyDistributionTestData::dan_id()).unwrap();
868        let dave_devices_shared =
869            share_result.devices.get(KeyDistributionTestData::dave_id()).unwrap();
870        let good_devices_shared =
871            share_result.devices.get(KeyDistributionTestData::good_id()).unwrap();
872
873        // With this strategy the room key would be distributed to all devices
874        assert_eq!(dan_devices_shared.len(), 2);
875        assert_eq!(dave_devices_shared.len(), 1);
876        assert_eq!(good_devices_shared.len(), 2);
877    }
878
879    #[async_test]
880    async fn test_share_with_only_trusted_strategy() {
881        let machine = test_machine().await;
882        import_known_users_to_test_machine(&machine).await;
883
884        let encryption_settings = EncryptionSettings {
885            sharing_strategy: CollectStrategy::OnlyTrustedDevices,
886            ..Default::default()
887        };
888
889        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
890
891        let share_result = collect_session_recipients(
892            machine.store(),
893            vec![
894                KeyDistributionTestData::dan_id(),
895                KeyDistributionTestData::dave_id(),
896                KeyDistributionTestData::good_id(),
897            ]
898            .into_iter(),
899            &encryption_settings,
900            &group_session,
901        )
902        .await
903        .unwrap();
904
905        assert!(!share_result.should_rotate);
906
907        let dave_devices_shared = share_result.devices.get(KeyDistributionTestData::dave_id());
908        let good_devices_shared = share_result.devices.get(KeyDistributionTestData::good_id());
909        // dave and good wouldn't receive any key
910        assert!(dave_devices_shared.unwrap().is_empty());
911        assert!(good_devices_shared.unwrap().is_empty());
912
913        // dan is verified by me and has one of his devices self signed, so should get
914        // the key
915        let dan_devices_shared =
916            share_result.devices.get(KeyDistributionTestData::dan_id()).unwrap();
917
918        assert_eq!(dan_devices_shared.len(), 1);
919        let dan_device_that_will_get_the_key = &dan_devices_shared[0];
920        assert_eq!(
921            dan_device_that_will_get_the_key.device_id().as_str(),
922            KeyDistributionTestData::dan_signed_device_id()
923        );
924
925        // Check withhelds for others
926        let (_, code) = share_result
927            .withheld_devices
928            .iter()
929            .find(|(d, _)| d.device_id() == KeyDistributionTestData::dan_unsigned_device_id())
930            .expect("This dan's device should receive a withheld code");
931
932        assert_eq!(code, &WithheldCode::Unverified);
933
934        let (_, code) = share_result
935            .withheld_devices
936            .iter()
937            .find(|(d, _)| d.device_id() == KeyDistributionTestData::dave_device_id())
938            .expect("This daves's device should receive a withheld code");
939
940        assert_eq!(code, &WithheldCode::Unverified);
941    }
942
943    /// Test that [`collect_session_recipients`] returns an error if there are
944    /// unsigned devices belonging to verified users, when
945    /// `error_on_verified_user_problem` is set.
946    #[async_test]
947    async fn test_error_on_unsigned_of_verified_users() {
948        use VerificationViolationTestData as DataSet;
949
950        // We start with Bob, who is verified and has one unsigned device.
951        let machine = unsigned_of_verified_setup().await;
952
953        // Add Carol, also verified with one unsigned device.
954        let carol_keys = DataSet::carol_keys_query_response_signed();
955        machine.mark_request_as_sent(&TransactionId::new(), &carol_keys).await.unwrap();
956
957        // Double-check the state of Carol.
958        let carol_identity =
959            machine.get_identity(DataSet::carol_id(), None).await.unwrap().unwrap();
960        assert!(carol_identity.other().unwrap().is_verified());
961
962        let carol_unsigned_device = machine
963            .get_device(DataSet::carol_id(), DataSet::carol_unsigned_device_id(), None)
964            .await
965            .unwrap()
966            .unwrap();
967        assert!(!carol_unsigned_device.is_verified());
968
969        // Sharing an OutboundGroupSession should fail.
970        let encryption_settings = error_on_verification_problem_encryption_settings();
971        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
972        let share_result = collect_session_recipients(
973            machine.store(),
974            vec![DataSet::bob_id(), DataSet::carol_id()].into_iter(),
975            &encryption_settings,
976            &group_session,
977        )
978        .await;
979
980        assert_let!(
981            Err(OlmError::SessionRecipientCollectionError(
982                SessionRecipientCollectionError::VerifiedUserHasUnsignedDevice(unverified_devices)
983            )) = share_result
984        );
985
986        // Check the list of devices in the error.
987        assert_eq!(
988            unverified_devices,
989            BTreeMap::from([
990                (DataSet::bob_id().to_owned(), vec![DataSet::bob_device_2_id().to_owned()]),
991                (
992                    DataSet::carol_id().to_owned(),
993                    vec![DataSet::carol_unsigned_device_id().to_owned()]
994                ),
995            ])
996        );
997    }
998
999    /// Test that we can resolve errors from
1000    /// `error_on_verified_user_problem` by whitelisting the
1001    /// device.
1002    #[async_test]
1003    async fn test_error_on_unsigned_of_verified_resolve_by_whitelisting() {
1004        use VerificationViolationTestData as DataSet;
1005
1006        let machine = unsigned_of_verified_setup().await;
1007
1008        // Whitelist the unsigned device
1009        machine
1010            .get_device(DataSet::bob_id(), DataSet::bob_device_2_id(), None)
1011            .await
1012            .unwrap()
1013            .unwrap()
1014            .set_local_trust(LocalTrust::Ignored)
1015            .await
1016            .unwrap();
1017
1018        let encryption_settings = error_on_verification_problem_encryption_settings();
1019        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
1020
1021        // We should be able to share a key, and it should include the unsigned device.
1022        let share_result = collect_session_recipients(
1023            machine.store(),
1024            iter::once(DataSet::bob_id()),
1025            &encryption_settings,
1026            &group_session,
1027        )
1028        .await
1029        .unwrap();
1030
1031        assert_eq!(2, share_result.devices.get(DataSet::bob_id()).unwrap().len());
1032        assert_eq!(0, share_result.withheld_devices.len());
1033    }
1034
1035    /// Test that we can resolve errors from
1036    /// `error_on_verified_user_problem` by blacklisting the
1037    /// device.
1038    #[async_test]
1039    async fn test_error_on_unsigned_of_verified_resolve_by_blacklisting() {
1040        use VerificationViolationTestData as DataSet;
1041
1042        let machine = unsigned_of_verified_setup().await;
1043
1044        // Blacklist the unsigned device
1045        machine
1046            .get_device(DataSet::bob_id(), DataSet::bob_device_2_id(), None)
1047            .await
1048            .unwrap()
1049            .unwrap()
1050            .set_local_trust(LocalTrust::BlackListed)
1051            .await
1052            .unwrap();
1053
1054        let encryption_settings = error_on_verification_problem_encryption_settings();
1055        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
1056
1057        // We should be able to share a key, and it should exclude the unsigned device.
1058        let share_result = collect_session_recipients(
1059            machine.store(),
1060            iter::once(DataSet::bob_id()),
1061            &encryption_settings,
1062            &group_session,
1063        )
1064        .await
1065        .unwrap();
1066
1067        assert_eq!(1, share_result.devices.get(DataSet::bob_id()).unwrap().len());
1068        let withheld_list: Vec<_> = share_result
1069            .withheld_devices
1070            .iter()
1071            .map(|(d, code)| (d.device_id().to_owned(), code.clone()))
1072            .collect();
1073        assert_eq!(
1074            withheld_list,
1075            vec![(DataSet::bob_device_2_id().to_owned(), WithheldCode::Blacklisted)]
1076        );
1077    }
1078
1079    /// Test that [`collect_session_recipients`] returns an error when
1080    /// `error_on_verified_user_problem` is set, if our own identity
1081    /// is verified and we have unsigned devices.
1082    #[async_test]
1083    async fn test_error_on_unsigned_of_verified_owner_is_us() {
1084        use VerificationViolationTestData as DataSet;
1085
1086        let machine = unsigned_of_verified_setup().await;
1087
1088        // Add a couple of devices to Alice's account
1089        let mut own_keys = DataSet::own_keys_query_response_1().clone();
1090        own_keys.device_keys.insert(
1091            DataSet::own_id().to_owned(),
1092            BTreeMap::from([
1093                DataSet::own_signed_device_keys(),
1094                DataSet::own_unsigned_device_keys(),
1095            ]),
1096        );
1097        machine.mark_request_as_sent(&TransactionId::new(), &own_keys).await.unwrap();
1098
1099        let encryption_settings = error_on_verification_problem_encryption_settings();
1100        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
1101        let share_result = collect_session_recipients(
1102            machine.store(),
1103            iter::once(DataSet::own_id()),
1104            &encryption_settings,
1105            &group_session,
1106        )
1107        .await;
1108
1109        assert_let!(
1110            Err(OlmError::SessionRecipientCollectionError(
1111                SessionRecipientCollectionError::VerifiedUserHasUnsignedDevice(unverified_devices)
1112            )) = share_result
1113        );
1114
1115        // Check the list of devices in the error.
1116        assert_eq!(
1117            unverified_devices,
1118            BTreeMap::from([(
1119                DataSet::own_id().to_owned(),
1120                vec![DataSet::own_unsigned_device_id()]
1121            ),])
1122        );
1123    }
1124
1125    /// Test that an unsigned device of an unverified user doesn't cause an
1126    /// error.
1127    #[async_test]
1128    async fn test_should_not_error_on_unsigned_of_unverified() {
1129        use VerificationViolationTestData as DataSet;
1130
1131        let machine = OlmMachine::new(DataSet::own_id(), device_id!("LOCAL")).await;
1132
1133        // Tell the OlmMachine about our own public keys.
1134        let own_keys = DataSet::own_keys_query_response_1();
1135        machine.mark_request_as_sent(&TransactionId::new(), &own_keys).await.unwrap();
1136
1137        // Import the secret parts of our own cross-signing keys.
1138        machine
1139            .import_cross_signing_keys(CrossSigningKeyExport {
1140                master_key: DataSet::MASTER_KEY_PRIVATE_EXPORT.to_owned().into(),
1141                self_signing_key: DataSet::SELF_SIGNING_KEY_PRIVATE_EXPORT.to_owned().into(),
1142                user_signing_key: DataSet::USER_SIGNING_KEY_PRIVATE_EXPORT.to_owned().into(),
1143            })
1144            .await
1145            .unwrap();
1146
1147        // This time our own identity is trusted but is not signing bob.
1148        let bob_keys = DataSet::bob_keys_query_response_rotated();
1149        machine.mark_request_as_sent(&TransactionId::new(), &bob_keys).await.unwrap();
1150
1151        // Double-check the state of Bob: he should be unverified, and should have an
1152        // unsigned device.
1153        let bob_identity = machine.get_identity(DataSet::bob_id(), None).await.unwrap().unwrap();
1154        assert!(!bob_identity.other().unwrap().is_verified());
1155
1156        let bob_unsigned_device = machine
1157            .get_device(DataSet::bob_id(), DataSet::bob_device_1_id(), None)
1158            .await
1159            .unwrap()
1160            .unwrap();
1161        assert!(!bob_unsigned_device.is_cross_signed_by_owner());
1162
1163        let encryption_settings = error_on_verification_problem_encryption_settings();
1164        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
1165        collect_session_recipients(
1166            machine.store(),
1167            iter::once(DataSet::bob_id()),
1168            &encryption_settings,
1169            &group_session,
1170        )
1171        .await
1172        .unwrap();
1173    }
1174
1175    /// Test that an unsigned device of a signed user doesn't cause an
1176    /// error, when we have not verified our own identity.
1177    #[async_test]
1178    async fn test_should_not_error_on_unsigned_of_signed_but_unverified() {
1179        use VerificationViolationTestData as DataSet;
1180
1181        let machine = OlmMachine::new(DataSet::own_id(), device_id!("LOCAL")).await;
1182
1183        // Tell the OlmMachine about our own public keys.
1184        let keys_query = DataSet::own_keys_query_response_1();
1185        machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
1186
1187        // ... and those of Bob.
1188        let keys_query = DataSet::bob_keys_query_response_signed();
1189        machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
1190
1191        // Double-check the state of Bob: his identity should be signed but unverified,
1192        // and he should have an unsigned device.
1193        let bob_identity =
1194            machine.get_identity(DataSet::bob_id(), None).await.unwrap().unwrap().other().unwrap();
1195        assert!(bob_identity
1196            .own_identity
1197            .as_ref()
1198            .unwrap()
1199            .is_identity_signed(&bob_identity.inner));
1200        assert!(!bob_identity.is_verified());
1201
1202        let bob_unsigned_device = machine
1203            .get_device(DataSet::bob_id(), DataSet::bob_device_2_id(), None)
1204            .await
1205            .unwrap()
1206            .unwrap();
1207        assert!(!bob_unsigned_device.is_cross_signed_by_owner());
1208
1209        // Share a session, and ensure that it doesn't error.
1210        let encryption_settings = error_on_verification_problem_encryption_settings();
1211        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
1212        collect_session_recipients(
1213            machine.store(),
1214            iter::once(DataSet::bob_id()),
1215            &encryption_settings,
1216            &group_session,
1217        )
1218        .await
1219        .unwrap();
1220    }
1221
1222    /// Test that a verified user changing their identity causes an error in
1223    /// `collect_session_recipients`, and that it can be resolved by
1224    /// withdrawing verification
1225    #[async_test]
1226    async fn test_verified_user_changed_identity() {
1227        use test_json::keys_query_sets::VerificationViolationTestData as DataSet;
1228
1229        // We start with Bob, who is verified and has one unsigned device. We have also
1230        // verified our own identity.
1231        let machine = unsigned_of_verified_setup().await;
1232
1233        // Bob then rotates his identity
1234        let bob_keys = DataSet::bob_keys_query_response_rotated();
1235        machine.mark_request_as_sent(&TransactionId::new(), &bob_keys).await.unwrap();
1236
1237        // Double-check the state of Bob
1238        let bob_identity = machine.get_identity(DataSet::bob_id(), None).await.unwrap().unwrap();
1239        assert!(bob_identity.has_verification_violation());
1240
1241        // Sharing an OutboundGroupSession should fail.
1242        let encryption_settings = error_on_verification_problem_encryption_settings();
1243        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
1244        let share_result = collect_session_recipients(
1245            machine.store(),
1246            iter::once(DataSet::bob_id()),
1247            &encryption_settings,
1248            &group_session,
1249        )
1250        .await;
1251
1252        assert_let!(
1253            Err(OlmError::SessionRecipientCollectionError(
1254                SessionRecipientCollectionError::VerifiedUserChangedIdentity(violating_users)
1255            )) = share_result
1256        );
1257        assert_eq!(violating_users, vec![DataSet::bob_id()]);
1258
1259        // Resolve by calling withdraw_verification
1260        bob_identity.withdraw_verification().await.unwrap();
1261
1262        collect_session_recipients(
1263            machine.store(),
1264            iter::once(DataSet::bob_id()),
1265            &encryption_settings,
1266            &group_session,
1267        )
1268        .await
1269        .unwrap();
1270    }
1271
1272    /// Test that our own identity being changed causes an error in
1273    /// `collect_session_recipients`, and that it can be resolved by
1274    /// withdrawing verification
1275    #[async_test]
1276    async fn test_own_verified_identity_changed() {
1277        use test_json::keys_query_sets::VerificationViolationTestData as DataSet;
1278
1279        // We start with a verified identity.
1280        let machine = unsigned_of_verified_setup().await;
1281        let own_identity = machine.get_identity(DataSet::own_id(), None).await.unwrap().unwrap();
1282        assert!(own_identity.own().unwrap().is_verified());
1283
1284        // Another device rotates our own identity.
1285        let own_keys = DataSet::own_keys_query_response_2();
1286        machine.mark_request_as_sent(&TransactionId::new(), &own_keys).await.unwrap();
1287
1288        let own_identity = machine.get_identity(DataSet::own_id(), None).await.unwrap().unwrap();
1289        assert!(!own_identity.is_verified());
1290
1291        // Sharing an OutboundGroupSession should fail.
1292        let encryption_settings = error_on_verification_problem_encryption_settings();
1293        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
1294        let share_result = collect_session_recipients(
1295            machine.store(),
1296            iter::once(DataSet::own_id()),
1297            &encryption_settings,
1298            &group_session,
1299        )
1300        .await;
1301
1302        assert_let!(
1303            Err(OlmError::SessionRecipientCollectionError(
1304                SessionRecipientCollectionError::VerifiedUserChangedIdentity(violating_users)
1305            )) = share_result
1306        );
1307        assert_eq!(violating_users, vec![DataSet::own_id()]);
1308
1309        // Resolve by calling withdraw_verification
1310        own_identity.withdraw_verification().await.unwrap();
1311
1312        collect_session_recipients(
1313            machine.store(),
1314            iter::once(DataSet::own_id()),
1315            &encryption_settings,
1316            &group_session,
1317        )
1318        .await
1319        .unwrap();
1320    }
1321
1322    /// A set of tests for the behaviour of [`collect_session_recipients`] with
1323    /// a dehydrated device
1324    mod dehydrated_device {
1325        use std::{collections::HashSet, iter};
1326
1327        use insta::{allow_duplicates, assert_json_snapshot, with_settings};
1328        use matrix_sdk_common::deserialized_responses::WithheldCode;
1329        use matrix_sdk_test::{
1330            async_test, ruma_response_to_json,
1331            test_json::keys_query_sets::{
1332                KeyDistributionTestData, KeyQueryResponseTemplate,
1333                KeyQueryResponseTemplateDeviceOptions,
1334            },
1335        };
1336        use ruma::{device_id, user_id, DeviceId, TransactionId, UserId};
1337        use vodozemac::{Curve25519PublicKey, Ed25519SecretKey};
1338
1339        use super::{
1340            all_devices_strategy_settings, create_test_outbound_group_session,
1341            error_on_verification_problem_encryption_settings, identity_based_strategy_settings,
1342            test_machine,
1343        };
1344        use crate::{
1345            session_manager::group_sessions::{
1346                share_strategy::collect_session_recipients, CollectRecipientsResult,
1347            },
1348            EncryptionSettings, OlmMachine,
1349        };
1350
1351        #[async_test]
1352        async fn test_all_devices_strategy_should_share_with_verified_dehydrated_device() {
1353            should_share_with_verified_dehydrated_device(&all_devices_strategy_settings()).await
1354        }
1355
1356        #[async_test]
1357        async fn test_error_on_verification_problem_strategy_should_share_with_verified_dehydrated_device(
1358        ) {
1359            should_share_with_verified_dehydrated_device(
1360                &error_on_verification_problem_encryption_settings(),
1361            )
1362            .await
1363        }
1364
1365        #[async_test]
1366        async fn test_identity_based_strategy_should_share_with_verified_dehydrated_device() {
1367            should_share_with_verified_dehydrated_device(&identity_based_strategy_settings()).await
1368        }
1369
1370        /// Common helper for
1371        /// [`test_all_devices_strategy_should_share_with_verified_dehydrated_device`],
1372        /// [`test_error_on_verification_problem_strategy_should_share_with_verified_dehydrated_device`]
1373        /// and [`test_identity_based_strategy_should_share_with_verified_dehydrated_device`].
1374        async fn should_share_with_verified_dehydrated_device(
1375            encryption_settings: &EncryptionSettings,
1376        ) {
1377            let machine = test_machine().await;
1378
1379            // Bob is a user with cross-signing, who has a single (verified) dehydrated
1380            // device.
1381            let bob_user_id = user_id!("@bob:localhost");
1382            let bob_dehydrated_device_id = device_id!("DEHYDRATED_DEVICE");
1383            let keys_query = key_query_response_template_with_cross_signing(bob_user_id)
1384                .with_dehydrated_device(bob_dehydrated_device_id, true)
1385                .build_response();
1386            allow_duplicates! {
1387                with_settings!({ sort_maps => true, prepend_module_to_snapshot => false }, {
1388                    assert_json_snapshot!(ruma_response_to_json(keys_query.clone()))
1389                });
1390            }
1391            machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
1392
1393            // When we collect the recipients ...
1394            let recips = share_test_session_and_collect_recipients(
1395                &machine,
1396                bob_user_id,
1397                encryption_settings,
1398            )
1399            .await;
1400
1401            // ... then the dehydrated device should be included
1402            assert_shared_with(recips, bob_user_id, [bob_dehydrated_device_id].into());
1403        }
1404
1405        #[async_test]
1406        async fn test_all_devices_strategy_should_not_share_with_unverified_dehydrated_device() {
1407            should_not_share_with_unverified_dehydrated_device(&all_devices_strategy_settings())
1408                .await
1409        }
1410
1411        #[async_test]
1412        async fn test_error_on_verification_problem_strategy_should_not_share_with_unverified_dehydrated_device(
1413        ) {
1414            should_not_share_with_unverified_dehydrated_device(
1415                &error_on_verification_problem_encryption_settings(),
1416            )
1417            .await
1418        }
1419
1420        #[async_test]
1421        async fn test_identity_based_strategy_should_not_share_with_unverified_dehydrated_device() {
1422            should_not_share_with_unverified_dehydrated_device(&identity_based_strategy_settings())
1423                .await
1424        }
1425
1426        /// Common helper for
1427        /// [`test_all_devices_strategy_should_not_share_with_unverified_dehydrated_device`],
1428        /// [`test_error_on_verification_problem_strategy_should_not_share_with_unverified_dehydrated_device`]
1429        /// and [`test_identity_based_strategy_should_not_share_with_unverified_dehydrated_device`].
1430        async fn should_not_share_with_unverified_dehydrated_device(
1431            encryption_settings: &EncryptionSettings,
1432        ) {
1433            let machine = test_machine().await;
1434
1435            // Bob is a user with cross-signing, who has a single (unverified) dehydrated
1436            // device.
1437            let bob_user_id = user_id!("@bob:localhost");
1438            let bob_dehydrated_device_id = device_id!("DEHYDRATED_DEVICE");
1439            let keys_query = key_query_response_template_with_cross_signing(bob_user_id)
1440                .with_dehydrated_device(bob_dehydrated_device_id, false)
1441                .build_response();
1442            allow_duplicates! {
1443                with_settings!({ sort_maps => true, prepend_module_to_snapshot => false }, {
1444                    assert_json_snapshot!(ruma_response_to_json(keys_query.clone()))
1445                });
1446            }
1447            machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
1448
1449            // When we collect the recipients ...
1450            let recips = share_test_session_and_collect_recipients(
1451                &machine,
1452                bob_user_id,
1453                encryption_settings,
1454            )
1455            .await;
1456
1457            // ... it shouldn't be shared with anyone, and there should be a withheld
1458            // message for the dehydrated device.
1459            assert_withheld_to(recips, bob_user_id, bob_dehydrated_device_id);
1460        }
1461
1462        #[async_test]
1463        async fn test_all_devices_strategy_should_share_with_verified_device_of_pin_violation_user()
1464        {
1465            should_share_with_verified_device_of_pin_violation_user(
1466                &all_devices_strategy_settings(),
1467            )
1468            .await
1469        }
1470
1471        #[async_test]
1472        async fn test_error_on_verification_problem_strategy_should_share_with_verified_device_of_pin_violation_user(
1473        ) {
1474            should_share_with_verified_device_of_pin_violation_user(
1475                &error_on_verification_problem_encryption_settings(),
1476            )
1477            .await
1478        }
1479
1480        #[async_test]
1481        async fn test_identity_based_strategy_should_share_with_verified_device_of_pin_violation_user(
1482        ) {
1483            should_share_with_verified_device_of_pin_violation_user(
1484                &identity_based_strategy_settings(),
1485            )
1486            .await
1487        }
1488
1489        /// Common helper for
1490        /// [`test_all_devices_strategy_should_share_with_verified_device_of_pin_violation_user`],
1491        /// [`test_error_on_verification_problem_strategy_should_share_with_verified_device_of_pin_violation_user`]
1492        /// and [`test_identity_based_strategy_should_share_with_verified_device_of_pin_violation_user`].
1493        async fn should_share_with_verified_device_of_pin_violation_user(
1494            encryption_settings: &EncryptionSettings,
1495        ) {
1496            let machine = test_machine().await;
1497
1498            // Bob starts out with one identity
1499            let bob_user_id = user_id!("@bob:localhost");
1500            let keys_query =
1501                key_query_response_template_with_cross_signing(bob_user_id).build_response();
1502            machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
1503
1504            // He then changes identity, and adds a dehydrated device (signed with his new
1505            // identity)
1506            let bob_dehydrated_device_id = device_id!("DEHYDRATED_DEVICE");
1507            let keys_query = key_query_response_template_with_changed_cross_signing(bob_user_id)
1508                .with_dehydrated_device(bob_dehydrated_device_id, true)
1509                .build_response();
1510            allow_duplicates! {
1511                with_settings!({ sort_maps => true, prepend_module_to_snapshot => false }, {
1512                    assert_json_snapshot!(ruma_response_to_json(keys_query.clone()))
1513                });
1514            }
1515            machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
1516
1517            // When we collect the recipients ...
1518            let recips = share_test_session_and_collect_recipients(
1519                &machine,
1520                bob_user_id,
1521                encryption_settings,
1522            )
1523            .await;
1524
1525            // ... then the dehydrated device should be included
1526            assert_shared_with(recips, bob_user_id, [bob_dehydrated_device_id].into());
1527        }
1528
1529        #[async_test]
1530        async fn test_all_devices_strategy_should_not_share_with_dehydrated_device_of_verification_violation_user(
1531        ) {
1532            should_not_share_with_dehydrated_device_of_verification_violation_user(
1533                &all_devices_strategy_settings(),
1534            )
1535            .await
1536        }
1537
1538        /// Helper function for
1539        /// [`test_all_devices_strategy_should_not_share_with_dehydrated_device_of_verification_violation_user`].
1540        async fn should_not_share_with_dehydrated_device_of_verification_violation_user(
1541            encryption_settings: &EncryptionSettings,
1542        ) {
1543            let bob_user_id = user_id!("@bob:localhost");
1544            let bob_dehydrated_device_id = device_id!("DEHYDRATED_DEVICE");
1545            let machine = prepare_machine_with_dehydrated_device_of_verification_violation_user(
1546                bob_user_id,
1547                bob_dehydrated_device_id,
1548            )
1549            .await;
1550
1551            // When we collect the recipients ...
1552            let recips = share_test_session_and_collect_recipients(
1553                &machine,
1554                bob_user_id,
1555                encryption_settings,
1556            )
1557            .await;
1558
1559            // ... it shouldn't be shared with anyone, and there should be a withheld
1560            // message for the dehydrated device.
1561            assert_withheld_to(recips, bob_user_id, bob_dehydrated_device_id);
1562        }
1563
1564        #[async_test]
1565        async fn test_error_on_verification_problem_strategy_should_give_error_for_dehydrated_device_of_verification_violation_user(
1566        ) {
1567            should_give_error_for_dehydrated_device_of_verification_violation_user(
1568                &error_on_verification_problem_encryption_settings(),
1569            )
1570            .await
1571        }
1572
1573        #[async_test]
1574        async fn test_identity_based_strategy_should_give_error_for_dehydrated_device_of_verification_violation_user(
1575        ) {
1576            // This hits the same codepath as
1577            // `test_share_identity_strategy_report_verification_violation`, but
1578            // we test dehydrated devices here specifically, for completeness.
1579            should_give_error_for_dehydrated_device_of_verification_violation_user(
1580                &identity_based_strategy_settings(),
1581            )
1582            .await
1583        }
1584
1585        /// Common helper for
1586        /// [`test_error_on_verification_problem_strategy_should_give_error_for_dehydrated_device_of_verification_violation_user`]
1587        /// and [`test_identity_based_strategy_should_give_error_for_dehydrated_device_of_verification_violation_user`].
1588        async fn should_give_error_for_dehydrated_device_of_verification_violation_user(
1589            encryption_settings: &EncryptionSettings,
1590        ) {
1591            let bob_user_id = user_id!("@bob:localhost");
1592            let bob_dehydrated_device_id = device_id!("DEHYDRATED_DEVICE");
1593            let machine = prepare_machine_with_dehydrated_device_of_verification_violation_user(
1594                bob_user_id,
1595                bob_dehydrated_device_id,
1596            )
1597            .await;
1598
1599            let group_session = create_test_outbound_group_session(&machine, encryption_settings);
1600            let share_result = collect_session_recipients(
1601                machine.store(),
1602                iter::once(bob_user_id),
1603                encryption_settings,
1604                &group_session,
1605            )
1606            .await;
1607
1608            // The key share should fail with an error indicating that recipients
1609            // were previously verified.
1610            assert_matches::assert_matches!(
1611                share_result,
1612                Err(crate::OlmError::SessionRecipientCollectionError(
1613                    crate::SessionRecipientCollectionError::VerifiedUserChangedIdentity(_)
1614                ))
1615            );
1616        }
1617
1618        /// Prepare an OlmMachine which knows about a user `bob_user_id`, who
1619        /// has recently changed identity, and then added a new
1620        /// dehydrated device `bob_dehydrated_device_id`.
1621        async fn prepare_machine_with_dehydrated_device_of_verification_violation_user(
1622            bob_user_id: &UserId,
1623            bob_dehydrated_device_id: &DeviceId,
1624        ) -> OlmMachine {
1625            let machine = test_machine().await;
1626
1627            // Bob starts out with one identity, which we have verified
1628            let keys_query = key_query_response_template_with_cross_signing(bob_user_id)
1629                .with_user_verification_signature(
1630                    KeyDistributionTestData::me_id(),
1631                    &KeyDistributionTestData::me_private_user_signing_key(),
1632                )
1633                .build_response();
1634            machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
1635
1636            // He then changes identity, and adds a dehydrated device (signed with his new
1637            // identity)
1638            let keys_query = key_query_response_template_with_changed_cross_signing(bob_user_id)
1639                .with_dehydrated_device(bob_dehydrated_device_id, true)
1640                .build_response();
1641            allow_duplicates! {
1642                with_settings!({ sort_maps => true, prepend_module_to_snapshot => false }, {
1643                    assert_json_snapshot!(ruma_response_to_json(keys_query.clone()))
1644                });
1645            }
1646            machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
1647
1648            machine
1649        }
1650
1651        /// Create a test megolm session and prepare to share it with the given
1652        /// users, using the given sharing strategy.
1653        async fn share_test_session_and_collect_recipients(
1654            machine: &OlmMachine,
1655            target_user_id: &UserId,
1656            encryption_settings: &EncryptionSettings,
1657        ) -> CollectRecipientsResult {
1658            let group_session = create_test_outbound_group_session(machine, encryption_settings);
1659            collect_session_recipients(
1660                machine.store(),
1661                iter::once(target_user_id),
1662                encryption_settings,
1663                &group_session,
1664            )
1665            .await
1666            .unwrap()
1667        }
1668
1669        /// Assert that the session is shared with the given devices, and that
1670        /// there are no "withheld" messages
1671        fn assert_shared_with(
1672            recips: CollectRecipientsResult,
1673            user_id: &UserId,
1674            device_ids: HashSet<&DeviceId>,
1675        ) {
1676            let bob_devices_shared: HashSet<_> = recips
1677                .devices
1678                .get(user_id)
1679                .unwrap_or_else(|| panic!("session not shared with {user_id}"))
1680                .iter()
1681                .map(|d| d.device_id())
1682                .collect();
1683            assert_eq!(bob_devices_shared, device_ids);
1684
1685            assert!(recips.withheld_devices.is_empty(), "Unexpected withheld messages");
1686        }
1687
1688        /// Assert that the session is not shared with any devices, and that
1689        /// there is a withheld code for the given device.
1690        fn assert_withheld_to(
1691            recips: CollectRecipientsResult,
1692            bob_user_id: &UserId,
1693            bob_dehydrated_device_id: &DeviceId,
1694        ) {
1695            // The share list should be empty
1696            for (user, device_list) in recips.devices {
1697                assert_eq!(device_list.len(), 0, "session unexpectedly shared with {user}");
1698            }
1699
1700            // ... and there should be one withheld message
1701            assert_eq!(recips.withheld_devices.len(), 1);
1702            assert_eq!(recips.withheld_devices[0].0.user_id(), bob_user_id);
1703            assert_eq!(recips.withheld_devices[0].0.device_id(), bob_dehydrated_device_id);
1704            assert_eq!(recips.withheld_devices[0].1, WithheldCode::Unverified);
1705        }
1706
1707        /// Start a [`KeysQueryResponseTemplate`] for the given user, with
1708        /// cross-signing keys.
1709        fn key_query_response_template_with_cross_signing(
1710            user_id: &UserId,
1711        ) -> KeyQueryResponseTemplate {
1712            KeyQueryResponseTemplate::new(user_id.to_owned()).with_cross_signing_keys(
1713                Ed25519SecretKey::from_slice(b"master12master12master12master12"),
1714                Ed25519SecretKey::from_slice(b"self1234self1234self1234self1234"),
1715                Ed25519SecretKey::from_slice(b"user1234user1234user1234user1234"),
1716            )
1717        }
1718
1719        /// Start a [`KeysQueryResponseTemplate`] for the given user, with
1720        /// *different* cross signing key to
1721        /// [`key_query_response_template_with_cross_signing`].
1722        fn key_query_response_template_with_changed_cross_signing(
1723            bob_user_id: &UserId,
1724        ) -> KeyQueryResponseTemplate {
1725            KeyQueryResponseTemplate::new(bob_user_id.to_owned()).with_cross_signing_keys(
1726                Ed25519SecretKey::from_slice(b"newmaster__newmaster__newmaster_"),
1727                Ed25519SecretKey::from_slice(b"self1234self1234self1234self1234"),
1728                Ed25519SecretKey::from_slice(b"user1234user1234user1234user1234"),
1729            )
1730        }
1731
1732        trait KeyQueryResponseTemplateExt {
1733            fn with_dehydrated_device(
1734                self,
1735                device_id: &DeviceId,
1736                verified: bool,
1737            ) -> KeyQueryResponseTemplate;
1738        }
1739
1740        impl KeyQueryResponseTemplateExt for KeyQueryResponseTemplate {
1741            /// Add a dehydrated device to the KeyQueryResponseTemplate
1742            fn with_dehydrated_device(
1743                self,
1744                device_id: &DeviceId,
1745                verified: bool,
1746            ) -> KeyQueryResponseTemplate {
1747                self.with_device(
1748                    device_id,
1749                    &Curve25519PublicKey::from(b"curvepubcurvepubcurvepubcurvepub".to_owned()),
1750                    &Ed25519SecretKey::from_slice(b"device12device12device12device12"),
1751                    KeyQueryResponseTemplateDeviceOptions::new()
1752                        .dehydrated(true)
1753                        .verified(verified),
1754                )
1755            }
1756        }
1757    }
1758
1759    #[async_test]
1760    async fn test_share_with_identity_strategy() {
1761        let machine = test_machine().await;
1762        import_known_users_to_test_machine(&machine).await;
1763
1764        let encryption_settings = identity_based_strategy_settings();
1765
1766        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
1767
1768        let share_result = collect_session_recipients(
1769            machine.store(),
1770            vec![
1771                KeyDistributionTestData::dan_id(),
1772                KeyDistributionTestData::dave_id(),
1773                KeyDistributionTestData::good_id(),
1774            ]
1775            .into_iter(),
1776            &encryption_settings,
1777            &group_session,
1778        )
1779        .await
1780        .unwrap();
1781
1782        assert!(!share_result.should_rotate);
1783
1784        let dave_devices_shared = share_result.devices.get(KeyDistributionTestData::dave_id());
1785        let good_devices_shared = share_result.devices.get(KeyDistributionTestData::good_id());
1786        // dave has no published identity so will not receive the key
1787        assert!(dave_devices_shared.unwrap().is_empty());
1788
1789        // @good has properly signed his devices, he should get the keys
1790        assert_eq!(good_devices_shared.unwrap().len(), 2);
1791
1792        // dan has one of his devices self signed, so should get
1793        // the key
1794        let dan_devices_shared =
1795            share_result.devices.get(KeyDistributionTestData::dan_id()).unwrap();
1796
1797        assert_eq!(dan_devices_shared.len(), 1);
1798        let dan_device_that_will_get_the_key = &dan_devices_shared[0];
1799        assert_eq!(
1800            dan_device_that_will_get_the_key.device_id().as_str(),
1801            KeyDistributionTestData::dan_signed_device_id()
1802        );
1803
1804        // Check withhelds for others
1805        let (_, code) = share_result
1806            .withheld_devices
1807            .iter()
1808            .find(|(d, _)| d.device_id() == KeyDistributionTestData::dan_unsigned_device_id())
1809            .expect("This dan's device should receive a withheld code");
1810
1811        assert_eq!(code, &WithheldCode::Unverified);
1812
1813        // Check withhelds for others
1814        let (_, code) = share_result
1815            .withheld_devices
1816            .iter()
1817            .find(|(d, _)| d.device_id() == KeyDistributionTestData::dave_device_id())
1818            .expect("This dave device should receive a withheld code");
1819
1820        assert_eq!(code, &WithheldCode::Unverified);
1821    }
1822
1823    /// Test key sharing with the identity-based strategy with different
1824    /// states of our own verification.
1825    #[async_test]
1826    async fn test_share_identity_strategy_no_cross_signing() {
1827        // Starting off, we have not yet set up our own cross-signing, so
1828        // sharing with the identity-based strategy should fail.
1829        let machine: OlmMachine = OlmMachine::new(
1830            KeyDistributionTestData::me_id(),
1831            KeyDistributionTestData::me_device_id(),
1832        )
1833        .await;
1834
1835        let keys_query = KeyDistributionTestData::dan_keys_query_response();
1836        machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
1837
1838        let fake_room_id = room_id!("!roomid:localhost");
1839
1840        let encryption_settings = identity_based_strategy_settings();
1841
1842        let request_result = machine
1843            .share_room_key(
1844                fake_room_id,
1845                iter::once(KeyDistributionTestData::dan_id()),
1846                encryption_settings.clone(),
1847            )
1848            .await;
1849
1850        assert_matches!(
1851            request_result,
1852            Err(OlmError::SessionRecipientCollectionError(
1853                SessionRecipientCollectionError::CrossSigningNotSetup
1854            ))
1855        );
1856
1857        // We now get our public cross-signing keys, but we don't trust them
1858        // yet.  In this case, sharing the keys should still fail since our own
1859        // device is still unverified.
1860        let keys_query = KeyDistributionTestData::me_keys_query_response();
1861        machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
1862
1863        let request_result = machine
1864            .share_room_key(
1865                fake_room_id,
1866                iter::once(KeyDistributionTestData::dan_id()),
1867                encryption_settings.clone(),
1868            )
1869            .await;
1870
1871        assert_matches!(
1872            request_result,
1873            Err(OlmError::SessionRecipientCollectionError(
1874                SessionRecipientCollectionError::SendingFromUnverifiedDevice
1875            ))
1876        );
1877
1878        // Finally, after we trust our own cross-signing keys, key sharing
1879        // should succeed.
1880        machine
1881            .import_cross_signing_keys(CrossSigningKeyExport {
1882                master_key: KeyDistributionTestData::MASTER_KEY_PRIVATE_EXPORT.to_owned().into(),
1883                self_signing_key: KeyDistributionTestData::SELF_SIGNING_KEY_PRIVATE_EXPORT
1884                    .to_owned()
1885                    .into(),
1886                user_signing_key: KeyDistributionTestData::USER_SIGNING_KEY_PRIVATE_EXPORT
1887                    .to_owned()
1888                    .into(),
1889            })
1890            .await
1891            .unwrap();
1892
1893        let requests = machine
1894            .share_room_key(
1895                fake_room_id,
1896                iter::once(KeyDistributionTestData::dan_id()),
1897                encryption_settings.clone(),
1898            )
1899            .await
1900            .unwrap();
1901
1902        // Dan has two devices, but only one is cross-signed, so there should
1903        // only be one key share.
1904        assert_eq!(requests.len(), 1);
1905    }
1906
1907    /// Test that identity-based key sharing gives an error when a verified
1908    /// user changes their identity, and that the key can be shared when the
1909    /// identity change is resolved.
1910    #[async_test]
1911    async fn test_share_identity_strategy_report_verification_violation() {
1912        let machine: OlmMachine = OlmMachine::new(
1913            KeyDistributionTestData::me_id(),
1914            KeyDistributionTestData::me_device_id(),
1915        )
1916        .await;
1917
1918        machine.bootstrap_cross_signing(false).await.unwrap();
1919
1920        // We will try sending a key to two different users.
1921        let user1 = IdentityChangeDataSet::user_id();
1922        let user2 = MaloIdentityChangeDataSet::user_id();
1923
1924        // We first get both users' initial device and identity keys.
1925        let keys_query = IdentityChangeDataSet::key_query_with_identity_a();
1926        machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
1927
1928        let keys_query = MaloIdentityChangeDataSet::initial_key_query();
1929        machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
1930
1931        // And then we get both user' changed identity keys.  We simulate a
1932        // verification violation by marking both users as having been
1933        // previously verified, in which case the key sharing should fail.
1934        let keys_query = IdentityChangeDataSet::key_query_with_identity_b();
1935        machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
1936        machine
1937            .get_identity(user1, None)
1938            .await
1939            .unwrap()
1940            .unwrap()
1941            .other()
1942            .unwrap()
1943            .mark_as_previously_verified()
1944            .await
1945            .unwrap();
1946
1947        let keys_query = MaloIdentityChangeDataSet::updated_key_query();
1948        machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
1949        machine
1950            .get_identity(user2, None)
1951            .await
1952            .unwrap()
1953            .unwrap()
1954            .other()
1955            .unwrap()
1956            .mark_as_previously_verified()
1957            .await
1958            .unwrap();
1959
1960        let fake_room_id = room_id!("!roomid:localhost");
1961
1962        // We share the key using the identity-based strategy.
1963        let encryption_settings = identity_based_strategy_settings();
1964
1965        let request_result = machine
1966            .share_room_key(
1967                fake_room_id,
1968                vec![user1, user2].into_iter(),
1969                encryption_settings.clone(),
1970            )
1971            .await;
1972
1973        // The key share should fail with an error indicating that recipients
1974        // were previously verified.
1975        assert_let!(
1976            Err(OlmError::SessionRecipientCollectionError(
1977                SessionRecipientCollectionError::VerifiedUserChangedIdentity(affected_users)
1978            )) = request_result
1979        );
1980        // Both our recipients should be in `affected_users`.
1981        assert_eq!(2, affected_users.len());
1982
1983        // We resolve this for user1 by withdrawing their verification.
1984        machine
1985            .get_identity(user1, None)
1986            .await
1987            .unwrap()
1988            .unwrap()
1989            .withdraw_verification()
1990            .await
1991            .unwrap();
1992
1993        // We resolve this for user2 by re-verifying.
1994        let verification_request = machine
1995            .get_identity(user2, None)
1996            .await
1997            .unwrap()
1998            .unwrap()
1999            .other()
2000            .unwrap()
2001            .verify()
2002            .await
2003            .unwrap();
2004
2005        let master_key =
2006            &machine.get_identity(user2, None).await.unwrap().unwrap().other().unwrap().master_key;
2007
2008        let my_identity = machine
2009            .get_identity(KeyDistributionTestData::me_id(), None)
2010            .await
2011            .expect("Should not fail to find own identity")
2012            .expect("Our own identity should not be missing")
2013            .own()
2014            .expect("Our own identity should be of type Own");
2015
2016        let msk = json!({ user2: serde_json::to_value(master_key).expect("Should not fail to serialize")});
2017        let ssk =
2018            serde_json::to_value(&MaloIdentityChangeDataSet::updated_key_query().self_signing_keys)
2019                .expect("Should not fail to serialize");
2020
2021        let kq_response = simulate_key_query_response_for_verification(
2022            verification_request,
2023            my_identity,
2024            KeyDistributionTestData::me_id(),
2025            user2,
2026            msk,
2027            ssk,
2028        );
2029
2030        machine
2031            .mark_request_as_sent(
2032                &TransactionId::new(),
2033                crate::types::requests::AnyIncomingResponse::KeysQuery(&kq_response),
2034            )
2035            .await
2036            .unwrap();
2037
2038        assert!(machine.get_identity(user2, None).await.unwrap().unwrap().is_verified());
2039
2040        // And now the key share should succeed.
2041        machine
2042            .share_room_key(
2043                fake_room_id,
2044                vec![user1, user2].into_iter(),
2045                encryption_settings.clone(),
2046            )
2047            .await
2048            .unwrap();
2049    }
2050
2051    #[async_test]
2052    async fn test_should_rotate_based_on_visibility() {
2053        let machine = test_machine().await;
2054        import_known_users_to_test_machine(&machine).await;
2055
2056        let strategy = CollectStrategy::AllDevices;
2057
2058        let encryption_settings = EncryptionSettings {
2059            sharing_strategy: strategy.clone(),
2060            history_visibility: HistoryVisibility::Invited,
2061            ..Default::default()
2062        };
2063
2064        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
2065
2066        let _ = collect_session_recipients(
2067            machine.store(),
2068            vec![KeyDistributionTestData::dan_id()].into_iter(),
2069            &encryption_settings,
2070            &group_session,
2071        )
2072        .await
2073        .unwrap();
2074
2075        // Try to share again with updated history visibility
2076        let encryption_settings = EncryptionSettings {
2077            sharing_strategy: strategy.clone(),
2078            history_visibility: HistoryVisibility::Shared,
2079            ..Default::default()
2080        };
2081
2082        let share_result = collect_session_recipients(
2083            machine.store(),
2084            vec![KeyDistributionTestData::dan_id()].into_iter(),
2085            &encryption_settings,
2086            &group_session,
2087        )
2088        .await
2089        .unwrap();
2090
2091        assert!(share_result.should_rotate);
2092    }
2093
2094    /// Test that the session is rotated when a device is removed from the
2095    /// recipients. In that case we simulate that dan has logged out one of
2096    /// his devices.
2097    #[async_test]
2098    async fn test_should_rotate_based_on_device_excluded() {
2099        let machine = test_machine().await;
2100        import_known_users_to_test_machine(&machine).await;
2101
2102        let encryption_settings = all_devices_strategy_settings();
2103        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
2104        let sender_key = machine.identity_keys().curve25519;
2105
2106        group_session
2107            .mark_shared_with(
2108                KeyDistributionTestData::dan_id(),
2109                KeyDistributionTestData::dan_signed_device_id(),
2110                sender_key,
2111            )
2112            .await;
2113        group_session
2114            .mark_shared_with(
2115                KeyDistributionTestData::dan_id(),
2116                KeyDistributionTestData::dan_unsigned_device_id(),
2117                sender_key,
2118            )
2119            .await;
2120
2121        // Try to share again after dan has removed one of his devices
2122        let keys_query = KeyDistributionTestData::dan_keys_query_response_device_loggedout();
2123        machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
2124
2125        // share again
2126        let share_result = collect_session_recipients(
2127            machine.store(),
2128            vec![KeyDistributionTestData::dan_id()].into_iter(),
2129            &encryption_settings,
2130            &group_session,
2131        )
2132        .await
2133        .unwrap();
2134
2135        assert!(share_result.should_rotate);
2136    }
2137
2138    /// Test that the session is rotated if a devices has a pending
2139    /// to-device request that would share the keys with it.
2140    #[async_test]
2141    async fn test_should_rotate_based_on_device_with_pending_request_excluded() {
2142        let machine = test_machine().await;
2143        import_known_users_to_test_machine(&machine).await;
2144
2145        let encryption_settings = all_devices_strategy_settings();
2146        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
2147        let sender_key = machine.identity_keys().curve25519;
2148
2149        let dan_user = KeyDistributionTestData::dan_id();
2150        let dan_dev1 = KeyDistributionTestData::dan_signed_device_id();
2151        let dan_dev2 = KeyDistributionTestData::dan_unsigned_device_id();
2152
2153        // Share the session with device 1
2154        group_session.mark_shared_with(dan_user, dan_dev1, sender_key).await;
2155
2156        {
2157            // Add a pending request to share with device 2
2158            let share_infos = BTreeMap::from([(
2159                dan_user.to_owned(),
2160                BTreeMap::from([(
2161                    dan_dev2.to_owned(),
2162                    ShareInfo::new_shared(sender_key, 0, SequenceNumber::default()),
2163                )]),
2164            )]);
2165
2166            let txid = TransactionId::new();
2167            let req = Arc::new(ToDeviceRequest::for_recipients(
2168                dan_user,
2169                vec![dan_dev2.to_owned()],
2170                &ruma::events::AnyToDeviceEventContent::Dummy(ToDeviceDummyEventContent),
2171                txid.clone(),
2172            ));
2173            group_session.add_request(txid, req, share_infos);
2174        }
2175
2176        // Remove device 2
2177        let keys_query = KeyDistributionTestData::dan_keys_query_response_device_loggedout();
2178        machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
2179
2180        // Share again
2181        let share_result = collect_session_recipients(
2182            machine.store(),
2183            vec![KeyDistributionTestData::dan_id()].into_iter(),
2184            &encryption_settings,
2185            &group_session,
2186        )
2187        .await
2188        .unwrap();
2189
2190        assert!(share_result.should_rotate);
2191    }
2192
2193    /// Test that the session is not rotated if a devices is removed
2194    /// but was already withheld from receiving the session.
2195    #[async_test]
2196    async fn test_should_not_rotate_if_keys_were_withheld() {
2197        let machine = test_machine().await;
2198        import_known_users_to_test_machine(&machine).await;
2199
2200        let encryption_settings = all_devices_strategy_settings();
2201        let group_session = create_test_outbound_group_session(&machine, &encryption_settings);
2202        let fake_room_id = group_session.room_id();
2203
2204        // Because we don't have Olm sessions initialized, this will contain
2205        // withheld requests for both of Dan's devices
2206        let requests = machine
2207            .share_room_key(
2208                fake_room_id,
2209                vec![KeyDistributionTestData::dan_id()].into_iter(),
2210                encryption_settings.clone(),
2211            )
2212            .await
2213            .unwrap();
2214
2215        for r in requests {
2216            machine
2217                .inner
2218                .group_session_manager
2219                .mark_request_as_sent(r.as_ref().txn_id.as_ref())
2220                .await
2221                .unwrap();
2222        }
2223
2224        // Try to share again after dan has removed one of his devices
2225        let keys_query = KeyDistributionTestData::dan_keys_query_response_device_loggedout();
2226        machine.mark_request_as_sent(&TransactionId::new(), &keys_query).await.unwrap();
2227
2228        // share again
2229        let share_result = collect_session_recipients(
2230            machine.store(),
2231            vec![KeyDistributionTestData::dan_id()].into_iter(),
2232            &encryption_settings,
2233            &group_session,
2234        )
2235        .await
2236        .unwrap();
2237
2238        assert!(!share_result.should_rotate);
2239    }
2240
2241    /// Common setup for tests which require a verified user to have unsigned
2242    /// devices.
2243    ///
2244    /// Returns an `OlmMachine` which is properly configured with trusted
2245    /// cross-signing keys. Also imports a set of keys for
2246    /// Bob ([`VerificationViolationTestData::bob_id`]), where Bob is verified
2247    /// and has 2 devices, one signed and the other not.
2248    async fn unsigned_of_verified_setup() -> OlmMachine {
2249        use test_json::keys_query_sets::VerificationViolationTestData as DataSet;
2250
2251        let machine = OlmMachine::new(DataSet::own_id(), device_id!("LOCAL")).await;
2252
2253        // Tell the OlmMachine about our own public keys.
2254        let own_keys = DataSet::own_keys_query_response_1();
2255        machine.mark_request_as_sent(&TransactionId::new(), &own_keys).await.unwrap();
2256
2257        // Import the secret parts of our own cross-signing keys.
2258        machine
2259            .import_cross_signing_keys(CrossSigningKeyExport {
2260                master_key: DataSet::MASTER_KEY_PRIVATE_EXPORT.to_owned().into(),
2261                self_signing_key: DataSet::SELF_SIGNING_KEY_PRIVATE_EXPORT.to_owned().into(),
2262                user_signing_key: DataSet::USER_SIGNING_KEY_PRIVATE_EXPORT.to_owned().into(),
2263            })
2264            .await
2265            .unwrap();
2266
2267        // Tell the OlmMachine about Bob's keys.
2268        let bob_keys = DataSet::bob_keys_query_response_signed();
2269        machine.mark_request_as_sent(&TransactionId::new(), &bob_keys).await.unwrap();
2270
2271        // Double-check the state of Bob: he should be verified, and should have one
2272        // signed and one unsigned device.
2273        let bob_identity = machine.get_identity(DataSet::bob_id(), None).await.unwrap().unwrap();
2274        assert!(bob_identity.other().unwrap().is_verified());
2275
2276        let bob_signed_device = machine
2277            .get_device(DataSet::bob_id(), DataSet::bob_device_1_id(), None)
2278            .await
2279            .unwrap()
2280            .unwrap();
2281        assert!(bob_signed_device.is_verified());
2282        assert!(bob_signed_device.device_owner_identity.is_some());
2283
2284        let bob_unsigned_device = machine
2285            .get_device(DataSet::bob_id(), DataSet::bob_device_2_id(), None)
2286            .await
2287            .unwrap()
2288            .unwrap();
2289        assert!(!bob_unsigned_device.is_verified());
2290
2291        machine
2292    }
2293
2294    /// [`EncryptionSettings`] with [`CollectStrategy::AllDevices`]
2295    fn all_devices_strategy_settings() -> EncryptionSettings {
2296        EncryptionSettings { sharing_strategy: CollectStrategy::AllDevices, ..Default::default() }
2297    }
2298
2299    /// [`EncryptionSettings`] with
2300    /// [`CollectStrategy::ErrorOnVerifiedUserProblem`]
2301    fn error_on_verification_problem_encryption_settings() -> EncryptionSettings {
2302        EncryptionSettings {
2303            sharing_strategy: CollectStrategy::ErrorOnVerifiedUserProblem,
2304            ..Default::default()
2305        }
2306    }
2307
2308    /// [`EncryptionSettings`] with [`CollectStrategy::IdentityBasedStrategy`]
2309    fn identity_based_strategy_settings() -> EncryptionSettings {
2310        EncryptionSettings {
2311            sharing_strategy: CollectStrategy::IdentityBasedStrategy,
2312            ..Default::default()
2313        }
2314    }
2315
2316    /// Create an [`OutboundGroupSession`], backed by the given olm machine,
2317    /// without sharing it.
2318    fn create_test_outbound_group_session(
2319        machine: &OlmMachine,
2320        encryption_settings: &EncryptionSettings,
2321    ) -> OutboundGroupSession {
2322        OutboundGroupSession::new(
2323            machine.device_id().into(),
2324            Arc::new(machine.identity_keys()),
2325            room_id!("!roomid:localhost"),
2326            encryption_settings.clone(),
2327        )
2328        .expect("creating an outbound group session should not fail")
2329    }
2330}