1use std::{fmt, sync::Arc};
16
17use ruma::{SecondsSinceUnixEpoch, serde::Raw};
18use serde::{Deserialize, Serialize};
19use serde_json::Value;
20use tokio::sync::Mutex;
21use tracing::{Span, debug};
22use vodozemac::{
23 Curve25519PublicKey,
24 olm::{DecryptionError, OlmMessage, Session as InnerSession, SessionConfig, SessionPickle},
25};
26
27#[cfg(feature = "experimental-algorithms")]
28use crate::types::events::room::encrypted::OlmV2Curve25519AesSha2Content;
29use crate::{
30 DeviceData,
31 error::{EventError, OlmResult, SessionUnpickleError},
32 types::{
33 DeviceKeys, EventEncryptionAlgorithm,
34 events::{
35 EventType,
36 olm_v1::{DecryptedOlmV1Event, OlmV1Keys},
37 room::encrypted::{OlmV1Curve25519AesSha2Content, ToDeviceEncryptedEventContent},
38 },
39 },
40};
41
42#[derive(Clone)]
45pub struct Session {
46 pub inner: Arc<Mutex<InnerSession>>,
48 pub session_id: Arc<str>,
50 pub sender_key: Curve25519PublicKey,
52 pub our_device_keys: DeviceKeys,
54 pub created_using_fallback_key: bool,
56 pub creation_time: SecondsSinceUnixEpoch,
58 pub last_use_time: SecondsSinceUnixEpoch,
60}
61
62#[cfg(not(tarpaulin_include))]
63impl fmt::Debug for Session {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 f.debug_struct("Session")
66 .field("session_id", &self.session_id())
67 .field("sender_key", &self.sender_key)
68 .finish()
69 }
70}
71
72impl Session {
73 pub async fn decrypt(&mut self, message: &OlmMessage) -> Result<String, DecryptionError> {
82 let mut inner = self.inner.lock().await;
83 Span::current().record("session_id", inner.session_id());
84
85 let plaintext = inner.decrypt(message)?;
86 debug!(session=?inner, "Decrypted an Olm message");
87
88 let plaintext = String::from_utf8_lossy(&plaintext).to_string();
89
90 self.last_use_time = SecondsSinceUnixEpoch::now();
91
92 Ok(plaintext)
93 }
94
95 pub fn sender_key(&self) -> Curve25519PublicKey {
97 self.sender_key
98 }
99
100 pub async fn session_config(&self) -> SessionConfig {
102 self.inner.lock().await.session_config()
103 }
104
105 #[allow(clippy::unused_async)] pub async fn algorithm(&self) -> EventEncryptionAlgorithm {
108 #[cfg(feature = "experimental-algorithms")]
109 if self.session_config().await.version() == 2 {
110 EventEncryptionAlgorithm::OlmV2Curve25519AesSha2
111 } else {
112 EventEncryptionAlgorithm::OlmV1Curve25519AesSha2
113 }
114
115 #[cfg(not(feature = "experimental-algorithms"))]
116 EventEncryptionAlgorithm::OlmV1Curve25519AesSha2
117 }
118
119 pub(crate) async fn encrypt_helper(&mut self, plaintext: &str) -> OlmResult<OlmMessage> {
127 let mut session = self.inner.lock().await;
128 let message = session.encrypt(plaintext)?;
129
130 self.last_use_time = SecondsSinceUnixEpoch::now();
131 debug!(?session, "Successfully encrypted an event");
132
133 Ok(message)
134 }
135
136 pub async fn encrypt(
147 &mut self,
148 recipient_device: &DeviceData,
149 event_type: &str,
150 content: impl Serialize,
151 message_id: Option<String>,
152 ) -> OlmResult<Raw<ToDeviceEncryptedEventContent>> {
153 #[derive(Debug)]
154 struct Content<'a> {
155 event_type: &'a str,
156 content: Raw<Value>,
157 }
158
159 impl EventType for Content<'_> {
160 const EVENT_TYPE: &'static str = "";
170
171 fn event_type(&self) -> &str {
172 self.event_type
173 }
174 }
175
176 impl Serialize for Content<'_> {
177 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
178 where
179 S: serde::Serializer,
180 {
181 self.content.serialize(serializer)
182 }
183 }
184
185 let plaintext = {
186 let content = serde_json::to_value(content)?;
187 let content = Content { event_type, content: Raw::new(&content)? };
188
189 let recipient_signing_key =
190 recipient_device.ed25519_key().ok_or(EventError::MissingSigningKey)?;
191
192 let content = DecryptedOlmV1Event {
193 sender: self.our_device_keys.user_id.clone(),
194 recipient: recipient_device.user_id().into(),
195 keys: OlmV1Keys {
196 ed25519: self
197 .our_device_keys
198 .ed25519_key()
199 .expect("Our own device should have an Ed25519 public key"),
200 },
201 recipient_keys: OlmV1Keys { ed25519: recipient_signing_key },
202 sender_device_keys: Some(self.our_device_keys.clone()),
203 content,
204 };
205
206 serde_json::to_string(&content)?
207 };
208
209 let ciphertext = self.encrypt_helper(&plaintext).await?;
210
211 let content = self.build_encrypted_event(ciphertext, message_id).await?;
212 let content = Raw::new(&content)?;
213 Ok(content)
214 }
215
216 pub(crate) async fn build_encrypted_event(
225 &self,
226 ciphertext: OlmMessage,
227 message_id: Option<String>,
228 ) -> OlmResult<ToDeviceEncryptedEventContent> {
229 let content = match self.algorithm().await {
230 EventEncryptionAlgorithm::OlmV1Curve25519AesSha2 => OlmV1Curve25519AesSha2Content {
231 ciphertext,
232 recipient_key: self.sender_key,
233 sender_key: self
234 .our_device_keys
235 .curve25519_key()
236 .expect("Device doesn't have curve25519 key"),
237 message_id,
238 }
239 .into(),
240 #[cfg(feature = "experimental-algorithms")]
241 EventEncryptionAlgorithm::OlmV2Curve25519AesSha2 => OlmV2Curve25519AesSha2Content {
242 ciphertext,
243 sender_key: self
244 .our_device_keys
245 .curve25519_key()
246 .expect("Device doesn't have curve25519 key"),
247 message_id,
248 }
249 .into(),
250 _ => unreachable!(),
251 };
252
253 Ok(content)
254 }
255
256 pub fn session_id(&self) -> &str {
258 &self.session_id
259 }
260
261 pub async fn pickle(&self) -> PickledSession {
268 let pickle = self.inner.lock().await.pickle();
269
270 PickledSession {
271 pickle,
272 sender_key: self.sender_key,
273 created_using_fallback_key: self.created_using_fallback_key,
274 creation_time: self.creation_time,
275 last_use_time: self.last_use_time,
276 }
277 }
278
279 pub fn from_pickle(
289 our_device_keys: DeviceKeys,
290 pickle: PickledSession,
291 ) -> Result<Self, SessionUnpickleError> {
292 if our_device_keys.curve25519_key().is_none() {
293 return Err(SessionUnpickleError::MissingIdentityKey);
294 }
295 if our_device_keys.ed25519_key().is_none() {
296 return Err(SessionUnpickleError::MissingSigningKey);
297 }
298
299 let session: vodozemac::olm::Session = pickle.pickle.into();
300 let session_id = session.session_id();
301
302 Ok(Session {
303 inner: Arc::new(Mutex::new(session)),
304 session_id: session_id.into(),
305 created_using_fallback_key: pickle.created_using_fallback_key,
306 sender_key: pickle.sender_key,
307 our_device_keys,
308 creation_time: pickle.creation_time,
309 last_use_time: pickle.last_use_time,
310 })
311 }
312}
313
314impl PartialEq for Session {
315 fn eq(&self, other: &Self) -> bool {
316 self.session_id() == other.session_id()
317 }
318}
319
320#[derive(Serialize, Deserialize)]
325#[allow(missing_debug_implementations)]
326pub struct PickledSession {
327 pub pickle: SessionPickle,
329 pub sender_key: Curve25519PublicKey,
331 #[serde(default)]
333 pub created_using_fallback_key: bool,
334 pub creation_time: SecondsSinceUnixEpoch,
336 pub last_use_time: SecondsSinceUnixEpoch,
338}
339
340#[cfg(test)]
341mod tests {
342 use matrix_sdk_test::async_test;
343 use ruma::{device_id, user_id};
344 use serde_json::{self, Value};
345 use strass::assert_let;
346 use vodozemac::olm::{OlmMessage, SessionConfig};
347
348 use crate::{
349 identities::DeviceData,
350 olm::Account,
351 types::events::{
352 dummy::DummyEventContent, olm_v1::DecryptedOlmV1Event,
353 room::encrypted::ToDeviceEncryptedEventContent,
354 },
355 };
356
357 #[async_test]
358 async fn test_encryption_and_decryption() {
359 use ruma::events::dummy::ToDeviceDummyEventContent;
360
361 let alice =
363 Account::with_device_id(user_id!("@alice:localhost"), device_id!("ALICEDEVICE"));
364 let mut bob = Account::with_device_id(user_id!("@bob:localhost"), device_id!("BOBDEVICE"));
365
366 #[cfg(not(feature = "experimental-algorithms"))]
367 let config = SessionConfig::version_1();
368
369 #[cfg(feature = "experimental-algorithms")]
370 let config = SessionConfig::version_2();
371
372 bob.generate_one_time_keys(1);
374 let one_time_key = *bob.one_time_keys().values().next().unwrap();
375 let sender_key = bob.identity_keys().curve25519;
376 let mut alice_session = alice
377 .create_outbound_session_helper(
378 config,
379 sender_key,
380 one_time_key,
381 false,
382 alice.device_keys(),
383 )
384 .unwrap();
385
386 let alice_device = DeviceData::from_account(&alice);
387
388 let message = alice_session
390 .encrypt(&alice_device, "m.dummy", ToDeviceDummyEventContent::new(), None)
391 .await
392 .unwrap()
393 .deserialize()
394 .unwrap();
395
396 #[cfg(feature = "experimental-algorithms")]
397 assert_let!(ToDeviceEncryptedEventContent::OlmV2Curve25519AesSha2(content) = message);
398 #[cfg(not(feature = "experimental-algorithms"))]
399 assert_let!(ToDeviceEncryptedEventContent::OlmV1Curve25519AesSha2(content) = message);
400
401 let OlmMessage::PreKey(prekey) = content.ciphertext else {
402 panic!("Wrong Olm message type");
403 };
404
405 let bob_session_result = bob
408 .create_inbound_session(
409 alice_device.curve25519_key().unwrap(),
410 bob.device_keys(),
411 &prekey,
412 )
413 .unwrap();
414
415 let plaintext: Value = serde_json::from_str(&bob_session_result.plaintext).unwrap();
418 assert_eq!(plaintext["sender_device_keys"]["user_id"].as_str(), Some("@alice:localhost"));
419
420 let event: DecryptedOlmV1Event<DummyEventContent> =
423 serde_json::from_str(&bob_session_result.plaintext).unwrap();
424 assert_eq!(event.sender_device_keys.unwrap(), alice.device_keys());
425 }
426}