diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index 0ca2abad9..c33f97408 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..da0ca7fd1 --- /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: &Vec, + ) -> Result { + let inner = slice.as_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: &Vec, + ) -> Result { + let inner = slice.as_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()); + } +}