CVE-2022-39252 (published 2022-09-29T15:15Z)

matrix-rust-sdk is an implementation of a Matrix client-server library in Rust, and matrix-sdk-crypto is the Matrix encryption library. Prior to version 0.6, when a user requests a room key from their devices, the software correctly remembers the request. When the user receives a forwarded room key, the software accepts it without checking who the room key came from. This allows homeservers to try to insert room keys of questionable validity, potentially mounting an impersonation attack. Version 0.6 fixes this issue.

CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N)

Weakness information:
cwe_id: CWE-287
cwe_name: Improper Authentication
description: When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
extended_description: 
            
			
         
         
cwe_id: CWE-322
cwe_name: Key Exchange without Entity Authentication
description: The product performs a key exchange with an actor without verifying the identity of that actor.
extended_description: Performing a key exchange will preserve the integrity of the information sent between two entities, but this will not guarantee that the entities are who they claim they are. This may enable an attacker to impersonate an actor by modifying traffic between the two entities.  Typically, this involves a victim client that contacts a malicious server that is impersonating a trusted server. If the client skips authentication or ignores an authentication failure, the malicious server may request authentication information from the user. The malicious server can then use this authentication information to log in to the trusted server using the victim's credentials, sniff traffic between the victim and trusted server, etc.
         

Fix information:
repo_url: https://github.com/matrix-org/matrix-rust-sdk
commit_details: repo_url: https://github.com/matrix-org/matrix-rust-sdk
author: Damir Jelić
author_date: 2022-08-02 12:52:02+02:00
msg: fix(crypto): Only accept forwarded room keys from our own trusted devices
repository: repo_url: https://github.com/matrix-org/matrix-rust-sdk
repo_name: matrix-org/matrix-rust-sdk
description: Matrix Client-Server SDK for Rust
repo_language: Rust
owner: matrix-org
file_changes: filename: machine.rs
change_type: ModificationType.MODIFY
diff: @@ -945,6 +945,21 @@ impl GossipMachine {
         }
     }
 
