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, 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 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 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}