#[cfg(feature = "sqlite_cryptostore")]
use std::path::Path;
use std::{collections::BTreeMap, mem, sync::Arc};
use dashmap::DashMap;
use tracing::{debug, error, info, trace, warn};
use matrix_sdk_common::{
api::r0::{
keys::{
claim_keys::{Request as KeysClaimRequest, Response as KeysClaimResponse},
get_keys::Response as KeysQueryResponse,
upload_keys,
upload_signatures::Request as UploadSignaturesRequest,
},
sync::sync_events::Response as SyncResponse,
},
assign,
events::{
room::encrypted::EncryptedEventContent, room_key::RoomKeyEventContent,
AnyMessageEventContent, AnySyncRoomEvent, AnyToDeviceEvent, SyncMessageEvent,
ToDeviceEvent,
},
identifiers::{
DeviceId, DeviceIdBox, DeviceKeyAlgorithm, EventEncryptionAlgorithm, RoomId, UserId,
},
locks::Mutex,
uuid::Uuid,
Raw, UInt,
};
#[cfg(feature = "sqlite_cryptostore")]
use crate::store::sqlite::SqliteStore;
use crate::{
error::{EventError, MegolmError, MegolmResult, OlmError, OlmResult},
identities::{Device, IdentityManager, UserDevices},
key_request::KeyRequestMachine,
olm::{
Account, EncryptionSettings, ExportedRoomKey, GroupSessionKey, IdentityKeys,
InboundGroupSession, OlmDecryptionInfo, PrivateCrossSigningIdentity, ReadOnlyAccount,
SessionType,
},
requests::{IncomingResponse, OutgoingRequest, UploadSigningKeysRequest},
session_manager::{GroupSessionManager, SessionManager},
store::{
Changes, CryptoStore, DeviceChanges, IdentityChanges, MemoryStore, Result as StoreResult,
Store,
},
verification::{Sas, VerificationMachine},
ToDeviceRequest,
};
#[derive(Clone)]
pub struct OlmMachine {
user_id: Arc<UserId>,
device_id: Arc<Box<DeviceId>>,
account: Account,
user_identity: Arc<Mutex<PrivateCrossSigningIdentity>>,
store: Store,
session_manager: SessionManager,
group_session_manager: GroupSessionManager,
verification_machine: VerificationMachine,
key_request_machine: KeyRequestMachine,
identity_manager: IdentityManager,
cross_signing_request: Arc<Mutex<Option<UploadSignaturesRequest>>>,
}
#[cfg(not(tarpaulin_include))]
impl std::fmt::Debug for OlmMachine {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OlmMachine")
.field("user_id", &self.user_id)
.field("device_id", &self.device_id)
.finish()
}
}
impl OlmMachine {
pub fn new(user_id: &UserId, device_id: &DeviceId) -> Self {
let store: Box<dyn CryptoStore> = Box::new(MemoryStore::new());
let device_id: DeviceIdBox = device_id.into();
let account = ReadOnlyAccount::new(&user_id, &device_id);
OlmMachine::new_helper(
user_id,
device_id,
store,
account,
PrivateCrossSigningIdentity::empty(user_id.to_owned()),
)
}
fn new_helper(
user_id: &UserId,
device_id: DeviceIdBox,
store: Box<dyn CryptoStore>,
account: ReadOnlyAccount,
user_identity: PrivateCrossSigningIdentity,
) -> Self {
let user_id = Arc::new(user_id.clone());
let user_identity = Arc::new(Mutex::new(user_identity));
let store = Arc::new(store);
let verification_machine =
VerificationMachine::new(account.clone(), user_identity.clone(), store.clone());
let store = Store::new(
user_id.clone(),
user_identity.clone(),
store,
verification_machine.clone(),
);
let device_id: Arc<DeviceIdBox> = Arc::new(device_id);
let outbound_group_sessions = Arc::new(DashMap::new());
let users_for_key_claim = Arc::new(DashMap::new());
let key_request_machine = KeyRequestMachine::new(
user_id.clone(),
device_id.clone(),
store.clone(),
outbound_group_sessions,
users_for_key_claim.clone(),
);
let account = Account {
inner: account,
store: store.clone(),
};
let session_manager = SessionManager::new(
account.clone(),
users_for_key_claim,
key_request_machine.clone(),
store.clone(),
);
let group_session_manager = GroupSessionManager::new(account.clone(), store.clone());
let identity_manager = IdentityManager::new(
user_id.clone(),
device_id.clone(),
store.clone(),
group_session_manager.clone(),
);
OlmMachine {
user_id,
device_id,
account,
user_identity,
store,
session_manager,
group_session_manager,
verification_machine,
key_request_machine,
identity_manager,
cross_signing_request: Arc::new(Mutex::new(None)),
}
}
pub async fn new_with_store(
user_id: UserId,
device_id: DeviceIdBox,
store: Box<dyn CryptoStore>,
) -> StoreResult<Self> {
let account = match store.load_account().await? {
Some(a) => {
debug!("Restored account");
a
}
None => {
debug!("Creating a new account");
let account = ReadOnlyAccount::new(&user_id, &device_id);
store.save_account(account.clone()).await?;
account
}
};
let identity = match store.load_identity().await? {
Some(i) => {
debug!("Restored the cross signing identity");
i
}
None => {
debug!("Creating an empty cross signing identity stub");
PrivateCrossSigningIdentity::empty(user_id.clone())
}
};
Ok(OlmMachine::new_helper(
&user_id, device_id, store, account, identity,
))
}
#[cfg(feature = "sqlite_cryptostore")]
#[cfg_attr(feature = "docs", doc(cfg(r#sqlite_cryptostore)))]
pub async fn new_with_default_store(
user_id: &UserId,
device_id: &DeviceId,
path: impl AsRef<Path>,
passphrase: &str,
) -> StoreResult<Self> {
let store =
SqliteStore::open_with_passphrase(&user_id, device_id, path, passphrase).await?;
OlmMachine::new_with_store(user_id.to_owned(), device_id.into(), Box::new(store)).await
}
pub fn user_id(&self) -> &UserId {
&self.user_id
}
pub fn device_id(&self) -> &DeviceId {
&self.device_id
}
pub fn identity_keys(&self) -> &IdentityKeys {
self.account.identity_keys()
}
pub async fn outgoing_requests(&self) -> Vec<OutgoingRequest> {
let mut requests = Vec::new();
if let Some(r) = self.keys_for_upload().await.map(|r| OutgoingRequest {
request_id: Uuid::new_v4(),
request: Arc::new(r.into()),
}) {
requests.push(r);
}
if let Some(r) =
self.identity_manager
.users_for_key_query()
.await
.map(|r| OutgoingRequest {
request_id: Uuid::new_v4(),
request: Arc::new(r.into()),
})
{
requests.push(r);
}
requests.append(&mut self.outgoing_to_device_requests());
requests.append(&mut self.key_request_machine.outgoing_to_device_requests());
requests
}
pub async fn mark_request_as_sent<'a>(
&self,
request_id: &Uuid,
response: impl Into<IncomingResponse<'a>>,
) -> OlmResult<()> {
match response.into() {
IncomingResponse::KeysUpload(response) => {
self.receive_keys_upload_response(response).await?;
}
IncomingResponse::KeysQuery(response) => {
self.receive_keys_query_response(response).await?;
}
IncomingResponse::KeysClaim(response) => {
self.receive_keys_claim_response(response).await?;
}
IncomingResponse::ToDevice(_) => {
self.mark_to_device_request_as_sent(&request_id).await?;
}
IncomingResponse::SigningKeysUpload(_) => {
self.receive_cross_signing_upload_response().await?;
}
IncomingResponse::SignatureUpload(_) => {
self.verification_machine.mark_request_as_sent(request_id);
}
};
Ok(())
}
async fn receive_cross_signing_upload_response(&self) -> StoreResult<()> {
let identity = self.user_identity.lock().await;
identity.mark_as_shared();
let changes = Changes {
private_identity: Some(identity.clone()),
..Default::default()
};
self.store.save_changes(changes).await
}
pub async fn bootstrap_cross_signing(
&self,
reset: bool,
) -> StoreResult<(UploadSigningKeysRequest, UploadSignaturesRequest)> {
let mut identity = self.user_identity.lock().await;
if identity.is_empty().await || reset {
info!("Creating new cross signing identity");
let (id, request, signature_request) = self.account.bootstrap_cross_signing().await;
*identity = id;
let public = identity.as_public_identity().await.expect(
"Couldn't create a public version of the identity from a new private identity",
);
let changes = Changes {
identities: IdentityChanges {
new: vec![public.into()],
..Default::default()
},
private_identity: Some(identity.clone()),
..Default::default()
};
self.store.save_changes(changes).await?;
Ok((request, signature_request))
} else {
info!("Trying to upload the existing cross signing identity");
let request = identity.as_upload_request().await;
let signature_request = identity
.sign_account(&self.account)
.await
.expect("Can't sign device keys");
Ok((request, signature_request))
}
}
#[cfg(test)]
async fn should_upload_keys(&self) -> bool {
self.account.should_upload_keys().await
}
#[cfg(test)]
pub(crate) fn account(&self) -> &ReadOnlyAccount {
&self.account
}
async fn receive_keys_upload_response(
&self,
response: &upload_keys::Response,
) -> OlmResult<()> {
self.account.receive_keys_upload_response(response).await
}
pub async fn get_missing_sessions(
&self,
users: &mut impl Iterator<Item = &UserId>,
) -> OlmResult<Option<(Uuid, KeysClaimRequest)>> {
self.session_manager.get_missing_sessions(users).await
}
async fn receive_keys_claim_response(&self, response: &KeysClaimResponse) -> OlmResult<()> {
self.session_manager
.receive_keys_claim_response(response)
.await
}
async fn receive_keys_query_response(
&self,
response: &KeysQueryResponse,
) -> OlmResult<(DeviceChanges, IdentityChanges)> {
self.identity_manager
.receive_keys_query_response(response)
.await
}
async fn keys_for_upload(&self) -> Option<upload_keys::Request> {
let (device_keys, one_time_keys) = self.account.keys_for_upload().await?;
Some(assign!(upload_keys::Request::new(), { device_keys, one_time_keys }))
}
async fn decrypt_to_device_event(
&self,
event: &ToDeviceEvent<EncryptedEventContent>,
) -> OlmResult<OlmDecryptionInfo> {
let mut decrypted = self.account.decrypt_to_device_event(event).await?;
if let (Some(event), group_session) =
self.handle_decrypted_to_device_event(&decrypted).await?
{
decrypted.event = event;
decrypted.inbound_group_session = group_session;
}
Ok(decrypted)
}
async fn add_room_key(
&self,
sender_key: &str,
signing_key: &str,
event: &mut ToDeviceEvent<RoomKeyEventContent>,
) -> OlmResult<(Option<Raw<AnyToDeviceEvent>>, Option<InboundGroupSession>)> {
match event.content.algorithm {
EventEncryptionAlgorithm::MegolmV1AesSha2 => {
let session_key = GroupSessionKey(mem::take(&mut event.content.session_key));
let session = InboundGroupSession::new(
sender_key,
signing_key,
&event.content.room_id,
session_key,
)?;
let event = Raw::from(AnyToDeviceEvent::RoomKey(event.clone()));
Ok((Some(event), Some(session)))
}
_ => {
warn!(
"Received room key with unsupported key algorithm {}",
event.content.algorithm
);
Ok((None, None))
}
}
}
#[cfg(test)]
pub(crate) async fn create_outbound_group_session_with_defaults(
&self,
room_id: &RoomId,
) -> OlmResult<()> {
let (_, session) = self
.group_session_manager
.create_outbound_group_session(room_id, EncryptionSettings::default())
.await?;
self.store.save_inbound_group_sessions(&[session]).await?;
Ok(())
}
pub async fn encrypt(
&self,
room_id: &RoomId,
content: AnyMessageEventContent,
) -> MegolmResult<EncryptedEventContent> {
self.group_session_manager.encrypt(room_id, content).await
}
pub fn should_share_group_session(&self, room_id: &RoomId) -> bool {
self.group_session_manager
.should_share_group_session(room_id)
}
pub fn invalidate_group_session(&self, room_id: &RoomId) -> bool {
self.group_session_manager.invalidate_group_session(room_id)
}
pub async fn share_group_session(
&self,
room_id: &RoomId,
users: impl Iterator<Item = &UserId>,
encryption_settings: impl Into<EncryptionSettings>,
) -> OlmResult<Vec<Arc<ToDeviceRequest>>> {
self.group_session_manager
.share_group_session(room_id, users, encryption_settings)
.await
}
async fn handle_decrypted_to_device_event(
&self,
decrypted: &OlmDecryptionInfo,
) -> OlmResult<(Option<Raw<AnyToDeviceEvent>>, Option<InboundGroupSession>)> {
let event = match decrypted.event.deserialize() {
Ok(e) => e,
Err(e) => {
warn!(
"Decrypted to-device event failed to be parsed correctly {:?}",
e
);
return Ok((None, None));
}
};
match event {
AnyToDeviceEvent::RoomKey(mut e) => Ok(self
.add_room_key(&decrypted.sender_key, &decrypted.signing_key, &mut e)
.await?),
AnyToDeviceEvent::ForwardedRoomKey(mut e) => Ok(self
.key_request_machine
.receive_forwarded_room_key(&decrypted.sender_key, &mut e)
.await?),
_ => {
warn!("Received an unexpected encrypted to-device event");
Ok((None, None))
}
}
}
async fn handle_verification_event(&self, mut event: &mut AnyToDeviceEvent) {
if let Err(e) = self.verification_machine.receive_event(&mut event).await {
error!("Error handling a verification event: {:?}", e);
}
}
fn outgoing_to_device_requests(&self) -> Vec<OutgoingRequest> {
self.verification_machine.outgoing_to_device_requests()
}
async fn mark_to_device_request_as_sent(&self, request_id: &Uuid) -> StoreResult<()> {
self.verification_machine.mark_request_as_sent(request_id);
self.key_request_machine
.mark_outgoing_request_as_sent(request_id)
.await?;
self.group_session_manager.mark_request_as_sent(request_id);
self.session_manager
.mark_outgoing_request_as_sent(request_id);
Ok(())
}
pub fn get_verification(&self, flow_id: &str) -> Option<Sas> {
self.verification_machine.get_sas(flow_id)
}
async fn update_one_time_key_count(&self, key_count: &BTreeMap<DeviceKeyAlgorithm, UInt>) {
self.account.update_uploaded_key_count(key_count).await;
}
pub async fn receive_sync_response(&self, response: &mut SyncResponse) -> OlmResult<()> {
self.verification_machine.garbage_collect();
let mut changes = Changes {
account: Some(self.account.inner.clone()),
..Default::default()
};
self.update_one_time_key_count(&response.device_one_time_keys_count)
.await;
for user_id in &response.device_lists.changed {
if let Err(e) = self.identity_manager.mark_user_as_changed(&user_id).await {
error!("Error marking a tracked user as changed {:?}", e);
}
}
for event_result in &mut response.to_device.events {
let mut event = if let Ok(e) = event_result.deserialize() {
e
} else {
warn!("Received an invalid to-device event {:?}", event_result);
continue;
};
info!("Received a to-device event {:?}", event);
match &mut event {
AnyToDeviceEvent::RoomEncrypted(e) => {
let decrypted = match self.decrypt_to_device_event(e).await {
Ok(e) => e,
Err(err) => {
warn!(
"Failed to decrypt to-device event from {} {}",
e.sender, err
);
if let OlmError::SessionWedged(sender, curve_key) = err {
if let Err(e) = self
.session_manager
.mark_device_as_wedged(&sender, &curve_key)
.await
{
error!(
"Couldn't mark device from {} to be unwedged {:?}",
sender, e
);
}
}
continue;
}
};
match decrypted.session {
SessionType::New(s) => {
changes.sessions.push(s);
changes.account = Some(self.account.inner.clone());
}
SessionType::Existing(s) => {
changes.sessions.push(s);
}
}
changes.message_hashes.push(decrypted.message_hash);
if let Some(group_session) = decrypted.inbound_group_session {
changes.inbound_group_sessions.push(group_session);
}
*event_result = decrypted.event;
}
AnyToDeviceEvent::RoomKeyRequest(e) => {
self.key_request_machine.receive_incoming_key_request(e)
}
AnyToDeviceEvent::KeyVerificationAccept(..)
| AnyToDeviceEvent::KeyVerificationCancel(..)
| AnyToDeviceEvent::KeyVerificationKey(..)
| AnyToDeviceEvent::KeyVerificationMac(..)
| AnyToDeviceEvent::KeyVerificationRequest(..)
| AnyToDeviceEvent::KeyVerificationStart(..) => {
self.handle_verification_event(&mut event).await;
}
_ => continue,
}
}
let changed_sessions = self
.key_request_machine
.collect_incoming_key_requests()
.await?;
changes.sessions.extend(changed_sessions);
Ok(self.store.save_changes(changes).await?)
}
pub async fn decrypt_room_event(
&self,
event: &SyncMessageEvent<EncryptedEventContent>,
room_id: &RoomId,
) -> MegolmResult<Raw<AnySyncRoomEvent>> {
let content = match &event.content {
EncryptedEventContent::MegolmV1AesSha2(c) => c,
_ => return Err(EventError::UnsupportedAlgorithm.into()),
};
let session = self
.store
.get_inbound_group_session(room_id, &content.sender_key, &content.session_id)
.await?;
let session = if let Some(s) = session {
s
} else {
self.key_request_machine
.create_outgoing_key_request(room_id, &content.sender_key, &content.session_id)
.await?;
return Err(MegolmError::MissingSession);
};
let (decrypted_event, _) = session.decrypt(event).await?;
trace!("Successfully decrypted Megolm event {:?}", decrypted_event);
Ok(decrypted_event)
}
pub async fn update_tracked_users(&self, users: impl IntoIterator<Item = &UserId>) {
self.identity_manager.update_tracked_users(users).await
}
pub async fn get_device(
&self,
user_id: &UserId,
device_id: &DeviceId,
) -> StoreResult<Option<Device>> {
self.store.get_device(user_id, device_id).await
}
pub async fn get_user_devices(&self, user_id: &UserId) -> StoreResult<UserDevices> {
self.store.get_user_devices(user_id).await
}
pub async fn import_keys(
&self,
exported_keys: Vec<ExportedRoomKey>,
) -> StoreResult<(usize, usize)> {
struct ShallowSessions {
inner: BTreeMap<Arc<RoomId>, u32>,
}
impl ShallowSessions {
fn has_better_session(&self, session: &InboundGroupSession) -> bool {
self.inner
.get(&session.room_id)
.map(|existing| existing <= &session.first_known_index())
.unwrap_or(false)
}
}
let mut sessions = Vec::new();
let existing_sessions = ShallowSessions {
inner: self
.store
.get_inbound_group_sessions()
.await?
.into_iter()
.map(|s| {
let index = s.first_known_index();
(s.room_id, index)
})
.collect(),
};
let total_sessions = exported_keys.len();
for key in exported_keys.into_iter() {
let session = InboundGroupSession::from_export(key)?;
if !existing_sessions.has_better_session(&session) {
sessions.push(session)
}
}
let num_sessions = sessions.len();
let changes = Changes {
inbound_group_sessions: sessions,
..Default::default()
};
self.store.save_changes(changes).await?;
info!(
"Successfully imported {} inbound group sessions",
num_sessions
);
Ok((num_sessions, total_sessions))
}
pub async fn export_keys(
&self,
mut predicate: impl FnMut(&InboundGroupSession) -> bool,
) -> StoreResult<Vec<ExportedRoomKey>> {
let mut exported = Vec::new();
let mut sessions: Vec<InboundGroupSession> = self
.store
.get_inbound_group_sessions()
.await?
.drain(..)
.filter(|s| predicate(&s))
.collect();
for session in sessions.drain(..) {
let export = session.export().await;
exported.push(export);
}
Ok(exported)
}
}
#[cfg(test)]
pub(crate) mod test {
static USER_ID: &str = "@bob:example.org";
use std::{
collections::BTreeMap,
convert::{TryFrom, TryInto},
sync::Arc,
time::SystemTime,
};
use http::Response;
use serde_json::json;
#[cfg(feature = "sqlite_cryptostore")]
use tempfile::tempdir;
use crate::{
machine::OlmMachine,
olm::Utility,
verification::test::{outgoing_request_to_event, request_to_event},
EncryptionSettings, ReadOnlyDevice, ToDeviceRequest,
};
use matrix_sdk_common::{
api::r0::keys::{claim_keys, get_keys, upload_keys, OneTimeKey},
events::{
room::{
encrypted::EncryptedEventContent,
message::{MessageEventContent, TextMessageEventContent},
},
AnyMessageEventContent, AnySyncMessageEvent, AnySyncRoomEvent, AnyToDeviceEvent,
EventType, SyncMessageEvent, ToDeviceEvent, Unsigned,
},
identifiers::{
event_id, room_id, user_id, DeviceId, DeviceKeyAlgorithm, DeviceKeyId, UserId,
},
Raw,
};
use matrix_sdk_test::test_json;
type OneTimeKeys = BTreeMap<DeviceKeyId, OneTimeKey>;
use matrix_sdk_common::uint;
fn alice_id() -> UserId {
user_id!("@alice:example.org")
}
fn alice_device_id() -> Box<DeviceId> {
"JLAFKJWSCS".into()
}
fn user_id() -> UserId {
UserId::try_from(USER_ID).unwrap()
}
pub fn response_from_file(json: &serde_json::Value) -> Response<Vec<u8>> {
Response::builder()
.status(200)
.body(json.to_string().as_bytes().to_vec())
.unwrap()
}
fn keys_upload_response() -> upload_keys::Response {
let data = response_from_file(&test_json::KEYS_UPLOAD);
upload_keys::Response::try_from(data).expect("Can't parse the keys upload response")
}
fn keys_query_response() -> get_keys::Response {
let data = response_from_file(&test_json::KEYS_QUERY);
get_keys::Response::try_from(data).expect("Can't parse the keys upload response")
}
fn to_device_requests_to_content(requests: Vec<Arc<ToDeviceRequest>>) -> EncryptedEventContent {
let to_device_request = &requests[0];
let content: Raw<EncryptedEventContent> = serde_json::from_str(
to_device_request
.messages
.values()
.next()
.unwrap()
.values()
.next()
.unwrap()
.get(),
)
.unwrap();
content.deserialize().unwrap()
}
pub(crate) async fn get_prepared_machine() -> (OlmMachine, OneTimeKeys) {
let machine = OlmMachine::new(&user_id(), &alice_device_id());
machine.account.inner.update_uploaded_key_count(0);
let request = machine
.keys_for_upload()
.await
.expect("Can't prepare initial key upload");
let response = keys_upload_response();
machine
.receive_keys_upload_response(&response)
.await
.unwrap();
(machine, request.one_time_keys.unwrap())
}
async fn get_machine_after_query() -> (OlmMachine, OneTimeKeys) {
let (machine, otk) = get_prepared_machine().await;
let response = keys_query_response();
machine
.receive_keys_query_response(&response)
.await
.unwrap();
(machine, otk)
}
async fn get_machine_pair() -> (OlmMachine, OlmMachine, OneTimeKeys) {
let (bob, otk) = get_prepared_machine().await;
let alice_id = alice_id();
let alice_device = alice_device_id();
let alice = OlmMachine::new(&alice_id, &alice_device);
let alice_deivce = ReadOnlyDevice::from_machine(&alice).await;
let bob_device = ReadOnlyDevice::from_machine(&bob).await;
alice.store.save_devices(&[bob_device]).await.unwrap();
bob.store.save_devices(&[alice_deivce]).await.unwrap();
(alice, bob, otk)
}
async fn get_machine_pair_with_session() -> (OlmMachine, OlmMachine) {
let (alice, bob, one_time_keys) = get_machine_pair().await;
let mut bob_keys = BTreeMap::new();
let one_time_key = one_time_keys.iter().next().unwrap();
let mut keys = BTreeMap::new();
keys.insert(one_time_key.0.clone(), one_time_key.1.clone());
bob_keys.insert(bob.device_id().into(), keys);
let mut one_time_keys = BTreeMap::new();
one_time_keys.insert(bob.user_id().clone(), bob_keys);
let response = claim_keys::Response::new(one_time_keys);
alice.receive_keys_claim_response(&response).await.unwrap();
(alice, bob)
}
async fn get_machine_pair_with_setup_sessions() -> (OlmMachine, OlmMachine) {
let (alice, bob) = get_machine_pair_with_session().await;
let bob_device = alice
.get_device(&bob.user_id, &bob.device_id)
.await
.unwrap()
.unwrap();
let (session, content) = bob_device
.encrypt(EventType::Dummy, json!({}))
.await
.unwrap();
alice.store.save_sessions(&[session]).await.unwrap();
let event = ToDeviceEvent {
sender: alice.user_id().clone(),
content,
};
let decrypted = bob.decrypt_to_device_event(&event).await.unwrap();
bob.store
.save_sessions(&[decrypted.session.session()])
.await
.unwrap();
(alice, bob)
}
#[tokio::test]
async fn create_olm_machine() {
let machine = OlmMachine::new(&user_id(), &alice_device_id());
assert!(machine.should_upload_keys().await);
}
#[tokio::test]
async fn receive_keys_upload_response() {
let machine = OlmMachine::new(&user_id(), &alice_device_id());
let mut response = keys_upload_response();
response
.one_time_key_counts
.remove(&DeviceKeyAlgorithm::SignedCurve25519)
.unwrap();
assert!(machine.should_upload_keys().await);
machine
.receive_keys_upload_response(&response)
.await
.unwrap();
assert!(machine.should_upload_keys().await);
response
.one_time_key_counts
.insert(DeviceKeyAlgorithm::SignedCurve25519, uint!(10));
machine
.receive_keys_upload_response(&response)
.await
.unwrap();
assert!(machine.should_upload_keys().await);
response
.one_time_key_counts
.insert(DeviceKeyAlgorithm::SignedCurve25519, uint!(50));
machine
.receive_keys_upload_response(&response)
.await
.unwrap();
assert!(!machine.should_upload_keys().await);
}
#[tokio::test]
async fn generate_one_time_keys() {
let machine = OlmMachine::new(&user_id(), &alice_device_id());
let mut response = keys_upload_response();
assert!(machine.should_upload_keys().await);
machine
.receive_keys_upload_response(&response)
.await
.unwrap();
assert!(machine.should_upload_keys().await);
assert!(machine.account.generate_one_time_keys().await.is_ok());
response
.one_time_key_counts
.insert(DeviceKeyAlgorithm::SignedCurve25519, uint!(50));
machine
.receive_keys_upload_response(&response)
.await
.unwrap();
assert!(machine.account.generate_one_time_keys().await.is_err());
}
#[tokio::test]
async fn test_device_key_signing() {
let machine = OlmMachine::new(&user_id(), &alice_device_id());
let mut device_keys = machine.account.device_keys().await;
let identity_keys = machine.account.identity_keys();
let ed25519_key = identity_keys.ed25519();
let utility = Utility::new();
let ret = utility.verify_json(
&machine.user_id,
&DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, machine.device_id()),
ed25519_key,
&mut json!(&mut device_keys),
);
assert!(ret.is_ok());
}
#[tokio::test]
async fn tests_session_invalidation() {
let machine = OlmMachine::new(&user_id(), &alice_device_id());
let room_id = room_id!("!test:example.org");
machine
.create_outbound_group_session_with_defaults(&room_id)
.await
.unwrap();
assert!(machine
.group_session_manager
.get_outbound_group_session(&room_id)
.is_some());
machine.invalidate_group_session(&room_id);
assert!(machine
.group_session_manager
.get_outbound_group_session(&room_id)
.is_none());
}
#[tokio::test]
async fn test_invalid_signature() {
let machine = OlmMachine::new(&user_id(), &alice_device_id());
let mut device_keys = machine.account.device_keys().await;
let utility = Utility::new();
let ret = utility.verify_json(
&machine.user_id,
&DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, machine.device_id()),
"fake_key",
&mut json!(&mut device_keys),
);
assert!(ret.is_err());
}
#[tokio::test]
async fn test_one_time_key_signing() {
let machine = OlmMachine::new(&user_id(), &alice_device_id());
machine.account.inner.update_uploaded_key_count(49);
let mut one_time_keys = machine.account.signed_one_time_keys().await.unwrap();
let identity_keys = machine.account.identity_keys();
let ed25519_key = identity_keys.ed25519();
let mut one_time_key = one_time_keys.values_mut().next().unwrap();
let utility = Utility::new();
let ret = utility.verify_json(
&machine.user_id,
&DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, machine.device_id()),
ed25519_key,
&mut json!(&mut one_time_key),
);
assert!(ret.is_ok());
}
#[tokio::test]
async fn test_keys_for_upload() {
let machine = OlmMachine::new(&user_id(), &alice_device_id());
machine.account.inner.update_uploaded_key_count(0);
let identity_keys = machine.account.identity_keys();
let ed25519_key = identity_keys.ed25519();
let mut request = machine
.keys_for_upload()
.await
.expect("Can't prepare initial key upload");
let utility = Utility::new();
let ret = utility.verify_json(
&machine.user_id,
&DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, machine.device_id()),
ed25519_key,
&mut json!(&mut request.one_time_keys.as_mut().unwrap().values_mut().next()),
);
assert!(ret.is_ok());
let utility = Utility::new();
let ret = utility.verify_json(
&machine.user_id,
&DeviceKeyId::from_parts(DeviceKeyAlgorithm::Ed25519, machine.device_id()),
ed25519_key,
&mut json!(&mut request.device_keys.unwrap()),
);
assert!(ret.is_ok());
let mut response = keys_upload_response();
response.one_time_key_counts.insert(
DeviceKeyAlgorithm::SignedCurve25519,
(request.one_time_keys.unwrap().len() as u64)
.try_into()
.unwrap(),
);
machine
.receive_keys_upload_response(&response)
.await
.unwrap();
let ret = machine.keys_for_upload().await;
assert!(ret.is_none());
}
#[tokio::test]
async fn test_keys_query() {
let (machine, _) = get_prepared_machine().await;
let response = keys_query_response();
let alice_id = user_id!("@alice:example.org");
let alice_device_id: &DeviceId = "JLAFKJWSCS".into();
let alice_devices = machine.store.get_user_devices(&alice_id).await.unwrap();
assert!(alice_devices.devices().peekable().peek().is_none());
machine
.receive_keys_query_response(&response)
.await
.unwrap();
let device = machine
.store
.get_device(&alice_id, alice_device_id)
.await
.unwrap()
.unwrap();
assert_eq!(device.user_id(), &alice_id);
assert_eq!(device.device_id(), alice_device_id);
}
#[tokio::test]
async fn test_missing_sessions_calculation() {
let (machine, _) = get_machine_after_query().await;
let alice = alice_id();
let alice_device = alice_device_id();
let (_, missing_sessions) = machine
.get_missing_sessions(&mut [alice.clone()].iter())
.await
.unwrap()
.unwrap();
assert!(missing_sessions.one_time_keys.contains_key(&alice));
let user_sessions = missing_sessions.one_time_keys.get(&alice).unwrap();
assert!(user_sessions.contains_key(&alice_device));
}
#[tokio::test]
async fn test_session_creation() {
let (alice_machine, bob_machine, one_time_keys) = get_machine_pair().await;
let mut bob_keys = BTreeMap::new();
let one_time_key = one_time_keys.iter().next().unwrap();
let mut keys = BTreeMap::new();
keys.insert(one_time_key.0.clone(), one_time_key.1.clone());
bob_keys.insert(bob_machine.device_id().into(), keys);
let mut one_time_keys = BTreeMap::new();
one_time_keys.insert(bob_machine.user_id().clone(), bob_keys);
let response = claim_keys::Response::new(one_time_keys);
alice_machine
.receive_keys_claim_response(&response)
.await
.unwrap();
let session = alice_machine
.store
.get_sessions(bob_machine.account.identity_keys().curve25519())
.await
.unwrap()
.unwrap();
assert!(!session.lock().await.is_empty())
}
#[tokio::test]
async fn test_olm_encryption() {
let (alice, bob) = get_machine_pair_with_session().await;
let bob_device = alice
.get_device(&bob.user_id, &bob.device_id)
.await
.unwrap()
.unwrap();
let event = ToDeviceEvent {
sender: alice.user_id().clone(),
content: bob_device
.encrypt(EventType::Dummy, json!({}))
.await
.unwrap()
.1,
};
let event = bob
.decrypt_to_device_event(&event)
.await
.unwrap()
.event
.deserialize()
.unwrap();
if let AnyToDeviceEvent::Dummy(e) = event {
assert_eq!(&e.sender, alice.user_id());
} else {
panic!("Wrong event type found {:?}", event);
}
}
#[tokio::test]
async fn test_room_key_sharing() {
let (alice, bob) = get_machine_pair_with_session().await;
let room_id = room_id!("!test:example.org");
let to_device_requests = alice
.share_group_session(
&room_id,
[bob.user_id().clone()].iter(),
EncryptionSettings::default(),
)
.await
.unwrap();
let event = ToDeviceEvent {
sender: alice.user_id().clone(),
content: to_device_requests_to_content(to_device_requests),
};
let alice_session = alice
.group_session_manager
.get_outbound_group_session(&room_id)
.unwrap();
let decrypted = bob.decrypt_to_device_event(&event).await.unwrap();
bob.store
.save_sessions(&[decrypted.session.session()])
.await
.unwrap();
bob.store
.save_inbound_group_sessions(&[decrypted.inbound_group_session.unwrap()])
.await
.unwrap();
let event = decrypted.event.deserialize().unwrap();
if let AnyToDeviceEvent::RoomKey(event) = event {
assert_eq!(&event.sender, alice.user_id());
assert!(event.content.session_key.is_empty());
} else {
panic!("expected RoomKeyEvent found {:?}", event);
}
let session = bob
.store
.get_inbound_group_session(
&room_id,
alice.account.identity_keys().curve25519(),
alice_session.session_id(),
)
.await;
assert!(session.unwrap().is_some());
}
#[tokio::test]
async fn test_megolm_encryption() {
let (alice, bob) = get_machine_pair_with_setup_sessions().await;
let room_id = room_id!("!test:example.org");
let to_device_requests = alice
.share_group_session(
&room_id,
[bob.user_id().clone()].iter(),
EncryptionSettings::default(),
)
.await
.unwrap();
let event = ToDeviceEvent {
sender: alice.user_id().clone(),
content: to_device_requests_to_content(to_device_requests),
};
let group_session = bob
.decrypt_to_device_event(&event)
.await
.unwrap()
.inbound_group_session;
bob.store
.save_inbound_group_sessions(&[group_session.unwrap()])
.await
.unwrap();
let plaintext = "It is a secret to everybody";
let content = MessageEventContent::Text(TextMessageEventContent::plain(plaintext));
let encrypted_content = alice
.encrypt(
&room_id,
AnyMessageEventContent::RoomMessage(content.clone()),
)
.await
.unwrap();
let event = SyncMessageEvent {
event_id: event_id!("$xxxxx:example.org"),
origin_server_ts: SystemTime::now(),
sender: alice.user_id().clone(),
content: encrypted_content,
unsigned: Unsigned::default(),
};
let decrypted_event = bob
.decrypt_room_event(&event, &room_id)
.await
.unwrap()
.deserialize()
.unwrap();
match decrypted_event {
AnySyncRoomEvent::Message(AnySyncMessageEvent::RoomMessage(SyncMessageEvent {
sender,
content,
..
})) => {
assert_eq!(&sender, alice.user_id());
if let MessageEventContent::Text(c) = &content {
assert_eq!(&c.body, plaintext);
} else {
panic!("Decrypted event has a missmatched content");
}
}
_ => panic!("Decrypted room event has the wrong type"),
}
}
#[tokio::test(threaded_scheduler)]
#[cfg(feature = "sqlite_cryptostore")]
async fn test_machine_with_default_store() {
let tmpdir = tempdir().unwrap();
let machine = OlmMachine::new_with_default_store(
&user_id(),
&alice_device_id(),
tmpdir.as_ref(),
"test",
)
.await
.unwrap();
let user_id = machine.user_id().to_owned();
let device_id = machine.device_id().to_owned();
let ed25519_key = machine.identity_keys().ed25519().to_owned();
machine
.receive_keys_upload_response(&keys_upload_response())
.await
.unwrap();
drop(machine);
let machine = OlmMachine::new_with_default_store(
&user_id,
&alice_device_id(),
tmpdir.as_ref(),
"test",
)
.await
.unwrap();
assert_eq!(&user_id, machine.user_id());
assert_eq!(&*device_id, machine.device_id());
assert_eq!(ed25519_key, machine.identity_keys().ed25519());
}
#[tokio::test]
async fn interactive_verification() {
let (alice, bob) = get_machine_pair_with_setup_sessions().await;
let bob_device = alice
.get_device(bob.user_id(), bob.device_id())
.await
.unwrap()
.unwrap();
assert!(!bob_device.is_trusted());
let (alice_sas, request) = bob_device.start_verification().await.unwrap();
let mut event = request_to_event(alice.user_id(), &request);
bob.handle_verification_event(&mut event).await;
let bob_sas = bob.get_verification(alice_sas.flow_id()).unwrap();
assert!(alice_sas.emoji().is_none());
assert!(bob_sas.emoji().is_none());
let mut event = bob_sas
.accept()
.map(|r| request_to_event(bob.user_id(), &r))
.unwrap();
alice.handle_verification_event(&mut event).await;
let mut event = alice
.outgoing_to_device_requests()
.first()
.map(|r| outgoing_request_to_event(alice.user_id(), r))
.unwrap();
bob.handle_verification_event(&mut event).await;
let mut event = bob
.outgoing_to_device_requests()
.first()
.map(|r| outgoing_request_to_event(bob.user_id(), r))
.unwrap();
alice.handle_verification_event(&mut event).await;
assert!(alice_sas.emoji().is_some());
assert!(bob_sas.emoji().is_some());
assert_eq!(alice_sas.emoji(), bob_sas.emoji());
assert_eq!(alice_sas.decimals(), bob_sas.decimals());
let mut event = bob_sas
.confirm()
.await
.unwrap()
.0
.map(|r| request_to_event(bob.user_id(), &r))
.unwrap();
alice.handle_verification_event(&mut event).await;
assert!(!alice_sas.is_done());
assert!(!bob_sas.is_done());
let mut event = alice_sas
.confirm()
.await
.unwrap()
.0
.map(|r| request_to_event(alice.user_id(), &r))
.unwrap();
assert!(alice_sas.is_done());
assert!(bob_device.is_trusted());
let alice_device = bob
.get_device(alice.user_id(), alice.device_id())
.await
.unwrap()
.unwrap();
assert!(!alice_device.is_trusted());
bob.handle_verification_event(&mut event).await;
assert!(bob_sas.is_done());
assert!(alice_device.is_trusted());
}
}