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