1mod 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 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 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}