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