Skip to main content

matrix_sdk_crypto/olm/
mod.rs

1// Copyright 2020 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
15//! The crypto specific Olm objects.
16//!
17//! Note: You'll only be interested in these if you are implementing a custom
18//! `CryptoStore`.
19
20mod account;
21mod group_sessions;
22mod session;
23mod signing;
24pub(crate) mod utility;
25
26pub use account::{Account, OlmMessageHash, PickledAccount, StaticAccountData};
27pub(crate) use account::{OlmDecryptionInfo, SessionType};
28pub use group_sessions::{
29    BackedUpRoomKey, EncryptionSettings, ExportedRoomKey, ForwarderData, InboundGroupSession,
30    KnownSenderData, OutboundGroupSession, OutboundGroupSessionEncryptionResult,
31    PickledInboundGroupSession, PickledOutboundGroupSession, SenderData, SenderDataType,
32    SessionCreationError, SessionExportError, SessionKey, ShareInfo,
33};
34pub(crate) use group_sessions::{
35    ShareState,
36    sender_data_finder::{self, SenderDataFinder},
37};
38pub use session::{PickledSession, Session};
39pub use signing::{CrossSigningStatus, PickledCrossSigningIdentity, PrivateCrossSigningIdentity};
40pub(crate) use utility::{SignedJsonObject, VerifyJson};
41pub use vodozemac::{Curve25519PublicKey, olm::IdentityKeys};
42
43#[cfg(test)]
44pub(crate) mod tests {
45    use assert_matches::assert_matches;
46    use matrix_sdk_test::{async_test, message_like_event_content};
47    use ruma::{
48        DeviceId, UserId, device_id, event_id,
49        events::{
50            AnyMessageLikeEvent, AnyTimelineEvent, MessageLikeEvent,
51            relation::Replacement,
52            room::message::{Relation, RoomMessageEventContent},
53        },
54        room_id,
55        serde::Raw,
56        user_id,
57    };
58    use serde_json::{Value, from_value, json};
59    use vodozemac::olm::{OlmMessage, SessionConfig};
60
61    use crate::{
62        olm::{Account, ExportedRoomKey, InboundGroupSession, Session},
63        types::events::{
64            forwarded_room_key::ForwardedRoomKeyContent, room::encrypted::EncryptedEvent,
65        },
66        utilities::json_convert,
67    };
68
69    fn alice_id() -> &'static UserId {
70        user_id!("@alice:example.org")
71    }
72
73    fn alice_device_id() -> &'static DeviceId {
74        device_id!("ALICEDEVICE")
75    }
76
77    fn bob_id() -> &'static UserId {
78        user_id!("@bob:example.org")
79    }
80
81    fn bob_device_id() -> &'static DeviceId {
82        device_id!("BOBDEVICE")
83    }
84
85    pub(crate) fn get_account_and_session_test_helper() -> (Account, Session) {
86        let alice = Account::with_device_id(alice_id(), alice_device_id());
87        let mut bob = Account::with_device_id(bob_id(), bob_device_id());
88
89        bob.generate_one_time_keys(1);
90        let one_time_key = *bob.one_time_keys().values().next().unwrap();
91        let sender_key = bob.identity_keys().curve25519;
92        let session = alice
93            .create_outbound_session_helper(
94                SessionConfig::default(),
95                sender_key,
96                one_time_key,
97                false,
98                alice.device_keys(),
99            )
100            .unwrap();
101
102        (alice, session)
103    }
104
105    #[test]
106    fn test_account_creation() {
107        let mut account = Account::with_device_id(alice_id(), alice_device_id());
108
109        assert!(!account.shared());
110
111        account.mark_as_shared();
112        assert!(account.shared());
113    }
114
115    #[test]
116    fn test_one_time_keys_creation() {
117        let mut account = Account::with_device_id(alice_id(), alice_device_id());
118        let one_time_keys = account.one_time_keys();
119
120        assert!(!one_time_keys.is_empty());
121        assert_ne!(account.max_one_time_keys(), 0);
122
123        account.generate_one_time_keys(10);
124        let one_time_keys = account.one_time_keys();
125
126        assert_ne!(one_time_keys.values().len(), 0);
127        assert_ne!(one_time_keys.keys().len(), 0);
128        assert_ne!(one_time_keys.iter().len(), 0);
129
130        account.mark_keys_as_published();
131        let one_time_keys = account.one_time_keys();
132        assert!(one_time_keys.is_empty());
133    }
134
135    #[async_test]
136    async fn test_session_creation() {
137        let mut alice = Account::with_device_id(alice_id(), alice_device_id());
138        let bob = Account::with_device_id(bob_id(), bob_device_id());
139        let alice_keys = alice.identity_keys();
140        alice.generate_one_time_keys(1);
141        let one_time_keys = alice.one_time_keys();
142        alice.mark_keys_as_published();
143
144        let one_time_key = *one_time_keys.values().next().unwrap();
145
146        #[cfg(not(feature = "experimental-algorithms"))]
147        let config = SessionConfig::version_1();
148
149        #[cfg(feature = "experimental-algorithms")]
150        let config = SessionConfig::version_2();
151
152        let mut bob_session = bob
153            .create_outbound_session_helper(
154                config,
155                alice_keys.curve25519,
156                one_time_key,
157                false,
158                bob.device_keys(),
159            )
160            .unwrap();
161
162        let plaintext = "Hello world";
163
164        let message = bob_session.encrypt_helper(plaintext).await.unwrap();
165
166        let prekey_message = match message {
167            OlmMessage::PreKey(m) => m,
168            OlmMessage::Normal(_) => panic!("Incorrect message type"),
169        };
170
171        let bob_keys = bob.identity_keys();
172        let result = alice
173            .create_inbound_session(bob_keys.curve25519, alice.device_keys(), &prekey_message)
174            .unwrap();
175
176        assert_eq!(bob_session.session_id(), result.session.session_id());
177
178        assert_eq!(plaintext, result.plaintext);
179    }
180
181    #[async_test]
182    async fn test_group_session_creation() {
183        let alice = Account::with_device_id(alice_id(), alice_device_id());
184        let room_id = room_id!("!test:localhost");
185
186        let (outbound, inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
187
188        assert_eq!(0, outbound.message_index().await);
189        assert!(!outbound.shared());
190        outbound.mark_as_shared();
191        assert!(outbound.shared());
192
193        assert_eq!(0, inbound.first_known_index());
194        assert_eq!(outbound.session_id(), inbound.session_id());
195
196        let plaintext = "This is a secret to everybody".to_owned();
197        let ciphertext = outbound.encrypt_helper(plaintext.clone()).await;
198
199        assert_eq!(
200            plaintext.as_bytes(),
201            inbound.decrypt_helper(&ciphertext).await.unwrap().plaintext
202        );
203    }
204
205    #[async_test]
206    async fn test_edit_decryption() {
207        let alice = Account::with_device_id(alice_id(), alice_device_id());
208        let room_id = room_id!("!test:localhost");
209        let event_id = event_id!("$1234adfad:asdf");
210
211        let (outbound, inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
212
213        assert_eq!(0, outbound.message_index().await);
214        assert!(!outbound.shared());
215        outbound.mark_as_shared();
216        assert!(outbound.shared());
217
218        let mut content = RoomMessageEventContent::text_plain("Hello");
219        content.relates_to = Some(Relation::Replacement(Replacement::new(
220            event_id.to_owned(),
221            RoomMessageEventContent::text_plain("Hello edit").into(),
222        )));
223
224        assert_eq!(0, inbound.first_known_index());
225        assert_eq!(outbound.session_id(), inbound.session_id());
226
227        let result = outbound.encrypt("m.room.message", &Raw::new(&content).unwrap().cast()).await;
228
229        let event = json!({
230            "sender": alice.user_id(),
231            "event_id": event_id,
232            "origin_server_ts": 0u64,
233            "room_id": room_id,
234            "type": "m.room.encrypted",
235            "content": result.content,
236        });
237
238        let event = json_convert(&event).unwrap();
239        let decrypted = inbound.decrypt(&event).await.unwrap().0;
240
241        if let AnyTimelineEvent::MessageLike(AnyMessageLikeEvent::RoomMessage(
242            MessageLikeEvent::Original(e),
243        )) = from_value(decrypted.into()).unwrap()
244        {
245            assert_matches!(e.content.relates_to, Some(Relation::Replacement(_)));
246        } else {
247            panic!("Invalid event type");
248        }
249    }
250
251    #[async_test]
252    async fn test_relates_to_decryption() {
253        let alice = Account::with_device_id(alice_id(), alice_device_id());
254        let room_id = room_id!("!test:localhost");
255        let event_id = event_id!("$1234adfad:asdf");
256
257        let relation_json = json!({
258            "rel_type": "m.reference",
259            "event_id": "$WUreEJERkFzO8i2dk6CmTex01cP1dZ4GWKhKCwkWHrQ"
260        });
261
262        let (outbound, inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
263
264        // We first test that we're copying the relation from the content that
265        // will be encrypted to the content that will stay plaintext.
266        let content = message_like_event_content!({
267            "m.relates_to": relation_json,
268        });
269        let result = outbound.encrypt("m.dummy", &content).await;
270
271        let event = json!({
272            "sender": alice.user_id(),
273            "event_id": event_id,
274            "origin_server_ts": 0u64,
275            "room_id": room_id,
276            "type": "m.room.encrypted",
277            "content": result.content,
278        });
279        let event: EncryptedEvent = json_convert(&event).unwrap();
280
281        assert_eq!(
282            event.content.relates_to.as_ref(),
283            Some(&relation_json),
284            "The encrypted event should contain an unencrypted relation"
285        );
286
287        let (decrypted, _) = inbound.decrypt(&event).await.unwrap();
288
289        let decrypted: Value = json_convert(&decrypted).unwrap();
290        let relation = decrypted.get("content").and_then(|c| c.get("m.relates_to"));
291        assert_eq!(relation, Some(&relation_json), "The decrypted event should contain a relation");
292
293        let content = message_like_event_content!({});
294        let result = outbound.encrypt("m.dummy", &content).await;
295        let mut encrypted: Value = json_convert(&result.content).unwrap();
296        encrypted.as_object_mut().unwrap().insert("m.relates_to".to_owned(), relation_json.clone());
297
298        // Let's now test if we copy the correct relation if there is no
299        // relation in the encrypted content.
300        let event = json!({
301            "sender": alice.user_id(),
302            "event_id": event_id,
303            "origin_server_ts": 0u64,
304            "room_id": room_id,
305            "type": "m.room.encrypted",
306            "content": encrypted,
307        });
308        let event: EncryptedEvent = json_convert(&event).unwrap();
309
310        assert_eq!(
311            event.content.relates_to,
312            Some(relation_json),
313            "The encrypted event should contain an unencrypted relation"
314        );
315
316        let (decrypted, _) = inbound.decrypt(&event).await.unwrap();
317
318        let decrypted: Value = json_convert(&decrypted).unwrap();
319        let relation = decrypted.get("content").and_then(|c| c.get("m.relates_to"));
320        assert!(relation.is_some(), "The decrypted event should contain a relation");
321    }
322
323    #[async_test]
324    async fn test_group_session_export() {
325        let alice = Account::with_device_id(alice_id(), alice_device_id());
326        let room_id = room_id!("!test:localhost");
327
328        let (_, inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
329
330        let export = inbound.export().await;
331        let export: ForwardedRoomKeyContent = export.try_into().unwrap();
332        let export = ExportedRoomKey::try_from(export).unwrap();
333
334        let imported = InboundGroupSession::from_export(&export)
335            .expect("We can always import an inbound group session from a fresh export");
336
337        assert_eq!(inbound.session_id(), imported.session_id());
338    }
339}