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;
24mod utility;
25
26pub use account::{Account, OlmMessageHash, PickledAccount, StaticAccountData};
27pub(crate) use account::{OlmDecryptionInfo, SessionType};
28pub(crate) use group_sessions::{
29    sender_data_finder::{self, SenderDataFinder},
30    ShareState,
31};
32pub use group_sessions::{
33    BackedUpRoomKey, EncryptionSettings, ExportedRoomKey, InboundGroupSession, KnownSenderData,
34    OutboundGroupSession, PickledInboundGroupSession, PickledOutboundGroupSession, SenderData,
35    SenderDataType, SessionCreationError, SessionExportError, SessionKey, ShareInfo,
36};
37pub use session::{PickledSession, Session};
38pub use signing::{CrossSigningStatus, PickledCrossSigningIdentity, PrivateCrossSigningIdentity};
39pub(crate) use utility::{SignedJsonObject, VerifyJson};
40pub use vodozemac::{olm::IdentityKeys, Curve25519PublicKey};
41
42#[cfg(test)]
43pub(crate) mod tests {
44    use assert_matches::assert_matches;
45    use matrix_sdk_test::{async_test, message_like_event_content};
46    use ruma::{
47        device_id, event_id,
48        events::{
49            relation::Replacement,
50            room::message::{Relation, RoomMessageEventContent},
51            AnyMessageLikeEvent, AnyTimelineEvent, MessageLikeEvent,
52        },
53        room_id,
54        serde::Raw,
55        user_id, DeviceId, UserId,
56    };
57    use serde_json::{from_value, json, Value};
58    use vodozemac::{
59        olm::{OlmMessage, SessionConfig},
60        Curve25519PublicKey, Ed25519PublicKey,
61    };
62
63    use crate::{
64        olm::{Account, ExportedRoomKey, InboundGroupSession, SenderData, Session},
65        types::events::{
66            forwarded_room_key::ForwardedRoomKeyContent, room::encrypted::EncryptedEvent,
67        },
68        utilities::json_convert,
69    };
70
71    fn alice_id() -> &'static UserId {
72        user_id!("@alice:example.org")
73    }
74
75    fn alice_device_id() -> &'static DeviceId {
76        device_id!("ALICEDEVICE")
77    }
78
79    fn bob_id() -> &'static UserId {
80        user_id!("@bob:example.org")
81    }
82
83    fn bob_device_id() -> &'static DeviceId {
84        device_id!("BOBDEVICE")
85    }
86
87    pub(crate) fn get_account_and_session_test_helper() -> (Account, Session) {
88        let alice = Account::with_device_id(alice_id(), alice_device_id());
89        let mut bob = Account::with_device_id(bob_id(), bob_device_id());
90
91        bob.generate_one_time_keys(1);
92        let one_time_key = *bob.one_time_keys().values().next().unwrap();
93        let sender_key = bob.identity_keys().curve25519;
94        let session = alice.create_outbound_session_helper(
95            SessionConfig::default(),
96            sender_key,
97            one_time_key,
98            false,
99            alice.device_keys(),
100        );
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        let mut bob_session = bob.create_outbound_session_helper(
147            SessionConfig::default(),
148            alice_keys.curve25519,
149            one_time_key,
150            false,
151            bob.device_keys(),
152        );
153
154        let plaintext = "Hello world";
155
156        let message = bob_session.encrypt_helper(plaintext).await;
157
158        let prekey_message = match message {
159            OlmMessage::PreKey(m) => m,
160            OlmMessage::Normal(_) => panic!("Incorrect message type"),
161        };
162
163        let bob_keys = bob.identity_keys();
164        let result = alice
165            .create_inbound_session(bob_keys.curve25519, alice.device_keys(), &prekey_message)
166            .unwrap();
167
168        assert_eq!(bob_session.session_id(), result.session.session_id());
169
170        assert_eq!(plaintext, result.plaintext);
171    }
172
173    #[async_test]
174    async fn test_group_session_creation() {
175        let alice = Account::with_device_id(alice_id(), alice_device_id());
176        let room_id = room_id!("!test:localhost");
177
178        let (outbound, _) = alice.create_group_session_pair_with_defaults(room_id).await;
179
180        assert_eq!(0, outbound.message_index().await);
181        assert!(!outbound.shared());
182        outbound.mark_as_shared();
183        assert!(outbound.shared());
184
185        let inbound = InboundGroupSession::new(
186            Curve25519PublicKey::from_base64("Nn0L2hkcCMFKqynTjyGsJbth7QrVmX3lbrksMkrGOAw")
187                .unwrap(),
188            Ed25519PublicKey::from_base64("ee3Ek+J2LkkPmjGPGLhMxiKnhiX//xcqaVL4RP6EypE").unwrap(),
189            room_id,
190            &outbound.session_key().await,
191            SenderData::unknown(),
192            outbound.settings().algorithm.to_owned(),
193            None,
194        )
195        .expect("We can always create an inbound group session from an outbound one");
196
197        assert_eq!(0, inbound.first_known_index());
198
199        assert_eq!(outbound.session_id(), inbound.session_id());
200
201        let plaintext = "This is a secret to everybody".to_owned();
202        let ciphertext = outbound.encrypt_helper(plaintext.clone()).await;
203
204        assert_eq!(
205            plaintext.as_bytes(),
206            inbound.decrypt_helper(&ciphertext).await.unwrap().plaintext
207        );
208    }
209
210    #[async_test]
211    async fn test_edit_decryption() {
212        let alice = Account::with_device_id(alice_id(), alice_device_id());
213        let room_id = room_id!("!test:localhost");
214        let event_id = event_id!("$1234adfad:asdf");
215
216        let (outbound, _) = alice.create_group_session_pair_with_defaults(room_id).await;
217
218        assert_eq!(0, outbound.message_index().await);
219        assert!(!outbound.shared());
220        outbound.mark_as_shared();
221        assert!(outbound.shared());
222
223        let mut content = RoomMessageEventContent::text_plain("Hello");
224        content.relates_to = Some(Relation::Replacement(Replacement::new(
225            event_id.to_owned(),
226            RoomMessageEventContent::text_plain("Hello edit").into(),
227        )));
228
229        let inbound = InboundGroupSession::new(
230            Curve25519PublicKey::from_base64("Nn0L2hkcCMFKqynTjyGsJbth7QrVmX3lbrksMkrGOAw")
231                .unwrap(),
232            Ed25519PublicKey::from_base64("ee3Ek+J2LkkPmjGPGLhMxiKnhiX//xcqaVL4RP6EypE").unwrap(),
233            room_id,
234            &outbound.session_key().await,
235            SenderData::unknown(),
236            outbound.settings().algorithm.to_owned(),
237            None,
238        )
239        .unwrap();
240
241        assert_eq!(0, inbound.first_known_index());
242
243        assert_eq!(outbound.session_id(), inbound.session_id());
244
245        let encrypted_content =
246            outbound.encrypt("m.room.message", &Raw::new(&content).unwrap().cast()).await;
247
248        let event = json!({
249            "sender": alice.user_id(),
250            "event_id": event_id,
251            "origin_server_ts": 0u64,
252            "room_id": room_id,
253            "type": "m.room.encrypted",
254            "content": encrypted_content,
255        });
256
257        let event = json_convert(&event).unwrap();
258        let decrypted = inbound.decrypt(&event).await.unwrap().0;
259
260        if let AnyTimelineEvent::MessageLike(AnyMessageLikeEvent::RoomMessage(
261            MessageLikeEvent::Original(e),
262        )) = from_value(decrypted.into()).unwrap()
263        {
264            assert_matches!(e.content.relates_to, Some(Relation::Replacement(_)));
265        } else {
266            panic!("Invalid event type")
267        }
268    }
269
270    #[async_test]
271    async fn test_relates_to_decryption() {
272        let alice = Account::with_device_id(alice_id(), alice_device_id());
273        let room_id = room_id!("!test:localhost");
274        let event_id = event_id!("$1234adfad:asdf");
275
276        let relation_json = json!({
277            "rel_type": "m.reference",
278            "event_id": "$WUreEJERkFzO8i2dk6CmTex01cP1dZ4GWKhKCwkWHrQ"
279        });
280
281        let (outbound, inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
282
283        // We first test that we're copying the relation from the content that
284        // will be encrypted to the content that will stay plaintext.
285        let content = message_like_event_content!({
286            "m.relates_to": relation_json,
287        });
288        let encrypted = outbound.encrypt("m.dummy", &content).await;
289
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.as_ref(),
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_eq!(relation, Some(&relation_json), "The decrypted event should contain a relation");
311
312        let content = message_like_event_content!({});
313        let encrypted = outbound.encrypt("m.dummy", &content).await;
314        let mut encrypted: Value = json_convert(&encrypted).unwrap();
315        encrypted.as_object_mut().unwrap().insert("m.relates_to".to_owned(), relation_json.clone());
316
317        // Let's now test if we copy the correct relation if there is no
318        // relation in the encrypted content.
319        let event = json!({
320            "sender": alice.user_id(),
321            "event_id": event_id,
322            "origin_server_ts": 0u64,
323            "room_id": room_id,
324            "type": "m.room.encrypted",
325            "content": encrypted,
326        });
327        let event: EncryptedEvent = json_convert(&event).unwrap();
328
329        assert_eq!(
330            event.content.relates_to,
331            Some(relation_json),
332            "The encrypted event should contain an unencrypted relation"
333        );
334
335        let (decrypted, _) = inbound.decrypt(&event).await.unwrap();
336
337        let decrypted: Value = json_convert(&decrypted).unwrap();
338        let relation = decrypted.get("content").and_then(|c| c.get("m.relates_to"));
339        assert!(relation.is_some(), "The decrypted event should contain a relation");
340    }
341
342    #[async_test]
343    async fn test_group_session_export() {
344        let alice = Account::with_device_id(alice_id(), alice_device_id());
345        let room_id = room_id!("!test:localhost");
346
347        let (_, inbound) = alice.create_group_session_pair_with_defaults(room_id).await;
348
349        let export = inbound.export().await;
350        let export: ForwardedRoomKeyContent = export.try_into().unwrap();
351        let export = ExportedRoomKey::try_from(export).unwrap();
352
353        let imported = InboundGroupSession::from_export(&export)
354            .expect("We can always import an inbound group session from a fresh export");
355
356        assert_eq!(inbound.session_id(), imported.session_id());
357    }
358}