Commit bb770dbf authored by Johannes Hayeß's avatar Johannes Hayeß
Browse files

Handle MegolmEncrypt action

parent 44b11b46
Pipeline #72838 failed with stages
in 5 minutes and 16 seconds
......@@ -14,26 +14,36 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use std::collections::{BTreeMap, HashMap};
use std::sync::mpsc::{Receiver, Sender};
use std::{thread, time};
use olm_rs::account::OlmAccount;
use olm_rs::inbound_group_session::OlmInboundGroupSession;
use olm_rs::utility::OlmUtility;
use serde_json;
use crate::blocker::BlockerManager;
use crate::crypto_routines;
use crate::crypto_routines::OlmHandleType;
use crate::json_objects::{
event::{Encryption, MegolmEncrypted, OlmEncrypted, RoomEncrypted},
device::{DeviceKeysRequest, DeviceQueryResponse, SignedDeviceKeys},
event::{
Encryption, MegolmEncrypted, OlmEncrypted, OlmPayload, RoomEncrypted,
RoomEncryptedForSending, RoomKey, RoomKeyContent,
},
info::UsersForRoom,
misc::{MegolmPlaintextPacketBody, MegolmPlaintextPacketBodyContentErr},
otk::OneTimeKeyCount,
self_info::{MegolmEncryptedBody, RoomKeyBody},
olm::OlmIdentityKeys,
otk::{OneTimeKeyCount, OneTimeKeyQuery},
self_info::{MegolmEncryptBody, MegolmEncryptedBody, RoomKeyBody},
storage::InboundGroupSessionWithKeyForStorage,
transport::C2MEncryptEventBody,
};
use crate::otk_replenisher::OTKReplenisher;
use crate::packet::*;
use crate::session_cache::{InboundMegolmSessionIdentifier, SessionCache};
use olm_rs::account::OlmAccount;
use olm_rs::inbound_group_session::OlmInboundGroupSession;
use serde_json;
use std::collections::BTreeMap;
use std::sync::mpsc::{Receiver, Sender};
use std::{thread, time};
/// Monolithically encapsulates all functionallity of the Metaolm module.
pub struct Metaolm {
......@@ -179,6 +189,9 @@ impl Metaolm {
}
}
}
ActionType::GetNextTxnID => {
// TODO: not implemented on this side
}
},
_ => {}
}
......@@ -376,6 +389,272 @@ impl Metaolm {
);
}
}
M2CPacketType::SelfInfo(SelfInfoType::MegolmEncrypt) => {
let event_to_encrypt: C2MEncryptEventBody =
serde_json::from_str(&blocked_item.body).unwrap();
// we had a session previously stored
if !packet.body.is_empty() {
let stored_session: MegolmEncryptBody =
serde_json::from_str(&packet.body).unwrap();
self.session_cache.cache_outbound_megolm_session(
stored_session.session.into_active(),
stored_session.room_id,
);
crypto_routines::try_encrypt_room_message(
&event_to_encrypt,
packet.blocker_id,
&mut self.blocker_manager,
&mut self.blocked_items,
&mut self.session_cache,
&self.olm_account,
self.device_id.clone(),
self.sender.clone(),
);
// no previously stored session, so we have to create a new one
} else {
// create new session and add event_to_encrypt to blocked_items
crypto_routines::create_new_outbound_megolm_session(
&event_to_encrypt,
blocked_item.blocker_id,
// FIXME: default values for now, find out how to
// get the actual values
7 * 24 * 60 * 60 * 1000, // a week in ms
100,
&mut self.blocker_manager,
&mut self.blocked_items,
&mut self.session_cache,
&self.olm_account,
self.sender.clone(),
);
}
}
M2CPacketType::SelfInfo(SelfInfoType::RequestedUsersForRoom) => {
// we have received the list of users for the room
let users_list: UsersForRoom =
serde_json::from_str(&packet.body).unwrap();
let mut device_keys_req = DeviceKeysRequest::new();
device_keys_req.add_users(users_list.users);
self.send(M2CPacket {
header: M2CPacketType::HTTPReq(
HTTPReqType::Post,
"/_matrix/client/r0/keys/query".to_string(),
),
body: serde_json::to_string(&device_keys_req).unwrap(),
blocker_id: blocked_item.blocker_id,
});
}
M2CPacketType::SelfInfo(SelfInfoType::RequestedDeviceKeys) => {
let device_query_rsp: DeviceQueryResponse =
serde_json::from_str(&packet.body).unwrap();
// mapping of user IDs to the associated keys
let mut clean_devices: Vec<SignedDeviceKeys> = Vec::new();
// First we have to check if the inner user_id and device_id are
// equal to those in the signed device keys object.
// This prevents a malicious homeserver from replacing keys, without
// us noticing.
for (user_id, device_list) in device_query_rsp.device_keys {
for (device_id, signed_device_keys) in device_list {
// if they don't match we simply ignore and don't further process
if user_id == signed_device_keys.user_id
&& device_id == signed_device_keys.device_id
{
clean_devices.push(signed_device_keys);
}
}
}
// Now we have to check if the signature on each device is correct.
// TODO: impl logic if a device has been seen before or not
// device parameters to remember: (user_id, device_id, ed25519)
//
// ^ check if device has been seen before,
// and if information deviates from previously known info
{
let utility = OlmUtility::new();
// the closure checks if the signature is valid
clean_devices.retain(|signed_device| {
let sign_key =
signed_device.get_device_keys_only().keys.ed25519;
let device_keys_no_sig = serde_json::to_string(
&signed_device.get_device_keys_only(),
)
.unwrap();
if let Some(mut signature) =
signed_device.get_own_signature()
{
if utility
.ed25519_verify(
sign_key.as_str(),
&device_keys_no_sig,
signature.as_mut_str(),
)
.unwrap()
{
// signature is valid
true
} else {
// incorrect signature, abort processing for device
false
}
} else {
// no signature is present, the device can't be trusted
false
}
});
}
let event_to_encrypt: C2MEncryptEventBody =
serde_json::from_str(&blocked_item.body).unwrap();
let outbound_megolm_session = self
.session_cache
.get_cached_outbound_megolm_session(&event_to_encrypt.room_id)
.unwrap();
let room_key_event = RoomKey {
content: RoomKeyContent {
algorithm: "m.megolm.v1.aes-sha2".to_string(),
room_id: event_to_encrypt.room_id.clone(),
session_id: outbound_megolm_session.session.session_id(),
session_key: outbound_megolm_session.session.session_key(),
},
r#type: "m.room_key".to_string(),
};
let mut olm_payload = OlmPayload {
r#type: "m.room_key".to_string(),
content: serde_json::to_string(&room_key_event).unwrap(),
sender: self.user_id.clone(),
recipient: String::new(), // filled out later
recipient_keys: HashMap::new(), // filled out later
keys: HashMap::new(),
};
// fill in with own ed25519 key
let identity_keys: OlmIdentityKeys =
serde_json::from_str(&self.olm_account.identity_keys())
.unwrap();
olm_payload
.keys
.insert("ed25519".to_string(), identity_keys.ed25519.clone());
for device_key in clean_devices {
// fill Olm payload with recipient key info
olm_payload.recipient = device_key.user_id.clone();
olm_payload.recipient_keys.insert(
"ed25519".to_string(),
device_key.keys.ed25519.clone(),
);
// check if we have a sesison ready
if let Some(olm_sessions) = self
.session_cache
.get_cached_olm_sessions(&device_key.keys.ed25519)
{
if olm_sessions.len() > 1 {
// get the one with the smallest session_id
let mut order_map: HashMap<String, usize> =
HashMap::new();
let mut order_vec: Vec<String> = Vec::new();
let mut i = 0;
for session in olm_sessions {
order_map.insert(session.session_id(), i);
order_vec.push(session.session_id());
i += 1;
}
order_vec.sort_unstable();
let selected_session_index =
order_map.get(order_vec.get(0).unwrap()).unwrap();
let session =
olm_sessions.get(*selected_session_index).unwrap();
crypto_routines::megolm_key_encrypt_and_send(
session,
&olm_payload,
device_key.keys.ed25519.clone(),
identity_keys.ed25519.clone(),
event_to_encrypt.room_id.clone(),
self.sender.clone(),
);
} else {
if let Some(session) = olm_sessions.get(0) {
// encrypt
crypto_routines::megolm_key_encrypt_and_send(
session,
&olm_payload,
device_key.keys.ed25519.clone(),
identity_keys.ed25519.clone(),
event_to_encrypt.room_id.clone(),
self.sender.clone(),
);
} else {
// FIXME: make this an error instead
unreachable!();
}
}
} else {
// We'll have to create a new sesison here and in order to do that
// we have to claim a one-time-key (OTK) for the device.
let claim_otk_bid = self.blocker_manager.next_blocker_id();
self.send(M2CPacket {
header: M2CPacketType::HTTPReq(
HTTPReqType::Post,
"/keys/claim".to_string(),
),
body: serde_json::to_string(&OneTimeKeyQuery::new(
device_key.user_id.clone(),
device_key.device_id.clone(),
))
.unwrap(),
blocker_id: Some(claim_otk_bid),
});
// set up the according blocked item for the callback
self.blocked_items.insert(
claim_otk_bid,
M2CPacket {
header: M2CPacketType::SelfInfo(
SelfInfoType::ClaimOTK,
),
body: serde_json::to_string(&event_to_encrypt)
.unwrap(),
blocker_id: blocked_item.blocker_id,
},
);
}
}
// finally encrypt the room message and send it back to the client
self.send(M2CPacket {
header: M2CPacketType::ActionRsp(ActionType::EncryptEvent),
body: serde_json::to_string(&RoomEncryptedForSending {
algorithm: "m.megolm.v1.aes-sha2".to_string(),
sender_key: identity_keys.ed25519.clone(),
ciphertext: outbound_megolm_session.session.encrypt(
serde_json::to_string(&event_to_encrypt.event).unwrap(),
),
session_id: outbound_megolm_session.session.session_id(),
device_id: self.device_id.clone(),
})
.unwrap(),
blocker_id: blocked_item.blocker_id,
});
}
_ => {
self.send(blocked_item);
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment