1use std::collections::BTreeMap;
18
19use ruma::{OwnedDeviceId, RoomId, events::AnyToDeviceEventContent, serde::JsonCastable};
20use serde::{Deserialize, Serialize};
21use serde_json::Value;
22use vodozemac::{Curve25519PublicKey, megolm::MegolmMessage, olm::OlmMessage};
23
24use super::Event;
25use crate::types::{
26 EventEncryptionAlgorithm, deserialize_curve_key,
27 events::{
28 EventType, ToDeviceEvent,
29 room_key_request::{self, SupportedKeyInfo},
30 },
31 serde_curve_key_option, serialize_curve_key,
32};
33
34pub type EncryptedEvent = Event<RoomEncryptedEventContent>;
36
37impl EncryptedEvent {
38 pub fn room_key_info(&self, room_id: &RoomId) -> Option<SupportedKeyInfo> {
44 let room_id = room_id.to_owned();
45
46 match &self.content.scheme {
47 RoomEventEncryptionScheme::MegolmV1AesSha2(c) => Some(
48 room_key_request::MegolmV1AesSha2Content {
49 room_id,
50 sender_key: c.sender_key,
51 session_id: c.session_id.clone(),
52 }
53 .into(),
54 ),
55 #[cfg(feature = "experimental-algorithms")]
56 RoomEventEncryptionScheme::MegolmV2AesSha2(c) => Some(
57 room_key_request::MegolmV2AesSha2Content {
58 room_id,
59 session_id: c.session_id.clone(),
60 }
61 .into(),
62 ),
63 RoomEventEncryptionScheme::Unknown(_) => None,
64 }
65 }
66}
67
68impl JsonCastable<EncryptedEvent>
69 for ruma::events::room::encrypted::OriginalSyncRoomEncryptedEvent
70{
71}
72
73#[cfg(feature = "experimental-encrypted-state-events")]
74impl JsonCastable<EncryptedEvent>
75 for ruma::events::room::encrypted::unstable_state::OriginalSyncStateRoomEncryptedEvent
76{
77}
78
79pub type EncryptedToDeviceEvent = ToDeviceEvent<ToDeviceEncryptedEventContent>;
81
82impl EncryptedToDeviceEvent {
83 pub fn algorithm(&self) -> EventEncryptionAlgorithm {
85 self.content.algorithm()
86 }
87}
88
89#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
91#[serde(try_from = "Helper")]
92pub enum ToDeviceEncryptedEventContent {
93 OlmV1Curve25519AesSha2(Box<OlmV1Curve25519AesSha2Content>),
96 #[cfg(feature = "experimental-algorithms")]
99 OlmV2Curve25519AesSha2(Box<OlmV2Curve25519AesSha2Content>),
100 Unknown(UnknownEncryptedContent),
103}
104
105impl EventType for ToDeviceEncryptedEventContent {
106 const EVENT_TYPE: &'static str = "m.room.encrypted";
107}
108
109impl JsonCastable<AnyToDeviceEventContent> for ToDeviceEncryptedEventContent {}
110
111impl ToDeviceEncryptedEventContent {
112 pub fn algorithm(&self) -> EventEncryptionAlgorithm {
114 match self {
115 ToDeviceEncryptedEventContent::OlmV1Curve25519AesSha2(_) => {
116 EventEncryptionAlgorithm::OlmV1Curve25519AesSha2
117 }
118 #[cfg(feature = "experimental-algorithms")]
119 ToDeviceEncryptedEventContent::OlmV2Curve25519AesSha2(_) => {
120 EventEncryptionAlgorithm::OlmV2Curve25519AesSha2
121 }
122 ToDeviceEncryptedEventContent::Unknown(c) => c.algorithm.to_owned(),
123 }
124 }
125}
126
127#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
130#[serde(try_from = "OlmHelper")]
131pub struct OlmV1Curve25519AesSha2Content {
132 pub ciphertext: OlmMessage,
134
135 pub recipient_key: Curve25519PublicKey,
137
138 pub sender_key: Curve25519PublicKey,
140
141 pub message_id: Option<String>,
143}
144
145#[cfg(feature = "experimental-algorithms")]
148#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
149pub struct OlmV2Curve25519AesSha2Content {
150 pub ciphertext: OlmMessage,
152
153 #[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")]
155 pub sender_key: Curve25519PublicKey,
156
157 #[serde(default, skip_serializing_if = "Option::is_none", rename = "org.matrix.msgid")]
159 pub message_id: Option<String>,
160}
161
162#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
163struct OlmHelper {
164 #[serde(deserialize_with = "deserialize_curve_key", serialize_with = "serialize_curve_key")]
165 sender_key: Curve25519PublicKey,
166 ciphertext: BTreeMap<String, OlmMessage>,
167 #[serde(default, skip_serializing_if = "Option::is_none", rename = "org.matrix.msgid")]
168 message_id: Option<String>,
169}
170
171impl Serialize for OlmV1Curve25519AesSha2Content {
172 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
173 where
174 S: serde::Serializer,
175 {
176 let ciphertext =
177 BTreeMap::from([(self.recipient_key.to_base64(), self.ciphertext.clone())]);
178
179 OlmHelper {
180 sender_key: self.sender_key,
181 ciphertext,
182 message_id: self.message_id.to_owned(),
183 }
184 .serialize(serializer)
185 }
186}
187
188impl TryFrom<OlmHelper> for OlmV1Curve25519AesSha2Content {
189 type Error = serde_json::Error;
190
191 fn try_from(value: OlmHelper) -> Result<Self, Self::Error> {
192 let (recipient_key, ciphertext) = value.ciphertext.into_iter().next().ok_or_else(|| {
193 serde::de::Error::custom(
194 "The `m.room.encrypted` event is missing a ciphertext".to_owned(),
195 )
196 })?;
197
198 let recipient_key =
199 Curve25519PublicKey::from_base64(&recipient_key).map_err(serde::de::Error::custom)?;
200
201 Ok(Self {
202 ciphertext,
203 recipient_key,
204 sender_key: value.sender_key,
205 message_id: value.message_id,
206 })
207 }
208}
209
210#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
212pub struct RoomEncryptedEventContent {
213 #[serde(flatten)]
215 pub scheme: RoomEventEncryptionScheme,
216
217 #[serde(rename = "m.relates_to", skip_serializing_if = "Option::is_none")]
219 pub relates_to: Option<Value>,
220
221 #[serde(flatten)]
223 pub(crate) other: BTreeMap<String, Value>,
224}
225
226impl RoomEncryptedEventContent {
227 pub fn algorithm(&self) -> EventEncryptionAlgorithm {
229 self.scheme.algorithm()
230 }
231}
232
233impl EventType for RoomEncryptedEventContent {
234 const EVENT_TYPE: &'static str = "m.room.encrypted";
235}
236
237impl JsonCastable<ruma::events::AnyMessageLikeEventContent> for RoomEncryptedEventContent {}
238
239#[cfg(feature = "experimental-encrypted-state-events")]
240impl JsonCastable<ruma::events::AnyStateEventContent> for RoomEncryptedEventContent {}
241
242#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
244#[serde(try_from = "Helper")]
245pub enum RoomEventEncryptionScheme {
246 MegolmV1AesSha2(MegolmV1AesSha2Content),
249 #[cfg(feature = "experimental-algorithms")]
252 MegolmV2AesSha2(MegolmV2AesSha2Content),
253 Unknown(UnknownEncryptedContent),
256}
257
258impl RoomEventEncryptionScheme {
259 pub fn algorithm(&self) -> EventEncryptionAlgorithm {
261 match self {
262 RoomEventEncryptionScheme::MegolmV1AesSha2(_) => {
263 EventEncryptionAlgorithm::MegolmV1AesSha2
264 }
265 #[cfg(feature = "experimental-algorithms")]
266 RoomEventEncryptionScheme::MegolmV2AesSha2(_) => {
267 EventEncryptionAlgorithm::MegolmV2AesSha2
268 }
269 RoomEventEncryptionScheme::Unknown(c) => c.algorithm.to_owned(),
270 }
271 }
272}
273
274pub(crate) enum SupportedEventEncryptionSchemes<'a> {
275 MegolmV1AesSha2(&'a MegolmV1AesSha2Content),
276 #[cfg(feature = "experimental-algorithms")]
277 MegolmV2AesSha2(&'a MegolmV2AesSha2Content),
278}
279
280impl SupportedEventEncryptionSchemes<'_> {
281 pub fn session_id(&self) -> &str {
283 match self {
284 SupportedEventEncryptionSchemes::MegolmV1AesSha2(c) => &c.session_id,
285 #[cfg(feature = "experimental-algorithms")]
286 SupportedEventEncryptionSchemes::MegolmV2AesSha2(c) => &c.session_id,
287 }
288 }
289
290 pub fn message_index(&self) -> u32 {
292 match self {
293 SupportedEventEncryptionSchemes::MegolmV1AesSha2(c) => c.ciphertext.message_index(),
294 #[cfg(feature = "experimental-algorithms")]
295 SupportedEventEncryptionSchemes::MegolmV2AesSha2(c) => c.ciphertext.message_index(),
296 }
297 }
298}
299
300impl<'a> From<&'a MegolmV1AesSha2Content> for SupportedEventEncryptionSchemes<'a> {
301 fn from(c: &'a MegolmV1AesSha2Content) -> Self {
302 Self::MegolmV1AesSha2(c)
303 }
304}
305
306#[cfg(feature = "experimental-algorithms")]
307impl<'a> From<&'a MegolmV2AesSha2Content> for SupportedEventEncryptionSchemes<'a> {
308 fn from(c: &'a MegolmV2AesSha2Content) -> Self {
309 Self::MegolmV2AesSha2(c)
310 }
311}
312
313#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
316pub struct MegolmV1AesSha2Content {
317 pub ciphertext: MegolmMessage,
319
320 #[serde(default, with = "serde_curve_key_option", skip_serializing_if = "Option::is_none")]
322 pub sender_key: Option<Curve25519PublicKey>,
323
324 #[serde(skip_serializing_if = "Option::is_none")]
326 pub device_id: Option<OwnedDeviceId>,
327
328 pub session_id: String,
330}
331
332#[cfg(feature = "experimental-algorithms")]
335#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
336pub struct MegolmV2AesSha2Content {
337 pub ciphertext: MegolmMessage,
339
340 pub session_id: String,
342}
343
344#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
346pub struct UnknownEncryptedContent {
347 pub algorithm: EventEncryptionAlgorithm,
349 #[serde(flatten)]
351 other: BTreeMap<String, Value>,
352}
353
354#[derive(Debug, Deserialize, Serialize)]
355struct Helper {
356 algorithm: EventEncryptionAlgorithm,
357 #[serde(flatten)]
358 other: Value,
359}
360
361macro_rules! scheme_serialization {
362 ($something:ident, $($algorithm:ident => $content:ident),+ $(,)?) => {
363 $(
364 impl From<$content> for $something {
365 fn from(c: $content) -> Self {
366 Self::$algorithm(c.into())
367 }
368 }
369 )+
370
371 impl TryFrom<Helper> for $something {
372 type Error = serde_json::Error;
373
374 fn try_from(value: Helper) -> Result<Self, Self::Error> {
375 Ok(match value.algorithm {
376 $(
377 EventEncryptionAlgorithm::$algorithm => {
378 let content: $content = serde_json::from_value(value.other)?;
379 content.into()
380 }
381 )+
382 _ => Self::Unknown(UnknownEncryptedContent {
383 algorithm: value.algorithm,
384 other: serde_json::from_value(value.other)?,
385 }),
386 })
387 }
388 }
389
390 impl Serialize for $something {
391 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
392 where
393 S: serde::Serializer,
394 {
395 let helper = match self {
396 $(
397 Self::$algorithm(r) => Helper {
398 algorithm: self.algorithm(),
399 other: serde_json::to_value(r).map_err(serde::ser::Error::custom)?,
400 },
401 )+
402 Self::Unknown(r) => Helper {
403 algorithm: r.algorithm.clone(),
404 other: serde_json::to_value(r.other.clone()).map_err(serde::ser::Error::custom)?,
405 },
406 };
407
408 helper.serialize(serializer)
409 }
410 }
411 };
412}
413
414#[cfg(feature = "experimental-algorithms")]
415scheme_serialization!(
416 RoomEventEncryptionScheme,
417 MegolmV1AesSha2 => MegolmV1AesSha2Content,
418 MegolmV2AesSha2 => MegolmV2AesSha2Content
419);
420
421#[cfg(not(feature = "experimental-algorithms"))]
422scheme_serialization!(
423 RoomEventEncryptionScheme,
424 MegolmV1AesSha2 => MegolmV1AesSha2Content,
425);
426
427#[cfg(feature = "experimental-algorithms")]
428scheme_serialization!(
429 ToDeviceEncryptedEventContent,
430 OlmV1Curve25519AesSha2 => OlmV1Curve25519AesSha2Content,
431 OlmV2Curve25519AesSha2 => OlmV2Curve25519AesSha2Content,
432);
433
434#[cfg(not(feature = "experimental-algorithms"))]
435scheme_serialization!(
436 ToDeviceEncryptedEventContent,
437 OlmV1Curve25519AesSha2 => OlmV1Curve25519AesSha2Content,
438);
439
440#[cfg(test)]
441pub(crate) mod tests {
442 use assert_matches::assert_matches;
443 use assert_matches2::assert_let;
444 use serde_json::{Value, json};
445 use vodozemac::Curve25519PublicKey;
446
447 use super::{
448 EncryptedEvent, EncryptedToDeviceEvent, OlmV1Curve25519AesSha2Content,
449 RoomEventEncryptionScheme, ToDeviceEncryptedEventContent,
450 };
451
452 pub fn json() -> Value {
453 json!({
454 "sender": "@alice:example.org",
455 "event_id": "$Nhl3rsgHMjk-DjMJANawr9HHAhLg4GcoTYrSiYYGqEE",
456 "content": {
457 "m.custom": "something custom",
458 "algorithm": "m.megolm.v1.aes-sha2",
459 "device_id": "DEWRCMENGS",
460 "session_id": "ZFD6+OmV7fVCsJ7Gap8UnORH8EnmiAkes8FAvQuCw/I",
461 "sender_key": "WJ6Ce7U67a6jqkHYHd8o0+5H4bqdi9hInZdk0+swuXs",
462 "ciphertext":
463 "AwgAEiBQs2LgBD2CcB+RLH2bsgp9VadFUJhBXOtCmcJuttBDOeDNjL21d9\
464 z0AcVSfQFAh9huh4or7sWuNrHcvu9/sMbweTgc0UtdA5xFLheubHouXy4a\
465 ewze+ShndWAaTbjWJMLsPSQDUMQHBA",
466 "m.relates_to": {
467 "rel_type": "m.reference",
468 "event_id": "$WUreEJERkFzO8i2dk6CmTex01cP1dZ4GWKhKCwkWHrQ"
469 },
470 },
471 "type": "m.room.encrypted",
472 "origin_server_ts": 1632491098485u64,
473 "m.custom.top": "something custom in the top",
474 })
475 }
476
477 pub fn olm_v1_json() -> Value {
478 json!({
479 "algorithm": "m.olm.v1.curve25519-aes-sha2",
480 "ciphertext": {
481 "Nn0L2hkcCMFKqynTjyGsJbth7QrVmX3lbrksMkrGOAw": {
482 "body":
483 "Awogv7Iysf062hV1gZNfG/SdO5TdLYtkRI12em6LxralPxoSICC/Av\
484 nha6NfkaMWSC+5h+khS0wHiUzA2bPmAvVo/iYhGiAfDNh4F0eqPvOc\
485 4Hw9wMgd+frzedZgmhUNfKT0UzHQZSJPAwogF8fTdTcPt1ppJ/KAEi\
486 vFZ4dIyAlRUjzhlqzYsw9C1HoQACIgb9MK/a9TRLtwol9gfy7OeKdp\
487 mSe39YhP+5OchhKvX6eO3/aED3X1oA",
488 "type": 0
489 }
490 },
491 "sender_key": "mjkTX0I0Cp44ZfolOVbFe5WYPRmT6AX3J0ZbnGWnnWs"
492 })
493 }
494
495 pub fn to_device_json() -> Value {
496 json!({
497 "content": olm_v1_json(),
498 "sender": "@example:morpheus.localhost",
499 "type": "m.room.encrypted"
500 })
501 }
502
503 #[test]
504 fn deserialization() -> Result<(), serde_json::Error> {
505 let json = json();
506 let event: EncryptedEvent = serde_json::from_value(json.clone())?;
507
508 assert_matches!(event.content.scheme, RoomEventEncryptionScheme::MegolmV1AesSha2(_));
509 assert!(event.content.relates_to.is_some());
510 let serialized = serde_json::to_value(event)?;
511 assert_eq!(json, serialized);
512
513 let json = olm_v1_json();
514 let content: OlmV1Curve25519AesSha2Content = serde_json::from_value(json)?;
515
516 assert_eq!(
517 content.sender_key,
518 Curve25519PublicKey::from_base64("mjkTX0I0Cp44ZfolOVbFe5WYPRmT6AX3J0ZbnGWnnWs")
519 .unwrap()
520 );
521
522 assert_eq!(
523 content.recipient_key,
524 Curve25519PublicKey::from_base64("Nn0L2hkcCMFKqynTjyGsJbth7QrVmX3lbrksMkrGOAw")
525 .unwrap()
526 );
527
528 let json = to_device_json();
529 let event: EncryptedToDeviceEvent = serde_json::from_value(json.clone())?;
530
531 assert_let!(
532 ToDeviceEncryptedEventContent::OlmV1Curve25519AesSha2(content) = &event.content
533 );
534 assert!(content.message_id.is_none());
535
536 let serialized = serde_json::to_value(event)?;
537 assert_eq!(json, serialized);
538
539 Ok(())
540 }
541
542 #[test]
543 fn deserialization_missing_sender_key_device_id() -> Result<(), serde_json::Error> {
544 let json = json!({
545 "sender": "@alice:example.org",
546 "event_id": "$Nhl3rsgHMjk-DjMJANawr9HHAhLg4GcoTYrSiYYGqEE",
547 "content": {
548 "m.custom": "something custom",
549 "algorithm": "m.megolm.v1.aes-sha2",
550 "session_id": "ZFD6+OmV7fVCsJ7Gap8UnORH8EnmiAkes8FAvQuCw/I",
551 "ciphertext":
552 "AwgAEiBQs2LgBD2CcB+RLH2bsgp9VadFUJhBXOtCmcJuttBDOeDNjL21d9\
553 z0AcVSfQFAh9huh4or7sWuNrHcvu9/sMbweTgc0UtdA5xFLheubHouXy4a\
554 ewze+ShndWAaTbjWJMLsPSQDUMQHBA",
555 "m.relates_to": {
556 "rel_type": "m.reference",
557 "event_id": "$WUreEJERkFzO8i2dk6CmTex01cP1dZ4GWKhKCwkWHrQ"
558 },
559 },
560 "type": "m.room.encrypted",
561 "origin_server_ts": 1632491098485u64,
562 "m.custom.top": "something custom in the top",
563 });
564
565 let event: EncryptedEvent = serde_json::from_value(json.clone())?;
566
567 assert_matches!(event.content.scheme, RoomEventEncryptionScheme::MegolmV1AesSha2(_));
568 assert!(event.content.relates_to.is_some());
569 let serialized = serde_json::to_value(event)?;
570 assert_eq!(json, serialized);
571
572 Ok(())
573 }
574}