From 1e35188aec90f2abe39999258a394ecda8a6318e Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Tue, 19 Mar 2024 10:33:13 -0400 Subject: [PATCH] Add a dehydrated flag to device_keys of dehydrated devices (#3164) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hubert Chathi Co-authored-by: Damir Jelić --- Cargo.lock | 1 + bindings/matrix-sdk-crypto-ffi/src/lib.rs | 1 + crates/matrix-sdk-crypto/CHANGELOG.md | 3 ++ crates/matrix-sdk-crypto/Cargo.toml | 1 + .../src/dehydrated_devices.rs | 12 ++++++-- .../src/identities/device.rs | 5 ++++ crates/matrix-sdk-crypto/src/olm/account.rs | 28 +++++++++++++++++-- .../src/types/device_keys.rs | 10 +++++++ 8 files changed, 57 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17f0edaf4..880825f6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3115,6 +3115,7 @@ dependencies = [ "http", "indoc", "itertools 0.12.1", + "js_option", "matrix-sdk-common", "matrix-sdk-qrcode", "matrix-sdk-test", diff --git a/bindings/matrix-sdk-crypto-ffi/src/lib.rs b/bindings/matrix-sdk-crypto-ffi/src/lib.rs index fe6ffb705..dc48c07f5 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/lib.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/lib.rs @@ -243,6 +243,7 @@ async fn migrate_data( user_id: parse_user_id(&data.account.user_id)?, device_id: device_id.clone(), pickle, + dehydrated: false, // dehydrated devices are never involved in migration shared: data.account.shared, uploaded_signed_key_count: data.account.uploaded_signed_key_count as u64, creation_local_time: MilliSecondsSinceUnixEpoch::now(), diff --git a/crates/matrix-sdk-crypto/CHANGELOG.md b/crates/matrix-sdk-crypto/CHANGELOG.md index 722489f9e..7e3b3abfe 100644 --- a/crates/matrix-sdk-crypto/CHANGELOG.md +++ b/crates/matrix-sdk-crypto/CHANGELOG.md @@ -14,6 +14,9 @@ Breaking changes: - Move `OlmMachine::export_room_keys` to `matrix_sdk_crypto::store::Store`. (Call it with `olm_machine.store().export_room_keys(...)`.) +- Add new `dehydrated` property to `olm::account::PickledAccount`. + ([#3164](https://github.com/matrix-org/matrix-rust-sdk/pull/3164)) + Additions: - When Olm message decryption fails, report the error code(s) from the failure. diff --git a/crates/matrix-sdk-crypto/Cargo.toml b/crates/matrix-sdk-crypto/Cargo.toml index 16def859c..0e3f77323 100644 --- a/crates/matrix-sdk-crypto/Cargo.toml +++ b/crates/matrix-sdk-crypto/Cargo.toml @@ -43,6 +43,7 @@ hkdf = "0.12.3" hmac = "0.12.1" http = { workspace = true, optional = true } # feature = testing only itertools = { workspace = true } +js_option = "0.1.1" matrix-sdk-qrcode = { workspace = true, optional = true } matrix-sdk-common = { workspace = true } pbkdf2 = { version = "0.12.2", default-features = false } diff --git a/crates/matrix-sdk-crypto/src/dehydrated_devices.rs b/crates/matrix-sdk-crypto/src/dehydrated_devices.rs index 4e4402415..dad7938c7 100644 --- a/crates/matrix-sdk-crypto/src/dehydrated_devices.rs +++ b/crates/matrix-sdk-crypto/src/dehydrated_devices.rs @@ -95,7 +95,7 @@ impl DehydratedDevices { let user_id = self.inner.user_id(); let user_identity = self.inner.store().private_identity(); - let account = Account::new(user_id); + let account = Account::new_dehydrated(user_id); let store = Arc::new(CryptoStoreWrapper::new(user_id, MemoryStore::new())); let verification_machine = VerificationMachine::new( @@ -378,6 +378,7 @@ fn expand_pickle_key(key: &[u8; 32], device_id: &DeviceId) -> Box<[u8; 32]> { mod tests { use std::{collections::BTreeMap, iter}; + use js_option::JsOption; use matrix_sdk_test::async_test; use ruma::{ api::client::keys::get_keys::v3::Response as KeysQueryResponse, assign, @@ -390,7 +391,7 @@ mod tests { create_session, get_prepared_machine_test_helper, to_device_requests_to_content, }, olm::OutboundGroupSession, - types::events::ToDeviceEvent, + types::{events::ToDeviceEvent, DeviceKeys as DeviceKeysType}, utilities::json_convert, EncryptionSettings, OlmMachine, }; @@ -477,6 +478,13 @@ mod tests { !request.fallback_keys.is_empty(), "The dehydrated device creation request should contain some fallback keys" ); + + let device_keys: DeviceKeysType = request.device_keys.deserialize_as().unwrap(); + assert_eq!( + device_keys.dehydrated, + JsOption::Some(true), + "The device keys of the dehydrated device should be marked as dehydrated." + ); } #[async_test] diff --git a/crates/matrix-sdk-crypto/src/identities/device.rs b/crates/matrix-sdk-crypto/src/identities/device.rs index cdaebf021..8fc0cc763 100644 --- a/crates/matrix-sdk-crypto/src/identities/device.rs +++ b/crates/matrix-sdk-crypto/src/identities/device.rs @@ -481,6 +481,11 @@ impl Device { Ok(raw_encrypted) } + + /// Whether or not the device is a dehydrated device. + pub fn is_dehydrated(&self) -> bool { + self.inner.inner.dehydrated.unwrap_or(false) + } } /// A read only view over all devices belonging to a user. diff --git a/crates/matrix-sdk-crypto/src/olm/account.rs b/crates/matrix-sdk-crypto/src/olm/account.rs index 7d8708db6..036367d2d 100644 --- a/crates/matrix-sdk-crypto/src/olm/account.rs +++ b/crates/matrix-sdk-crypto/src/olm/account.rs @@ -20,6 +20,7 @@ use std::{ time::Duration, }; +use js_option::JsOption; use ruma::{ api::client::{ dehydrated_device::{DehydratedDeviceData, DehydratedDeviceV1}, @@ -162,6 +163,8 @@ pub struct StaticAccountData { pub device_id: OwnedDeviceId, /// The associated identity keys. pub identity_keys: Arc, + /// Whether the account is for a dehydrated device. + pub dehydrated: bool, // The creation time of the account in milliseconds since epoch. creation_local_time: MilliSecondsSinceUnixEpoch, } @@ -282,13 +285,17 @@ impl StaticAccountData { ), ]); - DeviceKeys::new( + let mut ret = DeviceKeys::new( (*self.user_id).to_owned(), (*self.device_id).to_owned(), Self::ALGORITHMS.iter().map(|a| (**a).clone()).collect(), keys, Default::default(), - ) + ); + if self.dehydrated { + ret.dehydrated = JsOption::Some(true); + } + ret } /// Get the user id of the owner of the account. @@ -361,6 +368,9 @@ pub struct PickledAccount { pub pickle: AccountPickle, /// Was the account shared. pub shared: bool, + /// Whether this is for a dehydrated device + #[serde(default)] + pub dehydrated: bool, /// The number of uploaded one-time keys we have on the server. pub uploaded_signed_key_count: u64, /// The local time creation of this account (milliseconds since epoch), used @@ -411,6 +421,7 @@ impl Account { user_id: user_id.into(), device_id: device_id.into(), identity_keys: Arc::new(identity_keys), + dehydrated: false, creation_local_time: MilliSecondsSinceUnixEpoch::now(), }, inner: Box::new(account), @@ -437,6 +448,17 @@ impl Account { Self::new_helper(account, user_id, &device_id) } + /// Create a new random Olm Account for a dehydrated device + pub fn new_dehydrated(user_id: &UserId) -> Self { + let account = InnerAccount::new(); + let device_id: OwnedDeviceId = + base64_encode(account.identity_keys().curve25519.as_bytes()).into(); + + let mut ret = Self::new_helper(account, user_id, &device_id); + ret.static_data.dehydrated = true; + ret + } + /// Get the immutable data for this account. pub fn static_data(&self) -> &StaticAccountData { &self.static_data @@ -650,6 +672,7 @@ impl Account { device_id: self.device_id().to_owned(), pickle, shared: self.shared(), + dehydrated: self.static_data.dehydrated, uploaded_signed_key_count: self.uploaded_key_count(), creation_local_time: self.static_data.creation_local_time, fallback_key_creation_timestamp: self.fallback_creation_timestamp, @@ -704,6 +727,7 @@ impl Account { user_id: (*pickle.user_id).into(), device_id: (*pickle.device_id).into(), identity_keys: Arc::new(identity_keys), + dehydrated: pickle.dehydrated, creation_local_time: pickle.creation_local_time, }, inner: Box::new(account), diff --git a/crates/matrix-sdk-crypto/src/types/device_keys.rs b/crates/matrix-sdk-crypto/src/types/device_keys.rs index c4bdd87c5..0689ef193 100644 --- a/crates/matrix-sdk-crypto/src/types/device_keys.rs +++ b/crates/matrix-sdk-crypto/src/types/device_keys.rs @@ -20,6 +20,7 @@ use std::collections::BTreeMap; +use js_option::JsOption; use ruma::{ serde::Raw, DeviceKeyAlgorithm, DeviceKeyId, OwnedDeviceId, OwnedDeviceKeyId, OwnedUserId, }; @@ -52,6 +53,10 @@ pub struct DeviceKeys { /// Signatures for the device key object. pub signatures: Signatures, + /// Whether the device is a dehydrated device or not + #[serde(default, skip_serializing_if = "JsOption::is_undefined")] + pub dehydrated: JsOption, + /// Additional data added to the device key information by intermediate /// servers, and not covered by the signatures. #[serde(default, skip_serializing_if = "UnsignedDeviceInfo::is_empty")] @@ -77,6 +82,7 @@ impl DeviceKeys { algorithms, keys, signatures, + dehydrated: JsOption::Undefined, unsigned: Default::default(), other: BTreeMap::new(), } @@ -182,6 +188,8 @@ struct DeviceKeyHelper { pub device_id: OwnedDeviceId, pub algorithms: Vec, pub keys: BTreeMap, + #[serde(default, skip_serializing_if = "JsOption::is_undefined")] + pub dehydrated: JsOption, pub signatures: Signatures, #[serde(default, skip_serializing_if = "UnsignedDeviceInfo::is_empty")] pub unsigned: UnsignedDeviceInfo, @@ -216,6 +224,7 @@ impl TryFrom for DeviceKeys { device_id: value.device_id, algorithms: value.algorithms, keys: keys?, + dehydrated: value.dehydrated, signatures: value.signatures, unsigned: value.unsigned, other: value.other, @@ -233,6 +242,7 @@ impl From for DeviceKeyHelper { device_id: value.device_id, algorithms: value.algorithms, keys, + dehydrated: value.dehydrated, signatures: value.signatures, unsigned: value.unsigned, other: value.other,