// Copyright 2020 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use matrix_sdk_common::{ events::forwarded_room_key::ForwardedRoomKeyEventContent, identifiers::{DeviceKeyAlgorithm, EventEncryptionAlgorithm, RoomId}, }; use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, convert::TryInto}; use zeroize::Zeroize; mod inbound; mod outbound; pub use inbound::{InboundGroupSession, InboundGroupSessionPickle, PickledInboundGroupSession}; pub use outbound::{EncryptionSettings, OutboundGroupSession}; /// The private session key of a group session. /// Can be used to create a new inbound group session. #[derive(Clone, Debug, Serialize, Deserialize, Zeroize)] #[zeroize(drop)] pub struct GroupSessionKey(pub String); /// The exported version of an private session key of a group session. /// Can be used to create a new inbound group session. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Zeroize)] #[zeroize(drop)] pub struct ExportedGroupSessionKey(pub String); /// An exported version of a `InboundGroupSession` /// /// This can be used to share the `InboundGroupSession` in an exported file. #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] pub struct ExportedRoomKey { /// The encryption algorithm that the session uses. pub algorithm: EventEncryptionAlgorithm, /// The room where the session is used. pub room_id: RoomId, /// The Curve25519 key of the device which initiated the session originally. pub sender_key: String, /// The ID of the session that the key is for. pub session_id: String, /// The key for the session. pub session_key: ExportedGroupSessionKey, /// The Ed25519 key of the device which initiated the session originally. pub sender_claimed_keys: BTreeMap, /// Chain of Curve25519 keys through which this session was forwarded, via /// m.forwarded_room_key events. pub forwarding_curve25519_key_chain: Vec, } impl TryInto for ExportedRoomKey { type Error = (); /// Convert an exported room key into a content for a forwarded room key /// event. /// /// This will fail if the exported room key has multiple sender claimed keys /// or if the algorithm of the claimed sender key isn't /// `DeviceKeyAlgorithm::Ed25519`. fn try_into(self) -> Result { if self.sender_claimed_keys.len() != 1 { Err(()) } else { let (algorithm, claimed_key) = self.sender_claimed_keys.iter().next().ok_or(())?; if algorithm != &DeviceKeyAlgorithm::Ed25519 { return Err(()); } Ok(ForwardedRoomKeyEventContent { algorithm: self.algorithm, room_id: self.room_id, sender_key: self.sender_key, session_id: self.session_id, session_key: self.session_key.0.clone(), sender_claimed_ed25519_key: claimed_key.to_owned(), forwarding_curve25519_key_chain: self.forwarding_curve25519_key_chain, }) } } } impl From for ExportedRoomKey { /// Convert the content of a forwarded room key into a exported room key. fn from(forwarded_key: ForwardedRoomKeyEventContent) -> Self { let mut sender_claimed_keys: BTreeMap = BTreeMap::new(); sender_claimed_keys.insert( DeviceKeyAlgorithm::Ed25519, forwarded_key.sender_claimed_ed25519_key, ); Self { algorithm: forwarded_key.algorithm, room_id: forwarded_key.room_id, session_id: forwarded_key.session_id, forwarding_curve25519_key_chain: forwarded_key.forwarding_curve25519_key_chain, sender_claimed_keys, sender_key: forwarded_key.sender_key, session_key: ExportedGroupSessionKey(forwarded_key.session_key), } } } #[cfg(test)] mod test { use std::{ sync::Arc, time::{Duration, Instant}, }; use matrix_sdk_common::{ events::{ room::message::{MessageEventContent, TextMessageEventContent}, AnyMessageEventContent, }, identifiers::{room_id, user_id}, }; use super::EncryptionSettings; use crate::ReadOnlyAccount; #[tokio::test] #[cfg(target_os = "linux")] async fn expiration() { let settings = EncryptionSettings { rotation_period_msgs: 1, ..Default::default() }; let account = ReadOnlyAccount::new(&user_id!("@alice:example.org"), "DEVICEID".into()); let (session, _) = account .create_group_session_pair(&room_id!("!test_room:example.org"), settings) .await .unwrap(); assert!(!session.expired()); let _ = session .encrypt(AnyMessageEventContent::RoomMessage( MessageEventContent::Text(TextMessageEventContent::plain("Test message")), )) .await; assert!(session.expired()); let settings = EncryptionSettings { rotation_period: Duration::from_millis(100), ..Default::default() }; let (mut session, _) = account .create_group_session_pair(&room_id!("!test_room:example.org"), settings) .await .unwrap(); assert!(!session.expired()); session.creation_time = Arc::new(Instant::now() - Duration::from_secs(60 * 60)); assert!(session.expired()); } }