Add a dehydrated flag to device_keys of dehydrated devices (#3164)

Signed-off-by: Hubert Chathi <hubert@uhoreg.ca>
Co-authored-by: Damir Jelić <poljar@termina.org.uk>
This commit is contained in:
Hubert Chathi
2024-03-19 10:33:13 -04:00
committed by GitHub
parent c6da40cf55
commit 1e35188aec
8 changed files with 57 additions and 4 deletions

1
Cargo.lock generated
View File

@@ -3115,6 +3115,7 @@ dependencies = [
"http",
"indoc",
"itertools 0.12.1",
"js_option",
"matrix-sdk-common",
"matrix-sdk-qrcode",
"matrix-sdk-test",

View File

@@ -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(),

View File

@@ -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.

View File

@@ -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 }

View File

@@ -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]

View File

@@ -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.

View File

@@ -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<IdentityKeys>,
/// 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),

View File

@@ -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<bool>,
/// 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<EventEncryptionAlgorithm>,
pub keys: BTreeMap<OwnedDeviceKeyId, String>,
#[serde(default, skip_serializing_if = "JsOption::is_undefined")]
pub dehydrated: JsOption<bool>,
pub signatures: Signatures,
#[serde(default, skip_serializing_if = "UnsignedDeviceInfo::is_empty")]
pub unsigned: UnsignedDeviceInfo,
@@ -216,6 +224,7 @@ impl TryFrom<DeviceKeyHelper> 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<DeviceKeys> for DeviceKeyHelper {
device_id: value.device_id,
algorithms: value.algorithms,
keys,
dehydrated: value.dehydrated,
signatures: value.signatures,
unsigned: value.unsigned,
other: value.other,