crypto: Split out the Olm session handling logic into a separate module.

master
Damir Jelić 2020-10-08 14:41:34 +02:00
parent da5ef42719
commit 279ce0bba0
3 changed files with 172 additions and 104 deletions

View File

@ -35,6 +35,7 @@ mod key_request;
mod machine; mod machine;
pub mod olm; pub mod olm;
mod requests; mod requests;
mod session_manager;
pub mod store; pub mod store;
mod verification; mod verification;

View File

@ -14,7 +14,7 @@
#[cfg(feature = "sqlite_cryptostore")] #[cfg(feature = "sqlite_cryptostore")]
use std::path::Path; use std::path::Path;
use std::{collections::BTreeMap, mem, sync::Arc, time::Duration}; use std::{collections::BTreeMap, mem, sync::Arc};
use dashmap::DashMap; use dashmap::DashMap;
use tracing::{debug, error, info, instrument, trace, warn}; use tracing::{debug, error, info, instrument, trace, warn};
@ -54,6 +54,7 @@ use crate::{
InboundGroupSession, ReadOnlyAccount, InboundGroupSession, ReadOnlyAccount,
}, },
requests::{IncomingResponse, OutgoingRequest}, requests::{IncomingResponse, OutgoingRequest},
session_manager::SessionManager,
store::{CryptoStore, MemoryStore, Result as StoreResult, Store}, store::{CryptoStore, MemoryStore, Result as StoreResult, Store},
verification::{Sas, VerificationMachine}, verification::{Sas, VerificationMachine},
ToDeviceRequest, ToDeviceRequest,
@ -73,6 +74,8 @@ pub struct OlmMachine {
/// Persists all the encryption keys so a client can resume the session /// Persists all the encryption keys so a client can resume the session
/// without the need to create new keys. /// without the need to create new keys.
store: Store, store: Store,
/// A state machine that handles Olm sessions creation.
session_manager: SessionManager,
/// A state machine that keeps track of our outbound group sessions. /// A state machine that keeps track of our outbound group sessions.
group_session_manager: GroupSessionManager, group_session_manager: GroupSessionManager,
/// A state machine that is responsible to handle and keep track of SAS /// A state machine that is responsible to handle and keep track of SAS
@ -97,8 +100,6 @@ impl std::fmt::Debug for OlmMachine {
} }
impl OlmMachine { impl OlmMachine {
const KEY_CLAIM_TIMEOUT: Duration = Duration::from_secs(10);
/// Create a new memory based OlmMachine. /// Create a new memory based OlmMachine.
/// ///
/// The created machine will keep the encryption keys only in memory and /// The created machine will keep the encryption keys only in memory and
@ -142,6 +143,8 @@ impl OlmMachine {
store: store.clone(), store: store.clone(),
}; };
let session_manager =
SessionManager::new(account.clone(), key_request_machine.clone(), store.clone());
let group_session_manager = GroupSessionManager::new(account.clone(), store.clone()); let group_session_manager = GroupSessionManager::new(account.clone(), store.clone());
let identity_manager = IdentityManager::new( let identity_manager = IdentityManager::new(
user_id.clone(), user_id.clone(),
@ -155,6 +158,7 @@ impl OlmMachine {
device_id, device_id,
account, account,
store, store,
session_manager,
group_session_manager, group_session_manager,
verification_machine, verification_machine,
key_request_machine, key_request_machine,
@ -386,63 +390,7 @@ impl OlmMachine {
&self, &self,
users: &mut impl Iterator<Item = &UserId>, users: &mut impl Iterator<Item = &UserId>,
) -> OlmResult<Option<(Uuid, KeysClaimRequest)>> { ) -> OlmResult<Option<(Uuid, KeysClaimRequest)>> {
let mut missing = BTreeMap::new(); self.session_manager.get_missing_sessions(users).await
// Add the list of devices that the user wishes to establish sessions
// right now.
for user_id in users {
let user_devices = self.store.get_user_devices(user_id).await?;
for device in user_devices.devices() {
let sender_key = if let Some(k) = device.get_key(DeviceKeyAlgorithm::Curve25519) {
k
} else {
continue;
};
let sessions = self.store.get_sessions(sender_key).await?;
let is_missing = if let Some(sessions) = sessions {
sessions.lock().await.is_empty()
} else {
true
};
if is_missing {
missing
.entry(user_id.to_owned())
.or_insert_with(BTreeMap::new)
.insert(
device.device_id().into(),
DeviceKeyAlgorithm::SignedCurve25519,
);
}
}
}
// Add the list of sessions that for some reason automatically need to
// create an Olm session.
for item in self.key_request_machine.users_for_key_claim().iter() {
let user = item.key();
for device_id in item.value().iter() {
missing
.entry(user.to_owned())
.or_insert_with(BTreeMap::new)
.insert(device_id.to_owned(), DeviceKeyAlgorithm::SignedCurve25519);
}
}
if missing.is_empty() {
Ok(None)
} else {
Ok(Some((
Uuid::new_v4(),
assign!(KeysClaimRequest::new(missing), {
timeout: Some(OlmMachine::KEY_CLAIM_TIMEOUT),
}),
)))
}
} }
/// Receive a successful key claim response and create new Olm sessions with /// Receive a successful key claim response and create new Olm sessions with
@ -452,50 +400,9 @@ impl OlmMachine {
/// ///
/// * `response` - The response containing the claimed one-time keys. /// * `response` - The response containing the claimed one-time keys.
async fn receive_keys_claim_response(&self, response: &KeysClaimResponse) -> OlmResult<()> { async fn receive_keys_claim_response(&self, response: &KeysClaimResponse) -> OlmResult<()> {
// TODO log the failures here self.session_manager
.receive_keys_claim_response(response)
for (user_id, user_devices) in &response.one_time_keys { .await
for (device_id, key_map) in user_devices {
let device = match self.store.get_readonly_device(&user_id, device_id).await {
Ok(Some(d)) => d,
Ok(None) => {
warn!(
"Tried to create an Olm session for {} {}, but the device is unknown",
user_id, device_id
);
continue;
}
Err(e) => {
warn!(
"Tried to create an Olm session for {} {}, but \
can't fetch the device from the store {:?}",
user_id, device_id, e
);
continue;
}
};
info!("Creating outbound Session for {} {}", user_id, device_id);
let session = match self.account.create_outbound_session(device, &key_map).await {
Ok(s) => s,
Err(e) => {
warn!("{:?}", e);
continue;
}
};
if let Err(e) = self.store.save_sessions(&[session]).await {
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.
self.key_request_machine.retry_keyshare(&user_id, device_id);
}
}
Ok(())
} }
/// Receive a successful keys query response. /// Receive a successful keys query response.

View File

@ -0,0 +1,160 @@
// Copyright 2020 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::{collections::BTreeMap, time::Duration};
use matrix_sdk_common::{
api::r0::keys::claim_keys::{Request as KeysClaimRequest, Response as KeysClaimResponse},
assign,
identifiers::{DeviceKeyAlgorithm, UserId},
uuid::Uuid,
};
use tracing::{error, info, warn};
use crate::{error::OlmResult, key_request::KeyRequestMachine, olm::Account, store::Store};
#[derive(Debug, Clone)]
pub(crate) struct SessionManager {
account: Account,
store: Store,
key_request_machine: KeyRequestMachine,
}
impl SessionManager {
const KEY_CLAIM_TIMEOUT: Duration = Duration::from_secs(10);
pub fn new(account: Account, key_request_machine: KeyRequestMachine, store: Store) -> Self {
Self {
account,
store,
key_request_machine,
}
}
pub async fn get_missing_sessions(
&self,
users: &mut impl Iterator<Item = &UserId>,
) -> OlmResult<Option<(Uuid, KeysClaimRequest)>> {
let mut missing = BTreeMap::new();
// Add the list of devices that the user wishes to establish sessions
// right now.
for user_id in users {
let user_devices = self.store.get_user_devices(user_id).await?;
for device in user_devices.devices() {
let sender_key = if let Some(k) = device.get_key(DeviceKeyAlgorithm::Curve25519) {
k
} else {
continue;
};
let sessions = self.store.get_sessions(sender_key).await?;
let is_missing = if let Some(sessions) = sessions {
sessions.lock().await.is_empty()
} else {
true
};
if is_missing {
missing
.entry(user_id.to_owned())
.or_insert_with(BTreeMap::new)
.insert(
device.device_id().into(),
DeviceKeyAlgorithm::SignedCurve25519,
);
}
}
}
// Add the list of sessions that for some reason automatically need to
// create an Olm session.
for item in self.key_request_machine.users_for_key_claim().iter() {
let user = item.key();
for device_id in item.value().iter() {
missing
.entry(user.to_owned())
.or_insert_with(BTreeMap::new)
.insert(device_id.to_owned(), DeviceKeyAlgorithm::SignedCurve25519);
}
}
if missing.is_empty() {
Ok(None)
} else {
Ok(Some((
Uuid::new_v4(),
assign!(KeysClaimRequest::new(missing), {
timeout: Some(Self::KEY_CLAIM_TIMEOUT),
}),
)))
}
}
/// 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.
pub async fn receive_keys_claim_response(&self, response: &KeysClaimResponse) -> OlmResult<()> {
// 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 = match self.store.get_readonly_device(&user_id, device_id).await {
Ok(Some(d)) => d,
Ok(None) => {
warn!(
"Tried to create an Olm session for {} {}, but the device is unknown",
user_id, device_id
);
continue;
}
Err(e) => {
warn!(
"Tried to create an Olm session for {} {}, but \
can't fetch the device from the store {:?}",
user_id, device_id, e
);
continue;
}
};
info!("Creating outbound Session for {} {}", user_id, device_id);
let session = match self.account.create_outbound_session(device, &key_map).await {
Ok(s) => s,
Err(e) => {
warn!("{:?}", e);
continue;
}
};
if let Err(e) = self.store.save_sessions(&[session]).await {
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.
self.key_request_machine.retry_keyshare(&user_id, device_id);
}
}
Ok(())
}
}