matrix_sdk_crypto/olm/group_sessions/
forwarder_data.rs

1// Copyright 2025 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 ruma::{DeviceId, UserId};
16use serde::{Deserialize, Serialize};
17
18use crate::olm::{KnownSenderData, SenderData};
19
20/// Represents information about the user who forwarded a megolm session under
21/// [MSC4268]. This is similar to [`SenderData`], but it is limited to variants
22/// where a cross-signing key has been received from the forwarder, as specified
23/// in the MSC.
24///
25/// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub enum ForwarderData {
28    /// We have found proof that this user, with this cross-signing key, sent
29    /// the to-device message that established this session, but we have not yet
30    /// verified the cross-signing key.
31    ///
32    /// See [SenderData::SenderUnverified].
33    SenderUnverified(KnownSenderData),
34
35    /// We have found proof that this user, with this cross-signing key, sent
36    /// the to-device message that established this session, and we have
37    /// verified the cross-signing key.
38    ///
39    /// See [SenderData::SenderVerified].
40    SenderVerified(KnownSenderData),
41}
42
43impl TryFrom<&SenderData> for ForwarderData {
44    type Error = ();
45
46    fn try_from(value: &SenderData) -> Result<Self, Self::Error> {
47        // The sender's device must be either `SenderData::SenderUnverified` (i.e.,
48        // TOFU-trusted) or `SenderData::SenderVerified` (i.e., fully verified
49        // via user verification and cross-signing).
50        match value {
51            SenderData::SenderUnverified(known_sender_data) => {
52                Ok(Self::SenderUnverified(known_sender_data.clone()))
53            }
54            SenderData::SenderVerified(known_sender_data) => {
55                Ok(Self::SenderVerified(known_sender_data.clone()))
56            }
57            _ => Err(()),
58        }
59    }
60}
61
62impl ForwarderData {
63    /// Return the user ID of the associated forwarder.
64    pub fn user_id(&self) -> &UserId {
65        match &self {
66            ForwarderData::SenderUnverified(known_sender_data)
67            | ForwarderData::SenderVerified(known_sender_data) => {
68                known_sender_data.user_id.as_ref()
69            }
70        }
71    }
72
73    /// Return the device ID of the associated forwarder, if available.
74    pub fn device_id(&self) -> Option<&DeviceId> {
75        match &self {
76            ForwarderData::SenderUnverified(known_sender_data)
77            | ForwarderData::SenderVerified(known_sender_data) => {
78                known_sender_data.device_id.as_deref()
79            }
80        }
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use insta::assert_json_snapshot;
87    use ruma::{device_id, owned_user_id, user_id};
88    use vodozemac::Ed25519PublicKey;
89
90    use super::ForwarderData;
91    use crate::olm::{KnownSenderData, SenderData};
92
93    #[test]
94    fn snapshot_forwarder_data() {
95        insta::with_settings!({prepend_module_to_snapshot => false}, {
96            assert_json_snapshot!(ForwarderData::SenderUnverified(KnownSenderData {
97                user_id: owned_user_id!("@foo:bar.baz"),
98                device_id: None,
99                master_key: Box::new(Ed25519PublicKey::from_slice(&[1u8; 32]).unwrap()),
100            }));
101            assert_json_snapshot!(ForwarderData::SenderVerified(KnownSenderData {
102                user_id: owned_user_id!("@foo:bar.baz"),
103                device_id: None,
104                master_key: Box::new(Ed25519PublicKey::from_slice(&[1u8; 32]).unwrap()),
105            }));
106        });
107    }
108
109    // A previous version of this implementation used [`SenderData`] instead of
110    // [`ForwarderData`]. To ensure we can still treat existing serialised data as
111    // `ForwarderData`, we check we can deserialise the old variants.
112    #[test]
113    fn test_deserialize_old_format() {
114        let master_key = Ed25519PublicKey::from_slice(&[1u8; 32]).unwrap();
115
116        let sender_unverified =
117            SenderData::sender_unverified(user_id!("@u:s.co"), device_id!("DEV"), master_key);
118        let sender_verified =
119            SenderData::sender_verified(user_id!("@u:s.co"), device_id!("DEV"), master_key);
120
121        let serialized_unverified = serde_json::to_string(&sender_unverified).unwrap();
122        let serialized_verified = serde_json::to_string(&sender_verified).unwrap();
123
124        let deserialized_unverified: ForwarderData =
125            serde_json::from_str(&serialized_unverified).unwrap();
126        let deserialized_verified: ForwarderData =
127            serde_json::from_str(&serialized_verified).unwrap();
128
129        assert!(matches!(deserialized_unverified, ForwarderData::SenderUnverified(_)));
130        assert!(matches!(deserialized_verified, ForwarderData::SenderVerified(_)));
131    }
132
133    #[test]
134    fn test_try_from_senderdata() {
135        let master_key = Ed25519PublicKey::from_slice(&[1u8; 32]).unwrap();
136
137        let sender_unverified = SenderData::sender_unverified(
138            user_id!("@test:example.org"),
139            device_id!("TEST_DEV"),
140            master_key,
141        );
142        let sender_verified = SenderData::sender_verified(
143            user_id!("@test:example.org"),
144            device_id!("TEST_DEV"),
145            master_key,
146        );
147
148        let forwarder_unverified = ForwarderData::try_from(&sender_unverified).unwrap();
149        let forwarder_verified = ForwarderData::try_from(&sender_verified).unwrap();
150
151        assert!(matches!(forwarder_unverified, ForwarderData::SenderUnverified(_)));
152        assert!(matches!(forwarder_verified, ForwarderData::SenderVerified(_)));
153
154        assert!(
155            ForwarderData::try_from(&SenderData::unknown()).is_err(),
156            "We should not be able to convert a non-trusted SenderData to a ForwarderData"
157        );
158    }
159}