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