change(crypto): provide encryption information back directly from the Olm machine's raw encryption methods

This commit is contained in:
Stefan Ceriu
2025-12-08 19:21:31 +02:00
committed by Damir Jelić
parent b98a832c67
commit b7d22da9f0
11 changed files with 138 additions and 67 deletions
@@ -802,12 +802,12 @@ impl OlmMachine {
let room_id = RoomId::parse(room_id)?;
let content = serde_json::from_str(&content)?;
let encrypted_content = self
let result = self
.runtime
.block_on(self.inner.encrypt_room_event_raw(&room_id, &event_type, &content))
.expect("Encrypting an event produced an error");
Ok(serde_json::to_string(&encrypted_content)?)
Ok(serde_json::to_string(&result.content)?)
}
/// Encrypt the given event with the given type and content for the given
@@ -1295,8 +1295,8 @@ mod tests {
.await
.unwrap();
let content = group_session.encrypt("m.dummy", &message_like_event_content!({})).await;
let event = wrap_encrypted_content(bob_machine.user_id(), content);
let result = group_session.encrypt("m.dummy", &message_like_event_content!({})).await;
let event = wrap_encrypted_content(bob_machine.user_id(), result.content);
// Alice wants to request the outbound group session from bob.
assert!(
@@ -1385,8 +1385,8 @@ mod tests {
let (outbound, session) = account.create_group_session_pair_with_defaults(room_id()).await;
let content = outbound.encrypt("m.dummy", &message_like_event_content!({})).await;
let event = wrap_encrypted_content(machine.user_id(), content);
let result = outbound.encrypt("m.dummy", &message_like_event_content!({})).await;
let event = wrap_encrypted_content(machine.user_id(), result.content);
assert!(machine.outgoing_to_device_requests().await.unwrap().is_empty());
let (cancel, request) = machine.request_key(session.room_id(), &event).await.unwrap();
@@ -1413,8 +1413,8 @@ mod tests {
machine.inner.store.save_device_data(&[alice_device]).await.unwrap();
let (outbound, session) = account.create_group_session_pair_with_defaults(room_id()).await;
let content = outbound.encrypt("m.dummy", &message_like_event_content!({})).await;
let event = wrap_encrypted_content(machine.user_id(), content);
let result = outbound.encrypt("m.dummy", &message_like_event_content!({})).await;
let event = wrap_encrypted_content(machine.user_id(), result.content);
assert!(machine.outgoing_to_device_requests().await.unwrap().is_empty());
machine.create_outgoing_key_request(session.room_id(), &event).await.unwrap();
@@ -1451,8 +1451,8 @@ mod tests {
assert!(!machine.are_room_key_requests_enabled());
let (outbound, session) = account.create_group_session_pair_with_defaults(room_id()).await;
let content = outbound.encrypt("m.dummy", &message_like_event_content!({})).await;
let event = wrap_encrypted_content(machine.user_id(), content);
let result = outbound.encrypt("m.dummy", &message_like_event_content!({})).await;
let event = wrap_encrypted_content(machine.user_id(), result.content);
// The outgoing to-device requests should be empty before and after
// `create_outgoing_key_request`.
@@ -1476,8 +1476,8 @@ mod tests {
machine.inner.store.save_device_data(devices).await.unwrap();
let (outbound, session) = account.create_group_session_pair_with_defaults(room_id()).await;
let content = outbound.encrypt("m.dummy", &message_like_event_content!({})).await;
let room_event = wrap_encrypted_content(machine.user_id(), content);
let result = outbound.encrypt("m.dummy", &message_like_event_content!({})).await;
let room_event = wrap_encrypted_content(machine.user_id(), result.content);
machine.create_outgoing_key_request(session.room_id(), &room_event).await.unwrap();
+55 -5
View File
@@ -52,9 +52,10 @@ use ruma::{
AnyTimelineEvent, AnyToDeviceEvent, MessageLikeEventContent,
},
serde::{JsonObject, Raw},
DeviceId, MilliSecondsSinceUnixEpoch, OneTimeKeyAlgorithm, OwnedDeviceId, OwnedDeviceKeyId,
OwnedTransactionId, OwnedUserId, RoomId, TransactionId, UInt, UserId,
DeviceId, DeviceKeyAlgorithm, MilliSecondsSinceUnixEpoch, OneTimeKeyAlgorithm, OwnedDeviceId,
OwnedDeviceKeyId, OwnedTransactionId, OwnedUserId, RoomId, TransactionId, UInt, UserId,
};
use serde::Serialize;
use serde_json::{value::to_raw_value, Value};
use tokio::sync::Mutex;
use tracing::{
@@ -114,6 +115,15 @@ use crate::{
RoomEventDecryptionResult, SignatureError, TrustRequirement,
};
#[derive(Debug, Serialize)]
/// The result of encrypting a room event.
pub struct RawEncryptionResult {
/// The encrypted event content.
pub content: Raw<RoomEncryptedEventContent>,
/// Information about the encryption that was performed.
pub encryption_info: EncryptionInfo,
}
/// State machine implementation of the Olm/Megolm encryption protocol used for
/// Matrix end to end encryption.
#[derive(Clone)]
@@ -1056,7 +1066,7 @@ impl OlmMachine {
&self,
room_id: &RoomId,
content: impl MessageLikeEventContent,
) -> MegolmResult<Raw<RoomEncryptedEventContent>> {
) -> MegolmResult<RawEncryptionResult> {
let event_type = content.event_type().to_string();
let content = Raw::new(&content)?.cast_unchecked();
self.encrypt_room_event_raw(room_id, &event_type, &content).await
@@ -1086,8 +1096,48 @@ impl OlmMachine {
room_id: &RoomId,
event_type: &str,
content: &Raw<AnyMessageLikeEventContent>,
) -> MegolmResult<Raw<RoomEncryptedEventContent>> {
self.inner.group_session_manager.encrypt(room_id, event_type, content).await
) -> MegolmResult<RawEncryptionResult> {
self.inner.group_session_manager.encrypt(room_id, event_type, content).await.map(|result| {
RawEncryptionResult {
content: result.content,
encryption_info: self
.own_encryption_info(result.algorithm, result.session_id.to_string()),
}
})
}
fn own_encryption_info(
&self,
algorithm: EventEncryptionAlgorithm,
session_id: String,
) -> EncryptionInfo {
let identity_keys = self.identity_keys();
let algorithm_info = match algorithm {
EventEncryptionAlgorithm::MegolmV1AesSha2 => AlgorithmInfo::MegolmV1AesSha2 {
curve25519_key: identity_keys.curve25519.to_base64(),
sender_claimed_keys: BTreeMap::from([(
DeviceKeyAlgorithm::Ed25519,
identity_keys.ed25519.to_base64(),
)]),
session_id: Some(session_id),
},
EventEncryptionAlgorithm::OlmV1Curve25519AesSha2 => {
AlgorithmInfo::OlmV1Curve25519AesSha2 {
curve25519_public_key_base64: identity_keys.curve25519.to_base64(),
}
}
_ => unreachable!(
"Only MegolmV1AesSha2 and OlmV1Curve25519AesSha2 are supported on this level"
),
};
EncryptionInfo {
sender: self.inner.user_id.clone(),
sender_device: Some(self.inner.device_id.clone()),
algorithm_info,
verification_state: VerificationState::Verified,
}
}
/// Encrypt a state event for the given room.
@@ -107,7 +107,7 @@ async fn test_decryption_verification_state() {
let content = RoomMessageEventContent::text_plain(plaintext);
let encrypted_content = alice
let result = alice
.encrypt_room_event(room_id, AnyMessageLikeEventContent::RoomMessage(content.clone()))
.await
.unwrap();
@@ -117,7 +117,7 @@ async fn test_decryption_verification_state() {
"origin_server_ts": MilliSecondsSinceUnixEpoch::now(),
"sender": alice.user_id(),
"type": "m.room.encrypted",
"content": encrypted_content,
"content": result.content,
});
let event = json_convert(&event).unwrap();
@@ -366,7 +366,7 @@ async fn test_verification_states_spoofed_sender(
// Alice now sends a second message to Bob, using the same room key, but the HS
// admin rewrites the 'sender' to Charlie.
let encrypted_content = alice
let result = alice
.encrypt_room_event(
room_id,
AnyMessageLikeEventContent::RoomMessage(RoomMessageEventContent::text_plain(
@@ -380,7 +380,7 @@ async fn test_verification_states_spoofed_sender(
"origin_server_ts": MilliSecondsSinceUnixEpoch::now(),
"sender": "@charlie:example.org", // Note! spoofed sender
"type": "m.room.encrypted",
"content": encrypted_content,
"content": result.content,
});
let event = json_convert(&event).unwrap();
@@ -668,7 +668,7 @@ async fn encrypt_message(
let content = RoomMessageEventContent::text_plain(plaintext);
let encrypted_content = sender
let result = sender
.encrypt_room_event(room_id, AnyMessageLikeEventContent::RoomMessage(content.clone()))
.await
.unwrap();
@@ -678,7 +678,7 @@ async fn encrypt_message(
"origin_server_ts": MilliSecondsSinceUnixEpoch::now(),
"sender": sender.user_id(),
"type": "m.room.encrypted",
"content": encrypted_content,
"content": result.content,
});
let event = json_convert(&event).unwrap();
@@ -688,7 +688,7 @@ async fn test_megolm_encryption() {
let content = RoomMessageEventContent::text_plain(plaintext);
let encrypted_content = alice
let result = alice
.encrypt_room_event(room_id, AnyMessageLikeEventContent::RoomMessage(content.clone()))
.await
.unwrap();
@@ -698,7 +698,7 @@ async fn test_megolm_encryption() {
"origin_server_ts": MilliSecondsSinceUnixEpoch::now(),
"sender": alice.user_id(),
"type": "m.room.encrypted",
"content": encrypted_content,
"content": result.content,
});
let event = json_convert(&event).unwrap();
@@ -932,7 +932,8 @@ async fn test_megolm_state_encryption_outer_state_key_no_inner() {
let encrypted_content = alice
.encrypt_room_event(room_id, AnyMessageLikeEventContent::RoomMessage(content))
.await
.unwrap();
.unwrap()
.content;
// Construct an outer event that has `state_key` defined.
let event = json!({
@@ -1076,7 +1077,7 @@ async fn test_withheld_unverified() {
let content = RoomMessageEventContent::text_plain(plaintext);
let content = alice
let result = alice
.encrypt_room_event(room_id, AnyMessageLikeEventContent::RoomMessage(content.clone()))
.await
.unwrap();
@@ -1086,7 +1087,7 @@ async fn test_withheld_unverified() {
"origin_server_ts": MilliSecondsSinceUnixEpoch::now(),
"sender": alice.user_id(),
"type": "m.room.encrypted",
"content": content,
"content": result.content,
});
let room_event = json_convert(&room_event).unwrap();
@@ -1281,7 +1282,7 @@ async fn test_query_ratcheted_key() {
let content = RoomMessageEventContent::text_plain(plaintext);
let content = alice
let result = alice
.encrypt_room_event(room_id, AnyMessageLikeEventContent::RoomMessage(content.clone()))
.await
.unwrap();
@@ -1291,7 +1292,7 @@ async fn test_query_ratcheted_key() {
"origin_server_ts": MilliSecondsSinceUnixEpoch::now(),
"sender": alice.user_id(),
"type": "m.room.encrypted",
"content": content,
"content": result.content,
});
// should share at index 1
@@ -1395,11 +1396,10 @@ async fn test_room_key_over_megolm() {
"session_key": session_key.to_base64(),
});
let encrypted_content =
alice.encrypt_room_event_raw(room_id, "m.room_key", &content).await.unwrap();
let result = alice.encrypt_room_event_raw(room_id, "m.room_key", &content).await.unwrap();
let event = json!({
"sender": alice.user_id(),
"content": encrypted_content,
"content": result.content,
"type": "m.room.encrypted",
});
@@ -1470,7 +1470,7 @@ async fn test_room_key_with_fake_identity_keys() {
inbound.creator_info.signing_keys = signing_keys.into();
let content = message_like_event_content!({});
let content = outbound.encrypt("m.dummy", &content).await;
let result = outbound.encrypt("m.dummy", &content).await;
alice.store().save_inbound_group_sessions(&[inbound]).await.unwrap();
let event = json!({
@@ -1478,7 +1478,7 @@ async fn test_room_key_with_fake_identity_keys() {
"event_id": "$xxxxx:example.org",
"origin_server_ts": MilliSecondsSinceUnixEpoch::now(),
"type": "m.room.encrypted",
"content": content,
"content": result.content,
});
let event = json_convert(&event).unwrap();
@@ -1745,7 +1745,7 @@ async fn test_unsigned_decryption() {
// Encrypt first message.
let first_message_text = "This is the original message";
let first_message_content = RoomMessageEventContent::text_plain(first_message_text);
let first_message_encrypted_content =
let first_message_result =
alice.encrypt_room_event(room_id, first_message_content).await.unwrap();
let mut first_message_encrypted_event = json!({
@@ -1753,7 +1753,7 @@ async fn test_unsigned_decryption() {
"origin_server_ts": MilliSecondsSinceUnixEpoch::now(),
"sender": alice.user_id(),
"type": "m.room.encrypted",
"content": first_message_encrypted_content,
"content": first_message_result.content,
});
let raw_encrypted_event = json_convert(&first_message_encrypted_event).unwrap();
@@ -1790,7 +1790,7 @@ async fn test_unsigned_decryption() {
let second_message_text = "This is the ~~original~~ edited message";
let second_message_content =
RoomMessageEventContent::text_plain(second_message_text).make_replacement(first_message);
let second_message_encrypted_content =
let second_message_result =
alice.encrypt_room_event(room_id, second_message_content).await.unwrap();
let second_message_encrypted_event = json!({
@@ -1798,7 +1798,7 @@ async fn test_unsigned_decryption() {
"origin_server_ts": MilliSecondsSinceUnixEpoch::now(),
"sender": alice.user_id(),
"type": "m.room.encrypted",
"content": second_message_encrypted_content,
"content": second_message_result.content,
});
// Bundle the edit in the unsigned object of the first event.
@@ -1900,7 +1900,7 @@ async fn test_unsigned_decryption() {
let third_message_text = "This a reply in a thread";
let third_message_content = RoomMessageEventContent::text_plain(third_message_text)
.make_for_thread(first_message, ReplyWithinThread::No, AddMentions::No);
let third_message_encrypted_content =
let third_message_result =
alice.encrypt_room_event(room_id, third_message_content).await.unwrap();
let third_message_encrypted_event = json!({
@@ -1908,7 +1908,7 @@ async fn test_unsigned_decryption() {
"origin_server_ts": MilliSecondsSinceUnixEpoch::now(),
"sender": alice.user_id(),
"type": "m.room.encrypted",
"content": third_message_encrypted_content,
"content": third_message_result.content,
"room_id": room_id,
});
@@ -23,7 +23,8 @@ pub(crate) mod sender_data_finder;
pub use inbound::{InboundGroupSession, PickledInboundGroupSession};
pub(crate) use outbound::ShareState;
pub use outbound::{
EncryptionSettings, OutboundGroupSession, PickledOutboundGroupSession, ShareInfo,
EncryptionSettings, OutboundGroupSession, OutboundGroupSessionEncryptionResult,
PickledOutboundGroupSession, ShareInfo,
};
pub use sender_data::{KnownSenderData, SenderData, SenderDataType};
use thiserror::Error;
@@ -151,6 +151,19 @@ impl EncryptionSettings {
}
}
/// The result of encrypting a message with an outbound group session.
///
/// Contains the encrypted content, the algorithm used, and the session ID.
#[derive(Debug)]
pub struct OutboundGroupSessionEncryptionResult {
/// The encrypted content of the message.
pub content: Raw<RoomEncryptedEventContent>,
/// The algorithm used to encrypt the message.
pub algorithm: EventEncryptionAlgorithm,
/// The session ID used to encrypt the message.
pub session_id: Arc<str>,
}
/// Outbound group session.
///
/// Outbound group sessions are used to exchange room messages between a group
@@ -485,7 +498,7 @@ impl OutboundGroupSession {
&self,
payload: &T,
relates_to: Option<serde_json::Value>,
) -> Raw<RoomEncryptedEventContent> {
) -> OutboundGroupSessionEncryptionResult {
let ciphertext = self
.encrypt_helper(
serde_json::to_string(payload).expect("payload serialization never fails"),
@@ -509,7 +522,13 @@ impl OutboundGroupSession {
),
};
let content = RoomEncryptedEventContent { scheme, relates_to, other: Default::default() };
Raw::new(&content).expect("m.room.encrypted event content can always be serialized")
OutboundGroupSessionEncryptionResult {
content: Raw::new(&content)
.expect("m.room.encrypted event content can always be serialized"),
algorithm: self.settings.algorithm.to_owned(),
session_id: self.session_id.clone(),
}
}
/// Encrypt a room message for the given room.
@@ -532,7 +551,7 @@ impl OutboundGroupSession {
&self,
event_type: &str,
content: &Raw<AnyMessageLikeEventContent>,
) -> Raw<RoomEncryptedEventContent> {
) -> OutboundGroupSessionEncryptionResult {
#[derive(Serialize)]
struct Payload<'a> {
#[serde(rename = "type")]
@@ -586,7 +605,7 @@ impl OutboundGroupSession {
}
let payload = Payload { event_type, state_key, content, room_id: &self.room_id };
self.encrypt_inner(&payload, None).await
self.encrypt_inner(&payload, None).await.content
}
fn elapsed(&self) -> bool {
+9 -9
View File
@@ -31,8 +31,9 @@ pub(crate) use group_sessions::{
};
pub use group_sessions::{
BackedUpRoomKey, EncryptionSettings, ExportedRoomKey, InboundGroupSession, KnownSenderData,
OutboundGroupSession, PickledInboundGroupSession, PickledOutboundGroupSession, SenderData,
SenderDataType, SessionCreationError, SessionExportError, SessionKey, ShareInfo,
OutboundGroupSession, OutboundGroupSessionEncryptionResult, PickledInboundGroupSession,
PickledOutboundGroupSession, SenderData, SenderDataType, SessionCreationError,
SessionExportError, SessionKey, ShareInfo,
};
pub use session::{PickledSession, Session};
pub use signing::{CrossSigningStatus, PickledCrossSigningIdentity, PrivateCrossSigningIdentity};
@@ -213,8 +214,7 @@ pub(crate) mod tests {
assert_eq!(0, inbound.first_known_index());
assert_eq!(outbound.session_id(), inbound.session_id());
let encrypted_content =
outbound.encrypt("m.room.message", &Raw::new(&content).unwrap().cast()).await;
let result = outbound.encrypt("m.room.message", &Raw::new(&content).unwrap().cast()).await;
let event = json!({
"sender": alice.user_id(),
@@ -222,7 +222,7 @@ pub(crate) mod tests {
"origin_server_ts": 0u64,
"room_id": room_id,
"type": "m.room.encrypted",
"content": encrypted_content,
"content": result.content,
});
let event = json_convert(&event).unwrap();
@@ -256,7 +256,7 @@ pub(crate) mod tests {
let content = message_like_event_content!({
"m.relates_to": relation_json,
});
let encrypted = outbound.encrypt("m.dummy", &content).await;
let result = outbound.encrypt("m.dummy", &content).await;
let event = json!({
"sender": alice.user_id(),
@@ -264,7 +264,7 @@ pub(crate) mod tests {
"origin_server_ts": 0u64,
"room_id": room_id,
"type": "m.room.encrypted",
"content": encrypted,
"content": result.content,
});
let event: EncryptedEvent = json_convert(&event).unwrap();
@@ -281,8 +281,8 @@ pub(crate) mod tests {
assert_eq!(relation, Some(&relation_json), "The decrypted event should contain a relation");
let content = message_like_event_content!({});
let encrypted = outbound.encrypt("m.dummy", &content).await;
let mut encrypted: Value = json_convert(&encrypted).unwrap();
let result = outbound.encrypt("m.dummy", &content).await;
let mut encrypted: Value = json_convert(&result.content).unwrap();
encrypted.as_object_mut().unwrap().insert("m.relates_to".to_owned(), relation_json.clone());
// Let's now test if we copy the correct relation if there is no
@@ -45,18 +45,19 @@ pub(crate) use share_strategy::{
};
use tracing::{debug, error, info, instrument, trace, warn, Instrument};
#[cfg(feature = "experimental-encrypted-state-events")]
use crate::types::events::room::encrypted::RoomEncryptedEventContent;
use crate::{
error::{EventError, MegolmResult, OlmResult},
identities::device::MaybeEncryptedRoomKey,
olm::{
InboundGroupSession, OutboundGroupSession, SenderData, SenderDataFinder, Session,
ShareInfo, ShareState,
InboundGroupSession, OutboundGroupSession, OutboundGroupSessionEncryptionResult,
SenderData, SenderDataFinder, Session, ShareInfo, ShareState,
},
store::{types::Changes, CryptoStoreWrapper, Result as StoreResult, Store},
types::{
events::{
room::encrypted::{RoomEncryptedEventContent, ToDeviceEncryptedEventContent},
room_key_bundle::RoomKeyBundleContent,
room::encrypted::ToDeviceEncryptedEventContent, room_key_bundle::RoomKeyBundleContent,
EventType,
},
requests::ToDeviceRequest,
@@ -211,19 +212,19 @@ impl GroupSessionManager {
room_id: &RoomId,
event_type: &str,
content: &Raw<AnyMessageLikeEventContent>,
) -> MegolmResult<Raw<RoomEncryptedEventContent>> {
) -> MegolmResult<OutboundGroupSessionEncryptionResult> {
let session =
self.sessions.get_or_load(room_id).await.expect("Session wasn't created nor shared");
assert!(!session.expired(), "Session expired");
let content = session.encrypt(event_type, content).await;
let result = session.encrypt(event_type, content).await;
let mut changes = Changes::default();
changes.outbound_group_sessions.push(session);
self.store.save_changes(changes).await?;
Ok(content)
Ok(result)
}
/// Encrypts a state event for the given room using its outbound group
+3 -4
View File
@@ -204,10 +204,9 @@ impl<'a> IntoFuture for SendRawMessageLikeEvent<'a> {
let olm = room.client.olm_machine().await;
let olm = olm.as_ref().expect("Olm machine wasn't started");
content = olm
.encrypt_room_event_raw(room.room_id(), event_type, &content)
.await?
.cast();
let result =
olm.encrypt_room_event_raw(room.room_id(), event_type, &content).await?;
content = result.content.cast();
event_type = "m.room.encrypted";
}
} else {
@@ -1439,7 +1439,8 @@ async fn test_enable_from_secret_storage_and_download_after_utd_from_old_message
let encrypted_event_content = serde_json::to_value(
outbound_group_session
.encrypt("m.room.message", &serde_json::from_value(event_body)?)
.await,
.await
.content,
)?;
mock_get_event(room_id, event_id, encrypted_event_content, &server).await;