Skip to content

Commit c5f4168

Browse files
authored
Merge pull request #3442 from matrix-org/rav/no_device_update_on_unchanged
crypto: emit no `identities_stream` items on no-op changes
2 parents 7ea804b + 818778c commit c5f4168

File tree

4 files changed

+97
-24
lines changed

4 files changed

+97
-24
lines changed

crates/matrix-sdk-crypto/CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ Security fixes:
44

55
- Don't log the private part of the backup key, introduced in [#71136e4](https://github.com/matrix-org/matrix-rust-sdk/commit/71136e44c03c79f80d6d1a2446673bc4d53a2067).
66

7-
Changed:
7+
Changes:
8+
9+
- Avoid emitting entries from `identities_stream_raw` and `devices_stream` when
10+
we receive a `/keys/query` response which shows that no devices changed.
11+
([#3442](https://github.com/matrix-org/matrix-rust-sdk/pull/3442))
812

913
- Fallback keys are rotated in a time-based manner, instead of waiting for the
1014
server to tell us that a fallback key got used.

crates/matrix-sdk-crypto/src/identities/device.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -848,7 +848,12 @@ impl ReadOnlyDevice {
848848
}
849849

850850
/// Update a device with a new device keys struct.
851-
pub(crate) fn update_device(&mut self, device_keys: &DeviceKeys) -> Result<(), SignatureError> {
851+
///
852+
/// Returns `true` if any changes were made to the data.
853+
pub(crate) fn update_device(
854+
&mut self,
855+
device_keys: &DeviceKeys,
856+
) -> Result<bool, SignatureError> {
852857
self.verify_device_keys(device_keys)?;
853858

854859
if self.user_id() != device_keys.user_id || self.device_id() != device_keys.device_id {
@@ -858,10 +863,13 @@ impl ReadOnlyDevice {
858863
self.ed25519_key().map(Box::new),
859864
device_keys.ed25519_key().map(Box::new),
860865
))
861-
} else {
866+
} else if self.inner.as_ref() != device_keys {
862867
self.inner = device_keys.clone().into();
863868

864-
Ok(())
869+
Ok(true)
870+
} else {
871+
// no changes needed
872+
Ok(false)
865873
}
866874
}
867875

@@ -1066,9 +1074,11 @@ pub(crate) mod tests {
10661074

10671075
let mut device_keys = device_keys();
10681076
device_keys.unsigned.device_display_name = Some(display_name.clone());
1069-
device.update_device(&device_keys).unwrap();
1070-
1077+
assert!(device.update_device(&device_keys).unwrap());
10711078
assert_eq!(&display_name, device.display_name().as_ref().unwrap());
1079+
1080+
// A second call to `update_device` with the same data should return `false`.
1081+
assert!(!device.update_device(&device_keys).unwrap());
10721082
}
10731083

10741084
#[test]

crates/matrix-sdk-crypto/src/identities/manager.rs

Lines changed: 75 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -227,17 +227,18 @@ impl IdentityManager {
227227
store.get_readonly_device(&device_keys.user_id, &device_keys.device_id).await?;
228228

229229
if let Some(mut device) = old_device {
230-
if let Err(e) = device.update_device(&device_keys) {
231-
warn!(
232-
user_id = ?device.user_id(),
233-
device_id = ?device.device_id(),
234-
error = ?e,
235-
"Rejecting device update",
236-
);
237-
238-
Ok(DeviceChange::None)
239-
} else {
240-
Ok(DeviceChange::Updated(device))
230+
match device.update_device(&device_keys) {
231+
Err(e) => {
232+
warn!(
233+
user_id = ?device.user_id(),
234+
device_id = ?device.device_id(),
235+
error = ?e,
236+
"Rejecting device update",
237+
);
238+
Ok(DeviceChange::None)
239+
}
240+
Ok(true) => Ok(DeviceChange::Updated(device)),
241+
Ok(false) => Ok(DeviceChange::None),
241242
}
242243
} else {
243244
match ReadOnlyDevice::try_from(&device_keys) {
@@ -1668,6 +1669,67 @@ pub(crate) mod tests {
16681669
assert_eq!(devices.devices().next().unwrap().device_id(), "LVWOVGOXME");
16691670
}
16701671

1672+
#[async_test]
1673+
async fn test_invalid_key_response() {
1674+
let my_user_id = user_id();
1675+
let my_device_id = device_id();
1676+
let manager = manager_test_helper(my_user_id, my_device_id).await;
1677+
1678+
// First of all, populate the store with good data
1679+
let (reqid, _) = manager.build_key_query_for_users(vec![user_id()]);
1680+
let (device_changes, identity_changes) =
1681+
manager.receive_keys_query_response(&reqid, &own_key_query()).await.unwrap();
1682+
assert_eq!(device_changes.new.len(), 1);
1683+
let test_device_id = device_changes.new.first().unwrap().device_id().to_owned();
1684+
use crate::store::Changes;
1685+
let changes =
1686+
Changes { devices: device_changes, identities: identity_changes, ..Changes::default() };
1687+
manager.store.save_changes(changes).await.unwrap();
1688+
1689+
// Now provide an invalid update
1690+
let (reqid, _) = manager.build_key_query_for_users(vec![my_user_id]);
1691+
let response_data = matrix_sdk_test::response_from_file(&json!({
1692+
"device_keys": {
1693+
my_user_id: {
1694+
test_device_id.as_str(): {
1695+
"algorithms": [
1696+
"m.olm.v1.curve25519-aes-sha2",
1697+
],
1698+
"device_id": test_device_id.as_str(),
1699+
"keys": {
1700+
format!("curve25519:{}", test_device_id): "wnip2tbJBJxrFayC88NNJpm61TeSNgYcqBH4T9yEDhU",
1701+
format!("ed25519:{}", test_device_id): "lQ+eshkhgKoo+qp9Qgnj3OX5PBoWMU5M9zbuEevwYqE"
1702+
},
1703+
"signatures": {
1704+
my_user_id: {
1705+
// Not a valid signature.
1706+
format!("ed25519:{}", test_device_id): "imadethisup",
1707+
}
1708+
},
1709+
"user_id": my_user_id,
1710+
}
1711+
}
1712+
}
1713+
}));
1714+
let response =
1715+
ruma::api::client::keys::get_keys::v3::Response::try_from_http_response(response_data)
1716+
.expect("Can't parse the `/keys/upload` response");
1717+
1718+
let (device_changes, identity_changes) =
1719+
manager.receive_keys_query_response(&reqid, &response).await.unwrap();
1720+
1721+
// The result should be empty
1722+
assert_eq!(device_changes.new.len(), 0);
1723+
assert_eq!(device_changes.changed.len(), 0);
1724+
assert_eq!(device_changes.deleted.len(), 0);
1725+
assert_eq!(identity_changes.new.len(), 0);
1726+
1727+
// And the device should not have been updated.
1728+
let device =
1729+
manager.store.get_user_devices(my_user_id).await.unwrap().get(&test_device_id).unwrap();
1730+
assert_eq!(device.algorithms().len(), 2);
1731+
}
1732+
16711733
#[async_test]
16721734
async fn test_devices_stream() {
16731735
let manager = manager_test_helper(user_id(), device_id()).await;
@@ -1723,18 +1785,15 @@ pub(crate) mod tests {
17231785
manager.as_ref().unwrap().build_key_query_for_users(vec![user_id()]);
17241786

17251787
// A second `/keys/query` response with the same result shouldn't fire a change
1726-
// notification: the identity should be unchanged.
1788+
// notification: the identity and device should be unchanged.
17271789
manager
17281790
.as_ref()
17291791
.unwrap()
17301792
.receive_keys_query_response(&new_request_id, &own_key_query())
17311793
.await
17321794
.unwrap();
17331795

1734-
let (identity_update_2, _) = assert_ready!(stream);
1735-
assert_eq!(identity_update_2.new.len(), 0);
1736-
assert_eq!(identity_update_2.changed.len(), 0);
1737-
assert_eq!(identity_update_2.unchanged.len(), 1);
1796+
assert_pending!(stream);
17381797

17391798
// dropping the manager (and hence dropping the store) should close the stream
17401799
manager.take();

crates/matrix-sdk-crypto/src/types/device_keys.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ use super::{EventEncryptionAlgorithm, Signatures};
3939
/// identity keys.
4040
///
4141
/// [device_keys_spec]: https://spec.matrix.org/v1.10/client-server-api/#_matrixclientv3keysupload_devicekeys
42-
#[derive(Clone, Debug, Deserialize, Serialize)]
42+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
4343
#[serde(try_from = "DeviceKeyHelper", into = "DeviceKeyHelper")]
4444
pub struct DeviceKeys {
4545
/// The ID of the user the device belongs to.
@@ -130,7 +130,7 @@ impl DeviceKeys {
130130
}
131131

132132
/// Additional data added to device key information by intermediate servers.
133-
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
133+
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
134134
pub struct UnsignedDeviceInfo {
135135
/// The display name which the user set on the device.
136136
#[serde(skip_serializing_if = "Option::is_none")]

0 commit comments

Comments
 (0)