From 8fc4e9f1a3bfbbbe36d1d12069b5d74b90e296fa Mon Sep 17 00:00:00 2001 From: Aaron Date: Wed, 16 Aug 2023 23:44:15 +0200 Subject: [PATCH 1/3] Merge branch 'main' into dev3 --- presage-cli/src/main.rs | 2 +- presage-store-sled/src/lib.rs | 163 ++++++++++++++++++++++++---------- presage/Cargo.toml | 4 +- presage/src/lib.rs | 1 + presage/src/manager.rs | 105 ++++++++++++++++++---- presage/src/store.rs | 26 ++++-- 6 files changed, 228 insertions(+), 73 deletions(-) diff --git a/presage-cli/src/main.rs b/presage-cli/src/main.rs index 222f18eac..93cab3162 100644 --- a/presage-cli/src/main.rs +++ b/presage-cli/src/main.rs @@ -126,7 +126,7 @@ enum Cmd { group( ArgGroup::new("list-messages") .required(true) - .args(&["recipient-uuid", "group-master-key"]) + .args(&["recipient_uuid", "group_master_key"]) ) )] ListMessages { diff --git a/presage-store-sled/src/lib.rs b/presage-store-sled/src/lib.rs index 5c5a7eb60..6384a0eaa 100644 --- a/presage-store-sled/src/lib.rs +++ b/presage-store-sled/src/lib.rs @@ -9,25 +9,24 @@ use async_trait::async_trait; use log::{debug, error, trace, warn}; #[cfg(feature = "encryption")] use matrix_sdk_store_encryption::StoreCipher; -use presage::{ - libsignal_service::{ - self, - groups_v2::{decrypt_group, Group}, - models::Contact, - prelude::{ - protocol::{ - Context, Direction, IdentityKey, IdentityKeyPair, IdentityKeyStore, PreKeyId, - PreKeyRecord, PreKeyStore, ProtocolAddress, ProtocolStore, SenderKeyRecord, - SenderKeyStore, SessionRecord, SessionStore, SessionStoreExt, SignalProtocolError, - SignedPreKeyId, SignedPreKeyRecord, SignedPreKeyStore, - }, - Content, ProfileKey, Uuid, +use presage::libsignal_service::{ + self, + groups_v2::Group, + models::Contact, + prelude::{ + protocol::{ + Context, Direction, GenericSignedPreKey, IdentityKey, IdentityKeyPair, + IdentityKeyStore, KyberPreKeyId, KyberPreKeyRecord, KyberPreKeyStore, PreKeyId, + PreKeyRecord, PreKeyStore, ProtocolAddress, ProtocolStore, SenderKeyRecord, + SenderKeyStore, SessionRecord, SessionStore, SessionStoreExt, SignalProtocolError, + SignedPreKeyId, SignedPreKeyRecord, SignedPreKeyStore, }, - push_service::DEFAULT_DEVICE_ID, - Profile, ServiceAddress, + Content, ProfileKey, Uuid, }, ThreadMetadata, prelude::proto, + push_service::DEFAULT_DEVICE_ID, + Profile, ServiceAddress, }; use prost::Message; use protobuf::ContentProto; @@ -49,6 +48,7 @@ const SLED_TREE_PRE_KEYS: &str = "pre_keys"; const SLED_TREE_SENDER_KEYS: &str = "sender_keys"; const SLED_TREE_SESSIONS: &str = "sessions"; const SLED_TREE_SIGNED_PRE_KEYS: &str = "signed_pre_keys"; +const SLED_TREE_KYBER_PRE_KEYS: &str = "kyber_pre_keys"; const SLED_TREE_STATE: &str = "state"; const SLED_TREE_THREADS_PREFIX: &str = "threads"; const SLED_TREE_THREADS_METADATA: &str = "threads_metadata"; @@ -56,6 +56,7 @@ const SLED_TREE_PROFILES: &str = "profiles"; const SLED_TREE_PROFILE_KEYS: &str = "profile_keys"; const SLED_KEY_NEXT_SIGNED_PRE_KEY_ID: &str = "next_signed_pre_key_id"; +const SLED_KEY_NEXT_PQ_PRE_KEY_ID: &str = "next_pq_pre_key_id"; const SLED_KEY_PRE_KEYS_OFFSET_ID: &str = "pre_keys_offset_id"; const SLED_KEY_REGISTRATION: &str = "registration"; const SLED_KEY_SCHEMA_VERSION: &str = "schema_version"; @@ -88,13 +89,13 @@ pub enum SchemaVersion { #[default] V0 = 0, V1 = 1, - /// the current version V2 = 2, + V3 = 3, } impl SchemaVersion { fn current() -> SchemaVersion { - Self::V2 + Self::V3 } /// return an iterator on all the necessary migration steps from another version @@ -106,6 +107,7 @@ impl SchemaVersion { .map(|i| match i { 1 => SchemaVersion::V1, 2 => SchemaVersion::V2, + 3 => SchemaVersion::V3, _ => unreachable!("oops, this not supposed to happen!"), }) } @@ -300,6 +302,12 @@ fn migrate( db.flush()?; } } + SchemaVersion::V3 => { + debug!("migrating from schema v2 to v3: dropping encrypted group cache"); + let db = store.write(); + db.drop_tree(SLED_TREE_GROUPS)?; + db.flush()?; + } _ => return Err(SledStoreError::MigrationConflict), } @@ -421,6 +429,17 @@ impl Store for SledStore { Ok(()) } + fn next_pq_pre_key_id(&self) -> Result { + Ok(self + .get(SLED_TREE_STATE, SLED_KEY_NEXT_PQ_PRE_KEY_ID)? + .unwrap_or(0)) + } + + fn set_next_pq_pre_key_id(&mut self, id: u32) -> Result<(), SledStoreError> { + self.insert(SLED_TREE_STATE, SLED_KEY_NEXT_PQ_PRE_KEY_ID, id)?; + Ok(()) + } + /// Contacts fn clear_contacts(&mut self) -> Result<(), SledStoreError> { @@ -476,24 +495,15 @@ impl Store for SledStore { &self, master_key_bytes: GroupMasterKeyBytes, ) -> Result, SledStoreError> { - let val: Option> = self.get(SLED_TREE_GROUPS, master_key_bytes)?; - match val { - Some(ref v) => { - let encrypted_group = proto::Group::decode(v.as_slice())?; - let group = decrypt_group(&master_key_bytes, encrypted_group) - .map_err(|_| SledStoreError::GroupDecryption)?; - Ok(Some(group)) - } - None => Ok(None), - } + self.get(SLED_TREE_GROUPS, master_key_bytes) } fn save_group( &self, master_key: GroupMasterKeyBytes, - group: proto::Group, + group: &Group, ) -> Result<(), SledStoreError> { - self.insert(SLED_TREE_GROUPS, master_key, group.encode_to_vec())?; + self.insert(SLED_TREE_GROUPS, master_key, group)?; Ok(()) } @@ -513,6 +523,16 @@ impl Store for SledStore { Ok(()) } + fn clear_thread(&mut self, thread: &Thread) -> Result<(), SledStoreError> { + log::trace!("clearing thread {thread}"); + + let db = self.write(); + db.drop_tree(self.messages_thread_tree_name(thread))?; + db.flush()?; + + Ok(()) + } + fn save_message(&mut self, thread: &Thread, message: Content) -> Result<(), SledStoreError> { log::trace!( "storing a message with thread: {thread}, timestamp: {}", @@ -665,26 +685,21 @@ impl Iterator for SledGroupsIter { type Item = Result<(GroupMasterKeyBytes, Group), SledStoreError>; fn next(&mut self) -> Option { - self.iter - .next()? - .map_err(SledStoreError::from) - .and_then(|(master_key_bytes, value)| { - let decrypted_data: Vec = self.cipher.as_ref().map_or_else( + Some(self.iter.next()?.map_err(SledStoreError::from).and_then( + |(group_master_key_bytes, value)| { + let group = self.cipher.as_ref().map_or_else( || serde_json::from_slice(&value).map_err(SledStoreError::from), |c| c.decrypt_value(&value).map_err(SledStoreError::from), )?; - Ok((master_key_bytes, decrypted_data)) - }) - .and_then(|(master_key_bytes, encrypted_group_data)| { - let encrypted_group = proto::Group::decode(encrypted_group_data.as_slice())?; - let master_key: GroupMasterKeyBytes = master_key_bytes[..] - .try_into() - .expect("wrong group master key length"); - let decrypted_group = decrypt_group(&master_key, encrypted_group) - .map_err(|_| SledStoreError::GroupDecryption)?; - Ok((master_key, decrypted_group)) - }) - .into() + Ok(( + group_master_key_bytes + .as_ref() + .try_into() + .map_err(|_| SledStoreError::GroupDecryption)?, + group, + )) + }, + )) } } @@ -764,6 +779,57 @@ impl SignedPreKeyStore for SledStore { } } +#[async_trait(?Send)] +impl KyberPreKeyStore for SledStore { + async fn get_kyber_pre_key( + &self, + kyber_prekey_id: KyberPreKeyId, + _ctx: Context, + ) -> Result { + let buf: Vec = self + .get(SLED_TREE_KYBER_PRE_KEYS, kyber_prekey_id.to_string()) + .ok() + .flatten() + .ok_or(SignalProtocolError::InvalidKyberPreKeyId)?; + KyberPreKeyRecord::deserialize(&buf) + } + + async fn save_kyber_pre_key( + &mut self, + kyber_prekey_id: KyberPreKeyId, + record: &KyberPreKeyRecord, + _ctx: Context, + ) -> Result<(), SignalProtocolError> { + self.insert( + SLED_TREE_KYBER_PRE_KEYS, + kyber_prekey_id.to_string(), + record.serialize()?, + ) + .map_err(|e| { + log::error!("sled error: {}", e); + SignalProtocolError::InvalidState("save_kyber_pre_key", "sled error".into()) + })?; + Ok(()) + } + + async fn mark_kyber_pre_key_used( + &mut self, + kyber_prekey_id: KyberPreKeyId, + _ctx: Context, + ) -> Result<(), SignalProtocolError> { + if self + .remove(SLED_TREE_KYBER_PRE_KEYS, kyber_prekey_id.to_string()) + .map_err(|e| { + log::error!("sled error: {}", e); + SignalProtocolError::InvalidState("mark_kyber_pre_key_used", "sled error".into()) + })? + { + log::trace!("removed kyber pre-key {kyber_prekey_id}"); + } + Ok(()) + } +} + #[async_trait(?Send)] impl SessionStore for SledStore { async fn load_session( @@ -1054,8 +1120,9 @@ mod tests { content::{ContentBody, Metadata}, prelude::{ protocol::{ - self, Direction, IdentityKeyStore, PreKeyRecord, PreKeyStore, SessionRecord, - SessionStore, SignedPreKeyRecord, SignedPreKeyStore, + self, Direction, GenericSignedPreKey, IdentityKeyStore, PreKeyRecord, + PreKeyStore, SessionRecord, SessionStore, SignedPreKeyRecord, + SignedPreKeyStore, }, Uuid, }, diff --git a/presage/Cargo.toml b/presage/Cargo.toml index 00e39b31e..8faac2fee 100644 --- a/presage/Cargo.toml +++ b/presage/Cargo.toml @@ -6,8 +6,8 @@ authors = ["Gabriel FĂ©ron "] edition = "2021" [dependencies] -libsignal-service = { git = "https://github.com/whisperfish/libsignal-service-rs", rev = "c2f70ef" } -libsignal-service-hyper = { git = "https://github.com/whisperfish/libsignal-service-rs", rev = "c2f70ef" } +libsignal-service = { git = "https://github.com/whisperfish/libsignal-service-rs", rev = "3c65765" } +libsignal-service-hyper = { git = "https://github.com/whisperfish/libsignal-service-rs", rev = "3c65765" } base64 = "0.12" futures = "0.3" diff --git a/presage/src/lib.rs b/presage/src/lib.rs index 897652598..bb989f124 100644 --- a/presage/src/lib.rs +++ b/presage/src/lib.rs @@ -21,6 +21,7 @@ pub mod prelude { self, Content, ContentBody, DataMessage, GroupContext, GroupContextV2, GroupType, Metadata, SyncMessage, }, + groups_v2::{AccessControl, Group, GroupChange, PendingMember, RequestingMember, Timer}, models::Contact, prelude::{ phonenumber::{self, PhoneNumber}, diff --git a/presage/src/manager.rs b/presage/src/manager.rs index 44041ada9..e37519d05 100644 --- a/presage/src/manager.rs +++ b/presage/src/manager.rs @@ -17,7 +17,7 @@ use libsignal_service::{ cipher, configuration::{ServiceConfiguration, SignalServers, SignalingKey}, content::{ContentBody, DataMessage, DataMessageFlags, Metadata, SyncMessage}, - groups_v2::{Group, GroupsManager, InMemoryCredentialsCache}, + groups_v2::{decrypt_group, Group, GroupsManager, InMemoryCredentialsCache}, messagepipe::ServiceCredentials, models::Contact, prelude::{ @@ -35,8 +35,8 @@ use libsignal_service::{ VerificationCodeResponse, }, push_service::{ - AccountAttributes, DeviceCapabilities, DeviceId, ProfileKeyExt, ServiceError, ServiceIds, - WhoAmIResponse, DEFAULT_DEVICE_ID, + AccountAttributes, DeviceCapabilities, DeviceId, ServiceError, ServiceIds, WhoAmIResponse, + DEFAULT_DEVICE_ID, }, receiver::MessageReceiver, sender::{AttachmentSpec, AttachmentUploadError}, @@ -309,6 +309,7 @@ impl Manager { phone_number, device_id: DeviceId { device_id }, registration_id, + pni_registration_id, profile_key, service_ids, aci_private_key, @@ -331,7 +332,7 @@ impl Manager { password, device_id: Some(device_id), registration_id, - pni_registration_id: None, + pni_registration_id: Some(pni_registration_id), aci_public_key, aci_private_key, pni_public_key: Some(pni_public_key), @@ -437,7 +438,7 @@ impl Manager { fetches_messages: true, pin: None, registration_lock: None, - unidentified_access_key: Some(profile_key.derive_access_key()), + unidentified_access_key: Some(profile_key.derive_access_key().to_vec()), unrestricted_unidentified_access: false, // TODO: make this configurable? discoverable_by_phone_number: true, capabilities: DeviceCapabilities { @@ -524,12 +525,13 @@ impl Manager { let mut account_manager = AccountManager::new(self.push_service()?, Some(self.state.profile_key)); - let (pre_keys_offset_id, next_signed_pre_key_id) = account_manager + let (pre_keys_offset_id, next_signed_pre_key_id, next_pq_pre_key_id) = account_manager .update_pre_key_bundle( &mut self.config_store.clone(), &mut self.rng, self.config_store.pre_keys_offset_id()?, self.config_store.next_signed_pre_key_id()?, + self.config_store.next_pq_pre_key_id()?, true, ) .await?; @@ -538,6 +540,8 @@ impl Manager { .set_pre_keys_offset_id(pre_keys_offset_id)?; self.config_store .set_next_signed_pre_key_id(next_signed_pre_key_id)?; + self.config_store + .set_next_pq_pre_key_id(next_pq_pre_key_id)?; trace!("registered pre keys"); Ok(()) @@ -570,7 +574,7 @@ impl Manager { fetches_messages: true, pin: None, registration_lock: None, - unidentified_access_key: Some(self.state.profile_key.derive_access_key()), + unidentified_access_key: Some(self.state.profile_key.derive_access_key().to_vec()), unrestricted_unidentified_access: false, discoverable_by_phone_number: true, capabilities: DeviceCapabilities { @@ -1002,6 +1006,9 @@ impl Manager { /// Sends a messages to the provided [ServiceAddress]. /// The timestamp should be set to now and is used by Signal mobile apps /// to order messages later, and apply reactions. + /// + /// This method will automatically update the [DataMessage::expiration_timer] if it is set to + /// [None] such that the chat will keep the current expire timer. pub async fn send_message( &mut self, recipient_addr: impl Into, @@ -1012,14 +1019,31 @@ impl Manager { let online_only = false; let recipient = recipient_addr.into(); - let content_body: ContentBody = message.into(); + let mut content_body: ContentBody = message.into(); + + // Only update the expiration timer if it is not set. + match content_body { + ContentBody::DataMessage(DataMessage { + expire_timer: ref mut timer, + .. + }) if timer.is_none() => { + // Set the expire timer to None for errors. + let store_expire_timer = self + .config_store + .expire_timer(&Thread::Contact(recipient.uuid)) + .unwrap_or_default(); + + *timer = store_expire_timer; + } + _ => {} + } let sender_certificate = self.sender_certificate().await?; let unidentified_access = self.config_store .profile_key(&recipient.uuid)? .map(|profile_key| UnidentifiedAccess { - key: profile_key.derive_access_key(), + key: profile_key.derive_access_key().to_vec(), certificate: sender_certificate.clone(), }); @@ -1067,13 +1091,36 @@ impl Manager { Ok(upload.await) } - /// Sends one message in a group (v2). + /// Sends one message in a group (v2). The `master_key_bytes` is required to have 32 elements. + /// + /// This method will automatically update the [DataMessage::expiration_timer] if it is set to + /// [None] such that the chat will keep the current expire timer. pub async fn send_message_to_group( &mut self, master_key_bytes: &[u8], - message: DataMessage, + mut message: DataMessage, timestamp: u64, ) -> Result<(), Error> { + // Only update the expiration timer if it is not set. + match message { + DataMessage { + expire_timer: ref mut timer, + .. + } if timer.is_none() => { + // Set the expire timer to None for errors. + let store_expire_timer = self + .config_store + .expire_timer(&Thread::Group( + master_key_bytes + .try_into() + .expect("Master key bytes to be of size 32."), + )) + .unwrap_or_default(); + + *timer = store_expire_timer; + } + _ => {} + } let mut sender = self.new_message_sender().await?; let mut groups_manager = self.groups_manager()?; @@ -1092,7 +1139,7 @@ impl Manager { self.config_store .profile_key(&member.uuid)? .map(|profile_key| UnidentifiedAccess { - key: profile_key.derive_access_key(), + key: profile_key.derive_access_key().to_vec(), certificate: sender_certificate.clone(), }); recipients.push((member.uuid.into(), unidentified_access)); @@ -1422,6 +1469,29 @@ impl Manager { Ok(()) } + /// Returns the title of a thread (contact or group). + pub async fn thread_title(&self, thread: &Thread) -> Result> { + match thread { + Thread::Contact(uuid) => { + let contact = match self.contact_by_id(uuid) { + Ok(contact) => contact, + Err(e) => { + log::info!("Error getting contact by id: {}, {:?}", e, uuid); + None + } + }; + Ok(match contact { + Some(contact) => contact.name, + None => uuid.to_string(), + }) + } + Thread::Group(id) => match self.group(id)? { + Some(group) => Ok(group.title), + None => Ok("".to_string()), + }, + } + } + #[deprecated = "use Manager::contact_by_id"] pub fn get_contacts( &self, @@ -1451,7 +1521,7 @@ async fn upsert_group( master_key_bytes: &[u8], revision: &u32, ) -> Result, Error> { - let save_group = match config_store.group(master_key_bytes.try_into()?) { + let upsert_group = match config_store.group(master_key_bytes.try_into()?) { Ok(Some(group)) => { log::debug!("loaded group from local db {}", group.title); group.revision < *revision @@ -1463,11 +1533,12 @@ async fn upsert_group( } }; - if save_group { - log::debug!("fetching group"); + if upsert_group { + log::debug!("fetching and saving group"); match groups_manager.fetch_encrypted_group(master_key_bytes).await { - Ok(group) => { - if let Err(e) = config_store.save_group(master_key_bytes.try_into()?, group) { + Ok(encrypted_group) => { + let group = decrypt_group(&master_key_bytes, encrypted_group)?; + if let Err(e) = config_store.save_group(master_key_bytes.try_into()?, &group) { log::error!("failed to save group {master_key_bytes:?}: {e}",); } } diff --git a/presage/src/store.rs b/presage/src/store.rs index 9ce2ed5b1..d03f92363 100644 --- a/presage/src/store.rs +++ b/presage/src/store.rs @@ -49,13 +49,20 @@ pub trait Store: ProtocolStore + SenderKeyStore + SessionStoreExt + Sync + Clone fn next_signed_pre_key_id(&self) -> Result; + fn next_pq_pre_key_id(&self) -> Result; + fn set_next_signed_pre_key_id(&mut self, id: u32) -> Result<(), Self::Error>; + fn set_next_pq_pre_key_id(&mut self, id: u32) -> Result<(), Self::Error>; + /// Messages // Clear all stored messages. fn clear_messages(&mut self) -> Result<(), Self::Error>; + // Clear the messages in a thread. + fn clear_thread(&mut self, thread: &Thread) -> Result<(), Self::Error>; + /// Save a message in a [Thread] identified by a timestamp. fn save_message(&mut self, thread: &Thread, message: Content) -> Result<(), Self::Error>; @@ -73,6 +80,18 @@ pub trait Store: ProtocolStore + SenderKeyStore + SessionStoreExt + Sync + Clone range: impl RangeBounds, ) -> Result; + /// Get the expire timer from a [Thread], which corresponds to either [Contact::expire_timer] + /// or [Group::disappearing_messages_timer]. + fn expire_timer(&self, thread: &Thread) -> Result, Self::Error> { + match thread { + Thread::Contact(uuid) => Ok(self.contact_by_id(*uuid)?.map(|c| c.expire_timer)), + Thread::Group(key) => Ok(self + .group(*key)? + .and_then(|g| g.disappearing_messages_timer) + .map(|t| t.duration)), + } + } + /// Contacts /// Clear all saved synchronized contact data @@ -95,11 +114,8 @@ pub trait Store: ProtocolStore + SenderKeyStore + SessionStoreExt + Sync + Clone fn clear_groups(&mut self) -> Result<(), Self::Error>; /// Save a group in the cache - fn save_group( - &self, - master_key: GroupMasterKeyBytes, - group: crate::prelude::proto::Group, - ) -> Result<(), Self::Error>; + fn save_group(&self, master_key: GroupMasterKeyBytes, group: &Group) + -> Result<(), Self::Error>; /// Get an iterator on all cached groups fn groups(&self) -> Result; From 1804942bc2aa7255cb6ec8b8ed4ceef41b4143e9 Mon Sep 17 00:00:00 2001 From: Aaron Date: Wed, 16 Aug 2023 23:48:31 +0200 Subject: [PATCH 2/3] more error handling --- presage/src/manager.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/presage/src/manager.rs b/presage/src/manager.rs index e37519d05..2dc84455c 100644 --- a/presage/src/manager.rs +++ b/presage/src/manager.rs @@ -210,12 +210,19 @@ impl Manager { .request_voice_verification_code(captcha, None) .await? } else { - provisioning_manager + match provisioning_manager .request_sms_verification_code(captcha, None) - .await? + .await{ + Ok(verification_code_response) => verification_code_response, + Err(e) => { + error!("failed to request sms verification code: {}", e); + return Err(Error::UnknownError(format!("failed to request sms verification code: {}", e))); + } + } }; if let VerificationCodeResponse::CaptchaRequired = verification_code_response { + error!("captcha required"); return Err(Error::CaptchaRequired); } From b7e47cc75a1ad103c61b56adb5087d67752ee3ce Mon Sep 17 00:00:00 2001 From: Aaron Date: Wed, 16 Aug 2023 23:48:54 +0200 Subject: [PATCH 3/3] more error handling2 --- presage-store-sled/src/lib.rs | 4 ++-- presage/src/errors.rs | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/presage-store-sled/src/lib.rs b/presage-store-sled/src/lib.rs index 6384a0eaa..bf7fd1cdd 100644 --- a/presage-store-sled/src/lib.rs +++ b/presage-store-sled/src/lib.rs @@ -23,11 +23,11 @@ use presage::libsignal_service::{ }, Content, ProfileKey, Uuid, }, - ThreadMetadata, - prelude::proto, push_service::DEFAULT_DEVICE_ID, Profile, ServiceAddress, }; +use presage::ThreadMetadata; + use prost::Message; use protobuf::ContentProto; use serde::{de::DeserializeOwned, Deserialize, Serialize}; diff --git a/presage/src/errors.rs b/presage/src/errors.rs index 115e22853..21c7a535a 100644 --- a/presage/src/errors.rs +++ b/presage/src/errors.rs @@ -62,6 +62,10 @@ pub enum Error { Timeout(#[from] tokio::time::error::Elapsed), #[error("store error: {0}")] Store(S), + #[error("unknown error: {0}")] + UnknownError(String), + #[error("verification error")] + VerificationCodeRequestError, } impl From for Error {