diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index 379913054..d86af6c67 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -18,6 +18,7 @@ use tracing_futures::Instrument; use zkgroup::profiles::ProfileKey; use crate::content::ContentBody; +use crate::master_key::MasterKey; use crate::pre_keys::{ KyberPreKeyEntity, PreKeyEntity, PreKeysStore, SignedPreKeyEntity, PRE_KEY_BATCH_SIZE, PRE_KEY_MINIMUM, @@ -277,6 +278,7 @@ impl AccountManager { aci_identity_store: &dyn IdentityKeyStore, pni_identity_store: &dyn IdentityKeyStore, credentials: ServiceCredentials, + master_key: Option, ) -> Result<(), ProvisioningError> { let query: HashMap<_, _> = url.query_pairs().collect(); let ephemeral_id = @@ -328,7 +330,7 @@ impl AccountManager { provisioning_code: Some(provisioning_code), read_receipts: None, user_agent: None, - master_key: None, // XXX + master_key: master_key.map(|x| x.into()), }; let cipher = ProvisioningCipher::from_public(pub_key); diff --git a/libsignal-service/src/lib.rs b/libsignal-service/src/lib.rs index a8a806e27..177325328 100644 --- a/libsignal-service/src/lib.rs +++ b/libsignal-service/src/lib.rs @@ -14,6 +14,7 @@ pub mod content; mod digeststream; pub mod envelope; pub mod groups_v2; +pub mod master_key; pub mod messagepipe; pub mod models; pub mod pre_keys; @@ -79,6 +80,7 @@ pub mod prelude { AccessControl, Group, Member, PendingMember, RequestingMember, Timer, }, + master_key::{MasterKey, MasterKeyStore, StorageServiceKey}, proto::{ attachment_pointer::AttachmentIdentifier, sync_message::Contacts, AttachmentPointer, diff --git a/libsignal-service/src/master_key.rs b/libsignal-service/src/master_key.rs new file mode 100644 index 000000000..40e60a27a --- /dev/null +++ b/libsignal-service/src/master_key.rs @@ -0,0 +1,111 @@ +const MASTER_KEY_LEN: usize = 32; +const STORAGE_KEY_LEN: usize = 32; + +#[derive(Debug, PartialEq)] +pub struct MasterKey { + pub inner: [u8; MASTER_KEY_LEN], +} + +impl MasterKey { + pub fn generate() -> Self { + use rand::Rng; + + // Create random bytes + let mut rng = rand::thread_rng(); + let mut inner = [0_u8; MASTER_KEY_LEN]; + rng.fill(&mut inner); + Self { inner } + } + + pub fn from_slice( + slice: &[u8], + ) -> Result { + let inner = slice.try_into()?; + Ok(Self { inner }) + } +} + +impl From for Vec { + fn from(val: MasterKey) -> Self { + val.inner.to_vec() + } +} + +#[derive(Debug, PartialEq)] +pub struct StorageServiceKey { + pub inner: [u8; STORAGE_KEY_LEN], +} + +impl StorageServiceKey { + pub fn from_master_key(master_key: &MasterKey) -> Self { + use hmac::{Hmac, Mac}; + use sha2::Sha256; + + type HmacSha256 = Hmac; + const KEY: &[u8] = b"Storage Service Encryption"; + + let mut mac = HmacSha256::new_from_slice(&master_key.inner).unwrap(); + mac.update(KEY); + let result = mac.finalize(); + let inner: [u8; STORAGE_KEY_LEN] = result.into_bytes().into(); + + Self { inner } + } + + pub fn from_slice( + slice: &[u8], + ) -> Result { + let inner = slice.try_into()?; + Ok(Self { inner }) + } +} + +impl From for Vec { + fn from(val: StorageServiceKey) -> Self { + val.inner.to_vec() + } +} + +/// Storage trait for handling MasterKey and StorageKey. +pub trait MasterKeyStore { + /// Fetch the master key from the store if it exists. + fn fetch_master_key(&self) -> Option; + + /// Fetch the storage service key from the store if it exists. + fn fetch_storage_service_key(&self) -> Option; + + /// Save (or clear) the master key to the store. + fn store_master_key(&self, master_key: Option<&MasterKey>); + + /// Save (or clear) the storage service key to the store. + fn store_storage_service_key( + &self, + storage_key: Option<&StorageServiceKey>, + ); +} + +mod tests { + #[test] + fn derive_storage_key_from_master_key() { + use super::{MasterKey, StorageServiceKey}; + use base64::prelude::*; + + // This test passed with actual 'masterKey' and 'storageKey' values taken + // from Signal Desktop v7.23.0 database at 2024-09-08 after linking it with Signal Andoid. + + let master_key_bytes = BASE64_STANDARD + .decode("9hquLIIZmom8fHF7H8pbUAreawmPLEqli5ceJ94pFkU=") + .unwrap(); + let storage_key_bytes = BASE64_STANDARD + .decode("QMgZ5RGTLFTr4u/J6nypaJX6DKDlSgMw8vmxU6gxnvI=") + .unwrap(); + assert_eq!(master_key_bytes.len(), 32); + assert_eq!(storage_key_bytes.len(), 32); + + let master_key = MasterKey::from_slice(&master_key_bytes).unwrap(); + let storage_key = StorageServiceKey::from_master_key(&master_key); + + assert_eq!(master_key.inner, master_key_bytes.as_slice()); + assert_eq!(storage_key.inner, storage_key_bytes.as_slice()); + } +} diff --git a/libsignal-service/src/sender.rs b/libsignal-service/src/sender.rs index dca9ba03e..99e3d1273 100644 --- a/libsignal-service/src/sender.rs +++ b/libsignal-service/src/sender.rs @@ -752,6 +752,56 @@ where Ok(()) } + /// Send `Keys` synchronization message + #[tracing::instrument(skip(self))] + pub async fn send_keys( + &mut self, + recipient: &ServiceAddress, + keys: sync_message::Keys, + ) -> Result<(), MessageSenderError> { + let msg = SyncMessage { + keys: Some(keys), + ..SyncMessage::with_padding() + }; + + let ts = Utc::now().timestamp_millis() as u64; + self.send_message(recipient, None, msg, ts, false, false) + .await?; + + Ok(()) + } + + /// Send a `Keys` request message + #[tracing::instrument(skip(self))] + pub async fn send_sync_message_request( + &mut self, + recipient: &ServiceAddress, + request_type: sync_message::request::Type, + ) -> Result<(), MessageSenderError> { + if self.device_id == DEFAULT_DEVICE_ID.into() { + let reason = format!( + "Primary device can't send sync requests, ignoring {:?}", + request_type + ); + return Err(MessageSenderError::ServiceError( + ServiceError::SendError { reason }, + )); + } + + let msg = SyncMessage { + request: Some(sync_message::Request { + r#type: Some(request_type.into()), + }), + ..SyncMessage::with_padding() + }; + + let ts = Utc::now().timestamp_millis() as u64; + self.send_message(recipient, None, msg, ts, false, false) + .await?; + + Ok(()) + } + #[tracing::instrument(level = "trace", skip(self))] fn create_pni_signature( &mut self,