Merge pull request #3252 from matrix-org/andybalaam/run-integration-tests-for-memorystore

crypto: Run the crypto integration tests against MemoryStore
This commit is contained in:
Andy Balaam
2024-03-25 12:23:52 +00:00
committed by GitHub
3 changed files with 312 additions and 32 deletions

View File

@@ -33,7 +33,7 @@ pub mod types;
mod utilities;
mod verification;
#[cfg(feature = "testing")]
#[cfg(any(test, feature = "testing"))]
/// Testing facilities and helpers for crypto tests
pub mod testing {
pub use crate::identities::{

View File

@@ -3,45 +3,41 @@
macro_rules! cryptostore_integration_tests {
() => {
mod cryptostore_integration_tests {
use std::time::Duration;
use std::collections::{BTreeMap, HashMap};
use std::time::Duration;
use assert_matches::assert_matches;
use matrix_sdk_test::async_test;
use ruma::{
device_id,
events::secret::request::SecretName,
room_id,
serde::Raw,
to_device::DeviceIdOrAllDevices,
user_id, DeviceId, RoomId, TransactionId, UserId
device_id, events::secret::request::SecretName, room_id, serde::Raw,
to_device::DeviceIdOrAllDevices, user_id, DeviceId, RoomId, TransactionId, UserId,
};
use serde_json::value::to_raw_value;
use $crate::{
olm::{
Curve25519PublicKey, InboundGroupSession, OlmMessageHash,
PrivateCrossSigningIdentity, Account, Session,
Account, Curve25519PublicKey, InboundGroupSession, OlmMessageHash,
PrivateCrossSigningIdentity, Session,
},
store::{
Changes, CryptoStore, DeviceChanges,
GossipRequest, IdentityChanges, BackupDecryptionKey, RoomSettings, PendingChanges,
BackupDecryptionKey, Changes, CryptoStore, DeviceChanges, GossipRequest,
IdentityChanges, PendingChanges, RoomSettings,
},
testing::{get_device, get_other_identity, get_own_identity},
types::{
events::{
dummy::DummyEventContent,
olm_v1::{DecryptedSecretSendEvent, OlmV1Keys},
room_key_request::MegolmV1AesSha2Content,
room_key_withheld::{
CommonWithheldCodeContent, MegolmV1AesSha2WithheldContent,
RoomKeyWithheldContent, WithheldCode,
},
olm_v1::{DecryptedSecretSendEvent, OlmV1Keys},
secret_send::SecretSendContent,
ToDeviceEvent,
},
EventEncryptionAlgorithm,
},
ReadOnlyDevice, SecretInfo, ToDeviceRequest, TrackedUser, GossippedSecret,
GossippedSecret, ReadOnlyDevice, SecretInfo, ToDeviceRequest, TrackedUser,
};
use super::get_store;
@@ -82,13 +78,12 @@ macro_rules! cryptostore_integration_tests {
bob.generate_one_time_keys(1);
let one_time_key = *bob.one_time_keys().values().next().unwrap();
let sender_key = bob.identity_keys().curve25519;
let session = alice
.create_outbound_session_helper(
Default::default(),
sender_key,
one_time_key,
false,
);
let session = alice.create_outbound_session_helper(
Default::default(),
sender_key,
one_time_key,
false,
);
(alice, session)
}
@@ -101,7 +96,7 @@ macro_rules! cryptostore_integration_tests {
let account = get_account();
store
.save_pending_changes(PendingChanges { account: Some(account), })
.save_pending_changes(PendingChanges { account: Some(account) })
.await
.expect("Can't save account");
assert!(store.get_static_account().is_some());
@@ -114,7 +109,10 @@ macro_rules! cryptostore_integration_tests {
assert!(store.load_account().await.unwrap().is_none());
let account = get_account();
store.save_pending_changes(PendingChanges { account: Some(account), }).await.expect("Can't save account");
store
.save_pending_changes(PendingChanges { account: Some(account) })
.await
.expect("Can't save account");
assert!(store.get_static_account().is_some());
}
@@ -123,7 +121,10 @@ macro_rules! cryptostore_integration_tests {
let store = get_store("load_account", None).await;
let account = get_account();
store.save_pending_changes(PendingChanges { account: Some(account.deep_clone()), }).await.expect("Can't save account");
store
.save_pending_changes(PendingChanges { account: Some(account.deep_clone()) })
.await
.expect("Can't save account");
let loaded_account = store.load_account().await.expect("Can't load account");
let loaded_account = loaded_account.unwrap();
@@ -137,7 +138,10 @@ macro_rules! cryptostore_integration_tests {
get_store("load_account_with_passphrase", Some("secret_passphrase")).await;
let account = get_account();
store.save_pending_changes(PendingChanges { account: Some(account.deep_clone()), }).await.expect("Can't save account");
store
.save_pending_changes(PendingChanges { account: Some(account.deep_clone()) })
.await
.expect("Can't save account");
let loaded_account = store.load_account().await.expect("Can't load account");
let loaded_account = loaded_account.unwrap();
@@ -150,12 +154,18 @@ macro_rules! cryptostore_integration_tests {
let store = get_store("save_and_share_account", None).await;
let mut account = get_account();
store.save_pending_changes(PendingChanges { account: Some(account.deep_clone()), }).await.expect("Can't save account");
store
.save_pending_changes(PendingChanges { account: Some(account.deep_clone()) })
.await
.expect("Can't save account");
account.mark_as_shared();
account.update_uploaded_key_count(50);
store.save_pending_changes(PendingChanges { account: Some(account.deep_clone()), }).await.expect("Can't save account");
store
.save_pending_changes(PendingChanges { account: Some(account.deep_clone()) })
.await
.expect("Can't save account");
let loaded_account = store.load_account().await.expect("Can't load account");
let loaded_account = loaded_account.unwrap();
@@ -168,7 +178,10 @@ macro_rules! cryptostore_integration_tests {
async fn load_sessions() {
let store = get_store("load_sessions", None).await;
let (account, session) = get_account_and_session().await;
store.save_pending_changes(PendingChanges { account: Some(account.deep_clone()), }).await.expect("Can't save account");
store
.save_pending_changes(PendingChanges { account: Some(account.deep_clone()) })
.await
.expect("Can't save account");
let changes = Changes { sessions: vec![session.clone()], ..Default::default() };
@@ -195,7 +208,12 @@ macro_rules! cryptostore_integration_tests {
let sender_key = session.sender_key.to_base64();
let session_id = session.session_id().to_owned();
store.save_pending_changes(PendingChanges { account: Some(account.deep_clone()), }).await.expect("Can't save account");
store
.save_pending_changes(PendingChanges {
account: Some(account.deep_clone()),
})
.await
.expect("Can't save account");
let changes = Changes { sessions: vec![session.clone()], ..Default::default() };
store.save_changes(changes).await.unwrap();
@@ -236,7 +254,8 @@ macro_rules! cryptostore_integration_tests {
"Initially there should be no outbound group session"
);
let (session, _) = account.create_group_session_pair_with_defaults(&room_id).await;
let (session, _) =
account.create_group_session_pair_with_defaults(&room_id).await;
let user_id = user_id!("@example:localhost");
let request = ToDeviceRequest::new(
@@ -338,7 +357,6 @@ macro_rules! cryptostore_integration_tests {
session_info(&sessions[9]),
]).await.expect("Failed to mark sessions as backed up");
// And ask which still need backing up
let to_back_up = store.inbound_group_sessions_for_backup(10).await.unwrap();
let needs_backing_up = |i: usize| to_back_up.iter().any(|s| s.session_id() == sessions[i].session_id());
@@ -909,7 +927,7 @@ macro_rules! cryptostore_integration_tests {
#[async_test]
async fn backup_keys_saving() {
let (account, store) = get_loaded_store("backup_keys_saving").await;
let (_account, store) = get_loaded_store("backup_keys_saving").await;
let restored = store.load_backup_keys().await.unwrap();
assert!(restored.decryption_key.is_none(), "Initially no backup decryption key should be present");

View File

@@ -634,3 +634,265 @@ mod tests {
assert!(store.is_message_known(&hash).await.unwrap());
}
}
#[cfg(test)]
mod integration_tests {
use std::{
collections::HashMap,
sync::{Arc, Mutex, OnceLock},
};
use async_trait::async_trait;
use ruma::{
events::secret::request::SecretName, DeviceId, OwnedDeviceId, RoomId, TransactionId, UserId,
};
use super::MemoryStore;
use crate::{
cryptostore_integration_tests, cryptostore_integration_tests_time,
olm::{
InboundGroupSession, OlmMessageHash, OutboundGroupSession, PrivateCrossSigningIdentity,
StaticAccountData,
},
store::{BackupKeys, Changes, CryptoStore, PendingChanges, RoomKeyCounts, RoomSettings},
types::events::room_key_withheld::RoomKeyWithheldEvent,
Account, GossipRequest, GossippedSecret, ReadOnlyDevice, ReadOnlyUserIdentities,
SecretInfo, Session, TrackedUser,
};
/// Holds on to a MemoryStore during a test, and moves it back into STORES
/// when this is dropped
#[derive(Clone, Debug)]
struct PersistentMemoryStore(Arc<MemoryStore>);
impl PersistentMemoryStore {
fn new() -> Self {
Self(Arc::new(MemoryStore::new()))
}
fn get_static_account(&self) -> Option<StaticAccountData> {
self.0.get_static_account()
}
}
impl MemoryStore {
fn get_static_account(&self) -> Option<StaticAccountData> {
self.account.read().unwrap().as_ref().map(|acc| acc.static_data().clone())
}
}
/// Return a clone of the store for the test with the supplied name. Note:
/// dropping this store won't destroy its data, since
/// [PersistentMemoryStore] is a reference-counted smart pointer
/// to an underlying [MemoryStore].
async fn get_store(name: &str, _passphrase: Option<&str>) -> PersistentMemoryStore {
// Holds on to one [PersistentMemoryStore] per test, so even if the test drops
// the store, we keep its data alive. This simulates the behaviour of
// the other stores, which keep their data in a real DB, allowing us to
// test MemoryStore using the same code.
static STORES: OnceLock<Mutex<HashMap<String, PersistentMemoryStore>>> = OnceLock::new();
let stores = STORES.get_or_init(|| Mutex::new(HashMap::new()));
stores
.lock()
.unwrap()
.entry(name.to_owned())
.or_insert_with(PersistentMemoryStore::new)
.clone()
}
/// Forwards all methods to the underlying [MemoryStore].
#[async_trait]
impl CryptoStore for PersistentMemoryStore {
type Error = <MemoryStore as CryptoStore>::Error;
async fn load_account(&self) -> Result<Option<Account>, Self::Error> {
self.0.load_account().await
}
async fn load_identity(&self) -> Result<Option<PrivateCrossSigningIdentity>, Self::Error> {
self.0.load_identity().await
}
async fn save_changes(&self, changes: Changes) -> Result<(), Self::Error> {
self.0.save_changes(changes).await
}
async fn save_pending_changes(&self, changes: PendingChanges) -> Result<(), Self::Error> {
self.0.save_pending_changes(changes).await
}
async fn get_sessions(
&self,
sender_key: &str,
) -> Result<Option<Arc<tokio::sync::Mutex<Vec<Session>>>>, Self::Error> {
self.0.get_sessions(sender_key).await
}
async fn get_inbound_group_session(
&self,
room_id: &RoomId,
session_id: &str,
) -> Result<Option<InboundGroupSession>, Self::Error> {
self.0.get_inbound_group_session(room_id, session_id).await
}
async fn get_withheld_info(
&self,
room_id: &RoomId,
session_id: &str,
) -> Result<Option<RoomKeyWithheldEvent>, Self::Error> {
self.0.get_withheld_info(room_id, session_id).await
}
async fn get_inbound_group_sessions(
&self,
) -> Result<Vec<InboundGroupSession>, Self::Error> {
self.0.get_inbound_group_sessions().await
}
async fn inbound_group_session_counts(&self) -> Result<RoomKeyCounts, Self::Error> {
self.0.inbound_group_session_counts().await
}
async fn inbound_group_sessions_for_backup(
&self,
limit: usize,
) -> Result<Vec<InboundGroupSession>, Self::Error> {
self.0.inbound_group_sessions_for_backup(limit).await
}
async fn mark_inbound_group_sessions_as_backed_up(
&self,
room_and_session_ids: &[(&RoomId, &str)],
) -> Result<(), Self::Error> {
self.0.mark_inbound_group_sessions_as_backed_up(room_and_session_ids).await
}
async fn reset_backup_state(&self) -> Result<(), Self::Error> {
self.0.reset_backup_state().await
}
async fn load_backup_keys(&self) -> Result<BackupKeys, Self::Error> {
self.0.load_backup_keys().await
}
async fn get_outbound_group_session(
&self,
room_id: &RoomId,
) -> Result<Option<OutboundGroupSession>, Self::Error> {
self.0.get_outbound_group_session(room_id).await
}
async fn load_tracked_users(&self) -> Result<Vec<TrackedUser>, Self::Error> {
self.0.load_tracked_users().await
}
async fn save_tracked_users(&self, users: &[(&UserId, bool)]) -> Result<(), Self::Error> {
self.0.save_tracked_users(users).await
}
async fn get_device(
&self,
user_id: &UserId,
device_id: &DeviceId,
) -> Result<Option<ReadOnlyDevice>, Self::Error> {
self.0.get_device(user_id, device_id).await
}
async fn get_user_devices(
&self,
user_id: &UserId,
) -> Result<HashMap<OwnedDeviceId, ReadOnlyDevice>, Self::Error> {
self.0.get_user_devices(user_id).await
}
async fn get_user_identity(
&self,
user_id: &UserId,
) -> Result<Option<ReadOnlyUserIdentities>, Self::Error> {
self.0.get_user_identity(user_id).await
}
async fn is_message_known(
&self,
message_hash: &OlmMessageHash,
) -> Result<bool, Self::Error> {
self.0.is_message_known(message_hash).await
}
async fn get_outgoing_secret_requests(
&self,
request_id: &TransactionId,
) -> Result<Option<GossipRequest>, Self::Error> {
self.0.get_outgoing_secret_requests(request_id).await
}
async fn get_secret_request_by_info(
&self,
secret_info: &SecretInfo,
) -> Result<Option<GossipRequest>, Self::Error> {
self.0.get_secret_request_by_info(secret_info).await
}
async fn get_unsent_secret_requests(&self) -> Result<Vec<GossipRequest>, Self::Error> {
self.0.get_unsent_secret_requests().await
}
async fn delete_outgoing_secret_requests(
&self,
request_id: &TransactionId,
) -> Result<(), Self::Error> {
self.0.delete_outgoing_secret_requests(request_id).await
}
async fn get_secrets_from_inbox(
&self,
secret_name: &SecretName,
) -> Result<Vec<GossippedSecret>, Self::Error> {
self.0.get_secrets_from_inbox(secret_name).await
}
async fn delete_secrets_from_inbox(
&self,
secret_name: &SecretName,
) -> Result<(), Self::Error> {
self.0.delete_secrets_from_inbox(secret_name).await
}
async fn get_room_settings(
&self,
room_id: &RoomId,
) -> Result<Option<RoomSettings>, Self::Error> {
self.0.get_room_settings(room_id).await
}
async fn get_custom_value(&self, key: &str) -> Result<Option<Vec<u8>>, Self::Error> {
self.0.get_custom_value(key).await
}
async fn set_custom_value(&self, key: &str, value: Vec<u8>) -> Result<(), Self::Error> {
self.0.set_custom_value(key, value).await
}
async fn remove_custom_value(&self, key: &str) -> Result<(), Self::Error> {
self.0.remove_custom_value(key).await
}
async fn try_take_leased_lock(
&self,
lease_duration_ms: u32,
key: &str,
holder: &str,
) -> Result<bool, Self::Error> {
self.0.try_take_leased_lock(lease_duration_ms, key, holder).await
}
async fn next_batch_token(&self) -> Result<Option<String>, Self::Error> {
self.0.next_batch_token().await
}
}
cryptostore_integration_tests!();
cryptostore_integration_tests_time!();
}