From 4bf103db38b328cdc7a5312e8d356449551513ba Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 16 Apr 2025 12:20:15 +0200 Subject: [PATCH] test: Add more olm decryption encryption_info tests --- .../src/machine/test_helpers.rs | 63 +++++++- .../src/machine/tests/mod.rs | 2 + .../tests/olm_decryption_encryption_info.rs | 149 ++++++++++++++++++ .../machine/tests/send_encrypted_to_device.rs | 33 ++-- 4 files changed, 223 insertions(+), 24 deletions(-) create mode 100644 crates/matrix-sdk-crypto/src/machine/tests/olm_decryption_encryption_info.rs diff --git a/crates/matrix-sdk-crypto/src/machine/test_helpers.rs b/crates/matrix-sdk-crypto/src/machine/test_helpers.rs index 588569932..53f9720bf 100644 --- a/crates/matrix-sdk-crypto/src/machine/test_helpers.rs +++ b/crates/matrix-sdk-crypto/src/machine/test_helpers.rs @@ -18,6 +18,7 @@ use std::collections::BTreeMap; use as_variant::as_variant; +use matrix_sdk_common::deserialized_responses::ProcessedToDeviceEvent; use matrix_sdk_test::{ruma_response_from_json, test_json}; use ruma::{ api::client::keys::{ @@ -29,14 +30,20 @@ use ruma::{ encryption::OneTimeKey, events::dummy::ToDeviceDummyEventContent, serde::Raw, + to_device::DeviceIdOrAllDevices, user_id, DeviceId, OwnedOneTimeKeyId, TransactionId, UserId, }; -use serde_json::json; +use serde_json::{json, Value}; use crate::{ + machine::tests, store::{Changes, MemoryStore}, - types::{events::ToDeviceEvent, requests::AnyOutgoingRequest}, - CrossSigningBootstrapRequests, DeviceData, OlmMachine, + types::{ + events::ToDeviceEvent, + requests::{AnyOutgoingRequest, ToDeviceRequest}, + }, + utilities::json_convert, + CrossSigningBootstrapRequests, DeviceData, EncryptionSyncChanges, OlmMachine, }; /// These keys need to be periodically uploaded to the server. @@ -172,7 +179,7 @@ pub async fn get_machine_pair_with_session( } /// Create a session for the two supplied Olm machines to communicate. -async fn build_session_for_pair( +pub async fn build_session_for_pair( alice: OlmMachine, bob: OlmMachine, mut one_time_keys: BTreeMap< @@ -276,3 +283,51 @@ pub fn bootstrap_requests_to_keys_query_response( ruma_response_from_json(&kq_response) } + +/// Encrypt and send a given to device event from the sender to the recipient. +/// +/// Simulates the reception by having the recipient machine receiving a sync +/// with the encrypted message. +/// Returns the event as received by the recipient. +pub async fn encrypt_to_device_helper( + sender: &OlmMachine, + recipient: &OlmMachine, + event_type: &str, + event_content: Value, +) -> ProcessedToDeviceEvent { + let device = + sender.get_device(recipient.user_id(), recipient.device_id(), None).await.unwrap().unwrap(); + let raw_encrypted = device + .encrypt_event_raw(event_type, &event_content) + .await + .expect("Should have encrypted the content"); + + let request = ToDeviceRequest::new( + recipient.user_id(), + DeviceIdOrAllDevices::DeviceId(recipient.device_id().to_owned()), + "m.room.encrypted", + raw_encrypted.cast(), + ); + + let encrypted_event = ToDeviceEvent::new( + sender.user_id().to_owned(), + tests::to_device_requests_to_content(vec![request.clone().into()]), + ); + + let encrypted_event = json_convert(&encrypted_event).unwrap(); + + let sync_changes = EncryptionSyncChanges { + to_device_events: vec![encrypted_event], + changed_devices: &Default::default(), + one_time_keys_counts: &Default::default(), + unused_fallback_keys: None, + next_batch_token: None, + }; + + let (to_devices, _) = recipient + .receive_sync_changes(sync_changes) + .await + .expect("Receive Sync changes should not fail"); + + to_devices.first().unwrap().clone() +} diff --git a/crates/matrix-sdk-crypto/src/machine/tests/mod.rs b/crates/matrix-sdk-crypto/src/machine/tests/mod.rs index e5d0dc54a..da0aa4b48 100644 --- a/crates/matrix-sdk-crypto/src/machine/tests/mod.rs +++ b/crates/matrix-sdk-crypto/src/machine/tests/mod.rs @@ -82,6 +82,7 @@ use crate::{ mod decryption_verification_state; mod interactive_verification; mod megolm_sender_data; +mod olm_decryption_encryption_info; mod olm_encryption; mod room_settings; mod send_encrypted_to_device; @@ -837,6 +838,7 @@ async fn test_decrypt_unencrypted_event() { ); } +/// This only bootstrap cross-signing but it will not sign the current device !! pub async fn setup_cross_signing_for_machine_test_helper(alice: &OlmMachine, bob: &OlmMachine) { let CrossSigningBootstrapRequests { upload_signing_keys_req: alice_upload_signing, .. } = alice.bootstrap_cross_signing(false).await.expect("Expect Alice x-signing key request"); diff --git a/crates/matrix-sdk-crypto/src/machine/tests/olm_decryption_encryption_info.rs b/crates/matrix-sdk-crypto/src/machine/tests/olm_decryption_encryption_info.rs new file mode 100644 index 000000000..da62905bd --- /dev/null +++ b/crates/matrix-sdk-crypto/src/machine/tests/olm_decryption_encryption_info.rs @@ -0,0 +1,149 @@ +// 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 assert_matches2::{assert_let, assert_matches}; +use matrix_sdk_common::deserialized_responses::{ + ProcessedToDeviceEvent, VerificationLevel, VerificationState, +}; +use matrix_sdk_test::async_test; +use ruma::TransactionId; +use serde_json::json; + +use crate::{ + machine::{ + test_helpers::{ + bootstrap_requests_to_keys_query_response, build_session_for_pair, + encrypt_to_device_helper, get_machine_pair_with_session, + get_prepared_machine_test_helper, + }, + tests, + tests::{ + alice_device_id, alice_id, + decryption_verification_state::mark_alice_identity_as_verified_test_helper, + }, + }, + verification::tests::bob_id, + DeviceData, OlmMachine, +}; + +#[async_test] +async fn test_to_device_verification_state_signed_device() { + let (alice, bob) = get_machine_pair_with_session(alice_id(), tests::user_id(), false).await; + + let bootstrap_requests = alice.bootstrap_cross_signing(false).await.unwrap(); + let kq_response = bootstrap_requests_to_keys_query_response(bootstrap_requests); + bob.receive_keys_query_response(&TransactionId::new(), &kq_response).await.unwrap(); + + let custom_event_type = "m.hello"; + + let custom_content = json!({ + "what": "foo", + }); + + let received = encrypt_to_device_helper(&alice, &bob, custom_event_type, custom_content).await; + + assert_let!(ProcessedToDeviceEvent::Decrypted { encryption_info, .. } = received); + + assert_matches!( + encryption_info.verification_state, + VerificationState::Unverified(VerificationLevel::UnverifiedIdentity) + ); +} + +#[async_test] +async fn test_to_device_verification_state_verified() { + let (alice, bob) = get_machine_pair_with_session(alice_id(), tests::user_id(), false).await; + + let bootstrap_requests = alice.bootstrap_cross_signing(false).await.unwrap(); + let kq_response = bootstrap_requests_to_keys_query_response(bootstrap_requests); + bob.receive_keys_query_response(&TransactionId::new(), &kq_response).await.unwrap(); + + let bootstrap_requests = bob.bootstrap_cross_signing(false).await.unwrap(); + let kq_response = bootstrap_requests_to_keys_query_response(bootstrap_requests); + alice.receive_keys_query_response(&TransactionId::new(), &kq_response).await.unwrap(); + + mark_alice_identity_as_verified_test_helper(&alice, &bob).await; + + let custom_event_type = "m.hello"; + + let custom_content = json!({ + "what": "foo", + }); + + let received = encrypt_to_device_helper(&alice, &bob, custom_event_type, custom_content).await; + + assert_let!(ProcessedToDeviceEvent::Decrypted { encryption_info, .. } = received); + + assert_matches!(encryption_info.verification_state, VerificationState::Verified); +} + +#[async_test] +async fn test_to_device_verification_state_verification_violation() { + let (alice, bob) = get_machine_pair_with_session(alice_id(), tests::user_id(), false).await; + + let bootstrap_requests = alice.bootstrap_cross_signing(false).await.unwrap(); + let kq_response = bootstrap_requests_to_keys_query_response(bootstrap_requests); + bob.receive_keys_query_response(&TransactionId::new(), &kq_response).await.unwrap(); + + // Simulate Alice's cross-signing key changing after having been verified by + // setting the `previously_verified` flag + let alice_identity = + bob.store().get_identity(alice.user_id()).await.unwrap().unwrap().other().unwrap(); + alice_identity.mark_as_previously_verified().await.unwrap(); + + let custom_event_type = "m.hello"; + + let custom_content = json!({ + "what": "foo", + }); + + let received = encrypt_to_device_helper(&alice, &bob, custom_event_type, custom_content).await; + + assert_let!(ProcessedToDeviceEvent::Decrypted { encryption_info, .. } = received); + + assert_matches!( + encryption_info.verification_state, + VerificationState::Unverified(VerificationLevel::VerificationViolation) + ); +} + +#[async_test] +async fn test_to_device_verification_level_none() { + // Arrange + // Set up prepared machine where it is the first time ever bob will receive a + // message from this alice device. So bob is not yet aware of alice device. + let (bob, otk) = get_prepared_machine_test_helper(bob_id(), false).await; + let alice_device = alice_device_id(); + let alice = OlmMachine::new(alice_id(), alice_device).await; + // Bob doesn't know yet alice keys. Alice contact in an async way + let bob_device = DeviceData::from_machine_test_helper(&bob).await.unwrap(); + alice.store().save_device_data(&[bob_device]).await.unwrap(); + // Let alice claim one otk and create the outbound session + let (alice, bob) = build_session_for_pair(alice, bob, otk).await; + + // Act + + let custom_event_type = "m.hello"; + + let custom_content = json!({ + "what": "foo", + }); + + let received = encrypt_to_device_helper(&alice, &bob, custom_event_type, custom_content).await; + + // The decryption will fail (MissingSigningKey) for custom to-device events if + // the recipient does not know the sender. + // The VerificationLevel::None can only happen for room_key not custom events. + assert_matches!(received, ProcessedToDeviceEvent::UnableToDecrypt { .. }); +} diff --git a/crates/matrix-sdk-crypto/src/machine/tests/send_encrypted_to_device.rs b/crates/matrix-sdk-crypto/src/machine/tests/send_encrypted_to_device.rs index aa6234984..79bfd7659 100644 --- a/crates/matrix-sdk-crypto/src/machine/tests/send_encrypted_to_device.rs +++ b/crates/matrix-sdk-crypto/src/machine/tests/send_encrypted_to_device.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use assert_matches2::assert_matches; +use assert_matches2::{assert_let, assert_matches}; use matrix_sdk_common::deserialized_responses::{ AlgorithmInfo, ProcessedToDeviceEvent, VerificationLevel, VerificationState, }; @@ -88,9 +88,9 @@ async fn test_send_encrypted_to_device() { assert_eq!(1, decrypted.len()); let processed_event = &decrypted[0]; - let ProcessedToDeviceEvent::Decrypted { decrypted_event, .. } = processed_event else { - panic!("Unexpected variant"); - }; + + assert_let!(ProcessedToDeviceEvent::Decrypted { decrypted_event, .. } = processed_event); + let decrypted_event = decrypted_event.deserialize().unwrap(); assert_eq!(decrypted_event.event_type().to_string(), custom_event_type.to_owned()); @@ -210,17 +210,16 @@ async fn test_processed_to_device_variants() { assert_eq!(4, processed.len()); let processed_event = &processed[0]; - let ProcessedToDeviceEvent::Decrypted { encryption_info, .. } = processed_event else { - panic!("Unexpected variant"); - }; + + assert_let!(ProcessedToDeviceEvent::Decrypted { encryption_info, .. } = processed_event); assert_eq!(alice.user_id().to_owned(), encryption_info.sender); assert_eq!(Some(alice.device_id().to_owned()), encryption_info.sender_device); - let AlgorithmInfo::OlmV1Curve25519AesSha2 { curve25519_key } = &encryption_info.algorithm_info - else { - panic!("Unexpected algorithm info"); - }; + assert_let!( + AlgorithmInfo::OlmV1Curve25519AesSha2 { curve25519_key } = &encryption_info.algorithm_info + ); + assert_eq!(curve25519_key.to_owned(), alice_curve.to_base64()); assert_eq!(encryption_info.session_id, None); @@ -230,19 +229,13 @@ async fn test_processed_to_device_variants() { ); let processed_event = &processed[1]; - let ProcessedToDeviceEvent::PlainText(_) = processed_event else { - panic!("Unexpected variant"); - }; + assert_matches!(processed_event, ProcessedToDeviceEvent::PlainText(_)); let processed_event = &processed[2]; - let ProcessedToDeviceEvent::NotProcessed(_) = processed_event else { - panic!("Unexpected variant"); - }; + assert_matches!(processed_event, ProcessedToDeviceEvent::NotProcessed(_)); let processed_event = &processed[3]; - let ProcessedToDeviceEvent::UnableToDecrypt { .. } = processed_event else { - panic!("Unexpected variant"); - }; + assert_matches!(processed_event, ProcessedToDeviceEvent::UnableToDecrypt { .. }); } #[async_test]