From 588ca72bfc7bcc2e248af3dcce905fcde49cacd2 Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Sun, 14 Jan 2024 15:43:03 +0100 Subject: [PATCH 01/34] PushService::distribute_pni_keys --- libsignal-service/src/pre_keys.rs | 2 +- libsignal-service/src/push_service.rs | 46 +++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/libsignal-service/src/pre_keys.rs b/libsignal-service/src/pre_keys.rs index 17123c4b4..0c5c6b4ac 100644 --- a/libsignal-service/src/pre_keys.rs +++ b/libsignal-service/src/pre_keys.rs @@ -64,7 +64,7 @@ impl TryFrom for PreKeyEntity { } } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SignedPreKeyEntity { pub key_id: u32, diff --git a/libsignal-service/src/push_service.rs b/libsignal-service/src/push_service.rs index b99fa1e2d..2c718175f 100644 --- a/libsignal-service/src/push_service.rs +++ b/libsignal-service/src/push_service.rs @@ -1,4 +1,4 @@ -use std::{fmt, time::Duration}; +use std::{collections::HashMap, fmt, time::Duration}; use crate::{ configuration::{Endpoint, ServiceCredentials}, @@ -10,7 +10,7 @@ use crate::{ }, profile_cipher::ProfileCipherError, proto::{attachment_pointer::AttachmentIdentifier, AttachmentPointer}, - sender::{OutgoingPushMessages, SendMessageResponse}, + sender::{OutgoingPushMessage, OutgoingPushMessages, SendMessageResponse}, utils::{serde_base64, serde_optional_base64, serde_phone_number}, websocket::SignalWebSocket, MaybeSend, ParseServiceAddressError, Profile, ServiceAddress, @@ -1313,4 +1313,46 @@ pub trait PushService: MaybeSend { .await?; Ok(res) } + + async fn distribute_pni_keys( + &mut self, + pni_identity_key: IdentityKey, + device_messages: Vec, + device_pni_signed_prekeys: HashMap<&str, SignedPreKeyEntity>, + device_pni_last_resort_kyber_prekeys: HashMap<&str, KyberPreKeyEntity>, + pni_registration_ids: HashMap<&str, u32>, + signature_valid_on_each_signed_pre_key: bool, + ) -> Result { + #[derive(serde::Serialize, Debug)] + #[serde(rename_all = "camelCase")] + struct PniKeyDistributionRequest<'a> { + #[serde(with = "serde_base64")] + pni_identity_key: Vec, + device_messages: Vec, + device_pni_signed_prekeys: HashMap<&'a str, SignedPreKeyEntity>, + #[serde(rename = "devicePniPqLastResortPrekeys")] + device_pni_last_resort_kyber_prekeys: + HashMap<&'a str, KyberPreKeyEntity>, + pni_registration_ids: HashMap<&'a str, u32>, + signature_valid_on_each_signed_pre_key: bool, + } + + let res: VerifyAccountResponse = self + .put_json( + Endpoint::Service, + "/v2/accounts/phone_number_identity_key_distribution", + &[], + HttpAuthOverride::NoOverride, + PniKeyDistributionRequest { + pni_identity_key: pni_identity_key.serialize().into(), + device_messages, + device_pni_signed_prekeys, + device_pni_last_resort_kyber_prekeys, + pni_registration_ids, + signature_valid_on_each_signed_pre_key, + }, + ) + .await?; + Ok(res) + } } From db4466b494956090f78545c69b1fe74bf038cde0 Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Sun, 14 Jan 2024 16:27:47 +0100 Subject: [PATCH 02/34] Stop returning next pre key id's from update_pre_key_bundle --- libsignal-service/src/account_manager.rs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index 8e42e4485..fc14a5ff1 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -90,8 +90,6 @@ impl AccountManager { /// signed pre-keys. /// /// Equivalent to Java's RefreshPreKeysJob - /// - /// Returns the next pre-key offset, pq pre-key offset, and next signed pre-key offset as a tuple. #[allow(clippy::too_many_arguments)] #[tracing::instrument(skip(self, protocol_store, csprng))] pub async fn update_pre_key_bundle< @@ -103,7 +101,7 @@ impl AccountManager { service_id_type: ServiceIdType, csprng: &mut R, use_last_resort_key: bool, - ) -> Result<(u32, u32, u32), ServiceError> { + ) -> Result<(), ServiceError> { let pre_keys_offset_id = protocol_store.next_pre_key_id().await?; let next_signed_pre_key_id = protocol_store.next_signed_pre_key_id().await?; @@ -136,11 +134,7 @@ impl AccountManager { && prekey_status.pq_count >= PRE_KEY_MINIMUM { tracing::info!("Available keys sufficient"); - return Ok(( - pre_keys_offset_id, - pq_pre_keys_offset_id, - next_signed_pre_key_id, - )); + return Ok(()); } let pre_key_state = { @@ -252,11 +246,7 @@ impl AccountManager { )) .await?; - Ok(( - pre_keys_offset_id + PRE_KEY_BATCH_SIZE, - pq_pre_keys_offset_id + PRE_KEY_BATCH_SIZE, - next_signed_pre_key_id + 1, - )) + Ok(()) } async fn new_device_provisioning_code( From 6e298046a8d29942897365b5abe340580db8976c Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Sun, 14 Jan 2024 16:29:33 +0100 Subject: [PATCH 03/34] Take IdentityKey by ref --- libsignal-service/src/push_service.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsignal-service/src/push_service.rs b/libsignal-service/src/push_service.rs index 2c718175f..a7448968a 100644 --- a/libsignal-service/src/push_service.rs +++ b/libsignal-service/src/push_service.rs @@ -1316,7 +1316,7 @@ pub trait PushService: MaybeSend { async fn distribute_pni_keys( &mut self, - pni_identity_key: IdentityKey, + pni_identity_key: &IdentityKey, device_messages: Vec, device_pni_signed_prekeys: HashMap<&str, SignedPreKeyEntity>, device_pni_last_resort_kyber_prekeys: HashMap<&str, KyberPreKeyEntity>, From b3f6f2b1fd8814374be1cacfb26861c5389522d4 Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Sun, 14 Jan 2024 16:45:14 +0100 Subject: [PATCH 04/34] Factor out generation of pre keys to separate function --- libsignal-service/src/account_manager.rs | 254 +++++++++++++---------- 1 file changed, 150 insertions(+), 104 deletions(-) diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index fc14a5ff1..d7500cafb 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -17,9 +17,10 @@ use sha2::Sha256; use tracing_futures::Instrument; use zkgroup::profiles::ProfileKey; -use crate::pre_keys::{KyberPreKeyEntity, PreKeysStore}; +use crate::pre_keys::{KyberPreKeyEntity, PreKeysStore, SignedPreKeyEntity}; use crate::proto::DeviceName; use crate::push_service::{AvatarWrite, RecaptchaAttributes, ServiceIdType}; +use crate::sender::OutgoingPushMessage; use crate::utils::BASE64_RELAXED; use crate::ServiceAddress; use crate::{ @@ -84,6 +85,132 @@ impl AccountManager { } } + #[tracing::instrument(skip(self, protocol_store, csprng))] + async fn generate_pre_keys< + R: rand::Rng + rand::CryptoRng, + P: PreKeysStore, + >( + &mut self, + protocol_store: &mut P, + service_id_type: ServiceIdType, + csprng: &mut R, + use_last_resort_key: bool, + ) -> Result< + ( + Vec, + SignedPreKeyRecord, + Vec, + Option, + ), + ServiceError, + > { + let pre_keys_offset_id = protocol_store.next_pre_key_id().await?; + let next_signed_pre_key_id = + protocol_store.next_signed_pre_key_id().await?; + let pq_pre_keys_offset_id = protocol_store.next_pq_pre_key_id().await?; + + let span = tracing::span!(tracing::Level::DEBUG, "Generating pre keys"); + + let identity_key_pair = protocol_store + .get_identity_key_pair() + .instrument( + tracing::trace_span!(parent: &span, "get identity key pair"), + ) + .await?; + + let mut pre_key_entities = vec![]; + let mut pq_pre_key_entities = vec![]; + + // EC keys + for i in 0..PRE_KEY_BATCH_SIZE { + let key_pair = KeyPair::generate(csprng); + let pre_key_id = (((pre_keys_offset_id + i) + % (PRE_KEY_MEDIUM_MAX_VALUE - 1)) + + 1) + .into(); + let pre_key_record = PreKeyRecord::new(pre_key_id, &key_pair); + protocol_store + .save_pre_key(pre_key_id, &pre_key_record) + .instrument(tracing::trace_span!(parent: &span, "save pre key", ?pre_key_id)).await?; + // TODO: Shouldn't this also remove the previous pre-keys from storage? + // I think we might want to update the storage, and then sync the storage to the + // server. + + pre_key_entities.push(PreKeyEntity::try_from(pre_key_record)?); + } + + // Kyber keys + for i in 0..PRE_KEY_BATCH_SIZE { + let pre_key_id = (((pq_pre_keys_offset_id + i) + % (PRE_KEY_MEDIUM_MAX_VALUE - 1)) + + 1) + .into(); + let pre_key_record = KyberPreKeyRecord::generate( + kem::KeyType::Kyber1024, + pre_key_id, + identity_key_pair.private_key(), + )?; + protocol_store + .save_kyber_pre_key(pre_key_id, &pre_key_record) + .instrument(tracing::trace_span!(parent: &span, "save kyber pre key", ?pre_key_id)).await?; + // TODO: Shouldn't this also remove the previous pre-keys from storage? + // I think we might want to update the storage, and then sync the storage to the + // server. + + pq_pre_key_entities + .push(KyberPreKeyEntity::try_from(pre_key_record)?); + } + + // Generate and store the next signed prekey + let signed_pre_key_pair = KeyPair::generate(csprng); + let signed_pre_key_public = signed_pre_key_pair.public_key; + let signed_pre_key_signature = identity_key_pair + .private_key() + .calculate_signature(&signed_pre_key_public.serialize(), csprng)?; + + let unix_time = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap(); + + let signed_prekey_record = SignedPreKeyRecord::new( + next_signed_pre_key_id.into(), + unix_time.as_millis() as u64, + &signed_pre_key_pair, + &signed_pre_key_signature, + ); + + protocol_store + .save_signed_pre_key( + next_signed_pre_key_id.into(), + &signed_prekey_record, + ) + .instrument(tracing::trace_span!(parent: &span, "save signed pre key", signed_pre_key_id = ?next_signed_pre_key_id)).await?; + + let pq_last_resort_key = if use_last_resort_key { + tracing::warn!("Last resort Kyber key unimplemented"); + // Note about the last-resort key: + // mark_kyber_pre_key_used() should retain the last-resort key, but can safely + // remove the ephemeral pre keys. This implies that generating the last-resort key + // should notify the pre-key store, when saving the key, that it concerns a + // last-resort key. I don't see how this can be communicated to the store, and I + // fear that we need to reengineer the whole prekeystore system as a whole. + None + // Some(KyberPreKeyEntity { + // key_id: 0x7fffffff, + // public_key: "NDI=".into(), + // }) + } else { + None + }; + + Ok(( + pre_key_entities, + signed_prekey_record, + pq_pre_key_entities, + pq_last_resort_key, + )) + } + /// Checks the availability of pre-keys, and updates them as necessary. /// /// Parameters are the protocol's `StoreContext`, and the offsets for the next pre-key and @@ -102,11 +229,6 @@ impl AccountManager { csprng: &mut R, use_last_resort_key: bool, ) -> Result<(), ServiceError> { - let pre_keys_offset_id = protocol_store.next_pre_key_id().await?; - let next_signed_pre_key_id = - protocol_store.next_signed_pre_key_id().await?; - let pq_pre_keys_offset_id = protocol_store.next_pq_pre_key_id().await?; - let prekey_status = match self .service .get_pre_key_status(service_id_type) @@ -137,105 +259,29 @@ impl AccountManager { return Ok(()); } - let pre_key_state = { - let span = - tracing::span!(tracing::Level::DEBUG, "Generating pre keys"); - - let identity_key_pair = - protocol_store.get_identity_key_pair().instrument(tracing::trace_span!(parent: &span, "get identity key pair")).await?; - - let mut pre_key_entities = vec![]; - let mut pq_pre_key_entities = vec![]; - - // EC keys - for i in 0..PRE_KEY_BATCH_SIZE { - let key_pair = KeyPair::generate(csprng); - let pre_key_id = (((pre_keys_offset_id + i) - % (PRE_KEY_MEDIUM_MAX_VALUE - 1)) - + 1) - .into(); - let pre_key_record = PreKeyRecord::new(pre_key_id, &key_pair); - protocol_store - .save_pre_key(pre_key_id, &pre_key_record) - .instrument(tracing::trace_span!(parent: &span, "save pre key", ?pre_key_id)).await?; - // TODO: Shouldn't this also remove the previous pre-keys from storage? - // I think we might want to update the storage, and then sync the storage to the - // server. - - pre_key_entities.push(PreKeyEntity::try_from(pre_key_record)?); - } - - // Kyber keys - for i in 0..PRE_KEY_BATCH_SIZE { - let pre_key_id = (((pq_pre_keys_offset_id + i) - % (PRE_KEY_MEDIUM_MAX_VALUE - 1)) - + 1) - .into(); - let pre_key_record = KyberPreKeyRecord::generate( - kem::KeyType::Kyber1024, - pre_key_id, - identity_key_pair.private_key(), - )?; - protocol_store - .save_kyber_pre_key(pre_key_id, &pre_key_record) - .instrument(tracing::trace_span!(parent: &span, "save kyber pre key", ?pre_key_id)).await?; - // TODO: Shouldn't this also remove the previous pre-keys from storage? - // I think we might want to update the storage, and then sync the storage to the - // server. - - pq_pre_key_entities - .push(KyberPreKeyEntity::try_from(pre_key_record)?); - } - - // Generate and store the next signed prekey - let signed_pre_key_pair = KeyPair::generate(csprng); - let signed_pre_key_public = signed_pre_key_pair.public_key; - let signed_pre_key_signature = - identity_key_pair.private_key().calculate_signature( - &signed_pre_key_public.serialize(), - csprng, - )?; - - let unix_time = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap(); - - let signed_prekey_record = SignedPreKeyRecord::new( - next_signed_pre_key_id.into(), - unix_time.as_millis() as u64, - &signed_pre_key_pair, - &signed_pre_key_signature, - ); - - protocol_store - .save_signed_pre_key( - next_signed_pre_key_id.into(), - &signed_prekey_record, - ) - .instrument(tracing::trace_span!(parent: &span, "save signed pre key", signed_pre_key_id = ?next_signed_pre_key_id)).await?; + let (pre_keys, signed_pre_key_record, pq_pre_keys, pq_last_resort_key) = + self.generate_pre_keys( + protocol_store, + service_id_type, + csprng, + use_last_resort_key, + ) + .await?; - PreKeyState { - pre_keys: pre_key_entities, - signed_pre_key: signed_prekey_record.try_into()?, - identity_key: *identity_key_pair.public_key(), - pq_pre_keys: pq_pre_key_entities, - pq_last_resort_key: if use_last_resort_key { - tracing::warn!("Last resort Kyber key unimplemented"); - // Note about the last-resort key: - // mark_kyber_pre_key_used() should retain the last-resort key, but can safely - // remove the ephemeral pre keys. This implies that generating the last-resort key - // should notify the pre-key store, when saving the key, that it concerns a - // last-resort key. I don't see how this can be communicated to the store, and I - // fear that we need to reengineer the whole prekeystore system as a whole. - None - // Some(KyberPreKeyEntity { - // key_id: 0x7fffffff, - // public_key: "NDI=".into(), - // }) - } else { - None - }, - } + let identity_key = protocol_store + .get_identity_key_pair() + .instrument(tracing::trace_span!("get identity key pair")) + .await? + .identity_key() + .public_key() + .clone(); + + let pre_key_state = PreKeyState { + pre_keys, + signed_pre_key: signed_pre_key_record.try_into()?, + identity_key, + pq_pre_keys, + pq_last_resort_key, }; self.service From 27b07fb32ccf49e3e74a5e844af2bff5dc8cfed2 Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Sun, 14 Jan 2024 18:25:25 +0100 Subject: [PATCH 05/34] WIP pnp_initialize_devices --- libsignal-service/src/account_manager.rs | 65 +++++++++++++++++++++++- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index d7500cafb..9acd8075f 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -95,6 +95,8 @@ impl AccountManager { service_id_type: ServiceIdType, csprng: &mut R, use_last_resort_key: bool, + pre_key_count: u32, + kyber_pre_key_count: u32, ) -> Result< ( Vec, @@ -122,7 +124,7 @@ impl AccountManager { let mut pq_pre_key_entities = vec![]; // EC keys - for i in 0..PRE_KEY_BATCH_SIZE { + for i in 0..pre_key_count { let key_pair = KeyPair::generate(csprng); let pre_key_id = (((pre_keys_offset_id + i) % (PRE_KEY_MEDIUM_MAX_VALUE - 1)) @@ -140,7 +142,7 @@ impl AccountManager { } // Kyber keys - for i in 0..PRE_KEY_BATCH_SIZE { + for i in 0..kyber_pre_key_count { let pre_key_id = (((pq_pre_keys_offset_id + i) % (PRE_KEY_MEDIUM_MAX_VALUE - 1)) + 1) @@ -265,6 +267,8 @@ impl AccountManager { service_id_type, csprng, use_last_resort_key, + PRE_KEY_BATCH_SIZE, + PRE_KEY_BATCH_SIZE, ) .await?; @@ -590,6 +594,63 @@ impl AccountManager { ) .await } + + /// Initialize PNI on linked devices. + /// + /// Should be called as the primary device to migrate from pre-PNI to PNI. + pub async fn pnp_initialize_devices< + R: rand::Rng + rand::CryptoRng, + P: PreKeysStore, + >( + &mut self, + pni_protocol_store: &mut P, + csprng: &mut R, + use_last_resort_key: bool, + ) -> Result<(), ServiceError> { + let pni_identity_key_pair = + pni_protocol_store.get_identity_key_pair().await?; + + let pni_identity_key = pni_identity_key_pair.identity_key(); + let device_messages: Vec; // XXX TODO + let device_pni_signed_prekeys: HashMap<&str, SignedPreKeyEntity>; + let device_pni_last_resort_kyber_prekeys: HashMap< + &str, + KyberPreKeyEntity, + >; + let pni_registration_ids: HashMap<&str, u32>; + let signature_valid_on_each_signed_pre_key: bool; + + // This needs to be repeated for every device that we have linked. + let ( + _pre_keys, + signed_pre_key_entity, + kyber_pre_key_entities, + last_resort_kyber_prekey, + ) = self + .generate_pre_keys( + pni_protocol_store, + ServiceIdType::PhoneNumberIdentity, + csprng, + use_last_resort_key, + 0, + PRE_KEY_BATCH_SIZE, + ) + .await?; + assert_eq!(_pre_keys.len(), 0); + + self.service + .distribute_pni_keys( + pni_identity_key, + device_messages, + device_pni_signed_prekeys, + device_pni_last_resort_kyber_prekeys, + pni_registration_ids, + signature_valid_on_each_signed_pre_key, + ) + .await?; + + Ok(()) + } } fn calculate_hmac256( From 55810489dadc036634498f071e3996d31fbd7146 Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Sun, 14 Jan 2024 20:19:53 +0100 Subject: [PATCH 06/34] Deduplicate SignedPreKeyEntity and SignedPreKey --- libsignal-service/src/account_manager.rs | 12 +++++++----- libsignal-service/src/pre_keys.rs | 20 +++++--------------- libsignal-service/src/push_service.rs | 7 +++---- 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index 9acd8075f..65e53e499 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -100,7 +100,7 @@ impl AccountManager { ) -> Result< ( Vec, - SignedPreKeyRecord, + SignedPreKeyEntity, Vec, Option, ), @@ -187,6 +187,8 @@ impl AccountManager { &signed_prekey_record, ) .instrument(tracing::trace_span!(parent: &span, "save signed pre key", signed_pre_key_id = ?next_signed_pre_key_id)).await?; + let signed_prekey_entity = + SignedPreKeyEntity::try_from(signed_prekey_record)?; let pq_last_resort_key = if use_last_resort_key { tracing::warn!("Last resort Kyber key unimplemented"); @@ -207,7 +209,7 @@ impl AccountManager { Ok(( pre_key_entities, - signed_prekey_record, + signed_prekey_entity, pq_pre_key_entities, pq_last_resort_key, )) @@ -261,8 +263,8 @@ impl AccountManager { return Ok(()); } - let (pre_keys, signed_pre_key_record, pq_pre_keys, pq_last_resort_key) = - self.generate_pre_keys( + let (pre_keys, signed_pre_key, pq_pre_keys, pq_last_resort_key) = self + .generate_pre_keys( protocol_store, service_id_type, csprng, @@ -282,7 +284,7 @@ impl AccountManager { let pre_key_state = PreKeyState { pre_keys, - signed_pre_key: signed_pre_key_record.try_into()?, + signed_pre_key, identity_key, pq_pre_keys, pq_last_resort_key, diff --git a/libsignal-service/src/pre_keys.rs b/libsignal-service/src/pre_keys.rs index 0c5c6b4ac..af6c7d93a 100644 --- a/libsignal-service/src/pre_keys.rs +++ b/libsignal-service/src/pre_keys.rs @@ -74,24 +74,14 @@ pub struct SignedPreKeyEntity { pub signature: Vec, } -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct SignedPreKey { - key_id: u32, - #[serde(with = "serde_public_key")] - public_key: PublicKey, - #[serde(with = "serde_base64")] - signature: Vec, -} - -impl TryFrom for SignedPreKey { +impl TryFrom for SignedPreKeyEntity { type Error = SignalProtocolError; fn try_from(key: SignedPreKeyRecord) -> Result { - Ok(SignedPreKey { + Ok(SignedPreKeyEntity { key_id: key.id()?.into(), - public_key: key.key_pair()?.public_key, - signature: key.signature()?, + public_key: key.key_pair()?.public_key.serialize().to_vec(), + signature: key.signature()?.to_vec(), }) } } @@ -122,7 +112,7 @@ impl TryFrom for KyberPreKeyEntity { #[serde(rename_all = "camelCase")] pub struct PreKeyState { pub pre_keys: Vec, - pub signed_pre_key: SignedPreKey, + pub signed_pre_key: SignedPreKeyEntity, #[serde(with = "serde_public_key")] pub identity_key: PublicKey, #[serde(skip_serializing_if = "Option::is_none")] diff --git a/libsignal-service/src/push_service.rs b/libsignal-service/src/push_service.rs index a7448968a..6c9d7f3d4 100644 --- a/libsignal-service/src/push_service.rs +++ b/libsignal-service/src/push_service.rs @@ -5,8 +5,7 @@ use crate::{ envelope::*, groups_v2::GroupDecodingError, pre_keys::{ - KyberPreKeyEntity, PreKeyEntity, PreKeyState, SignedPreKey, - SignedPreKeyEntity, + KyberPreKeyEntity, PreKeyEntity, PreKeyState, SignedPreKeyEntity, }, profile_cipher::ProfileCipherError, proto::{attachment_pointer::AttachmentIdentifier, AttachmentPointer}, @@ -407,8 +406,8 @@ pub struct StaleDevices { pub struct LinkRequest { pub verification_code: String, pub account_attributes: LinkAccountAttributes, - pub aci_signed_pre_key: SignedPreKey, - pub pni_signed_pre_key: SignedPreKey, + pub aci_signed_pre_key: SignedPreKeyEntity, + pub pni_signed_pre_key: SignedPreKeyEntity, pub aci_pq_last_resort_pre_key: KyberPreKeyEntity, pub pni_pq_last_resort_pre_key: KyberPreKeyEntity, } From 45555916dc594ec16f729db7c1e60f0c703ced4c Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Sun, 14 Jan 2024 20:30:20 +0100 Subject: [PATCH 07/34] WIP pnp_initialize_devices --- libsignal-service/src/account_manager.rs | 106 +++++++++++++++++------ libsignal-service/src/push_service.rs | 17 ++-- 2 files changed, 89 insertions(+), 34 deletions(-) diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index 65e53e499..6b4077994 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -19,8 +19,10 @@ use zkgroup::profiles::ProfileKey; use crate::pre_keys::{KyberPreKeyEntity, PreKeysStore, SignedPreKeyEntity}; use crate::proto::DeviceName; +use crate::provisioning::generate_registration_id; use crate::push_service::{AvatarWrite, RecaptchaAttributes, ServiceIdType}; use crate::sender::OutgoingPushMessage; +use crate::session_store::SessionStoreExt; use crate::utils::BASE64_RELAXED; use crate::ServiceAddress; use crate::{ @@ -600,12 +602,17 @@ impl AccountManager { /// Initialize PNI on linked devices. /// /// 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. pub async fn pnp_initialize_devices< R: rand::Rng + rand::CryptoRng, - P: PreKeysStore, + Aci: PreKeysStore + SessionStoreExt, + Pni: PreKeysStore, >( &mut self, - pni_protocol_store: &mut P, + aci_protocol_store: &mut Aci, + pni_protocol_store: &mut Pni, + local_aci: ServiceAddress, csprng: &mut R, use_last_resort_key: bool, ) -> Result<(), ServiceError> { @@ -613,32 +620,77 @@ impl AccountManager { pni_protocol_store.get_identity_key_pair().await?; let pni_identity_key = pni_identity_key_pair.identity_key(); - let device_messages: Vec; // XXX TODO - let device_pni_signed_prekeys: HashMap<&str, SignedPreKeyEntity>; - let device_pni_last_resort_kyber_prekeys: HashMap< - &str, - KyberPreKeyEntity, - >; - let pni_registration_ids: HashMap<&str, u32>; - let signature_valid_on_each_signed_pre_key: bool; - - // This needs to be repeated for every device that we have linked. - let ( - _pre_keys, - signed_pre_key_entity, - kyber_pre_key_entities, - last_resort_kyber_prekey, - ) = self - .generate_pre_keys( - pni_protocol_store, - ServiceIdType::PhoneNumberIdentity, - csprng, - use_last_resort_key, - 0, - PRE_KEY_BATCH_SIZE, - ) + + // For every linked device, we generate a new set of pre-keys, and send them to the device. + let local_device_ids = aci_protocol_store + .get_sub_device_sessions(&local_aci) .await?; - assert_eq!(_pre_keys.len(), 0); + + let mut device_messages = + Vec::::with_capacity(local_device_ids.len()); + let mut device_pni_signed_prekeys = + HashMap::::with_capacity( + local_device_ids.len(), + ); + let mut device_pni_last_resort_kyber_prekeys = + HashMap::::with_capacity( + local_device_ids.len(), + ); + let mut pni_registration_ids = + HashMap::::with_capacity(local_device_ids.len()); + + let signature_valid_on_each_signed_pre_key = true; + for local_device_id in + std::iter::once(DEFAULT_DEVICE_ID).chain(local_device_ids) + { + let local_protocol_address = + local_aci.to_protocol_address(local_device_id); + let span = tracing::trace_span!( + "filtering devices", + address = %local_protocol_address + ); + // Skip if we don't have a session with the device + if (local_device_id != DEFAULT_DEVICE_ID) + && aci_protocol_store + .load_session(&local_protocol_address) + .instrument(span) + .await? + .is_none() + { + tracing::warn!( + "No session with device {}, skipping PNI provisioning", + local_device_id + ); + continue; + } + + let ( + _pre_keys, + signed_pre_key_entity, + kyber_pre_key_entities, + last_resort_kyber_prekey, + ) = self + .generate_pre_keys( + pni_protocol_store, + ServiceIdType::PhoneNumberIdentity, + csprng, + use_last_resort_key, + 0, + PRE_KEY_BATCH_SIZE, + ) + .await?; + let registration_id = generate_registration_id(csprng); + + let local_device_id_s = local_device_id.to_string(); + device_pni_signed_prekeys + .insert(local_device_id_s.clone(), signed_pre_key_entity); + device_pni_last_resort_kyber_prekeys + .insert(local_device_id_s.clone(), last_resort_kyber_prekey); + pni_registration_ids + .insert(local_device_id_s.clone(), registration_id); + + assert_eq!(_pre_keys.len(), 0); + } self.service .distribute_pni_keys( diff --git a/libsignal-service/src/push_service.rs b/libsignal-service/src/push_service.rs index 6c9d7f3d4..5d4e36703 100644 --- a/libsignal-service/src/push_service.rs +++ b/libsignal-service/src/push_service.rs @@ -1317,22 +1317,25 @@ pub trait PushService: MaybeSend { &mut self, pni_identity_key: &IdentityKey, device_messages: Vec, - device_pni_signed_prekeys: HashMap<&str, SignedPreKeyEntity>, - device_pni_last_resort_kyber_prekeys: HashMap<&str, KyberPreKeyEntity>, - pni_registration_ids: HashMap<&str, u32>, + device_pni_signed_prekeys: HashMap, + device_pni_last_resort_kyber_prekeys: HashMap< + String, + KyberPreKeyEntity, + >, + pni_registration_ids: HashMap, signature_valid_on_each_signed_pre_key: bool, ) -> Result { #[derive(serde::Serialize, Debug)] #[serde(rename_all = "camelCase")] - struct PniKeyDistributionRequest<'a> { + struct PniKeyDistributionRequest { #[serde(with = "serde_base64")] pni_identity_key: Vec, device_messages: Vec, - device_pni_signed_prekeys: HashMap<&'a str, SignedPreKeyEntity>, + device_pni_signed_prekeys: HashMap, #[serde(rename = "devicePniPqLastResortPrekeys")] device_pni_last_resort_kyber_prekeys: - HashMap<&'a str, KyberPreKeyEntity>, - pni_registration_ids: HashMap<&'a str, u32>, + HashMap, + pni_registration_ids: HashMap, signature_valid_on_each_signed_pre_key: bool, } From dc423222b2469dd7c96cc48b6c11c0e7c2b2466e Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Tue, 30 Jan 2024 16:38:46 +0100 Subject: [PATCH 08/34] Implement last resort pre key through extension trait --- libsignal-service/src/account_manager.rs | 54 +++++++++++++++--------- libsignal-service/src/pre_keys.rs | 44 +++++++++++++++++-- 2 files changed, 75 insertions(+), 23 deletions(-) diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index 6b4077994..548b8b395 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -193,18 +193,31 @@ impl AccountManager { SignedPreKeyEntity::try_from(signed_prekey_record)?; let pq_last_resort_key = if use_last_resort_key { - tracing::warn!("Last resort Kyber key unimplemented"); - // Note about the last-resort key: - // mark_kyber_pre_key_used() should retain the last-resort key, but can safely - // remove the ephemeral pre keys. This implies that generating the last-resort key - // should notify the pre-key store, when saving the key, that it concerns a - // last-resort key. I don't see how this can be communicated to the store, and I - // fear that we need to reengineer the whole prekeystore system as a whole. - None - // Some(KyberPreKeyEntity { - // key_id: 0x7fffffff, - // public_key: "NDI=".into(), - // }) + let pre_key_id = (((pq_pre_keys_offset_id + kyber_pre_key_count) + % (PRE_KEY_MEDIUM_MAX_VALUE - 1)) + + 1) + .into(); + + if !pq_pre_key_entities.is_empty() { + assert_eq!( + pq_pre_key_entities.last().unwrap().key_id + 1, + u32::from(pre_key_id) + ); + } + + let pre_key_record = KyberPreKeyRecord::generate( + kem::KeyType::Kyber1024, + pre_key_id, + identity_key_pair.private_key(), + )?; + protocol_store + .store_last_resort_kyber_pre_key(pre_key_id, &pre_key_record) + .instrument(tracing::trace_span!(parent: &span, "save last resort kyber pre key", ?pre_key_id)).await?; + // TODO: Shouldn't this also remove the previous pre-keys from storage? + // I think we might want to update the storage, and then sync the storage to the + // server. + + Some(KyberPreKeyEntity::try_from(pre_key_record)?) } else { None }; @@ -614,7 +627,6 @@ impl AccountManager { pni_protocol_store: &mut Pni, local_aci: ServiceAddress, csprng: &mut R, - use_last_resort_key: bool, ) -> Result<(), ServiceError> { let pni_identity_key_pair = pni_protocol_store.get_identity_key_pair().await?; @@ -663,20 +675,19 @@ impl AccountManager { ); continue; } - let ( _pre_keys, signed_pre_key_entity, - kyber_pre_key_entities, + _kyber_pre_key_entities, last_resort_kyber_prekey, ) = self .generate_pre_keys( pni_protocol_store, ServiceIdType::PhoneNumberIdentity, csprng, - use_last_resort_key, + true, + 0, 0, - PRE_KEY_BATCH_SIZE, ) .await?; let registration_id = generate_registration_id(csprng); @@ -684,12 +695,15 @@ impl AccountManager { let local_device_id_s = local_device_id.to_string(); device_pni_signed_prekeys .insert(local_device_id_s.clone(), signed_pre_key_entity); - device_pni_last_resort_kyber_prekeys - .insert(local_device_id_s.clone(), last_resort_kyber_prekey); + device_pni_last_resort_kyber_prekeys.insert( + local_device_id_s.clone(), + last_resort_kyber_prekey.expect("requested last resort key"), + ); pni_registration_ids .insert(local_device_id_s.clone(), registration_id); - assert_eq!(_pre_keys.len(), 0); + assert!(_pre_keys.is_empty()); + assert!(_kyber_pre_key_entities.is_empty()); } self.service diff --git a/libsignal-service/src/pre_keys.rs b/libsignal-service/src/pre_keys.rs index af6c7d93a..2cb748b17 100644 --- a/libsignal-service/src/pre_keys.rs +++ b/libsignal-service/src/pre_keys.rs @@ -4,18 +4,56 @@ use crate::utils::{serde_base64, serde_public_key}; use async_trait::async_trait; use libsignal_protocol::{ error::SignalProtocolError, kem, GenericSignedPreKey, IdentityKeyStore, - KeyPair, KyberPreKeyRecord, KyberPreKeyStore, PreKeyRecord, PreKeyStore, - PublicKey, SignedPreKeyRecord, SignedPreKeyStore, + KeyPair, KyberPreKeyId, KyberPreKeyRecord, KyberPreKeyStore, PreKeyRecord, + PreKeyStore, PublicKey, SignedPreKeyRecord, SignedPreKeyStore, }; use serde::{Deserialize, Serialize}; +#[async_trait(?Send)] +/// Additional methods for the Kyber pre key store +/// +/// Analogue of Android's ServiceKyberPreKeyStore +pub trait ServiceKyberPreKeyStore: KyberPreKeyStore { + async fn store_last_resort_kyber_pre_key( + &mut self, + kyber_prekey_id: KyberPreKeyId, + record: &KyberPreKeyRecord, + ) -> Result<(), SignalProtocolError>; + + async fn load_last_resort_kyber_pre_keys( + &self, + ) -> Result, SignalProtocolError>; + + async fn remove_kyber_pre_key( + &mut self, + kyber_prekey_id: KyberPreKeyId, + ) -> Result<(), SignalProtocolError>; + + /// Analogous to markAllOneTimeKyberPreKeysStaleIfNecessary + async fn mark_all_one_time_kyber_pre_keys_stale_if_necessary( + &mut self, + stale_time: chrono::DateTime, + ) -> Result<(), SignalProtocolError>; + + /// Analogue of deleteAllStaleOneTimeKyberPreKeys + async fn delete_all_stale_one_time_kyber_pre_keys( + &mut self, + threshold: chrono::DateTime, + min_count: usize, + ) -> Result<(), SignalProtocolError>; +} + /// Stores the ID of keys published ahead of time /// /// #[async_trait(?Send)] pub trait PreKeysStore: - PreKeyStore + IdentityKeyStore + SignedPreKeyStore + KyberPreKeyStore + PreKeyStore + + IdentityKeyStore + + SignedPreKeyStore + + KyberPreKeyStore + + ServiceKyberPreKeyStore { /// ID of the next pre key async fn next_pre_key_id(&self) -> Result; From b57d4d6e2268bb3d36e073bf96f869ced123fa7e Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Tue, 30 Jan 2024 17:27:31 +0100 Subject: [PATCH 09/34] Deduplicate pre key gen code for device linking --- libsignal-service/src/account_manager.rs | 3 +- libsignal-service/src/pre_keys.rs | 48 ---------------- libsignal-service/src/provisioning/mod.rs | 69 +++++++++++++++-------- 3 files changed, 49 insertions(+), 71 deletions(-) diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index 548b8b395..b472e7021 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -88,7 +88,8 @@ impl AccountManager { } #[tracing::instrument(skip(self, protocol_store, csprng))] - async fn generate_pre_keys< + // XXX: Maybe refactor this and move into pre_keys.rs + pub(crate) async fn generate_pre_keys< R: rand::Rng + rand::CryptoRng, P: PreKeysStore, >( diff --git a/libsignal-service/src/pre_keys.rs b/libsignal-service/src/pre_keys.rs index 2cb748b17..e4cc264c1 100644 --- a/libsignal-service/src/pre_keys.rs +++ b/libsignal-service/src/pre_keys.rs @@ -157,51 +157,3 @@ pub struct PreKeyState { pub pq_last_resort_key: Option, pub pq_pre_keys: Vec, } - -pub(crate) async fn generate_last_resort_kyber_key( - store: &mut S, - identity_key: &KeyPair, -) -> Result { - let id = store.next_pq_pre_key_id().await?; - let id = id.max(1); // TODO: Hack, keys start with 1 - - let record = KyberPreKeyRecord::generate( - kem::KeyType::Kyber1024, - id.into(), - &identity_key.private_key, - )?; - - store.save_kyber_pre_key(id.into(), &record).await?; - store.set_next_pq_pre_key_id(id + 1).await?; - - Ok(record) -} - -pub(crate) async fn generate_signed_pre_key< - S: PreKeysStore, - R: rand::Rng + rand::CryptoRng, ->( - store: &mut S, - csprng: &mut R, - identity_key: &KeyPair, -) -> Result { - let id = store.next_signed_pre_key_id().await?; - let id = id.max(1); // TODO: Hack, keys start with 1 - - let key_pair = KeyPair::generate(csprng); - let signature = identity_key - .private_key - .calculate_signature(&key_pair.public_key.serialize(), csprng)?; - - let unix_time = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_millis() as u64; - let record = - SignedPreKeyRecord::new(id.into(), unix_time, &key_pair, &signature); - - store.save_signed_pre_key(id.into(), &record).await?; - store.set_next_signed_pre_key_id(id + 1).await?; - - Ok(record) -} diff --git a/libsignal-service/src/provisioning/mod.rs b/libsignal-service/src/provisioning/mod.rs index 610891f49..2896140f0 100644 --- a/libsignal-service/src/provisioning/mod.rs +++ b/libsignal-service/src/provisioning/mod.rs @@ -10,7 +10,9 @@ use base64::Engine; use derivative::Derivative; use futures::StreamExt; use futures::{channel::mpsc::Sender, pin_mut, SinkExt}; -use libsignal_protocol::{DeviceId, KeyPair, PrivateKey, PublicKey}; +use libsignal_protocol::{ + DeviceId, KeyPair, PrivateKey, PublicKey, SessionStore, +}; use prost::Message; use serde::{Deserialize, Serialize}; use url::Url; @@ -20,12 +22,12 @@ use zkgroup::profiles::ProfileKey; use pipe::{ProvisioningPipe, ProvisioningStep}; use crate::prelude::ServiceError; +use crate::push_service::ServiceIdType; use crate::utils::BASE64_RELAXED; +use crate::AccountManager; use crate::{ account_manager::encrypt_device_name, - pre_keys::{ - generate_last_resort_kyber_key, generate_signed_pre_key, PreKeysStore, - }, + pre_keys::PreKeysStore, push_service::{ HttpAuth, LinkAccountAttributes, LinkCapabilities, LinkRequest, LinkResponse, PushService, ServiceIds, @@ -110,9 +112,9 @@ pub struct NewDeviceRegistration { pub async fn link_device< R: rand::Rng + rand::CryptoRng, - Aci: PreKeysStore, + Aci: PreKeysStore + SessionStore, Pni: PreKeysStore, - P: PushService, + P: PushService + Clone, >( aci_store: &mut Aci, pni_store: &mut Pni, @@ -211,18 +213,43 @@ pub async fn link_device< }, )?; - let aci_key_pair = KeyPair::new(aci_public_key, aci_private_key); - let pni_key_pair = KeyPair::new(pni_public_key, pni_private_key); - + let mut am = AccountManager::new(push_service.clone(), None); + + let ( + _aci_pre_keys, + aci_signed_pre_key, + _aci_pq_pre_keys, + aci_pq_last_resort_pre_key, + ) = am + .generate_pre_keys( + aci_store, + ServiceIdType::AccountIdentity, + csprng, + true, + 0, + 0, + ) + .await?; let aci_pq_last_resort_pre_key = - generate_last_resort_kyber_key(aci_store, &aci_key_pair).await?; + aci_pq_last_resort_pre_key.expect("requested last resort key"); + + let ( + _pni_pre_keys, + pni_signed_pre_key, + _pni_pq_pre_keys, + pni_pq_last_resort_pre_key, + ) = am + .generate_pre_keys( + pni_store, + ServiceIdType::PhoneNumberIdentity, + csprng, + true, + 0, + 0, + ) + .await?; let pni_pq_last_resort_pre_key = - generate_last_resort_kyber_key(pni_store, &pni_key_pair).await?; - - let aci_signed_pre_key = - generate_signed_pre_key(aci_store, csprng, &aci_key_pair).await?; - let pni_signed_pre_key = - generate_signed_pre_key(pni_store, csprng, &pni_key_pair).await?; + pni_pq_last_resort_pre_key.expect("requested last resort key"); let encrypted_device_name = BASE64_RELAXED.encode( encrypt_device_name(csprng, device_name, &aci_public_key)? @@ -245,12 +272,10 @@ pub async fn link_device< capabilities: LinkCapabilities { pni: true }, name: encrypted_device_name, }, - aci_signed_pre_key: aci_signed_pre_key.try_into()?, - pni_signed_pre_key: pni_signed_pre_key.try_into()?, - aci_pq_last_resort_pre_key: aci_pq_last_resort_pre_key - .try_into()?, - pni_pq_last_resort_pre_key: pni_pq_last_resort_pre_key - .try_into()?, + aci_signed_pre_key, + pni_signed_pre_key, + aci_pq_last_resort_pre_key, + pni_pq_last_resort_pre_key, }; let LinkResponse { From 6c7b1ccbd35bd906977e54962f1597ec97036b17 Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Tue, 30 Jan 2024 17:56:31 +0100 Subject: [PATCH 10/34] Move generate_pre_keys into pre_keys.rs --- libsignal-service/src/account_manager.rs | 201 +++------------------- libsignal-service/src/pre_keys.rs | 144 +++++++++++++++- libsignal-service/src/provisioning/mod.rs | 51 +++--- 3 files changed, 194 insertions(+), 202 deletions(-) diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index b472e7021..a99d5b466 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -1,15 +1,12 @@ use base64::prelude::*; use std::collections::HashMap; -use std::convert::{TryFrom, TryInto}; -use std::time::SystemTime; +use std::convert::TryInto; use aes::cipher::{KeyIvInit, StreamCipher as _}; use hmac::digest::Output; use hmac::{Hmac, Mac}; use libsignal_protocol::{ - kem, GenericSignedPreKey, IdentityKeyStore, KeyPair, KyberPreKeyRecord, - PreKeyRecord, PrivateKey, PublicKey, SignalProtocolError, - SignedPreKeyRecord, + IdentityKeyStore, KeyPair, PrivateKey, PublicKey, SignalProtocolError, }; use prost::Message; use serde::{Deserialize, Serialize}; @@ -17,7 +14,10 @@ use sha2::Sha256; use tracing_futures::Instrument; use zkgroup::profiles::ProfileKey; -use crate::pre_keys::{KyberPreKeyEntity, PreKeysStore, SignedPreKeyEntity}; +use crate::pre_keys::{ + KyberPreKeyEntity, PreKeysStore, SignedPreKeyEntity, PRE_KEY_BATCH_SIZE, + PRE_KEY_MINIMUM, +}; use crate::proto::DeviceName; use crate::provisioning::generate_registration_id; use crate::push_service::{AvatarWrite, RecaptchaAttributes, ServiceIdType}; @@ -27,7 +27,7 @@ use crate::utils::BASE64_RELAXED; use crate::ServiceAddress; use crate::{ configuration::{Endpoint, ServiceCredentials}, - pre_keys::{PreKeyEntity, PreKeyState}, + pre_keys::PreKeyState, profile_cipher::{ProfileCipher, ProfileCipherError}, profile_name::ProfileName, proto::{ProvisionEnvelope, ProvisionMessage, ProvisioningVersion}, @@ -75,10 +75,6 @@ pub struct Profile { pub avatar: Option, } -const PRE_KEY_MINIMUM: u32 = 10; -const PRE_KEY_BATCH_SIZE: u32 = 100; -const PRE_KEY_MEDIUM_MAX_VALUE: u32 = 0xFFFFFF; - impl AccountManager { pub fn new(service: Service, profile_key: Option) -> Self { Self { @@ -87,150 +83,6 @@ impl AccountManager { } } - #[tracing::instrument(skip(self, protocol_store, csprng))] - // XXX: Maybe refactor this and move into pre_keys.rs - pub(crate) async fn generate_pre_keys< - R: rand::Rng + rand::CryptoRng, - P: PreKeysStore, - >( - &mut self, - protocol_store: &mut P, - service_id_type: ServiceIdType, - csprng: &mut R, - use_last_resort_key: bool, - pre_key_count: u32, - kyber_pre_key_count: u32, - ) -> Result< - ( - Vec, - SignedPreKeyEntity, - Vec, - Option, - ), - ServiceError, - > { - let pre_keys_offset_id = protocol_store.next_pre_key_id().await?; - let next_signed_pre_key_id = - protocol_store.next_signed_pre_key_id().await?; - let pq_pre_keys_offset_id = protocol_store.next_pq_pre_key_id().await?; - - let span = tracing::span!(tracing::Level::DEBUG, "Generating pre keys"); - - let identity_key_pair = protocol_store - .get_identity_key_pair() - .instrument( - tracing::trace_span!(parent: &span, "get identity key pair"), - ) - .await?; - - let mut pre_key_entities = vec![]; - let mut pq_pre_key_entities = vec![]; - - // EC keys - for i in 0..pre_key_count { - let key_pair = KeyPair::generate(csprng); - let pre_key_id = (((pre_keys_offset_id + i) - % (PRE_KEY_MEDIUM_MAX_VALUE - 1)) - + 1) - .into(); - let pre_key_record = PreKeyRecord::new(pre_key_id, &key_pair); - protocol_store - .save_pre_key(pre_key_id, &pre_key_record) - .instrument(tracing::trace_span!(parent: &span, "save pre key", ?pre_key_id)).await?; - // TODO: Shouldn't this also remove the previous pre-keys from storage? - // I think we might want to update the storage, and then sync the storage to the - // server. - - pre_key_entities.push(PreKeyEntity::try_from(pre_key_record)?); - } - - // Kyber keys - for i in 0..kyber_pre_key_count { - let pre_key_id = (((pq_pre_keys_offset_id + i) - % (PRE_KEY_MEDIUM_MAX_VALUE - 1)) - + 1) - .into(); - let pre_key_record = KyberPreKeyRecord::generate( - kem::KeyType::Kyber1024, - pre_key_id, - identity_key_pair.private_key(), - )?; - protocol_store - .save_kyber_pre_key(pre_key_id, &pre_key_record) - .instrument(tracing::trace_span!(parent: &span, "save kyber pre key", ?pre_key_id)).await?; - // TODO: Shouldn't this also remove the previous pre-keys from storage? - // I think we might want to update the storage, and then sync the storage to the - // server. - - pq_pre_key_entities - .push(KyberPreKeyEntity::try_from(pre_key_record)?); - } - - // Generate and store the next signed prekey - let signed_pre_key_pair = KeyPair::generate(csprng); - let signed_pre_key_public = signed_pre_key_pair.public_key; - let signed_pre_key_signature = identity_key_pair - .private_key() - .calculate_signature(&signed_pre_key_public.serialize(), csprng)?; - - let unix_time = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap(); - - let signed_prekey_record = SignedPreKeyRecord::new( - next_signed_pre_key_id.into(), - unix_time.as_millis() as u64, - &signed_pre_key_pair, - &signed_pre_key_signature, - ); - - protocol_store - .save_signed_pre_key( - next_signed_pre_key_id.into(), - &signed_prekey_record, - ) - .instrument(tracing::trace_span!(parent: &span, "save signed pre key", signed_pre_key_id = ?next_signed_pre_key_id)).await?; - let signed_prekey_entity = - SignedPreKeyEntity::try_from(signed_prekey_record)?; - - let pq_last_resort_key = if use_last_resort_key { - let pre_key_id = (((pq_pre_keys_offset_id + kyber_pre_key_count) - % (PRE_KEY_MEDIUM_MAX_VALUE - 1)) - + 1) - .into(); - - if !pq_pre_key_entities.is_empty() { - assert_eq!( - pq_pre_key_entities.last().unwrap().key_id + 1, - u32::from(pre_key_id) - ); - } - - let pre_key_record = KyberPreKeyRecord::generate( - kem::KeyType::Kyber1024, - pre_key_id, - identity_key_pair.private_key(), - )?; - protocol_store - .store_last_resort_kyber_pre_key(pre_key_id, &pre_key_record) - .instrument(tracing::trace_span!(parent: &span, "save last resort kyber pre key", ?pre_key_id)).await?; - // TODO: Shouldn't this also remove the previous pre-keys from storage? - // I think we might want to update the storage, and then sync the storage to the - // server. - - Some(KyberPreKeyEntity::try_from(pre_key_record)?) - } else { - None - }; - - Ok(( - pre_key_entities, - signed_prekey_entity, - pq_pre_key_entities, - pq_last_resort_key, - )) - } - /// Checks the availability of pre-keys, and updates them as necessary. /// /// Parameters are the protocol's `StoreContext`, and the offsets for the next pre-key and @@ -279,10 +131,15 @@ impl AccountManager { return Ok(()); } - let (pre_keys, signed_pre_key, pq_pre_keys, pq_last_resort_key) = self - .generate_pre_keys( + let identity_key_pair = protocol_store + .get_identity_key_pair() + .instrument(tracing::trace_span!("get identity key pair")) + .await?; + + let (pre_keys, signed_pre_key, pq_pre_keys, pq_last_resort_key) = + crate::pre_keys::generate_pre_keys( protocol_store, - service_id_type, + &identity_key_pair, csprng, use_last_resort_key, PRE_KEY_BATCH_SIZE, @@ -290,13 +147,8 @@ impl AccountManager { ) .await?; - let identity_key = protocol_store - .get_identity_key_pair() - .instrument(tracing::trace_span!("get identity key pair")) - .await? - .identity_key() - .public_key() - .clone(); + let identity_key = + identity_key_pair.identity_key().public_key().clone(); let pre_key_state = PreKeyState { pre_keys, @@ -681,16 +533,15 @@ impl AccountManager { signed_pre_key_entity, _kyber_pre_key_entities, last_resort_kyber_prekey, - ) = self - .generate_pre_keys( - pni_protocol_store, - ServiceIdType::PhoneNumberIdentity, - csprng, - true, - 0, - 0, - ) - .await?; + ) = crate::pre_keys::generate_pre_keys( + pni_protocol_store, + &pni_identity_key_pair, + csprng, + true, + 0, + 0, + ) + .await?; let registration_id = generate_registration_id(csprng); let local_device_id_s = local_device_id.to_string(); diff --git a/libsignal-service/src/pre_keys.rs b/libsignal-service/src/pre_keys.rs index e4cc264c1..901aa7496 100644 --- a/libsignal-service/src/pre_keys.rs +++ b/libsignal-service/src/pre_keys.rs @@ -3,12 +3,14 @@ use std::{convert::TryFrom, time::SystemTime}; use crate::utils::{serde_base64, serde_public_key}; use async_trait::async_trait; use libsignal_protocol::{ - error::SignalProtocolError, kem, GenericSignedPreKey, IdentityKeyStore, - KeyPair, KyberPreKeyId, KyberPreKeyRecord, KyberPreKeyStore, PreKeyRecord, - PreKeyStore, PublicKey, SignedPreKeyRecord, SignedPreKeyStore, + error::SignalProtocolError, kem, GenericSignedPreKey, IdentityKeyPair, + IdentityKeyStore, KeyPair, KyberPreKeyId, KyberPreKeyRecord, + KyberPreKeyStore, PreKeyRecord, PreKeyStore, PublicKey, SignedPreKeyRecord, + SignedPreKeyStore, }; use serde::{Deserialize, Serialize}; +use tracing::Instrument; #[async_trait(?Send)] /// Additional methods for the Kyber pre key store @@ -157,3 +159,139 @@ pub struct PreKeyState { pub pq_last_resort_key: Option, pub pq_pre_keys: Vec, } + +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 generate_pre_keys< + R: rand::Rng + rand::CryptoRng, + P: PreKeysStore, +>( + protocol_store: &mut P, + identity_key_pair: &IdentityKeyPair, + csprng: &mut R, + use_last_resort_key: bool, + pre_key_count: u32, + kyber_pre_key_count: u32, +) -> Result< + ( + Vec, + SignedPreKeyEntity, + Vec, + Option, + ), + SignalProtocolError, +> { + let pre_keys_offset_id = protocol_store.next_pre_key_id().await?; + let next_signed_pre_key_id = + protocol_store.next_signed_pre_key_id().await?; + let pq_pre_keys_offset_id = protocol_store.next_pq_pre_key_id().await?; + + let span = tracing::span!(tracing::Level::DEBUG, "Generating pre keys"); + + let mut pre_key_entities = vec![]; + let mut pq_pre_key_entities = vec![]; + + // EC keys + for i in 0..pre_key_count { + let key_pair = KeyPair::generate(csprng); + let pre_key_id = + (((pre_keys_offset_id + i) % (PRE_KEY_MEDIUM_MAX_VALUE - 1)) + 1) + .into(); + let pre_key_record = PreKeyRecord::new(pre_key_id, &key_pair); + protocol_store + .save_pre_key(pre_key_id, &pre_key_record) + .instrument(tracing::trace_span!(parent: &span, "save pre key", ?pre_key_id)).await?; + // TODO: Shouldn't this also remove the previous pre-keys from storage? + // I think we might want to update the storage, and then sync the storage to the + // server. + + pre_key_entities.push(PreKeyEntity::try_from(pre_key_record)?); + } + + // Kyber keys + for i in 0..kyber_pre_key_count { + let pre_key_id = (((pq_pre_keys_offset_id + i) + % (PRE_KEY_MEDIUM_MAX_VALUE - 1)) + + 1) + .into(); + let pre_key_record = KyberPreKeyRecord::generate( + kem::KeyType::Kyber1024, + pre_key_id, + identity_key_pair.private_key(), + )?; + protocol_store + .save_kyber_pre_key(pre_key_id, &pre_key_record) + .instrument(tracing::trace_span!(parent: &span, "save kyber pre key", ?pre_key_id)).await?; + // TODO: Shouldn't this also remove the previous pre-keys from storage? + // I think we might want to update the storage, and then sync the storage to the + // server. + + pq_pre_key_entities.push(KyberPreKeyEntity::try_from(pre_key_record)?); + } + + // Generate and store the next signed prekey + let signed_pre_key_pair = KeyPair::generate(csprng); + let signed_pre_key_public = signed_pre_key_pair.public_key; + let signed_pre_key_signature = identity_key_pair + .private_key() + .calculate_signature(&signed_pre_key_public.serialize(), csprng)?; + + let unix_time = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap(); + + let signed_prekey_record = SignedPreKeyRecord::new( + next_signed_pre_key_id.into(), + unix_time.as_millis() as u64, + &signed_pre_key_pair, + &signed_pre_key_signature, + ); + + protocol_store + .save_signed_pre_key( + next_signed_pre_key_id.into(), + &signed_prekey_record, + ) + .instrument(tracing::trace_span!(parent: &span, "save signed pre key", signed_pre_key_id = ?next_signed_pre_key_id)).await?; + let signed_prekey_entity = + SignedPreKeyEntity::try_from(signed_prekey_record)?; + + let pq_last_resort_key = if use_last_resort_key { + let pre_key_id = (((pq_pre_keys_offset_id + kyber_pre_key_count) + % (PRE_KEY_MEDIUM_MAX_VALUE - 1)) + + 1) + .into(); + + if !pq_pre_key_entities.is_empty() { + assert_eq!( + pq_pre_key_entities.last().unwrap().key_id + 1, + u32::from(pre_key_id) + ); + } + + let pre_key_record = KyberPreKeyRecord::generate( + kem::KeyType::Kyber1024, + pre_key_id, + identity_key_pair.private_key(), + )?; + protocol_store + .store_last_resort_kyber_pre_key(pre_key_id, &pre_key_record) + .instrument(tracing::trace_span!(parent: &span, "save last resort kyber pre key", ?pre_key_id)).await?; + // TODO: Shouldn't this also remove the previous pre-keys from storage? + // I think we might want to update the storage, and then sync the storage to the + // server. + + Some(KyberPreKeyEntity::try_from(pre_key_record)?) + } else { + None + }; + + Ok(( + pre_key_entities, + signed_prekey_entity, + pq_pre_key_entities, + pq_last_resort_key, + )) +} diff --git a/libsignal-service/src/provisioning/mod.rs b/libsignal-service/src/provisioning/mod.rs index 2896140f0..7b0357870 100644 --- a/libsignal-service/src/provisioning/mod.rs +++ b/libsignal-service/src/provisioning/mod.rs @@ -11,7 +11,7 @@ use derivative::Derivative; use futures::StreamExt; use futures::{channel::mpsc::Sender, pin_mut, SinkExt}; use libsignal_protocol::{ - DeviceId, KeyPair, PrivateKey, PublicKey, SessionStore, + DeviceId, IdentityKey, IdentityKeyPair, PrivateKey, PublicKey, SessionStore, }; use prost::Message; use serde::{Deserialize, Serialize}; @@ -22,9 +22,7 @@ use zkgroup::profiles::ProfileKey; use pipe::{ProvisioningPipe, ProvisioningStep}; use crate::prelude::ServiceError; -use crate::push_service::ServiceIdType; use crate::utils::BASE64_RELAXED; -use crate::AccountManager; use crate::{ account_manager::encrypt_device_name, pre_keys::PreKeysStore, @@ -213,23 +211,29 @@ pub async fn link_device< }, )?; - let mut am = AccountManager::new(push_service.clone(), None); + let aci_key_pair = IdentityKeyPair::new( + IdentityKey::new(aci_public_key), + aci_private_key, + ); + let pni_key_pair = IdentityKeyPair::new( + IdentityKey::new(pni_public_key), + pni_private_key, + ); let ( _aci_pre_keys, aci_signed_pre_key, _aci_pq_pre_keys, aci_pq_last_resort_pre_key, - ) = am - .generate_pre_keys( - aci_store, - ServiceIdType::AccountIdentity, - csprng, - true, - 0, - 0, - ) - .await?; + ) = crate::pre_keys::generate_pre_keys( + aci_store, + &aci_key_pair, + csprng, + true, + 0, + 0, + ) + .await?; let aci_pq_last_resort_pre_key = aci_pq_last_resort_pre_key.expect("requested last resort key"); @@ -238,16 +242,15 @@ pub async fn link_device< pni_signed_pre_key, _pni_pq_pre_keys, pni_pq_last_resort_pre_key, - ) = am - .generate_pre_keys( - pni_store, - ServiceIdType::PhoneNumberIdentity, - csprng, - true, - 0, - 0, - ) - .await?; + ) = crate::pre_keys::generate_pre_keys( + pni_store, + &pni_key_pair, + csprng, + true, + 0, + 0, + ) + .await?; let pni_pq_last_resort_pre_key = pni_pq_last_resort_pre_key.expect("requested last resort key"); From 78aa4a80d8aea8c06e6f481f5e82af1c935b2587 Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Tue, 30 Jan 2024 18:01:37 +0100 Subject: [PATCH 11/34] Use IdentityKey where it makes sense --- libsignal-service/src/account_manager.rs | 17 +++++++++-------- libsignal-service/src/provisioning/mod.rs | 18 ++++++++---------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index a99d5b466..518ce2c4b 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -6,7 +6,8 @@ use aes::cipher::{KeyIvInit, StreamCipher as _}; use hmac::digest::Output; use hmac::{Hmac, Mac}; use libsignal_protocol::{ - IdentityKeyStore, KeyPair, PrivateKey, PublicKey, SignalProtocolError, + IdentityKey, IdentityKeyStore, KeyPair, PrivateKey, PublicKey, + SignalProtocolError, }; use prost::Message; use serde::{Deserialize, Serialize}; @@ -408,7 +409,7 @@ impl AccountManager { pub async fn update_device_name( &mut self, device_name: &str, - public_key: &PublicKey, + public_key: &IdentityKey, ) -> Result<(), ServiceError> { let encrypted_device_name = encrypt_device_name( &mut rand::thread_rng(), @@ -586,14 +587,14 @@ fn calculate_hmac256( pub fn encrypt_device_name( csprng: &mut R, device_name: &str, - identity_public: &PublicKey, + identity_public: &IdentityKey, ) -> Result { let plaintext = device_name.as_bytes().to_vec(); let ephemeral_key_pair = KeyPair::generate(csprng); let master_secret = ephemeral_key_pair .private_key - .calculate_agreement(identity_public)?; + .calculate_agreement(identity_public.public_key())?; let key1 = calculate_hmac256(&master_secret, b"auth")?; let synthetic_iv = calculate_hmac256(&key1, &plaintext)?; @@ -663,7 +664,7 @@ pub fn decrypt_device_name( mod tests { use crate::utils::BASE64_RELAXED; use base64::Engine; - use libsignal_protocol::{KeyPair, PrivateKey, PublicKey}; + use libsignal_protocol::{IdentityKeyPair, PrivateKey, PublicKey}; use super::DeviceName; @@ -671,16 +672,16 @@ mod tests { fn encrypt_device_name() -> anyhow::Result<()> { let input_device_name = "Nokia 3310 Millenial Edition"; let mut csprng = rand::thread_rng(); - let identity = KeyPair::generate(&mut csprng); + let identity = IdentityKeyPair::generate(&mut csprng); let device_name = super::encrypt_device_name( &mut csprng, input_device_name, - &identity.public_key, + &identity.identity_key(), )?; let decrypted_device_name = - super::decrypt_device_name(&identity.private_key, &device_name)?; + super::decrypt_device_name(&identity.private_key(), &device_name)?; assert_eq!(input_device_name, decrypted_device_name); diff --git a/libsignal-service/src/provisioning/mod.rs b/libsignal-service/src/provisioning/mod.rs index 7b0357870..2c6006a02 100644 --- a/libsignal-service/src/provisioning/mod.rs +++ b/libsignal-service/src/provisioning/mod.rs @@ -100,10 +100,10 @@ pub struct NewDeviceRegistration { pub service_ids: ServiceIds, #[derivative(Debug = "ignore")] pub aci_private_key: PrivateKey, - pub aci_public_key: PublicKey, + pub aci_public_key: IdentityKey, #[derivative(Debug = "ignore")] pub pni_private_key: PrivateKey, - pub pni_public_key: PublicKey, + pub pni_public_key: IdentityKey, #[derivative(Debug = "ignore")] pub profile_key: ProfileKey, } @@ -166,6 +166,7 @@ pub async fn link_device< reason: "missing public key".into(), }, )?)?; + let aci_public_key = IdentityKey::new(aci_public_key); let aci_private_key = PrivateKey::deserialize(&message.aci_identity_key_private.ok_or( @@ -180,6 +181,7 @@ pub async fn link_device< reason: "missing public key".into(), }, )?)?; + let pni_public_key = IdentityKey::new(pni_public_key); let pni_private_key = PrivateKey::deserialize(&message.pni_identity_key_private.ok_or( @@ -211,14 +213,10 @@ pub async fn link_device< }, )?; - let aci_key_pair = IdentityKeyPair::new( - IdentityKey::new(aci_public_key), - aci_private_key, - ); - let pni_key_pair = IdentityKeyPair::new( - IdentityKey::new(pni_public_key), - pni_private_key, - ); + let aci_key_pair = + IdentityKeyPair::new(aci_public_key, aci_private_key); + let pni_key_pair = + IdentityKeyPair::new(pni_public_key, pni_private_key); let ( _aci_pre_keys, From f8ea08649fff1711917f7f02ca67449f7f0fe9ac Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Sat, 9 Mar 2024 11:37:45 +0100 Subject: [PATCH 12/34] Don't generate last resort key if already stored --- libsignal-service/src/account_manager.rs | 26 ++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index 518ce2c4b..58e0adc96 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -1,6 +1,7 @@ use base64::prelude::*; use std::collections::HashMap; -use std::convert::TryInto; +use std::convert::{TryFrom, TryInto}; +use std::time::SystemTime; use aes::cipher::{KeyIvInit, StreamCipher as _}; use hmac::digest::Output; @@ -137,19 +138,36 @@ impl AccountManager { .instrument(tracing::trace_span!("get identity key pair")) .await?; + let last_resort_keys = protocol_store + .load_last_resort_kyber_pre_keys() + .instrument(tracing::trace_span!("fetch resort key")) + .await?; + // XXX: Maybe this check should be done in the generate_pre_keys function? + let has_last_resort_key = !last_resort_keys.is_empty(); + let (pre_keys, signed_pre_key, pq_pre_keys, pq_last_resort_key) = crate::pre_keys::generate_pre_keys( protocol_store, &identity_key_pair, csprng, - use_last_resort_key, + use_last_resort_key && !has_last_resort_key, PRE_KEY_BATCH_SIZE, PRE_KEY_BATCH_SIZE, ) .await?; - let identity_key = - identity_key_pair.identity_key().public_key().clone(); + let pq_last_resort_key = if has_last_resort_key { + if last_resort_keys.len() > 1 { + tracing::warn!( + "More than one last resort key found; only uploading first" + ); + } + Some(KyberPreKeyEntity::try_from(last_resort_keys[0].clone())?) + } else { + pq_last_resort_key + }; + + let identity_key = *identity_key_pair.identity_key().public_key(); let pre_key_state = PreKeyState { pre_keys, From 5b8b840b216e2e9a549b8002fefddac9b8c67824 Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Sat, 9 Mar 2024 12:32:19 +0100 Subject: [PATCH 13/34] WIP distribution message --- libsignal-service/src/account_manager.rs | 54 +++++++++++++++++++++--- libsignal-service/src/sender.rs | 2 +- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index 58e0adc96..55b62b69b 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -7,8 +7,8 @@ use aes::cipher::{KeyIvInit, StreamCipher as _}; use hmac::digest::Output; use hmac::{Hmac, Mac}; use libsignal_protocol::{ - IdentityKey, IdentityKeyStore, KeyPair, PrivateKey, PublicKey, - SignalProtocolError, + IdentityKey, IdentityKeyStore, KeyPair, PrivateKey, ProtocolStore, + PublicKey, SenderKeyStore, SignalProtocolError, }; use prost::Message; use serde::{Deserialize, Serialize}; @@ -16,11 +16,14 @@ use sha2::Sha256; use tracing_futures::Instrument; use zkgroup::profiles::ProfileKey; +use crate::content::ContentBody; use crate::pre_keys::{ KyberPreKeyEntity, PreKeysStore, SignedPreKeyEntity, PRE_KEY_BATCH_SIZE, PRE_KEY_MINIMUM, }; -use crate::proto::DeviceName; +use crate::prelude::{MessageSender, MessageSenderError}; +use crate::proto::sync_message::PniChangeNumber; +use crate::proto::{DeviceName, SyncMessage}; use crate::provisioning::generate_registration_id; use crate::push_service::{AvatarWrite, RecaptchaAttributes, ServiceIdType}; use crate::sender::OutgoingPushMessage; @@ -490,16 +493,26 @@ impl AccountManager { /// /// This is the equivalent of Android's PnpInitializeDevicesJob or iOS' PniHelloWorldManager. pub async fn pnp_initialize_devices< - R: rand::Rng + rand::CryptoRng, - Aci: PreKeysStore + SessionStoreExt, + // XXX So many constraints here, all imposed by the MessageSender + R: rand::Rng + rand::CryptoRng + Clone, + Aci: PreKeysStore + + ProtocolStore + + SenderKeyStore + + SessionStoreExt + + Sync + + Clone, Pni: PreKeysStore, >( &mut self, aci_protocol_store: &mut Aci, pni_protocol_store: &mut Pni, + sender: MessageSender, local_aci: ServiceAddress, csprng: &mut R, - ) -> Result<(), ServiceError> { + ) -> Result<(), MessageSenderError> + where + Service: Clone, + { let pni_identity_key_pair = pni_protocol_store.get_identity_key_pair().await?; @@ -575,6 +588,35 @@ impl AccountManager { assert!(_pre_keys.is_empty()); assert!(_kyber_pre_key_entities.is_empty()); + + if local_device_id == DEFAULT_DEVICE_ID { + // This is the primary device + // We don't need to send a message to the primary device + continue; + } + // cfr. SignalServiceMessageSender::getEncryptedSyncPniInitializeDeviceMessage + let msg = SyncMessage { + pni_change_number: Some(PniChangeNumber { + identity_key_pair: Some( + pni_identity_key_pair.serialize().to_vec(), + ), + signed_pre_key: todo!(), + last_resort_kyber_pre_key: todo!(), + registration_id: todo!(), + new_e164: todo!(), + }), + ..SyncMessage::default() + }; + let content: ContentBody = msg.into(); + let msg = sender + .create_encrypted_message( + &local_aci, + None, + local_device_id.into(), + &content.into_proto().encode_to_vec(), + ) + .await?; + device_messages.push(msg); } self.service diff --git a/libsignal-service/src/sender.rs b/libsignal-service/src/sender.rs index 1f434bc2d..2f30f41f8 100644 --- a/libsignal-service/src/sender.rs +++ b/libsignal-service/src/sender.rs @@ -725,7 +725,7 @@ where skip(self, unidentified_access, content), fields(unidentified_access = unidentified_access.is_some()), )] - async fn create_encrypted_message( + pub(crate) async fn create_encrypted_message( &mut self, recipient: &ServiceAddress, unidentified_access: Option<&SenderCertificate>, From 30e5ef543a6ea2a97d13b0e826dc47bc36a8f28d Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Sat, 9 Mar 2024 14:06:21 +0100 Subject: [PATCH 14/34] Remove Clone bound on some generics --- libsignal-service/src/account_manager.rs | 7 ++----- libsignal-service/src/cipher.rs | 2 +- libsignal-service/src/sender.rs | 4 ++-- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index 55b62b69b..a1cd284c5 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -494,7 +494,7 @@ impl AccountManager { /// This is the equivalent of Android's PnpInitializeDevicesJob or iOS' PniHelloWorldManager. pub async fn pnp_initialize_devices< // XXX So many constraints here, all imposed by the MessageSender - R: rand::Rng + rand::CryptoRng + Clone, + R: rand::Rng + rand::CryptoRng, Aci: PreKeysStore + ProtocolStore + SenderKeyStore @@ -509,10 +509,7 @@ impl AccountManager { sender: MessageSender, local_aci: ServiceAddress, csprng: &mut R, - ) -> Result<(), MessageSenderError> - where - Service: Clone, - { + ) -> Result<(), MessageSenderError> { let pni_identity_key_pair = pni_protocol_store.get_identity_key_pair().await?; diff --git a/libsignal-service/src/cipher.rs b/libsignal-service/src/cipher.rs index 8d7318757..7e65c4f7e 100644 --- a/libsignal-service/src/cipher.rs +++ b/libsignal-service/src/cipher.rs @@ -76,7 +76,7 @@ fn debug_envelope(envelope: &Envelope) -> String { impl ServiceCipher where S: ProtocolStore + KyberPreKeyStore + SenderKeyStore + Clone, - R: Rng + CryptoRng + Clone, + R: Rng + CryptoRng, { pub fn new( protocol_store: S, diff --git a/libsignal-service/src/sender.rs b/libsignal-service/src/sender.rs index 2f30f41f8..44471a590 100644 --- a/libsignal-service/src/sender.rs +++ b/libsignal-service/src/sender.rs @@ -122,9 +122,9 @@ pub enum MessageSenderError { impl MessageSender where - Service: PushService + Clone, + Service: PushService, S: ProtocolStore + SenderKeyStore + SessionStoreExt + Sync + Clone, - R: Rng + CryptoRng + Clone, + R: Rng + CryptoRng, { #[allow(clippy::too_many_arguments)] pub fn new( From bc9d1f680b7f355764cc309b206d7cf10a26cf6f Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Sat, 9 Mar 2024 18:46:31 +0100 Subject: [PATCH 15/34] generate_prekeys to return Records instead of Entity --- libsignal-service/src/account_manager.rs | 32 +++++++++++++++++------ libsignal-service/src/pre_keys.rs | 30 ++++++++++----------- libsignal-service/src/provisioning/mod.rs | 10 ++++--- 3 files changed, 44 insertions(+), 28 deletions(-) diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index a1cd284c5..01b3cb8e8 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -18,8 +18,8 @@ use zkgroup::profiles::ProfileKey; use crate::content::ContentBody; use crate::pre_keys::{ - KyberPreKeyEntity, PreKeysStore, SignedPreKeyEntity, PRE_KEY_BATCH_SIZE, - PRE_KEY_MINIMUM, + KyberPreKeyEntity, PreKeyEntity, PreKeysStore, SignedPreKeyEntity, + PRE_KEY_BATCH_SIZE, PRE_KEY_MINIMUM, }; use crate::prelude::{MessageSender, MessageSenderError}; use crate::proto::sync_message::PniChangeNumber; @@ -168,10 +168,21 @@ impl AccountManager { Some(KyberPreKeyEntity::try_from(last_resort_keys[0].clone())?) } else { pq_last_resort_key + .map(KyberPreKeyEntity::try_from) + .transpose()? }; let identity_key = *identity_key_pair.identity_key().public_key(); + let pre_keys = pre_keys + .into_iter() + .map(PreKeyEntity::try_from) + .collect::>()?; + let signed_pre_key = signed_pre_key.try_into()?; + let pq_pre_keys = pq_pre_keys + .into_iter() + .map(KyberPreKeyEntity::try_from) + .collect::>()?; let pre_key_state = PreKeyState { pre_keys, signed_pre_key, @@ -559,8 +570,8 @@ impl AccountManager { } let ( _pre_keys, - signed_pre_key_entity, - _kyber_pre_key_entities, + signed_pre_key, + _kyber_pre_keys, last_resort_kyber_prekey, ) = crate::pre_keys::generate_pre_keys( pni_protocol_store, @@ -574,17 +585,22 @@ impl AccountManager { let registration_id = generate_registration_id(csprng); let local_device_id_s = local_device_id.to_string(); - device_pni_signed_prekeys - .insert(local_device_id_s.clone(), signed_pre_key_entity); + device_pni_signed_prekeys.insert( + local_device_id_s.clone(), + SignedPreKeyEntity::try_from(signed_pre_key)?, + ); device_pni_last_resort_kyber_prekeys.insert( local_device_id_s.clone(), - last_resort_kyber_prekey.expect("requested last resort key"), + KyberPreKeyEntity::try_from( + last_resort_kyber_prekey + .expect("requested last resort key"), + )?, ); pni_registration_ids .insert(local_device_id_s.clone(), registration_id); assert!(_pre_keys.is_empty()); - assert!(_kyber_pre_key_entities.is_empty()); + assert!(_kyber_pre_keys.is_empty()); if local_device_id == DEFAULT_DEVICE_ID { // This is the primary device diff --git a/libsignal-service/src/pre_keys.rs b/libsignal-service/src/pre_keys.rs index 901aa7496..bf8d3cfff 100644 --- a/libsignal-service/src/pre_keys.rs +++ b/libsignal-service/src/pre_keys.rs @@ -176,10 +176,10 @@ pub(crate) async fn generate_pre_keys< kyber_pre_key_count: u32, ) -> Result< ( - Vec, - SignedPreKeyEntity, - Vec, - Option, + Vec, + SignedPreKeyRecord, + Vec, + Option, ), SignalProtocolError, > { @@ -190,8 +190,8 @@ pub(crate) async fn generate_pre_keys< let span = tracing::span!(tracing::Level::DEBUG, "Generating pre keys"); - let mut pre_key_entities = vec![]; - let mut pq_pre_key_entities = vec![]; + let mut pre_keys = vec![]; + let mut pq_pre_keys = vec![]; // EC keys for i in 0..pre_key_count { @@ -207,7 +207,7 @@ pub(crate) async fn generate_pre_keys< // I think we might want to update the storage, and then sync the storage to the // server. - pre_key_entities.push(PreKeyEntity::try_from(pre_key_record)?); + pre_keys.push(pre_key_record); } // Kyber keys @@ -228,7 +228,7 @@ pub(crate) async fn generate_pre_keys< // I think we might want to update the storage, and then sync the storage to the // server. - pq_pre_key_entities.push(KyberPreKeyEntity::try_from(pre_key_record)?); + pq_pre_keys.push(pre_key_record); } // Generate and store the next signed prekey @@ -255,8 +255,6 @@ pub(crate) async fn generate_pre_keys< &signed_prekey_record, ) .instrument(tracing::trace_span!(parent: &span, "save signed pre key", signed_pre_key_id = ?next_signed_pre_key_id)).await?; - let signed_prekey_entity = - SignedPreKeyEntity::try_from(signed_prekey_record)?; let pq_last_resort_key = if use_last_resort_key { let pre_key_id = (((pq_pre_keys_offset_id + kyber_pre_key_count) @@ -264,9 +262,9 @@ pub(crate) async fn generate_pre_keys< + 1) .into(); - if !pq_pre_key_entities.is_empty() { + if !pq_pre_keys.is_empty() { assert_eq!( - pq_pre_key_entities.last().unwrap().key_id + 1, + u32::from(pq_pre_keys.last().unwrap().id()?) + 1, u32::from(pre_key_id) ); } @@ -283,15 +281,15 @@ pub(crate) async fn generate_pre_keys< // I think we might want to update the storage, and then sync the storage to the // server. - Some(KyberPreKeyEntity::try_from(pre_key_record)?) + Some(pre_key_record) } else { None }; Ok(( - pre_key_entities, - signed_prekey_entity, - pq_pre_key_entities, + pre_keys, + signed_prekey_record, + pq_pre_keys, pq_last_resort_key, )) } diff --git a/libsignal-service/src/provisioning/mod.rs b/libsignal-service/src/provisioning/mod.rs index 2c6006a02..f6345b2c6 100644 --- a/libsignal-service/src/provisioning/mod.rs +++ b/libsignal-service/src/provisioning/mod.rs @@ -273,10 +273,12 @@ pub async fn link_device< capabilities: LinkCapabilities { pni: true }, name: encrypted_device_name, }, - aci_signed_pre_key, - pni_signed_pre_key, - aci_pq_last_resort_pre_key, - pni_pq_last_resort_pre_key, + aci_signed_pre_key: aci_signed_pre_key.try_into()?, + pni_signed_pre_key: pni_signed_pre_key.try_into()?, + aci_pq_last_resort_pre_key: aci_pq_last_resort_pre_key + .try_into()?, + pni_pq_last_resort_pre_key: pni_pq_last_resort_pre_key + .try_into()?, }; let LinkResponse { From ba774e725b14081ee2eb722d762b56a731e3d68f Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Sat, 9 Mar 2024 18:51:39 +0100 Subject: [PATCH 16/34] PreKeyEntity TryFrom based on ref instead of move --- libsignal-service/src/pre_keys.rs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/libsignal-service/src/pre_keys.rs b/libsignal-service/src/pre_keys.rs index bf8d3cfff..a8cb02a47 100644 --- a/libsignal-service/src/pre_keys.rs +++ b/libsignal-service/src/pre_keys.rs @@ -114,10 +114,10 @@ pub struct SignedPreKeyEntity { pub signature: Vec, } -impl TryFrom for SignedPreKeyEntity { +impl TryFrom<&'_ SignedPreKeyRecord> for SignedPreKeyEntity { type Error = SignalProtocolError; - fn try_from(key: SignedPreKeyRecord) -> Result { + fn try_from(key: &'_ SignedPreKeyRecord) -> Result { Ok(SignedPreKeyEntity { key_id: key.id()?.into(), public_key: key.key_pair()?.public_key.serialize().to_vec(), @@ -126,6 +126,14 @@ impl TryFrom for SignedPreKeyEntity { } } +impl TryFrom for SignedPreKeyEntity { + type Error = SignalProtocolError; + + fn try_from(key: SignedPreKeyRecord) -> Result { + SignedPreKeyEntity::try_from(&key) + } +} + #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KyberPreKeyEntity { @@ -136,10 +144,10 @@ pub struct KyberPreKeyEntity { pub signature: Vec, } -impl TryFrom for KyberPreKeyEntity { +impl TryFrom<&'_ KyberPreKeyRecord> for KyberPreKeyEntity { type Error = SignalProtocolError; - fn try_from(key: KyberPreKeyRecord) -> Result { + fn try_from(key: &'_ KyberPreKeyRecord) -> Result { Ok(KyberPreKeyEntity { key_id: key.id()?.into(), public_key: key.key_pair()?.public_key.serialize().to_vec(), @@ -148,6 +156,14 @@ impl TryFrom for KyberPreKeyEntity { } } +impl TryFrom for KyberPreKeyEntity { + type Error = SignalProtocolError; + + fn try_from(key: KyberPreKeyRecord) -> Result { + KyberPreKeyEntity::try_from(&key) + } +} + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct PreKeyState { From 68894e518ae0686278a1eabd8ff681d81c35ebeb Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Sat, 9 Mar 2024 18:52:22 +0100 Subject: [PATCH 17/34] Distribution message --- libsignal-service/src/account_manager.rs | 27 +++++++++++++++++------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index 01b3cb8e8..fe23d88f2 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -1,4 +1,5 @@ use base64::prelude::*; +use phonenumber::PhoneNumber; use std::collections::HashMap; use std::convert::{TryFrom, TryInto}; use std::time::SystemTime; @@ -7,8 +8,9 @@ use aes::cipher::{KeyIvInit, StreamCipher as _}; use hmac::digest::Output; use hmac::{Hmac, Mac}; use libsignal_protocol::{ - IdentityKey, IdentityKeyStore, KeyPair, PrivateKey, ProtocolStore, - PublicKey, SenderKeyStore, SignalProtocolError, + kem, GenericSignedPreKey, IdentityKey, IdentityKeyStore, KeyPair, + KyberPreKeyRecord, PrivateKey, ProtocolStore, PublicKey, SenderKeyStore, + SignalProtocolError, SignedPreKeyRecord, }; use prost::Message; use serde::{Deserialize, Serialize}; @@ -517,8 +519,9 @@ impl AccountManager { &mut self, aci_protocol_store: &mut Aci, pni_protocol_store: &mut Pni, - sender: MessageSender, + mut sender: MessageSender, local_aci: ServiceAddress, + e164: PhoneNumber, csprng: &mut R, ) -> Result<(), MessageSenderError> { let pni_identity_key_pair = @@ -587,12 +590,13 @@ impl AccountManager { let local_device_id_s = local_device_id.to_string(); device_pni_signed_prekeys.insert( local_device_id_s.clone(), - SignedPreKeyEntity::try_from(signed_pre_key)?, + SignedPreKeyEntity::try_from(&signed_pre_key)?, ); device_pni_last_resort_kyber_prekeys.insert( local_device_id_s.clone(), KyberPreKeyEntity::try_from( last_resort_kyber_prekey + .as_ref() .expect("requested last resort key"), )?, ); @@ -613,11 +617,18 @@ impl AccountManager { identity_key_pair: Some( pni_identity_key_pair.serialize().to_vec(), ), - signed_pre_key: todo!(), - last_resort_kyber_pre_key: todo!(), - registration_id: todo!(), - new_e164: todo!(), + signed_pre_key: Some(signed_pre_key.serialize()?), + last_resort_kyber_pre_key: Some( + last_resort_kyber_prekey + .expect("requested last resort key") + .serialize()?, + ), + registration_id: Some(registration_id), + new_e164: Some( + e164.format().mode(phonenumber::Mode::E164).to_string(), + ), }), + padding: Some(vec![/* TODO random-length max 512 bytes */]), ..SyncMessage::default() }; let content: ContentBody = msg.into(); From e87df732b5f2bc496dbabbe0681c5974017eb5fc Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Sat, 9 Mar 2024 18:54:07 +0100 Subject: [PATCH 18/34] Implement random padding --- libsignal-service/src/account_manager.rs | 4 ++-- libsignal-service/src/utils.rs | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index fe23d88f2..f34bfce2f 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -30,7 +30,7 @@ use crate::provisioning::generate_registration_id; use crate::push_service::{AvatarWrite, RecaptchaAttributes, ServiceIdType}; use crate::sender::OutgoingPushMessage; use crate::session_store::SessionStoreExt; -use crate::utils::BASE64_RELAXED; +use crate::utils::{random_length_padding, BASE64_RELAXED}; use crate::ServiceAddress; use crate::{ configuration::{Endpoint, ServiceCredentials}, @@ -628,7 +628,7 @@ impl AccountManager { e164.format().mode(phonenumber::Mode::E164).to_string(), ), }), - padding: Some(vec![/* TODO random-length max 512 bytes */]), + padding: Some(random_length_padding(csprng, 512)), ..SyncMessage::default() }; let content: ContentBody = msg.into(); diff --git a/libsignal-service/src/utils.rs b/libsignal-service/src/utils.rs index d6094c652..00f6242b6 100644 --- a/libsignal-service/src/utils.rs +++ b/libsignal-service/src/utils.rs @@ -11,6 +11,16 @@ pub const BASE64_RELAXED: base64::engine::GeneralPurpose = ), ); +pub fn random_length_padding( + csprng: &mut R, + max_len: usize, +) -> Vec { + let length = csprng.gen_range(0..max_len); + let mut padding = vec![0u8; length]; + csprng.fill_bytes(&mut padding); + padding +} + pub mod serde_base64 { use super::BASE64_RELAXED; use base64::prelude::*; From afb41ec7c0c7de1d80907e5844eacc7a1d97951e Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Sat, 9 Mar 2024 18:56:12 +0100 Subject: [PATCH 19/34] Some asserts for pre key gen --- libsignal-service/src/provisioning/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libsignal-service/src/provisioning/mod.rs b/libsignal-service/src/provisioning/mod.rs index f6345b2c6..7bc8b927d 100644 --- a/libsignal-service/src/provisioning/mod.rs +++ b/libsignal-service/src/provisioning/mod.rs @@ -234,6 +234,8 @@ pub async fn link_device< .await?; let aci_pq_last_resort_pre_key = aci_pq_last_resort_pre_key.expect("requested last resort key"); + assert!(_aci_pre_keys.is_empty()); + assert!(_aci_pq_pre_keys.is_empty()); let ( _pni_pre_keys, @@ -251,6 +253,8 @@ pub async fn link_device< .await?; let pni_pq_last_resort_pre_key = pni_pq_last_resort_pre_key.expect("requested last resort key"); + assert!(_pni_pre_keys.is_empty()); + assert!(_pni_pq_pre_keys.is_empty()); let encrypted_device_name = BASE64_RELAXED.encode( encrypt_device_name(csprng, device_name, &aci_public_key)? From 04edb85955a1506ecb220ccae7e1c8a61747287e Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Sat, 9 Mar 2024 19:15:22 +0100 Subject: [PATCH 20/34] More flexible trait bounds on pnp_initialize_devices --- libsignal-service/src/account_manager.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index f34bfce2f..922da26ae 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -508,18 +508,14 @@ impl AccountManager { pub async fn pnp_initialize_devices< // XXX So many constraints here, all imposed by the MessageSender R: rand::Rng + rand::CryptoRng, - Aci: PreKeysStore - + ProtocolStore - + SenderKeyStore - + SessionStoreExt - + Sync - + Clone, + Aci: PreKeysStore + SessionStoreExt, Pni: PreKeysStore, + AciOrPni: ProtocolStore + SenderKeyStore + SessionStoreExt + Sync + Clone, >( &mut self, aci_protocol_store: &mut Aci, pni_protocol_store: &mut Pni, - mut sender: MessageSender, + mut sender: MessageSender, local_aci: ServiceAddress, e164: PhoneNumber, csprng: &mut R, From 3077adade7f77923275c2918628997c2d6b62420 Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Sat, 9 Mar 2024 20:50:12 +0100 Subject: [PATCH 21/34] Implement PNI signatures --- libsignal-service/src/sender.rs | 71 ++++++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 14 deletions(-) diff --git a/libsignal-service/src/sender.rs b/libsignal-service/src/sender.rs index 44471a590..53ad9c699 100644 --- a/libsignal-service/src/sender.rs +++ b/libsignal-service/src/sender.rs @@ -2,8 +2,8 @@ use std::{collections::HashSet, time::SystemTime}; use chrono::prelude::*; use libsignal_protocol::{ - process_prekey_bundle, DeviceId, ProtocolStore, SenderCertificate, - SenderKeyStore, SignalProtocolError, + process_prekey_bundle, DeviceId, IdentityKeyPair, ProtocolStore, + SenderCertificate, SenderKeyStore, SignalProtocolError, }; use rand::{CryptoRng, Rng}; use tracing::{info, trace}; @@ -85,7 +85,10 @@ pub struct MessageSender { cipher: ServiceCipher, csprng: R, protocol_store: S, - local_address: ServiceAddress, + local_aci: ServiceAddress, + local_pni: ServiceAddress, + aci_identity: IdentityKeyPair, + pni_identity: Option, device_id: DeviceId, } @@ -134,7 +137,10 @@ where cipher: ServiceCipher, csprng: R, protocol_store: S, - local_address: ServiceAddress, + local_aci: ServiceAddress, + local_pni: ServiceAddress, + aci_identity: IdentityKeyPair, + pni_identity: Option, device_id: DeviceId, ) -> Self { MessageSender { @@ -144,7 +150,10 @@ where cipher, csprng, protocol_store, - local_address, + local_aci, + local_pni, + aci_identity, + pni_identity, device_id, } } @@ -283,7 +292,7 @@ where async fn is_multi_device(&self) -> bool { if self.device_id == DEFAULT_DEVICE_ID.into() { self.protocol_store - .get_sub_device_sessions(&self.local_address) + .get_sub_device_sessions(&self.local_aci) .await .map_or(false, |s| !s.is_empty()) } else { @@ -302,6 +311,7 @@ where mut unidentified_access: Option, message: impl Into, timestamp: u64, + include_pni_signature: bool, online: bool, ) -> SendMessageResult { let content_body = message.into(); @@ -316,7 +326,7 @@ where }; // don't send anything to self nor session enders to others as sealed sender - if recipient == &self.local_address || end_session { + if recipient == &self.local_aci || end_session { unidentified_access.take(); } @@ -327,6 +337,7 @@ where unidentified_access.as_ref(), &content_body, timestamp, + include_pni_signature, online, ) .await, @@ -347,11 +358,12 @@ where &results, ); self.try_send_message( - self.local_address, + self.local_aci, None, &sync_message, timestamp, false, + false, ) .await?; }, @@ -367,13 +379,18 @@ where } /// Send a message to the recipients in a group. + /// + /// Recipients are a list of tuples, each containing: + /// - The recipient's address + /// - The recipient's unidentified access + /// - Whether the recipient requires a PNI signature #[tracing::instrument( skip(self, recipients, message), fields(recipients = recipients.as_ref().len()), )] pub async fn send_message_to_group( &mut self, - recipients: impl AsRef<[(ServiceAddress, Option)]>, + recipients: impl AsRef<[(ServiceAddress, Option, bool)]>, message: impl Into, timestamp: u64, online: bool, @@ -388,13 +405,16 @@ where let recipients = recipients.as_ref(); let mut needs_sync_in_results = false; - for (recipient, unidentified_access) in recipients.iter() { + for (recipient, unidentified_access, include_pni_signature) in + recipients.iter() + { let result = self .try_send_message( *recipient, unidentified_access.as_ref(), &content_body, timestamp, + *include_pni_signature, online, ) .await; @@ -429,10 +449,11 @@ where let result = self .try_send_message( - self.local_address, + self.local_aci, None, &sync_message, timestamp, + false, // XXX: maybe the sync device does want a PNI signature? false, ) .await; @@ -456,11 +477,15 @@ where mut unidentified_access: Option<&UnidentifiedAccess>, content_body: &ContentBody, timestamp: u64, + include_pni_signature: bool, online: bool, ) -> SendMessageResult { use prost::Message; - let content = content_body.clone().into_proto(); + let mut content = content_body.clone().into_proto(); + if include_pni_signature { + content.pni_signature_message = Some(self.create_pni_signature()?); + } let content_bytes = content.encode_to_vec(); @@ -624,6 +649,7 @@ where unidentified_access, msg, Utc::now().timestamp_millis() as u64, + false, online, ) .await?; @@ -631,6 +657,23 @@ where Ok(()) } + #[tracing::instrument(level = "trace", skip(self))] + fn create_pni_signature( + &mut self, + ) -> Result { + let signature = self + .pni_identity + .expect("PNI key set when PNI signature requested") + .sign_alternate_identity( + self.aci_identity.identity_key(), + &mut self.csprng, + )?; + Ok(crate::proto::PniSignatureMessage { + pni: Some(self.local_pni.uuid.as_bytes().to_vec()), + signature: Some(signature.into()), + }) + } + // Equivalent with `getEncryptedMessages` #[tracing::instrument( level = "trace", @@ -657,7 +700,7 @@ where devices.insert(DEFAULT_DEVICE_ID.into()); // never try to send messages to the sender device - if recipient.aci() == self.local_address.aci() { + if recipient.aci() == self.local_aci.aci() { devices.remove(&self.device_id); } @@ -770,7 +813,7 @@ where }; for pre_key_bundle in pre_keys { - if recipient == &self.local_address + if recipient == &self.local_aci && self.device_id == pre_key_bundle.device_id()? { trace!("not establishing a session with myself!"); From 48bd38372297bd61f52d3074164875ce7dca64e2 Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Mon, 11 Mar 2024 11:34:30 +0100 Subject: [PATCH 22/34] Rename generate_pre_keys to replenish_pre_keys --- libsignal-service/src/account_manager.rs | 71 +++++++++++++++++++---- libsignal-service/src/pre_keys.rs | 2 +- libsignal-service/src/provisioning/mod.rs | 4 +- 3 files changed, 63 insertions(+), 14 deletions(-) diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index 922da26ae..e4c4eb87a 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -151,7 +151,7 @@ impl AccountManager { let has_last_resort_key = !last_resort_keys.is_empty(); let (pre_keys, signed_pre_key, pq_pre_keys, pq_last_resort_key) = - crate::pre_keys::generate_pre_keys( + crate::pre_keys::replenish_pre_keys( protocol_store, &identity_key_pair, csprng, @@ -572,16 +572,65 @@ impl AccountManager { signed_pre_key, _kyber_pre_keys, last_resort_kyber_prekey, - ) = crate::pre_keys::generate_pre_keys( - pni_protocol_store, - &pni_identity_key_pair, - csprng, - true, - 0, - 0, - ) - .await?; - let registration_id = generate_registration_id(csprng); + ) = if local_device_id == DEFAULT_DEVICE_ID { + crate::pre_keys::replenish_pre_keys( + pni_protocol_store, + &pni_identity_key_pair, + csprng, + true, + 0, + 0, + ) + .await? + } else { + // Generate a signed prekey + 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(), + csprng, + )?; + + let unix_time = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap(); + + let signed_prekey_record = SignedPreKeyRecord::new( + csprng.gen_range::(0..0xFFFFFF).into(), + unix_time.as_millis() as u64, + &signed_pre_key_pair, + &signed_pre_key_signature, + ); + + // Generate a last-resort Kyber prekey + let kyber_pre_key_record = KyberPreKeyRecord::generate( + kem::KeyType::Kyber1024, + csprng.gen_range::(0..0xFFFFFF).into(), + pni_identity_key_pair.private_key(), + )?; + ( + vec![], + signed_prekey_record, + vec![], + Some(kyber_pre_key_record), + ) + }; + + let registration_id = if local_device_id == DEFAULT_DEVICE_ID { + pni_protocol_store.get_local_registration_id().await? + } else { + loop { + let regid = generate_registration_id(csprng); + if pni_registration_ids + .iter() + .find(|(_k, v)| **v == regid) + .is_none() + { + break regid; + } + } + }; let local_device_id_s = local_device_id.to_string(); device_pni_signed_prekeys.insert( diff --git a/libsignal-service/src/pre_keys.rs b/libsignal-service/src/pre_keys.rs index a8cb02a47..587ff1ee5 100644 --- a/libsignal-service/src/pre_keys.rs +++ b/libsignal-service/src/pre_keys.rs @@ -180,7 +180,7 @@ 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 generate_pre_keys< +pub(crate) async fn replenish_pre_keys< R: rand::Rng + rand::CryptoRng, P: PreKeysStore, >( diff --git a/libsignal-service/src/provisioning/mod.rs b/libsignal-service/src/provisioning/mod.rs index 7bc8b927d..fc7ad7ec0 100644 --- a/libsignal-service/src/provisioning/mod.rs +++ b/libsignal-service/src/provisioning/mod.rs @@ -223,7 +223,7 @@ pub async fn link_device< aci_signed_pre_key, _aci_pq_pre_keys, aci_pq_last_resort_pre_key, - ) = crate::pre_keys::generate_pre_keys( + ) = crate::pre_keys::replenish_pre_keys( aci_store, &aci_key_pair, csprng, @@ -242,7 +242,7 @@ pub async fn link_device< pni_signed_pre_key, _pni_pq_pre_keys, pni_pq_last_resort_pre_key, - ) = crate::pre_keys::generate_pre_keys( + ) = crate::pre_keys::replenish_pre_keys( pni_store, &pni_key_pair, csprng, From 2b51a3464465e3bb7874f5f16d0ec0a8e4d406ce Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Tue, 12 Mar 2024 12:58:10 +0100 Subject: [PATCH 23/34] Add DeviceActivationRequest to linking and registration --- libsignal-service/src/provisioning/mod.rs | 15 +++++++----- libsignal-service/src/push_service.rs | 29 +++++++++++++++++++++-- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/libsignal-service/src/provisioning/mod.rs b/libsignal-service/src/provisioning/mod.rs index fc7ad7ec0..e3ae330d9 100644 --- a/libsignal-service/src/provisioning/mod.rs +++ b/libsignal-service/src/provisioning/mod.rs @@ -22,6 +22,7 @@ use zkgroup::profiles::ProfileKey; use pipe::{ProvisioningPipe, ProvisioningStep}; use crate::prelude::ServiceError; +use crate::push_service::DeviceActivationRequest; use crate::utils::BASE64_RELAXED; use crate::{ account_manager::encrypt_device_name, @@ -277,12 +278,14 @@ pub async fn link_device< capabilities: LinkCapabilities { pni: true }, name: encrypted_device_name, }, - aci_signed_pre_key: aci_signed_pre_key.try_into()?, - pni_signed_pre_key: pni_signed_pre_key.try_into()?, - aci_pq_last_resort_pre_key: aci_pq_last_resort_pre_key - .try_into()?, - pni_pq_last_resort_pre_key: pni_pq_last_resort_pre_key - .try_into()?, + device_activation_request: DeviceActivationRequest { + aci_signed_pre_key: aci_signed_pre_key.try_into()?, + pni_signed_pre_key: pni_signed_pre_key.try_into()?, + aci_pq_last_resort_pre_key: aci_pq_last_resort_pre_key + .try_into()?, + pni_pq_last_resort_pre_key: pni_pq_last_resort_pre_key + .try_into()?, + }, }; let LinkResponse { diff --git a/libsignal-service/src/push_service.rs b/libsignal-service/src/push_service.rs index 5d4e36703..9ccc5e0d5 100644 --- a/libsignal-service/src/push_service.rs +++ b/libsignal-service/src/push_service.rs @@ -406,6 +406,13 @@ pub struct StaleDevices { pub struct LinkRequest { pub verification_code: String, pub account_attributes: LinkAccountAttributes, + #[serde(flatten)] + pub device_activation_request: DeviceActivationRequest, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DeviceActivationRequest { pub aci_signed_pre_key: SignedPreKeyEntity, pub pni_signed_pre_key: SignedPreKeyEntity, pub aci_pq_last_resort_pre_key: KyberPreKeyEntity, @@ -1282,16 +1289,30 @@ pub trait PushService: MaybeSend { registration_method: RegistrationMethod<'a>, account_attributes: AccountAttributes, skip_device_transfer: bool, + aci_identity_key: IdentityKey, + pni_identity_key: IdentityKey, + device_activation_request: DeviceActivationRequest, ) -> Result { #[derive(serde::Serialize, Debug)] #[serde(rename_all = "camelCase")] struct RegistrationSessionRequestBody<'a> { - // TODO: This is an "old" version of the request. The new one includes atomic - // registration of prekeys and identities, but I'm to lazy to implement them today. + // Unhandled response 422 with body: + // {"errors":["deviceActivationRequest.pniSignedPreKey must not be + // null","deviceActivationRequest.pniPqLastResortPreKey must not be + // null","everySignedKeyValid must be true","aciIdentityKey must not be + // null","pniIdentityKey must not be null","deviceActivationRequest.aciSignedPreKey + // must not be null","deviceActivationRequest.aciPqLastResortPreKey must not be null"]} session_id: Option<&'a str>, recovery_password: Option<&'a str>, account_attributes: AccountAttributes, skip_device_transfer: bool, + every_signed_key_valid: bool, + #[serde(default, with = "serde_base64")] + pni_identity_key: Vec, + #[serde(default, with = "serde_base64")] + aci_identity_key: Vec, + #[serde(flatten)] + device_activation_request: DeviceActivationRequest, } let req = RegistrationSessionRequestBody { @@ -1299,6 +1320,10 @@ pub trait PushService: MaybeSend { recovery_password: registration_method.recovery_password(), account_attributes, skip_device_transfer, + aci_identity_key: aci_identity_key.serialize().into(), + pni_identity_key: pni_identity_key.serialize().into(), + device_activation_request, + every_signed_key_valid: true, }; let res: VerifyAccountResponse = self From e00c5a002650546530878a566400950f2598c8bc Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Tue, 12 Mar 2024 17:10:59 +0100 Subject: [PATCH 24/34] AccountManager::register_account --- libsignal-service/src/account_manager.rs | 87 +++++++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index e4c4eb87a..9ef90c0d2 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -27,7 +27,11 @@ use crate::prelude::{MessageSender, MessageSenderError}; use crate::proto::sync_message::PniChangeNumber; use crate::proto::{DeviceName, SyncMessage}; use crate::provisioning::generate_registration_id; -use crate::push_service::{AvatarWrite, RecaptchaAttributes, ServiceIdType}; +use crate::push_service::{ + AvatarWrite, DeviceActivationRequest, RecaptchaAttributes, + RegistrationMethod, ServiceIdType, VerifyAccountResponse, + DEFAULT_DEVICE_ID, +}; use crate::sender::OutgoingPushMessage; use crate::session_store::SessionStoreExt; use crate::utils::{random_length_padding, BASE64_RELAXED}; @@ -328,6 +332,87 @@ impl AccountManager { Ok(()) } + pub async fn register_account< + R: rand::Rng + rand::CryptoRng, + Aci: PreKeysStore + IdentityKeyStore, + Pni: PreKeysStore + IdentityKeyStore, + >( + &mut self, + csprng: &mut R, + registration_method: RegistrationMethod<'_>, + account_attributes: AccountAttributes, + aci_protocol_store: &mut Aci, + pni_protocol_store: &mut Pni, + skip_device_transfer: bool, + ) -> Result { + let aci_identity_key_pair = aci_protocol_store + .get_identity_key_pair() + .instrument(tracing::trace_span!("get ACI identity key pair")) + .await?; + let pni_identity_key_pair = pni_protocol_store + .get_identity_key_pair() + .instrument(tracing::trace_span!("get PNI identity key pair")) + .await?; + + let ( + _aci_pre_keys, + aci_signed_pre_key, + _aci_kyber_pre_keys, + aci_last_resort_kyber_prekey, + ) = crate::pre_keys::replenish_pre_keys( + aci_protocol_store, + &aci_identity_key_pair, + csprng, + true, + 0, + 0, + ) + .await?; + + let ( + _pni_pre_keys, + pni_signed_pre_key, + _pni_kyber_pre_keys, + pni_last_resort_kyber_prekey, + ) = crate::pre_keys::replenish_pre_keys( + pni_protocol_store, + &pni_identity_key_pair, + csprng, + true, + 0, + 0, + ) + .await?; + + let aci_identity_key = aci_identity_key_pair.identity_key(); + let pni_identity_key = pni_identity_key_pair.identity_key(); + + let dar = DeviceActivationRequest { + aci_signed_pre_key: aci_signed_pre_key.try_into()?, + pni_signed_pre_key: pni_signed_pre_key.try_into()?, + aci_pq_last_resort_pre_key: aci_last_resort_kyber_prekey + .expect("requested last resort prekey") + .try_into()?, + pni_pq_last_resort_pre_key: pni_last_resort_kyber_prekey + .expect("requested last resort prekey") + .try_into()?, + }; + + let result = self + .service + .submit_registration_request( + registration_method, + account_attributes, + skip_device_transfer, + *aci_identity_key, + *pni_identity_key, + dar, + ) + .await?; + + Ok(result) + } + /// Upload a profile /// /// Panics if no `profile_key` was set. From 99c7f2bf9623561cc4bca54bd1721033438144da Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Wed, 13 Mar 2024 09:59:38 +0100 Subject: [PATCH 25/34] Add example store --- libsignal-service/examples/storage.rs | 230 ++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 libsignal-service/examples/storage.rs diff --git a/libsignal-service/examples/storage.rs b/libsignal-service/examples/storage.rs new file mode 100644 index 000000000..8c0dcd3b4 --- /dev/null +++ b/libsignal-service/examples/storage.rs @@ -0,0 +1,230 @@ +use libsignal_service::pre_keys::{PreKeysStore, ServiceKyberPreKeyStore}; +use libsignal_service::protocol::{ + Direction, IdentityKey, IdentityKeyPair, IdentityKeyStore, KyberPreKeyId, + KyberPreKeyRecord, KyberPreKeyStore, PreKeyId, PreKeyRecord, PreKeyStore, + ProtocolAddress, SignalProtocolError, SignedPreKeyId, SignedPreKeyRecord, + SignedPreKeyStore, +}; + +pub struct ExampleStore {} + +impl ExampleStore { + pub fn new() -> Self { + Self {} + } +} + +#[async_trait::async_trait(?Send)] +impl PreKeyStore for ExampleStore { + /// Look up the pre-key corresponding to `prekey_id`. + async fn get_pre_key( + &self, + _prekey_id: PreKeyId, + ) -> Result { + todo!() + } + + /// Set the entry for `prekey_id` to the value of `record`. + async fn save_pre_key( + &mut self, + _prekey_id: PreKeyId, + _record: &PreKeyRecord, + ) -> Result<(), SignalProtocolError> { + todo!() + } + + /// Remove the entry for `prekey_id`. + async fn remove_pre_key( + &mut self, + _prekey_id: PreKeyId, + ) -> Result<(), SignalProtocolError> { + todo!() + } +} + +#[async_trait::async_trait(?Send)] +impl KyberPreKeyStore for ExampleStore { + /// Look up the signed kyber pre-key corresponding to `kyber_prekey_id`. + async fn get_kyber_pre_key( + &self, + _kyber_prekey_id: KyberPreKeyId, + ) -> Result { + todo!() + } + + /// Set the entry for `kyber_prekey_id` to the value of `record`. + async fn save_kyber_pre_key( + &mut self, + _kyber_prekey_id: KyberPreKeyId, + _record: &KyberPreKeyRecord, + ) -> Result<(), SignalProtocolError> { + todo!() + } + + /// Mark the entry for `kyber_prekey_id` as "used". + /// This would mean different things for one-time and last-resort Kyber keys. + async fn mark_kyber_pre_key_used( + &mut self, + _kyber_prekey_id: KyberPreKeyId, + ) -> Result<(), SignalProtocolError> { + todo!() + } +} + +#[async_trait::async_trait(?Send)] +impl SignedPreKeyStore for ExampleStore { + /// Look up the signed pre-key corresponding to `signed_prekey_id`. + async fn get_signed_pre_key( + &self, + _signed_prekey_id: SignedPreKeyId, + ) -> Result { + todo!() + } + + /// Set the entry for `signed_prekey_id` to the value of `record`. + async fn save_signed_pre_key( + &mut self, + _signed_prekey_id: SignedPreKeyId, + _record: &SignedPreKeyRecord, + ) -> Result<(), SignalProtocolError> { + todo!() + } +} + +#[async_trait::async_trait(?Send)] +impl ServiceKyberPreKeyStore for ExampleStore { + async fn store_last_resort_kyber_pre_key( + &mut self, + _kyber_prekey_id: KyberPreKeyId, + _record: &KyberPreKeyRecord, + ) -> Result<(), SignalProtocolError> { + todo!() + } + + async fn load_last_resort_kyber_pre_keys( + &self, + ) -> Result, SignalProtocolError> { + todo!() + } + + async fn remove_kyber_pre_key( + &mut self, + _kyber_prekey_id: KyberPreKeyId, + ) -> Result<(), SignalProtocolError> { + todo!() + } + + /// Analogous to markAllOneTimeKyberPreKeysStaleIfNecessary + async fn mark_all_one_time_kyber_pre_keys_stale_if_necessary( + &mut self, + _stale_time: chrono::DateTime, + ) -> Result<(), SignalProtocolError> { + todo!() + } + + /// Analogue of deleteAllStaleOneTimeKyberPreKeys + async fn delete_all_stale_one_time_kyber_pre_keys( + &mut self, + _threshold: chrono::DateTime, + _min_count: usize, + ) -> Result<(), SignalProtocolError> { + todo!() + } +} + +#[async_trait::async_trait(?Send)] +impl IdentityKeyStore for ExampleStore { + /// Return the single specific identity the store is assumed to represent, with private key. + async fn get_identity_key_pair( + &self, + ) -> Result { + todo!() + } + + /// Return a [u32] specific to this store instance. + /// + /// This local registration id is separate from the per-device identifier used in + /// [ProtocolAddress] and should not change run over run. + /// + /// If the same *device* is unregistered, then registers again, the [ProtocolAddress::device_id] + /// may be the same, but the store registration id returned by this method should + /// be regenerated. + async fn get_local_registration_id( + &self, + ) -> Result { + todo!() + } + + // TODO: make this into an enum instead of a bool! + /// Record an identity into the store. The identity is then considered "trusted". + /// + /// The return value represents whether an existing identity was replaced (`Ok(true)`). If it is + /// new or hasn't changed, the return value should be `Ok(false)`. + async fn save_identity( + &mut self, + _address: &ProtocolAddress, + _identity: &IdentityKey, + ) -> Result { + todo!() + } + + /// Return whether an identity is trusted for the role specified by `direction`. + async fn is_trusted_identity( + &self, + _address: &ProtocolAddress, + _identity: &IdentityKey, + _direction: Direction, + ) -> Result { + todo!() + } + + /// Return the public identity for the given `address`, if known. + async fn get_identity( + &self, + _address: &ProtocolAddress, + ) -> Result, SignalProtocolError> { + todo!() + } +} + +#[async_trait::async_trait(?Send)] +impl PreKeysStore for ExampleStore { + /// ID of the next pre key + async fn next_pre_key_id(&self) -> Result { + todo!() + } + + /// ID of the next signed pre key + async fn next_signed_pre_key_id(&self) -> Result { + todo!() + } + + /// ID of the next PQ pre key + async fn next_pq_pre_key_id(&self) -> Result { + todo!() + } + + /// set the ID of the next pre key + async fn set_next_pre_key_id( + &mut self, + _id: u32, + ) -> Result<(), SignalProtocolError> { + todo!() + } + + /// set the ID of the next signed pre key + async fn set_next_signed_pre_key_id( + &mut self, + _id: u32, + ) -> Result<(), SignalProtocolError> { + todo!() + } + + /// set the ID of the next PQ pre key + async fn set_next_pq_pre_key_id( + &mut self, + _id: u32, + ) -> Result<(), SignalProtocolError> { + todo!() + } +} From 8c1b9b6a67e2c9590029b83b4b3c7c87eefa473f Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Wed, 13 Mar 2024 10:02:13 +0100 Subject: [PATCH 26/34] Fix registration examples --- libsignal-service-actix/Cargo.toml | 1 + .../examples/registering.rs | 18 +++++++++++++++--- libsignal-service-hyper/Cargo.toml | 1 + .../examples/registering.rs | 18 +++++++++++++++--- 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/libsignal-service-actix/Cargo.toml b/libsignal-service-actix/Cargo.toml index 71429a25c..bf5f32b0e 100644 --- a/libsignal-service-actix/Cargo.toml +++ b/libsignal-service-actix/Cargo.toml @@ -33,6 +33,7 @@ async-trait = "0.1" phonenumber = "0.3" [dev-dependencies] +chrono = "0.4" image = { version = "0.23", default-features = false, features = ["png"] } opener = "0.5" qrcode = "0.12" diff --git a/libsignal-service-actix/examples/registering.rs b/libsignal-service-actix/examples/registering.rs index 727e55ad4..c9ce6076d 100644 --- a/libsignal-service-actix/examples/registering.rs +++ b/libsignal-service-actix/examples/registering.rs @@ -25,11 +25,14 @@ use libsignal_service::push_service::{ AccountAttributes, DeviceCapabilities, PushService, RegistrationMethod, VerificationTransport, }; -use libsignal_service::USER_AGENT; +use libsignal_service::{AccountManager, USER_AGENT}; use libsignal_service_actix::prelude::AwcPushService; use rand::RngCore; use structopt::StructOpt; +#[path = "../../libsignal-service/examples/storage.rs"] +mod storage; + #[actix_rt::main] async fn main() -> Result<(), Error> { let client = "libsignal-service-hyper-example"; @@ -136,8 +139,15 @@ async fn main() -> Result<(), Error> { rand::thread_rng().fill_bytes(&mut profile_key); let profile_key = ProfileKey::create(profile_key); let skip_device_transfer = false; - let _registration_data = push_service - .submit_registration_request( + + // Create the prekeys storage + let mut aci_store = storage::ExampleStore::new(); + let mut pni_store = storage::ExampleStore::new(); + + let mut account_manager = AccountManager::new(push_service, None); + let _registration_data = account_manager + .register_account( + &mut rand::thread_rng(), RegistrationMethod::SessionId(&session.id), AccountAttributes { signaling_key: Some(signaling_key.to_vec()), @@ -156,6 +166,8 @@ async fn main() -> Result<(), Error> { name: Some("libsignal-service-hyper test".into()), capabilities: DeviceCapabilities::default(), }, + &mut aci_store, + &mut pni_store, skip_device_transfer, ) .await; diff --git a/libsignal-service-hyper/Cargo.toml b/libsignal-service-hyper/Cargo.toml index 54ad2cb65..560b8f44f 100644 --- a/libsignal-service-hyper/Cargo.toml +++ b/libsignal-service-hyper/Cargo.toml @@ -36,6 +36,7 @@ tokio-rustls = "0.25" rustls-pemfile = "2.0" [dev-dependencies] +chrono = "0.4" rand = "0.8" tokio = { version = "1.0", features = ["rt-multi-thread"] } diff --git a/libsignal-service-hyper/examples/registering.rs b/libsignal-service-hyper/examples/registering.rs index 12623ddf0..7e8355a29 100644 --- a/libsignal-service-hyper/examples/registering.rs +++ b/libsignal-service-hyper/examples/registering.rs @@ -8,12 +8,15 @@ use libsignal_service::push_service::{ AccountAttributes, DeviceCapabilities, PushService, RegistrationMethod, VerificationTransport, }; -use libsignal_service::USER_AGENT; +use libsignal_service::{AccountManager, USER_AGENT}; use libsignal_service_hyper::prelude::HyperPushService; use rand::RngCore; +#[path = "../../libsignal-service/examples/storage.rs"] +mod storage; + #[tokio::main] async fn main() { let client = "libsignal-service-hyper-example"; @@ -104,8 +107,15 @@ async fn main() { rand::thread_rng().fill_bytes(&mut profile_key); let profile_key = ProfileKey::create(profile_key); let skip_device_transfer = false; - let _registration_data = push_service - .submit_registration_request( + + // Create the prekeys storage + let mut aci_store = storage::ExampleStore::new(); + let mut pni_store = storage::ExampleStore::new(); + + let mut account_manager = AccountManager::new(push_service, None); + let _registration_data = account_manager + .register_account( + &mut rand::thread_rng(), RegistrationMethod::SessionId(&session.id), AccountAttributes { signaling_key: Some(signaling_key.to_vec()), @@ -124,6 +134,8 @@ async fn main() { name: Some("libsignal-service-hyper test".into()), capabilities: DeviceCapabilities::default(), }, + &mut aci_store, + &mut pni_store, skip_device_transfer, ) .await; From df600f6fc6d5965f1cbea02da618347866369138 Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Wed, 13 Mar 2024 10:02:31 +0100 Subject: [PATCH 27/34] Fix storage example fn main --- libsignal-service/examples/storage.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libsignal-service/examples/storage.rs b/libsignal-service/examples/storage.rs index 8c0dcd3b4..11945874d 100644 --- a/libsignal-service/examples/storage.rs +++ b/libsignal-service/examples/storage.rs @@ -228,3 +228,8 @@ impl PreKeysStore for ExampleStore { todo!() } } + +#[allow(dead_code)] +fn main() { + let _ = ExampleStore::new(); +} From 55486a565c963d97c985b211a1df1ba003ff12d5 Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Wed, 13 Mar 2024 11:44:37 +0100 Subject: [PATCH 28/34] Don't require signaling key when not needed --- libsignal-service/src/envelope.rs | 4 +++- libsignal-service/src/messagepipe.rs | 5 +---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/libsignal-service/src/envelope.rs b/libsignal-service/src/envelope.rs index 46efe94f7..896f668f4 100644 --- a/libsignal-service/src/envelope.rs +++ b/libsignal-service/src/envelope.rs @@ -30,13 +30,15 @@ impl Envelope { #[tracing::instrument(skip(input, signaling_key), fields(input_size = input.len()))] pub fn decrypt( input: &[u8], - signaling_key: &SignalingKey, + signaling_key: Option<&SignalingKey>, is_signaling_key_encrypted: bool, ) -> Result { if !is_signaling_key_encrypted { tracing::trace!("Envelope::decrypt: not encrypted"); Ok(Envelope::decode(input)?) } else { + let signaling_key = signaling_key + .expect("signaling_key required to decrypt envelopes"); tracing::trace!("Envelope::decrypt: decrypting"); if input.len() < VERSION_LENGTH || input[VERSION_OFFSET] != SUPPORTED_VERSION diff --git a/libsignal-service/src/messagepipe.rs b/libsignal-service/src/messagepipe.rs index 683afe909..cd6e0506a 100644 --- a/libsignal-service/src/messagepipe.rs +++ b/libsignal-service/src/messagepipe.rs @@ -100,10 +100,7 @@ impl MessagePipe { }; Some(Incoming::Envelope(Envelope::decrypt( body, - self.credentials - .signaling_key - .as_ref() - .expect("signaling_key required to decrypt envelopes"), + self.credentials.signaling_key.as_ref(), request.is_signal_key_encrypted(), )?)) } else if request.is_queue_empty() { From ae373b4e84544f8e6d337aba2138e61dc922e1fe Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Wed, 13 Mar 2024 12:13:54 +0100 Subject: [PATCH 29/34] Log pre key upload --- libsignal-service/src/account_manager.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index 9ef90c0d2..962a15d72 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -149,7 +149,7 @@ impl AccountManager { let last_resort_keys = protocol_store .load_last_resort_kyber_pre_keys() - .instrument(tracing::trace_span!("fetch resort key")) + .instrument(tracing::trace_span!("fetch last resort key")) .await?; // XXX: Maybe this check should be done in the generate_pre_keys function? let has_last_resort_key = !last_resort_keys.is_empty(); @@ -180,15 +180,23 @@ impl AccountManager { let identity_key = *identity_key_pair.identity_key().public_key(); - let pre_keys = pre_keys + let pre_keys: Vec<_> = pre_keys .into_iter() .map(PreKeyEntity::try_from) .collect::>()?; let signed_pre_key = signed_pre_key.try_into()?; - let pq_pre_keys = pq_pre_keys + let pq_pre_keys: Vec<_> = pq_pre_keys .into_iter() .map(KyberPreKeyEntity::try_from) .collect::>()?; + + tracing::info!( + "Uploading pre-keys: {} one-time, {} PQ, {} PQ last resort", + pre_keys.len(), + pq_pre_keys.len(), + if pq_last_resort_key.is_some() { 1 } else { 0 } + ); + let pre_key_state = PreKeyState { pre_keys, signed_pre_key, From f92b19d4211f11f369639672affa3e7cf6485bf1 Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Wed, 13 Mar 2024 13:48:20 +0100 Subject: [PATCH 30/34] Rename pnp -> pni field --- libsignal-service/src/push_service.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsignal-service/src/push_service.rs b/libsignal-service/src/push_service.rs index 9ccc5e0d5..e55ee280c 100644 --- a/libsignal-service/src/push_service.rs +++ b/libsignal-service/src/push_service.rs @@ -174,7 +174,7 @@ pub struct DeviceCapabilities { #[serde(default)] pub gift_badges: bool, #[serde(default)] - pub pnp: bool, + pub pni: bool, } #[derive(Debug, Serialize, Deserialize)] From 5a5150c5654b52f776051c4cda85aa063183a589 Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Wed, 13 Mar 2024 13:51:33 +0100 Subject: [PATCH 31/34] Update DeviceCapabilities to be in sync with SA --- libsignal-service/src/push_service.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/libsignal-service/src/push_service.rs b/libsignal-service/src/push_service.rs index e55ee280c..c6f5beb95 100644 --- a/libsignal-service/src/push_service.rs +++ b/libsignal-service/src/push_service.rs @@ -157,17 +157,13 @@ pub struct AccountAttributes { #[derive(Debug, Serialize, Deserialize, Default, Eq, PartialEq, Clone)] #[serde(rename_all = "camelCase")] pub struct DeviceCapabilities { - #[serde(default)] - pub announcement_group: bool, - #[serde(rename(serialize = "gv2-3"), alias = "gv2-3", default)] - pub gv2: bool, #[serde(default)] pub storage: bool, - #[serde(rename = "gv1-migration", default)] - pub gv1_migration: bool, #[serde(default)] pub sender_key: bool, #[serde(default)] + pub announcement_group: bool, + #[serde(default)] pub change_number: bool, #[serde(default)] pub stories: bool, @@ -175,6 +171,8 @@ pub struct DeviceCapabilities { pub gift_badges: bool, #[serde(default)] pub pni: bool, + #[serde(default)] + pub payment_activation: bool, } #[derive(Debug, Serialize, Deserialize)] From 95e859f29d43807f57d2d9bb9283951abcba30b3 Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Wed, 13 Mar 2024 14:34:19 +0100 Subject: [PATCH 32/34] Bump protobufs --- libsignal-service/protobuf/Groups.proto | 4 +-- libsignal-service/protobuf/Provisioning.proto | 3 ++- .../protobuf/SignalService.proto | 27 ++++++++++--------- libsignal-service/protobuf/update-protos.sh | 8 +++--- libsignal-service/src/account_manager.rs | 1 + 5 files changed, 23 insertions(+), 20 deletions(-) diff --git a/libsignal-service/protobuf/Groups.proto b/libsignal-service/protobuf/Groups.proto index bad55e4a1..3f0b7300b 100644 --- a/libsignal-service/protobuf/Groups.proto +++ b/libsignal-service/protobuf/Groups.proto @@ -28,7 +28,7 @@ message Member { bytes userId = 1; Role role = 2; bytes profileKey = 3; - bytes presentation = 4; + bytes presentation = 4; // Only set when sending to server uint32 joinedAtRevision = 5; } @@ -41,7 +41,7 @@ message PendingMember { message RequestingMember { bytes userId = 1; bytes profileKey = 2; - bytes presentation = 3; + bytes presentation = 3; // Only set when sending to server uint64 timestamp = 4; } diff --git a/libsignal-service/protobuf/Provisioning.proto b/libsignal-service/protobuf/Provisioning.proto index 61b97019e..2b9bef426 100644 --- a/libsignal-service/protobuf/Provisioning.proto +++ b/libsignal-service/protobuf/Provisioning.proto @@ -32,7 +32,8 @@ message ProvisionMessage { optional bytes profileKey = 6; optional bool readReceipts = 7; optional uint32 provisioningVersion = 9; - // NEXT ID: 13 + optional bytes masterKey = 13; + // NEXT ID: 14 } enum ProvisioningVersion { diff --git a/libsignal-service/protobuf/SignalService.proto b/libsignal-service/protobuf/SignalService.proto index de9b4ea8d..2de3e6ac7 100644 --- a/libsignal-service/protobuf/SignalService.proto +++ b/libsignal-service/protobuf/SignalService.proto @@ -63,27 +63,22 @@ message CallMessage { } optional uint64 id = 1; - // Legacy/deprecated; replaced by 'opaque' - optional string sdp = 2; + reserved /* sdp */ 2; optional Type type = 3; optional bytes opaque = 4; } message Answer { optional uint64 id = 1; - // Legacy/deprecated; replaced by 'opaque' - optional string sdp = 2; + reserved /* sdp */ 2; optional bytes opaque = 3; } message IceUpdate { optional uint64 id = 1; - // Legacy/deprecated; remove when old clients are gone. - optional string mid = 2; - // Legacy/deprecated; remove when old clients are gone. - optional uint32 line = 3; - // Legacy/deprecated; replaced by 'opaque' - optional string sdp = 4; + reserved /* mid */ 2; + reserved /* line */ 3; + reserved /* sdp */ 4; optional bytes opaque = 5; } @@ -118,7 +113,7 @@ message CallMessage { optional Offer offer = 1; optional Answer answer = 2; repeated IceUpdate iceUpdate = 3; - optional Hangup legacyHangup = 4; + reserved /* legacyHangup */ 4; optional Busy busy = 5; reserved /* profileKey */ 6; optional Hangup hangup = 7; @@ -542,7 +537,9 @@ message SyncMessage { } message Keys { + // @deprecated optional bytes storageService = 1; + optional bytes master = 2; } message MessageRequestResponse { @@ -552,6 +549,8 @@ message SyncMessage { DELETE = 2; BLOCK = 3; BLOCK_AND_DELETE = 4; + SPAM = 5; + BLOCK_AND_SPAM = 6; } reserved /*threadE164*/ 1; @@ -675,7 +674,9 @@ message AttachmentPointer { optional uint32 size = 4; optional bytes thumbnail = 5; optional bytes digest = 6; - optional bytes incrementalDigest = 16; + reserved 16; + reserved 18; + optional bytes incrementalMac = 19; optional uint32 incrementalMacChunkSize = 17; optional string fileName = 7; optional uint32 flags = 8; @@ -685,7 +686,7 @@ message AttachmentPointer { optional string blurHash = 12; optional uint64 uploadTimestamp = 13; optional uint32 cdnNumber = 14; - // Next ID: 18 + // Next ID: 19 } message GroupContext { diff --git a/libsignal-service/protobuf/update-protos.sh b/libsignal-service/protobuf/update-protos.sh index 477d143b2..12d264a1e 100755 --- a/libsignal-service/protobuf/update-protos.sh +++ b/libsignal-service/protobuf/update-protos.sh @@ -4,10 +4,10 @@ set -euo pipefail update_proto() { case "$1" in Signal-Android) - git_revision="0cdd56e0accfe59e39a312f32bb1463f551dee33" - prefix="libsignal/service/src/main/protowire/";; + git_revision="940cee0f30d6a2873ae08c65bb821c34302ccf5d" + prefix="libsignal-service/src/main/protowire/";; Signal-Desktop) - git_revision="0e194975a23669263d053b03a42ac52ad38c5d87" + git_revision="70858d9063446b07b19c03ae7d0c01075a2849e3" prefix="protos/";; esac curl -LOf https://raw.githubusercontent.com/signalapp/${1}/${git_revision}/${prefix}${2} @@ -20,4 +20,4 @@ update_proto Signal-Android StickerResources.proto update_proto Signal-Android WebSocketResources.proto update_proto Signal-Desktop DeviceName.proto -update_proto Signal-Desktop UnidentifiedDelivery.proto \ No newline at end of file +update_proto Signal-Desktop UnidentifiedDelivery.proto diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index 962a15d72..51cca2d12 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -330,6 +330,7 @@ impl AccountManager { provisioning_code: Some(provisioning_code), read_receipts: None, user_agent: None, + master_key: None, // XXX }; let cipher = ProvisioningCipher::from_public(pub_key); From 05b4fcc981c5c149709999463c262609767b1a48 Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Wed, 13 Mar 2024 17:09:52 +0100 Subject: [PATCH 33/34] Allow force-refreshing prekeys --- libsignal-service/src/account_manager.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/libsignal-service/src/account_manager.rs b/libsignal-service/src/account_manager.rs index 51cca2d12..f379975f5 100644 --- a/libsignal-service/src/account_manager.rs +++ b/libsignal-service/src/account_manager.rs @@ -111,6 +111,7 @@ impl AccountManager { service_id_type: ServiceIdType, csprng: &mut R, use_last_resort_key: bool, + force: bool, ) -> Result<(), ServiceError> { let prekey_status = match self .service @@ -135,11 +136,18 @@ impl AccountManager { }; tracing::trace!("Remaining pre-keys on server: {:?}", prekey_status); + // XXX We should honestly compare the pre-key count with the number of pre-keys we have + // locally. If we have more than the server, we should upload them. + // Currently the trait doesn't allow us to do that, so we just upload the batch size and + // pray. if prekey_status.count >= PRE_KEY_MINIMUM && prekey_status.pq_count >= PRE_KEY_MINIMUM { - tracing::info!("Available keys sufficient"); - return Ok(()); + if !force { + tracing::debug!("Available keys sufficient"); + return Ok(()); + } + tracing::info!("Available keys sufficient; forcing refresh."); } let identity_key_pair = protocol_store From 4255520c2bb301b1428c71160625f333f12b9755 Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Fri, 29 Mar 2024 12:59:29 +0100 Subject: [PATCH 34/34] Fix envelope decryption test --- libsignal-service/src/envelope.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libsignal-service/src/envelope.rs b/libsignal-service/src/envelope.rs index 896f668f4..a68aa6edd 100644 --- a/libsignal-service/src/envelope.rs +++ b/libsignal-service/src/envelope.rs @@ -220,7 +220,8 @@ mod tests { ]; let signaling_key = [0u8; 52]; - let envelope = Envelope::decrypt(&body, &signaling_key, true).unwrap(); + let envelope = + Envelope::decrypt(&body, Some(&signaling_key), true).unwrap(); assert_eq!(envelope.server_timestamp(), 1594373582421); assert_eq!(envelope.timestamp(), 1594373580977); assert_eq!(