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, InboundGroupSession, KnownSenderData,
30    OutboundGroupSession, OutboundGroupSessionEncryptionResult, PickledInboundGroupSession,
31    PickledOutboundGroupSession, SenderData, SenderDataType, SessionCreationError,
32    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.create_outbound_session_helper(
93            SessionConfig::default(),
94            sender_key,
95            one_time_key,
96            false,
97            alice.device_keys(),
98        );
99
100        (alice, session)
101    }
102
103    #[test]
104    fn test_account_creation() {
105        let mut account = Account::with_device_id(alice_id(), alice_device_id());
106
107        assert!(!account.shared());
108
109        account.mark_as_shared();
110        assert!(account.shared());
111    }
112
113    #[test]
114    fn test_one_time_keys_creation() {
115        let mut account = Account::with_device_id(alice_id(), alice_device_id());
116        let one_time_keys = account.one_time_keys();
117
118        assert!(!one_time_keys.is_empty());
119        assert_ne!(account.max_one_time_keys(), 0);
120
121        account.generate_one_time_keys(10);
122        let one_time_keys = account.one_time_keys();
123
124        assert_ne!(one_time_keys.values().len(), 0);
125        assert_ne!(one_time_keys.keys().len(), 0);
126        assert_ne!(one_time_keys.iter().len(), 0);
127
128        account.mark_keys_as_published();
129        let one_time_keys = account.one_time_keys();
130        assert!(one_time_keys.is_empty());
131    }
132
133    #[async_test]
134    async fn test_session_creation() {
135        let mut alice = Account::with_device_id(alice_id(), alice_device_id());
136        let bob = Account::with_device_id(bob_id(), bob_device_id());
137        let alice_keys = alice.identity_keys();
138        alice.generate_one_time_keys(1);
139        let one_time_keys = alice.one_time_keys();
140        alice.mark_keys_as_published();
141
142        let one_time_key = *one_time_keys.values().next().unwrap();
143
144        let mut bob_session = bob.create_outbound_session_helper(
145            SessionConfig::default(),
146            alice_keys.curve25519,
147            one_time_key,
148            false,
149            bob.device_keys(),
150        );
151
152        let plaintext = "Hello world";
153
154        let message = bob_session.encrypt_helper(plaintext).await;
155
156        let prekey_message = match message {
157            OlmMessage::PreKey(m) => m,
158            OlmMessage::Normal(_) => panic!("Incorrect message type"),
159        };
160
161        let bob_keys = bob.identity_keys();
162        let result = alice
163            .create_inbound_session(bob_keys.curve25519, alice.device_keys(), &prekey_message)
164            .unwrap();
165
166        assert_eq!(bob_session.session_id(), result.session.session_id());
167
168        assert_eq!(plaintext, result.plaintext);
169    }
170
171    #[async_test]
172    async fn test_group_session_creation() {
173        let alice = Account::with_device_id(alice_id(), alice_device_id());
174        let room_id = room_id!("!test:localhost");
175
176        let (outbound, inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
177
178        assert_eq!(0, outbound.message_index().await);
179        assert!(!outbound.shared());
180        outbound.mark_as_shared();
181        assert!(outbound.shared());
182
183        assert_eq!(0, inbound.first_known_index());
184        assert_eq!(outbound.session_id(), inbound.session_id());
185
186        let plaintext = "This is a secret to everybody".to_owned();
187        let ciphertext = outbound.encrypt_helper(plaintext.clone()).await;
188
189        assert_eq!(
190            plaintext.as_bytes(),
191            inbound.decrypt_helper(&ciphertext).await.unwrap().plaintext
192        );
193    }
194
195    #[async_test]
196    async fn test_edit_decryption() {
197        let alice = Account::with_device_id(alice_id(), alice_device_id());
198        let room_id = room_id!("!test:localhost");
199        let event_id = event_id!("$1234adfad:asdf");
200
201        let (outbound, inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
202
203        assert_eq!(0, outbound.message_index().await);
204        assert!(!outbound.shared());
205        outbound.mark_as_shared();
206        assert!(outbound.shared());
207
208        let mut content = RoomMessageEventContent::text_plain("Hello");
209        content.relates_to = Some(Relation::Replacement(Replacement::new(
210            event_id.to_owned(),
211            RoomMessageEventContent::text_plain("Hello edit").into(),
212        )));
213
214        assert_eq!(0, inbound.first_known_index());
215        assert_eq!(outbound.session_id(), inbound.session_id());
216
217        let result = outbound.encrypt("m.room.message", &Raw::new(&content).unwrap().cast()).await;
218
219        let event = json!({
220            "sender": alice.user_id(),
221            "event_id": event_id,
222            "origin_server_ts": 0u64,
223            "room_id": room_id,
224            "type": "m.room.encrypted",
225            "content": result.content,
226        });
227
228        let event = json_convert(&event).unwrap();
229        let decrypted = inbound.decrypt(&event).await.unwrap().0;
230
231        if let AnyTimelineEvent::MessageLike(AnyMessageLikeEvent::RoomMessage(
232            MessageLikeEvent::Original(e),
233        )) = from_value(decrypted.into()).unwrap()
234        {
235            assert_matches!(e.content.relates_to, Some(Relation::Replacement(_)));
236        } else {
237            panic!("Invalid event type");
238        }
239    }
240
241    #[async_test]
242    async fn test_relates_to_decryption() {
243        let alice = Account::with_device_id(alice_id(), alice_device_id());
244        let room_id = room_id!("!test:localhost");
245        let event_id = event_id!("$1234adfad:asdf");
246
247        let relation_json = json!({
248            "rel_type": "m.reference",
249            "event_id": "$WUreEJERkFzO8i2dk6CmTex01cP1dZ4GWKhKCwkWHrQ"
250        });
251
252        let (outbound, inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
253
254        // We first test that we're copying the relation from the content that
255        // will be encrypted to the content that will stay plaintext.
256        let content = message_like_event_content!({
257            "m.relates_to": relation_json,
258        });
259        let result = outbound.encrypt("m.dummy", &content).await;
260
261        let event = json!({
262            "sender": alice.user_id(),
263            "event_id": event_id,
264            "origin_server_ts": 0u64,
265            "room_id": room_id,
266            "type": "m.room.encrypted",
267            "content": result.content,
268        });
269        let event: EncryptedEvent = json_convert(&event).unwrap();
270
271        assert_eq!(
272            event.content.relates_to.as_ref(),
273            Some(&relation_json),
274            "The encrypted event should contain an unencrypted relation"
275        );
276
277        let (decrypted, _) = inbound.decrypt(&event).await.unwrap();
278
279        let decrypted: Value = json_convert(&decrypted).unwrap();
280        let relation = decrypted.get("content").and_then(|c| c.get("m.relates_to"));
281        assert_eq!(relation, Some(&relation_json), "The decrypted event should contain a relation");
282
283        let content = message_like_event_content!({});
284        let result = outbound.encrypt("m.dummy", &content).await;
285        let mut encrypted: Value = json_convert(&result.content).unwrap();
286        encrypted.as_object_mut().unwrap().insert("m.relates_to".to_owned(), relation_json.clone());
287
288        // Let's now test if we copy the correct relation if there is no
289        // relation in the encrypted content.
290        let event = json!({
291            "sender": alice.user_id(),
292            "event_id": event_id,
293            "origin_server_ts": 0u64,
294            "room_id": room_id,
295            "type": "m.room.encrypted",
296            "content": encrypted,
297        });
298        let event: EncryptedEvent = json_convert(&event).unwrap();
299
300        assert_eq!(
301            event.content.relates_to,
302            Some(relation_json),
303            "The encrypted event should contain an unencrypted relation"
304        );
305
306        let (decrypted, _) = inbound.decrypt(&event).await.unwrap();
307
308        let decrypted: Value = json_convert(&decrypted).unwrap();
309        let relation = decrypted.get("content").and_then(|c| c.get("m.relates_to"));
310        assert!(relation.is_some(), "The decrypted event should contain a relation");
311    }
312
313    #[async_test]
314    async fn test_group_session_export() {
315        let alice = Account::with_device_id(alice_id(), alice_device_id());
316        let room_id = room_id!("!test:localhost");
317
318        let (_, inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
319
320        let export = inbound.export().await;
321        let export: ForwardedRoomKeyContent = export.try_into().unwrap();
322        let export = ExportedRoomKey::try_from(export).unwrap();
323
324        let imported = InboundGroupSession::from_export(&export)
325            .expect("We can always import an inbound group session from a fresh export");
326
327        assert_eq!(inbound.session_id(), imported.session_id());
328    }
329}