+    async fn should_accept_forward(
+        &self,
+        info: &GossipRequest,
+        sender_key: Curve25519PublicKey,
+    ) -> Result<bool, CryptoStoreError> {
+        let device =
+            self.store.get_device_from_curve_key(&info.request_recipient, sender_key).await?;
+
+        if let Some(device) = device {
+            Ok(device.user_id() == self.user_id() && device.is_verified())
+        } else {
+            Ok(false)
+        }
+    }
+
     /// Receive a forwarded room key event that was sent using any of our
     /// supported content types.
     async fn receive_supported_keys(
@@ -956,8 +971,22 @@ impl GossipMachine {
         let algorithm = event.content.algorithm();
 
         if let Some(info) = self.get_key_info(event).await? {
-            self.accept_forwarded_room_key(&info, &event.sender, sender_key, algorithm, content)
-                .await
+            if self.should_accept_forward(&info, sender_key).await? {
+                self.accept_forwarded_room_key(&info, &event.sender, sender_key, algorithm, content)
+                    .await
+            } else {
+                warn!(
+                    sender = %event.sender,
+                    %sender_key,
+                    room_id = %content.room_id,
+                    session_id = content.session_id.as_str(),
+                    claimed_sender_key = %content.claimed_sender_key,
+                    "Received a forwarded room key from an unknown device, or \
+                     from a device that the key request recipient doesn't own",
+                );
+
+                Ok(None)
+            }
         } else {
             warn!(
                 sender = %event.sender,

programming_language: Rust
repo_url: https://github.com/matrix-org/matrix-rust-sdk
commit_details: repo_url: https://github.com/matrix-org/matrix-rust-sdk
author: Damir Jelić
author_date: 2022-08-02 13:47:34+02:00
msg: test(crypto): Test that we reject forwarded room keys from other users
repository: repo_url: https://github.com/matrix-org/matrix-rust-sdk
repo_name: matrix-org/matrix-rust-sdk
description: Matrix Client-Server SDK for Rust
repo_language: Rust
owner: matrix-org
file_changes: filename: machine.rs
change_type: ModificationType.MODIFY
diff: @@ -1148,6 +1148,7 @@ mod tests {
     }
 
     async fn machines_for_key_share(
+        other_machine_owner: &UserId,
         create_sessions: bool,
     ) -> (GossipMachine, Account, OutboundGroupSession, GossipMachine) {
         let alice_machine = get_machine().await;
@@ -1157,12 +1158,14 @@ mod tests {
         };
         let alice_device = ReadOnlyDevice::from_account(alice_machine.store.account()).await;
 
-        let bob_machine = test_gossip_machine(alice_id());
+        let bob_machine = test_gossip_machine(other_machine_owner);
         let bob_device = ReadOnlyDevice::from_account(bob_machine.store.account()).await;
 
         // We need a trusted device, otherwise we won't request keys
+        let second_device = ReadOnlyDevice::from_account(&alice_2_account()).await;
+        second_device.set_trust_state(LocalTrust::Verified);
         bob_device.set_trust_state(LocalTrust::Verified);
-        alice_machine.store.save_devices(&[bob_device]).await.unwrap();
+        alice_machine.store.save_devices(&[bob_device, second_device]).await.unwrap();
         bob_machine.store.save_devices(&[alice_device.clone()]).await.unwrap();
 
         if create_sessions {
@@ -1535,7 +1538,7 @@ mod tests {
     #[async_test]
     async fn key_share_cycle() {
         let (alice_machine, alice_account, group_session, bob_machine) =
-            machines_for_key_share(true).await;
+            machines_for_key_share(alice_id(), true).await;
 
         // Get the request and convert it into a event.
         let requests = alice_machine.outgoing_to_device_requests().await.unwrap();
@@ -1599,6 +1602,59 @@ mod tests {
         assert_eq!(session.session_id(), group_session.session_id())
     }
 
+    #[async_test]
+    async fn reject_forward_from_another_user() {
+        let (alice_machine, alice_account, group_session, bob_machine) =
+            machines_for_key_share(bob_id(), true).await;
+
+        // Get the request and convert it into a event.
+        let requests = alice_machine.outgoing_to_device_requests().await.unwrap();
+        let request = &requests[0];
+        let event = request_to_event(alice_id(), alice_id(), request);
+
+        alice_machine.mark_outgoing_request_as_sent(&request.request_id).await.unwrap();
+
+        // Bob doesn't have any outgoing requests.
+        assert!(bob_machine.outgoing_requests.is_empty());
+
+        // Receive the room key request from alice.
+        bob_machine.receive_incoming_key_request(&event);
+        bob_machine.collect_incoming_key_requests().await.unwrap();
+        // Now bob does have an outgoing request.
+        assert!(!bob_machine.outgoing_requests.is_empty());
+
+        // Get the request and convert it to a encrypted to-device event.
+        let requests = bob_machine.outgoing_to_device_requests().await.unwrap();
+        let request = &requests[0];
+
+        let event: EncryptedToDeviceEvent = request_to_event(alice_id(), bob_id(), request);
+        bob_machine.mark_outgoing_request_as_sent(&request.request_id).await.unwrap();
+
+        // Check that alice doesn't have the session.
+        assert!(alice_machine
+            .store
+            .get_inbound_group_session(
+                room_id(),
+                &bob_machine.store.account().identity_keys().curve25519.to_base64(),
+                group_session.session_id()
+            )
+            .await
+            .unwrap()
+            .is_none());
+
+        let decrypted = alice_account.decrypt_to_device_event(&event).await.unwrap();
+        if let AnyDecryptedOlmEvent::ForwardedRoomKey(e) = decrypted.result.event {
+            let session = alice_machine
+                .receive_forwarded_room_key(decrypted.result.sender_key, &e)
+                .await
+                .unwrap();
+
+            assert!(session.is_none(), "We should not receive a room key from another user");
+        } else {
+            panic!("Invalid decrypted event type");
+        }
+    }
+
     #[async_test]
     async fn secret_share_cycle() {
         let alice_machine = get_machine().await;
@@ -1672,7 +1728,7 @@ mod tests {
     #[async_test]
     async fn key_share_cycle_without_session() {
         let (alice_machine, alice_account, group_session, bob_machine) =
-            machines_for_key_share(false).await;
+            machines_for_key_share(alice_id(), false).await;
 
         // Get the request and convert it into a event.
         let requests = alice_machine.outgoing_to_device_requests().await.unwrap();

programming_language: Rust