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