2020-02-25 13:24:18 +00:00
|
|
|
// 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.
|
|
|
|
|
2020-04-23 08:52:47 +00:00
|
|
|
use std::collections::{BTreeMap, HashMap, HashSet};
|
2020-07-14 09:17:09 +00:00
|
|
|
use std::convert::TryFrom;
|
2020-06-20 21:18:20 +00:00
|
|
|
use std::convert::TryInto;
|
2020-04-10 15:02:30 +00:00
|
|
|
use std::mem;
|
2020-03-21 15:41:48 +00:00
|
|
|
#[cfg(feature = "sqlite-cryptostore")]
|
2020-03-18 14:50:32 +00:00
|
|
|
use std::path::Path;
|
2020-03-18 13:15:56 +00:00
|
|
|
use std::result::Result as StdResult;
|
2020-02-25 13:24:18 +00:00
|
|
|
|
2020-04-30 15:10:12 +00:00
|
|
|
use super::error::{EventError, MegolmError, MegolmResult, OlmError, OlmResult, SignatureError};
|
2020-04-21 08:41:08 +00:00
|
|
|
use super::olm::{
|
2020-04-28 13:05:20 +00:00
|
|
|
Account, GroupSessionKey, IdentityKeys, InboundGroupSession, OlmMessage, OlmUtility,
|
|
|
|
OutboundGroupSession, Session,
|
2020-04-21 08:41:08 +00:00
|
|
|
};
|
2020-03-30 15:07:36 +00:00
|
|
|
use super::store::memorystore::MemoryStore;
|
2020-03-18 14:50:32 +00:00
|
|
|
#[cfg(feature = "sqlite-cryptostore")]
|
2020-04-30 15:10:12 +00:00
|
|
|
use super::store::sqlite::SqliteStore;
|
|
|
|
use super::{device::Device, store::Result as StoreError, CryptoStore};
|
2020-02-25 13:24:18 +00:00
|
|
|
|
2020-05-07 06:51:59 +00:00
|
|
|
use matrix_sdk_common::api;
|
|
|
|
use matrix_sdk_common::events::{
|
2020-06-20 21:18:20 +00:00
|
|
|
forwarded_room_key::ForwardedRoomKeyEventContent,
|
2020-07-13 11:19:25 +00:00
|
|
|
room::encrypted::{CiphertextInfo, EncryptedEventContent, OlmV1Curve25519AesSha2Content},
|
2020-04-09 14:22:25 +00:00
|
|
|
room::message::MessageEventContent,
|
2020-06-20 21:18:20 +00:00
|
|
|
room_key::RoomKeyEventContent,
|
|
|
|
room_key_request::RoomKeyRequestEventContent,
|
|
|
|
Algorithm, AnyRoomEventStub, AnyToDeviceEvent, EventJson, EventType, MessageEventStub,
|
|
|
|
ToDeviceEvent,
|
2020-03-12 14:41:11 +00:00
|
|
|
};
|
2020-05-07 06:51:59 +00:00
|
|
|
use matrix_sdk_common::identifiers::{DeviceId, RoomId, UserId};
|
2020-05-08 14:12:21 +00:00
|
|
|
use matrix_sdk_common::uuid::Uuid;
|
2020-04-21 08:41:08 +00:00
|
|
|
|
|
|
|
use api::r0::keys;
|
|
|
|
use api::r0::{
|
2020-07-10 15:53:04 +00:00
|
|
|
keys::{AlgorithmAndDeviceId, DeviceKeys, KeyAlgorithm, OneTimeKey},
|
2020-04-23 08:52:47 +00:00
|
|
|
sync::sync_events::Response as SyncResponse,
|
2020-05-05 13:29:25 +00:00
|
|
|
to_device::{send_event_to_device::Request as ToDeviceRequest, DeviceIdOrAllDevices},
|
2020-04-21 08:41:08 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
use serde_json::{json, Value};
|
|
|
|
use tracing::{debug, error, info, instrument, trace, warn};
|
2020-03-10 12:02:14 +00:00
|
|
|
|
2020-04-30 15:10:12 +00:00
|
|
|
/// A map from the algorithm and device id to a one-time key.
|
|
|
|
///
|
|
|
|
/// These keys need to be periodically uploaded to the server.
|
2020-04-23 08:52:47 +00:00
|
|
|
pub type OneTimeKeys = BTreeMap<AlgorithmAndDeviceId, OneTimeKey>;
|
2020-03-10 12:41:14 +00:00
|
|
|
|
2020-04-30 15:10:12 +00:00
|
|
|
/// State machine implementation of the Olm/Megolm encryption protocol used for
|
|
|
|
/// Matrix end to end encryption.
|
2020-03-11 09:04:04 +00:00
|
|
|
pub struct OlmMachine {
|
2020-02-25 13:24:18 +00:00
|
|
|
/// The unique user id that owns this account.
|
2020-04-28 13:05:20 +00:00
|
|
|
user_id: UserId,
|
2020-02-25 13:24:18 +00:00
|
|
|
/// The unique device id of the device that holds this account.
|
2020-04-28 13:05:20 +00:00
|
|
|
device_id: DeviceId,
|
2020-02-25 13:24:18 +00:00
|
|
|
/// Our underlying Olm Account holding our identity keys.
|
2020-04-28 13:05:20 +00:00
|
|
|
account: Account,
|
2020-03-18 14:50:32 +00:00
|
|
|
/// Store for the encryption keys.
|
2020-04-30 15:10:12 +00:00
|
|
|
/// Persists all the encryption keys so a client can resume the session
|
2020-03-18 14:50:32 +00:00
|
|
|
/// without the need to create new keys.
|
2020-03-30 15:07:36 +00:00
|
|
|
store: Box<dyn CryptoStore>,
|
2020-04-08 13:06:57 +00:00
|
|
|
/// The currently active outbound group sessions.
|
2020-04-28 08:47:08 +00:00
|
|
|
outbound_group_sessions: HashMap<RoomId, OutboundGroupSession>,
|
2020-02-25 13:24:18 +00:00
|
|
|
}
|
|
|
|
|
2020-06-24 09:25:31 +00:00
|
|
|
// #[cfg_attr(tarpaulin, skip)]
|
2020-04-23 09:37:47 +00:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-25 13:24:18 +00:00
|
|
|
impl OlmMachine {
|
2020-04-09 14:24:40 +00:00
|
|
|
const MAX_TO_DEVICE_MESSAGES: usize = 20;
|
|
|
|
|
2020-04-30 15:10:12 +00:00
|
|
|
/// Create a new memory based OlmMachine.
|
|
|
|
///
|
|
|
|
/// The created machine will keep the encryption keys only in memory and
|
|
|
|
/// once the object is dropped the keys will be lost.
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `user_id` - The unique id of the user that owns this machine.
|
|
|
|
///
|
|
|
|
/// * `device_id` - The unique id of the device that owns this machine.
|
2020-07-10 13:43:32 +00:00
|
|
|
pub fn new(user_id: &UserId, device_id: &DeviceId) -> Self {
|
2020-04-30 11:16:10 +00:00
|
|
|
OlmMachine {
|
2020-03-11 09:04:04 +00:00
|
|
|
user_id: user_id.clone(),
|
2020-02-25 13:24:18 +00:00
|
|
|
device_id: device_id.to_owned(),
|
2020-07-10 13:43:32 +00:00
|
|
|
account: Account::new(user_id, &device_id),
|
2020-03-30 15:07:36 +00:00
|
|
|
store: Box::new(MemoryStore::new()),
|
2020-04-28 08:47:08 +00:00
|
|
|
outbound_group_sessions: HashMap::new(),
|
2020-04-30 11:16:10 +00:00
|
|
|
}
|
2020-03-18 14:50:32 +00:00
|
|
|
}
|
|
|
|
|
2020-04-30 15:10:12 +00:00
|
|
|
/// Create a new OlmMachine with the given `CryptoStore`.
|
|
|
|
///
|
|
|
|
/// The created machine will keep the encryption keys only in memory and
|
|
|
|
/// once the object is dropped the keys will be lost.
|
|
|
|
///
|
|
|
|
/// If the store already contains encryption keys for the given user/device
|
|
|
|
/// pair those will be re-used. Otherwise new ones will be created and
|
|
|
|
/// stored.
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `user_id` - The unique id of the user that owns this machine.
|
|
|
|
///
|
|
|
|
/// * `device_id` - The unique id of the device that owns this machine.
|
|
|
|
///
|
|
|
|
/// * `store` - A `Cryptostore` implementation that will be used to store
|
|
|
|
/// the encryption keys.
|
|
|
|
pub async fn new_with_store(
|
2020-05-25 12:21:04 +00:00
|
|
|
user_id: UserId,
|
|
|
|
device_id: String,
|
|
|
|
mut store: Box<dyn CryptoStore>,
|
2020-04-30 11:16:10 +00:00
|
|
|
) -> StoreError<Self> {
|
2020-03-18 15:05:59 +00:00
|
|
|
let account = match store.load_account().await? {
|
2020-03-19 12:55:04 +00:00
|
|
|
Some(a) => {
|
|
|
|
debug!("Restored account");
|
|
|
|
a
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
debug!("Creating a new account");
|
2020-07-10 13:43:32 +00:00
|
|
|
Account::new(&user_id, &device_id)
|
2020-03-19 12:55:04 +00:00
|
|
|
}
|
2020-03-18 15:05:59 +00:00
|
|
|
};
|
|
|
|
|
2020-03-18 14:50:32 +00:00
|
|
|
Ok(OlmMachine {
|
2020-07-10 13:43:32 +00:00
|
|
|
user_id,
|
|
|
|
device_id,
|
2020-04-10 13:28:43 +00:00
|
|
|
account,
|
2020-05-25 12:21:04 +00:00
|
|
|
store,
|
2020-04-28 08:47:08 +00:00
|
|
|
outbound_group_sessions: HashMap::new(),
|
2020-03-18 13:15:56 +00:00
|
|
|
})
|
2020-02-25 13:24:18 +00:00
|
|
|
}
|
|
|
|
|
2020-04-30 15:10:12 +00:00
|
|
|
#[cfg(feature = "sqlite-cryptostore")]
|
|
|
|
#[instrument(skip(path, passphrase))]
|
|
|
|
/// Create a new machine with the default crypto store.
|
|
|
|
///
|
|
|
|
/// The default store uses a SQLite database to store the encryption keys.
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `user_id` - The unique id of the user that owns this machine.
|
|
|
|
///
|
|
|
|
/// * `device_id` - The unique id of the device that owns this machine.
|
|
|
|
pub async fn new_with_default_store<P: AsRef<Path>>(
|
|
|
|
user_id: &UserId,
|
|
|
|
device_id: &str,
|
|
|
|
path: P,
|
2020-05-25 12:21:04 +00:00
|
|
|
passphrase: &str,
|
2020-04-30 15:10:12 +00:00
|
|
|
) -> StoreError<Self> {
|
|
|
|
let store =
|
|
|
|
SqliteStore::open_with_passphrase(&user_id, device_id, path, passphrase).await?;
|
|
|
|
|
2020-05-25 12:21:04 +00:00
|
|
|
OlmMachine::new_with_store(user_id.to_owned(), device_id.to_owned(), Box::new(store)).await
|
2020-04-30 15:10:12 +00:00
|
|
|
}
|
|
|
|
|
2020-04-28 13:05:20 +00:00
|
|
|
/// The unique user id that owns this identity.
|
2020-04-30 12:07:49 +00:00
|
|
|
pub fn user_id(&self) -> &UserId {
|
2020-04-28 13:05:20 +00:00
|
|
|
&self.user_id
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The unique device id of the device that holds this identity.
|
2020-04-30 12:07:49 +00:00
|
|
|
pub fn device_id(&self) -> &DeviceId {
|
2020-04-28 13:05:20 +00:00
|
|
|
&self.device_id
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the public parts of the identity keys.
|
2020-04-30 12:07:49 +00:00
|
|
|
pub fn identity_keys(&self) -> &IdentityKeys {
|
2020-04-28 13:05:20 +00:00
|
|
|
self.account.identity_keys()
|
|
|
|
}
|
|
|
|
|
2020-02-25 13:24:18 +00:00
|
|
|
/// Should account or one-time keys be uploaded to the server.
|
2020-03-18 14:50:32 +00:00
|
|
|
pub async fn should_upload_keys(&self) -> bool {
|
2020-07-13 14:46:51 +00:00
|
|
|
self.account.should_upload_keys().await
|
2020-02-25 13:24:18 +00:00
|
|
|
}
|
|
|
|
|
2020-04-30 15:10:12 +00:00
|
|
|
/// Update the count of one-time keys that are currently on the server.
|
2020-04-28 13:47:49 +00:00
|
|
|
fn update_key_count(&mut self, count: u64) {
|
2020-07-13 13:49:16 +00:00
|
|
|
self.account.update_uploaded_key_count(count);
|
2020-04-28 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
2020-02-25 16:36:11 +00:00
|
|
|
/// Receive a successful keys upload response.
|
2020-02-25 13:24:18 +00:00
|
|
|
///
|
2020-02-25 16:36:11 +00:00
|
|
|
/// # Arguments
|
2020-02-25 13:24:18 +00:00
|
|
|
///
|
2020-03-02 10:31:03 +00:00
|
|
|
/// * `response` - The keys upload response of the request that the client
|
2020-03-11 09:04:04 +00:00
|
|
|
/// performed.
|
2020-03-19 12:55:04 +00:00
|
|
|
#[instrument]
|
2020-03-18 14:50:32 +00:00
|
|
|
pub async fn receive_keys_upload_response(
|
|
|
|
&mut self,
|
|
|
|
response: &keys::upload_keys::Response,
|
2020-04-30 11:16:10 +00:00
|
|
|
) -> OlmResult<()> {
|
2020-04-10 13:28:43 +00:00
|
|
|
if !self.account.shared() {
|
2020-03-19 12:55:04 +00:00
|
|
|
debug!("Marking account as shared");
|
|
|
|
}
|
2020-04-10 13:28:43 +00:00
|
|
|
self.account.mark_as_shared();
|
2020-03-11 09:04:04 +00:00
|
|
|
|
2020-02-25 13:24:18 +00:00
|
|
|
let one_time_key_count = response
|
|
|
|
.one_time_key_counts
|
|
|
|
.get(&keys::KeyAlgorithm::SignedCurve25519);
|
|
|
|
|
2020-03-11 09:09:00 +00:00
|
|
|
let count: u64 = one_time_key_count.map_or(0, |c| (*c).into());
|
2020-03-19 12:55:04 +00:00
|
|
|
debug!(
|
|
|
|
"Updated uploaded one-time key count {} -> {}, marking keys as published",
|
2020-07-13 13:49:16 +00:00
|
|
|
self.account.uploaded_key_count(),
|
2020-03-19 12:55:04 +00:00
|
|
|
count
|
|
|
|
);
|
2020-04-28 13:47:49 +00:00
|
|
|
self.update_key_count(count);
|
2020-02-25 13:24:18 +00:00
|
|
|
|
2020-04-10 13:28:43 +00:00
|
|
|
self.account.mark_keys_as_published().await;
|
2020-04-10 14:17:31 +00:00
|
|
|
self.store.save_account(self.account.clone()).await?;
|
2020-03-26 11:24:53 +00:00
|
|
|
|
2020-03-18 14:50:32 +00:00
|
|
|
Ok(())
|
2020-02-25 13:24:18 +00:00
|
|
|
}
|
|
|
|
|
2020-04-30 15:10:12 +00:00
|
|
|
/// Get the user/device pairs for which no Olm session exists.
|
|
|
|
///
|
|
|
|
/// Returns a map from the user id, to a map from the device id to a key
|
|
|
|
/// algorithm.
|
|
|
|
///
|
|
|
|
/// This can be used to make a key claiming request to the server.
|
|
|
|
///
|
|
|
|
/// Sessions need to be established between devices so group sessions for a
|
|
|
|
/// room can be shared with them.
|
|
|
|
///
|
|
|
|
/// This should be called every time a group session needs to be shared.
|
|
|
|
///
|
|
|
|
/// The response of a successful key claiming requests needs to be passed to
|
|
|
|
/// the `OlmMachine` with the `receive_keys_claim_response()`.
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// `users` - The list of users that we should check if we lack a session
|
|
|
|
/// with one of their devices.
|
2020-04-03 08:20:03 +00:00
|
|
|
pub async fn get_missing_sessions(
|
|
|
|
&mut self,
|
2020-04-03 15:00:37 +00:00
|
|
|
users: impl Iterator<Item = &UserId>,
|
2020-04-30 11:16:10 +00:00
|
|
|
) -> OlmResult<BTreeMap<UserId, BTreeMap<DeviceId, KeyAlgorithm>>> {
|
2020-04-23 08:52:47 +00:00
|
|
|
let mut missing = BTreeMap::new();
|
2020-04-03 08:20:03 +00:00
|
|
|
|
|
|
|
for user_id in users {
|
2020-04-27 14:26:03 +00:00
|
|
|
let user_devices = self.store.get_user_devices(user_id).await?;
|
2020-04-03 08:20:03 +00:00
|
|
|
|
|
|
|
for device in user_devices.devices() {
|
2020-04-30 11:16:10 +00:00
|
|
|
let sender_key = if let Some(k) = device.get_key(KeyAlgorithm::Curve25519) {
|
2020-04-03 08:20:03 +00:00
|
|
|
k
|
|
|
|
} else {
|
|
|
|
continue;
|
|
|
|
};
|
|
|
|
|
2020-04-27 14:26:03 +00:00
|
|
|
let sessions = self.store.get_sessions(sender_key).await?;
|
2020-04-03 08:20:03 +00:00
|
|
|
|
|
|
|
let is_missing = if let Some(sessions) = sessions {
|
|
|
|
sessions.lock().await.is_empty()
|
|
|
|
} else {
|
|
|
|
true
|
|
|
|
};
|
|
|
|
|
|
|
|
if is_missing {
|
2020-04-03 15:00:37 +00:00
|
|
|
if !missing.contains_key(user_id) {
|
2020-04-30 15:10:12 +00:00
|
|
|
let _ = missing.insert(user_id.clone(), BTreeMap::new());
|
2020-04-03 08:20:03 +00:00
|
|
|
}
|
|
|
|
|
2020-04-03 15:00:37 +00:00
|
|
|
let user_map = missing.get_mut(user_id).unwrap();
|
2020-04-30 15:10:12 +00:00
|
|
|
let _ = user_map.insert(
|
2020-04-03 08:20:03 +00:00
|
|
|
device.device_id().to_owned(),
|
|
|
|
KeyAlgorithm::SignedCurve25519,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-27 14:26:03 +00:00
|
|
|
Ok(missing)
|
2020-04-03 08:20:03 +00:00
|
|
|
}
|
|
|
|
|
2020-04-30 15:10:12 +00:00
|
|
|
/// Receive a successful key claim response and create new Olm sessions with
|
|
|
|
/// the claimed keys.
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `response` - The response containing the claimed one-time keys.
|
2020-04-02 14:07:21 +00:00
|
|
|
pub async fn receive_keys_claim_response(
|
|
|
|
&mut self,
|
|
|
|
response: &keys::claim_keys::Response,
|
2020-04-30 11:16:10 +00:00
|
|
|
) -> OlmResult<()> {
|
2020-04-02 14:07:21 +00:00
|
|
|
// TODO log the failures here
|
|
|
|
|
|
|
|
for (user_id, user_devices) in &response.one_time_keys {
|
|
|
|
for (device_id, key_map) in user_devices {
|
|
|
|
let device = if let Some(d) = self
|
|
|
|
.store
|
2020-04-03 15:00:37 +00:00
|
|
|
.get_device(&user_id, device_id)
|
2020-04-02 14:07:21 +00:00
|
|
|
.await
|
|
|
|
.expect("Can't get devices")
|
|
|
|
{
|
|
|
|
d
|
|
|
|
} else {
|
|
|
|
warn!(
|
|
|
|
"Tried to create an Olm session for {} {}, but the device is unknown",
|
|
|
|
user_id, device_id
|
|
|
|
);
|
|
|
|
continue;
|
|
|
|
};
|
|
|
|
|
2020-04-30 12:07:49 +00:00
|
|
|
let one_time_key = if let Some(k) = key_map.values().next() {
|
2020-04-02 14:07:21 +00:00
|
|
|
match k {
|
|
|
|
OneTimeKey::SignedKey(k) => k,
|
|
|
|
OneTimeKey::Key(_) => {
|
|
|
|
warn!(
|
|
|
|
"Tried to create an Olm session for {} {}, but
|
|
|
|
the requested key isn't a signed curve key",
|
|
|
|
user_id, device_id
|
|
|
|
);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
warn!(
|
|
|
|
"Tried to create an Olm session for {} {}, but the
|
|
|
|
signed one-time key is missing",
|
|
|
|
user_id, device_id
|
|
|
|
);
|
|
|
|
continue;
|
|
|
|
};
|
|
|
|
|
2020-04-30 11:16:10 +00:00
|
|
|
let signing_key = if let Some(k) = device.get_key(KeyAlgorithm::Ed25519) {
|
2020-04-02 14:07:21 +00:00
|
|
|
k
|
|
|
|
} else {
|
|
|
|
warn!(
|
|
|
|
"Tried to create an Olm session for {} {}, but the
|
|
|
|
device is missing the signing key",
|
|
|
|
user_id, device_id
|
|
|
|
);
|
|
|
|
continue;
|
|
|
|
};
|
|
|
|
|
|
|
|
if self
|
|
|
|
.verify_json(user_id, device_id, signing_key, &mut json!(&one_time_key))
|
|
|
|
.is_err()
|
|
|
|
{
|
|
|
|
warn!(
|
|
|
|
"Failed to verify the one-time key signatures for {} {}",
|
|
|
|
user_id, device_id
|
|
|
|
);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-04-30 11:16:10 +00:00
|
|
|
let curve_key = if let Some(k) = device.get_key(KeyAlgorithm::Curve25519) {
|
2020-04-02 14:07:21 +00:00
|
|
|
k
|
|
|
|
} else {
|
|
|
|
warn!(
|
|
|
|
"Tried to create an Olm session for {} {}, but the
|
|
|
|
device is missing the curve key",
|
|
|
|
user_id, device_id
|
|
|
|
);
|
|
|
|
continue;
|
|
|
|
};
|
|
|
|
|
|
|
|
info!("Creating outbound Session for {} {}", user_id, device_id);
|
|
|
|
|
|
|
|
let session = match self
|
|
|
|
.account
|
2020-04-03 08:16:20 +00:00
|
|
|
.create_outbound_session(curve_key, &one_time_key)
|
2020-04-10 13:28:43 +00:00
|
|
|
.await
|
2020-04-02 14:07:21 +00:00
|
|
|
{
|
|
|
|
Ok(s) => s,
|
|
|
|
Err(e) => {
|
|
|
|
warn!(
|
|
|
|
"Error creating new Olm session for {} {}: {}",
|
|
|
|
user_id, device_id, e
|
|
|
|
);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-04-30 10:08:38 +00:00
|
|
|
if let Err(e) = self.store.save_sessions(&[session]).await {
|
2020-04-02 14:07:21 +00:00
|
|
|
error!("Failed to store newly created Olm session {}", e);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO if this session was created because a previous one was
|
|
|
|
// wedged queue up a dummy event to be sent out.
|
|
|
|
// TODO if this session was created because of a key request,
|
|
|
|
// mark the forwarding keys to be sent out
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-04-01 13:37:00 +00:00
|
|
|
/// Receive a successful keys query response.
|
|
|
|
///
|
2020-04-21 07:44:44 +00:00
|
|
|
/// Returns a list of devices newly discovered devices and devices that
|
|
|
|
/// changed.
|
|
|
|
///
|
2020-04-01 13:37:00 +00:00
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `response` - The keys query response of the request that the client
|
|
|
|
/// performed.
|
|
|
|
pub async fn receive_keys_query_response(
|
|
|
|
&mut self,
|
|
|
|
response: &keys::get_keys::Response,
|
2020-04-30 11:16:10 +00:00
|
|
|
) -> OlmResult<Vec<Device>> {
|
2020-04-03 08:21:14 +00:00
|
|
|
let mut changed_devices = Vec::new();
|
|
|
|
|
2020-04-02 09:14:23 +00:00
|
|
|
for (user_id, device_map) in &response.device_keys {
|
2020-05-15 13:33:30 +00:00
|
|
|
self.store.update_tracked_user(user_id, false).await?;
|
2020-04-02 09:14:23 +00:00
|
|
|
|
|
|
|
for (device_id, device_keys) in device_map.iter() {
|
|
|
|
// We don't need our own device in the device store.
|
|
|
|
if user_id == &self.user_id && device_id == &self.device_id {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if user_id != &device_keys.user_id || device_id != &device_keys.device_id {
|
|
|
|
warn!(
|
|
|
|
"Mismatch in device keys payload of device {} from user {}",
|
|
|
|
device_keys.device_id, device_keys.user_id
|
|
|
|
);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-04-21 07:44:44 +00:00
|
|
|
let device = self.store.get_device(&user_id, device_id).await?;
|
2020-04-02 09:14:23 +00:00
|
|
|
|
2020-07-14 09:17:09 +00:00
|
|
|
let device = if let Some(mut device) = device {
|
|
|
|
if let Err(e) = device.update_device(device_keys) {
|
|
|
|
warn!(
|
|
|
|
"Failed to update the device keys for {} {}: {:?}",
|
|
|
|
user_id, device_id, e
|
|
|
|
);
|
|
|
|
continue;
|
2020-04-21 07:44:44 +00:00
|
|
|
}
|
2020-07-14 09:17:09 +00:00
|
|
|
device
|
2020-04-02 09:14:23 +00:00
|
|
|
} else {
|
2020-07-14 09:17:09 +00:00
|
|
|
let device = match Device::try_from(device_keys) {
|
|
|
|
Ok(d) => d,
|
|
|
|
Err(e) => {
|
|
|
|
warn!(
|
|
|
|
"Failed to create a new device for {} {}: {:?}",
|
|
|
|
user_id, device_id, e
|
|
|
|
);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
};
|
2020-04-21 07:44:44 +00:00
|
|
|
info!("Adding a new device to the device store {:?}", device);
|
|
|
|
device
|
|
|
|
};
|
|
|
|
|
|
|
|
changed_devices.push(device);
|
2020-04-02 09:14:23 +00:00
|
|
|
}
|
2020-04-02 12:52:15 +00:00
|
|
|
|
2020-04-09 14:22:25 +00:00
|
|
|
let current_devices: HashSet<&DeviceId> = device_map.keys().collect();
|
2020-04-03 15:00:37 +00:00
|
|
|
let stored_devices = self.store.get_user_devices(&user_id).await.unwrap();
|
2020-04-09 14:22:25 +00:00
|
|
|
let stored_devices_set: HashSet<&DeviceId> = stored_devices.keys().collect();
|
2020-04-02 12:52:15 +00:00
|
|
|
|
|
|
|
let deleted_devices = stored_devices_set.difference(¤t_devices);
|
|
|
|
|
2020-04-21 07:44:44 +00:00
|
|
|
for device_id in deleted_devices {
|
|
|
|
if let Some(device) = stored_devices.get(device_id) {
|
|
|
|
device.mark_as_deleted();
|
2020-04-22 10:12:47 +00:00
|
|
|
self.store.delete_device(device).await?;
|
2020-04-21 07:44:44 +00:00
|
|
|
}
|
2020-04-02 12:52:15 +00:00
|
|
|
}
|
2020-04-02 09:14:23 +00:00
|
|
|
}
|
2020-04-03 08:21:14 +00:00
|
|
|
|
2020-04-30 09:51:20 +00:00
|
|
|
self.store.save_devices(&changed_devices).await?;
|
2020-04-03 08:21:14 +00:00
|
|
|
|
2020-04-21 07:44:44 +00:00
|
|
|
Ok(changed_devices)
|
2020-04-01 13:37:00 +00:00
|
|
|
}
|
|
|
|
|
2020-02-25 16:36:11 +00:00
|
|
|
/// Verify a signed JSON object.
|
|
|
|
///
|
|
|
|
/// The object must have a signatures key associated with an object of the
|
|
|
|
/// form `user_id: {key_id: signature}`.
|
|
|
|
///
|
2020-03-02 10:31:03 +00:00
|
|
|
/// Returns Ok if the signature was successfully verified, otherwise an
|
|
|
|
/// SignatureError.
|
|
|
|
///
|
2020-02-25 16:36:11 +00:00
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `user_id` - The user who signed the JSON object.
|
2020-03-02 10:31:03 +00:00
|
|
|
///
|
2020-02-25 16:36:11 +00:00
|
|
|
/// * `device_id` - The device that signed the JSON object.
|
2020-03-02 10:31:03 +00:00
|
|
|
///
|
2020-02-25 16:36:11 +00:00
|
|
|
/// * `user_key` - The public ed25519 key which was used to sign the JSON
|
|
|
|
/// object.
|
|
|
|
///
|
2020-03-02 10:31:03 +00:00
|
|
|
/// * `json` - The JSON object that should be verified.
|
2020-02-25 16:36:11 +00:00
|
|
|
fn verify_json(
|
|
|
|
&self,
|
2020-03-10 12:02:14 +00:00
|
|
|
user_id: &UserId,
|
2020-02-25 16:36:11 +00:00
|
|
|
device_id: &str,
|
|
|
|
user_key: &str,
|
|
|
|
json: &mut Value,
|
2020-04-30 15:10:12 +00:00
|
|
|
) -> Result<(), SignatureError> {
|
2020-02-25 16:36:11 +00:00
|
|
|
let json_object = json.as_object_mut().ok_or(SignatureError::NotAnObject)?;
|
|
|
|
let unsigned = json_object.remove("unsigned");
|
|
|
|
let signatures = json_object.remove("signatures");
|
|
|
|
|
|
|
|
let canonical_json = cjson::to_string(json_object)?;
|
|
|
|
|
|
|
|
if let Some(u) = unsigned {
|
|
|
|
json_object.insert("unsigned".to_string(), u);
|
|
|
|
}
|
|
|
|
|
2020-03-10 12:02:14 +00:00
|
|
|
// TODO this should be part of ruma-client-api.
|
|
|
|
let key_id_string = format!("{}:{}", KeyAlgorithm::Ed25519, device_id);
|
2020-02-25 16:36:11 +00:00
|
|
|
|
|
|
|
let signatures = signatures.ok_or(SignatureError::NoSignatureFound)?;
|
|
|
|
let signature_object = signatures
|
|
|
|
.as_object()
|
|
|
|
.ok_or(SignatureError::NoSignatureFound)?;
|
|
|
|
let signature = signature_object
|
2020-03-10 12:02:14 +00:00
|
|
|
.get(&user_id.to_string())
|
2020-02-25 16:36:11 +00:00
|
|
|
.ok_or(SignatureError::NoSignatureFound)?;
|
|
|
|
let signature = signature
|
2020-03-10 12:02:14 +00:00
|
|
|
.get(key_id_string)
|
2020-02-25 16:36:11 +00:00
|
|
|
.ok_or(SignatureError::NoSignatureFound)?;
|
|
|
|
let signature = signature.as_str().ok_or(SignatureError::NoSignatureFound)?;
|
|
|
|
|
|
|
|
let utility = OlmUtility::new();
|
|
|
|
|
|
|
|
let ret = if utility
|
|
|
|
.ed25519_verify(&user_key, &canonical_json, signature)
|
|
|
|
.is_ok()
|
|
|
|
{
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(SignatureError::VerificationError)
|
|
|
|
};
|
|
|
|
|
|
|
|
json_object.insert("signatures".to_string(), signatures);
|
|
|
|
|
|
|
|
ret
|
|
|
|
}
|
2020-03-10 13:06:30 +00:00
|
|
|
|
|
|
|
/// Get a tuple of device and one-time keys that need to be uploaded.
|
|
|
|
///
|
|
|
|
/// Returns an empty error if no keys need to be uploaded.
|
2020-03-18 14:50:32 +00:00
|
|
|
pub async fn keys_for_upload(
|
|
|
|
&self,
|
|
|
|
) -> StdResult<(Option<DeviceKeys>, Option<OneTimeKeys>), ()> {
|
2020-07-13 14:46:51 +00:00
|
|
|
self.account.keys_for_upload().await
|
2020-03-10 13:06:30 +00:00
|
|
|
}
|
2020-03-12 14:41:11 +00:00
|
|
|
|
2020-04-30 15:10:12 +00:00
|
|
|
/// Try to decrypt an Olm message.
|
|
|
|
///
|
|
|
|
/// This try to decrypt an Olm message using all the sessions we share
|
|
|
|
/// have with the given sender.
|
|
|
|
async fn try_decrypt_olm_message(
|
2020-03-21 15:41:48 +00:00
|
|
|
&mut self,
|
2020-04-22 13:15:08 +00:00
|
|
|
sender: &UserId,
|
2020-03-21 15:41:48 +00:00
|
|
|
sender_key: &str,
|
|
|
|
message: &OlmMessage,
|
2020-04-30 11:16:10 +00:00
|
|
|
) -> OlmResult<Option<String>> {
|
2020-03-30 15:07:36 +00:00
|
|
|
let s = self.store.get_sessions(sender_key).await?;
|
2020-03-21 15:41:48 +00:00
|
|
|
|
2020-04-22 13:15:08 +00:00
|
|
|
// We don't have any existing sessions, return early.
|
2020-03-21 15:41:48 +00:00
|
|
|
let sessions = if let Some(s) = s {
|
|
|
|
s
|
|
|
|
} else {
|
|
|
|
return Ok(None);
|
|
|
|
};
|
|
|
|
|
2020-04-23 09:37:47 +00:00
|
|
|
let mut session_to_save = None;
|
|
|
|
let mut plaintext = None;
|
|
|
|
|
2020-04-14 12:05:18 +00:00
|
|
|
for session in &mut *sessions.lock().await {
|
2020-03-21 15:41:48 +00:00
|
|
|
let mut matches = false;
|
|
|
|
|
2020-04-22 10:54:49 +00:00
|
|
|
// If this is a pre-key message check if it was encrypted for our
|
|
|
|
// session, if it wasn't decryption will fail so no need to try.
|
2020-03-21 15:41:48 +00:00
|
|
|
if let OlmMessage::PreKey(m) = &message {
|
2020-04-14 12:05:18 +00:00
|
|
|
matches = session.matches(sender_key, m.clone()).await?;
|
2020-04-22 10:54:49 +00:00
|
|
|
|
2020-03-21 15:41:48 +00:00
|
|
|
if !matches {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-14 12:05:18 +00:00
|
|
|
let ret = session.decrypt(message.clone()).await;
|
2020-03-21 15:41:48 +00:00
|
|
|
|
|
|
|
if let Ok(p) = ret {
|
2020-04-23 09:37:47 +00:00
|
|
|
plaintext = Some(p);
|
|
|
|
session_to_save = Some(session.clone());
|
2020-04-22 10:54:49 +00:00
|
|
|
|
2020-04-23 09:37:47 +00:00
|
|
|
break;
|
2020-03-21 15:41:48 +00:00
|
|
|
} else {
|
2020-04-22 10:54:49 +00:00
|
|
|
// Decryption failed with a matching session, the session is
|
|
|
|
// likely wedged and needs to be rotated.
|
2020-03-21 15:41:48 +00:00
|
|
|
if matches {
|
2020-04-22 10:54:49 +00:00
|
|
|
warn!(
|
|
|
|
"Found a matching Olm session yet decryption failed
|
|
|
|
for sender {} and sender_key {}",
|
|
|
|
sender, sender_key
|
|
|
|
);
|
2020-03-21 15:41:48 +00:00
|
|
|
return Err(OlmError::SessionWedged);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-23 09:37:47 +00:00
|
|
|
if let Some(session) = session_to_save {
|
|
|
|
// Decryption was successful, save the new ratchet state of the
|
|
|
|
// session that was used to decrypt the message.
|
|
|
|
trace!("Saved the new session state for {}", sender);
|
2020-04-30 10:08:38 +00:00
|
|
|
self.store.save_sessions(&[session]).await?;
|
2020-04-23 09:37:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(plaintext)
|
2020-03-21 15:41:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn decrypt_olm_message(
|
|
|
|
&mut self,
|
2020-04-22 13:15:08 +00:00
|
|
|
sender: &UserId,
|
2020-03-21 15:41:48 +00:00
|
|
|
sender_key: &str,
|
|
|
|
message: OlmMessage,
|
2020-06-20 21:18:20 +00:00
|
|
|
) -> OlmResult<(EventJson<AnyToDeviceEvent>, String)> {
|
2020-04-22 10:54:49 +00:00
|
|
|
// First try to decrypt using an existing session.
|
|
|
|
let plaintext = if let Some(p) = self
|
2020-04-30 15:10:12 +00:00
|
|
|
.try_decrypt_olm_message(sender, sender_key, &message)
|
2020-04-22 10:54:49 +00:00
|
|
|
.await?
|
|
|
|
{
|
2020-04-30 15:10:12 +00:00
|
|
|
// Decryption succeeded, de-structure the plaintext out of the
|
2020-04-22 10:54:49 +00:00
|
|
|
// Option.
|
2020-03-21 15:41:48 +00:00
|
|
|
p
|
|
|
|
} else {
|
2020-04-22 10:54:49 +00:00
|
|
|
// Decryption failed with every known session, let's try to create a
|
|
|
|
// new session.
|
2020-03-21 15:41:48 +00:00
|
|
|
let mut session = match &message {
|
2020-04-22 13:15:08 +00:00
|
|
|
// A new session can only be created using a pre-key message,
|
|
|
|
// return with an error if it isn't one.
|
2020-04-22 10:54:49 +00:00
|
|
|
OlmMessage::Message(_) => {
|
|
|
|
warn!(
|
|
|
|
"Failed to decrypt a non-pre-key message with all
|
|
|
|
available sessions {} {}",
|
|
|
|
sender, sender_key
|
|
|
|
);
|
|
|
|
return Err(OlmError::SessionWedged);
|
|
|
|
}
|
|
|
|
|
2020-03-21 15:41:48 +00:00
|
|
|
OlmMessage::PreKey(m) => {
|
2020-04-22 13:15:08 +00:00
|
|
|
// Create the new session.
|
2020-04-22 10:54:49 +00:00
|
|
|
let session = match self
|
2020-04-10 14:18:55 +00:00
|
|
|
.account
|
2020-04-10 13:28:43 +00:00
|
|
|
.create_inbound_session(sender_key, m.clone())
|
2020-04-22 10:54:49 +00:00
|
|
|
.await
|
|
|
|
{
|
|
|
|
Ok(s) => s,
|
|
|
|
Err(e) => {
|
|
|
|
warn!(
|
|
|
|
"Failed to create a new Olm session for {} {}
|
|
|
|
from a prekey message: {}",
|
|
|
|
sender, sender_key, e
|
|
|
|
);
|
|
|
|
return Err(OlmError::SessionWedged);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-04-22 13:15:08 +00:00
|
|
|
// Save the account since we remove the one-time key that
|
|
|
|
// was used to create this session.
|
2020-04-10 14:18:55 +00:00
|
|
|
self.store.save_account(self.account.clone()).await?;
|
|
|
|
session
|
2020-03-21 15:41:48 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-04-22 13:15:08 +00:00
|
|
|
// Decrypt our message, this shouldn't fail since we're using a
|
|
|
|
// newly created Session.
|
2020-04-14 12:05:18 +00:00
|
|
|
let plaintext = session.decrypt(message).await?;
|
2020-04-22 10:54:49 +00:00
|
|
|
|
2020-04-22 13:15:08 +00:00
|
|
|
// Save the new ratcheted state of the session.
|
2020-04-30 10:08:38 +00:00
|
|
|
self.store.save_sessions(&[session]).await?;
|
2020-03-26 10:22:40 +00:00
|
|
|
plaintext
|
2020-03-21 15:41:48 +00:00
|
|
|
};
|
|
|
|
|
2020-03-24 15:21:06 +00:00
|
|
|
trace!("Successfully decrypted a Olm message: {}", plaintext);
|
2020-04-22 10:54:49 +00:00
|
|
|
|
2020-04-22 13:15:08 +00:00
|
|
|
Ok(self.parse_decrypted_to_device_event(sender, &plaintext)?)
|
|
|
|
}
|
|
|
|
|
2020-04-30 15:10:12 +00:00
|
|
|
/// Parse a decrypted Olm message, check that the plaintext and encrypted
|
|
|
|
/// senders match and that the message was meant for us.
|
2020-04-22 13:15:08 +00:00
|
|
|
fn parse_decrypted_to_device_event(
|
|
|
|
&self,
|
|
|
|
sender: &UserId,
|
|
|
|
plaintext: &str,
|
2020-06-20 21:18:20 +00:00
|
|
|
) -> OlmResult<(EventJson<AnyToDeviceEvent>, String)> {
|
2020-04-22 13:15:08 +00:00
|
|
|
// TODO make the errors a bit more specific.
|
|
|
|
let decrypted_json: Value = serde_json::from_str(&plaintext)?;
|
|
|
|
|
|
|
|
let encrytped_sender = decrypted_json
|
|
|
|
.get("sender")
|
|
|
|
.cloned()
|
2020-04-30 12:07:49 +00:00
|
|
|
.ok_or_else(|| EventError::MissingField("sender".to_string()))?;
|
2020-04-22 13:15:08 +00:00
|
|
|
let encrytped_sender: UserId = serde_json::from_value(encrytped_sender)?;
|
|
|
|
let recipient = decrypted_json
|
|
|
|
.get("recipient")
|
|
|
|
.cloned()
|
2020-04-30 12:07:49 +00:00
|
|
|
.ok_or_else(|| EventError::MissingField("recipient".to_string()))?;
|
2020-04-22 13:15:08 +00:00
|
|
|
let recipient: UserId = serde_json::from_value(recipient)?;
|
|
|
|
|
2020-04-23 08:52:47 +00:00
|
|
|
let recipient_keys: BTreeMap<KeyAlgorithm, String> = serde_json::from_value(
|
2020-04-22 13:15:08 +00:00
|
|
|
decrypted_json
|
|
|
|
.get("recipient_keys")
|
|
|
|
.cloned()
|
2020-04-30 12:07:49 +00:00
|
|
|
.ok_or_else(|| EventError::MissingField("recipient_keys".to_string()))?,
|
2020-04-22 13:15:08 +00:00
|
|
|
)?;
|
2020-04-23 08:52:47 +00:00
|
|
|
let keys: BTreeMap<KeyAlgorithm, String> = serde_json::from_value(
|
2020-04-22 13:15:08 +00:00
|
|
|
decrypted_json
|
|
|
|
.get("keys")
|
|
|
|
.cloned()
|
2020-04-30 12:07:49 +00:00
|
|
|
.ok_or_else(|| EventError::MissingField("keys".to_string()))?,
|
2020-04-22 13:15:08 +00:00
|
|
|
)?;
|
|
|
|
|
|
|
|
if recipient != self.user_id || sender != &encrytped_sender {
|
2020-04-30 12:07:49 +00:00
|
|
|
return Err(EventError::MissmatchedSender.into());
|
2020-04-22 13:15:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if self.account.identity_keys().ed25519()
|
|
|
|
!= recipient_keys
|
|
|
|
.get(&KeyAlgorithm::Ed25519)
|
2020-04-30 11:16:10 +00:00
|
|
|
.ok_or(EventError::MissingSigningKey)?
|
2020-04-22 13:15:08 +00:00
|
|
|
{
|
2020-04-30 12:07:49 +00:00
|
|
|
return Err(EventError::MissmatchedKeys.into());
|
2020-04-22 13:15:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let signing_key = keys
|
|
|
|
.get(&KeyAlgorithm::Ed25519)
|
2020-04-30 11:16:10 +00:00
|
|
|
.ok_or(EventError::MissingSigningKey)?;
|
2020-04-22 13:15:08 +00:00
|
|
|
|
|
|
|
Ok((
|
2020-06-20 21:18:20 +00:00
|
|
|
EventJson::from(serde_json::from_value::<AnyToDeviceEvent>(decrypted_json)?),
|
2020-04-22 13:15:08 +00:00
|
|
|
signing_key.to_owned(),
|
|
|
|
))
|
2020-03-21 15:41:48 +00:00
|
|
|
}
|
|
|
|
|
2020-03-12 14:41:11 +00:00
|
|
|
/// Decrypt a to-device event.
|
|
|
|
///
|
|
|
|
/// Returns a decrypted `ToDeviceEvent` if the decryption was successful,
|
|
|
|
/// an error indicating why decryption failed otherwise.
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `event` - The to-device event that should be decrypted.
|
2020-03-21 15:41:48 +00:00
|
|
|
async fn decrypt_to_device_event(
|
2020-03-23 15:14:10 +00:00
|
|
|
&mut self,
|
2020-06-20 21:18:20 +00:00
|
|
|
event: &ToDeviceEvent<EncryptedEventContent>,
|
|
|
|
) -> OlmResult<EventJson<AnyToDeviceEvent>> {
|
2020-03-19 12:55:04 +00:00
|
|
|
info!("Decrypting to-device event");
|
2020-03-21 15:41:48 +00:00
|
|
|
|
|
|
|
let content = if let EncryptedEventContent::OlmV1Curve25519AesSha2(c) = &event.content {
|
|
|
|
c
|
|
|
|
} else {
|
|
|
|
warn!("Error, unsupported encryption algorithm");
|
2020-04-30 12:07:49 +00:00
|
|
|
return Err(EventError::UnsupportedAlgorithm.into());
|
2020-03-21 15:41:48 +00:00
|
|
|
};
|
|
|
|
|
2020-04-10 13:28:43 +00:00
|
|
|
let identity_keys = self.account.identity_keys();
|
2020-03-21 15:41:48 +00:00
|
|
|
let own_key = identity_keys.curve25519();
|
|
|
|
let own_ciphertext = content.ciphertext.get(own_key);
|
|
|
|
|
2020-04-22 10:54:49 +00:00
|
|
|
// Try to find a ciphertext that was meant for our device.
|
2020-03-21 15:41:48 +00:00
|
|
|
if let Some(ciphertext) = own_ciphertext {
|
2020-03-23 15:14:10 +00:00
|
|
|
let message_type: u8 = ciphertext
|
|
|
|
.message_type
|
|
|
|
.try_into()
|
2020-04-30 11:16:10 +00:00
|
|
|
.map_err(|_| EventError::UnsupportedOlmType)?;
|
2020-04-22 10:54:49 +00:00
|
|
|
|
|
|
|
// Create a OlmMessage from the ciphertext and the type.
|
2020-03-21 15:41:48 +00:00
|
|
|
let message =
|
|
|
|
OlmMessage::from_type_and_ciphertext(message_type.into(), ciphertext.body.clone())
|
2020-04-30 11:16:10 +00:00
|
|
|
.map_err(|_| EventError::UnsupportedOlmType)?;
|
2020-03-21 15:41:48 +00:00
|
|
|
|
2020-04-22 10:54:49 +00:00
|
|
|
// Decrypt the OlmMessage and get a Ruma event out of it.
|
2020-05-06 10:15:15 +00:00
|
|
|
let (decrypted_event, signing_key) = self
|
2020-04-22 13:15:08 +00:00
|
|
|
.decrypt_olm_message(&event.sender, &content.sender_key, message)
|
2020-03-24 16:25:01 +00:00
|
|
|
.await?;
|
2020-04-22 10:54:49 +00:00
|
|
|
|
2020-03-24 16:25:01 +00:00
|
|
|
debug!("Decrypted a to-device event {:?}", decrypted_event);
|
2020-04-22 10:54:49 +00:00
|
|
|
|
2020-04-30 15:10:12 +00:00
|
|
|
// Handle the decrypted event, e.g. fetch out Megolm sessions out of
|
2020-04-22 10:54:49 +00:00
|
|
|
// the event.
|
2020-05-06 10:15:15 +00:00
|
|
|
if let Some(event) = self
|
|
|
|
.handle_decrypted_to_device_event(
|
|
|
|
&content.sender_key,
|
|
|
|
&signing_key,
|
|
|
|
&decrypted_event,
|
|
|
|
)
|
|
|
|
.await?
|
|
|
|
{
|
|
|
|
// Some events may have sensitive data e.g. private keys, while we
|
2020-07-03 19:29:10 +00:00
|
|
|
// want to notify our users that a private key was received we
|
2020-05-06 10:15:15 +00:00
|
|
|
// don't want them to be able to do silly things with it. Handling
|
|
|
|
// events modifies them and returns a modified one, so replace it
|
|
|
|
// here if we get one.
|
|
|
|
Ok(event)
|
|
|
|
} else {
|
|
|
|
Ok(decrypted_event)
|
|
|
|
}
|
2020-03-23 15:14:10 +00:00
|
|
|
} else {
|
|
|
|
warn!("Olm event doesn't contain a ciphertext for our key");
|
2020-04-30 12:07:49 +00:00
|
|
|
Err(EventError::MissingCiphertext.into())
|
2020-03-23 15:14:10 +00:00
|
|
|
}
|
2020-03-12 14:41:11 +00:00
|
|
|
}
|
|
|
|
|
2020-04-30 15:10:12 +00:00
|
|
|
/// Create a group session from a room key and add it to our crypto store.
|
2020-04-22 13:15:08 +00:00
|
|
|
async fn add_room_key(
|
|
|
|
&mut self,
|
|
|
|
sender_key: &str,
|
|
|
|
signing_key: &str,
|
2020-06-20 21:18:20 +00:00
|
|
|
event: &mut ToDeviceEvent<RoomKeyEventContent>,
|
|
|
|
) -> OlmResult<Option<EventJson<AnyToDeviceEvent>>> {
|
2020-03-24 15:21:06 +00:00
|
|
|
match event.content.algorithm {
|
2020-03-24 16:25:01 +00:00
|
|
|
Algorithm::MegolmV1AesSha2 => {
|
2020-04-10 15:02:30 +00:00
|
|
|
let session_key = GroupSessionKey(mem::take(&mut event.content.session_key));
|
2020-04-10 12:00:03 +00:00
|
|
|
|
2020-03-25 10:32:40 +00:00
|
|
|
let session = InboundGroupSession::new(
|
|
|
|
sender_key,
|
2020-03-27 16:01:21 +00:00
|
|
|
signing_key,
|
2020-04-03 15:00:37 +00:00
|
|
|
&event.content.room_id,
|
2020-04-10 12:00:03 +00:00
|
|
|
session_key,
|
2020-03-25 10:32:40 +00:00
|
|
|
)?;
|
2020-04-30 15:10:12 +00:00
|
|
|
let _ = self.store.save_inbound_group_session(session).await?;
|
2020-05-06 10:15:15 +00:00
|
|
|
|
2020-06-20 21:18:20 +00:00
|
|
|
let event = EventJson::from(AnyToDeviceEvent::RoomKey(event.clone()));
|
2020-05-06 10:15:15 +00:00
|
|
|
Ok(Some(event))
|
2020-03-25 10:32:40 +00:00
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
warn!(
|
|
|
|
"Received room key with unsupported key algorithm {}",
|
|
|
|
event.content.algorithm
|
|
|
|
);
|
2020-05-06 10:15:15 +00:00
|
|
|
Ok(None)
|
2020-03-24 16:25:01 +00:00
|
|
|
}
|
2020-03-24 15:21:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-30 15:10:12 +00:00
|
|
|
/// Create a new outbound group session.
|
|
|
|
///
|
|
|
|
/// This also creates a matching inbound group session and saves that one in
|
|
|
|
/// the store.
|
2020-04-30 11:16:10 +00:00
|
|
|
async fn create_outbound_group_session(&mut self, room_id: &RoomId) -> OlmResult<()> {
|
2020-06-25 11:31:30 +00:00
|
|
|
let (outbound, inbound) = self.account.create_group_session_pair(room_id).await;
|
2020-04-08 13:06:57 +00:00
|
|
|
|
2020-06-25 11:31:30 +00:00
|
|
|
let _ = self.store.save_inbound_group_session(inbound).await?;
|
2020-04-08 13:06:57 +00:00
|
|
|
|
2020-04-30 15:10:12 +00:00
|
|
|
let _ = self
|
|
|
|
.outbound_group_sessions
|
2020-06-25 11:31:30 +00:00
|
|
|
.insert(room_id.to_owned(), outbound);
|
2020-04-08 13:06:57 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-04-30 15:10:12 +00:00
|
|
|
/// Encrypt a room message for the given room.
|
|
|
|
///
|
|
|
|
/// Beware that a group session needs to be shared before this method can be
|
|
|
|
/// called using the `share_group_session()` method.
|
|
|
|
///
|
|
|
|
/// Since group sessions can expire or become invalid if the room membership
|
|
|
|
/// changes client authors should check with the
|
|
|
|
/// `should_share_group_session()` method if a new group session needs to
|
|
|
|
/// be shared.
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `room_id` - The id of the room for which the message should be
|
|
|
|
/// encrypted.
|
|
|
|
///
|
|
|
|
/// * `content` - The plaintext content of the message that should be
|
|
|
|
/// encrypted.
|
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
///
|
|
|
|
/// Panics if a group session for the given room wasn't shared beforehand.
|
2020-04-09 14:24:40 +00:00
|
|
|
pub async fn encrypt(
|
2020-04-10 09:44:09 +00:00
|
|
|
&self,
|
2020-04-09 14:24:40 +00:00
|
|
|
room_id: &RoomId,
|
|
|
|
content: MessageEventContent,
|
2020-04-30 11:16:10 +00:00
|
|
|
) -> MegolmResult<EncryptedEventContent> {
|
2020-04-28 08:47:08 +00:00
|
|
|
let session = self.outbound_group_sessions.get(room_id);
|
2020-04-09 14:24:40 +00:00
|
|
|
|
2020-04-10 09:44:09 +00:00
|
|
|
let session = if let Some(s) = session {
|
|
|
|
s
|
|
|
|
} else {
|
|
|
|
panic!("Session wasn't created nor shared");
|
|
|
|
};
|
2020-04-09 14:24:40 +00:00
|
|
|
|
|
|
|
if session.expired() {
|
2020-04-10 09:44:09 +00:00
|
|
|
panic!("Session is expired");
|
2020-04-09 14:24:40 +00:00
|
|
|
}
|
|
|
|
|
2020-07-13 12:32:59 +00:00
|
|
|
Ok(session.encrypt(content).await)
|
2020-04-09 14:24:40 +00:00
|
|
|
}
|
|
|
|
|
2020-04-30 15:10:12 +00:00
|
|
|
/// Encrypt some JSON content using the given Olm session.
|
2020-04-09 14:24:40 +00:00
|
|
|
async fn olm_encrypt(
|
|
|
|
&mut self,
|
2020-04-14 12:05:18 +00:00
|
|
|
mut session: Session,
|
2020-04-09 14:24:40 +00:00
|
|
|
recipient_device: &Device,
|
|
|
|
event_type: EventType,
|
|
|
|
content: Value,
|
2020-04-30 11:16:10 +00:00
|
|
|
) -> OlmResult<EncryptedEventContent> {
|
2020-04-10 13:28:43 +00:00
|
|
|
let identity_keys = self.account.identity_keys();
|
2020-04-09 14:24:40 +00:00
|
|
|
|
2020-07-11 10:05:52 +00:00
|
|
|
// TODO most of this could go into the session, the session already
|
|
|
|
// stores the curve key of the device, if we also store the ed25519 key
|
|
|
|
// with the session we'll only need to pass in the account to the
|
|
|
|
// session and all of this can live in the session.
|
|
|
|
|
2020-04-09 14:24:40 +00:00
|
|
|
let recipient_signing_key = recipient_device
|
2020-04-30 11:16:10 +00:00
|
|
|
.get_key(KeyAlgorithm::Ed25519)
|
|
|
|
.ok_or(EventError::MissingSigningKey)?;
|
2020-04-09 14:24:40 +00:00
|
|
|
let recipient_sender_key = recipient_device
|
2020-04-30 11:16:10 +00:00
|
|
|
.get_key(KeyAlgorithm::Curve25519)
|
|
|
|
.ok_or(EventError::MissingSigningKey)?;
|
2020-04-09 14:24:40 +00:00
|
|
|
|
|
|
|
let payload = json!({
|
|
|
|
"sender": self.user_id,
|
|
|
|
"sender_device": self.device_id,
|
|
|
|
"keys": {
|
|
|
|
"ed25519": identity_keys.ed25519(),
|
|
|
|
},
|
|
|
|
"recipient": recipient_device.user_id(),
|
|
|
|
"recipient_keys": {
|
|
|
|
"ed25519": recipient_signing_key,
|
|
|
|
},
|
|
|
|
"type": event_type,
|
|
|
|
"content": content,
|
|
|
|
});
|
|
|
|
|
|
|
|
let plaintext = cjson::to_string(&payload)
|
|
|
|
.unwrap_or_else(|_| panic!(format!("Can't serialize {} to canonical JSON", payload)));
|
|
|
|
|
2020-04-14 12:05:18 +00:00
|
|
|
let ciphertext = session.encrypt(&plaintext).await.to_tuple();
|
2020-04-09 14:24:40 +00:00
|
|
|
|
|
|
|
let message_type: usize = ciphertext.0.into();
|
|
|
|
|
|
|
|
let ciphertext = CiphertextInfo {
|
|
|
|
body: ciphertext.1,
|
|
|
|
message_type: (message_type as u32).into(),
|
|
|
|
};
|
|
|
|
|
2020-04-23 08:52:47 +00:00
|
|
|
let mut content = BTreeMap::new();
|
2020-04-09 14:24:40 +00:00
|
|
|
|
|
|
|
content.insert(recipient_sender_key.to_owned(), ciphertext);
|
|
|
|
|
2020-07-11 10:05:52 +00:00
|
|
|
self.store.save_sessions(&[session]).await?;
|
|
|
|
|
2020-04-22 13:38:42 +00:00
|
|
|
Ok(EncryptedEventContent::OlmV1Curve25519AesSha2(
|
|
|
|
OlmV1Curve25519AesSha2Content {
|
|
|
|
sender_key: identity_keys.curve25519().to_owned(),
|
|
|
|
ciphertext: content,
|
|
|
|
},
|
|
|
|
))
|
2020-04-09 14:24:40 +00:00
|
|
|
}
|
|
|
|
|
2020-04-10 09:46:31 +00:00
|
|
|
/// Should the client share a group session for the given room.
|
|
|
|
///
|
|
|
|
/// Returns true if a session needs to be shared before room messages can be
|
|
|
|
/// encrypted, false if one is already shared and ready to encrypt room
|
|
|
|
/// messages.
|
|
|
|
///
|
|
|
|
/// This should be called every time a new room message wants to be sent out
|
|
|
|
/// since group sessions can expire at any time.
|
|
|
|
pub fn should_share_group_session(&self, room_id: &RoomId) -> bool {
|
2020-04-28 08:47:08 +00:00
|
|
|
let session = self.outbound_group_sessions.get(room_id);
|
2020-04-10 09:46:31 +00:00
|
|
|
|
|
|
|
match session {
|
|
|
|
Some(s) => !s.shared() || s.expired(),
|
|
|
|
None => true,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-14 09:55:12 +00:00
|
|
|
/// Invalidate the currently active outbound group session for the given
|
|
|
|
/// room.
|
|
|
|
///
|
|
|
|
/// Returns true if a session was invalidated, false if there was no session
|
|
|
|
/// to invalidate.
|
|
|
|
pub fn invalidate_group_session(&mut self, room_id: &RoomId) -> bool {
|
|
|
|
self.outbound_group_sessions.remove(room_id).is_some()
|
|
|
|
}
|
|
|
|
|
2020-04-08 13:06:57 +00:00
|
|
|
// TODO accept an algorithm here
|
2020-04-29 07:48:00 +00:00
|
|
|
/// Get to-device requests to share a group session with users in a room.
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// `room_id` - The room id of the room where the group session will be
|
|
|
|
/// used.
|
|
|
|
///
|
|
|
|
/// `users` - The list of users that should receive the group session.
|
|
|
|
pub async fn share_group_session<'a, I>(
|
2020-04-08 13:06:57 +00:00
|
|
|
&mut self,
|
|
|
|
room_id: &RoomId,
|
|
|
|
users: I,
|
2020-04-30 11:16:10 +00:00
|
|
|
) -> OlmResult<Vec<ToDeviceRequest>>
|
2020-04-08 13:06:57 +00:00
|
|
|
where
|
|
|
|
I: IntoIterator<Item = &'a UserId>,
|
|
|
|
{
|
2020-04-10 09:45:50 +00:00
|
|
|
self.create_outbound_group_session(room_id).await?;
|
2020-04-28 08:47:08 +00:00
|
|
|
let megolm_session = self.outbound_group_sessions.get(room_id).unwrap();
|
2020-04-10 07:58:13 +00:00
|
|
|
|
|
|
|
if megolm_session.shared() {
|
|
|
|
panic!("Session is already shared");
|
|
|
|
}
|
|
|
|
|
2020-04-09 14:24:40 +00:00
|
|
|
let session_id = megolm_session.session_id().to_owned();
|
2020-07-11 10:05:52 +00:00
|
|
|
|
|
|
|
// TODO don't mark the session as shared automatically only, when all
|
|
|
|
// the requests are done, failure to send these requests will likely end
|
|
|
|
// up in wedged sessions. We'll need to store the requests and let the
|
|
|
|
// caller mark them as sent using an UUID.
|
2020-04-09 14:24:40 +00:00
|
|
|
megolm_session.mark_as_shared();
|
2020-04-08 13:06:57 +00:00
|
|
|
|
2020-07-11 10:05:52 +00:00
|
|
|
// TODO the key content creation can go into the OutboundGroupSession
|
|
|
|
// struct.
|
|
|
|
|
2020-04-08 13:06:57 +00:00
|
|
|
let key_content = json!({
|
|
|
|
"algorithm": Algorithm::MegolmV1AesSha2,
|
|
|
|
"room_id": room_id,
|
2020-04-09 14:24:40 +00:00
|
|
|
"session_id": session_id.clone(),
|
|
|
|
"session_key": megolm_session.session_key().await,
|
|
|
|
"chain_index": megolm_session.message_index().await,
|
2020-04-08 13:06:57 +00:00
|
|
|
});
|
|
|
|
|
2020-04-09 14:24:40 +00:00
|
|
|
let mut user_map = Vec::new();
|
|
|
|
|
2020-04-08 13:06:57 +00:00
|
|
|
for user_id in users {
|
|
|
|
for device in self.store.get_user_devices(user_id).await?.devices() {
|
2020-04-30 11:16:10 +00:00
|
|
|
let sender_key = if let Some(k) = device.get_key(KeyAlgorithm::Curve25519) {
|
2020-04-08 13:06:57 +00:00
|
|
|
k
|
|
|
|
} else {
|
|
|
|
warn!(
|
|
|
|
"The device {} of user {} doesn't have a curve 25519 key",
|
|
|
|
user_id,
|
|
|
|
device.device_id()
|
|
|
|
);
|
2020-04-10 09:46:31 +00:00
|
|
|
// TODO mark the user for a key query.
|
2020-04-08 13:06:57 +00:00
|
|
|
continue;
|
|
|
|
};
|
|
|
|
|
2020-04-09 14:24:40 +00:00
|
|
|
// TODO abort if the device isn't verified
|
|
|
|
let sessions = self.store.get_sessions(sender_key).await?;
|
|
|
|
|
|
|
|
if let Some(s) = sessions {
|
|
|
|
let session = &s.lock().await[0];
|
2020-07-11 10:05:52 +00:00
|
|
|
// TODO once the session has the all the device info, we
|
|
|
|
// won't need the device anymore to encrypt stuff with the
|
|
|
|
// session.
|
2020-04-09 14:24:40 +00:00
|
|
|
user_map.push((session.clone(), device.clone()));
|
|
|
|
} else {
|
|
|
|
warn!(
|
2020-04-30 15:10:12 +00:00
|
|
|
"Trying to encrypt a Megolm session for user
|
2020-04-09 14:24:40 +00:00
|
|
|
{} on device {}, but no Olm session is found",
|
|
|
|
user_id,
|
|
|
|
device.device_id()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut message_vec = Vec::new();
|
|
|
|
|
2020-04-10 09:43:03 +00:00
|
|
|
for user_map_chunk in user_map.chunks(OlmMachine::MAX_TO_DEVICE_MESSAGES) {
|
2020-04-23 08:52:47 +00:00
|
|
|
let mut messages = BTreeMap::new();
|
2020-04-09 14:24:40 +00:00
|
|
|
|
|
|
|
for (session, device) in user_map_chunk {
|
|
|
|
if !messages.contains_key(device.user_id()) {
|
2020-04-23 08:52:47 +00:00
|
|
|
messages.insert(device.user_id().clone(), BTreeMap::new());
|
2020-04-09 14:24:40 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let user_messages = messages.get_mut(device.user_id()).unwrap();
|
|
|
|
|
|
|
|
let encrypted_content = self
|
|
|
|
.olm_encrypt(
|
|
|
|
session.clone(),
|
|
|
|
&device,
|
|
|
|
EventType::RoomKey,
|
|
|
|
key_content.clone(),
|
|
|
|
)
|
|
|
|
.await?;
|
2020-04-08 13:06:57 +00:00
|
|
|
|
2020-05-05 13:29:25 +00:00
|
|
|
user_messages.insert(
|
|
|
|
DeviceIdOrAllDevices::DeviceId(device.device_id().clone()),
|
|
|
|
serde_json::value::to_raw_value(&encrypted_content)?,
|
|
|
|
);
|
2020-04-08 13:06:57 +00:00
|
|
|
}
|
2020-04-09 14:24:40 +00:00
|
|
|
|
|
|
|
message_vec.push(ToDeviceRequest {
|
2020-04-22 13:38:42 +00:00
|
|
|
event_type: EventType::RoomEncrypted,
|
2020-04-10 07:57:10 +00:00
|
|
|
txn_id: Uuid::new_v4().to_string(),
|
2020-04-09 14:24:40 +00:00
|
|
|
messages,
|
|
|
|
});
|
2020-04-08 13:06:57 +00:00
|
|
|
}
|
|
|
|
|
2020-04-09 14:24:40 +00:00
|
|
|
Ok(message_vec)
|
2020-04-08 13:06:57 +00:00
|
|
|
}
|
|
|
|
|
2020-03-25 10:32:40 +00:00
|
|
|
fn add_forwarded_room_key(
|
|
|
|
&self,
|
2020-04-03 10:34:05 +00:00
|
|
|
_sender_key: &str,
|
2020-04-22 13:15:08 +00:00
|
|
|
_signing_key: &str,
|
2020-06-20 21:18:20 +00:00
|
|
|
_event: &ToDeviceEvent<ForwardedRoomKeyEventContent>,
|
2020-04-30 11:16:10 +00:00
|
|
|
) -> OlmResult<()> {
|
2020-03-25 10:32:40 +00:00
|
|
|
Ok(())
|
2020-03-24 15:21:06 +00:00
|
|
|
// TODO
|
|
|
|
}
|
|
|
|
|
2020-04-30 15:10:12 +00:00
|
|
|
/// Receive and properly handle a decrypted to-device event.
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `sender_key` - The sender (curve25519) key of the event sender.
|
|
|
|
///
|
|
|
|
/// * `signing_key` - The signing (ed25519) key of the event sender.
|
|
|
|
///
|
|
|
|
/// * `event` - The decrypted to-device event.
|
2020-03-30 15:07:36 +00:00
|
|
|
async fn handle_decrypted_to_device_event(
|
2020-03-24 16:25:01 +00:00
|
|
|
&mut self,
|
|
|
|
sender_key: &str,
|
2020-04-22 13:15:08 +00:00
|
|
|
signing_key: &str,
|
2020-06-20 21:18:20 +00:00
|
|
|
event: &EventJson<AnyToDeviceEvent>,
|
|
|
|
) -> OlmResult<Option<EventJson<AnyToDeviceEvent>>> {
|
2020-04-23 08:52:47 +00:00
|
|
|
let event = if let Ok(e) = event.deserialize() {
|
2020-03-24 15:21:06 +00:00
|
|
|
e
|
|
|
|
} else {
|
|
|
|
warn!("Decrypted to-device event failed to be parsed correctly");
|
2020-05-06 10:15:15 +00:00
|
|
|
return Ok(None);
|
2020-03-24 15:21:06 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
match event {
|
2020-06-20 21:18:20 +00:00
|
|
|
AnyToDeviceEvent::RoomKey(mut e) => {
|
2020-05-06 10:15:15 +00:00
|
|
|
Ok(self.add_room_key(sender_key, signing_key, &mut e).await?)
|
2020-04-23 08:52:47 +00:00
|
|
|
}
|
2020-06-20 21:18:20 +00:00
|
|
|
AnyToDeviceEvent::ForwardedRoomKey(e) => {
|
2020-05-06 10:15:15 +00:00
|
|
|
self.add_forwarded_room_key(sender_key, signing_key, &e)?;
|
|
|
|
Ok(None)
|
2020-04-22 13:15:08 +00:00
|
|
|
}
|
2020-03-25 10:32:40 +00:00
|
|
|
_ => {
|
|
|
|
warn!("Received a unexpected encrypted to-device event");
|
2020-05-06 10:15:15 +00:00
|
|
|
Ok(None)
|
2020-03-25 10:32:40 +00:00
|
|
|
}
|
2020-03-24 15:21:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-20 21:18:20 +00:00
|
|
|
fn handle_room_key_request(&self, _: &ToDeviceEvent<RoomKeyRequestEventContent>) {
|
2020-03-12 14:41:11 +00:00
|
|
|
// TODO handle room key requests here.
|
|
|
|
}
|
|
|
|
|
2020-06-20 21:18:20 +00:00
|
|
|
fn handle_verification_event(&self, _: &AnyToDeviceEvent) {
|
2020-03-12 14:41:11 +00:00
|
|
|
// TODO handle to-device verification events here.
|
|
|
|
}
|
|
|
|
|
2020-03-25 10:32:40 +00:00
|
|
|
/// Handle a sync response and update the internal state of the Olm machine.
|
|
|
|
///
|
2020-04-30 15:10:12 +00:00
|
|
|
/// This will decrypt to-device events but will not touch events in the room
|
|
|
|
/// timeline.
|
|
|
|
///
|
|
|
|
/// To decrypt an event from the room timeline call `decrypt_room_event()`.
|
2020-03-25 10:32:40 +00:00
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `response` - The sync latest sync response.
|
2020-04-30 15:10:12 +00:00
|
|
|
#[instrument(skip(response))]
|
2020-03-23 15:14:10 +00:00
|
|
|
pub async fn receive_sync_response(&mut self, response: &mut SyncResponse) {
|
2020-03-12 15:14:43 +00:00
|
|
|
let one_time_key_count = response
|
|
|
|
.device_one_time_keys_count
|
|
|
|
.get(&keys::KeyAlgorithm::SignedCurve25519);
|
|
|
|
|
|
|
|
let count: u64 = one_time_key_count.map_or(0, |c| (*c).into());
|
2020-04-28 13:47:49 +00:00
|
|
|
self.update_key_count(count);
|
2020-03-12 15:14:43 +00:00
|
|
|
|
2020-06-20 21:18:20 +00:00
|
|
|
for user_id in &response.device_lists.changed {
|
|
|
|
if let Err(e) = self.mark_user_as_changed(&user_id).await {
|
|
|
|
error!("Error marking a tracked user as changed {:?}", e);
|
2020-05-21 12:30:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-31 11:38:44 +00:00
|
|
|
for event_result in &mut response.to_device.events {
|
2020-04-23 08:52:47 +00:00
|
|
|
let event = if let Ok(e) = event_result.deserialize() {
|
2020-03-12 14:41:11 +00:00
|
|
|
e
|
|
|
|
} else {
|
|
|
|
// Skip invalid events.
|
2020-03-31 11:38:44 +00:00
|
|
|
warn!("Received an invalid to-device event {:?}", event_result);
|
2020-03-12 14:41:11 +00:00
|
|
|
continue;
|
|
|
|
};
|
|
|
|
|
2020-03-19 12:55:04 +00:00
|
|
|
info!("Received a to-device event {:?}", event);
|
|
|
|
|
2020-04-23 08:52:47 +00:00
|
|
|
match &event {
|
2020-06-20 21:18:20 +00:00
|
|
|
AnyToDeviceEvent::RoomEncrypted(e) => {
|
2020-07-11 19:20:02 +00:00
|
|
|
let decrypted_event = match self.decrypt_to_device_event(e).await {
|
2020-03-24 15:21:06 +00:00
|
|
|
Ok(e) => e,
|
|
|
|
Err(err) => {
|
|
|
|
warn!(
|
|
|
|
"Failed to decrypt to-device event from {} {}",
|
|
|
|
e.sender, err
|
|
|
|
);
|
|
|
|
// TODO if the session is wedged mark it for
|
|
|
|
// unwedging.
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
};
|
2020-03-31 11:38:44 +00:00
|
|
|
|
|
|
|
// TODO make sure private keys are cleared from the event
|
|
|
|
// before we replace the result.
|
|
|
|
*event_result = decrypted_event;
|
2020-03-12 14:41:11 +00:00
|
|
|
}
|
2020-07-11 19:20:02 +00:00
|
|
|
AnyToDeviceEvent::RoomKeyRequest(e) => self.handle_room_key_request(e),
|
2020-06-20 21:18:20 +00:00
|
|
|
AnyToDeviceEvent::KeyVerificationAccept(..)
|
|
|
|
| AnyToDeviceEvent::KeyVerificationCancel(..)
|
|
|
|
| AnyToDeviceEvent::KeyVerificationKey(..)
|
|
|
|
| AnyToDeviceEvent::KeyVerificationMac(..)
|
|
|
|
| AnyToDeviceEvent::KeyVerificationRequest(..)
|
|
|
|
| AnyToDeviceEvent::KeyVerificationStart(..) => {
|
|
|
|
self.handle_verification_event(&event)
|
|
|
|
}
|
2020-03-12 14:41:11 +00:00
|
|
|
_ => continue,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-03-25 14:03:10 +00:00
|
|
|
|
2020-04-30 15:10:12 +00:00
|
|
|
/// Decrypt an event from a room timeline.
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `event` - The event that should be decrypted.
|
2020-07-13 12:00:42 +00:00
|
|
|
///
|
|
|
|
/// * `room_id` - The ID of the room where the event was sent to.
|
2020-03-25 14:03:10 +00:00
|
|
|
pub async fn decrypt_room_event(
|
2020-03-30 15:07:36 +00:00
|
|
|
&mut self,
|
2020-06-20 21:18:20 +00:00
|
|
|
event: &MessageEventStub<EncryptedEventContent>,
|
|
|
|
room_id: &RoomId,
|
|
|
|
) -> MegolmResult<EventJson<AnyRoomEventStub>> {
|
2020-03-25 14:03:10 +00:00
|
|
|
let content = match &event.content {
|
|
|
|
EncryptedEventContent::MegolmV1AesSha2(c) => c,
|
2020-04-30 12:07:49 +00:00
|
|
|
_ => return Err(EventError::UnsupportedAlgorithm.into()),
|
2020-03-25 14:03:10 +00:00
|
|
|
};
|
|
|
|
|
2020-03-30 15:07:36 +00:00
|
|
|
let session = self
|
|
|
|
.store
|
2020-06-20 21:18:20 +00:00
|
|
|
.get_inbound_group_session(room_id, &content.sender_key, &content.session_id)
|
2020-03-30 15:07:36 +00:00
|
|
|
.await?;
|
2020-04-30 15:10:12 +00:00
|
|
|
// TODO check if the Olm session is wedged and re-request the key.
|
2020-04-30 11:16:10 +00:00
|
|
|
let session = session.ok_or(MegolmError::MissingSession)?;
|
2020-03-25 14:03:10 +00:00
|
|
|
|
|
|
|
// TODO check the message index.
|
|
|
|
// TODO check if this is from a verified device.
|
2020-07-13 12:00:42 +00:00
|
|
|
let (decrypted_event, _) = session.decrypt(event).await?;
|
2020-03-25 14:03:10 +00:00
|
|
|
|
2020-04-30 15:10:12 +00:00
|
|
|
trace!("Successfully decrypted Megolm event {:?}", decrypted_event);
|
2020-03-25 14:03:10 +00:00
|
|
|
// TODO set the encryption info on the event (is it verified, was it
|
|
|
|
// decrypted, sender key...)
|
|
|
|
|
|
|
|
Ok(decrypted_event)
|
|
|
|
}
|
2020-04-01 13:37:00 +00:00
|
|
|
|
2020-05-14 15:26:51 +00:00
|
|
|
/// Mark that the given user has changed his devices.
|
|
|
|
///
|
|
|
|
/// This will queue up the given user for a key query.
|
|
|
|
///
|
|
|
|
/// Note: The user already needs to be tracked for it to be queued up for a
|
|
|
|
/// key query.
|
|
|
|
///
|
|
|
|
/// Returns true if the user was queued up for a key query, false otherwise.
|
2020-05-15 13:33:30 +00:00
|
|
|
pub async fn mark_user_as_changed(&mut self, user_id: &UserId) -> StoreError<bool> {
|
2020-05-14 15:26:51 +00:00
|
|
|
if self.store.tracked_users().contains(user_id) {
|
2020-05-15 13:33:30 +00:00
|
|
|
self.store.update_tracked_user(user_id, true).await?;
|
|
|
|
Ok(true)
|
2020-05-14 15:26:51 +00:00
|
|
|
} else {
|
2020-05-15 13:33:30 +00:00
|
|
|
Ok(false)
|
2020-05-14 15:26:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-01 13:37:00 +00:00
|
|
|
/// Update the tracked users.
|
|
|
|
///
|
2020-04-30 15:10:12 +00:00
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `users` - An iterator over user ids that should be marked for
|
|
|
|
/// tracking.
|
|
|
|
///
|
2020-05-14 15:26:51 +00:00
|
|
|
/// This will mark users that weren't seen before for a key query and
|
|
|
|
/// tracking.
|
|
|
|
///
|
2020-04-01 13:37:00 +00:00
|
|
|
/// If the user is already known to the Olm machine it will not be
|
|
|
|
/// considered for a key query.
|
|
|
|
///
|
|
|
|
/// Use the `mark_user_as_changed()` if the user really needs a key query.
|
|
|
|
pub async fn update_tracked_users<'a, I>(&mut self, users: I)
|
|
|
|
where
|
2020-04-03 15:00:37 +00:00
|
|
|
I: IntoIterator<Item = &'a UserId>,
|
2020-04-01 13:37:00 +00:00
|
|
|
{
|
|
|
|
for user in users {
|
2020-05-15 13:33:30 +00:00
|
|
|
if self.store.tracked_users().contains(user) {
|
|
|
|
continue;
|
|
|
|
}
|
2020-04-01 13:37:00 +00:00
|
|
|
|
2020-05-15 13:33:30 +00:00
|
|
|
if let Err(e) = self.store.update_tracked_user(user, true).await {
|
|
|
|
warn!("Error storing users for tracking {}", e);
|
2020-04-01 13:37:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-30 15:10:12 +00:00
|
|
|
/// Should the client perform a key query request.
|
2020-04-01 13:37:00 +00:00
|
|
|
pub fn should_query_keys(&self) -> bool {
|
2020-05-15 13:33:30 +00:00
|
|
|
!self.store.users_for_key_query().is_empty()
|
2020-04-01 13:37:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the set of users that we need to query keys for.
|
2020-04-30 15:10:12 +00:00
|
|
|
///
|
|
|
|
/// Returns a hash set of users that need to be queried for keys.
|
2020-04-03 15:00:37 +00:00
|
|
|
pub fn users_for_key_query(&self) -> HashSet<UserId> {
|
2020-05-15 13:33:30 +00:00
|
|
|
self.store.users_for_key_query().clone()
|
2020-04-01 13:37:00 +00:00
|
|
|
}
|
2020-02-25 13:24:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
2020-04-27 14:31:28 +00:00
|
|
|
static USER_ID: &str = "@bob:example.org";
|
2020-02-25 13:24:18 +00:00
|
|
|
|
2020-07-11 19:06:21 +00:00
|
|
|
use matrix_sdk_common::js_int::uint;
|
2020-04-27 14:31:28 +00:00
|
|
|
use std::collections::BTreeMap;
|
2020-02-25 13:24:18 +00:00
|
|
|
use std::convert::TryFrom;
|
2020-07-11 19:06:21 +00:00
|
|
|
use std::convert::TryInto;
|
2020-04-28 12:48:49 +00:00
|
|
|
use std::time::SystemTime;
|
2020-02-25 13:24:18 +00:00
|
|
|
|
2020-04-29 07:48:00 +00:00
|
|
|
use http::Response;
|
2020-03-10 12:02:14 +00:00
|
|
|
use serde_json::json;
|
|
|
|
|
2020-04-29 07:48:00 +00:00
|
|
|
use crate::machine::{OlmMachine, OneTimeKeys};
|
|
|
|
use crate::Device;
|
|
|
|
|
2020-05-07 06:51:59 +00:00
|
|
|
use matrix_sdk_common::api::r0::{
|
2020-05-05 13:29:25 +00:00
|
|
|
keys, to_device::send_event_to_device::Request as ToDeviceRequest,
|
2020-04-29 07:48:00 +00:00
|
|
|
};
|
2020-05-07 06:51:59 +00:00
|
|
|
use matrix_sdk_common::events::{
|
2020-04-28 12:48:49 +00:00
|
|
|
room::{
|
2020-06-20 21:18:20 +00:00
|
|
|
encrypted::EncryptedEventContent,
|
2020-04-28 12:48:49 +00:00
|
|
|
message::{MessageEventContent, TextMessageEventContent},
|
|
|
|
},
|
2020-06-20 21:18:20 +00:00
|
|
|
AnyMessageEventStub, AnyRoomEventStub, AnyToDeviceEvent, EventJson, EventType,
|
|
|
|
MessageEventStub, ToDeviceEvent, UnsignedData,
|
2020-04-27 16:27:33 +00:00
|
|
|
};
|
2020-05-07 06:51:59 +00:00
|
|
|
use matrix_sdk_common::identifiers::{DeviceId, EventId, RoomId, UserId};
|
2020-06-22 19:40:51 +00:00
|
|
|
use matrix_sdk_test::test_json;
|
2020-02-25 13:24:18 +00:00
|
|
|
|
2020-04-27 14:31:28 +00:00
|
|
|
fn alice_id() -> UserId {
|
|
|
|
UserId::try_from("@alice:example.org").unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn alice_device_id() -> DeviceId {
|
|
|
|
"JLAFKJWSCS".to_string()
|
|
|
|
}
|
|
|
|
|
2020-03-10 12:02:14 +00:00
|
|
|
fn user_id() -> UserId {
|
|
|
|
UserId::try_from(USER_ID).unwrap()
|
|
|
|
}
|
|
|
|
|
2020-06-22 19:40:51 +00:00
|
|
|
fn response_from_file(json: &serde_json::Value) -> Response<Vec<u8>> {
|
|
|
|
Response::builder()
|
|
|
|
.status(200)
|
|
|
|
.body(json.to_string().as_bytes().to_vec())
|
|
|
|
.unwrap()
|
2020-02-25 13:24:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn keys_upload_response() -> keys::upload_keys::Response {
|
2020-06-22 19:40:51 +00:00
|
|
|
let data = response_from_file(&test_json::KEYS_UPLOAD);
|
2020-02-25 13:24:18 +00:00
|
|
|
keys::upload_keys::Response::try_from(data).expect("Can't parse the keys upload response")
|
|
|
|
}
|
|
|
|
|
2020-04-21 07:45:46 +00:00
|
|
|
fn keys_query_response() -> keys::get_keys::Response {
|
2020-06-22 19:40:51 +00:00
|
|
|
let data = response_from_file(&test_json::KEYS_QUERY);
|
2020-04-21 07:45:46 +00:00
|
|
|
keys::get_keys::Response::try_from(data).expect("Can't parse the keys upload response")
|
|
|
|
}
|
|
|
|
|
2020-04-28 12:48:49 +00:00
|
|
|
fn to_device_requests_to_content(requests: Vec<ToDeviceRequest>) -> EncryptedEventContent {
|
|
|
|
let to_device_request = &requests[0];
|
|
|
|
|
|
|
|
let content: EventJson<EncryptedEventContent> = serde_json::from_str(
|
|
|
|
to_device_request
|
|
|
|
.messages
|
|
|
|
.values()
|
2020-04-30 12:29:58 +00:00
|
|
|
.next()
|
2020-04-28 12:48:49 +00:00
|
|
|
.unwrap()
|
|
|
|
.values()
|
2020-04-30 12:29:58 +00:00
|
|
|
.next()
|
2020-04-28 12:48:49 +00:00
|
|
|
.unwrap()
|
|
|
|
.get(),
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
content.deserialize().unwrap()
|
|
|
|
}
|
|
|
|
|
2020-04-27 14:31:28 +00:00
|
|
|
async fn get_prepared_machine() -> (OlmMachine, OneTimeKeys) {
|
2020-07-10 13:43:32 +00:00
|
|
|
let mut machine = OlmMachine::new(&user_id(), &alice_device_id());
|
2020-07-13 13:49:16 +00:00
|
|
|
machine.account.update_uploaded_key_count(0);
|
2020-04-27 14:31:28 +00:00
|
|
|
let (_, otk) = machine
|
2020-04-21 07:45:46 +00:00
|
|
|
.keys_for_upload()
|
|
|
|
.await
|
|
|
|
.expect("Can't prepare initial key upload");
|
|
|
|
let response = keys_upload_response();
|
|
|
|
machine
|
|
|
|
.receive_keys_upload_response(&response)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
2020-04-27 14:31:28 +00:00
|
|
|
(machine, otk.unwrap())
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn get_machine_after_query() -> (OlmMachine, OneTimeKeys) {
|
|
|
|
let (mut machine, otk) = get_prepared_machine().await;
|
|
|
|
let response = keys_query_response();
|
|
|
|
|
2020-04-21 07:45:46 +00:00
|
|
|
machine
|
2020-04-27 14:31:28 +00:00
|
|
|
.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();
|
2020-04-30 11:16:10 +00:00
|
|
|
let alice = OlmMachine::new(&alice_id, &alice_device);
|
2020-04-27 14:31:28 +00:00
|
|
|
|
|
|
|
let alice_deivce = Device::from(&alice);
|
|
|
|
let bob_device = Device::from(&bob);
|
2020-04-30 09:51:20 +00:00
|
|
|
alice.store.save_devices(&[bob_device]).await.unwrap();
|
|
|
|
bob.store.save_devices(&[alice_deivce]).await.unwrap();
|
2020-04-27 14:31:28 +00:00
|
|
|
|
|
|
|
(alice, bob, otk)
|
2020-04-21 07:45:46 +00:00
|
|
|
}
|
|
|
|
|
2020-04-27 16:27:33 +00:00
|
|
|
async fn get_machine_pair_with_session() -> (OlmMachine, OlmMachine) {
|
|
|
|
let (mut alice, bob, one_time_keys) = get_machine_pair().await;
|
|
|
|
|
|
|
|
let mut bob_keys = BTreeMap::new();
|
|
|
|
|
2020-05-06 13:00:16 +00:00
|
|
|
let one_time_key = one_time_keys.iter().next().unwrap();
|
2020-04-27 16:27:33 +00:00
|
|
|
let mut keys = BTreeMap::new();
|
|
|
|
keys.insert(one_time_key.0.clone(), one_time_key.1.clone());
|
|
|
|
bob_keys.insert(bob.device_id.clone(), keys);
|
|
|
|
|
|
|
|
let mut one_time_keys = BTreeMap::new();
|
|
|
|
one_time_keys.insert(bob.user_id.clone(), bob_keys);
|
|
|
|
|
|
|
|
let response = keys::claim_keys::Response {
|
|
|
|
failures: BTreeMap::new(),
|
|
|
|
one_time_keys,
|
|
|
|
};
|
|
|
|
|
|
|
|
alice.receive_keys_claim_response(&response).await.unwrap();
|
|
|
|
|
|
|
|
(alice, bob)
|
|
|
|
}
|
|
|
|
|
2020-04-28 12:48:49 +00:00
|
|
|
async fn get_machine_pair_with_setup_sessions() -> (OlmMachine, OlmMachine) {
|
|
|
|
let (mut alice, mut bob) = get_machine_pair_with_session().await;
|
|
|
|
|
|
|
|
let session = alice
|
|
|
|
.store
|
|
|
|
.get_sessions(bob.account.identity_keys().curve25519())
|
|
|
|
.await
|
|
|
|
.unwrap()
|
|
|
|
.unwrap()
|
|
|
|
.lock()
|
|
|
|
.await[0]
|
|
|
|
.clone();
|
|
|
|
|
|
|
|
let bob_device = alice
|
|
|
|
.store
|
|
|
|
.get_device(&bob.user_id, &bob.device_id)
|
|
|
|
.await
|
|
|
|
.unwrap()
|
|
|
|
.unwrap();
|
|
|
|
|
2020-06-20 21:18:20 +00:00
|
|
|
let event = ToDeviceEvent {
|
2020-04-28 12:48:49 +00:00
|
|
|
sender: alice.user_id.clone(),
|
|
|
|
content: alice
|
|
|
|
.olm_encrypt(session, &bob_device, EventType::Dummy, json!({}))
|
|
|
|
.await
|
|
|
|
.unwrap(),
|
|
|
|
};
|
|
|
|
|
|
|
|
bob.decrypt_to_device_event(&event).await.unwrap();
|
|
|
|
|
|
|
|
(alice, bob)
|
|
|
|
}
|
|
|
|
|
2020-03-18 14:50:32 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn create_olm_machine() {
|
2020-07-10 13:43:32 +00:00
|
|
|
let machine = OlmMachine::new(&user_id(), &alice_device_id());
|
2020-03-18 14:50:32 +00:00
|
|
|
assert!(machine.should_upload_keys().await);
|
2020-02-25 13:24:18 +00:00
|
|
|
}
|
|
|
|
|
2020-03-16 12:24:54 +00:00
|
|
|
#[tokio::test]
|
2020-02-25 13:24:18 +00:00
|
|
|
async fn receive_keys_upload_response() {
|
2020-07-10 13:43:32 +00:00
|
|
|
let mut machine = OlmMachine::new(&user_id(), &alice_device_id());
|
2020-02-25 13:24:18 +00:00
|
|
|
let mut response = keys_upload_response();
|
|
|
|
|
2020-02-25 13:36:09 +00:00
|
|
|
response
|
|
|
|
.one_time_key_counts
|
|
|
|
.remove(&keys::KeyAlgorithm::SignedCurve25519)
|
|
|
|
.unwrap();
|
2020-02-25 13:24:18 +00:00
|
|
|
|
2020-03-18 14:50:32 +00:00
|
|
|
assert!(machine.should_upload_keys().await);
|
|
|
|
machine
|
|
|
|
.receive_keys_upload_response(&response)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
assert!(machine.should_upload_keys().await);
|
2020-02-25 13:24:18 +00:00
|
|
|
|
2020-07-11 19:06:21 +00:00
|
|
|
response
|
|
|
|
.one_time_key_counts
|
|
|
|
.insert(keys::KeyAlgorithm::SignedCurve25519, uint!(10));
|
2020-03-18 14:50:32 +00:00
|
|
|
machine
|
|
|
|
.receive_keys_upload_response(&response)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
assert!(machine.should_upload_keys().await);
|
2020-02-25 13:24:18 +00:00
|
|
|
|
2020-07-11 19:06:21 +00:00
|
|
|
response
|
|
|
|
.one_time_key_counts
|
|
|
|
.insert(keys::KeyAlgorithm::SignedCurve25519, uint!(50));
|
2020-03-18 14:50:32 +00:00
|
|
|
machine
|
|
|
|
.receive_keys_upload_response(&response)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
assert!(!machine.should_upload_keys().await);
|
2020-02-25 13:24:18 +00:00
|
|
|
}
|
|
|
|
|
2020-03-16 12:24:54 +00:00
|
|
|
#[tokio::test]
|
2020-02-25 13:24:18 +00:00
|
|
|
async fn generate_one_time_keys() {
|
2020-07-10 13:43:32 +00:00
|
|
|
let mut machine = OlmMachine::new(&user_id(), &alice_device_id());
|
2020-02-25 13:24:18 +00:00
|
|
|
|
|
|
|
let mut response = keys_upload_response();
|
|
|
|
|
2020-03-18 14:50:32 +00:00
|
|
|
assert!(machine.should_upload_keys().await);
|
2020-02-25 13:24:18 +00:00
|
|
|
|
2020-03-18 14:50:32 +00:00
|
|
|
machine
|
|
|
|
.receive_keys_upload_response(&response)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
assert!(machine.should_upload_keys().await);
|
2020-07-13 14:46:51 +00:00
|
|
|
assert!(machine.account.generate_one_time_keys().await.is_ok());
|
2020-02-25 13:24:18 +00:00
|
|
|
|
2020-07-11 19:06:21 +00:00
|
|
|
response
|
|
|
|
.one_time_key_counts
|
|
|
|
.insert(keys::KeyAlgorithm::SignedCurve25519, uint!(50));
|
2020-03-18 14:50:32 +00:00
|
|
|
machine
|
|
|
|
.receive_keys_upload_response(&response)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
2020-07-13 14:46:51 +00:00
|
|
|
assert!(machine.account.generate_one_time_keys().await.is_err());
|
2020-02-25 13:24:18 +00:00
|
|
|
}
|
2020-02-25 16:36:11 +00:00
|
|
|
|
2020-03-18 14:50:32 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn test_device_key_signing() {
|
2020-07-10 13:43:32 +00:00
|
|
|
let machine = OlmMachine::new(&user_id(), &alice_device_id());
|
2020-02-25 16:36:11 +00:00
|
|
|
|
2020-07-10 15:10:34 +00:00
|
|
|
let mut device_keys = machine.account.device_keys().await;
|
2020-04-10 13:28:43 +00:00
|
|
|
let identity_keys = machine.account.identity_keys();
|
2020-02-25 16:36:11 +00:00
|
|
|
let ed25519_key = identity_keys.ed25519();
|
|
|
|
|
|
|
|
let ret = machine.verify_json(
|
|
|
|
&machine.user_id,
|
|
|
|
&machine.device_id,
|
|
|
|
ed25519_key,
|
2020-03-10 12:02:14 +00:00
|
|
|
&mut json!(&mut device_keys),
|
2020-02-25 16:36:11 +00:00
|
|
|
);
|
|
|
|
assert!(ret.is_ok());
|
|
|
|
}
|
2020-02-25 16:49:43 +00:00
|
|
|
|
2020-05-14 09:55:12 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn tests_session_invalidation() {
|
2020-07-10 13:43:32 +00:00
|
|
|
let mut machine = OlmMachine::new(&user_id(), &alice_device_id());
|
2020-05-14 09:55:12 +00:00
|
|
|
let room_id = RoomId::try_from("!test:example.org").unwrap();
|
|
|
|
|
|
|
|
machine
|
|
|
|
.create_outbound_group_session(&room_id)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
assert!(machine.outbound_group_sessions.get(&room_id).is_some());
|
|
|
|
|
|
|
|
machine.invalidate_group_session(&room_id);
|
|
|
|
|
|
|
|
assert!(machine.outbound_group_sessions.get(&room_id).is_none());
|
|
|
|
}
|
|
|
|
|
2020-03-18 14:50:32 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn test_invalid_signature() {
|
2020-07-10 13:43:32 +00:00
|
|
|
let machine = OlmMachine::new(&user_id(), &alice_device_id());
|
2020-02-25 16:49:43 +00:00
|
|
|
|
2020-07-10 15:10:34 +00:00
|
|
|
let mut device_keys = machine.account.device_keys().await;
|
2020-02-25 16:49:43 +00:00
|
|
|
|
|
|
|
let ret = machine.verify_json(
|
|
|
|
&machine.user_id,
|
|
|
|
&machine.device_id,
|
|
|
|
"fake_key",
|
2020-03-10 12:02:14 +00:00
|
|
|
&mut json!(&mut device_keys),
|
2020-02-25 16:49:43 +00:00
|
|
|
);
|
|
|
|
assert!(ret.is_err());
|
|
|
|
}
|
2020-02-29 10:13:57 +00:00
|
|
|
|
2020-03-18 14:50:32 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn test_one_time_key_signing() {
|
2020-07-13 13:49:16 +00:00
|
|
|
let machine = OlmMachine::new(&user_id(), &alice_device_id());
|
|
|
|
machine.account.update_uploaded_key_count(49);
|
2020-02-29 10:13:57 +00:00
|
|
|
|
2020-07-13 14:46:51 +00:00
|
|
|
let mut one_time_keys = machine.account.signed_one_time_keys().await.unwrap();
|
2020-04-10 13:28:43 +00:00
|
|
|
let identity_keys = machine.account.identity_keys();
|
2020-02-29 10:13:57 +00:00
|
|
|
let ed25519_key = identity_keys.ed25519();
|
|
|
|
|
2020-05-06 13:00:16 +00:00
|
|
|
let mut one_time_key = one_time_keys.values_mut().next().unwrap();
|
2020-02-29 10:13:57 +00:00
|
|
|
|
|
|
|
let ret = machine.verify_json(
|
|
|
|
&machine.user_id,
|
|
|
|
&machine.device_id,
|
|
|
|
ed25519_key,
|
2020-03-10 12:41:14 +00:00
|
|
|
&mut json!(&mut one_time_key),
|
2020-02-29 10:13:57 +00:00
|
|
|
);
|
|
|
|
assert!(ret.is_ok());
|
|
|
|
}
|
2020-03-10 13:06:30 +00:00
|
|
|
|
2020-03-16 12:24:54 +00:00
|
|
|
#[tokio::test]
|
2020-03-10 13:06:30 +00:00
|
|
|
async fn test_keys_for_upload() {
|
2020-07-10 13:43:32 +00:00
|
|
|
let mut machine = OlmMachine::new(&user_id(), &alice_device_id());
|
2020-07-13 13:49:16 +00:00
|
|
|
machine.account.update_uploaded_key_count(0);
|
2020-03-10 13:06:30 +00:00
|
|
|
|
2020-04-10 13:28:43 +00:00
|
|
|
let identity_keys = machine.account.identity_keys();
|
2020-03-10 13:06:30 +00:00
|
|
|
let ed25519_key = identity_keys.ed25519();
|
|
|
|
|
|
|
|
let (device_keys, mut one_time_keys) = machine
|
|
|
|
.keys_for_upload()
|
2020-03-18 14:50:32 +00:00
|
|
|
.await
|
2020-03-10 13:06:30 +00:00
|
|
|
.expect("Can't prepare initial key upload");
|
|
|
|
|
|
|
|
let ret = machine.verify_json(
|
|
|
|
&machine.user_id,
|
|
|
|
&machine.device_id,
|
|
|
|
ed25519_key,
|
2020-05-06 13:00:16 +00:00
|
|
|
&mut json!(&mut one_time_keys.as_mut().unwrap().values_mut().next()),
|
2020-03-10 13:06:30 +00:00
|
|
|
);
|
|
|
|
assert!(ret.is_ok());
|
|
|
|
|
|
|
|
let ret = machine.verify_json(
|
|
|
|
&machine.user_id,
|
|
|
|
&machine.device_id,
|
|
|
|
ed25519_key,
|
|
|
|
&mut json!(&mut device_keys.unwrap()),
|
|
|
|
);
|
|
|
|
assert!(ret.is_ok());
|
|
|
|
|
|
|
|
let mut response = keys_upload_response();
|
|
|
|
response.one_time_key_counts.insert(
|
|
|
|
keys::KeyAlgorithm::SignedCurve25519,
|
2020-07-11 19:06:21 +00:00
|
|
|
(one_time_keys.unwrap().len() as u64).try_into().unwrap(),
|
2020-03-10 13:06:30 +00:00
|
|
|
);
|
|
|
|
|
2020-03-18 14:50:32 +00:00
|
|
|
machine
|
|
|
|
.receive_keys_upload_response(&response)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
2020-03-10 13:06:30 +00:00
|
|
|
|
2020-03-18 14:50:32 +00:00
|
|
|
let ret = machine.keys_for_upload().await;
|
2020-03-10 13:06:30 +00:00
|
|
|
assert!(ret.is_err());
|
|
|
|
}
|
2020-04-21 07:45:46 +00:00
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_keys_query() {
|
2020-04-27 14:31:28 +00:00
|
|
|
let (mut machine, _) = get_prepared_machine().await;
|
2020-04-21 07:45:46 +00:00
|
|
|
let response = keys_query_response();
|
|
|
|
let alice_id = UserId::try_from("@alice:example.org").unwrap();
|
|
|
|
let alice_device_id = "JLAFKJWSCS".to_owned();
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2020-04-27 14:31:28 +00:00
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_missing_sessions_calculation() {
|
|
|
|
let (mut machine, _) = get_machine_after_query().await;
|
|
|
|
|
|
|
|
let alice = alice_id();
|
|
|
|
let alice_device = alice_device_id();
|
|
|
|
|
|
|
|
let missing_sessions = machine
|
|
|
|
.get_missing_sessions([alice.clone()].iter())
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
assert!(missing_sessions.contains_key(&alice));
|
|
|
|
let user_sessions = missing_sessions.get(&alice).unwrap();
|
|
|
|
assert!(user_sessions.contains_key(&alice_device));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
2020-04-27 16:27:33 +00:00
|
|
|
async fn test_session_creation() {
|
2020-04-27 14:31:28 +00:00
|
|
|
let (mut alice_machine, bob_machine, one_time_keys) = get_machine_pair().await;
|
|
|
|
|
|
|
|
let mut bob_keys = BTreeMap::new();
|
|
|
|
|
2020-05-06 13:00:16 +00:00
|
|
|
let one_time_key = one_time_keys.iter().next().unwrap();
|
2020-04-27 14:31:28 +00:00
|
|
|
let mut keys = BTreeMap::new();
|
|
|
|
keys.insert(one_time_key.0.clone(), one_time_key.1.clone());
|
|
|
|
bob_keys.insert(bob_machine.device_id.clone(), keys);
|
|
|
|
|
|
|
|
let mut one_time_keys = BTreeMap::new();
|
|
|
|
one_time_keys.insert(bob_machine.user_id.clone(), bob_keys);
|
|
|
|
|
|
|
|
let response = keys::claim_keys::Response {
|
|
|
|
failures: BTreeMap::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())
|
|
|
|
}
|
2020-04-27 16:27:33 +00:00
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_olm_encryption() {
|
|
|
|
let (mut alice, mut bob) = get_machine_pair_with_session().await;
|
|
|
|
|
|
|
|
let session = alice
|
|
|
|
.store
|
|
|
|
.get_sessions(bob.account.identity_keys().curve25519())
|
|
|
|
.await
|
|
|
|
.unwrap()
|
|
|
|
.unwrap()
|
|
|
|
.lock()
|
|
|
|
.await[0]
|
|
|
|
.clone();
|
|
|
|
|
|
|
|
let bob_device = alice
|
|
|
|
.store
|
|
|
|
.get_device(&bob.user_id, &bob.device_id)
|
|
|
|
.await
|
|
|
|
.unwrap()
|
|
|
|
.unwrap();
|
|
|
|
|
2020-06-20 21:18:20 +00:00
|
|
|
let event = ToDeviceEvent {
|
2020-04-27 16:27:33 +00:00
|
|
|
sender: alice.user_id.clone(),
|
|
|
|
content: alice
|
|
|
|
.olm_encrypt(session, &bob_device, EventType::Dummy, json!({}))
|
|
|
|
.await
|
|
|
|
.unwrap(),
|
|
|
|
};
|
|
|
|
|
2020-06-20 21:18:20 +00:00
|
|
|
let event = bob
|
|
|
|
.decrypt_to_device_event(&event)
|
|
|
|
.await
|
|
|
|
.unwrap()
|
|
|
|
.deserialize()
|
|
|
|
.unwrap();
|
2020-04-27 16:27:33 +00:00
|
|
|
|
2020-06-20 21:18:20 +00:00
|
|
|
if let AnyToDeviceEvent::Dummy(e) = event {
|
2020-04-27 16:27:33 +00:00
|
|
|
assert_eq!(e.sender, alice.user_id);
|
|
|
|
} else {
|
2020-06-20 21:18:20 +00:00
|
|
|
panic!("Wrong event type found {:?}", event);
|
2020-04-27 16:27:33 +00:00
|
|
|
}
|
|
|
|
}
|
2020-04-28 08:47:08 +00:00
|
|
|
|
2020-05-05 13:29:25 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn test_room_key_sharing() {
|
|
|
|
let (mut alice, mut bob) = get_machine_pair_with_session().await;
|
|
|
|
|
|
|
|
let room_id = RoomId::try_from("!test:example.org").unwrap();
|
|
|
|
|
|
|
|
let to_device_requests = alice
|
|
|
|
.share_group_session(&room_id, [bob.user_id.clone()].iter())
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
2020-06-20 21:18:20 +00:00
|
|
|
let event = ToDeviceEvent {
|
2020-05-05 13:29:25 +00:00
|
|
|
sender: alice.user_id.clone(),
|
|
|
|
content: to_device_requests_to_content(to_device_requests),
|
|
|
|
};
|
|
|
|
|
|
|
|
let alice_session = alice.outbound_group_sessions.get(&room_id).unwrap();
|
|
|
|
|
2020-06-20 21:18:20 +00:00
|
|
|
let event = bob
|
|
|
|
.decrypt_to_device_event(&event)
|
|
|
|
.await
|
|
|
|
.unwrap()
|
|
|
|
.deserialize()
|
|
|
|
.unwrap();
|
2020-05-05 13:29:25 +00:00
|
|
|
|
2020-06-20 21:18:20 +00:00
|
|
|
if let AnyToDeviceEvent::RoomKey(event) = event {
|
|
|
|
assert_eq!(event.sender, alice.user_id);
|
|
|
|
assert!(event.content.session_key.is_empty());
|
2020-05-05 13:29:25 +00:00
|
|
|
} else {
|
2020-06-20 21:18:20 +00:00
|
|
|
panic!("expected RoomKeyEvent found {:?}", event);
|
2020-05-05 13:29:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 (mut alice, mut bob) = get_machine_pair_with_setup_sessions().await;
|
|
|
|
let room_id = RoomId::try_from("!test:example.org").unwrap();
|
|
|
|
|
|
|
|
let to_device_requests = alice
|
|
|
|
.share_group_session(&room_id, [bob.user_id().clone()].iter())
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
2020-06-20 21:18:20 +00:00
|
|
|
let event = ToDeviceEvent {
|
|
|
|
sender: alice.user_id.clone(),
|
2020-05-05 13:29:25 +00:00
|
|
|
content: to_device_requests_to_content(to_device_requests),
|
|
|
|
};
|
|
|
|
|
|
|
|
bob.decrypt_to_device_event(&event).await.unwrap();
|
|
|
|
|
|
|
|
let plaintext = "It is a secret to everybody";
|
|
|
|
|
|
|
|
let content = MessageEventContent::Text(TextMessageEventContent::new_plain(plaintext));
|
|
|
|
|
|
|
|
let encrypted_content = alice.encrypt(&room_id, content.clone()).await.unwrap();
|
|
|
|
|
2020-06-20 21:18:20 +00:00
|
|
|
let event = MessageEventStub {
|
|
|
|
event_id: EventId::try_from("$xxxxx:example.org").unwrap(),
|
2020-05-05 13:29:25 +00:00
|
|
|
origin_server_ts: SystemTime::now(),
|
|
|
|
sender: alice.user_id().clone(),
|
|
|
|
content: encrypted_content,
|
|
|
|
unsigned: UnsignedData::default(),
|
|
|
|
};
|
|
|
|
|
|
|
|
let decrypted_event = bob
|
2020-06-20 21:18:20 +00:00
|
|
|
.decrypt_room_event(&event, &room_id)
|
2020-05-05 13:29:25 +00:00
|
|
|
.await
|
|
|
|
.unwrap()
|
|
|
|
.deserialize()
|
|
|
|
.unwrap();
|
|
|
|
|
2020-06-20 21:18:20 +00:00
|
|
|
match decrypted_event {
|
|
|
|
AnyRoomEventStub::Message(AnyMessageEventStub::RoomMessage(MessageEventStub {
|
|
|
|
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");
|
|
|
|
}
|
|
|
|
}
|
2020-05-05 13:29:25 +00:00
|
|
|
_ => panic!("Decrypted room event has the wrong type"),
|
|
|
|
}
|
|
|
|
}
|
2020-02-25 13:24:18 +00:00
|
|
|
}
|