use serde::{Deserialize, Serialize};
use super::{
SessionConfig, default_config, message::MegolmMessage, ratchet::Ratchet,
session_config::Version, session_keys::SessionKey,
};
use crate::{
PickleError,
cipher::Cipher,
types::Ed25519Keypair,
utilities::{pickle, unpickle},
};
pub struct GroupSession {
ratchet: Ratchet,
signing_key: Ed25519Keypair,
config: SessionConfig,
}
impl Default for GroupSession {
fn default() -> Self {
Self::new(Default::default())
}
}
impl GroupSession {
pub fn new(config: SessionConfig) -> Self {
let signing_key = Ed25519Keypair::new();
Self { signing_key, ratchet: Ratchet::new(), config }
}
pub fn session_id(&self) -> String {
self.signing_key.public_key().to_base64()
}
pub const fn message_index(&self) -> u32 {
self.ratchet.index()
}
pub const fn session_config(&self) -> SessionConfig {
self.config
}
pub fn encrypt(&mut self, plaintext: impl AsRef<[u8]>) -> MegolmMessage {
let cipher = Cipher::new_megolm(self.ratchet.as_bytes());
let message = match self.config.version {
Version::V1 => MegolmMessage::encrypt_truncated_mac(
self.message_index(),
&cipher,
&self.signing_key,
plaintext.as_ref(),
),
#[cfg(feature = "experimental-session-config")]
Version::V2 => MegolmMessage::encrypt_full_mac(
self.message_index(),
&cipher,
&self.signing_key,
plaintext.as_ref(),
),
};
self.ratchet.advance();
message
}
pub fn session_key(&self) -> SessionKey {
let mut session_key = SessionKey::new(&self.ratchet, self.signing_key.public_key());
let signature = self.signing_key.sign(&session_key.to_signature_bytes());
session_key.signature = signature;
session_key
}
pub fn pickle(&self) -> GroupSessionPickle {
GroupSessionPickle {
ratchet: self.ratchet.clone(),
signing_key: self.signing_key.clone(),
config: self.config,
}
}
pub fn from_pickle(pickle: GroupSessionPickle) -> Self {
pickle.into()
}
#[cfg(feature = "libolm-compat")]
pub fn from_libolm_pickle(
pickle: &str,
pickle_key: &[u8],
) -> Result<Self, crate::LibolmPickleError> {
use crate::{megolm::group_session::libolm_compat::Pickle, utilities::unpickle_libolm};
const PICKLE_VERSION: u32 = 1;
unpickle_libolm::<Pickle, _>(pickle, pickle_key, PICKLE_VERSION)
}
}
#[cfg(feature = "libolm-compat")]
mod libolm_compat {
use matrix_pickle::Decode;
use zeroize::{Zeroize, ZeroizeOnDrop};
use super::GroupSession;
use crate::{
Ed25519Keypair,
megolm::{SessionConfig, libolm::LibolmRatchetPickle},
utilities::LibolmEd25519Keypair,
};
#[derive(Zeroize, ZeroizeOnDrop, Decode)]
pub(super) struct Pickle {
version: u32,
ratchet: LibolmRatchetPickle,
ed25519_keypair: LibolmEd25519Keypair,
}
impl TryFrom<Pickle> for GroupSession {
type Error = crate::LibolmPickleError;
fn try_from(pickle: Pickle) -> Result<Self, Self::Error> {
#[allow(clippy::needless_borrow)]
let ratchet = (&pickle.ratchet).into();
let signing_key =
Ed25519Keypair::from_expanded_key(&pickle.ed25519_keypair.private_key)?;
Ok(Self { ratchet, signing_key, config: SessionConfig::version_1() })
}
}
}
#[derive(Serialize, Deserialize)]
pub struct GroupSessionPickle {
ratchet: Ratchet,
signing_key: Ed25519Keypair,
#[serde(default = "default_config")]
config: SessionConfig,
}
impl GroupSessionPickle {
pub fn encrypt(self, pickle_key: &[u8; 32]) -> String {
pickle(&self, pickle_key)
}
pub fn from_encrypted(ciphertext: &str, pickle_key: &[u8; 32]) -> Result<Self, PickleError> {
unpickle(ciphertext, pickle_key)
}
}
impl From<GroupSessionPickle> for GroupSession {
fn from(pickle: GroupSessionPickle) -> Self {
Self { ratchet: pickle.ratchet, signing_key: pickle.signing_key, config: pickle.config }
}
}
#[cfg(test)]
mod test {
use crate::megolm::{GroupSession, SessionConfig};
#[test]
fn create_with_session_config() {
assert_eq!(
GroupSession::new(SessionConfig::version_1()).session_config(),
SessionConfig::version_1()
);
#[cfg(feature = "experimental-session-config")]
assert_eq!(
GroupSession::new(SessionConfig::version_2()).session_config(),
SessionConfig::version_2()
);
}
}