diff --git a/src/account_manager.rs b/src/account_manager.rs index 09f02e012..aca1bf6cb 100644 --- a/src/account_manager.rs +++ b/src/account_manager.rs @@ -52,8 +52,7 @@ use crate::{ type Aes256Ctr128BE = ctr::Ctr128BE; -pub struct AccountManager { - csprng: R, +pub struct AccountManager { service: PushService, profile_key: Option, } @@ -74,14 +73,9 @@ pub struct Profile { pub avatar: Option, } -impl AccountManager { - pub fn new( - csprng: R, - service: PushService, - profile_key: Option, - ) -> Self { +impl AccountManager { + pub fn new(service: PushService, profile_key: Option) -> Self { Self { - csprng, service, profile_key, } @@ -94,9 +88,10 @@ impl AccountManager { /// /// Equivalent to Java's RefreshPreKeysJob #[allow(clippy::too_many_arguments)] - #[tracing::instrument(skip(self, protocol_store))] - pub async fn update_pre_key_bundle( + #[tracing::instrument(skip(self, csprng, protocol_store))] + pub async fn update_pre_key_bundle( &mut self, + csprng: &mut R, protocol_store: &mut P, service_id_type: ServiceIdType, use_last_resort_key: bool, @@ -156,8 +151,8 @@ impl AccountManager { let (pre_keys, signed_pre_key, pq_pre_keys, pq_last_resort_key) = crate::pre_keys::replenish_pre_keys( protocol_store, + csprng, &identity_key_pair, - &mut self.csprng, use_last_resort_key && !has_last_resort_key, PRE_KEY_BATCH_SIZE, PRE_KEY_BATCH_SIZE, @@ -283,8 +278,9 @@ impl AccountManager { /// ```java /// TextSecurePreferences.setIsUnidentifiedDeliveryEnabled(context, false); /// ``` - pub async fn link_device( + pub async fn link_device( &mut self, + csprng: &mut R, url: url::Url, aci_identity_store: &dyn IdentityKeyStore, pni_identity_store: &dyn IdentityKeyStore, @@ -346,7 +342,7 @@ impl AccountManager { let cipher = ProvisioningCipher::from_public(pub_key); - let encrypted = cipher.encrypt(&mut self.csprng, msg)?; + let encrypted = cipher.encrypt(csprng, msg)?; self.send_provisioning_message(ephemeral_id, encrypted) .await?; Ok(()) @@ -382,10 +378,12 @@ impl AccountManager { } pub async fn register_account< + R: Rng + CryptoRng, Aci: PreKeysStore + IdentityKeyStore, Pni: PreKeysStore + IdentityKeyStore, >( &mut self, + csprng: &mut R, registration_method: RegistrationMethod<'_>, account_attributes: AccountAttributes, aci_protocol_store: &mut Aci, @@ -408,8 +406,8 @@ impl AccountManager { aci_last_resort_kyber_prekey, ) = crate::pre_keys::replenish_pre_keys( aci_protocol_store, + csprng, &aci_identity_key_pair, - &mut self.csprng, true, 0, 0, @@ -423,8 +421,8 @@ impl AccountManager { pni_last_resort_kyber_prekey, ) = crate::pre_keys::replenish_pre_keys( pni_protocol_store, + csprng, &pni_identity_key_pair, - &mut self.csprng, true, 0, 0, @@ -470,15 +468,19 @@ impl AccountManager { /// ``` /// in which the `retain_avatar` parameter sets whether to remove (`false`) or retain (`true`) the /// currently set avatar. - pub async fn upload_versioned_profile_without_avatar>( + pub async fn upload_versioned_profile_without_avatar< + R: Rng + CryptoRng, + S: AsRef, + >( &mut self, aci: libsignal_protocol::Aci, name: ProfileName, about: Option, about_emoji: Option, retain_avatar: bool, + csprng: &mut R, ) -> Result<(), ProfileManagerError> { - self.upload_versioned_profile::>, _>( + self.upload_versioned_profile::>, _, _>( aci, name, about, @@ -488,6 +490,7 @@ impl AccountManager { } else { AvatarWrite::NoAvatar }, + csprng, ) .await?; Ok(()) @@ -505,8 +508,8 @@ impl AccountManager { .retrieve_profile_by_id(address, Some(profile_key)) .await?; - let profile_cipher = ProfileCipher::new(&mut self.csprng, profile_key); - Ok(encrypted_profile.decrypt(profile_cipher)?) + let profile_cipher = ProfileCipher::new(profile_key); + Ok(profile_cipher.decrypt(encrypted_profile)?) } /// Upload a profile @@ -517,6 +520,7 @@ impl AccountManager { pub async fn upload_versioned_profile< 's, C: std::io::Read + Send + 's, + R: Rng + CryptoRng, S: AsRef, >( &mut self, @@ -525,18 +529,18 @@ impl AccountManager { about: Option, about_emoji: Option, avatar: AvatarWrite<&'s mut C>, + csprng: &mut R, ) -> Result, ProfileManagerError> { let profile_key = self.profile_key.expect("set profile key in AccountManager"); - let mut profile_cipher = - ProfileCipher::new(&mut self.csprng, profile_key); + let profile_cipher = ProfileCipher::new(profile_key); // Profile encryption - let name = profile_cipher.encrypt_name(name.as_ref())?; + let name = profile_cipher.encrypt_name(name.as_ref(), csprng)?; let about = about.unwrap_or_default(); - let about = profile_cipher.encrypt_about(about)?; + let about = profile_cipher.encrypt_about(about, csprng)?; let about_emoji = about_emoji.unwrap_or_default(); - let about_emoji = profile_cipher.encrypt_emoji(about_emoji)?; + let about_emoji = profile_cipher.encrypt_emoji(about_emoji, csprng)?; // If avatar -> upload if matches!(avatar, AvatarWrite::NewAvatar(_)) { @@ -573,13 +577,14 @@ impl AccountManager { } /// Update (encrypted) device name - pub async fn update_device_name( + pub async fn update_device_name( &mut self, device_name: &str, public_key: &IdentityKey, + csprng: &mut R, ) -> Result<(), ServiceError> { let encrypted_device_name = - encrypt_device_name(&mut self.csprng, device_name, public_key)?; + encrypt_device_name(csprng, device_name, public_key)?; #[derive(Serialize)] #[serde(rename_all = "camelCase")] @@ -640,9 +645,9 @@ impl AccountManager { /// Should be called as the primary device to migrate from pre-PNI to PNI. /// /// This is the equivalent of Android's PnpInitializeDevicesJob or iOS' PniHelloWorldManager. - #[tracing::instrument(skip(self, aci_protocol_store, pni_protocol_store, sender, local_aci), fields(local_aci = %local_aci))] + #[tracing::instrument(skip(self, aci_protocol_store, pni_protocol_store, sender, local_aci, csprng), fields(local_aci = %local_aci))] pub async fn pnp_initialize_devices< - // XXX So many constraints here, all imposed by the MessageSender + R: Rng + CryptoRng, Aci: PreKeysStore + SessionStoreExt, Pni: PreKeysStore, AciOrPni: ProtocolStore + SenderKeyStore + SessionStoreExt + Sync + Clone, @@ -653,6 +658,7 @@ impl AccountManager { mut sender: MessageSender, local_aci: ServiceAddress, e164: PhoneNumber, + csprng: &mut R, ) -> Result<(), MessageSenderError> { let pni_identity_key_pair = pni_protocol_store.get_identity_key_pair().await?; @@ -709,8 +715,8 @@ impl AccountManager { ) = if local_device_id == DEFAULT_DEVICE_ID { crate::pre_keys::replenish_pre_keys( pni_protocol_store, + csprng, &pni_identity_key_pair, - &mut self.csprng, true, 0, 0, @@ -718,16 +724,16 @@ impl AccountManager { .await? } else { // Generate a signed prekey - let signed_pre_key_pair = KeyPair::generate(&mut self.csprng); + let signed_pre_key_pair = KeyPair::generate(csprng); let signed_pre_key_public = signed_pre_key_pair.public_key; let signed_pre_key_signature = pni_identity_key_pair.private_key().calculate_signature( &signed_pre_key_public.serialize(), - &mut self.csprng, + csprng, )?; let signed_prekey_record = SignedPreKeyRecord::new( - self.csprng.gen_range::(0..0xFFFFFF).into(), + csprng.gen_range::(0..0xFFFFFF).into(), Timestamp::now(), &signed_pre_key_pair, &signed_pre_key_signature, @@ -736,7 +742,7 @@ impl AccountManager { // Generate a last-resort Kyber prekey let kyber_pre_key_record = KyberPreKeyRecord::generate( kem::KeyType::Kyber1024, - self.csprng.gen_range::(0..0xFFFFFF).into(), + csprng.gen_range::(0..0xFFFFFF).into(), pni_identity_key_pair.private_key(), )?; ( @@ -751,7 +757,7 @@ impl AccountManager { pni_protocol_store.get_local_registration_id().await? } else { loop { - let regid = generate_registration_id(&mut self.csprng); + let regid = generate_registration_id(csprng); if !pni_registration_ids.iter().any(|(_k, v)| *v == regid) { break regid; } @@ -799,7 +805,7 @@ impl AccountManager { e164.format().mode(phonenumber::Mode::E164).to_string(), ), }), - padding: Some(random_length_padding(&mut self.csprng, 512)), + padding: Some(random_length_padding(csprng, 512)), ..SyncMessage::default() }; let content: ContentBody = msg.into(); diff --git a/src/cipher.rs b/src/cipher.rs index 057cc8332..fb5a58030 100644 --- a/src/cipher.rs +++ b/src/cipher.rs @@ -29,19 +29,17 @@ use crate::{ /// /// Equivalent of SignalServiceCipher in Java. #[derive(Clone)] -pub struct ServiceCipher { +pub struct ServiceCipher { protocol_store: S, - csprng: R, trust_root: PublicKey, local_uuid: Uuid, local_device_id: u32, } -impl fmt::Debug for ServiceCipher { +impl fmt::Debug for ServiceCipher { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ServiceCipher") .field("protocol_store", &"...") - .field("csprng", &"...") .field("trust_root", &"...") .field("local_uuid", &self.local_uuid) .field("local_device_id", &self.local_device_id) @@ -70,21 +68,18 @@ fn debug_envelope(envelope: &Envelope) -> String { } } -impl ServiceCipher +impl ServiceCipher where S: ProtocolStore + SenderKeyStore + SessionStoreExt + Clone, - R: Rng + CryptoRng, { pub fn new( protocol_store: S, - csprng: R, trust_root: PublicKey, local_uuid: Uuid, local_device_id: u32, ) -> Self { Self { protocol_store, - csprng, trust_root, local_uuid, local_device_id, @@ -94,13 +89,14 @@ where /// Opens ("decrypts") an envelope. /// /// Envelopes may be empty, in which case this method returns `Ok(None)` - #[tracing::instrument(skip(envelope), fields(envelope = debug_envelope(&envelope)))] - pub async fn open_envelope( + #[tracing::instrument(skip(envelope, csprng), fields(envelope = debug_envelope(&envelope)))] + pub async fn open_envelope( &mut self, envelope: Envelope, + csprng: &mut R, ) -> Result, ServiceError> { if envelope.content.is_some() { - let plaintext = self.decrypt(&envelope).await?; + let plaintext = self.decrypt(&envelope, csprng).await?; let message = crate::proto::Content::decode(plaintext.data.as_slice())?; if let Some(bytes) = message.sender_key_distribution_message { @@ -126,10 +122,11 @@ where /// Triage of legacy messages happens inside this method, as opposed to the /// Java implementation, because it makes the borrow checker and the /// author happier. - #[tracing::instrument(skip(envelope), fields(envelope = debug_envelope(envelope)))] - async fn decrypt( + #[tracing::instrument(skip(envelope, csprng), fields(envelope = debug_envelope(envelope)))] + async fn decrypt( &mut self, envelope: &Envelope, + csprng: &mut R, ) -> Result { let ciphertext = if let Some(msg) = envelope.content.as_ref() { msg @@ -180,7 +177,7 @@ where &mut self.protocol_store.clone(), &self.protocol_store.clone(), &mut self.protocol_store.clone(), - &mut self.csprng, + csprng, ) .await? .as_slice() @@ -236,7 +233,7 @@ where &sender, &mut self.protocol_store.clone(), &mut self.protocol_store.clone(), - &mut self.csprng, + csprng, ) .await? .as_slice() @@ -319,18 +316,19 @@ where } #[tracing::instrument( - skip(address, unidentified_access, content), + skip(address, unidentified_access, content, csprng), fields( address = %address, with_unidentified_access = unidentified_access.is_some(), content_length = content.len(), ) )] - pub(crate) async fn encrypt( + pub(crate) async fn encrypt( &mut self, address: &ProtocolAddress, unidentified_access: Option<&SenderCertificate>, content: &[u8], + csprng: &mut R, ) -> Result { let session_record = self .protocol_store @@ -354,7 +352,7 @@ where &mut self.protocol_store.clone(), &mut self.protocol_store, SystemTime::now(), - &mut self.csprng, + csprng, ) .await?; diff --git a/src/pre_keys.rs b/src/pre_keys.rs index cef57d5e1..4af0067da 100644 --- a/src/pre_keys.rs +++ b/src/pre_keys.rs @@ -12,6 +12,7 @@ use libsignal_protocol::{ SignedPreKeyRecord, SignedPreKeyStore, Timestamp, }; +use rand::{CryptoRng, Rng}; use serde::{Deserialize, Serialize}; use tracing::Instrument; @@ -175,13 +176,10 @@ pub(crate) const PRE_KEY_MINIMUM: u32 = 10; pub(crate) const PRE_KEY_BATCH_SIZE: u32 = 100; pub(crate) const PRE_KEY_MEDIUM_MAX_VALUE: u32 = 0xFFFFFF; -pub(crate) async fn replenish_pre_keys< - R: rand::Rng + rand::CryptoRng, - P: PreKeysStore, ->( +pub(crate) async fn replenish_pre_keys( protocol_store: &mut P, - identity_key_pair: &IdentityKeyPair, csprng: &mut R, + identity_key_pair: &IdentityKeyPair, use_last_resort_key: bool, pre_key_count: u32, kyber_pre_key_count: u32, diff --git a/src/profile_cipher.rs b/src/profile_cipher.rs index c711f2076..b0971bbbd 100644 --- a/src/profile_cipher.rs +++ b/src/profile_cipher.rs @@ -4,7 +4,9 @@ use aes_gcm::{aead::Aead, AeadCore, AeadInPlace, Aes256Gcm, KeyInit}; use rand::{CryptoRng, Rng}; use zkgroup::profiles::ProfileKey; -use crate::profile_name::ProfileName; +use crate::{ + profile_name::ProfileName, push_service::SignalServiceProfile, Profile, +}; /// Encrypt and decrypt a [`ProfileName`] and other profile information. /// @@ -26,8 +28,7 @@ use crate::profile_name::ProfileName; /// let decrypted = cipher.decrypt_name(&encrypted).unwrap().unwrap(); /// assert_eq!(decrypted.as_ref(), name); /// ``` -pub struct ProfileCipher { - csprng: R, +pub struct ProfileCipher { profile_key: ProfileKey, } @@ -73,27 +74,25 @@ fn pad_plaintext( Ok(len) } -impl ProfileCipher { - pub fn new(csprng: R, profile_key: ProfileKey) -> Self { - Self { - csprng, - profile_key, - } +impl ProfileCipher { + pub fn new(profile_key: ProfileKey) -> Self { + Self { profile_key } } pub fn into_inner(self) -> ProfileKey { self.profile_key } - fn pad_and_encrypt( - &mut self, + fn pad_and_encrypt( + &self, mut bytes: Vec, padding_brackets: &[usize], + csprng: &mut R, ) -> Result, ProfileCipherError> { let _len = pad_plaintext(&mut bytes, padding_brackets)?; let cipher = Aes256Gcm::new(&self.profile_key.get_bytes().into()); - let nonce = Aes256Gcm::generate_nonce(&mut self.csprng); + let nonce = Aes256Gcm::generate_nonce(csprng); cipher .encrypt_in_place(&nonce, b"", &mut bytes) @@ -132,6 +131,35 @@ impl ProfileCipher { Ok(plaintext) } + pub fn decrypt( + &self, + encrypted_profile: SignalServiceProfile, + ) -> Result { + let name = encrypted_profile + .name + .as_ref() + .map(|data| self.decrypt_name(data)) + .transpose()? + .flatten(); + let about = encrypted_profile + .about + .as_ref() + .map(|data| self.decrypt_about(data)) + .transpose()?; + let about_emoji = encrypted_profile + .about_emoji + .as_ref() + .map(|data| self.decrypt_emoji(data)) + .transpose()?; + + Ok(Profile { + name, + about, + about_emoji, + avatar: encrypted_profile.avatar, + }) + } + pub fn decrypt_avatar( &self, bytes: &[u8], @@ -139,13 +167,14 @@ impl ProfileCipher { self.decrypt_and_unpad(bytes) } - pub fn encrypt_name<'inp>( - &mut self, + pub fn encrypt_name<'inp, R: Rng + CryptoRng>( + &self, name: impl std::borrow::Borrow>, + csprng: &mut R, ) -> Result, ProfileCipherError> { let name = name.borrow(); let bytes = name.serialize(); - self.pad_and_encrypt(bytes, NAME_PADDING_BRACKETS) + self.pad_and_encrypt(bytes, NAME_PADDING_BRACKETS, csprng) } pub fn decrypt_name( @@ -159,12 +188,13 @@ impl ProfileCipher { Ok(ProfileName::::deserialize(&plaintext)?) } - pub fn encrypt_about( - &mut self, + pub fn encrypt_about( + &self, about: String, + csprng: &mut R, ) -> Result, ProfileCipherError> { let bytes = about.into_bytes(); - self.pad_and_encrypt(bytes, ABOUT_PADDING_BRACKETS) + self.pad_and_encrypt(bytes, ABOUT_PADDING_BRACKETS, csprng) } pub fn decrypt_about( @@ -179,12 +209,13 @@ impl ProfileCipher { Ok(std::str::from_utf8(&plaintext)?.into()) } - pub fn encrypt_emoji( - &mut self, + pub fn encrypt_emoji( + &self, emoji: String, + csprng: &mut R, ) -> Result, ProfileCipherError> { let bytes = emoji.into_bytes(); - self.pad_and_encrypt(bytes, &[EMOJI_PADDED_LENGTH]) + self.pad_and_encrypt(bytes, &[EMOJI_PADDED_LENGTH], csprng) } pub fn decrypt_emoji( @@ -225,14 +256,15 @@ mod tests { let mut rng = rand::thread_rng(); let some_randomness = rng.gen(); let profile_key = ProfileKey::generate(some_randomness); - let mut cipher = ProfileCipher::new(rng, profile_key); + let mut cipher = ProfileCipher::new(profile_key); for name in &names { let profile_name = ProfileName::<&str> { given_name: name, family_name: None, }; assert_eq!(profile_name.serialize().len(), name.len()); - let encrypted = cipher.encrypt_name(&profile_name).unwrap(); + let encrypted = + cipher.encrypt_name(&profile_name, &mut rng).unwrap(); let decrypted = cipher.decrypt_name(encrypted).unwrap().unwrap(); assert_eq!(decrypted.as_ref(), profile_name); @@ -250,10 +282,11 @@ mod tests { let mut rng = rand::thread_rng(); let some_randomness = rng.gen(); let profile_key = ProfileKey::generate(some_randomness); - let mut cipher = ProfileCipher::new(rng, profile_key); + let mut cipher = ProfileCipher::new(profile_key); for &about in &abouts { - let encrypted = cipher.encrypt_about(about.into()).unwrap(); + let encrypted = + cipher.encrypt_about(about.into(), &mut rng).unwrap(); let decrypted = cipher.decrypt_about(encrypted).unwrap(); assert_eq!(decrypted, about); @@ -267,10 +300,11 @@ mod tests { let mut rng = rand::thread_rng(); let some_randomness = rng.gen(); let profile_key = ProfileKey::generate(some_randomness); - let mut cipher = ProfileCipher::new(rng, profile_key); + let mut cipher = ProfileCipher::new(profile_key); for &emoji in &emojii { - let encrypted = cipher.encrypt_emoji(emoji.into()).unwrap(); + let encrypted = + cipher.encrypt_emoji(emoji.into(), &mut rng).unwrap(); let decrypted = cipher.decrypt_emoji(encrypted).unwrap(); assert_eq!(decrypted, emoji); diff --git a/src/provisioning/mod.rs b/src/provisioning/mod.rs index cac4c1015..f3dbaa947 100644 --- a/src/provisioning/mod.rs +++ b/src/provisioning/mod.rs @@ -237,8 +237,8 @@ pub async fn link_device< aci_pq_last_resort_pre_key, ) = crate::pre_keys::replenish_pre_keys( aci_store, - &aci_key_pair, csprng, + &aci_key_pair, true, 0, 0, @@ -257,8 +257,8 @@ pub async fn link_device< pni_pq_last_resort_pre_key, ) = crate::pre_keys::replenish_pre_keys( pni_store, - &pni_key_pair, csprng, + &pni_key_pair, true, 0, 0, diff --git a/src/push_service/profile.rs b/src/push_service/profile.rs index c427d7115..1a08b91b7 100644 --- a/src/push_service/profile.rs +++ b/src/push_service/profile.rs @@ -1,4 +1,3 @@ -use rand::{CryptoRng, Rng}; use reqwest::Method; use serde::{Deserialize, Serialize}; use zkgroup::profiles::{ProfileKeyCommitment, ProfileKeyVersion}; @@ -6,10 +5,9 @@ use zkgroup::profiles::{ProfileKeyCommitment, ProfileKeyVersion}; use crate::{ configuration::Endpoint, content::ServiceError, - profile_cipher::ProfileCipherError, push_service::AvatarWrite, utils::{serde_base64, serde_optional_base64}, - Profile, ServiceAddress, + ServiceAddress, }; use super::{DeviceCapabilities, HttpAuthOverride, PushService, ReqwestExt}; @@ -38,38 +36,6 @@ pub struct SignalServiceProfile { pub capabilities: DeviceCapabilities, } -impl SignalServiceProfile { - pub fn decrypt( - &self, - profile_cipher: crate::profile_cipher::ProfileCipher, - ) -> Result { - // Profile decryption - let name = self - .name - .as_ref() - .map(|data| profile_cipher.decrypt_name(data)) - .transpose()? - .flatten(); - let about = self - .about - .as_ref() - .map(|data| profile_cipher.decrypt_about(data)) - .transpose()?; - let about_emoji = self - .about_emoji - .as_ref() - .map(|data| profile_cipher.decrypt_emoji(data)) - .transpose()?; - - Ok(Profile { - name, - about, - about_emoji, - avatar: self.avatar.clone(), - }) - } -} - #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] struct SignalServiceProfileWrite<'s> { diff --git a/src/sender.rs b/src/sender.rs index 8fb959955..ea7cd2f9f 100644 --- a/src/sender.rs +++ b/src/sender.rs @@ -87,7 +87,7 @@ pub struct MessageSender { identified_ws: SignalWebSocket, unidentified_ws: SignalWebSocket, service: PushService, - cipher: ServiceCipher, + cipher: ServiceCipher, csprng: R, protocol_store: S, local_aci: ServiceAddress, @@ -160,7 +160,7 @@ where identified_ws: SignalWebSocket, unidentified_ws: SignalWebSocket, service: PushService, - cipher: ServiceCipher, + cipher: ServiceCipher, csprng: R, protocol_store: S, local_aci: impl Into, @@ -1031,7 +1031,12 @@ where let message = self .cipher - .encrypt(&recipient_protocol_address, unidentified_access, content) + .encrypt( + &recipient_protocol_address, + unidentified_access, + content, + &mut self.csprng, + ) .instrument(tracing::trace_span!("encrypting message")) .await?;